scanfがCでバッファオーバーフローを引き起こすのを防ぐ方法は?


83

私はこのコードを使用します:

ランダムな長さの文字列を渡すことができるように、バッファオーバーフローの可能性を防ぐための最良の方法は何でしょうか?

たとえば、次のように呼び出すことで、入力文字列を制限できることはわかっています。

しかし、ユーザーが入力したものは何でも処理できるようにしたいと思います。または、scanfを使用してこれを安全に行うことはできず、fgetsを使用する必要がありますか?

回答:


66

KernighanとPikeは、著書『プログラミング作法』(読む価値があります)でこの問題について説明し、関数ファミリーにsnprintf()渡すための正しいバッファーサイズで文字列を作成することで問題を解決していscanf()ます。事実上:

これでも、入力は「バッファ」として提供されるサイズに制限されることに注意してください。より多くのスペースが必要な場合は、メモリ割り当てを行うか、メモリ割り当てを行う非標準のライブラリ関数を使用する必要があります。


注のPOSIX 2008(2013)バージョンというscanf()機能の家族はフォーマット修飾子をサポートm文字列入力用(割り当て割り当て文字を() 、%s、)。%c 引数%[を取る代わりに、char *引数を取り、char **読み取る値に必要なスペースを割り当てます。

sscanf()関数がすべての変換仕様を満たさない場合、関数が%ms戻る前に、同様の変換に割り当てられたすべてのメモリが解放されます。


@サム:はい、そうあるべきですbuflen-1—ありがとう。次に、符号なしのアンダーフロー(かなり大きな数にラップする)、つまりifテストについて心配する必要があります。サイズとして0を渡すのに不注意な人がいる場合はassert()、それをに置き換えたり、開発中に発生するassert()前にバックアップしたりすることを強く望んでifいます。%0s意味についてドキュメントを注意深く確認していませんsscanf()—テストはとしてより良いかもしれませんif (buflen < 2)
Jonathan Leffler 2013

したがってsnprintf、データを文字列バッファに書き込みsscanf、作成された文字列から読み取ります。scanfstdinから読み取るという点で、これは正確にどこに置き換わるのでしょうか。
krb686 2015

また、結果文字列に「format」という単語を使用して、最初の引数として「format」を渡すことも非常に混乱しますsnprintfが、実際のフォーマットパラメータではありません。
krb686 2015

@ krb686:このコードは、スキャンされるデータがパラメーターに含まれdataているためsscanf()、適切であるように記述されています。代わりに標準入力から読み取りたい場合は、dataパラメーターを削除して代わりに呼び出しscanf()ます。formatの呼び出しでフォーマット文字列になる変数の名前の選択に関してはsscanf()、必要に応じて名前を変更する権利がありますが、その名前は不正確ではありません。どの代替案が理にかなっているのかわかりません。in_formatそれをより明確にするだろうか?このコードで変更する予定はありません。このアイデアを独自のコードで使用する場合は、可能性があります。
ジョナサンレフラー2015

1
@mabraham:macOS Sierra 10.12.5(2017-06-06まで)でも当てはまります— scanf()macOSはサポートとして文書化されていませんが%ms、便利です。
ジョナサンレフラー2017

31

gccを使用している場合は、GNU拡張a指定子を使用して、scanf()に入力を保持するためのメモリを割り当てることができます。

編集: Jonathanが指摘したscanfように、指定子が異なる%m可能性があり()、コンパイル時に特定の定義を有効にする必要がある場合があるため、manページを参照する必要があります。


8
これは、GNU Cコンパイラを使用するよりも、glibc(GNU Cライブラリ)を使用する場合の問題です。
Jonathan Leffler

3
また、POSIX 2008標準ではm、同じジョブを実行するための修飾子が提供されていることに注意してください。を参照してくださいscanf()。使用するシステムがこの修飾子をサポートしているかどうかを確認する必要があります。
ジョナサンレフラー2014

4
GNU(とにかく、Ubuntu 13.10にあります)はをサポートします%ms。表記%aはの同義語です%f(出力では、16進浮動小数点データを要求します)。以下のためのGNUのマニュアルページはscanf()言う:プログラムがコンパイルされている場合_それが利用できないgcc -std=c99か、GCCの-D_ISOC99_SOURCE(ない限り_GNU_SOURCEも指定されている)、その場合にはa浮動小数点数のための指定子として解釈される(上記参照)._
ジョナサンレフラー2014

8

組み合わせほとんどの時間fgetssscanf仕事をしていません。もう1つは、入力が適切にフォーマットされている場合は、独自のパーサーを作成することです。また、2番目の例を安全に使用するには、少し変更が必要であることに注意してください。

上記は、改行(\n)文字までの入力ストリームを破棄します。getchar()これを消費するには、を追加する必要があります。また、ストリームの終わりに到達したかどうかを確認してください。

それについてです。


2
feofコードをより大きなコンテキストに配置できますか?その関数はしばしば間違って使用されるので、私は尋ねています。
Roland Illig 2016年

1
arrayする必要があるchar array[LENGTH+1];
jxh

4

scanf(3)とそのバリアントを直接使用すると、多くの問題が発生します。通常、ユーザーと非対話型のユースケースは、入力行の観点から定義されます。十分な数のオブジェクトが見つからない場合、より多くの行で問題が解決するケースはめったにありませんが、それがscanfのデフォルトモードです。(ユーザーが最初の行に数字を入力することを知らなかった場合、2番目と3番目の行はおそらく役に立ちません。)

少なくともfgets(3)、プログラムに必要な入力行の数がわかっていて、バッファオーバーフローが発生しない場合は...


1

入力の長さを制限することは間違いなく簡単です。ループを使用し、一度に少しずつ読み取り、必要に応じて文字列のスペースを再割り当てすることで、任意の長さの入力を受け入れることができます...

しかし、それは大変な作業なので、ほとんどのCプログラマーは、任意の長さで入力を切り落とすだけです。これはすでにご存知だと思いますが、fgets()を使用しても、任意の量のテキストを受け入れることはできません。制限を設定する必要があります。


では、scanfでそれを行う方法を知っている人はいますか?
goe

3
ループでfgetsを使用すると、任意の量のテキストを受け入れることができますrealloc()。バッファーを保持し続けるだけです。
bdonlan 2009年

1

文字列に必要なメモリを割り当てる関数を作成するのはそれほど手間がかかりません。これは私が少し前に書いた小さなc関数で、文字列を読み取るために常に使用しています。

読み取られた文字列を返すか、メモリエラーが発生した場合はNULLを返します。ただし、文字列をfree()し、常に戻り値を確認する必要があることに注意してください。


sizeof (char)定義によるもの1です。ここでは必要ありません。
RastaJedi 2016

通常、ポインタの割り当て/解放を同じレベルに保つことをお勧めします。つまり、呼び出し元がメモリを解放する必要があるため、関数が独自にメモリを割り当てないようにする必要があります。ほとんどの標準ライブラリ/ posix関数は、静的文字列(like strerror(3))を返すか、事前に割り当てられた文字列が渡されることを期待する((strerror_r(3)-またはscanf(3))...
Michael Beer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.