Cyrus imapdでの誤用例
次にCyrus imapdにおけるsizeofオペレータの誤用例を見てみましょう。以下のコードはCyrus imapdのコードからの抜粋です。
int do_action_list(..., char *actions_string, ...){
......
switch(a->a){
case ACTION_REJECT:
......
snprintf(actions_string + strlen(actions_string),
sizeof(actions_string) - strlen(actions_string),
"Rejected with: %s\n", a->u.rej.msg);
break;
case ACTION_FILEINTO:
......
snprintf(actions_string + strlen(actions_string),
sizeof(actions_string) - strlen(actions_string),
"Filed into: %s\n", a->u.fil.mailbox);
break;
case ACTION_KEEP:
......
snprintf(actions_string + strlen(actions_string),
sizeof(actions_string) - strlen(actions_string),
"Kept\n");
break;
case ACTION_REDIRECT:
......
snprintf(actions_string + strlen(actions_string),
sizeof(actions_string) - strlen(actions_string),
"Redirected to %s\n", a->u.red.addr);
break;
......
}
}
このswitch文は、a->aの内容に応じた処理を行い、actions_stringが指すメモリ領域にログ情報を書き足していく部分です。各case節の中では、actions_stringの指すメモリ領域の、既に書き込まれている文字列の直後に新たにログ情報を書き込んでいます。その際、確保されているメモリ領域をはみ出さないように、snprintf()を使って、sizeof(actions_string)-strlen(actions_string)という式によって、書き込む文字数を制限しています。.
しかし actions_stringはポインタでした。従ってsizeof(actions_string) は、actions_stringが指すメモリ領域のサイズではなく、ポインタのサイズを表しています。それは意図していたものよりずっと小さい数値であることは間違いありません。その結果、(ログが書き込まれていくにつれて)sizeof(actions_string)-strlen(actions_string)は負の値になってしまうと考えられます。しかし、snprintf()の引数としてはsize_t、すなわち符号無し整数型として扱われるため、大きな正の値として解釈され、実質的に書き込む文字数を制限できていないということになります。
さらに悪いことに、書き込むログ情報を、攻撃者がある程度操作できる状況でした。攻撃者は入力を工夫することにより、actions_stringの指すメモリ領域の後ろに任意の値を書き込ませることができたのです。
上書きされる部分に、プログラムの挙動を制御する変数やライブラリ関数のエントリテーブルなどが置かれていた場合、攻撃者はプログラムを乗っ取ることができる可能性が高くなります。実際、この問題は任意のコード実行に繋がる脆弱性としてCVEに登録されています(CVE-2009-2632、CERT/CC VU#336053)。
この問題を修正するために、開発者は、sizeofオペレータによるメッセージ領域のサイズの計算を止めるという方法を選択しました。
- 確保するメモリ領域の大きさを表すシンボルを定義
- snprintf()はすべて次のように修正:
#define ACTIONS_STRING_LEN 4096
snprintf(actions_string + strlen(actions_string),
ACTIONS_STRING_LEN - strlen(actions_string),
"Redirected to %s\n", a->u.red.addr);
これによって、当初の意図どおりの動作を得ることができました。
#define ACTIONS_STRING_LEN 4096
int do_action_list(..., char *actions_string, ...){
......
switch(a->a){
case ACTION_REJECT:
......
snprintf(actions_string + strlen(actions_string),
ACTIONS_STRING_LEN - strlen(actions_string),
"Rejected with: %s\n", a->u.rej.msg);
break;
case ACTION_FILEINTO:
......
snprintf(actions_string + strlen(actions_string),
ACTIONS_STRING_LEN - strlen(actions_string),
"Filed into: %s\n", a->u.fil.mailbox);
break;
case ACTION_KEEP:
......
snprintf(actions_string + strlen(actions_string),
ACTIONS_STRING_LEN - strlen(actions_string),
"Kept\n");
break;
case ACTION_REDIRECT:
......
snprintf(actions_string + strlen(actions_string),
ACTIONS_STRING_LEN - strlen(actions_string),
"Redirected to %s\n", a->u.red.addr);
break;
......
}
}
今回は、sizeofオペレータの使用上の注意点とそれに関連した誤用例を紹介しました。今回紹介した2つの誤用例はどちらも過去のバージョンも含めてソースコードが公開されています。具体的にどのような間違いだったか、どのように修正したのか、を確かめてみてください。
参考資料
- C言語仕様セクション6.5.3.4 sizeof
- EXP01-C: ポインタが参照する型のサイズを求めるのにポインタのサイズを使わない
- ARR01-C. Do not apply the sizeof operator to a pointer when taking the size
- EXP06-C: sizeof 演算子のオペランドは副作用を持たせない
- PNG (Portable Network Graphics) Specification, Version 1.2 4. Chunk Specifications
- CVE-2006-5793
- Cyrus IMAPd buffer overflow vulnerability
- CVE-2009-2632
