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

ERR33-C. 標準ライブラリ関数のエラーを検出し対処する

入出力関数、メモリ割り当て関数などのすべての標準ライブラリ関数は、有効な値、またはエラーを示す適切な型の返り値(−1、NULLポインタなど)を返す。このような関数の呼び出しがすべて成功すると想定して、返り値でエラーの有無をチェックしなければ、エラーが発生していた場合に予期しない未定義の動作が発生する可能性があり、危険である。ERR00-Cに示されたエラー処理方針に従って、プログラムでエラーをすべて検出し、適切に対処する必要がある。包括的で整合性のあるエラー処理方針を採用すること。

次の表に示す各標準ライブラリ関数が成功終了であるか失敗であるかについては、関数の返り値と「エラー時の返り値」という列に示した値を比較することにより、または同じ列の脚注に記載したライブラリ関数のいずれかを呼び出すことにより、判断する。

関数

成功時の返り値

エラー時の返り値

aligned_alloc()

領域へのポインタ

NULL

asctime_s()

0

ゼロ以外

at_quick_exit()

0

ゼロ以外

atexit()

0

ゼロ以外

bsearch()

一致する要素へのポインタ

NULL

bsearch_s()

一致する要素へのポインタ

NULL

btowc()

変換されたワイド文字

WEOF

c16rtomb()

バイト数

(size_t)(-1)

c32rtomb()

バイト数

(size_t)(-1)

calloc()

領域へのポインタ

NULL

clock()

プロセッサの時刻

(clock_t)(-1)

cnd_broadcast()

thrd_success

thrd_error

cnd_init()

thrd_success

thrd_nomem または thrd_error

cnd_signal()

thrd_success

thrd_error

cnd_timedwait()

thrd_success

thrd_timedout または thrd_error

cnd_wait()

thrd_success

thrd_error

ctime_s()

0

ゼロ以外

fclose()

0

EOF (負数)

fflush()

0

EOF (負数)

fgetc()

読み取った文字

EOF2

fgetpos()

0

ゼロ以外

fgets()

文字列へのポインタ

NULL

fgetwc()

読み取ったワイド文字

WEOF2

fopen()

ストリームへのポインタ

NULL

fopen_s()

0

ゼロ以外

fprintf()

文字数(負数以外)

負数

fprintf_s()

文字数(負数以外)

負数

fputc()

書き出した文字

EOF1

fputs()

負数以外

EOF (負数)

fputws()

負数以外

EOF (負数)

fread()

読み取った要素

読み取った要素

freopen()

ストリームへのポインタ

NULL

freopen_s()

0

ゼロ以外

fscanf()

変換数(負数以外)

EOF (負数)

fscanf_s()

変換数(負数以外)

EOF (負数)

fseek()

0

ゼロ以外

fsetpos()

0

ゼロ以外

ftell()

ファイル位置

−1L

fwprintf()

ワイド文字の数(負数以外)

負数

fwprintf_s()

ワイド文字の数(負数以外)

負数

fwrite()

書き出した要素

書き出した要素

fwscanf()

変換数(負数以外)

EOF (負数)

fwscanf_s()

変換数(負数以外)

EOF (負数)

getc()

読み取った文字

EOF2

getchar()

読み取った文字

EOF2

getenv()

文字列へのポインタ

NULL

getenv_s()

文字列へのポインタ

NULL

gets_s()

文字列へのポインタ

NULL

getwc()

読み取ったワイド文字

WEOF

getwchar()

読み取ったワイド文字

WEOF

gmtime()

要素別の時刻へのポインタ

NULL

gmtime_s()

要素別の時刻へのポインタ

NULL

localtime()

要素別の時刻へのポインタ

NULL

localtime_s()

要素別の時刻へのポインタ

NULL

malloc()

領域へのポインタ

NULL

mblen(), s != NULL

バイト数

−1

mbrlen(), s != NULL

バイト数またはステータス数

(size_t)(-1)

mbrtoc16()

バイト数またはステータス数

(size_t)(-1), errno == EILSEQ

mbrtoc32()

バイト数またはステータス数

(size_t)(-1), errno == EILSEQ

mbrtowc(), s != NULL

バイト数またはステータス数

(size_t)(-1), errno == EILSEQ

mbsrtowcs()

NULL以外の要素の数

(size_t)(-1), errno == EILSEQ

