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