多年来,gets
一直被普遍贬低为不安全的功能。 (规范的 SO 问题是为什么 get 函数如此危险以至于不应该使用它?gets
功能非常糟糕,以至于已从 C11 语言标准中删除。gets
的支持者(如果有的话很少)会争辩说,如果你知道输入的结构,那么使用它完全没问题。
为什么贬低gets
并承认依赖输入结构的愚蠢的人允许使用%d
作为scanf
转换说明符? 这是一个社会学问题,真正的问题是:为什么scanf
格式字符串中的%d
不安全?
不,scanf("%d", …)
并不像gets
那么糟糕。
gets
就像它一样糟糕,因为它几乎不可能在任何环境中安全地使用它。 缓冲区溢出是可能的,无法防止的,并且很可能导致任意的不良后果。
另一方面,scanf("%d", …)
可能发生的最糟糕的事情是整数溢出。 虽然这在理论上也是未定义的行为,但在实践中,它实际上总是导致 (a) 安静的包装,(b) 溢出到INT_MAX
或INT_MIN
,或 (c) 可能终止调用程序的运行时异常。
很难想象攻击者可以使用scanf("%d", …)
利用程序的情况。 另一方面,涉及gets
的漏洞利用是司空见惯的。
(虽然不是提出的问题,但scanf("%s", …)
确实和gets
一样危险。这是一个公平的问题,为什么前者并不总是像后者那样被强烈贬低。
如果要scanf
的格式字符串包含原始%d
转换说明符("raw"表示"没有最大字段宽度"),则如果输入流包含的字符串是无法容纳在int
中的整数的有效表示形式,则行为是未定义的。 例如,字符串5294967296
不能在sizeof(int) == 4
的平台上的int
中表示。C
语言仅保证int
足够大以容纳范围 -32767 到 +32767,因此包含字符串32768
的任何输入流都可能导致未定义的行为。 这种潜在的未定义行为可以通过使用%4d
来避免。 大多数现代平台的 INT_MAX 值远大于 32767,因此实际上转换说明符上的宽度修饰符可以大于 4,但应该为平台确定(在编译时或运行时),并且它必须存在于格式字符串中。
如果不添加宽度修饰符,则不妨只使用gets
将行读入缓冲区并使用sscanf
来解析值。 这将(也许)使错误对读者更加明显。
众所周知,以前的gets()
不提供导致 UB 的缓冲区溢出的控制/检测。 它可能有一个大小参数。
除了@William Pursel关于int
范围的好答案。
scanf("%d", ...)
: 输入不限于一行。
gets()
读 1行。"%d"
scanf()
中,首先使用前导空格,其中可能包括几行。
scanf("%d", ...)
:不读取整行。
与gets()
不同,scanf("%d", ...)
在输入后留下任何输入int
。 这通常包括一个'n'
。 不阅读整行通常会为后续问题埋下种子。
根据目标,scanf("%d", ...)
不会抱怨尾随非数字文本。
C 缺乏读取行的可靠方法。 IMO、fgets()
、gets_s()
、scanf(anything)
、扩展getline()
都缺乏一些功能。
我会为一个总是读取一行,总是在buf
中形成一个字符串并返回EOF
(文件末尾,输入错误)的int scan_line(size_t sz, char *buf /*, size_t *length_read*/)
,成功时返回1,sz
太小时返回0。
或者(和更有争议的)*scanf()
可以改进:
-
添加为
"%s"
和朋友传递size
的功能。 这是非常需要的。 -
定义
int
溢出的行为。 -
像
"%#n"
在空白处扫描的东西,但不是'n'
. 不对返回值产生影响。 -
像
"%n"
在 1'n'
内扫描的东西. 参与返回值。 可以使用前导空格"% n"
以允许可选的前导非'n'
空格。 -
提供始终只读 1 行的
*scanfln()
。
gets
没有任何方法来防止缓冲区溢出错误。
对于scanf("%d", &x);
,无法使缓冲区溢出错误(它的类型与格式字符串匹配)。
现在在
char s[5];
scanf("%s", s);
存在缓冲区溢出的危险(当用户类型使用超过 4 个字符时),但很容易修复此代码以防止缓冲区溢出:
char s[5];
scanf("%4s", s);
现在此版本无法缓冲溢出。
请注意,scanf
容易发生中继错误,因此请防止将与格式字符串相关的常见错误威胁警告作为错误。
基本上gets
没有办法防止无效(到长)用户输入。此外,如果不破坏二进制或源兼容性,也无法修复它.
在这种情况下scanf
更高级的格式字符串可以保护您形成缓冲区溢出,这可以通过静态分析工具强制执行。