【问题标题】:When to quantify ignored pattern match in the C sscanf function何时量化 C sscanf 函数中忽略的模式匹配
【发布时间】:2014-12-22 15:21:59
【问题描述】:

Cppcheck 1.67 在此行的源代码中提出了可移植性问题:

sscanf(s, "%d%*[,;.]%d", &f, &a);

这是我从中得到的信息:

在某些版本的 libc 上,没有字段宽度限制的 scanf 可能会因大量输入数据而崩溃。

格式字符串的初衷是接受两个整数之间的三个可能的限制字符之一,而今天 - 感谢 Cppcheck[1] - 我看到 %*[,;.] 接受偶数字符串限制字符。但是我怀疑我的格式字符串可能会导致崩溃,因为无限制的部分被忽略了。

是否存在缓冲区溢出问题? ...可能在幕后?


[1] 如何在远视和失明之间迷失:

我尝试通过%1*[,;.](在some API doc之后)修复它,但Cppcheck坚持这个问题,所以我也尝试%*1[,;.]以同样的“成功”。看来我必须暂时压制它了……

【问题讨论】:

  • 所以,你有一个正确的格式,你把它改成别的东西(可能不正确?)来抑制误报,但没有成功?看起来更像是“内联抑制”的工作:在此处搜索 cppcheck.sourceforge.net/manual.pdf
  • 指出的消息没有意义,因为%*[,;.]不使用缓冲区。
  • @Deduplicator 不,我尝试改进模式以满足 Cppcheck 帮助检测的实际需求。
  • @BLUEPIXY 是的,但就我而言,有一个小问题......
  • @Wolf:是的,这是一种解决方法。我建议在上面提交错误报告,对吧?

标签: c++ c portability scanf cppcheck


【解决方案1】:

何时量化 C sscanf 函数中忽略的模式匹配?

总是量化可能是个好主意(见下文),但过度量化也可能会分散您的意图。在上述情况下,单个分隔符必须被跳过,量化肯定是有用的。

是否存在缓冲区溢出问题? ...也许在幕后?

您的代码不会导致崩溃。至于处理“幕后”问题,我尝试了大输入字符串。在我测试的 C 库中,没有内部缓冲区溢出。我尝试了 Borland C++ 5.6.4 附带的 C 库,发现 我无法在大输入(超过 4 亿个字符)时触发缓冲区溢出

令人惊讶的是,Cppcheck 并非完全错误 - 存在可移植性问题,但存在不同:

#include <stdio.h>
#include <assert.h>
#include <sstream>

int traced_sscanf_set(const int count, const bool limited)
{
    const char sep = '.';
    printf("\n");
    std::stringstream ss;
    ss << "123" << std::string(count, sep) << "456";
    std::string s = ss.str();
    printf("string of size %d with %d '%c's in it\n", s.size(), count, sep);
    std::stringstream fs;
    fs << "%d%";
    if (limited) {
        fs << count;
    }
    fs << "*["<< sep << "]%d";
    std::string fmt = fs.str();
    printf("fmt: \"%s\"\n", fmt.c_str());
    int a = 0;
    int b = 0;
    const sscanfResult = sscanf(s.c_str(), fmt.c_str(), &a, &b);
    printf("sscanfResult=%d, a=%d, b=%d\n", sscanfResult, a, b);
    return sscanfResult;
}

void test_sscanf()
{
    assert(traced_sscanf_set(0x7fff, true)==2);
    assert(traced_sscanf_set(0x7fff, false)==2);
    assert(traced_sscanf_set(0x8000, true)==2);
    assert(traced_sscanf_set(0x8000, false)==1);
}

如果格式参数中没有明确指定的限制,我检查的库在内部将消耗(和跳过)的输入限制为 32767 (215-1) 个字符。

如果有兴趣,这里是跟踪输出:

string of size 32773 with 32767 '.'s in it
fmt: "%d%32767*[.]%d"
sscanfResult=2, a=123, b=456

string of size 32773 with 32767 '.'s in it
fmt: "%d%*[.]%d"
sscanfResult=2, a=123, b=456

string of size 32774 with 32768 '.'s in it
fmt: "%d%32768*[.]%d"
sscanfResult=2, a=123, b=456

string of size 32774 with 32768 '.'s in it
fmt: "%d%*[.]%d"
sscanfResult=1, a=123, b=0

【讨论】:

    【解决方案2】:

    恭喜您在 Cppcheck 1.67(当前版本)中发现了一个错误。

    您基本上有三种解决方法:

    1. 忽略误报。
    2. 修改格式(分配该字段,因为您只想匹配一个字符)。

      char tmp;
      if(3 != sscanf(s, "%d %c%d", &f, &tmp, &a) || tmp!=',' && tmp!=';' && tmp!= '.')
          goto error;
      
    3. 直接抑制警告(最好是内联抑制):

      //cppcheck-suppress invalidscanf_libc
      if(2 != sscanf(s, "%d%1*[,;.]%d", &f, &a))
          goto error;
      

    不要忘记将错误报告为“缺陷/误报”,这样您就可以尽快退出并忘记该解决方法。

    【讨论】:

    • 再次,请重新考虑 #2 中的分隔符处理。我发现,"%d%c%d" 绝对可以胜任,因为它是一个分隔符(不能是数字)。
    • 但可能有空格。除非你说不可能,而且这种可能性隐含在原始 scanf 中。
    • (原文中没有空格)对不起,题主误称问题是关于scanf函数的问题,正文没有这个错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-01
    • 2016-07-09
    • 1970-01-01
    • 2022-12-07
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    相关资源
    最近更新 更多