2012/02/22
C 中級者が意外と陥りやすいワナ
ある程度プログラミングにも慣れてきて
言語仕様もそこそこ理解した中級者にとって
意外と陥りやすくセキュリティホールの温床になりがちなワナ。
勿論オンラインマニュアルにも仕様として明記してあるのだが、
直感とは反する仕様のなので特に中級者にこれらのミスが多い気がする。
- strncpy(3) は ``\0'' 終端してくれない場合がある
- strncat(3) のサイズ指定はコピー先のサイズを指定するのではない
- snprintf(3) は領域が重なってはいけない
strcpy(3) はバッファオーバーフローの危険性があるから
strncpy(3) を利用する様によく言われるが、
ここにワナが潜んでいる。
strncpy(dst, src, len);
とした時に
文字列 src の長さが len バイト未満の場合は
dst は ``\0'' で終端される。
終端どころか dst の残り領域は何故か
全て ``\0'' が詰められるという
無駄とも思われる謎仕様。
ところが文字列 src の長さが len バイト以上の場合、
dst に len バイトだけコピーするという
memmove(3) 同様の動作となってしまい、
dst が ``\0'' で終端されない。
文字列操作の関数なのにも関わらず得られた結果が
文字列として扱えないという不思議な仕様なのだ。
snprintf(3) などの様に
len - 1 バイトを dst にコピーして
``\0'' で終端される事を期待していると
痛い目に遭ってしまう。
文字列操作関数なので、
殆んどの場合 ``\0'' 終端が必要になると思うので
以下の様な処理が代替になる。
strncpy(dst, src, sizeof(dst) - 1); *(dst + sizeof(dst) - 1) = '\0';
strcat(3) も同様にバッファオーバーフローの危険性があるので
strncat(3) を利用する様によく言われるが、
ここにもワナが潜んでいる。
strncat(dst, src, len);
とした時の len はコピーする src の長さであって、
前述の strncpy(3) や snprintf(3)の様に
dst のサイズ指定ではない
ここで dst のサイズのつもりで len を指定すると
バッファオーバーフローの原因になってしまう。
殆んどの場合は dst の最大値が重要になると思うので
以下の処理が代替になる。
strncat(dst, src, sizeof(dst) - strlen(src) - 1);
前述した通りに使いやすいとは言いづらい strncpy(3) や
strncat(3) の代用として、
コピー先のサイズ指定が直感的な
snprintf(3) を利用する場合も多いと思うが、
strncat(dst, src, len) の置き換えとして
snprintf(dst, sizeof(dst), "%s%s", dst, src);
とすると正しく動作しない(結果は処理系依存になる)。
ANSI X3.159-1989 (ANSI C) や ISO/IEC 9899:1999 (ISO C99) 、
IEEE Std 1003.1-1988 (POSIX.1) 等をざっと探したけど
関連する記述は見つける事はできなかったが、
snprintf(3) は重なった領域では正しく動作しない様だ。