GET関数が危険なため使用できないのはなぜですか?


229

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

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

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

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

gets()とても危険なのなら、なぜそれを削除できないのでしょうか?



回答:


179

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

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

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

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

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


79
警告するのは実際にはgccではなく、プラグマまたは属性が含まれているglibcでありgets()、使用するとコンパイラーが警告を発します。
fuz

@fuz実際には、警告するのはコンパイラだけではありません。OPで引用された警告はリンカーによって出力されました!
ルスラン

163

なぜgets()危険なのか

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

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

C11標準ISO / IEC 9899:2011 gets()が標準機能として削除されました。これはA Good Thing™です(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;
}

また、としてカフェコメントとして指摘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()、バッファがどこで終了しているかわからないため、端から楽に踏みつけ、美しく手入れされたメモリレイアウトに大混乱を引き起こし、バッファが割り当てられている場合、リターンスタック(スタックオーバーフロー)を台無しにすることが多いため、発生しません。スタック、またはバッファが動的に割り当てられている場合は制御情報を踏みつけ、バッファが静的に割り当てられている場合は他の貴重なグローバル(またはモジュール)変数にデータをコピーします。これらはどれも良いアイデアではありません。「未定義の動作」というフレーズの典型です。


また、以下を含むさまざまな機能のより安全な代替手段を提供する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);

ランタイム制約

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

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

説明

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

5ファイルの終わりが検出され、配列に文字が読み込まれていない場合、または操作中に読み取りエラーが発生した場合s[0]は、null文字に設定され、の他の要素は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()calledの安全な代替手段も提供しますgetline()。それはラインのためのスペースを動的に割り当てます、従ってあなたはそれを解放する必要があることになります。したがって、行の長さの制限がなくなります。また、読み取られたデータの長さ、または-1(ではなくEOF)データの長さも返します。つまり、入力のnullバイトを確実に処理できます。と呼ばれる「独自の1文字の区切り文字を選択する」バリエーションもありgetdelim()ます。これは、たとえばfind -print0、ファイル名の末尾がASCII NUL '\0'文字でマークされている場所からの出力を処理する場合に役立ちます。


8
またfgets()fgets_wrapper()バージョンによっては、次の入力関数によって読み取られるように、長すぎる行の末尾部分が入力バッファーに残されることにも注意してください。多くの場合、これらの文字の読み取りと破棄が必要になります。
ca

5
なぜばかげた呼び出しをせずに機能を使用できるfgets()代替を追加しなかったのはなぜでしょうか。たとえば、文字列に読み込まれたバイト数を返したfgetsバリアントは、最後に読み込まれたバイトが改行かどうかをコードが簡単に確認できるようにします。バッファにnullポインタを渡す動作が「次の改行まで最大n-1バイトを読み取って破棄する」と定義されている場合、コードは長すぎる行の末尾を簡単に破棄できます。
スーパーキャット2015年

2
@supercat:はい、同意します-残念です。これに最も近いアプローチは、おそらくPOSIX getline()とそのrelative getdelim()であり、コマンドによって読み取られた「行」の長さを返し、行全体を格納できるように必要に応じてスペースを割り当てます。サイズが数ギガバイトの単一行のJSONファイルになる場合でも、問題が発生する可能性があります。あなたはそのすべての記憶を買う余裕がありますか?(私たちはそれでいる間そして、我々は持つことができますstrcpy()し、strcat()最後にヌルバイトへのポインタを返すバリアントを等?)
ジョナサン・レフラー

4
@supercat:その他の問題fgets()は、ファイルにnullバイトが含まれている場合、nullバイトの後から行末(またはEOF)までのデータ量がわからないことです。 strlen()データのnullバイトまでしか報告できません。その後、それは当て推量であるため、ほぼ間違いなく間違っています。
Jonathan Leffler

7
「あなたが今までにそのgets()存在を聞いたことを忘れてください。」私がこれをするとき、私は再びそれに遭遇し、ここに戻ってきます。投票を獲得するためにstackoverflowをハッキングしていますか?
candied_orange 2016年

21

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

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

gets(array1);

さて、まず第一に、あなたはあなたが望む何文字を入力することが許されます、getsそれは気にしないでしょう。次に、それらを配置した配列のサイズを超えるバイト数(この場合はarray1)は、メモリにあるものをすべてgets書き込みます。前の例では、これは、"abcdefghijklmnopqrts"多分、予期せずに入力した場合、それもarray2また何でも上書きすることを意味します。

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


3
何がgets完全に使用不能にすることは、それがかかること、配列の長さ/カウントパラメータを持っていないということです。そこにあれば、それはもう1つの通常のC標準関数になります。
legends2k 2013

@ legends2k:意図された使用法が何であるか知りたいのですgetsが、なぜ標準のfgetsバリアントが、改行が入力の一部として望まれないユースケースほど便利にならないのですか?
スーパーキャット2015年

1
@supercat getsは、その名前が示すように、から文字列を取得するように設計されていますがstdinサイズパラメータを持たない理由は、Cの精神によるものである可能性があります。プログラマを信頼してください。この関数はC11で削除され、指定された置換gets_sでは入力バッファーのサイズが使用されます。fgetsしかし、私はその部分についての手がかりはありません。
legends2k 2015年

