ある程度プログラミングにも慣れ、言語仕様もそこそこ理解した中級者にとって意外と陥りやすくセキュリティホールの温床になりがちなワナを幾つか。
勿論オンラインマニュアルにも仕様として明記されているのですが、直感とは反する仕様のなので特に中級者にこれらのミスが多い気がするので注意喚起を込めての記事です。
strncpy (3) は \0 終端してくれない場合がある
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 終端が必要になると思うので、以下の様な処理が代替になります。
char *
mystrncpy(char *dst, const char *src, size_t len)
{
strncpy(dst, src, len - 1);
*(dst + len - 1) = '\0';
return(dst);
}
strncat (3) のサイズ指定はコピー先のサイズを指定するのではない
strcat (3) も同様にバッファオーバーフローの危険性があるので strncat (3) を利用する様によく言われますが、 ここにもワナが潜んでいます。
strncat(dst, src, len);
とした時の len はコピーする src の長さであって、 strncpy (3) や snprintf (3)の様に dst のサイズ指定ではありません。ここで dst のサイズのつもりで len を指定するとバッファオーバーフローの原因になってしまいますので要注意。
殆んどの場合は dst の最大値が重要になると思うので以下の処理が代替になります。
char *
mystrncat(char *restrict dst, const char *restrict src, size_t len)
{
return(strncat(dst, src, len - strlen(dst) - 1));
}
snprintf (3) は領域が重なってはいけない
前述した通りに使いやすいとは言いづらい 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) は重なった領域では正しく動作しない様です。
FreeBSD のオンラインマニュアルには
int
snprintf(char * restrict str, size_t size, const char * restrict format, ...);となっているので確かに重なった領域はダメな様ですが規格的な根拠> はいずこ?
以上、C によるプログラミングに慣れた頃に陥りがちなワナでした