簡単な質問です。複雑な答え!
はい、このクラスの正規表現は、スタック オーバーフローによる未処理のセグメンテーション エラーで Apache/PHP を繰り返し (そして静かに) クラッシュさせます!
バックグラウンド:
PHPpreg_*
ファミリの正規表現関数は、Philip Hazel による強力なPCRE ライブラリを使用します。このライブラリには、内部match()
関数への再帰呼び出しを多数必要とする特定のクラスの正規表現があり、これが大量のスタック スペースを使用します (使用されるスタック スペースは、一致する対象の文字列のサイズに正比例します)。 . したがって、件名の文字列が長すぎると、スタック オーバーフローと対応するセグメンテーション違反が発生します。この動作については、pcrestackというタイトルのセクションの末尾にあるPCRE ドキュメントで説明されています。
PHP バグ 1: PHP セット:pcre.recursion_limit
大きすぎます。
PCRE のドキュメントでは、再帰の深さを、リンクされたアプリケーションのスタック サイズを 500 で割った値にほぼ等しい安全な値に制限することで、スタック オーバーフローのセグメンテーション違反を回避する方法について説明しています。再帰の深さが推奨どおりに適切に制限されている場合、ライブラリはスタック オーバーフローが発生し、代わりにエラー コードで正常に終了します。PHP では、この最大再帰深度はpcre.recursion_limit
構成変数で指定され、(残念ながら) デフォルト値は 100,000 に設定されています。この値は大きすぎます! pcre.recursion_limit
さまざまな実行可能スタック サイズの安全な値の表を次に示します。
Stacksize pcre.recursion_limit
64 MB 134217
32 MB 67108
16 MB 33554
8 MB 16777
4 MB 8388
2 MB 4194
1 MB 2097
512 KB 1048
256 KB 524
httpd.exe
したがって、 256KB の (比較的小さい) スタック サイズを持つApache Web サーバー ( ) の Win32 ビルドの場合、 の正しい値はpcre.recursion_limit
524 に設定する必要があります。これは、次の PHP コード行で実現できます。
ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.
このコードを PHP スクリプトに追加すると、スタック オーバーフローは発生しませんが、意味のあるエラー コードが生成されます。つまり、エラーコードを生成する必要があります。(しかし、残念ながら、別の PHP バグにより、そうでpreg_match()
はありません。)
PHP バグ 2:preg_match()
エラー時に FALSE を返さない。
の PHP ドキュメントにpreg_match()
は、エラー時に FALSE を返すと書かれています。残念ながら、PHP バージョン 5.3.3 以下にはバグ ( #52732 ) があり、エラー時に がpreg_match()
返されません(代わりに、不一致の場合に返される値と同じ値である が返されます)。このバグは、PHP バージョン 5.3.4 で修正されました。FALSE
int(0)
解決:
WAMP 2.0 (PHP 5.3.0 を使用) を引き続き使用すると仮定すると、ソリューションでは上記の両方のバグを考慮する必要があります。これが私がお勧めするものです:
- 安全な値に減らす必要が
pcre.recursion_limit
あります: 524.
preg_match()
以外のものを返すたびに、PCRE エラーを明示的にチェックする必要がありますint(1)
。
preg_match()
が返された場合int(1)
、一致は成功しています。
preg_match()
が返された場合int(0)
、一致が失敗したか、エラーが発生しました。
以下は、再帰制限エラーの原因となるサブジェクト文字列の長さを決定するスクリプトの修正版 (コマンド ラインから実行するように設計されています) です。
<?php
// This test script is designed to be run from the command line.
// It measures the subject string length that results in a
// PREG_RECURSION_LIMIT_ERROR error in the preg_match() function.
echo("Entering TEST.PHP...\n");
// Set and display pcre.recursion_limit. (set to stacksize / 500).
// Under Win32 httpd.exe has a stack = 256KB and 8MB for php.exe.
//ini_set("pcre.recursion_limit", "524"); // Stacksize = 256KB.
ini_set("pcre.recursion_limit", "16777"); // Stacksize = 8MB.
echo(sprintf("PCRE pcre.recursion_limit is set to %s\n",
ini_get("pcre.recursion_limit")));
function parseAPIResults($results){
$pattern = "/\[(.|\n)+\]/";
$resultsArray = preg_match($pattern, $results, $matches);
if ($resultsArray === 1) {
$msg = 'Successful match.';
} else {
// Either an unsuccessful match, or a PCRE error occurred.
$pcre_err = preg_last_error(); // PHP 5.2 and above.
if ($pcre_err === PREG_NO_ERROR) {
$msg = 'Successful non-match.';
} else {
// preg_match error!
switch ($pcre_err) {
case PREG_INTERNAL_ERROR:
$msg = 'PREG_INTERNAL_ERROR';
break;
case PREG_BACKTRACK_LIMIT_ERROR:
$msg = 'PREG_BACKTRACK_LIMIT_ERROR';
break;
case PREG_RECURSION_LIMIT_ERROR:
$msg = 'PREG_RECURSION_LIMIT_ERROR';
break;
case PREG_BAD_UTF8_ERROR:
$msg = 'PREG_BAD_UTF8_ERROR';
break;
case PREG_BAD_UTF8_OFFSET_ERROR:
$msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
break;
default:
$msg = 'Unrecognized PREG error';
break;
}
}
}
return($msg);
}
// Build a matching test string of increasing size.
function buildTestString() {
static $content = "";
$content .= "A";
return '['. $content .']';
}
// Find subject string length that results in error.
for (;;) { // Infinite loop. Break out.
$str = buildTestString();
$msg = parseAPIResults($str);
printf("Length =%10d\r", strlen($str));
if ($msg !== 'Successful match.') break;
}
echo(sprintf("\nPCRE_ERROR = \"%s\" at subject string length = %d\n",
$msg, strlen($str)));
echo("Exiting TEST.PHP...");
?>
このスクリプトを実行すると、対象の文字列の現在の長さが継続的に読み取られます。をデフォルト値pcre.recursion_limit
が高すぎるままにしておくと、実行可能ファイルがクラッシュする原因となる文字列の長さを測定できます。
コメント:
- この質問への回答を調べる前に、PCRE ライブラリでエラーが発生したとき
preg_match()
に返されないPHP のバグを知りませんでした。このバグは確かに、 !FALSE
を使用する多くのコードに疑問を投げかけています。preg_match
(もちろん、私自身の PHP コードの目録を作成するつもりです。)
- Windows では、Apache Web サーバー実行可能ファイル (
httpd.exe
) は 256KB のスタックサイズでビルドされます。PHP コマンド ライン実行可能ファイル ( php.exe
) は、8 MB のスタック サイズでビルドされます。の安全な値はpcre.recursion_limit
、スクリプトが実行されている実行可能ファイルに従って設定する必要があります (それぞれ 524 と 16777)。
- *nix システムでは、Apache Web サーバーとコマンド ライン実行可能ファイルの両方が通常 8 MB のスタック サイズでビルドされるため、この問題はそれほど頻繁には発生しません。
- PHP 開発者は、のデフォルト値を
pcre.recursion_limit
安全な値に設定する必要があります。
- PHP 開発者は、
preg_match()
バグ修正を PHP バージョン 5.2 に適用する必要があります。
- CFF Explorerフリーウェア プログラムを使用して、Windows 実行可能ファイルのスタックサイズを手動で変更できます。このプログラムを使用して、Apache
httpd.exe
実行可能ファイルのスタックサイズを増やすことができます。(これは XP で動作しますが、Vista と Win7 では問題が発生する可能性があります。)