JPCERT コーディネーションセンター

INT02-C. 整数変換のルールを理解する

INT02-C. 整数変換のルールを理解する

整数変換は、キャストの結果明示的に起こることもあれば、演算に必要なため暗黙的に起こることもある。整数変換は一般に、プログラムを正しく実行するために必要とされるが、場合によってはデータの欠損や誤解釈につながることもある。オペランド値の適合する型への変換は、値または表現に影響しない。

C の整数変換規則は、C コンパイラがどのように整数変換を扱うべきかを定義している。これらの規則には、整数拡張(integer promotions)、整数変換の順位(integer conversion rank)、および 通常の算術型変換(usual arithmetic conversions) が含まれる。整数変換規則の意図は、整数変換の結果いつも同じ値が得られること、そして他の演算への影響を最小限におさえること、である。C が標準化される前の実装においては、符号を維持することが一般に好まれていた。

整数拡張 (Integer Promotions)

int より小さな整数型は、演算時に整数拡張される。元の型のすべての値を intで 表現できる場合、小さな型の値を int に変換する。それ以外の場合、unsigned int に変換する。整数拡張は通常の算術型変換の一部として、引数式や、単項 +、-、~演算子, シフト演算子のオペランドに対して適用される。以下に示すコードは、整数拡張の適用例を示している。

char c1, c2;
c1 = c1 + c2;

整数拡張は、変数c1 と c2 がそれぞれ int に整数拡張されることを要求する。この2つの int の値は加算され、和は char 型に収まるように切り捨てられる。整数拡張が行われるのは、計算途中の値がオーバーフローして算術エラーが起こることを防ぐためである。

signed char cresult, c1, c2, c3;
c1 = 100;
c2 = 3;
c3 = 4;
cresult = c1 * c2 / c3;

この例では、(演算子の優先順位の規則に従って) c1 の値が c2 と乗算され、その積が次に c3 の値で除算されている。signed char が8ビットの値として表現されると仮定すると、c1 と c2 の積である 300 という値は表現することができない。しかし、整数拡張の結果、c1、c2、c3の値はそれぞれ int に変換され、式全体は正しく評価される。結果の値は切り捨てられ、cresult に格納される。最終結果の 75 は signed char 型の範囲にあるため、int から signed char への変換ではデータの欠損は発生しない。

整数変換の順位 (Integer Conversion Rank)

すべての整数型は整数変換の順位を持ち、これによってどのように変換が行われるかが決まる。この順位は、各整数型が、より低い順位の型と少なくとも同じビット数を持つように定められている。C 言語規格セクション 6.3.1.1 では、次のように整数変換の順位を定義している[ISO/IEC 9899:2011]。

整数変換の順位は通常の算術型変換で使用される。異なる整数型が混在している場合に、どのような変換が必要となるか整数変換の順位にもとづいて決定される。

通常の算術型変換

二項演算子の2つのオペランドや条件演算子 (?:) の第二、第三オペランドは共通の型の値として扱われる。通常の算術型変換は、ここで使われる共通の型を導出するための規則である。型変換は2つのオペランドの型が異なる場合に行われ、一方もしくは両方のオペランドが変換される。算術オペランドをとる演算子の多くは、通常の算術型変換にしたがって型変換を行う。両方のオペランドに整数拡張が適用され、その後で以下の規則が適用される。

  1. 両方のオペランドが同じ型をもつ場合、更なる型変換は行わない。
  2. そうでない場合、両方のオペランドが符号付き整数型をもつ、又は両方のオペランドが符号無し整数型をもつならば、整数変換の順位の低い方の型を、高い方の型に変換する。
  3. そうでない場合、符号無し整数型を持つオペランドが、他方のオペランドの整数変換の順位より高い又は等しい順位をもつならば、符号付き整数型をもつオペランドを、符号無し整数型をもつオペランドの型に変換する。
  4. そうでない場合、符号付き整数型をもつオペランドの型が、符号無し整数型をもつオペランドの型のすべての値を表現できるならば、符号無し整数型をもつオペランドを、符号付き整数型をもつオペランドの型に変換する。
  5. そうでない場合、両方のオペランドを、符号付き整数型をもつオペランドの型に対応する符号無し整数型に変換する。
例

以下では、8ビット char、32ビット int、64ビット long long の処理系でコードがコンパイルされると仮定する。

signed char sc = SCHAR_MAX;
unsigned char uc = UCHAR_MAX;
signed long long sll = sc + uc;

この例では、signed char sc と unsigned char uc はどちらも整数拡張される。元の型のすべての値が int で表現できるため、整数拡張の過程で両方の値は自動的に int 型に変換される。通常の算術型変換では、もし両方のオペランドの型が等しくなければさらに変換が行われる可能性がある。この例では、実際の加算演算は32ビット int の値に対して行われる。演算結果の値は signed long long 型整数に格納されるが、演算自体はそれに影響されず、32ビット int での演算となることに注意。加算結果の32ビット値は単純に符号拡張され64ビットの値として signed long long 型整数に格納される。