mbsrtowcs_s()

0

ゼロ以外

mbstowcs()

NULL以外の要素の数

(size_t)(-1)

mbstowcs_s()

0

ゼロ以外

mbtowc(), s != NULL

バイト数

−1

memchr()

見つかった文字へのポインタ

NULL

mktime()

カレンダー時間

(time_t)(-1)

mtx_init()

thrd_success

thrd_error

mtx_lock()

thrd_success

thrd_error

mtx_timedlock()

thrd_success

thrd_timedout または thrd_error

mtx_trylock()

thrd_success

thrd_busy または thrd_error

mtx_unlock()

thrd_success

thrd_error

printf_s()

文字数(負数以外)

負数

putc()

書き出した文字

EOF1

putwc()

書き出したワイド文字

WEOF

raise()

0

ゼロ以外

realloc()

領域へのポインタ

NULL

remove()

0

ゼロ以外

rename()

0

ゼロ以外

setlocale()

文字列へのポインタ

NULL

setvbuf()

0

ゼロ以外

scanf()

変換数(負数以外)

EOF (負数)

scanf_s()

変換数(負数以外)

EOF (負数)

signal()

前の関数へのポインタ

SIG_ERR, errno > 0

snprintf()

書き出される文字数(負数以外)

負数

snprintf_s()

書き出される文字数(負数以外)

負数

sprintf()

書き出したNULL以外の文字の数

負数

sprintf_s()

書き出したNULL以外の文字の数

負数

sscanf()

変換数(負数以外)

EOF (負数)

sscanf_s()

変換数(負数以外)

EOF (負数)

strchr()

見つかった文字へのポインタ

NULL

strerror_s()

0

ゼロ以外

strftime()

NULL以外の文字の数

0

strpbrk()

見つかった文字へのポインタ

NULL

strrchr()

見つかった文字へのポインタ

NULL

strstr()

見つかった文字列へのポインタ

NULL

strtod()

変換された値

0, errno == ERANGE

strtof()

変換された値

0, errno == ERANGE

strtoimax()

変換された値

INTMAX_MAX または INTMAX_MIN, errno == ERANGE

strtok()

トークンの最初の文字へのポインタ

NULL

strtok_s()

トークンの最初の文字へのポインタ

NULL

strtol()

変換された値

LONG_MAX または LONG_MIN, errno == ERANGE

strtold()

変換された値

0, errno == ERANGE

strtoll()

変換された値

LLONG_MAX または LLONG_MIN, errno == ERANGE

strtoumax()

変換された値

UINTMAX_MAX, errno == ERANGE

strtoul()

変換された値

ULONG_MAX, errno == ERANGE

strtoull()

変換された値

ULLONG_MAX, errno == ERANGE

strxfrm()

変換された文字列の長さ

>= n

swprintf()

NULL以外のワイド文字の数

負数

swprintf_s()

NULL以外のワイド文字の数

負数

swscanf()

変換数(負数以外)

EOF (負数)

swscanf_s()

変換数(負数以外)

EOF (負数)

thrd_create()

thrd_success

thrd_nomem または thrd_error

thrd_detach()

thrd_success

thrd_error

thrd_join()

thrd_success

thrd_error

thrd_sleep()

0

負数

time()

カレンダー時間

(time_t)(-1)

timespec_get()

タイムベース

0

tmpfile()

ストリームへのポインタ

NULL

tmpfile_s()

0

ゼロ以外

tmpnam()

NULL以外のポインタ

NULL

tmpnam_s()

0

ゼロ以外

tss_create()

thrd_success

thrd_error

tss_get()

スレッド固有ストレージの値

0

tss_set()

thrd_success

thrd_error

ungetc()

押し戻された文字

EOF (負数)

ungetwc()

押し戻された文字

WEOF (負数)

vfprintf()

文字数(負数以外)

負数

vfprintf_s()

文字数(負数以外)

負数

vfscanf()

変換数(負数以外)

EOF (負数)

vfscanf_s()

変換数(負数以外)

EOF (負数)

vfwprintf()

ワイド文字の数(負数以外)

負数

vfwprintf_s()

ワイド文字の数(負数以外)

負数

vfwscanf()

変換数(負数以外)

EOF (負数)

vfwscanf_s()

変換数(負数以外)

EOF (負数)

vprintf_s()

文字数(負数以外)

負数

vscanf()

