🔤 文字列
🎯 バッファオーバーフローの危険を理解し、snprintfやfgetsで安全に書ける
文字列を扱う配列のことを、バッファ(buffer)と呼びます。そしてCの歴史上、最も多くの被害を出してきたバグが、このバッファから溢れる事故です。
char name[5];
strcpy(name, "Alexander"); // 9文字+'\0' で10バイト必要なのに、箱は5つコンパイルは通ります。実行すると、strcpy は行き先の大きさを知らないまま10バイトを書き込み、配列の外へはみ出します。隣の変数が壊れる、関数の戻り先が壊れる、あるいは何事もなく動いてしまう——境界外アクセスそのものです。
これがバッファオーバーフローで、攻撃者が意図的に長いデータを送り込んでプログラムを乗っ取る手口としても知られています。
書き込む前に、入る大きさを渡す
対策の原則はひとつ。行き先の大きさを関数に教えることです。いちばん使いやすいのが snprintf です。
#include <stdio.h>
int main(void)
{
char name[5];
snprintf(name, sizeof(name), "%s", "Alexander");
printf("%s\n", name); // Alex(4文字+'\0'。溢れずに切り詰められる)
return 0;
}snprintf(行き先, 行き先の大きさ, 書式, …) の形です。指定した大きさを超えることはなく、末尾には必ず '\0' が付きます。書式指定子が使えるので、数値と文字列を混ぜて組み立てることもできます。
strncpy の落とし穴
「n付き」の strncpy もありますが、こちらは注意が必要です。コピー元が長いと、'\0' が付かないまま打ち切られます。
char dst[5];
strncpy(dst, "Alexander", sizeof(dst)); // "Alex" までコピー、'\0' なし!
dst[sizeof(dst) - 1] = '\0'; // 自分で終端する必要がある安全そうな名前なのに一手間が要る、というのがCらしいところです。迷ったら snprintf を選ぶのが無難です。
入力も同じ考え方
キーボードからの入力にも同じ危険があります。scanf の %s は入力の長さを制限しないため、長い入力で簡単に溢れます。文字列の入力には fgets を使いましょう。
#include <stdio.h>
int main(void)
{
char line[64];
if (fgets(line, sizeof(line), stdin) != NULL) {
printf("入力: %s", line);
}
return 0;
}fgets(行き先, 大きさ, stdin) も大きさを渡す形です。なお fgets は改行文字も一緒に取り込むので、必要なら取り除きます(t09-08で扱います)。かつて存在した gets という関数は、大きさを指定できず危険すぎたため、標準から削除されました。
罠・注意
sizeof(buf) を大きさとして渡すのが定石です。数字を直接書くと、配列の大きさを変えたときに直し忘れます。まとめ: 文字列を書き込む関数には、必ず行き先の大きさを渡す。strcpy より snprintf、scanf("%s") より fgets。
配列の要素数を超えた添字にアクセスしてしまう重大な事故のこと。コンパイルは通ってしまうため、実行時に予期せぬ動作を引き起こす。他の変数の領域を書き換えてしまい、原因の分かりにくいバグにつながりやすい。
もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。