@ legends2k:私が見ることができる唯一のコンテキストはgets、ある特定の長さにわたって物理的に行を送信することができなかったハードウェアラインバッファI / Oシステムを使用していて、プログラムの予定寿命があった場合です。ハードウェアの寿命よりも短かった。その場合、ハードウェアが127バイトを超える長さの行を送信できない場合、gets128バイトのバッファーに入れるのは妥当かもしれませんが、小さい入力を期待するときに短いバッファーを指定できることの利点は、費用。
スーパーキャット2015年

@ legends2k:実際には、「文字列ポインタ」に、いくつかの異なる文字列/バッファ/バッファ情報形式から選択するバイトを識別させることが理想的だったかもしれません。接頭辞バイト[プラスパディング]、および実際のテキストのバッファサイズ、使用サイズ、およびアドレス。このようなパターンは、コードが何かをコピーすることなく、別の文字列の任意の部分文字列(だけでなく、尾を)渡しすることが可能になるだろう、とのような方法が可能になるgetsstrcat、安全には限り収まるよう受け入れます。
スーパーキャット2015年

16

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

実際、ISOは実際にC標準から削除 するステップを踏んでいgetsます(C99では非推奨でしたが、C11以降)。これは、下位互換性の評価の高さを考えると、その機能がどれほど悪かったかを示すものです。

ユーザーから読み取られる文字を制限できるためfgetsstdinファイルハンドルで関数を使用するのが正しい方法です。

しかし、これには次のような問題もあります。

  • ユーザーが入力した余分な文字は、次回からピックアップされます。
  • ユーザーが入力したデータが多すぎるという迅速な通知はありません。

そのために、キャリアのある時点でほぼすべての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バッファーオーバーフローを防ぐという点で同じ保護を提供しますが、何が起こったかについて呼び出し元に通知し、次の入力操作に影響を与えないように余分な文字を消去します。

自由に使ってください。私は、「やりたいことをやりたい」というライセンスの下でリリースします:-)


実際、元のC99標準はgets()、セクション7.19.7.7で定義されている場合、またはセクション7.26.9将来のライブラリの指示とのサブセクションで明示的に非推奨になりませんでした<stdio.h>。それが危険であることについての脚注さえありません。(それを言って、私は「:1999 / Cor.3:これは、9899 ISO / IECで廃止予定の2007年の(E)参照)」で答えによってゆうハオ。)しかし、C11は、標準からそれを削除しなかった-とない時間の前に!
Jonathan Leffler、2015年

int getLine (char *prmpt, char *buff, size_t sz) { ... if (fgets (buff, sz, stdin) == NULL)の変換を非表示にsize_tします。 の奇妙な値をキャッチします。intszsz > INT_MAX || sz < 2sz
chux-モニカを復元する16年

if (buff[strlen(buff)-1] != '\n') {入力された悪意のあるユーザーの最初の文字が埋め込まれたnull文字レンダリングbuff[strlen(buff)-1]UBである可能性があるため、ハッカーのエクスプロイトです。 while (((ch = getchar())...ユーザーがnull文字を入力すると問題が発生します。
chux-モニカを復元する16年


6

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

これが、1つのリファレンスが与える理由です。

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


4

私は中に、最近読んへのUSENETのポストcomp.lang.cgets()標準から取り除かなっています。うん

委員会がget()をドラフトから削除することも(結局のところ全会一致で)投票したことを知ってうれしいです。


3
標準から削除されているのは素晴らしいことです。ただし、ほとんどの実装では、下位互換性のため、少なくとも今後20年間は「非標準の拡張」として提供されます。
ジョナサンレフラー

1
はい、そうですが、gcc -std=c2012 -pedantic ...gets()を使用してコンパイルした場合は通過しません。(-stdパラメータを作成したところです)
pmg

4

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結果配列内の改行文字の有無に注意を払う必要があります。のfgets代わりに(改行文字に基づく必要な処理とともに)の 使用を検討してくださいgets_s


3

gets()ユーザーがプロンプトに入力しすぎるとプログラムがクラッシュする可能性があるため、危険です。使用可能なメモリの終わりを検出できないため、目的に対して小さすぎる量のメモリを割り当てると、セグメンテーション違反とクラッシュが発生する可能性があります。ユーザーが人の名前を示すプロンプトに1000文字を入力することはほとんどありませんが、プログラマーとして、プログラムを完全なものにする必要があります。(また、ユーザーが大量のデータを送信してシステムプログラムをクラッシュさせる可能性がある場合は、セキュリティ上のリスクとなる可能性があります)。

fgets() 標準入力バッファーから取り出す文字数を指定できるので、変数がオーバーランすることはありません。


本当の危険は、プログラムをクラッシュさせることではなく、任意のコードを実行させることにあることに注意してください。(一般的に、未定義の動作を悪用します。)
Tanz87

2

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

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

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


2

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

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

1時間は一見の価値がありますが、彼のコメントでは、30分以降の具体的なコメントを見ると、約39分で批判されます。

これが全体の話に対するあなたの食欲を刺激することを願っています。これは、言語におけるより正確な正当性の証明がどのように必要であるか、そして言語デザイナーがプログラマーではなく彼らの言語の間違いに対して非難されるべきかに注意を向けます。これが、悪意のある言語の設計者が「プログラマーの自由」を装ってプログラマーに非難を強いる全体の疑わしい理由だったようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.