変換数(負数以外)

EOF (負数)

vscanf_s()

変換数(負数以外)

EOF (負数)

vsnprintf()

書き出される文字数(負数以外)

負数

vsnprintf_s()

書き出される文字数(負数以外)

負数

vsprintf()

NULL以外の文字の数(負数以外)

負数

vsprintf_s()

NULL以外の文字の数(負数以外)

負数

vsscanf()

変換数(負数以外)

EOF (負数)

vsscanf_s()

変換数(負数以外)

EOF (負数)

vswprintf()

NULL以外のワイド文字の数

負数

vswprintf_s()

NULL以外のワイド文字の数

負数

vswscanf()

変換数(負数以外)

EOF (負数)

vswscanf_s()

変換数(負数以外)

EOF (負数)

vwprintf_s()

ワイド文字の数(負数以外)

負数

vwscanf()

変換数(負数以外)

EOF (負数)

vwscanf_s()

変換数(負数以外)

EOF (負数)

wcrtomb()

格納されたバイト数

(size_t)(-1)

wcschr()

見つかったワイド文字へのポインタ

NULL

wcsftime()

NULL以外のワイド文字の数

0

wcspbrk()

見つかったワイド文字へのポインタ

NULL

wcsrchr()

見つかったワイド文字へのポインタ

NULL

wcsrtombs()

NULL以外のバイトの数

(size_t)(-1), errno == EILSEQ

wcsrtombs_s()

0

ゼロ以外

wcsstr()

見つかったワイド文字列へのポインタ

NULL

wcstod()

変換された値

0, errno == ERANGE

wcstof()

変換された値

0, errno == ERANGE

wcstoimax()

変換された値

INTMAX_MAX または INTMAX_MIN, errno == ERANGE

wcstok()

トークンの最初のワイド文字へのポインタ

NULL

wcstok_s()

トークンの最初のワイド文字へのポインタ

NULL

wcstol()

変換された値

LONG_MAX または LONG_MIN, errno == ERANGE

wcstold()

変換された値

0, errno == ERANGE

wcstoll()

変換された値

LLONG_MAX または LLONG_MIN, errno == ERANGE

wcstombs()

NULL以外のバイトの数

(size_t)(-1)

wcstombs_s()

0

ゼロ以外

wcstoumax()

変換された値

UINTMAX_MAX, errno == ERANGE

wcstoul()

変換された値

ULONG_MAX, errno == ERANGE

wcstoull()

変換された値

ULLONG_MAX, errno == ERANGE

wcsxfrm()

変換されたワイド文字列の長さ

>= n

wctob()

変換された文字

EOF

wctomb(), s != NULL

格納されたバイト数

−1

wctomb_s(), s != NULL

格納されたバイト数

−1

wctrans()

towctrans への有効な引数

0

wctype()

iswctype への有効な引数

0

wmemchr()

見つかったワイド文字へのポインタ

NULL

wprintf_s()

ワイド文字の数(負数以外)

負数

wscanf()

変換数(負数以外)

EOF (負数)

wscanf_s()

変換数(負数以外)

EOF (負数)

「FIO35-C. sizeof(int) == sizeof(char) の場合、EOF およびファイルエラーの検出に feof() と ferror() を使用する」が適用される場合、関数の呼び出し元はこの表の関数が成功したか失敗したかを次のいずれかの方法で判断する。

  1. ferror を呼び出す
  2. ferrorfeof を呼び出す

ungetc() 関数は失敗してもエラー表示子を設定しないため、引数が EOF と等しくないことがわかっていない限り、確実にエラーを検査することはできない。C言語仕様[ISO/IEC 9899:2011]は「1文字の押戻しを保証する」と定めているため、再度読み取る前に最大1文字しか押し戻さないのであれば問題はない(「FIO13-C. 読み取った一文字以外は押し戻さない」を参照)。

違反コード (setlocale())

以下のコード例では、関数 utf8_to_ucs() は一連のUTF-8文字をUCSに変換する。最初に setlocale() を呼び出してグローバルロケールを "en_US.UTF-8" に設定するが、失敗のチェックは行わない。ロケールがインストールされていない場合などに setlocale() はエラーとなり、NULLポインタを返す(関数は、リソースの不足などの理由でエラーとなることもある)。utf8 が指し示す一連の文字によって、その後の mbstowcs() の呼び出しがエラーになったり、用意されたバッファ ucs に予期しない一連のワイド文字が格納されたりすることがある。

