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)
