278

GCC で関数を使用する C コードをコンパイルしようとすると、次のgets()警告が表示されます。

(.text+0x34): 警告: `gets' 関数は危険なので使用しないでください。

これはスタック保護とセキュリティに関係していることを覚えていますが、正確な理由はわかりません。

この警告を削除するにはどうすればよいですか?また、使用に関する警告が表示されるのはなぜgets()ですか?

そんなに危険ならgets()、なぜ私たちはそれを取り除けないのですか?

4

13 に答える 13

216

安全に使用するgetsには、読み取る文字数を正確に把握して、バッファーを十分大きくできるようにする必要があります。読み取るデータを正確に知っている場合にのみ、それがわかります。

を使用する代わりに、シグネチャを持つgetsを使用したいfgets

char* fgets(char *string, int length, FILE * stream);

( fgets、行全体を読み取る場合'\n'、文字列に the が残ります。これに対処する必要があります。)

gets1999 年の ISO C 標準までは言語の公式な部分でしたが、2011 年の標準で正式に削除されました。ほとんどの C 実装はまだそれをサポートしていますが、少なくとも gcc はそれを使用するすべてのコードに対して警告を発行します。

于 2009-11-07T18:56:46.980 に答える
208

なぜgets()危険なのか

最初のインターネットワーム(Morris Internet Worm)は約30年前(1988-11-02)に逃げ出し、gets()システム間を伝播する方法の1つとしてバッファオーバーフローを使用しました。基本的な問題は、関数がバッファーの大きさを認識していないため、改行が見つかるかEOFに遭遇するまで読み取りを続行し、指定されたバッファーの境界をオーバーフローする可能性があることです。

gets()あなたはそれが存在したと聞いたことを忘れるべきです。

C11標準ISO/IEC 9899:2011gets()は、標準機能として削除されました。これはAGoodThing™です(ISO / IEC 9899:1999 / Cor.3:2007で正式に「廃止」および「非推奨」とマークされました—技術的基準C99の場合は3、C11で削除されます)。残念ながら、下位互換性の理由から、ライブラリには何年も(「数十年」を意味する)残ります。それが私次第だったとしたら、の実装は次のgets()ようになります。

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

いずれにせよ、コードが遅かれ早かれクラッシュすることを考えると、問題を遅らせるよりも早く解決する方がよいでしょう。エラーメッセージを追加する準備ができています:

fputs("obsolete and dangerous function gets() called\n", stderr);

Linuxコンパイルシステムの最新バージョンでは、リンクするgets()と警告が生成されます。また、セキュリティ上の問題がある他の機能についても警告が生成されます(mktemp()、…)。

の代替gets()

fgets()

他の誰もが言ったように、の標準的な代替手段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()データが1行に残っている可能性があります。私のラッパーコードは、そのデータを次回読み取るために残します。必要に応じて、データの残りの行をむさぼり食うように簡単に変更できます。

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

残りの問題は、3つの異なる結果状態(EOFまたはエラー、行の読み取りと切り捨てられていない、部分的な行の読み取りでデータが切り捨てられている)をどのように報告するかです。

この問題はgets()、バッファがどこで終了するかわからず、終了を超えて陽気に踏みにじり、美しく手入れされたメモリレイアウトに大混乱をもたらし、バッファが割り当てられている場合にリターンスタック(スタックオーバーフロー)を台無しにするため、発生しません。スタック、またはバッファが動的に割り当てられている場合は制御情報を踏みにじる、またはバッファが静的に割り当てられている場合は他の貴重なグローバル(またはモジュール)変数にデータをコピーする。これらはどれも良い考えではありません—「未定義動作」というフレーズの典型です。


TR 24731-1 (C標準委員会からのテクニカルレポート)もあり、次のようなさまざまな機能のより安全な代替手段を提供しますgets()

§6.5.4.1gets_s機能

あらすじ

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

実行時の制約

snullポインタであってはなりません。nゼロに等しくなく、RSIZE_MAXより大きくてはなりません。n-1からの文字の読み取り中に、改行文字、ファイルの終わり、または読み取りエラーが発生するものと しますstdin25)

3実行時制約違反がありs[0]、ヌル文字に設定されている場合、stdin改行文字が読み取られるか、ファイルの終わりまたは読み取りエラーが発生するまで、文字が読み取られて破棄されます。

説明

4この関数は、で指定さ れたストリームから、でgets_s指定された文字数より多くても1少ない文字を、で指定された配列に読み込みます。改行文字(破棄される)の後、またはファイルの終わりの後、追加の文字は読み取られません。破棄された改行文字は、読み取られた文字数にはカウントされません。配列に最後に読み込まれた文字の直後にヌル文字が書き込まれます。nstdins

5ファイルの終わりが検出され、配列に文字が読み込まれなかった場合、または操作中に読み取りエラーが発生した場合s[0]は、ヌル文字に設定され、の他の要素はs指定されていない値を取ります。

推奨される方法

6このfgets関数を使用すると、適切に作成されたプログラムで、結果配列に格納するには長すぎる入力行を安全に処理できます。一般に、これには、呼び出し元がfgets結果配列内の改行文字の有無に注意を払う必要があります。fgetsの代わりに(改行文字に基づく必要な処理とともに) 使用することを検討してくださいgets_s

25)このgets_s関数は、とは異なりgets、入力行がバッファをオーバーフローして格納するための実行時制約違反になります。とは異なりfgetsgets_s入力行とへの呼び出しの成功の間で1対1の関係を維持しますgets_s。を使用するプログラムは、そのgetsような関係を期待しています。

Microsoft Visual Studioコンパイラは、TR 24731-1標準の近似を実装していますが、Microsoftによって実装された署名とTRの署名には違いがあります。

C11規格、ISO / IEC 9899-2011には、ライブラリのオプション部分として付録KのTR24731が含まれています。残念ながら、Unixライクなシステムに実装されることはめったにありません。


getline()— POSIX

POSIX 2008は、gets()と呼ばれるものの安全な代替手段も提供しますgetline()。回線に動的にスペースが割り当てられるため、回線を解放する必要があります。したがって、行の長さの制限がなくなります。また、読み取られたデータの長さ、または-1EOF!ではなく)を返します。これは、入力のnullバイトを確実に処理できることを意味します。'独自の単一文字区切り文字を選択する'バリエーションもありますgetdelim(); これは、たとえばfind -print0、ファイル名の末尾がASCIINUL文字でマークされている出力を処理する場合に役立ちます。'\0'

于 2010-11-30T01:51:46.033 に答える
24

標準入力からバイトを取得してどこかに配置getsする際に、いかなる種類のチェックも行わないためです。簡単な例:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

さて、まず第一に、あなたが望む文字数を入力することができますが、gets気にする必要はありません. 第二に、それらを配置する配列のサイズを超えるバイト (この場合array1は ) は、メモリ内で見つけたものを上書きするため、gets書き込みます。前の例では、これは、"abcdefghijklmnopqrts"多分、予期せずに入力すると、それも上書きされることを意味しますarray2

この関数は、一貫した入力を前提としているため安全ではありません。絶対に使用しないでください。

于 2009-11-07T19:03:26.013 に答える
17

getsバッファ オーバーフローを止める方法がないため、使用しないでください。ユーザーがバッファに収まりきらないほど多くのデータを入力すると、破損したり、さらに悪化したりする可能性が高くなります。

実際、ISO は実際にC 標準から削除する gets措置を講じています (C11 の時点で、C99 で廃止されました)。これは、後方互換性を高く評価していることを考えると、その機能がどれほど悪かったかを示すものです。

ユーザーから読み取られる文字を制限できるため、正しいことはファイルハンドル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発生したことを呼び出し元に通知し、次の入力操作に影響を与えないように余分な文字を消去します。

あなたが望むようにそれを自由に使用してください.

于 2010-11-30T01:56:28.430 に答える
14

fgets

標準入力から読み取るには:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
于 2010-11-30T01:28:13.927 に答える
11

API を壊さずに API 関数を削除することはできません。そうした場合、多くのアプリケーションがコンパイルまたは実行されなくなります。

これが、ある参考文献が与える理由です:

s が指す配列をオーバーフローする行を読み取ると、未定義の動作が発生します。fgets() の使用をお勧めします。

于 2009-11-07T18:58:21.843 に答える
5

C11(ISO/IEC 9899:201x) では、gets()削除されました。(ISO/IEC 9899:1999/Cor.3:2007(E) では推奨されていません)

に加えてfgets()、C11 では新しい安全な代替手段が導入されていますgets_s()

C11 K.3.5.4.1gets_s機能

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

ただし、推奨プラクティスセクションでfgets()は、引き続き優先されます。

このfgets関数を使用すると、適切に作成されたプログラムで、長すぎて結果配列に格納できない入力行を安全に処理できます。一般に、これには呼び出し元がfgets結果配列内の改行文字の有無に注意を払う必要があります。fgetsの代わりに (改行文字に基づく必要な処理と共に) を 使用することを検討してくださいgets_s

于 2013-10-06T06:15:49.650 に答える
5

私は最近、 へのUSENET の投稿でcomp.lang.c、それgets()が標準から削除されつつあるのを読みました。ウーフー

委員会が (結果として、全会一致で) 草案からも gets() を削除することを投票したことを知って嬉しく思います。

于 2009-11-07T19:21:20.033 に答える
3

C の gets 関数は危険であり、非常にコストのかかる間違いでした。Tony Hoare は、彼の講演「Null References: The Billion Dollar Mistake」で具体的な言及のためにそれを選び出しています。

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

この 1 時間は一見の価値がありますが、彼のコメントを見ると、30 分から 39 分ほどで特定の部分が批判されます。

言語でより正式な正当性の証明が必要であり、プログラマーではなく、言語設計者が言語の間違いをどのように非難されるべきかについて注意を喚起するこの講演全体への意欲を、これが刺激してくれることを願っています。これが、悪い言語の設計者が「プログラマーの自由」を装ってプログラマーに責任を押し付ける疑わしい理由のようです。

于 2016-05-01T01:00:46.270 に答える
1

「誰かがまだそれに依存している場合に備えて」ライブラリにまだ含まれているCライブラリメンテナーに真剣な招待状を送りたいとgets思います:実装を次の同等のものに置き換えてください

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

これは、誰もまだ依存していないことを確認するのに役立ちます。ありがとうございました。

于 2016-03-31T21:52:25.650 に答える