INT34-C. 負のビット数のシフトやオペランドのビット数以上のシフトを行わない
ビット単位のシフトには、シフト式 <<
加減式 形式の左シフト演算と、シフト式 >>
加減式 形式の右シフト演算がある。オペランド(各オペランドは整数型)にはまず整数拡張が適用される。シフト結果の型は、格上げされた左オペランドの型である。右オペランドの値が負または格上げされた左オペランドのビット幅以上である時、シフト動作は未定義となる (「未定義の動作51」を参照 )。
式を負のビット数分シフトしたり、格上げ後の左オペランドの精度と同じかそれ以上のビット数分シフトしてはならない。ある整数型の精度とは、その型が値を表現するために使用するビット数であり、符号ビットやパディングビットは含まない。符号なし型の幅と精度は同じであるが、符号付き型においては幅は精度よりも1ビット大きい。本ルールでは、幅ではなく精度を使用しているが、その理由は、ほとんどすべての場合、オペランドの精度と同じかそれ以上のビット数のシフトはバグ(ロジックエラー)を意味するからである。ロジックエラーは、単に整数表現の不足が問題となるオーバーフローとは異なる問題である。一般に、シフト演算は符号無しオペランドに対してのみ行うべきである(「INT13-C. ビット単位の演算子は符号なしオペランドに対してのみ使用する」を参照)。
違反コード (左シフト、符号なしの型)
E1 << E2
の結果は、E1
を E2
ビット左にシフトした値であり、空いたビットは 0 で埋められる。次の図に左シフト演算を示す。
C言語規格によると、E1
が符号なし型を持つ場合、結果の値は、E1 * 2E2
の、結果の型で表現可能な最大値より 1 大きい値を法とする剰余となる。
次の違反コードは、右オペランドが格上げされた左オペランドの精度以下であることを確認していない。
void func(unsigned int ui_a, unsigned int ui_b) { unsigned int uresult = ui_a << ui_b; /* ... */ }
適合コード (左シフト、符号なしの型)
以下の適合コードは左オペランドの精度と同じかそれ以上のビット数でシフトを行う可能性を排除している。
#include <limits.h> #include <stddef.h> #include <inttypes.h> extern size_t popcount(uintmax_t); #define PRECISION(x) popcount(x) void func(unsigned int ui_a, unsigned int ui_b) { unsigned int uresult = 0; if (ui_b >= PRECISION(UINT_MAX)) { /* エラー処理 */ } else { uresult = ui_a << ui_b; } /* ... */ }
マクロ PRECISION()
と関数 popcount()
はあらゆる整数型について正しい精度を計算する (INT35-C. Use correct integer precisions を参照)。
符号なし整数型の左シフトによって生じる剰余動作は、「INT30-C. 符号無し整数の演算結果がラップアラウンドしないようにする」の例外 INT30-EX3 で認められている。
違反コード (左シフト、符号付きの型)
E1 << E2
の結果は、E1
を E2
ビット左にシフトした値であり、空いたビットは 0 で埋められる。E1
が符号付きの型で非負の値であり、 E1 * 2E2
が結果の型で表現できる場合、それが結果の値となる。それ以外の場合の動作は未定義である。
次の違反コードは、左右のオペランドの値が負ではないこと、また、右オペランドが格上げされた左オペランドの精度以下であることを確認していない。一方、「INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する」にならい、符号付き整数のオーバーフローはチェックしている。
#include <limits.h> #include <stddef.h> #include <inttypes.h> void func(signed long si_a, signed long si_b) { signed long result; if (si_a > (LONG_MAX >> si_b)) { /* Handle error */ } else { result = si_a << si_b; } /* ... */ }
「INT13-C. ビット単位の演算子は符号なしオペランドに対してのみ使用する」に従い、シフト演算子やその他のビット単位の演算子は符号なし整数に対してのみ使用すべきである。
適合コード (左シフト、符号付き型)
次の適合コードでは、オーバーフローのチェックに加え、左右のオペランドの値が非負であり、かつ右オペランドが格上げ後の左オペランドの精度以下であることを確認している。
#include <limits.h> #include <stddef.h> #include <inttypes.h> extern size_t popcount(uintmax_t); #define PRECISION(x) popcount(x) void func(signed long si_a, signed long si_b) { signed long result; if ((si_a < 0) || (si_b < 0) || (si_b >= PRECISION(ULONG_MAX)) || (si_a > (LONG_MAX >> si_b))) { /* エラー処理 */ } else { result = si_a << si_b; } /* ... */ }
違反コード (右シフト)
E1 >> E2
の結果は、E1
を E2
ビット右にシフトした値である。E1
が符号なし型の場合、あるいは E1
は符号付き型だが負ではない値の場合、結果の値は E1 / 2E2
の値の整数部分となる。E1
が符号付き型で値が負の場合、結果として得られる値は 処理系定義 であり、次に示すどちらかとなる。算術(符号付き)シフトの場合は次の通りである。
論理(符号なし)シフトの場合は次の通りである。
下記の違反コードは、右オペランドが格上げされた左オペランドの精度以上であるかを検査しておらず、未定義の動作を引き起こす可能性がある。
void func(unsigned int ui_a, unsigned int ui_b) { unsigned int uresult = ui_a >> ui_b; /* ... */ }
符号付きオペランドを伴う場合、右シフトが算術(符号付き)シフトとして実装されている、あるいは、論理(符号なし)シフトとして実装されている、と仮定してしまうと、どちらにせよ、脆弱性につながる可能性がある (「INT13-C. ビット単位の演算子は符号無しオペランドに対してのみ使用する」を参照)。
適合コード (右シフト)
以下の適合コードでは、左オペランドの精度以上にシフトする可能性を排除している。
#include <limits.h> #include <stddef.h> #include <inttypes.h> extern size_t popcount(uintmax_t); #define PRECISION(x) popcount(x) void func(unsigned int ui_a, unsigned int ui_b) { unsigned int uresult = 0; if (ui_b >= PRECISION(UINT_MAX)) { /* エラー処理 */ } else { uresult = ui_a >> ui_b; } /* ... */ }
処理系固有の詳細
GCCは、負のビット数あるいは型のビット幅を超えるビット数のシフト演算を検出したり、そのようなシフト演算を行おうとするとトラップを生成するようなコードを出力するオプションを備えていない。そのようなシフト演算は常に未定義であるとして処理される。プロセッサによっては、シフト命令の実行において、与えられたシフト幅の値をその型のビット幅で剰余して使う場合がある。たとえば、x86-32 環境では、32ビットシフトは次の命令を使って実装されている。
sa[rl]l %cl, %eax
sa[rl]l
命令は、%cl
の下位 5 ビットのビットマスクを使用して [0, 31] の範囲内の値を生成し、%eax
をその値だけシフトする。
// IA-32 プラットフォームにおける 64ビットのシフトは次の通り sh[rl]dl %eax, %edx sa[rl]l %cl, %eax
%eax
にはシフトされるダブルワードの下位ビット列が格納され、%edx
には上位ビット列が格納される。
リスク評価
Cでは、負のビット数のシフトあるいは格上げされた左オペランドのビット幅以上のシフトは未定義の動作となる。しかし、プロセッサレベルのシフト演算では、与えられたシフト幅をその型のビット幅で剰余した値にして使うことが多いため、一般にリスクは低い。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
INT34-C |
低 |
低 |
中 |
P2 |
L3 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
CodeSonar | 4.0 |
LANG.ARITH.BIGSHIFT LANG.ARITH.NEGSHIFT |
Shift Amount Exceeds Bit Width Negative Shift Amount |
|
|
このルールの違反を検出できる。「INT13-C. ビット単位の演算子は符号なしオペランドに対してのみ使用する」のチェックにおいて、符号なしオペランドが使われていることを検出する。 |
|
ECLAIR | 1.2 | CC2.INT34 | 部分的に実装済み |
5.0 |
|
CERT C Rule Packを使ってこのルールの違反を検出できる |
|
LDRA tool suite |
8.5.4 |
403 S |
部分的に実装済み |
PRQA QA-C | v8.2 |
0499 |
部分的に実装済み |
関連するガイドライン
CERT C コーディングスタンダード | INT13-C. ビット単位の演算子は符号無しオペランドに対してのみ使用する INT35-C. Use correct integer precisions INT32-C. 符号付き整数演算がオーバーフローを引き起こさないことを保証する |
CERT C++ Secure Coding Standard | INT34-CPP. Do not shift a negative number of bits or more bits than exist in the operand |
ISO/IEC TR 24772:2013 | Arithmetic Wrap-around Error [FIF] |
参考資料
[C99 Rationale 2003] | 6.5.7, "Bitwise Shift Operators" |
[Dowd 2006] | Chapter 6, "C Language Issues" |
[Seacord 2013b] | Chapter 5, "Integer Security" |
[Viega 2005] | Section 5.2.7, "Integer Overflow" |
翻訳元
これは以下のページを翻訳したものです。