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

EXP05-C. const 修飾をキャストではずさない

ポインタ型のオブジェクトに対するconst修飾をキャストしてはずさないこと。const 修飾をキャストして外すと、ポインタによって参照されるオブジェクトをプログラムで変更できるようになり、未定義の動作となる可能性がある。C 標準の附属書 J 「未定義の動作」の 64 を参照すること。

C 標準 [ISO/IEC 9899:2011] の脚注には以下のように記載されている (セクション 6.7.3, para. 4):

処理系は、volatileでないconstオブジェクトを、読み取り専用記憶域に置いてもよい。さらに、処理系はそのアドレスが使われないならば、そのようなオブジェクトに記憶域を割り付けなくてもよい。

違反コード

以下のコード例の remove_spaces() 関数は、引数として文字列へのポインタ str および文字列長 slen をとり、文字列から空文字を取り除き、残りの文字を文字列の先頭に向かってシフトする。関数 remove_spaces()const char へのポインタを引数としてとる。const 修飾子はキャストの結果はずされ、文字列の内容が変更されている。

void remove_spaces(const char *str, size_t slen) {
  char *p = (char *)str;
  size_t i;
  for (i = 0; i < slen && str[i]; i++) {
    if (str[i] != ' ') *p++ = str[i];
  }
  *p = '\0';
}
適合コード

以下の適合コードでは、remove_spaces() 関数に const修飾していない char型ポインタを渡している。関数呼出しでは、文字列のコピーを作成するあるいはなにか他の方法で、関数に渡されるnull終端バイト文字列がconst修飾されていないことを保証しなくてはならない。

void remove_spaces(char *str, size_t slen) {
  char *p = str;
  size_t i;
  for (i = 0; i < slen && str[i]; i++) {
    if (str[i] != ' ') *p++ = str[i];
  }
  *p = '\0';
}
違反コード

以下のコード例では、const int 型配列 vals の内容が memset() 呼び出しによってクリアされている。

const int vals[3] = {3, 4, 5};
memset(vals, 0, sizeof(vals));

memset() 関数は const 修飾されていない void型へのポインタを引数としてとるため、コンパイラは暗黙的にconstをキャストではずさなくてはならない。

処理系固有の詳細

GCC は暗黙的なキャストに警告を出す。

適合コード

コードの意図が配列の変更をゆるすのであるならば、配列をconstとして宣言しないこと。

int vals[3] = {3, 4, 5};
memset(vals, 0, sizeof(vals));

そうでないならば、配列の内容を変更しようとしないこと。

例外

EXP05-EX1:const 引数を受け付けないレガシーAPIを呼び出す際 const をキャストで外さなければならない場合は、その関数が参照される変数を変更しない限りにおいて、このレコメンデーションの例外として良い。たとえば、以下のコードでは、audit_log() 呼び出し時に INVFNAMEconst 修飾をキャストではずしている。

/* どこか別の場所で定義されたレガシー関数 - 変更できない */
void audit_log(char *errstr) {
  fprintf(stderr, "Error: %s.\n", errstr);
}

/* ... */
const char INVFNAME[] = "Invalid file name.";
audit_log((char *)INVFNAME); /* EXP05-EX1 */
/* ... */

EXP05-EX2: 多数の C 標準ライブラリ関数が、const 修飾された引数を参照する const 修飾していないポインタを返すよう指定されている。このような関数の実際の引数が const オブジェクトを参照する場合、返された const 修飾していないポインタを使用して const オブジェクトを変更しようとすると、「EXP40-C. 定数値は変更しない」の違反となり、未定義の動作となる。これらの関数は以下のとおりである。

memchr

strchr

strpbrk

strrchr

strstr

strtod

strtof

strtold

strtol

strtoll

strtoul

strtoull

wmemchr

wcschr

wcspbrk

wcsrchr

wcsstr

 

 

 

たとえば、次の例では、関数 strchr は、定数文字配列 s (ROM に格納可能) の null 終端文字を指す非修飾型の char* を返す。ポインタは const 型ではないが、指している文字を変更しようとすると未定義の動作となる。

  extern const char s[];
  char* where;
  where = strchr(s, '\0');
  /* *s を変更すると未定義の動作となる */

同様に、次の例では、関数 strtol は、end によって参照される非修飾型の char* ポインタを、定数文字配列 s (ROM に格納可能) の中で正常に解析された最後の文字の次のポイントに設定する。ポインタは const 型ではないが、指している文字を変更しようとすると未定義の動作となる。

  extern const char s[];
  long x;
  char* end;
  x = strtol(s, &end, 0);
  /* *end を変更すると未定義の動作となる */

EXP05-EX3: const は「定数」ではなく「読み取り専用」を意味するため、診断を行うために struct メンバを const オブジェクト (へのポインタ) として宣言することが有益な場合もある。たとえば、そのデータ型が維持されるよう特別に設計された関数を使用しないでこのメンバを変更しようとする場合などである。ただし、これらの関数内でこれらのメンバを更新するために const 修飾をはずす必要がある場合もある。

リスク評価

オブジェクトが定数であると、コンパイラはROMや書き込み禁止メモリ領域に記憶域を割り当てるかもしれない。そのようなオブジェクトを変更しようとすると、プログラムがクラッシュしたりサービス運用妨害攻撃につながる恐れがある。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

EXP05-C

P8

L2

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

 

ECLAIR

1.1

castexpr

実装済み

GCC

V. 4.3.5

 

GCC は -Wcast-qual フラグを使用することでこのレコメンデーションの違反を検出できる。

LDRA tool suite

V. 8.5.4

203 S

実装済み

PRQA QA-C 8.1 0311 実装済み
関連するガイドライン
CERT C++ Secure Coding Standard EXP35-CPP. Do not cast away a const qualification
ISO/IEC TR 24772:2013 Pointer Casting and Pointer Type Changes [HFC]
Type System [IHN]
MISRA-C Rule 11.5
MITRE CWE CWE-704, Incorrect type conversion or cast
参考資料
[ISO/IEC 9899:2011] Section 6.7.3, "Type Qualifiers"
翻訳元

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

EXP05-C. Do not cast away a const qualification (revision 116)

Top へ

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