c语言 - is 'scanf( "%d" , ...)'和'得到'一样糟糕?



多年来,gets一直被普遍贬低为不安全的功能。 (规范的 SO 问题是为什么 get 函数如此危险以至于不应该使用它?gets功能非常糟糕,以至于已从 C11 语言标准中删除。gets的支持者(如果有的话很少)会争辩说,如果你知道输入的结构,那么使用它完全没问题。

为什么贬低gets并承认依赖输入结构的愚蠢的人允许使用%d作为scanf转换说明符? 这是一个社会学问题,真正的问题是:为什么scanf格式字符串中的%d不安全?

不,scanf("%d", …)并不像gets那么糟糕。

gets就像它一样糟糕,因为它几乎不可能在任何环境中安全地使用它。 缓冲区溢出是可能的,无法防止的,并且很可能导致任意的不良后果。

另一方面,scanf("%d", …)可能发生的最糟糕的事情是整数溢出。 虽然这在理论上也是未定义的行为,但在实践中,它实际上总是导致 (a) 安静的包装,(b) 溢出到INT_MAXINT_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更高级的格式字符串可以保护您形成缓冲区溢出,这可以通过静态分析工具强制执行。

相关内容

最新更新