[PHP-dev 1449] max_execution_time を設定すると Apache が SIGABRT で終了する

Hoshizuki Yusuke hossy421 @ yahoo.co.jp
2009年 3月 13日 (金) 18:19:55 JST


はじめまして。
今回初めてこちらの ML に登録させていただきました。
星月優佑と申します。

php5 の mod_php におきまして、件名のとおりのバグを発見し、
ソースコードを追いかけて原因も判明しましたので、
こちらで報告させていただきたいと思います。


1. 環境

当方の環境は以下の通りです。

OS: FreeBSD 6.3R ( in jail )
HTTPd: Apache 2.2.11
PHP: PHP5 5.2.8

Apache も PHP も ports からインストールしたもので、
以下すべてこのバージョンについて書いてあります。

他のバージョンに関しては検証していませんが、
おそらく OS や Apache のバージョンには関係しないものと考えられます。


2. 症状

httpd-error.log に以下のようなメッセージを残して httpd が死にます。

> httpd in free(): error: recursive call
> [notice] child pid XXXXX exit signal Abort trap (6)

XXXXX の部分は httpd の pid で、毎回異なる番号となります。


3. 原因

php.ini にて max_execution_time の設定が有効になっていると、
Zend/zend_execute_API.c にて

> setitimer(ITIMER_PROF, &t_r, NULL);
> signal(SIGPROF, zend_timeout);

このように、指定時間後に SIGPROF が飛んでくるよう設定されます。

このシグナルハンドラ、zend_timeout() は Zend/zend.c にて

> /* The error may not be safe to handle in user-space */
> zend_error_cb(type, error_filename, error_lineno, format, args);

このようにコールバック関数を呼ぶようになっていますが、
デバッガで追いかけると main/main.c の php_error_cb() を指していました。
ここでは

> buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, args);

> efree(buffer);

このように、malloc や free などが呼ばれています。


何もないところで SIGPROF が飛んでこればいいのですが、
malloc(), free() の関数内を実行中に SIGPROF が飛んでくると、
先ほどのログにあるように free() の再帰呼び出しとなってしまい、
それを検出した libc が abort() を実行しているのが原因です。

そもそも、原則としてシグナルハンドラ中はアトミックな操作しか
行ってはいけないはずですが、それが破られているのが原因だと考えられます。


4. 解決(回避)方法

いくつか考えられるものがあります。

4.1. max_execution_time を 0 ( 無効 ) にする

おそらく最も順当な手法と思いますが、スクリプトが暴走した場合に
サーバ自体が停止するので怖くて試せませんでした。

4.2. zend_timeout() で何も言わずに即死させるパッチを当てる

つまりはこういうことです。

void zend_timeout() { _exit( -2 ); }

exit() では atexit() で指定されたフック関数が呼ばれるので NG。
グローバルリソースの解放などが行われなくなる可能性があるので
怖くて試せませんでした。

4.3. malloc(), free() をフックしてシグナルをブロックする

すごく気持ち悪い方法ですが、LD_PRELOAD を用いて
とりあえず malloc(), free(), realloc() をハックし、
SIGPROF を一時的にブロックするフック関数をかませます。

詳細については私の blog にまとめてありますので参考までに。
ref: http://d.hatena.ne.jp/Hossy/20090222#p1



以上が今回報告させていただくバグの概要です。

一応 4.3. の方法でエラーは発生しなくなりましたが、
いろいろと気持ちの悪い方法な上、そもそもシグナルハンドラ内で
複雑な処理を行いすぎること自体が問題であるため、
きちんと PHP インタプリタの側でデバッグが必要であると思います。


5. 教えていただきたいこと

今回この ML への投稿で教えていただきたいことは以下の2点です。

・もっとスマートな(安全な)回避方法はないでしょうか
・とりあえず日本語ということでこちらの ML に投げましたが、
 もっと適したところがありましたら紹介していただけないでしょうか


以上です。長文失礼いたしました。

*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
 送信者 : 星月 優佑
   Mail : hossy421 @ yahoo.co.jp
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*



PHP-dev メーリングリストの案内