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

INT18-C. より大きいサイズの整数との比較や代入を行う前に、整数式をそのサイズで評価する

演算を含む整数式をより大きいサイズの整数型と比較したり代入する場合、オペランドのいずれかを明示的にキャストすることで整数式をより大きいサイズの型で評価しなければならない。

違反コード

次のコードは、size_t の値が符号無し32ビットで long long の値が64ビットであるシステムにおいてルールに違反している。このコード例では、SIZE_MAXlength + BLOCK_HEADER_SIZE を比較することでラップアラウンドが発生するかどうか確認している。lengthsize_t 型として宣言されているので、加算は32ビットで演算され、結果はラップアラウンドする可能性がある。SIZE_MAX との比較は常に偽になる。ラップアラウンドが発生すると、malloc()mBlock に十分なメモリ領域を割り当てることができず、バッファオーバーフローにつながってしまう。

#include <stdlib.h>
#include <stdint.h>  /* SIZE_MAX を使用するため */

enum { BLOCK_HEADER_SIZE = 16 };

void *AllocateBlock(size_t length) {
  struct memBlock *mBlock;

  if (length + BLOCK_HEADER_SIZE > (unsigned long long)SIZE_MAX)
    return NULL;
  mBlock = (struct memBlock *)malloc(
    length + BLOCK_HEADER_SIZE
  );
  if (!mBlock) { return NULL; }
  /* ブロックヘッダにデータを書き込み、データを返す */

  return mBlock;
}

この条件を検出するコンパイラもある。

適合コード (アップキャスト)

次の解決法では、length オペランドを unsigned long long にキャストし、このサイズで加算を行っている。

#include <stdlib.h>
#include <stdint.h>


enum { BLOCK_HEADER_SIZE = 16 };

void *AllocateBlock(size_t length) {
  struct memBlock *mBlock;

  if ((unsigned long long)length + BLOCK_HEADER_SIZE > SIZE_MAX) {
    return NULL;
  }
  mBlock = (struct memBlock *)malloc(
    length + BLOCK_HEADER_SIZE
  );
  if (!mBlock) { return NULL; }
  /* ブロックヘッダにデータを書き込み、データを返す */

  return mBlock;
}

このラップアラウンドのテストは、sizeof(unsigned long long) > sizeof(size_t) である場合だけ有効である。size_tunsigned long long 型が64ビット符号無しの値として表現されている場合には、加算演算の結果を unsigned long long 型の値として表現できない可能性がある。

適合コード (式の再配置)

以下の適合コードでは、lengthSIZE_MAX から引くことでラップアラウンドが発生しないようにしている。詳しくは「INT30-C. 符号無し整数の演算結果がラップアラウンドしないようにする」を参照。

#include <stdlib.h>
#include <stdint.h>

enum { BLOCK_HEADER_SIZE = 16 };

void *AllocateBlock(size_t length) {
  struct memBlock *mBlock;

  if (SIZE_MAX - length < BLOCK_HEADER_SIZE) return NULL;
  mBlock = (struct memBlock *)malloc(
    length + BLOCK_HEADER_SIZE
  );
  if (!mBlock) { return NULL; }
  /* ブロックヘッダにデータを書き込み、データを返す */

  return mBlock;
}
違反コード

以下のコードでは、ラップアラウンドを回避しようとして unsigned long long 型変数 alloc を割り当て、この変数に cBlocks * 16 の結果を代入している。

#include <stdlib.h>
#include <limits.h>

void *AllocBlocks(size_t cBlocks) {
  if (cBlocks == 0) { return NULL; }
  unsigned long long alloc = cBlocks * 16;
  return (alloc < UINT_MAX) ? malloc(cBlocks * 16) : NULL;
}

このコードでは2つの問題が発生する。1つ目の問題は、unsigned long longsize_t より少なくとも4ビット大きい実装を想定しているということである。2つ目の問題は、size_t の値が32ビットで unsigned long long の値が64ビットでは、C言語規格に従い、2つの32ビット値の乗算結果が32ビットになるということである。乗算でラップアラウンドが発生しても検出されず、式 alloc < UINT_MAX は常に真になる。

適合コード

以下の解決法では、オペランド cBlocksunsigned long long にアップキャストされているので、乗算はこのサイズで行われる。

#include <stdlib.h>
#include <assert.h>
#include <limits.h>

static_assert(
  CHAR_BIT * sizeof(unsigned long long) >=
  CHAR_BIT * sizeof(size_t) + 4,
  "乗算で発生したラップアラウンドを検出できない"
);

void *AllocBlocks(size_t cBlocks) {
  if (cBlocks == 0) return NULL;
  unsigned long long alloc = (unsigned long long)cBlocks * 16;
  return (alloc < UINT_MAX) ? malloc(cBlocks * 16) : NULL;
}

ただし、unsigned long long 型が size_t より少なくとも4ビット大きくないと、ラップアラウンドを防げない。

違反コード (size_t)

mbstowcs() 関数はマルチバイト文字列をワイド文字列に変換し、変換した文字数を返す。不正なマルチバイト文字に遭遇すると、(size_t)(-1) を返す。size_t の実装によっては、mbstowcs() の戻り値と整数リテラル -1 との比較は期待通りにならない。

#include <stdlib.h>

void func(wchar_t *pwcs, const char *restrict s, size_t n) {
  size_t count_modified = mbstowcs(pwcs, s, n);
  if (count_modified == -1) {
    /* エラー処理 */
  }
}
適合コード (size_t)

比較を正しく行うには, mbstowcs() の戻り値を size_t にキャストした -1 と比較しなくてはならない。

#include <stdlib.h>

void func(wchar_t *pwcs, const char *restrict s, size_t n) {
  size_t count_modified = mbstowcs(pwcs, s, n);
  if (count_modified == (size_t)-1) {
    /* エラー処理 */
  }
}
リスク評価

キャストせずに整数型をより大きなサイズと比較したり代入すると、脆弱性につながり、脆弱なプロセスの実行権限で任意のコードを実行されてしまう可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

INT18-C

P18

L1

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

Can detect violations of this rule. It should look for patterns of (a op1 b) op2 c where

    • c has a bigger type than a or b
    • Neither a nor b is typecast to c's type
    • op2 is assignment or comparison
Coverity 6.5 OVERFLOW_BEFORE_WIDEN 実装済み

Fortify SCA

5.0

 

CERT C Rule Pack を使用することでこのルールを検出できる

PRQA QA-C 8.1

1890
1891
1892
1893
1894
1895
2790 (C)

部分的に実装済み
関連するガイドライン
CERT C++ Secure Coding Standard INT35-CPP. Evaluate integer expressions in a larger size before comparing or assigning to that size
ISO/IEC TR 24772:2013 Numeric Conversion Errors [FLC]
MITRE CWE CWE-681, Incorrect conversion between numeric types
CWE-190, Integer overflow (wrap or wraparound)
参考資料
[Dowd 2006] Chapter 6, "C Language Issues"
[Seacord 2013] Chapter 5, "Integer Security"
翻訳元

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

INT18-C. Evaluate integer expressions in a larger size before comparing or assigning to that size (revision 83)

Top へ

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