PHPセキュリティ対策:数値型と制約

(1/1)
PHPセキュリティ対策:数値型と制約
PHPにはデータ型が無いと言われるが、内部的には他の言語と同じくデータ型は存在する。とくに大きな正数/負数を扱う場合には、PHPの制約を超えないように注意する必要がある。
制約を超えると演算が正常に行われなくなり、プログラム全体の動作がおかしくなる場合がある。最悪の場合、プログラムが無限ループに陥ったり、思わぬエラーを晒したりする恐れがあるので、セキュリティ上、注意が必要だ。
PHPで数値を扱う場合には、integer 型(整数型)と float 型(浮動小数型)の2種類に分かれる。両者とも常に符号ありのデータ型である。

(2022年6月27日)FastCGIで正常動作しない不具合を修正

目次

サンプル・プログラム

圧縮ファイルの内容
overflow.phpサンプル・プログラム本体。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「PHPでGET/POSTでフォームから値を受け取る」「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。

integer 型の制約

まず、integer 型の制約について見てみよう。

  52: /**
  53:  * integer型のオーバーフローを見る
  54:  * @param   なし
  55:  * @return  なし
  56: */
  57: function overflow1() {
  58:     echo '■integer型のオーバーフロー/アンダーフローを見る<br />';
  59:     echo 'PHP_INT_SIZE = ' . PHP_INT_SIZE . '<br />';   //integer型のバイト長
  60:     echo 'PHP_INT_MAX = '  . PHP_INT_MAX . '<br />';    //integer型の最大値
  61:     echo '<hr />';
  62:     //正数のオーバーフロー
  63:     $a = PHP_INT_MAX - 2;
  64:     for ($i = 0$i < 5$i++) {
  65:         var_dump($a);
  66:         echo '<br />';
  67:         $a++;
  68:     }
  69:     echo '<hr />';
  70:     //負数のアンダーフロー
  71:     $a = 0 - PHP_INT_MAX;
  72:     for ($i = 0$i > -5$i--) {
  73:         var_dump($a);
  74:         echo '<br />';
  75:         $a--;
  76:     }
  77: }

PHPの integer 型については、組み込み定数 PHP_INT_SIZE がバイト数を、PHP_INT_MAX が正数の最大値を示している。
実際には32ビット環境と64ビット環境で変わり、以下のようになっている。
ビット長PHP_INT_SIZEPHP_INT_MAX
32ビット環境32ビット42,147,483,647
64ビット環境64ビット89,223,372,036,854,775,807


■32ビット環境
PHP_INT_SIZE = 4
PHP_INT_MAX = 2,147,483,647
-231 ≦ integer ≦ 231 - 1
-2,147,483,648 ≦ integer ≦ 2,147,483,647

■64ビット環境
PHP_INT_SIZE = 8
PHP_INT_MAX = 9,223,372,036,854,775,807
-263 ≦ integer ≦ 263 - 1
-9,223,372,036,854,775,808 ≦ integer ≦ 9,223,372,036,854,775,807

プログラムを実行すると、この制約を上回る/下回ると、integer 型から float 型へ自動的に移行するのが分かる。

float型から integer型へは自動移行しない

では、オーバーフローまたはアンダーフローを起こし、integer 型から float 型へ移行してしまったデータ型は自動的に元に戻るか確認しておこう。

  79: /**
  80:  * float型からinteger型へは自動移行しない
  81:  * @param   なし
  82:  * @return  なし
  83: */
  84: function overflow2() {
  85:     echo '■float型からinteger型へは自動移行しない<br />';
  86:     //正数
  87:     $a = PHP_INT_MAX + 2;
  88:     for ($i = 5$i > 0$i--) {
  89:         var_dump($a);
  90:         echo '<br />';
  91:         $a--;
  92:     }
  93:     echo '<hr />';
  94:     //負数
  95:     $a = -3 - PHP_INT_MAX;
  96:     for ($i = -5$i < 0$i++) {
  97:         var_dump($a);
  98:         echo '<br />';
  99:         $a++;
 100:     }
 101: }

プログラムを実行すると分かるように、いったん float 型へ移行してしまったデータは元の integer 型には自動的に戻らない。この点は注意が必要だ。
もし integer 型にしたいのであれば、強制的にキャストしてやる必要がある。

float 型の制約と誤差

次に、 float 型の制約についてみておこう。

 103: /**
 104:  * float型のオーバーフローを見る
 105:  * @param   なし
 106:  * @return  なし
 107: */
 108: function overflow3() {
 109:     echo '■float型のオーバーフロー/アンダーフローを見る<br />';
 110:     //float型のオーバーフロー(大数側)
 111:     $a = +1.79E+305;
 112:     for ($i = 0$i < 5$i++) {
 113:         var_dump($a);
 114:         echo '<br />';
 115:         $a *10;
 116:     }
 117:     echo '<hr />';
 118:     //float型のオーバーフロー(小数側)
 119:     $a = +4.94E-323;
 120:     for ($i = 0$i < 5$i++) {
 121:         var_dump($a);
 122:         echo '<br />';
 123:         $a /= 10;
 124:     }
 125:     echo '<hr />';
 126:     //float型のアンダーフロー(大数側)
 127:     $a = -1.79E+305;
 128:     for ($i = 0$i < 5$i++) {
 129:         var_dump($a);
 130:         echo '<br />';
 131:         $a *10;
 132:     }
 133:     echo '<hr />';
 134:     //float型のアンダーフロー(小数側)
 135:     $a = -4.94E-323;
 136:     for ($i = 0$i < 5$i++) {
 137:         var_dump($a);
 138:         echo '<br />';
 139:         $a /= 10;
 140:     }
 141: }

float 型の範囲は、PHPの実装に依存する。ただ、大多数の処理系では IEEE 754 規格の倍精度に準拠している。
つまり、絶対値の範囲は下記の通りである。
4.9406564584124654E-324 ≦ float ≦ 1.7976931348623157E+308

これをプログラムで確かめると、この範囲をオーバーフローすると 'INF' または '0' になる、逆にアンダーフローすると '-INF' または '-0' になることが分かる。INFは無限大を意味する。
また、10で除算しているにもかかわらず、2進数特有の“誤差”が発生していることも分かる。

これほどの大きさ/小ささの数値データを扱うことは希だろうが、もしループ処理内でオーバーフロー/アンダーフローしてしまうとINFや0が返るので、PHP処理系ではエラーを出さずに無限ループに陥る恐れがある。注意しておきたいところだ。
(この項おわり)
header