[迷信] 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 のセキュア・プログラミング講座の記事も参考にしてください。

補足説明

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

は、10文字以上文字列を入力した場合を想定しています。
実際にこの記述を行った場合、9文字以下しか入力しなければ改行文字が残ります。
9文字以下にも対応するには、例えば下記のようにします。

  char s[10];
  char c;
  if (scanf("%9s%c", s, &c) == 2)
  {
    if (c != '\n')
    {
      ungetc(c, stdin);
      scanf("%*[^\n]%*c");
    }
  }

面倒ですが、fgets を用いても、

  char s[10];
  char c;
  fgets(s, 10, stdin);
  if (s[strlen(s)-1] != '\n')
  {
    while (getchar() != '\n');
  }

のようにせざるを得ませんから、大差はありません。
もちろん、行中の余計な文字を読み飛ばすだけですので、

  char s[10];
  char c;
  if (scanf("%9s%c", s, &c) == 2)
  {
    while (c != '\n') c = getchar();
  }

でもよいでしょう。
なお、改行無しで EOF が発生する場合や I/O エラーに備えるためには、もう少しエラーチェックが必要です。

scanf("%s ",

scanf("%s ", value);
%sの後にスペースを入れると、改行空白の類は全て無視されるようになりますが、
それではダメでしょうか?

このエントリーを含むはてなブックマーク