| « 0DAY Firefox Remote Code Execution and Denial of Service Vulnerability | Windows Vista のUser Acount Protection(UAP)機能 » |
PHPの問題? BugTrackのレポート - Multiple PHP4/PHP5 vulnerabilities
Link: http://www.infigo.hr/en/in_focus/advisories/INFIGO-2006-04-02
このアドバイザリのメールの日付は何故か「2005/11/13」となっているのですが4/24のメールの様です。結論から言うとこのアドバイザリは通常は「無視してOK」です。
メールではメモリ消費のPoCとして以下のコードを例示しています。
i. wordwrap()
------
<?
$a = str_repeat ("A",438013);
$b = str_repeat ("B",951140);
wordwrap ($a,0,$b,0);
?>
------ii. array_fill()
------
<array_fill (1,123456789,"Infigo-IS");?>
------iii. substr_compare()
------
<?substr_compare ("A","A",12345678);?%gt;
------
まず1つ目ですが日本語環境でwordwrapを使っているケースは無いでしょう。更にプログラマが長大な分割文字(ラップした時の区切り文字)を指定する事はあり得ません。攻撃される事はまず無いでしょう。とは言っても典型的な整数オーバーフロー問題がある事は確かです。しかし、リスクがMediumとされていますがLowが適切でしょう。
【追記】古いwordwrapには整数オーバーフローがあったため修正されていました。まだ整数オーバーフローがあるのはおかしい、と思いPHP 5.1.2のソースを確認しました。少なくともPHP 5.1.2ではオーバーフローは発生せず整数オーバーフローが発生した場合、エラーが発生するコードになっています。と思っていたら、間違ってCVSのパッチ適用済みPHP5.1.2版のソースを見ていたようです。PHP5.1.2にも整数オーバーフローがあります。PHP 4.4.2も整数オーバーフローの影響を受けます。しかし、既に書いたとおり脆弱性自体の危険性は非常に低いです。同じようなバグが過去にレポートされていて直されていたと思うのですがまだ残っていたようです。
【追記2】substr_compare("A","A",12345678) ですがメモリ参照の問題でSegfaultします。バグですがこの手の問題は普通DoSとは言いませんね... PHP 5.1.3では修正されます。 "The start position cannot exceed initial string length."とエラーになります。このエラーメッセージからも分かるように確保しているメモリよりも先のアドレスを参照している為、Segfaultしてしまったバグです。プログラム中に間違えて踏みそうなバグですが、この時点までこんなバグが残っていたのは色々な意味で「どうかな...」と思います。このバグ「クラッシュするよ」とレポートがあれば、PHPソース中で不具合を発生させる箇所が不明でも、バックトレース一発で原因まで特定できる単純なバグです。substr_compareはPHP5からの関数ですがzval(PHP変数のデータ構造体)には文字列(データ)の長さが保存されているのですが、これを使っていなかったのも「どうかな...」と思わされます...
2、3つ目ですがDoSが可能となるとしていますがこれに書いてあるコードでメモリを沢山消費しても仕方ないコードです。(ちょっと乱暴な例えかもしれませんが)C言語で「メモリをGB単位で確保して、確保したメモリにmemsetするとsegfaultする」と言っているような物です。言語とアプリケーションは違います。言語でメモリを大量に消費すると問題になるケースもありますが、これらは問題として取り扱うような動作ではありません。memory_limitの引っかかってしまっても当たり前でしょう。レポートした方はどうすべき、と考えているか聞いてみたいくらいです。
今までにもこの手のレポートはいくつかあったのですが、PHPをプログラミング言語として捉えていない人が多いのは驚かされます... たしか、最近もまた
function foo($arg) {
foo($arg);
}
で「PHPがクラッシュするからおかしい」とメールがあったと思います。メモリは有限リソースなんですけどね... しかも同じ内容のメールは私が気が付いただけでも複数回見ています... 初心者向けに再帰呼び出し回数に制限を設けてもよいかも知れませんが、再帰呼び出し制限なんて必要なんでしょうか?
私が知らないだけかも知れませんが手続き型のライトウェイト言語(Ruby、Python、Perl、Tclなど)で「関数のコールスタックは1000まで」と言った感じでチェックしている(できる)言語がある??!
【追記】
気になりついでにPHP,Ruby,Python,Perlの動作も調べたので、気になる方はコメントをどうぞ。制限するか? しないか? ポリシーの問題のようですね。Perl,PHPは制限しない。Ruby,Pythonは制限する。で、最も中途半端なのはPHP ;) 詳しくはコメントをどうぞ。
1 comment
Perlは仮想メモリもどんどん使っていくのでLinuxではメモリ管理の仕様もあるので途中でCTRL+C。
PHPは直ぐにクラッシュ、と言った感じです。(多分、静的にVMのスタック領域を確保しているので無限再帰呼び出しでもメモリを使い切らない)
テストコードはこんな感じです。
[yohgaki@dev template-bench]$ cat test.php
<?php
function foo($bar) {
static $i = 0;
$i++;
echo $i.PHP_EOL;
foo($bar);
}
foo('AAAAAAAAAAAAAA');
?>
[yohgaki@dev template-bench]$ cat test.pl
sub foo {
my $bar = shift(@_);
foo($bar);
}
foo('AAAAAAAAAAAAAAAA');
[yohgaki@dev template-bench]$ cat test.rb
def foo(bar)
foo(bar);
end
foo('AAAAAAAAAAAAAAA');
[yohgaki@dev template-bench]$ cat test.py
def foo(bar):
foo(bar)
foo('AAAAAAAAAAAAAAAAA');
Perlの様にいけるところまで行かない分まし(?)なようですがPHPがクラッシュするのは行儀が悪い方みたいですね。これはやはり直すべき? RubyやPythonのコールスタックの制限は結構厳しいですね。Pythonは1000、Rubyは1900弱。これくらいが妥当? PHPは2万回以上の再帰呼び出しが可能です。Perlは10万回以上でも余裕で行けるかも知れません!親切にするには捨てなければならない物もあると言うことですね。
PHPも動作自体を直すのは簡単で、スタックのレベルを保持して2000を超えたら停止とすれば普通にエラーが出せます。ただメモリのアクセス違反関係の領域に達してからだと面倒だったような気がします。当たり前ですがもうメモリが無いのでやれる事は限られてしまいます、という理由だったような気がしますがコードを見ないと分かりません。
Quick Sortとか簡単に2000くらいなら超えるケースはありそうです、と言うよりQuick Sortの最悪のケースはO(n)なのでちょっと大きめの配列のソートなら直ぐに限界に達します。ライトウェイト言語のレベルでQuick Sortなど深い再帰呼び出しが必要な機能を実装すること自体が「間違っている」とも考えれますが、言語のベンチマークサイトでいくつかテストで実行不可とかなっていた分はこれらの仕様のせいかな?(いつ暇になるか分かりませんが、暇なときに見てみよう。PHPには別のレベルの凄い仕様もあるので他の言語の仕様云々を言える言語ではありませんけど。)頭は固い方ではないので「ライトウェイト言語で深い再帰呼び出しなんて使うな」というポリシーもそれなりに良い考えにも思えてきました。
C/C++のバックグラウンドを持つPHPの開発者にはPHPのこの仕様は「まいいんじゃない」と受け止められていますが、ライトウェイト言語は実行環境であるべきか、言語として自由度を最大限にすべきか?難しいですね。
これを議論しだすと先に書いたポリシーの問題になるので結論は出ないかも知れません。しかし、PHPには既にmemory_limitというある意味「凄い」仕様もある訳で「再帰呼び出しをRubyやPythonの様に制限する」のは整合性の面からは妥当な選択とも考えられます。
どうでしょう?