gets は、C言語における標準入力から1行分の文字列を取り出す関数である。この関数はバッファオーバーランを防ぐことが不可能という致命的な脆弱性を持っており、2011年に改訂されたC11規格以降の標準Cライブラリでは廃止された[1]。
C11の1つ前の版の標準規格であるC99までにおいて、ヘッダーファイル <stdio.h> で gets は以下のような形式で宣言されていることが規定されていた。
char *gets(char *s);
gets は標準入力から改行 ('\n'
) が現れるまでデータを読み込み、引数で渡された s に格納する。このとき改行文字は終端記号 ('\0'
) に置き換えられる。戻り値は、エラーが発生した場合は NULL
を、それ以外は s
を返す。
scanf の書式文字列の %d
や %s
と異なり、改行文字は読み込んだ後で終端記号に書き換えるため、ストリームに改行文字は残らない。このため scanf のような改行文字の処理は必要とはしない。逆に scanf で呼ばれた直後に gets を呼ぶと、入力ストリームの先頭に改行文字が残っているため gets は空の文字列を返してしまう。このため scanf と混在して使うべきではないとされている[2]。
以下のようなコードがあった場合を考えてみる。
char a[10];
gets(a);
このとき、入力されたデータが a の長さから終端記号を除いた9バイトより大きいものであるなら、このコードではバッファオーバーランが発生する。入力されるデータの長さの予測は不可能なので、他の関数(scanf や fgets など)では格納先に入力できる最大長を指定することでバッファオーバーランを回避する方法がとられる。しかし、上記の宣言を見てもわかるように、gets には入力される文字列の長さを制限する機能が存在しない。
以上、gets を用いるとバッファオーバーランを防ぐ手段はないため gets は実用的なプログラミングでは絶対に使用してはならない関数とされており、実際に2011年に行われた改訂(ISO/IEC 9899:2011、通称C11)で廃止された。POSIX.1-2008では廃止予定の分類である。例えばGCCでは、getsを使用するプログラムをコンパイルすると「the `gets' function is dangerous and should not be used. ('gets' 関数は危険なため使うべきではありません。)」と警告される。
C++においても、std::gets
関数はC++11規格の標準C++ライブラリで廃止予定 (deprecated) となり、C++14規格以降で廃止(削除)された[3]。
gets の使用を停止し、以下の関数に置き換えることが考えられる。
_GNU_SOURCE
を define する必要がある。libc 4.6.27以降で使用可能。あるいはgetchar
関数を使用して以下のような代替関数を実装する方法もある。
#include <stdio.h>
#include <assert.h>
/* 標準入力から最大 (size - 1) 文字を読み取る。 */
int GetLine(char* buffer, int size) {
int i;
assert(size >= 2);
for (i = 0; i < (size - 1); ++i) {
const int ch = getchar();
/* '\r' は改行文字とみなしていない。 */
if (ch == '\n') {
buffer[i] = '\0';
return 0;
}
if (ch == EOF) {
/* ストリーム終端に到達した場合、あるいは読み取りエラーが発生した場合。 */
buffer[i] = '\0';
return -1;
}
buffer[i] = ch;
};
assert(i == (size - 1));
/* バッファに収まりきらなかった文字がストリームに残っている可能性がある。 */
buffer[size - 1] = '\0';
return -2;
}
C++においては、標準ライブラリにstd::getline
関数[4]が用意されており、各種ストリームから1行分の入力を文字列クラスstd::string
のオブジェクトとして安全に取得できるので、gets を使用する必要はまったくない。
gets(3)
– JM Project Linux Library Functions マニュアル