この記事は後半です。前半は Qiita にあります: memory_limit を越えた瞬間、PHP は何を取り残すのか——PHP2Shell が露わにした「起動前侵入」

画像: daubois.dev より

画像: thephp.foundation より
まず押さえるべきなのは、memory_limit に触れた瞬間に残るのは、単なるメモリ断片ではないことだ。GitHub の advisory が示しているのは、リクエスト用の内部データがまだ完全に初期化されていない段階で OOM が起きると、その途中状態が「無効なまま残る」という筋書きだ。そこから request shutdown が走ると、PHP はその壊れた状態を見にいく。つまり残るのは、値そのものというより「未完成のまま公開された状態」だ。
もう一方の CVE-2026-109619 で残るのは、前のリクエストに属していた state だ。error、session、object、extension などのグローバル状態が、解放済みのメモリを指したままワーカーに残る。次のリクエストが初期化より先にそこへ触れれば、すでに free 済みのものを valid なものとして扱ってしまう。ここでは「何が残ったか」より、「何が reset されずに次へ渡ったか」が本質になる。

画像: www.wiz.io より
修正の中心は、OOM 時の longjmp() をやめたことだ。以前は emalloc() の途中で限界に触れると、その場から抜けて request を中断していた。だがこの抜け方だと、呼び出し元の C 関数が持っていた構造体やポインタが、整合性を保てないまま残りやすい。そこでパッチは、即座に跳ぶ代わりに、次の VM の安全な地点で遅延した soft OOM を出す方式に変えた。構造体を途中半端にしたまま離脱しない、という設計に寄せたわけだ。
加えて、より高い閾値に達した場合は hard OOM として _exit() で落とす。これは「壊れたまま続ける」より「その worker を止める」方を優先した形だ。PHP Foundation の説明でも、更新が唯一の完全な解決策であり、補助的な緩和策は攻撃経路を狭めるだけだとされている。ここは大事で、memory_limit=-1 は安全化の魔法ではない。予測しやすい per-request の失敗を外すことで、確認済みの exploit path を潰しやすくするだけだ。基盤の allocation failure 自体は残りうる。
実運用での使いどころはかなり現実的だ。PHP の公式告知では、まず更新。それができないなら、OOM 系には memory_limit=-1 を管理側で固定し、アプリ側が覆せないようにする。さらに post_max_size を抑え、必要なら upload のサイズも別に絞る。フロントエンドで decoded request size、field 数、multipart part 数、header 数、ネスト深さまで制限する、という順番だ。PHP の内部で頑張るより、入口で削る方が筋がいい。
ただし、ここにも限界がある。multipart を使わないアプリなら、そもそも multipart/form-data をフロントで拒否するのが強い。逆に upload や import を受けるアプリでは、単に「サイズを下げればよい」では済まない。OOM の起点はアプリ固有の処理にもありうるし、fatal error や timeout が producer になって stale state を生むこともある。session.upload_progress.enabled=0 は一部の経路を塞ぐが、別の consumer は残る。fatal_error_backtraces=0 も同じで、確認された backtrace 経路だけを止めるにすぎない。
自分なら、まずは更新を前提にして、暫定策は二段でしか使わない。ひとつは、PHP の worker が触る前に危険な multipart をフロントで落とすこと。もうひとつは、どうしても止められない環境だけ memory_limit=-1 を管理側で固定し、アプリの設定で戻せないようにすることだ。ini_set() や .user.ini に頼るのは避ける。告知が言う通り、そこは初期化後の話で、今回の入口では間に合わない。
PHP2Shell の面白さは、派手な payload そのものではなく、PHP が「普通なら安全だと思っていた失敗」をどう扱っていたかにある。攻撃者はコードを注入する前に、まず失敗の形を選ぶ。だからこの件では、memory_limit は防波堤ではなく、時に踏み台になる。アップデート後も残る教訓は単純だ。エラーを隠すより、初期化前に触らせない。そこを外すと、アプリは一行も動かないまま、先に主導権を失う。