OOM(メモリ不足)によるデータ破壊を悪用したリモートコード実行。

## 概要

PHPは、メモリ不足(OOM)エラーが発生した場合、`longjmp()`関数を使用して現在の処理を中断し、リクエストの処理を中止します。特定の状況下では、リクエストのメモリ領域が完全に初期化される前に一時的にアクセス可能になることがあります。その時点でメモリ割り当てが失敗した場合、PHPは早期の終了を適切に処理できず、リクエストのメモリ領域を無効な状態にしてしまいます。このメモリ割り当ての失敗は、無効なメモリ領域とは直接関係がない場合もあります。その後、リクエストの終了処理がこの無効なメモリ領域を読み込んだり、参照したりする可能性があります。複数のOOM(メモリ不足)対策がこの状態を確実に引き起こし、リモートコード実行(RCE)につながる可能性があります。

## 脆弱性

### OOM(メモリ不足)を引き起こす要因

いくつかのマルチパート解析の問題により、リクエストがPHPの制限内に留まりながらも、メモリ不足状態になることがあります。

- 有効なContent-Lengthヘッダーが宣言されていないマルチパートリクエストは、初期の`post_max_size`チェックを通過し、デコードされたデータが継続的に供給されることがあります。
- マルチパートの各パートには、制限のない数のヘッダーを含めることができます。これらのヘッダーをPHP内で表現すると、ネットワーク上での表現よりもはるかに多くのメモリを消費する可能性があり、`post_max_size`内のリクエストが通常の`memory_limit`を超えてしまうことがあります。
- 空のファイルコントロールは`max_file_uploads`にはカウントされませんが、通常のファイルメタデータ配列を作成します。ネストされたフィールド名によって、生成される配列の処理が増加します。

攻撃者が制御する入力によって、十分な大きさのメモリ割り当てが行われたり、メモリが繰り返し使用されたりする場合、アプリケーション固有の方法でもOOM状態になることがあります。

### 脆弱性の原因

Apache mod_phpは、リクエストの開始に失敗した場合でも、リクエストの終了処理を無条件に実行します。リクエストの開始時に、自動的なボディ解析が行われ、設定されたメモリ制限を超えてしまい、その結果、リクエストが一時的に無効な状態のまま終了することがあります。その後、終了処理がこの無効な状態を処理します。これは、アプリケーション固有のコードが実行される前に完全に発生します。

FPMは、リクエストの開始に失敗した場合、子プロセスを終了します。これにより、無効な状態が消費されるのを防ぎ、プロセスが終了します。

プールからプロセスを回収します。通常起動後も、早期の終了を考慮していない状況下では、OOM(メモリ不足)が発生する可能性があります。無効な状態が、リクエストの終了処理中に処理されることがあります。そのため、ソースコードの分析では、FPMが脆弱である可能性が示唆されていますが、このSAPIに対する完全なFPMの脆弱性を悪用する事例は確認されていません。

ソースコードの分析によると、Apache mod_phpに類似したFrankenPHPは、リクエストの終了処理を無条件に呼び出すため、アプリケーションに依存しない攻撃に対して脆弱である可能性があります。LSAPIはFPMと同様のモデルであり、アプリケーション固有の脆弱性が存在する可能性はありますが、検証されていません。

## 対策
この修正プログラムは、OOMが発生した場合の`longjmp()`を削除し、早期の終了によって適切に処理されないコンテキストからの抜け出しを防ぎます。代わりに、次のVMの安全なポイントで発生する、より穏やかなソフトOOMと、メモリ消費量が2つ目の、より高い閾値に達した場合に発生する、即時のハードOOMを使用します。これにより、終了処理で無効な状態が表示されることはなくなり、PHPが通常持っているメモリ消費量を制限する機能も維持されます。

> [警告]
> この脆弱性に対する修正プログラムが含まれるPHPのバージョンに更新することが、唯一の完全な対策です。

更新できない環境では、管理者が`memory_limit`を`-1`に設定し、それを強制することが、主な設定による対策となります。これにより、確認された脆弱性を悪用する、制御可能なOOMを発生させる、予測可能なリクエストごとの制限がなくなります。ただし、これは完全な対策ではありません。`mmap()`やその他の基盤となるメモリ割り当てが失敗した場合、PHPは依然として`longjmp()`を使用する可能性があり、未完了のリクエストのメモリがアクセス可能になる可能性があります。すべてのPHPワーカーを再起動し、どのプール、ディレクトリ、またはアプリケーションの設定でも、PHPのメモリ制限が有限の値に設定されていないことを確認してください。

`post_max_size`を控えめに設定し、フロントエンドでデコードされたリクエストのサイズを個別に制限してください。`max_input_nesting_level`を減らし、入力変数、マルチパートのパーツ数、各パーツ内のヘッダー数、およびアップロード数を制限してください。これらの制御は、修正プログラムを適用した後でも、ハードOOMやサービス拒否のリスクを軽減します。

ただし、アップデートは行わないでください。アプリケーションがmultipart形式のデータを送信しない場合、PHPに到達する前に、フロントエンドでそのようなリクエストをブロックしてください。

コンテナまたはcgroupのメモリ制限(`memory.max`)は、PHP自身の`memory_limit`の代替となりえますが、いくつかの制限があります。メモリ割り当ての失敗を完全に防ぐことは保証されず、完全な対策にはなりません。例えば、コンテナまたはcgroupを停止すると、マスタープロセスとすべてのワーカープロセスも停止する可能性があります。ただし、各ワーカープロセスが独自のグループを持つ場合は、この影響は軽減されます。FPMのチャイルドプロセスを停止しても、そのプロセスが処理中のリクエストのみが影響を受け、FPMのマスタープロセスがそれを迅速に置き換えます。`RLIMIT_AS`または`RLIMIT_DATA`は使用しないでください。これらは、メモリ制限に達すると`mmap()`が失敗し、脆弱な`longjmp()`の実行経路がトリガーされる原因となります。

**重要**: 関連するレポートからの修正に関するアドバイスも必ず確認してください。
https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp

## 影響と信頼性

確認された影響は、Webワーカーアカウントとして認証されていないリモートコード実行です。デフォルトのPHPの入力とメモリ制限、および標準的なprefork構成で複数のワーカーを使用した場合、HTTP経由で完全なリモートコード実行が検証されました。追加のワーカーや関連性のないトラフィックは、必要な試行回数を増やす可能性がありますが、根本的な問題を解消するものではありません。エクスプロイトの移植性と信頼性は、PHPのビルド、拡張機能、フロントエンドの動作、およびアロケータのレイアウトに依存します。

影響を受けるパッケージ:
- php php-src: 8.2.35より前のバージョンが影響を受けます。8.2.35で修正されました。
- php php-src: 8.3.36より前のバージョンが影響を受けます。8.3.36で修正されました。
- php php-src: 8.4.27より前のバージョンが影響を受けます。8.4.27で修正されました。
- php php-src: 8.5.12より前のバージョンが影響を受けます。8.5.12で修正されました。