安全でないコードサンプル
scanfは使ってはいけないと耳にしました。
むちゃくちゃ長い文字列を入力したりすると、クラッシュするそうです。
バッファーオーバーランだそうです。
へぇ~
まず、私がよく眼にするメモリ破壊コードを紹介します。私の知っているメモリを破壊するコードはこんな感じです。
char p[10]; // ①
for(int i = 0; i < 11; i++) // ②
p[ i ] = 0x00;
とても慎重にコーディングをしないと、この手のバグの除去は困難です。①と②が別のスコープあるいはモジュール、ソースファイルにあったりするのが普通でしょうから、実地ではもっとヤバゲです。これにマルチスレッドとかマルチタスクがからんでくるんですからね。
さて、scanfでも似たような問題は起こりえます。
#include <stdio.h>
int main()
{
char p[10];scanf( "%s", &p ); // 何文字来るかわからんよイヒヒ
printf( "----\n" );
printf( "%s", p );return 0;
}
意気込んで、コンソールから11文字(2文字オーバー)入力しても何も起こりません。
$ ./ts.exe
01234567890
----
01234567890
もっとたくさん打ち込んだら、皆さんが期待した結果が得られました。
$ ./ts.exe
1111111111111111111111111111111111111111111111111111111111
----
6 [main] ts 2780 _cygtls::handle_exceptions: Error while dumping state (pr
obably corrupted stack)
Segmentation fault (core dumped)
現象としては、前者の方が厄介です。後者は、「誰かが悪さしている」というシグナルが発せられているからです。文字通りに!
前者はめぐりめぐってどこかで影響がでます。事前にどこかはわかりませんし、オブジェクトのでき方次第では、現象は日々変化するでしょう。
scanfの%sについては、対策は簡単です。
#include <stdio.h>
int main()
{
char p[10];scanf( "%9s", &p ); // 最大幅を指定すれば多くても安心
printf( "----\n" );
printf( "%s", p );return 0;
}
実行結果
$ ./ts.exe
01234567890
----
012345678
見事に切り捨てられます。libcを書いているような人たち、即ち巨人の肩によるバッファオーバーチェックです。
scanfが危険だとかいう人たちは、きっと、getsの話と勘違いしているだけなのだと思います。
個人的には、どこの馬の骨が書いたかわからない、getc()を使ったパーサもどきルーチンの方がよっぽど怖いです。
ここまで書いても、まだ心配な人がいるようです。次の例です。
#include <stdio.h>
int main()
{
float f;scanf( "%f", &f );
printf( "----\n" );
printf( "%f", f );return 0;
}
実行結果
$ ./ts
12.3
----
12.300000$ ./ts
12.30000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000
----
12.300000$ ./ts
AAAAAAAAAAAAAAAAAAAGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAA
----
0.000000
相当長い文字列入れても大丈夫だと思います。最大長のチェックしていると思うんで。文字列だとゼロになるのは、どうでしょうかね。
(2009.06.15追記・改)
scanfで数値を取り組む場合、オーバーフローについては動作が未定義のようです。特に浮動小数点については丸め処理の例もあるように、処理系/実行環境の差異が多数あるので、意図しない値が入るだけ、という甘い考えでははまることがあるようです。(コメント欄も参照ねがいまっする)
(追記終わり)
巨人の肩にタックルしてみたい人はどうぞご勝手にです。
私が言いたいのは、scanfは正しく使えば、仕様通りには使うことができるということで、正しい使い方はそれほど難しいものではないということです。もちろん、自由自在ではないでしょう。書式指定がしょぼいとか。しかしそれは箸でスープを飲もうとして文句を並べている鶴と同じです。
最初に紹介した例は、そういうレベルの話ではなく、ごく普通にそこらじゅうにあるコードでかつ、危険だということです。しかも、唯一の対策が、
慎重に、注意して、厳重にチェックしないとコロス
という、いかにも情けない旧社会主義的方法論しかないということです。
配列のサイズに#defineマクロやconst変数を使ったってだめです。①で指定したシンボルと②で指定するシンボルを間違えたらドボンですから。
コンテナクラスを作ろうが、STLを使おうが同じことです。
所詮はアセンブラのマクロのマクロのマクロなんですから。1バイト間違えれば死が待っている世界なのです。
$ gcc --version
gcc (GCC) 3.4.4 (cygming special) (gdc 0.12, using dmd 0.125)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
りんくる
(2009.06.07追記)
もっとまじめに解説したページをたまたま見つけました。
[迷信] scanf ではバッファオーバーランを防げない
http://www.kijineko.co.jp/tech/superstitions/buffer-overrun-of-scanf.html
・・・scanf 系の関数は、文字列を読み込むことに限れば、fgets なんかよりずっと柔軟な処理ができます。要は、書式指定を完全に把握する気があるかどうかの問題です。
・・・
バッファオーバーランに関しては、IPA のセキュア・プログラミング講座の記事も参考にしてください。
そうそう。このIPAの記事を見れば道案内にはなります。
| 固定リンク
「プログラミング」カテゴリの記事
- VS MS-Word Highlighter2013(2015.07.08)
- テストマシンと環境(2004.02.08)
- Eclipse on Debian(2004.03.02)
- VSSで普通のファイルを管理する その2(2004.06.21)
- WTL7.5をVC6で使ってみる(2004.06.23)
コメント
> scanf( "%f", &f );
これは大丈夫だとはいえません。
具体的には、1e10000を入力してみてください。
処理系によって、fに無限大が格納されたり、SIGFPEが発生したり、クラッシュしたり、変な動きをすることになります。
特定処理系だけを対象にして、その仕様を調査した上で問題無しとするならよいですが、未定義の動作に依存することになりますので、少なくとも移植性はありません。
投稿: たかぎ | 2009.06.15 17:35
どうもコメントありがとうございます。
浮動小数点の変換の問題については、(高木さんが書いた?)きじねこさんのページの説明で拝見しております。
さっきJISでC99を確認したのですが、scanfの浮動小数点の扱いはstrtodに依存するカタチでかかれており、strtodはそれなりにNaNやINFに対応するように実装されることが求められているようにも読めます。
この場をお借りしてご教授いただきたいのですが、C90以前とかANSIとかK&Rでは、浮動小数点のNaNとかINFの取り扱いは未定義だったという理解であっていますでしょうか?
C99ではすくなとも規格準拠していれば処理系依存とならず、準拠しているはずの動作が未定義ならライブラリのバグになろうかと思います。
投稿: ごみためまん | 2009.06.15 20:34
> scanfの浮動小数点の扱いはstrtodに依存するカタチでかかれており、strtodはそれなりにNaNやINFに対応するように実装されることが求められているようにも読めます。
これは、scanfでは入力された文字列がstrtodに対する入力と同じ形式であることを要求しているだけです(実装方法の話ではありません)。
多くの処理系では、%ldに対してオーバーフローする値を入力した場合にLONG_MAXにならないことも、その傍証になるはずです(%fがstrtodと同様に振る舞うなら、%ldはstrtolと同じように振る舞うはず)。
fscanfの長さ修飾子に関する記述の直前に、
このオブジェクトが適切な型をもたない場合,又は変換結果が指定されたオブジェクト内で表現できない場合,その動作は未定義とする。
とありますので、オーバーフローした場合の動作は未定義と考えるべきかと思います。
投稿: たかぎ | 2009.06.15 21:41
コメントスパム対策でお手数をおかけしているようですみませんです。
>このオブジェクトが適切な型をもたない場合,又は変換結果が指定されたオブジェクト内で表現できない場合,その動作は未定義とする。
なるほど確かに読み落としておりました。
本文中の追記を改めておきました。
貴重な情報ありがとうございます。
投稿: ごみためまん | 2009.06.15 22:19