size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) {
  setlocale(LC_CTYPE, "en_US.UTF-8");
  return mbstowcs(ucs, utf8, n);
}
適合コード (setlocale())

以下のコード例では、setlocale() からの返り値をチェックし、関数が失敗した場合は mbstowcs() を呼び出さない。また、制御を関数呼び出し元に返す前に、ロケールを初期設定に戻す。

size_t utf8_to_ucs(wchar_t *ucs, size_t n, const char *utf8) {
  const char *save;
  save = setlocale(LC_CTYPE, "en_US.UTF-8");
  if (NULL == save) {
    /* 関数呼び出し元へのエラーの伝播 */
    return (size_t)-1;
  }

  n = mbstowcs(ucs, utf8, n);
  if (NULL == setlocale(LC_CTYPE, save))
    n = -1;

  return n;
}
違反コード (signal())

以下のコード例では、SIGINT シグナル用のハンドラをインストールするために関数 signal() が呼び出される。signal() は、成功時は前にインストールされたハンドラへのポインタを返し、失敗時は値 SIG_ERR を返す。ただし、関数呼び出し元はエラーのチェックを行わないので、signal() がエラーになっても、中断できずに長い処理が続いてしまうことがある。

volatile sig_atomic_t interrupted;

void handle_interrupt(int signo) {
  interrupted = 1;
}

int f() {
  int result = 0;

  signal(SIGINT, handle_interrupt);

  while (0 == result && 0 == interrupted) {
    /* 長い処理の実行 */
  }

  /* 成功か失敗かを示す */
  return interrupted ? -1 : result;
}
適合コード (signal())

以下のコード例では、signal() 関数からの返り値をチェックし、signal() 関数が失敗した場合は長い処理を実行しない。関数の呼び出しでは、制御を関数呼び出し元に返す前に SIGINT シグナルを初期設定に戻す。

volatile sig_atomic_t interrupted;

void handle_interrupt(int signo) {
  interrupted = 1;
}

int f() {
  int result = 0;
  void (*saved_handler)(int);
  saved_handler = signal(SIGINT, handle_interrupt);

  if (SIG_ERR == saved_handler) {
    /* 失敗を示す */
    return -1;
  }

  while (0 == result && 0 == interrupted) {
    /* 長い処理の実行 */
  }

  if (SIG_ERR == signal(SIGINT, saved_handler))
    return -1;

  /* 成功か失敗かを示す */
  return interrupted ? -1 : result;
}
違反コード (malloc())

以下のコード例では、temp_numtmp2、および num_of_records は悪意のあるユーザによる制御の影響下にある。攻撃者は、num_of_records に大きな値を割り当てることにより、容易に malloc() を失敗させることができる。

signal_info * start = malloc(num_of_records * sizeof(signal_info));
signal_info * point = (signal_info *)start;
point = start + temp_num - 1; 
memcpy(point->sig_desc, tmp2, strlen(tmp2));
/* ... */ 

malloc() は失敗すると NULL ポインタを返し、これは start に代入される。start がNULLであっても、攻撃者は、sizeof signal_info によってスケールされた場合に最終的に制御が移される書き込み可能アドレスを参照する temp_num の値を指定できる。tmp2 によって参照される文字列の内容はアドレスの上書きに使用でき、任意のコードが実行される脆弱性につながる。

適合コード (malloc())

この誤りを修正するには、malloc() が返すポインタが NULL でないことを確認すること。こうすることでさらに「MEM32-C. メモリ割り当てエラーを検出し、対処する」に適合することにもなる。

signal_info * start = malloc(num_of_records * sizeof(signal_info));
if (start == NULL) {
  /* 割り当てエラーの処理 */
}
signal_info * point = (signal_info *)start;
point = start + temp_num - 1; 
memcpy(point->sig_desc, tmp2, strlen(tmp2));
/* ... */ 
違反コード (malloc())

以下の違反コードでは、input_stringstr が参照する動的に割り当てられたメモリにコピーされる。しかし、str を参照する前に malloc() の結果をチェックしていない。この結果、malloc() が失敗した場合、プログラムは未定義の動作となる(C言語仕様の付属書Jに記載されている未定義の動作109を参照)。実際には、一般的にプロセスの異常終了が発生し、サービス運用妨害(DoS)攻撃の機会を与えることになる。場合によっては、ほかの脆弱性の原因になることもある(「関連する脆弱性」のセクションを参照)。「MEM32-C. メモリ割り当てエラーを検出し、対処する」も参照すること。

