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

STR34-C. 文字データをより大きなサイズの整数型に変換するときは事前に unsigned char 型に変換する

STR34-C. 文字データをより大きなサイズの整数型に変換するときは事前に unsigned char 型に変換する

符号付き文字データは、より大きな符号付きの型に代入したり変換したりする前に、 unsigned char 型に変換しなくてはならない。char 型データは、コンパイラ実装に依存し、signed char 型あるいは unsigned char 型のいずれかに解釈される。つまり、それらの型と同じ値の範囲、バイナリ表現、動作となるように扱われる。したがって、このルールは signed char 型および (単なる) char 型データの両方に適用するべきである。

このルールは、文字データが負の値として解釈される可能性のある値を含む場合のみに適用される。たとえば、char 型が 2 の補数の 8 ビット値で表現される場合、+127 よりも大きな値の文字はすべて負の値として解釈される。

このルールは、「STR37-C. 文字処理関数への引数は unsigned char として表現できなければならない」を汎用化したものである。

違反コード

以下のコード例は、bash バージョン 1.14.6 以前で見つかった脆弱なコードから抜粋した。この脆弱性について CERT Advisory CA-1996-22 が発行されたが、脆弱性は、bash のソースコードの parse.y モジュールにある yy_string_get() 関数のなかで、string ポインタが参照する文字データが符号拡張されたことに起因する。

static int yy_string_get() {
  register char *string;
  register int c;

  string = bash_input.location.string;
  c = EOF;

  /* 文字列が存在しないか空の場合、EOF を返す */
  if (string && *string) {
    c = *string++;
    bash_input.location.string = string;
  }
  return (c);
}

string 変数を使って解析すべきコマンドラインを含む文字列を走査している。このポインタから1文字取得し、int 型の変数に格納している。char 型をデフォルトで signed char 型として扱うコンパイラでは、この値は int 型の変数に割り当てられるとき符号拡張される。文字コード 255 (10 進数、2 の補数表記では −1) は符号拡張の結果 −1 として変数 c に代入され、EOF と区別がつかなくなる。

この問題は、以下のように string 変数を unsigned char 型として明示的に宣言することで修正された。

static int yy_string_get() {
  register unsigned char *string;
  register int c;

  string = bash_input.location.string;
  c = EOF;

  /* 文字列が存在しないか空の場合、EOF を返す */
  if (string && *string) {
    c = *string++;
    bash_input.location.string = string;
  }
  return (c);
}

ただしこのコード例は、「STR04-C. 基本文字集合にある文字を表すには単なる char を使用する」に違反している。

適合コード

以下のコード例では、式 *string++ の結果は (unsigned char) 型にキャストされた上で int 型の変数 c に代入されている。

static int yy_string_get() {
  register char *string;
  register int c;

  string = bash_input.location.string;
  c = EOF;

  /* 文字列が存在しないか空の場合、EOF を返す */
  if (string && *string) {
    /* unsigned 型にキャストする */
    c = (unsigned char)*string++;

    bash_input.location.string = string;
  }
  return (c);
}
違反コード

以下のコード例では、整数拡張のため、*s から unsigned int へのキャストによって値が UCHAR_MAX を超過し、配列のインデックスが有効な範囲内にあるという VOID の保証に関数が違反することになり、未定義の動作につながる可能性がある。

static const char table[UCHAR_MAX] = { /* ... /* };

int first_not_in_table(const char *str) {
  const char *s = str;
  for (; *s; ++s) {
    if (table[(unsigned)*s] != *s)
      return s - str;
  return -1;
}
適合コード

以下のコード例では、char 値が暗黙的に大きな符号付き整数型に拡張される前に、この値を unsigned char にキャストする。

static const char table[UCHAR_MAX] = { /* ... /* };

ptrdiff_t first_not_in_table(const char *str) {
  const char *s = str;
  for (; *s; ++s) {
    if (table[(unsigned char)*s] != *s)
      return s - str;
  return -1;
}
リスク評価

コード上はごく小さなエラーだが、きわめて幅広い範囲の深刻な脆弱性を招く可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

STR34-C

P8

L2

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

ツール

バージョン

チェッカー

説明

Compass/ROSE

   

「INT07-C. 数値には符号の有無が明示された文字型を使用する」の違反をチェックするときにこのルールの違反を検出することができる。

ECLAIR

1.1

charcast

実装済み

Fortify SCA

5.0

 

CERT C Rule Pack を使ってこのルールの違反を検出できる。

GCC

2.95 以降

-Wchar-subscripts

配列インデックスとして使用される char 型のオブジェクトを検出する。

LDRA tool suite

V. 8.5.4

434 S

実装済み
PRQA QA-C 8.1 3704 実装済み
関連する脆弱性

CVE-2009-0887 はこのルールへの違反の結果である。Linux PAM (バージョン 1.0.3 まで) では、strtoklibpam を実装すると、配列のインデックスとして使用できるように文字が整数にキャストされる(基本的に符号付き)。攻撃者はこの脆弱性を悪用し、ASCII 以外の文字列を入力して、キャストで負のインデックスを生成し、配列の範囲外のメモリにアクセスすることが可能である [xorl 2009]。

関連するガイドライン
CERT C++ Secure Coding Standard STR34-CPP. Cast characters to unsigned types before converting to larger integer sizes
ISO/IEC TS 17961 (ドラフト) Conversion of signed characters to wider integer types before a check for EOF [signconv]
MISRA-C Rule 6.1
MITRE CWE CWE-704, Incorrect type conversion or cast
参考資料
[xorl 2009 ] CVE-2009-0887: Linux-PAM Signedness Issue
翻訳元

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

STR34-C. Cast characters to unsigned char before converting to larger integer sizes (revision 58)

Top へ

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