“scanf(“%d”,...)“是否和”gets“一样糟糕?

lnlaulya  于 2022-12-26  发布在  其他
关注(0)|答案(4)|浏览(139)

许多年来,gets一直被认为是一个不安全的函数。(规范的SO问题是Why is the gets function so dangerous that it should not be used?)。gets函数是如此糟糕,以至于它已经被从C11语言标准中删除。gets的支持者(如果有的话,也很少)会争辩说,如果你知道输入的结构,使用它是完全好的。
为什么那些贬低gets并承认依赖输入结构是愚蠢的人允许使用%d作为scanf转换说明符呢?这是一个社会学问题,真实的的问题是:为什么scanf字符串中的%d不安全?

bnlyeluc

bnlyeluc1#

如果scanf的格式字符串包含原始%d转换说明符(“原始”意味着“没有最大字段宽度”),如果输入流包含作为不能适合于X1 M2 N1 X的整数的有效表示的字符串,则该行为是未定义的。例如,字符串5294967296无法在sizeof(int) == 4所在平台上的int中表示。C仅保证int足够大以容纳范围-32767到+32767,因此,任何包含字符串32768的输入流都可能导致未定义的行为。使用%4d可以避免这种潜在的未定义行为。大多数现代平台的INT_MAX值远大于32767,因此实际上转换说明符上的宽度修饰符可以大于4,但是应该为平台确定它(在编译时或者在运行时),并且它 * 必须 * 出现在格式字符串中。
如果你不添加宽度修饰符,你也可以只使用gets将一行读入缓冲区,然后使用sscanf来解析值,这(可能)会使错误对读者更明显。

dz6r00yl

dz6r00yl2#

不,scanf("%d", …)没有gets那么糟糕。
gets是最糟糕的,因为它不可能在几乎任何环境中安全地使用它。缓冲区溢出是可能的,无法防止,并且很可能导致任意的坏后果。
另一方面,scanf("%d", …)可能发生的最糟糕的事情是 * 整数 * 溢出。虽然这在理论上也是未定义的行为,但实际上它总是导致(a)安静的回绕,(b)溢出到INT_MAXINT_MIN,或者(c)可能终止调用程序的运行时异常。
很难想象攻击者能够利用使用scanf("%d", …)的程序进行攻击的场景,但涉及gets的攻击却很常见。
(虽然不是这个问题,但scanf("%s", …)确实和gets一样危险。为什么前者不总是像后者那样受到强烈的贬低,这是一个公平的问题。)

6qqygrtg

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行。
mhd8tkvw

mhd8tkvw4#

gets没有任何防止缓冲区溢出错误的方法。
对于scanf("%d", &x);,无法产生缓冲区溢出错误(其类型与格式字符串匹配)。
现在以防

char s[5];
scanf("%s", s);

存在缓冲区溢出的危险(当用户类型使用4个以上的字符时),但很容易修复此代码以防止缓冲区溢出:

char s[5];
scanf("%4s", s);

现在这个版本不能缓冲区溢出。
请注意,scanf是中继漏洞,所以防止与格式字符串相关的常见错误威胁警告作为错误。
基本上gets没有办法保护无效(太长)的用户输入。也没有办法在不破坏二进制或源代码兼容性的情况下修复它。
scanf的情况下,更高级的格式字符串可以防止缓冲区溢出,这可以通过静态分析工具来执行。

相关问题