我目前正在为字符串操作编写代码。
作为其中一部分,我使用的是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()
之间的区别是什么?
答案 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_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
指针视为错误的目标。
解决方法是这样:将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
这可以防止警告所有已弃用的库函数。
另请注意,增加警告级别并让编译器警告潜在的错误非常有用,例如给定格式字符串的参数不一致。使用gcc
和clang
,您可以传递-Wall
和其他命令行选项以启用此类行为。对于Visual Studio,您可以使用/W4
添加更多警告。