void f(char *input_string) {
  size_t size = strlen(input_string) + 1;
  char *str = (char *)malloc(size);
  strcpy(str, input_string);
  /* ... */
}
適合コード (malloc())

malloc() 関数は、他のメモリ割り当て関数と同様に、NULL ポインタまたは割り当てられたメモリ領域へのポインタのどちらかを返す。返されたポインタは必ず検査し、ポインタを参照する前にそのポインタがNULLではないことを確認する必要がある。返されたポインタがNULLの場合、適切にエラー条件を処理すること。割り当て失敗からの回復が不可能な場合、失敗を関数呼び出し元に伝播する。

int f(char *input_string) {
  size_t size = strlen(input_string) + 1;
  char *str = (char *)malloc(size);
  if (str == NULL) {
    /* 割り当て失敗の処理をし、エラーステータスを返す */
    return -1;
  }
  strcpy(str, input_string);
  /* ... */
  free(str);
  return 0;
}
違反コード (realloc())

以下のコードでは、realloc() により p が参照するメモリのサイズを変更している。しかし、realloc() は失敗すると NULL ポインタを返す。この結果、元のメモリブロックと p の間の関連が切断され、メモリリークを引き起こす。

void *p;
size_t new_size = /* ゼロでない値 */;

p = realloc(p, new_size);
if (p == NULL)   {
 /* エラー処理 */
}

realloc() を使用する場合、ゼロバイトの割り当ての場合を考慮することが重要である(「MEM04-C. サイズ 0 のメモリ割り当てを行わない」を参照。)

適合コード (realloc())

以下のコード例では、realloc() の結果を、元のポインタ p に代入する前に仮のポインタ q に代入し、妥当性を確認している。

void *p;
void *q;
size_t new_size= /* ゼロでない値 */;

q = realloc(p, new_size);
if (q == NULL)   {
 /* エラー処理 */
}
else {
  p = q;
}
違反コード (fseek())

以下のコード例では、fseek() 関数を使用して、ファイルからのバイトの読み出しを行う前に、file が参照しているファイル内の offset の位置にファイル位置を設定する。しかし、seek 操作で入出力エラーが発生すると、その後の読み出しではバッファが不正な内容で満たされる。

size_t read_at(FILE *file, long offset, void *buf, size_t nbytes) {
  fseek(file, offset, SEEK_SET);
  return fread(buf, 1, nbytes, file);
}
適合コード (fseek())

C言語仕様によると、fseek() 関数は、エラーが発生したことを示すゼロ以外の値を返す。ファイルからの読み出しを行う前にこの状態を検査することで、fseek() が失敗した場合にそのファイルの間違った部分を操作する可能性を排除する。ファイルに対する処理を行う前には常に返り値をチェックし、エラーが発生していないことを確認すること。エラーが発生した場合は、適切に処理すること。

size_t read_at(FILE *file, long offset, void *buf, size_t nbytes) {
  if (fseek(file, offset, SEEK_SET) != 0) {
    /* 関数呼び出し元にエラーを通知 */
    return 0;
  }
  return fread(buf, 1, nbytes, file);
}
違反コード (snprintf())

以下のコード例では、snprinf() が成功することを想定している。しかし、(GNU libc bug 441945 に示すように、メモリの不足などにより)呼び出しに失敗すると、文字バッファは初期化されず、NULLで終端することが必須でないため、その後の log_message() の呼び出しで未定義の動作が発生する可能性がある。

extern void log_message(const char*);

void f(int i, int width, int prec) {
  char buf[40];
  snprintf(buf, sizeof buf, "i = %*.*i", width, prec, i);
  log_message(buf);
  /* ... */
}
適合コード (snprintf())

以下のコード例では、どのような引数でも snprintf() が成功するという想定は避け、関数の出力先となるバッファを使用する前に関数の返り値をテストする。また、snprintf() で終端のNULLを付加するには用意されたバッファが不十分な場合、書式付き文字列をNULLで終端する。

extern void log_message(const char*);