signed char の精度が7ビットであり、unsigned char の精度が8ビットであると仮定した場合、この演算はまったく安全に行える。しかし、もしコンパイラが signed char と unsigned char をそれぞれ 31ビットと32ビットの精度で表現した場合、変数 uc は signed int ではなく、unsigned int に変換する必要がある。通常の算術型変換の結果、signed int は符号無し型に変換され、加算は2つの unsigned int 型の値に対して行われる。また、uc は UCHAR_MAX に等しく、これは UINT_MAX に等しくなるため、この例の加算はオーバーフローを引き起こす結果となる。結果の値はゼロ拡張され、sll に割り当てられた64ビットの領域に格納される。

違反コード (比較)

異なる型が混在する演算には注意する必要がある。以下のコード例では整数拡張の特異性を示している。

int si = -1;
unsigned int ui = 1;
printf("%d\n", si < ui);

このコード例では、比較演算子のオペランドとして signed int 型および unsigned int 型の値をとる。変換規則によって、si の値は unsigned int 型に変換される。−1 は unsigned int 型の値として表現できないため、C 言語規格セクション 6.3.1.3、第2パラグラフ [ISO/IEC 9899:2011] に従い、UINT_MAX に変換される。

新しい型で表現できない場合、新しい型が符号無し整数型であれば、新しい型で表現しうる最大の数に1加えた数を加えることまたは減じることを、新しい型の範囲に入るまで繰り返すことによって得られる値に変換する。

その結果、UINT_MAX は 1より小さくはないため、プログラムは 0 を出力する。

適合コード

比較が signed int 型で行われるように強制することで、直観通りの結果を出力するよう修正することができる。

int si = -1;
unsigned ui = 1;
printf("%d\n", si < (int)ui);

このプログラムは期待通り 1 を出力する。この例で (int)ui としてよいのは、ui の値が int で表現できることがあらかじめ分かっているからだ、という点に注意。あらかじめ分かっていないのであれば、以下のように書きかえる必要がある。

int si = /* 符号付きの何かしらの値 */;
unsigned ui = /* 符号無しの何かしらの値 */;
printf("%d\n", (si < 0 || (unsigned)si < ui));
違反コード

以下のコード例は、int 型より小さい整数型に対してビット単位の演算を行うとどのような予期せぬ結果がもたらされるかを示している。

uint8_t port = 0x5a;
uint8_t result_8 = ( ~port ) >> 4;

この例では、port のビット補数が最初に計算され、その後4ビット右シフトしている。これらの演算はどちらも8ビット符号無し整数に対して行われたとすると、result_8 は 0x0a という値をとる。ただし、以下の結果では、port は最初に signed int 型に拡張される (int 型が 32 ビットの典型的なアーキテクチャ)。

式

種類

値

備考

port

uint8_t

0x5a

 

~port

int

0xffffffa5

 

~port >> 4

int

0x0ffffffa

値がマイナスかどうかは処理系定義である。

result_8

uint8_t

0xfa

 

適合コード

以下の適合コードでは、port のビット補数は8ビットに変換される。これにより、result_8 には期待通り 0x0aU が割り当てられる。

uint8_t port = 0x5a;
uint8_t result_8 = (uint8_t) (~port) >> 4;
リスク評価

整数変換規則について正しく理解していないとエラーにつながり、これが攻撃可能な脆弱性を引き起こす恐れがある。とくに次のような場合に重大なリスクとなる: 小さな型に変換する場合(明示的なキャストや代入)、符号無しの型から符号付きの型への変換、負の値から符号無しの型への変換。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

INT02-C

中

中

中

P8

L2

自動検出(最新の情報はこちら)

ツール

バージョン

チェッカー

説明

ECLAIR

1.1

utypbtws

実装済み

LDRA tool suite

V. 8.5.4

52 S
93 S
96 S
101 S
332 S
334 S
433 S
434 S
442 S
443 S
446 S
452 S
457 S
458 S
491 S

実装済み

PRQA QA-C 8.1

0290
0291
3755
3756
3757
3758
3759
3760
3762
3763
3764
3765

実装済み
関連する脆弱性

Adobe Flashの脆弱性 VU#159523 は、Flashが符号付き整数を calloc() に渡すために生じている。攻撃者はこの整数を制御し、負の値を渡すことができる。calloc() の引数は符号無しの size_t 型として扱われるため、負の値は大きな正の値に変換される。変換された正の値は一般にメモリ割り当てには大きすぎる値であり、その結果、calloc() は NULL を返すことになる。

関連するガイドライン
CERT C++ Coding Standard INT02-CPP. Understand integer conversion rules
ISO/IEC TR 24772:2013 Numeric Conversion Errors [FLC]
MISRA C:2012 Rule 10.1 (required)
Rule 10.3 (required)
Rule 10.4 (required)
Rule 10.6 (required)
Rule 10.7 (required)
Rule 10.8 (required)
MITRE CWE CWE-192, Integer coercion error
CWE-197, Numeric truncation error
参考資料
[Dowd 2006] Chapter 6, "C Language Issues" ("Type Conversions," pp. 223–270)
[Seacord 2013]

Chapter 5, "Integer Security"

翻訳元

これは以下のページを翻訳したものです。

INT02-C. Understand integer conversion rules

Top へ

Topへ
最新情報(RSS|メーリングリスト|X (旧Twitter) )