vsnprintf和vsprintf_s有什么区别?

时间:2017-09-29 09:16:19

标签: c string printf c11 tr24731

我目前正在为字符串操作编写代码。 作为其中一部分,我使用的是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()之间的区别是什么?

2 个答案:

答案 0 :(得分:6)

解决方案是始终添加

#define _CRT_SECURE_NO_WARNINGS

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

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

这将禁用Microsoft已通过C标准“弃用” 必需 的警告。

答案 1 :(得分:3)

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

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

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

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说明符。
  • 不允许大小参数n具有0值。
  • 不允许s目标数组为空指针,如果snprintf()大小为n,则0允许。
  • 当输出字符串长于vsprintf_s时,
  • vsnprintf_sn-1不同:它将目标设置为空字符串,调用错误处理程序并返回0或者负数而不是格式化字符串的长度,vsnprintfvsnprintf_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指针视为错误的目标。

解决方法是这样:将vsnprintf替换为vsnprintf_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

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

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