読者です 読者をやめる 読者になる 読者になる

PHP はいつもわたしに新鮮な驚きを与えてくれる

php

ことの始まり

PHP の srand 関数について調べていて、ひょんな拍子にsrandのseedに文字列(numericである必要はあるけど)を渡せることを知った。

では、ここに long を超えるものを放り込むとどうなるのか。

では結果をごらんください。

「!?!?」

なぜこうなるのか

秘密は PHP 処理系の zend_parse_arg_impl 関数にあります。

zend_parse_arg_impl はphpの関数に渡された引数をパースする部分で、longを要求する関数にstringな値が渡された時の処理はこの部分ですね。

https://github.com/php/php-src/blob/master/Zend/zend_API.c#L335

さて、読み進めていくと「ん!?!?」ってなる行があるはずです。

この行ですね

https://github.com/php/php-src/blob/master/Zend/zend_API.c#L345

stringで与えれた数字が!!!!longよりも大きい場合!!!!!longの最大値に刈り込む!!!!!!!!!!!

ヤバいにおいが充満してきました。

※これ嘘だった。String が渡された場合はそのまま convert_to_long に渡され、その中で strtol 使って変換している。このとき strtol("10000000000000000000000000000000000000") は 0 を返すので、LONG_MAX ではなく 0 に刈り込まれている。まじかよ https://github.com/php/php-src/blob/c786c30108c44079a704cab36451a608fcb55938/Zend/zend_operators.c#L393

実際に問題になる場面

CakePHP の Security::cipher を利用している場合などにおかしなことになります。

https://github.com/cakephp/cakephp/blob/df52e85e0c1f48228e8b768074b6764f9d9659bf/lib/Cake/Utility/Security.php#L184

この関数ですね。なんとCakePHPは暗号化を独自実装しており(!)、その実装が srand 関数に依存しています(!)。ではここで srand に与えられている 'Security.cipherSeed' というConfig を見てみましょう。

https://github.com/cakephp/cakephp/blob/df52e85e0c1f48228e8b768074b6764f9d9659bf/app/Config/core.php#L230

ここですね。

んんんん???? まあ処理系による部分もありますが、この桁数だと普通にlong の最大値を超えているぞ????

あまり深いところまで追ってない場合、「同じ桁数に設定しとけばいいんだろうな」みたいな感じで同じ桁数で違う数字を設定するみたいなケースが多いように思いますが、桁数は一緒だけど数字が違うみたいなやつは結局longの最大値に丸められるので、その変更は無意味である。そのうえ警告も出ない。

!!!!!!!!無意味である!!!!!!!!!

!!!!!!!!警告も出ない!!!!!!!!!

まあそもそも CakePHP の Secutiry::cipher は deprecated であるようなので使うなという話ですし srand なんてものをセキュリティ上重要な部分に使うなという話ですが。

なんにせよ、PHPにはいろんな驚きが隠されているなぁという話でした。

PS

mt_srandを使えという向きに残念なお知らせです。

PS2

ちなみにPerl

さいきんのPerlだと警告が出るみたい。

ようするに暗黙の型変換がやばいって話だしその地雷をフレームワークがおもいっきり踏み抜いてるのがヤバいって話

PS3

ちなみにだが、この cipher メソッドは deprecated ではあるが、CakePHP 自身が Cookie Component 内で内部的にこの deprecated なメソッドをデフォルトで使っているので要注意である。https://github.com/cakephp/cakephp/blob/df52e85e0c1f48228e8b768074b6764f9d9659bf/lib/Cake/Controller/Component/CookieComponent.php#L137

なお、 3.0 ではこの問題は解決しているようである