Home > ラーニング > セキュアコーディング > C セキュアコーディングスタンダード > 01. プリプロセッサ (PRE)
数値定数をマスクや特定のビット値として使用することは多々ある。通常、これらの定数は16進数で表記され、マシン上でデータが実際どのように表現されるのかという点に関して、プログラマに明解な意図を伝える。しかしこれは、システムによっては成り立たないような前提をすることにつながる。(特にILP32データモデルを前提として書かれたコードがLP64データモデルのマシンに移植される場合など)
接尾辞のついていない16進定数は、32ビットで表現可能でありかつ上位ビットが立っている場合、unsigned int として定義される。たとえば、0xFFFFFFFFL は 32ビットシステムでは signed long である (すべてのビットが '1')。しかし、64ビットシステムでは、下位32ビットだけがセットされ、0x00000000FFFFFFFF という値になる。以下の例は2の補数演算を前提としている。
以下のコードはマスクを使って long の値の2の補数を計算しようとしている。このコードは32ビットマシンでコンパイルされれば意図したとおりに動作するが、64ビットマシンでコンパイルされると正しく動作しない。
#define MASK 0xFFFFFFFFL ... long x = -1L; long positive_x = (x ^ MASK) + 1;
全てのビットを立てたいのであれば、移植性がある(かつ安全な)やり方は -1 の値をもつ signed long 定数をマクロとして定義してやることである。2の補数演算が使用され、すべてのビットが立つ。
#define MASK -1L ... long x = -1L; long positive_x = (x ^ MASK) + 1;
もうひとつの解決法は、マクロではなく定数を使うことである。
long MASK = -1L; ... long x = -1L; long positive_x = (x ^ MASK) + 1;
定数は明示的に型付けできるので、上述のようなintとlongのあいまいさを回避できる。定数とマクロの比較については、「DCL06-C. プログラムロジックでリテラル値を表現するには意味のあるシンボル定数を使う」を参照のこと。
もうひとつ考えられる問題は、最上位ビット(MSB)を立てることである。32ビットシステムでは、定数 0x80000000 が用いられる:
#define MASK 0x80000000L ... long x = -1L; long ones_complement_x = (x ^ MASK);
より移植性の高い方法は、シフト式を使うことである。
#define MASK (1L << ((sizeof(long) * CHAR_BIT) - 1)) ... long x = -1L; long ones_complement_x = (x ^ MASK);
ほかに整数の変換がどのようにセキュリティに影響するかの詳細は、「INT02-C. 整数変換のルールを理解する」を参照のこと。
コードを移植する際には新たなセキュリティ上の問題が入りこむ可能性が高くなる。入力文字列のサイズを定義するために数値定数を使用している場合、バッファのチェックが無効になる可能性があり、バッファオーバーフロー攻撃をうける可能性が作りこまれてしまう。将来、コードを他のデータモデルに移植することがあらかじめ分かっているのであれば、最初から(特定のデータモデルを前提としない)移植性の高いコードを書くことが推奨される。
| レコメンデーション | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| PRE12-C | 高 | 中 | 低 | P6 | L2 |