void f(int i, int width, int prec) {
  char buf[40];
  int n;
  n = snprintf(buf, sizeof buf, "i = %*.*i", width, prec, i);
  if (n < 0) {
    /* snprintf() のエラーを処理 */
    strcpy(buf, "unknown error");
  }

  /* オーバーフローする場合にバッファをNULLで終端 */
  buf[sizeof buf - 1] = '\0';    
  log_message(buf);
}

書式付き文字列の長さに制限がない場合、関数を2回呼び出すことを推奨する。1回目は出力のサイズを確認するために NULL バッファを使用して呼び出し、十分なサイズのバッファを動的に割り当ててから再度 snprintf() を呼び出し、動的に割り当てたバッファの出力を作成する。このアプローチを採用した場合でも、すべての呼び出しが成功したかどうかをテストする必要があり、エラーが発生した場合は適切に対処する必要がある。最適な処理を行うには、まず文字列をスタック上に割り当てられた比較的小さなバッファとして作成し、バッファが小さすぎる場合のみ十分なサイズのバッファを動的に割り当てる。このアプローチを以下のコード例に示す。

void f(int i, int width, int prec) {
  char buffer[20];
  char *buf = buffer;
  int n  = sizeof buffer;
  const char fmt[] = "i = %*.*i";

  n = snprintf(buf, n, fmt, width, prec, i);
  if (n < 0) {
    /* snprintf() のエラーを処理 */
    strcpy(buffer, "unknown error");
    goto write_log;
  }

  if (n < sizeof buffer)
    goto write_log;

  buf = (char*)malloc(n + 1);
  if (NULL == buf) {
    /* malloc() エラーの処理 */
    strcpy(buffer, "unknown error");
    goto write_log;
  }

  n = snprintf(buf, n, fmt, width, prec, i);
  if (n < 0) {
    /* snprintf() のエラーを処理 */
    strcpy(buffer, "unknown error");
  }

write_log:
  log_message(buf);

  if (buf != buffer)
    free(buf);
}

このコード例は、「MEM32-C. メモリ割り当てエラーを検出し、対処する」に従ってメモリ割り当てエラーに正しく対処し、「MEM12-C. リソースの使用および解放の最中に発生するエラーが原因で関数を終了する場合に、Goto 連鎖の使用を検討する」での提案に従って goto を使用している。

処理系固有の詳細

POSIX

前述のC言語標準ライブラリ関数のほか、POSIXで定義された次の関数においてエラーチェックが必要となる(以下のリストは包括的なものではない)。

関数

成功時の返り値

エラー時の返り値

errno

fmemopen()

FILE オブジェクトへのポインタ

NULL

ENOMEM

open_memstream()

FILE オブジェクトへのポインタ

NULL

ENOMEM

posix_memalign()

0

ゼロ以外

変更なし

errno の設定は、POSIX [ISO/IEC 9945:2008] によってC言語仕様が拡張されたものである。エラー時、posix_memalign()<errno.h> ヘッダで定義された定数のいずれかに対応する値を返す。関数は errno を設定しない。posix_memalign() 関数はオプションで、POSIXに合致する処理系による提供は必須ではない。

違反コード (POSIX)

以下のコード例では、fmemopen()open_memstream() が成功することを想定している。しかし、呼び出しに失敗した場合、2 つのファイルポインタ in および out は NULL となり、プログラムは未定義の動作となる。

int main(int argc, char *argv[])
{
FILE *out, *in;

if (argc != 2) {
	/* エラー処理 */
}

in = fmemopen(argv[1], strlen(argv[1]), "r"); /* 違反 */
/* in の使用 */

out = open_memstream(&ptr, &size); /* 違反 */
/* out の使用 */

}
適合コード (POSIX)

以下のコード例では、どのような引数でも fmemopen()open_memstream() が成功するという想定は避け、ファイルポインタ in および out を使用する前に関数の返り値をテストする。

int main(int argc, char *argv[])
{
FILE *out, *in;

if (argc != 2) {
	/* エラー処理 */
}

in = fmemopen(argv[1], strlen(argv[1]), "r");


if (in == NULL){
	/* エラー処理 */
}
/* in の使用 */

out = open_memstream(&ptr, &size);

if (out == NULL){
	/* エラー処理 */

}
/* out の使用 */

}

例外

