「Gets」の版間の差分
編集の要約なし |
|||
4行目: | 4行目: | ||
== 形式 == |
== 形式 == |
||
C11の1つ前の版の標準規格である[[C99]]までにおいて、[[ヘッダーファイル]] <tt><nowiki><stdio.h></nowiki></tt> で gets は以下のような形式で宣言されていることが規定されていた。 |
C11の1つ前の版の標準規格である[[C99]]までにおいて、[[ヘッダーファイル]] <tt><nowiki><stdio.h></nowiki></tt> で gets は以下のような形式で宣言されていることが規定されていた。 |
||
< |
<syntaxhighlight lang="c"> |
||
char *gets(char *s); |
char *gets(char *s); |
||
</syntaxhighlight> |
|||
</source> |
|||
gets は標準入力から改行 (<code>'\n'</code>) が現れるまでデータを読み込み、引数で渡された s に格納する。このとき改行文字は終端記号 (<code>'\0'</code>) に置き換えられる。[[戻り値]]は、エラーが発生した場合は <code>[[Null|NULL]]</code> を、それ以外は <code>s</code> を返す。 |
gets は標準入力から改行 (<code>'\n'</code>) が現れるまでデータを読み込み、引数で渡された s に格納する。このとき改行文字は終端記号 (<code>'\0'</code>) に置き換えられる。[[戻り値]]は、エラーが発生した場合は <code>[[Null|NULL]]</code> を、それ以外は <code>s</code> を返す。 |
||
13行目: | 13行目: | ||
== セキュリティ問題 == |
== セキュリティ問題 == |
||
以下のようなコードがあった場合を考えてみる。 |
以下のようなコードがあった場合を考えてみる。 |
||
< |
<syntaxhighlight lang="c"> |
||
char a[10]; |
char a[10]; |
||
gets(a); |
gets(a); |
||
</syntaxhighlight> |
|||
</source> |
|||
このとき、入力されたデータが a の長さから終端記号を除いた9バイトより大きいものであるなら、このコードでは[[バッファオーバーラン]]が発生する。入力されるデータの長さの予測は不可能なので、他の関数(scanf や fgets など)では格納先に入力できる最大長を指定することでバッファオーバーランを回避する方法がとられる。しかし、上記の宣言を見てもわかるように、gets には入力される文字列の長さを制限する機能が存在しない。 |
このとき、入力されたデータが a の長さから終端記号を除いた9バイトより大きいものであるなら、このコードでは[[バッファオーバーラン]]が発生する。入力されるデータの長さの予測は不可能なので、他の関数(scanf や fgets など)では格納先に入力できる最大長を指定することでバッファオーバーランを回避する方法がとられる。しかし、上記の宣言を見てもわかるように、gets には入力される文字列の長さを制限する機能が存在しない。 |
||
33行目: | 33行目: | ||
あるいは<code>getchar</code>関数を使用して以下のような代替関数を実装する方法もある。 |
あるいは<code>getchar</code>関数を使用して以下のような代替関数を実装する方法もある。 |
||
< |
<syntaxhighlight lang="c"> |
||
#include <stdio.h> |
#include <stdio.h> |
||
#include <assert.h> |
#include <assert.h> |
||
60行目: | 60行目: | ||
return -2; |
return -2; |
||
} |
} |
||
</syntaxhighlight> |
|||
</source> |
|||
C++においては、標準ライブラリに<code>std::getline</code>関数<ref>[https://ja.cppreference.com/w/cpp/string/basic_string/getline std::getline - cppreference.com]</ref>が用意されており、各種ストリームから1行分の入力を文字列クラス<code>std::string</code>のオブジェクトとして安全に取得できるので、gets を使用する必要はまったくない。 |
C++においては、標準ライブラリに<code>std::getline</code>関数<ref>[https://ja.cppreference.com/w/cpp/string/basic_string/getline std::getline - cppreference.com]</ref>が用意されており、各種ストリームから1行分の入力を文字列クラス<code>std::string</code>のオブジェクトとして安全に取得できるので、gets を使用する必要はまったくない。 |
2020年7月5日 (日) 22:58時点における最新版
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 の使用を停止し、以下の関数に置き換えることが考えられる。
- fgets 関数
- gets と末尾の改行文字の扱いが異なるため単純に gets を fgets で置き換えることは出来ない。具体的な置き換え方法は fgets の項が詳しい。
- gets_s 関数
- C11で追加されたが、C11において実装が必須ではないため必ずしも利用可能とは限らない。
- getline 関数
- POSIX 2008で追加された。stdio.h のヘッダに含まれるが使用には
_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 を使用する必要はまったくない。
関連項目
[編集]脚注
[編集]- ^ “N1570 Committee Draft ISO/IEC 9899:201x Programming languages C” (PDF) (英語). p. xiii. 2012年3月30日閲覧。 “removed the gets function (<stdio.h>)”
- ^ http://www.kouno.jp/home/c_faq/c12.html#18
- ^ std::gets - cppreference.com
- ^ std::getline - cppreference.com
外部リンク
[編集]gets(3)
– JM Project Linux Library Functions マニュアル