ことの始まり
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 を利用している場合などにおかしなことになります。
この関数ですね。なんとCakePHPは暗号化を独自実装しており(!)、その実装が srand 関数に依存しています(!)。ではここで srand に与えられている 'Security.cipherSeed' というConfig を見てみましょう。
ここですね。
んんんん???? まあ処理系による部分もありますが、この桁数だと普通に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 ではこの問題は解決しているようである