9

我目前正在编写用于字符串操作的代码。作为其中的一部分,我正在使用vsnprintf().

但是,编译器会在错误消息下方闪烁:

dont_call: vsnprintf(). Invokation of a potentially dangerous function
    that could introduce a vulnerability. remediation:
    Recommendation: Use vsprintf_s() instead!

结果与vsprintf_s()预期不符。

vsnprintf()和 和有什么不一样vsprintf_s()

4

2 回答 2

9

vsnprintf()是一个完美的标准函数,如果使用得当,没有安全问题,特别是如果格式字符串是与传递的参数兼容的字符串文字。

Microsoft 已弃用此功能,因为攻击者可以利用使用用户提供的文本作为格式规范的草率代码:精心制作的用户提供的字符串可以使用该%n格式来尝试破坏程序的数据并使程序执行任意代码。

使用可变格式字符串容易出错,因为很难验证参数的类型是否与此动态生成的格式字符串兼容。合理的编码约定不允许这样的代码。将用户提供的字符串作为格式字符串传递是完全不合适的,因为它是未定义行为的微不足道的来源。禁用有用的和标准的功能,因为它们可能被滥用并造成安全漏洞,这是值得称赞的,但在这种特殊情况下,建议的替代方案存在缺陷。

有 2 个替代方案vsnprintf(),在附件 K 中标准化,但具有可选支持,因此不可跨环境移植:

int vsprintf_s(char * restrict s, rsize_t n,
          const char * restrict format,
          va_list arg);

int vsnprintf_s(char * restrict s, rsize_t n,
          const char * restrict format,
          va_list arg);

这些函数的行为与snprintf微妙的方式不同:

  • 都不支持说明%n符。
  • 两者都不允许 size 参数n0值。
  • 两者都不允许s目标数组为空指针,snprintf()如果n大小为0.
  • vsprintf_svsnprintf_s输出字符串长于时不同n-1:它将目标设置为空字符串,调用错误处理程序并返回0一个负数,而不是格式化字符串的长度,就像两者vsnprintf一样vsnprintf_s

您没有发布要替换的代码snprintf(),但一个经典的用例是这样的:

/* allocate a string formated according to `format` */
int vasprintf(char **strp, const char *format, va_list ap) {
    va_list arg;
    int ret;

    if (!strp) {
        errno = EINVAL;
        return -1;
    }

    va_copy(arg, ap);
    ret = vsnprintf(NULL, 0, format, arg);
    va_end(arg);

    *strp = NULL;
    if (ret < 0 || (*strp = malloc(ret + 1)) == NULL) {
        return -1;
    }

    return vsnprintf(*strp, ret + 1, format, ap);
}

在上面的代码中,vnsprintf不能替换为vsprintf_s因为vsprintf_s不能用于计算所需的大小:它总是将短大小视为错误。vsnprintf_s不能用作直接替换,因为它认为NULL指针作为目标也是错误的。

解决方案是:替换vsnprintfvsnprintf_s并传递一个大小为的短本地缓冲区,1而不是NULL, 0

/* allocate a string formated according to `format` */
int vasprintf(char **strp, const char *format, va_list ap) {
    char buf[1];
    va_list arg;
    int ret;

    if (!strp) {
        errno = EINVAL;
        return -1;
    }

    va_copy(arg, ap);
    ret = vsnprintf_s(buf, 1, format, arg);
    va_end(arg);

    *strp = NULL;
    if (ret < 0 || (*strp = malloc(ret + 1)) == NULL) {
        return -1;
    }

    return vsnprintf_s(*strp, ret + 1, format, ap);
}

请注意,还有另一个更简单的解决方案:您可以通过在有问题的代码之前定义一个宏来防止 Visual Studio 发出此警告:

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

这可以防止对所有已弃用的库函数发出警告。

另请注意,提高警告级别并让编译器警告潜在的错误非常有用,例如给定格式字符串的参数不一致。使用gccand clang,您可以传递-Wall和其他命令行选项来启用此类行为。对于 Visual Studio,您可以使用/W4.

于 2017-10-01T13:21:28.137 回答
7

解决方案是始终添加

#define _CRT_SECURE_NO_WARNINGS

作为使用 Visual Studio 编译时的第一行代码。或者,如果您正在编写跨平台代码并且第一行类似于 ,则可能是第二行#ifdef _WIN32,例如

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

这将禁用有关 Microsoft 具有C 标准要求的“已弃用”功能的警告。

于 2017-09-29T12:34:49.540 回答