scanf Cppcheck 警告

8

Cppcheck对scanf函数显示以下警告:

Message: scanf函数没有设置字段宽度限制,可能会因为输入数据过大而导致崩溃。要修复此错误,请添加字段宽度说明符:
    %s => %20s
    %i => %3i
以下是一个可能会崩溃的示例程序:
#include int main() { int a; scanf("%i", &a); return 0; }
要使其崩溃: perl -e 'print "5"x2100000' | ./a.out

我无法通过“huge input data”来使该程序崩溃。具体应该输入什么才能导致崩溃?我也不理解这个警告中的最后一行的含义:

perl -e ...


7
"按任意键继续。" "哪里是任意键??" - Dave
1
@Dave: 你的评论看起来像是垃圾邮件 :( - Alex F
2
什么?不是的。从你的问题措辞来看,似乎你误解了“大量输入数据”这个短语——它不是你要键入的内容,而是输入的属性。这与经典的任意键笑话情境相同,我将其用作你问题的隐喻。 - Dave
@Dave:嗯,现在我明白了,我的问题看起来很有趣...只有一个人真正回答了它。也许这是“哪里是巨大的输入数据”问题 :) - Alex F
3个回答

6
最后一行是一个演示崩溃的命令,使用样例程序来展示。它会让perl打印2,100,000次"5",然后将其传递到“a.out”程序的stdin中(这是编译后的样例程序)。
首先,仅应在测试时使用scanf(),而不要在真实世界的程序中使用,因为它无法优雅地处理几个问题(例如要求输入“%i”,但用户输入了“12345abc”(“abc”将保留在stdin中,并可能导致随后的输入被填充而没有机会让用户更改它们)。
关于这个问题: scanf() 知道它应该读取整数值,但它不知道它有多长。指针可以指向 16 位整数、32 位整数或 64 位整数,或者甚至更大的某些东西(它不知道)。具有可变数量参数(使用 ... 定义)的函数不知道传递元素的确切数据类型,因此必须依赖格式字符串(格式标记不能像C#中那样是可选的,只需对它们进行编号,例如 "{0} {1} {2}")。没有给定长度,它必须假设某些长度,这可能也与平台有关(使函数更加不安全)。
总的来说,它可能会对程序造成伤害,并且成为缓冲区溢出攻击的起点。如果您想要保护和优化您的程序,请从替代方案开始。

4
Scanf知道指针的大小限制:%d表示的是sizeof(int)*8位,%ld表示的是sizeof(long)*8位,以此类推。所有这些信息在编译时都已知。 - Dave
@Arvid 虽然我的回答有点跑题,但这不是可变参数函数的工作方式。参数数量和类型基本上是“丢失”的,必须根据格式字符串(或根据函数的其他机制)进行重构。我不确定标准规定了什么,但如果输入超过变量可以接受的长度,你可能会进入未定义的行为领域。 - Mario
如果是这样,那似乎是一个规范缺陷。正如Dave所指出的,格式字符串包含有关变量宽度的信息,而且似乎scanf()可以确保不会溢出它们。 - Arvid
1
@CodeAbominator,那么如果您可以的话,要么编辑答案,要么发布自己的答案呢?仅仅抱怨未提及的事情并不能帮助任何人。请随意发布替代性和更完整的答案,然后在评论中提到问题的原始创建者以考虑交换接受的答案。 - Mario
@CodeAbominator 没有适用于所有人的神奇解决方案。问题的很大一部分在于格式字符串与参数指向的内存之间可能存在不匹配。例如,iostream 通过了解和考虑实际类型来避免这种情况。您仍然可以使用 scanf(),解决方案在警告消息中,只是使用它正确起来更加困难。 - Mario
显示剩余4条评论

0

我尝试在Linux上运行Perl表达式与C程序,但它在这里崩溃了(段错误)。


0
在现实世界的应用中,通常不建议使用“scanf”(或fscanf和sscanf)函数,因为它不安全,如果提供了一些不正确的输入数据,通常会导致缓冲区溢出漏洞。 有很多更安全的方法可以在许多常用的C++库(如QT、Microsoft Visual C++运行时库等)中输入数字。或许你也可以找到“纯”C语言的安全替代方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接