GCCでgets()
関数を使用するCコードをコンパイルしようとすると、次の警告が表示されます。
(.text + 0x34):警告:「gets」関数は危険であり、使用しないでください。
これはスタックの保護とセキュリティに関係していることを覚えていますが、正確な理由はわかりません。
この警告を削除するにはどうすればよいですか?また、gets()
の使用に関してこのような警告があるのはなぜですか?
gets()
が非常に危険な場合は、なぜ削除できないのですか?
gets
を安全に使用するには、読み込む文字数を正確に知る必要があるため、バッファを十分に大きくすることができます。読み取るデータを正確に知っている場合にのみ、そのことを知ることができます。
gets
を使用する代わりに、署名を持つ fgets
を使用します。
char* fgets(char *string, int length, FILE * stream);
(fgets
は、行全体を読み取る場合、文字列に'\n'
を残すため、対処する必要があります。)
1999 ISO C標準までは言語の公式部分でしたが、2011標準では公式に削除されました。ほとんどのC実装はまだサポートしていますが、少なくともgccはそれを使用するコードに対して警告を発行します。
gets()
が危険な理由最初のインターネットワーム( Morris Internet Worm )は約30年前(1988-11-02)に脱出し、システム間を伝播する方法の1つとしてgets()
とバッファオーバーフローを使用しました。基本的な問題は、関数がバッファの大きさを知らないため、改行を見つけるかEOFに遭遇するまで読み取りを続け、与えられたバッファの境界をオーバーフローさせる可能性があることです。
gets()
が存在したことを聞いたことは忘れてください。
C11標準ISO/IEC 9899:2011は、標準機能としてgets()
を削除しました。これはA Good Thing™です(ISO/IEC 9899:1999/Cor.3:2007で正式に「廃止」および「非推奨」としてマークされました)—テクニカルC99の正誤表3。その後、C11で削除。残念ながら、後方互換性の理由で、ライブラリに何年も(「10年」を意味する)残っています。私次第であれば、gets()
の実装は次のようになります。
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
いずれにせよ、コードが遅かれ早かれクラッシュすることを考えると、遅滞よりも遅かれ早かれトラブルを回避する方が良いでしょう。エラーメッセージを追加する準備ができています。
fputs("obsolete and dangerous function gets() called\n", stderr);
Linuxコンパイルシステムの最新バージョンでは、gets()
をリンクすると警告が生成されます。また、セキュリティ上の問題がある他の関数(mktemp()
、…)についても警告が生成されます。
gets()
の代替他の皆が言ったように、gets()
の標準的な代替は fgets()
ファイルストリームとしてstdin
を指定することです。
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
まだ言及されていないのは、gets()
には改行が含まれていないが、fgets()
には含まれているということです。そのため、改行を削除するfgets()
のラッパーを使用する必要がある場合があります。
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
または、より良い:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
また、 caf がコメントで指摘し、 paxdiablo がその答えに示されているように、fgets()
を使用すると、行にデータが残る場合があります。私のラッパーコードでは、そのデータは次に読み取られます。必要に応じて、データ行の残りを取得するように簡単に変更できます。
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
残された問題は、3つの異なる結果状態を報告する方法です。EOFまたはエラー、行の読み取りで切り捨てられていない、部分的な行の読み取りでデータが切り捨てられました。
この問題はgets()
では発生しません。なぜなら、バッファの終了位置がわからず、端を越えて楽々踏みにじられ、美しく整頓されたメモリレイアウトに大混乱を引き起こし、多くの場合リターンスタックを台無しにします(a Stack Overflow =)バッファがスタックに割り当てられている場合、またはバッファが動的に割り当てられている場合は制御情報を踏みつけ、またはバッファが静的に割り当てられている場合は他の貴重なグローバル(またはモジュール)変数にデータをコピーします。これらはどれも良いアイデアではありません-それらは「未定義の振る舞い」というフレーズの典型です。
TR 24731-1 (C標準委員会の技術レポート)もあり、これはgets()
を含むさまざまな関数のより安全な代替手段を提供します。
§6.5.4.1
gets_s
関数あらすじ
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
実行時の制約
s
はNULLポインターであってはなりません。n
はゼロに等しくなく、RSIZE_MAXを超えてはなりません。stdin
からのn-1
文字の読み取り中に、改行文字、ファイルの終わり、または読み取りエラーが発生します。25)3実行時制約違反がある場合、
s[0]
はヌル文字に設定され、改行文字が読み取られるか、ファイルの終わりまたは読み取りエラーが発生するまで、stdin
から文字が読み取られて破棄されます。説明
4
gets_s
関数は、n
で指定されたストリームから、stdin
で指定された文字数よりも多くない文字を、s
で指定された配列に読み込みます。改行文字(破棄される)またはファイルの終わりの後に、追加の文字は読み込まれません。破棄された改行文字は、読み取られた文字数にはカウントされません。ヌル文字は、最後の文字が配列に読み込まれた直後に書き込まれます。5ファイルの終わりが検出され、配列に文字が読み込まれなかった場合、または操作中に読み取りエラーが発生した場合、
s[0]
はヌル文字に設定され、s
の他の要素は未指定の値を取ります。推奨プラクティス
6
fgets
関数を使用すると、適切に作成されたプログラムが、結果配列に格納するには長すぎる入力行を安全に処理できます。一般に、これにはfgets
の呼び出し元が、結果配列内の改行文字の有無に注意を払う必要があります。gets_s
の代わりにfgets
を(改行文字に基づいて必要な処理とともに)使用することを検討してください。25)
gets_s
関数は、gets
とは異なり、入力行に対する実行時制約違反となり、バッファーをオーバーフローさせて格納します。fgets
とは異なり、gets_s
は、入力行とgets_s
の呼び出しの間に1対1の関係を維持します。gets
を使用するプログラムは、このような関係を期待しています。
Microsoft Visual StudioコンパイラはTR 24731-1規格に近似するものを実装していますが、Microsoftが実装するシグネチャとTRのシグネチャには違いがあります。
C11標準であるISO/IEC 9899-2011には、ライブラリのオプション部分としてAnnex KにTR24731が含まれています。残念ながら、Unixライクなシステムではめったに実装されません。
getline()
— POSIXPOSIX 2008は、 gets()
と呼ばれるgetline()
の安全な代替手段も提供します。回線にスペースを動的に割り当てるため、最終的には解放する必要があります。したがって、行の長さの制限がなくなります。また、読み取ったデータの長さ、または-1
(EOF
ではなく)を返します。これは、入力のnullバイトを確実に処理できることを意味します。 getdelim()
と呼ばれる「独自の単一文字区切り文字を選択する」バリエーションもあります。これは、ファイル名の末尾にASCII NUL find -print0
文字などのマークが付けられている'\0'
からの出力を処理する場合に役立ちます。
gets
は、stdinからバイトを取得してどこかに配置する際に、いかなる種類のチェックも行わないためです。簡単な例:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
さて、まず最初に、あなたが望む文字数を入力することが許可されます。gets
はそれを気にしません。次に、それらを配置する配列のサイズ(この場合はarray1
)を超えるバイトは、gets
が書き込むため、メモリ内で見つかったものを上書きします。前の例では、これは、"abcdefghijklmnopqrts"
を入力すると、予想外に、array2
なども上書きすることを意味します。
この関数は、一貫した入力を前提としているため安全ではありません。 絶対に使用しないでください!
gets
を使用しないでください。バッファーオーバーフローを停止する方法がないためです。ユーザーがバッファーに収まらないほど多くのデータを入力すると、ほとんどの場合、破損したり悪化したりします。
実際、ISOは実際には、C標準(C99では廃止されましたが、C11の時点で)から削除gets
のステップを踏んでおり、下位互換性をどれだけ高く評価しているかを示す必要がありますその機能がどれほど悪かった。
ユーザーから読み取られる文字を制限できるため、fgets
関数をstdin
ファイルハンドルと共に使用するのが正しい方法です。
しかし、これには次のような問題もあります。
そのために、キャリアのある時点でほぼすべてのCコーダーは、fgets
の周りに、より便利なラッパーを作成します。これが私のものです:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
テストコード付き:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
printf ("No input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long\n");
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
fgets
と同じ保護を提供しますが、バッファオーバーフローを防ぎますが、何が起こったのかを呼び出し側に通知し、次の入力操作に影響を与えないように余分な文字を消去します。
あなたが望むようにそれを自由に使用してください、私はここで「あなたがやりたいことをやりたい」ライセンスの下でそれをリリースします:-)
fgets 。
Stdinから読み取るには:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
APIを壊さずにAPI関数を削除することはできません。そうした場合、多くのアプリケーションはコンパイルも実行もできなくなります。
これが 1つの参照 が与える理由です:
Sが指す配列をオーバーフローする行を読み取ると、未定義の動作が発生します。 fgets()の使用をお勧めします。
C11(ISO/IEC 9899:201x)では、gets()
が削除されました。 (ISO/IEC 9899:1999/Cor.3:2007(E)で廃止されました)
fgets()
に加えて、C11は新しい安全な代替gets_s()
を導入しています。
C11 K.3.5.4.1
gets_s
関数#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
ただし、推奨されるプラクティスセクションでは、fgets()
が引き続き推奨されます。
fgets
関数を使用すると、適切に作成されたプログラムが、結果配列に格納するには長すぎる入力行を安全に処理できます。一般に、これにはfgets
の呼び出し元が、結果配列内の改行文字の有無に注意を払う必要があります。gets_s
の代わりにfgets
を(改行文字に基づいて必要な処理とともに)使用することを検討してください。
最近、 comp.lang.c
へのUSENETの投稿 で、gets()
が標準から削除されることを読みました。 WOOHOO
あなたは、委員会がドラフトからgets()を削除するために投票したことを知って喜んでいるでしょう(結局のところ、全員一致で)。
gets()
は危険です。ユーザーがプロンプトに入力しすぎるとプログラムがクラッシュする可能性があるためです。使用可能なメモリの終わりを検出できないため、そのために割り当てるメモリの量が少なすぎると、セグフォールトやクラッシュを引き起こす可能性があります。ユーザーが名前に対応するプロンプトに1000文字を入力することはほとんどないように思えますが、プログラマーとして、プログラムを防弾にする必要があります。 (ユーザーが大量のデータを送信してシステムプログラムをクラッシュさせる可能性がある場合、セキュリティリスクになる可能性もあります)。
fgets()
を使用すると、標準入力バッファーから取り出す文字数を指定できるため、変数がオーバーランすることはありません。
「誰かがまだ依存している場合に備えて」ライブラリにgets
をまだ含めているCライブラリのメンテナーに真剣に招待したいと思います:実装を同等のものに置き換えてください
char *gets(char *str)
{
strcpy(str, "Never use gets!");
return str;
}
これにより、誰もまだ依存していないことを確認できます。ありがとうございました。
Cのgets関数は危険であり、非常にコストのかかる間違いでした。トニー・ホアは、彼の講演「Null References:The Billion Dollar Mistake」で具体的に言及するためにそれを選び出している。
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
1時間は見る価値がありますが、彼のコメントでは、30分からは39分前後の批判があります。
これがあなたの話全体の欲求を刺激することを願っています。これは、言語でより正式な正しさの証明が必要であり、言語デザイナーがプログラマーではなく、言語の誤りを非難する方法に注意を喚起します。これが、悪い言語のデザイナーが「プログラマーの自由」を装ってプログラマーに非難をかける全疑わしい理由だったようです。