INT10-C. % 演算子を使用する際、結果の剰余が正であると想定しない
C89 (および歴史的な K&R 実装)では、負のオペランドに対する剰余演算は処理系定義であった。これは C99 で変更され、C11 にも受け継がれている。
すべての C コンパイラが C 言語規格に厳密に準拠しているわけではないため、様々なプラットフォームの様々なコンパイラでコンパイルされて利用されるコードは、%
演算子の動作に依存してはならない。
C 標準 [ISO/IEC 9899:2011] セクション 6.5.5 には次のように記載されている。
/
演算子の結果は、第1オペランドを第2オペランドで除した商とし、%
演算子の結果は剰余とする。両演算子とも、第2オペランドの値が0の場合、その動作は 未定義 とする。
および
整数同士の除算の場合、
/
演算子の結果は、代数的な商から小数部を切り捨てた値とする。商a/b
が表現できる場合、式(a/b)*b + a%b
は、a
に等しくなければならない。
剰余の小数部を切り捨てることを、0方向への切捨て と呼ぶことが多い。
C の %
演算子の定義は以下の動作を示唆している:
17 % 3 -> 2 17 % -3 -> 2 -17 % 3 -> -2 -17 % -3 -> -2
結果の値は非除数(式中の最初のオペランド)と同じ符号を持つ。
違反コード
以下のコード例で insert()
関数が値を追加する位置は、剰余演算を使って計算している。つまり、バッファの終端に達したら、次に値を挿入する位置はバッファの先頭になる。しかし、size
および index
は int
型として宣言されており、正の値であるとは限らない。処理系および size
と index
の符号次第では、(index + 1) % size
の結果は負の値になるかもしれず、そうなると配列 list
の境界外への書き込みを行ってしまうかもしれない。
int insert(int index, int *list, int size, int value) { if (size != 0) { index = (index + 1) % size; list[index] = value; return index; } else { return -1; } }
このコードは、「ERR02-C. 正常終了時の値とエラーの値は別の手段で通知する」にも違反している。
違反コード
剰余演算の絶対値をとれば、必ず正の値となる。
int insert(int index, int *list, int size, int value) { if (size != 0) { index = abs((index + 1) % size); list[index] = value; return index; } else { return -1; } }
しかし、このコードは「INT01-C. オブジェクトのサイズを表現するすべての整数値に rsize_t もしくは size_t を使用する」に違反している。また、(index + 1)
が符号付き整数オーバーフローを招く可能性もある。これは「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」に違反する。
適合コード (符号無し型)
この場合最も適切な処理は、符号無しの型を使用して処理系定義の動作を排除することだ。また、「ERR02-C. 正常終了時の値とエラーの値は別の手段で通知する」に適合するために、関数の返り値は、処理が正常に完了した場合にゼロ以外を返し、value
を格納するために使われた index
の値は引数 result
が指すオブジェクトに代入して呼出し元に返している。
int insert(size_t* result, size_t index, int *list, size_t size, int value) { if (size != 0 && size != SIZE_MAX) { index = (index + 1) % size; list[index] = value; *result = index; return 1; } else { return 0; } }
リスク評価
レコメンデーション |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
INT10-C |
低 |
低 |
高 |
P1 |
L3 |
自動検出
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
特定の違反コード例を検出できる。% 演算の結果が負であるかもしれない場合を特定し、その結果の値が配列のインデックスに使用される場合を警告する。また、結果の値が正であることをチェックせずに値を使用するケースについて警告することもできるが、そうすると誤検出が多発するだろう。 |
Fortify SCA |
5.0 |
|
CERT C Rule Packを使ってこのレコメンデーションの違反を検出できる |
LDRA tool suite |
V. 8.5.4 |
584 S |
実装済み |
PRQA QA-C | 8.1 | 3103 | 実装済み |
関連するガイドライン
CERT C++ Secure Coding Standard | INT10-CPP. Do not assume a positive remainder when using the % operator |
Java セキュアコーディングスタンダード CERT/Oracle 版 | NUM02-J. 除算と剰余演算でゼロ除算エラーを起こさない |
MITRE CWE | CWE-682, Incorrect calculation CWE-129, Unchecked array indexing |
参考資料
[Beebe 2005] | |
[ISO/IEC 9899:2011] | Section 6.5.5, "Multiplicative Operators" |
[Microsoft 2007] | C Multiplicative Operators |
[Sun 2005] | Appendix E, "Implementation-Defined ISO/IEC C90 Behavior" |
翻訳元
これは以下のページを翻訳したものです。
INT10-C. Do not assume a positive remainder when using the % operator (revision 69)