[迷信] scanf ではバッファオーバーランを防げない

これも根が深い迷信です。この迷信を根拠に、scanf は使うべきではないという人も大勢います。おそらくこういうことでしょう。

char s[10];
scanf("%s", s);

確かにこれでは、ユーザーが10文字以上入力した時点で未定義の動作を引き起こしてしまいます。しかし、これは書式指定が不適切なために発生する脆弱性であって、scanf の問題ではありません。

バッファオーバーランを回避するには次のようにします。

char s[10];
scanf("%9s", s);

これで差し当たっての問題はなくなりました。

さて、熱心な迷信の信者は、何とかして scanf の名誉回復を阻むために、巧みに論点のすり替えを行いながら、scanf を貶めようとすることでしょう。

例えば、上記のように文字列を読み込めばバッファオーバーランは防げるかもしれないが、格納されなかった文字、あるいは改行文字がバッファに残ってしまい、後で悪さをすると。

そんな場合は、こうすれば解決します。

char s[10];
scanf("%9s%*[^\n]%*c", s);

※ 注意! ここはやや誤解を与える表現になっていました。コメントを参照してください。

scanf 系の関数は、文字列を読み込むことに限れば、fgets なんかよりずっと柔軟な処理ができます。要は、書式指定を完全に把握する気があるかどうかの問題です。

ところで、先ほど「文字列を読み込むことに限れば」と敢えて書きました。というのも、文字列を読み込む以外、例えば整数や実浮動小数点数を読み込む場合、scanf 系関数には意外に知られていない問題点があるからです。これは、fscanf でも sscanf でも同じことがいえます。

scanf 系の関数では、整数や実浮動小数点数を読み込む際に、オーバーフローやアンダーフローが発生しても検知することができません。実引数で指定した格納先の型で、入力した数値を表現できない場合の動作は未定義なのです。

scanf でバッファオーバーランを防げないと盲信している人は、数値の入力には脊椎反射的に fgets と sscanf で置き換えるという選択をすることが少なくありません。しかし、先に書いたような問題があるため、そうした方法は安全ではありません。ですから、

char s[100];
int x;

fgets(s, 100, stdin);
if (sscanf("%d", &x) < 1)
{
  /* エラー処理 */
}

ではなく

char s[100];
long t;
int x;
char *endptr;

fgets(s, 100, stdin);
errno = 0;
t = strtol(s, &endptr, 10);
if (errno != 0 || *endptr != '\n' || (t < INT_MIN || INT_MAX < t))
{
  /* エラー処理 */
}
x = t;

とした方がよいのです。

最後の方は、かなり横道にそれてしまいましたが、scanf 系関数に関連することですのでご容赦ください。
バッファオーバーランに関しては、IPA のセキュア・プログラミング講座の記事も参考にしてください。