【问题标题】:Valid printf() statements in CC 中的有效 printf() 语句
【发布时间】:2014-06-26 03:11:27
【问题描述】:

鉴于:

char *message = "Hello, World";
char *format  = "x=%i\n";
int x = 10;

为什么printf (message); 无效(即编译器因可能不安全而拒绝)而printf (format, x); 无效?

在这种情况下,format 是否被视为字符串文字,message 是否被视为格式字符串?如果有,为什么?


更新


我知道为什么printf (message); 被拒绝了。我的问题是,为什么printf (format, x); 也没有被拒绝。

我正在使用clang。 printf (message); 的错误消息是 format string is not string literal (potentially insecure)

它在 gcc 下编译得很好。所以它似乎是特定于编译器的,并且与 clang 如何设置警告有关。

【问题讨论】:

  • 您收到的实际错误消息是什么,您使用的是什么编译器?这些是至关重要的信息。
  • 什么编译器?它究竟是如何被拒绝的?
  • 最好发布完整的代码。编译器可能会看到format 只使用一次并优化了printf (format, x),这样它就知道format 不会改变,即使声明了char *format。可能编译器看到了 message 值的潜力,这取决于其他代码。因此存在安全风险。存在其他可能性,但查看完整代码会有所帮助。
  • @chux 我并不是指一个具体的例子。两个字符串指针按照上面的定义一起定义和初始化。在代码的两个单独编译中,我只是对每个使用printf (),仅此而已。
  • 在那种情况下,我会选择@this answer。

标签: c string gcc printf clang


【解决方案1】:

这是一个编译器限制。

如果在编译时知道指针指向一个字符串字面量,那么编译器可以检查说明符并忽略警告。

没有什么特别的原因,为什么您会收到一个警告而不是另一个警告。标准没有指定与此问题相关的任何内容。这就是编译器的实现方式。另一个可能会针对这两种情况发出警告,或者都不发出警告。

【讨论】:

    【解决方案2】:

    您可以通过启用-Wformat-nonliteral 选项在这两种情况下收到警告,该选项不包含在-Wall-Wextra 中(但它-Weverything 中)。

    无论出于何种原因,这似乎是一个有意的设计决定,即仅当非文字 printf 语句不带其他参数时才发出安全警告。发出此警告的源代码可以在lib/Sema/SemaChecking.cpp 中找到:

      // If there are no arguments specified, warn with -Wformat-security, otherwise
      // warn only with -Wformat-nonliteral.
      if (Args.size() == firstDataArg)
        Diag(Args[format_idx]->getLocStart(),
             diag::warn_format_nonliteral_noargs)
          << OrigFormatExpr->getSourceRange();
      else
        Diag(Args[format_idx]->getLocStart(),
             diag::warn_format_nonliteral)
               << OrigFormatExpr->getSourceRange();
    

    我猜这是为了与现有的遗留代码兼容,但这纯粹是猜测。

    【讨论】:

      【解决方案3】:

      如果您将要格式化的参数传递给printf,它希望您知道第一个参数将是一个格式字符串。如果它不是格式字符串,那么它会如何处理所有这些额外的参数?这是一个合理的推论。

      另一方面,假设我们没有指定要格式化的数据超出格式字符串本身。那么如果它确实有格式说明符呢?除非其中没有格式说明符,否则它将始终是错误的,因此对于 这种 情况有一个安全的替代方案,它会警告您。

      【讨论】:

      • 注意:传递额外的,未使用的参数给 printf 是定义的。
      【解决方案4】:

      Clang 默认开启-Wformat-security。您可以通过在编译时传递选项 -Wno-format-security 来抑制警告。那应该让你编译printf(message);

      你可以找到描述here

      来自 gcc 文档页面:

      -Wformat-安全性: 如果指定了 -Wformat,还会警告使用表示可能的安全问题的格式函数。目前,这会警告调用 printf 和 scanf 函数,其中格式字符串不是字符串文字并且没有格式参数,如在 printf (foo); 中。如果格式字符串来自不受信任的输入并包含“%n”,这可能是一个安全漏洞。 (这目前是 -Wformat-nonliteral 警告的子集,但将来可能会将警告添加到 -Wformat-security 中,而这些警告未包含在 -Wformat-nonliteral 中。)"

      因此,如果没有格式参数,它只会检查格式字符串是否为字符串文字。这是string literal 的定义。从 C++11 开始,char[] 和 char* 不再被视为字符串文字。请注意,您可以通过将 const char[] 设为 message 将其设为字符串文字。

      针对您在评论中提出的问题,如果设置了-Wformatprintf("%fHello") 将生成警告。

      【讨论】:

      • 但是为什么它会捕获message 而不是format?我对实际打印 message 不感兴趣,因为我可以通过使用 gcc 轻松打印。
      • 用 gnu 警告选项页面的链接编辑了我的答案。
      • 这是否意味着 clang 会首先检查字符串中是否存在在转换为像 %i 这样的字符串文字时可能导致问题的字符?
      【解决方案5】:

      printf 的函数原型声明为:

      int printf (const char * format, ...);

      其中... 是变量参数列表(即0 到∞)。

      format 被视为格式字符串。如果您想使用printf() 打印message,您可以尝试printf (format, message);,其中format 等于"%s"

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-12-21
        • 2021-10-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-15
        • 1970-01-01
        相关资源
        最近更新 更多