当使用%d时,fscanf是否会发生缓冲区溢出?

3
我在 Fortify 静态代码分析器 上运行了 ossec-hids 存储库,并报告了以下 缓冲区溢出:格式化字符串 的发现,位于 src/analysisd/stats.c:415

stats.c 文件的第 415 行中,fscanf() 的格式化字符串参数未正确限制函数可以写入的数据量,这允许程序在已分配内存的范围之外进行写入。此行为可能会破坏数据、导致程序崩溃或执行恶意代码。

有问题的代码行如下所示:

if (fscanf(fp, "%d", &_RWHour[i][j]) <= 0) {

_RWHour被声明为

static int _RWHour[7][25];

在同一文件的第33行。我相信在第33行声明和第415行使用_RWHour之间不存在屏蔽,因为当我选择第33行的声明时,我的IDE(Visual Studio 2019)会在第415行中突出显示_RWHour

当我查看fscanf的cppreference文档时,它说:

d 匹配十进制整数。数字的格式与期望的strtol的基本参数值为10相同。

我从上面引用的表格中可以看出,当对%d不使用长度修饰符(正如所讨论的fscanf调用的情况),参数类型应为signed int*unsigned int*
我的问题是:
在这种情况下,Fortify的发现可能是一个误报吗?或者当将int的地址传递给fscanf时,是否可能写入到int之外的内存?
如果使用%dfscanf时可能写入到int之外的内存,如何安全地避免这种情况?

2
只要int指针有效,它应该是安全的。在你的情况下,这意味着ij必须在适当的范围内。 - Tom Karzes
2
虽然没有缓冲区溢出的问题,但是使用fscanf读取整数时,当所读取的值无法被对象的类型表示时,会产生其他可能(且不可避免的)未定义行为。 - Ian Abbott
2
请注意,以下划线开头并紧跟大写字母的标识符在技术上是非法的。重命名数组也将确认该数组没有被遮蔽。 - user3386109
2
@Shane Bishop,请尝试使用if (fscanf(fp, "%4d", &_RWHour[i][j]) <= 0) {语句,并告诉我们报告是否仍然存在。 - chux - Reinstate Monica
2
@Shane Bishop,请尝试使用if (fscanf(fp, "%4d", &_RWHour[i][j]) <= 0) {,并告诉我们报告是否仍然存在。 - chux - Reinstate Monica
显示剩余8条评论
3个回答

5

防止转换溢出

fscanf(fp, "%d", &_RWHour[i][j]) 如果数字文本尝试转换为超出 int 范围的值,则会产生未定义行为(UB)*1

防止 UB 的快速修复方法是使用宽度限制读取的字符数:

//fscanf(fp, "%d", &_RWHour[i][j])
fscanf(fp, "%4d", &_RWHour[i][j])  // Limit [-999 ... 9999].

一个更强大的解决方案是读取文本并使用 strtol() 进行转换。
我建议创建一个辅助函数来处理读取 int
这种检查水平有点糟糕,不是吗? i, j 在范围内 对 OP 引用的代码进行审查看起来还可以,但分析工具可能会对此发出警告。

*1

... 如果转换的结果无法表示为对象,则行为是未定义的。C23dr § 7.23.6.2 10

对于未定义行为(UB),"有可能写入 int 外的内存"。


3
@pmacfarlane 引用已添加。 - chux - Reinstate Monica
3
@pmacfarlane 引用已添加。 - chux - Reinstate Monica
1
哇,每天都学到新东西。我知道fscanf()不太好用,但我不知道它竟然这么糟糕。 - pmacfarlane
4
我会说,尽管有人说“你会得到鼻妖哈哈曼特拉”,但整数溢出不会直接导致内存损坏的可能性为0%。原帖中提到了一种防止fscanf格式字符串中数组溢出的方法,但实际上并不存在这样的方法。正如评论所指出的,OP是否从int n; fscanf(fp, "%d", &n);中得到了错误提示?UB指的是未定义,而不是编译器抛出错误。否则,我们将无法编写能够处理用户输入错误的代码。 - Weather Vane
4
我会说,尽管"你会得到鼻妖哈哈口头禅",但是发生整数溢出不会直接导致内存损坏的几率为0%。原帖中提到了一种防止fscanf格式字符串中数组溢出的方法,但实际上并不存在。正如评论所指出的那样,OP是否从int n; fscanf(fp, "%d", &n);中收到错误标记?UB指的是未定义,而不是编译器抛出错误。否则,就无法编写能够处理用户输入错误的代码。 - Weather Vane
显示剩余23条评论

4
Fortify的发现可能是误报吗?
当然可能,一般来说,Fortify会报告误报。尽管@chux关于可能的未定义行为的回答不成立,但我认为这确实可以被归类为误报。至少,这是一个不准确的诊断。从形式上讲,执行未定义行为的程序可以做任何事情,但在实践中,在这种情况下超出指定int对象的边界是非常不可能的结果,并且对此进行诊断会忽略所有其他可能的结果。
假设其他参数与格式正确匹配,scanf系列函数溢出目标对象的主要风险是使用没有宽度的%s或%[转换说明符,或者使用过大宽度的%s、%[或%c转换说明符。我认为Fortify不适当地概括了这一点。
或者,当将int的地址传递给fscanf时,是否可能写入int之外的内存?

没有未定义行为的参与,无论是来自输入过长还是其他来源,都无法避免。

如果在使用 fscanf 的 %d 时可能写入 int 内存之外,如何安全地避免此问题?

实际上,我认为这不是一个需要担心的问题。

然而,你可以通过在转换说明符中添加适当的字段宽度来满足 Fortify 的要求,但在这种情况下确定 "适当 "的含义可能会很棘手。这也将排除调用引起的任何 UB 的可能性,这是一个值得追求的目标。

或者,你可以使用除 fscanf() 之外的其他方式解析数字。


0
代码似乎不符合工具报告的问题。
此外,同一文件中还有另一个对fscanf的调用,应该报告相同的问题,只是目标int是1D数组中的一个条目,而不是2D数组。
381                if (fscanf(fp, "%d", &_RHour[i]) <= 0) {

但在这两种情况下,很明显并且应该由工具推断出索引变量应保持在适当的范围内。
你可以尝试使用以下代码更改404..426行中的循环,以调查是否是工具的问题:
       for (j = 0; j <= 24; j++) {
            _CWHour[i][j] = 0;
            _RWHour[i][j] = 0;
            snprintf(_weekly, 128, "%s/%d/%d", STATWQUEUE, i, j);
            if (File_DateofChange(_weekly) >= 0) {
                FILE *fp = fopen(_weekly, "r");
                if (fp != NULL) {
                    int hour;
                    if (fscanf(fp, "%d", &hour) == 1 && hour >= 0) {
                        _RWHour[i][j] = hour;
                    }
                    fclose(fp);
                }
            }
        }

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