EXP12-EX1:EXP12-C. 関数の返り値を無視しない」からの例外も適用される。返り値が重要でないあるいはエラーを無視しても安全なら(たとえば副作用の結果呼び出された関数の場合)、その関数は明示的に void にキャストし、プログラマの意図を示すべきである。この例外の例としては、「FIO10-C. rename() 関数の使用に注意する」の「可搬性のある動作」にある適合コード(変更先の名前の既存ファイルを削除したい場合)を参照のこと。

ERR33-EX1: 関数呼び出しが必ず成功するあるいは返り値がエラー条件を示さないのであれば、返り値を無視でき、診断する必要がない。このような関数には、strcpy() などがある。

次の関数ではエラーチェックが省略されるのが普通であったため、返り値のチェックは不要で、セキュリティへの影響もない。

関数

成功時の返り値

エラー時の返り値

printf()

文字数(負数以外)

負数

putchar()

書き出した文字

EOF

puts()

負数以外

EOF (負数)

putwchar()

書き出したワイド文字

WEOF

vprintf()

文字数(負数以外)

負数

vwprintf()

ワイド文字の数(負数以外)

負数

wprintf()

ワイド文字の数(負数以外)

負数

リスク評価

エラー条件の検出を行わないと、プログラムの異常終了、サービス運用妨害(DoS)攻撃などの予期しない結果につながり、場合によっては攻撃者に任意のコードの実行を許してしまうこともある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

ERR33-C

P18

L1

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

   

「EXP12-C. 関数の返り値を無視しない」および「EXP34-C. NULL ポインタを参照しない」に対する違反がないか検査することによって、このレコメンデーションの違反を検出できる。

Coverity

6.5

CHECKED_RETURN

関数呼び出しから返される値の処理方法の不一致を検出する。Coverity Prevent では、このレコメンデーションに対する違反をすべて検出できるとは限らないため、さらなる検証が必要である。

Fortify SCA

5.0

 

 

LDRA tool suite

V. 8.5.4

80 D

部分的に実装済み
PRQA QA-C 8.1

3200

部分的に実装済み
関連コーディング手法

コーディング手法

深刻度

可能性

修正コスト

優先度

レベル

EXP12-C. 関数の返り値を無視しない

P2

L3

FLP03-C. 浮動小数点エラーを検知して処理する

P2

L3

FLP32-C. 数学関数における定義域エラーおよび値域エラーを防止または検出する

P8

L2

MEM32-C. メモリ割り当てエラーを検出し、対処する

P18

L1

FIO04-C. 入出力エラーを検出し、処理する

P4

L3

FIO33-C. 未定義の動作となる入出力のエラーを検出して処理する

P12

L1

FIO35-C. sizeof(int) == sizeof(char) の場合、EOF およびファイルエラーの検出には feof() と ferror() を使用する

P2

L3

ERR00-C. エラー処理には一貫性のある方針を採用する

P4

L3

ERR02-C. 正常終了時の値とエラーの値は別の手段で通知する

P1

L3

ERR05-C. アプリケーション非依存なコードではエラー検知のみ行ない、エラー処理は行わない

P4

L3

API04-C. 一貫性があり使いやすいエラー検査方法を提供する

P2

L3

MSC02-C. 省略のエラーを回避する

P6

L2

MSC03-C. 追加のエラーを回避する

P6

L2

関連する脆弱性

Adobe Flashの脆弱性 [VU#159523] は、Flashが calloc() からの返り値をチェックしないことに起因する。calloc()NULL を返す場合でも、Flashは返り値からのオフセットに書き出す。通常、NULL を参照するとプログラムクラッシュが発生するが、NULL からのオフセットを参照した場合、プログラムをクラッシュさせずに悪用することができてしまう。

関連するガイドライン
CERT C++ Secure Coding Standard ERR10-CPP. Check for error conditions
FIO04-CPP. Detect and handle input and output errors
ISO/IEC TR 17961 (ドラフト) Failing to detect and handle standard library errors [liberr]
MITRE CWE CWE-252, Unchecked return value
CWE-253, Incorrect check of function return value
CWE-390, Detection of error condition without action
CWE-391, Unchecked error condition
参考資料
[DHS 2006] Handle All Errors Safely
[Henricson 1997] Recommendation 12.1, "Check for All Errors Reported from Functions"
[ISO/IEC 9899:2011] Section 7.21.7.10, "The ungetc Function"
翻訳元

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

ERR33-C. Detect and handle standard library errors (revision 27)

Top へ

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