平时公司的代码安全扫描会给出不安全代码的告警,其中会检查代码中间的strcpy和sprintf函数,而要求使用strncpy和snprintf。今天我们讨论一下怎样写出完美的snprintf。
snprintf是一个在C99才被加入如标准的函数,原来的各个编译器都有自己的实现,至少.NET2003编译器还要是使用_snprintf这样的函数名称。
而这些编译器间都有差异,而且Glibc库又有自己的不同的实现。
查询一下snprintf的函数的MSDN说明。如下:
Let len be the length of the formatted data string (not including the terminating null). len and count are in bytes for _snprintf, wide characters for _snwprintf.
If len < count, then len characters are stored in buffer, a null-terminator is appended, and len is returned.
If len = count, then len characters are stored in buffer, no null-terminator is appended, and len is returned.
If len > count, then count characters are stored in buffer, no null-terminator is appended, and a negative value is returned.
If buffer is a null pointer and count is nonzero, or format is a null pointer, the invalid parameter handler is invoked, as described in Parameter Validation. If execution is allowed to continue, these functions return -1 and set errno to EINVAL.
For information about these and other error codes, see _doserrno, errno, _sys_errlist, and _sys_nerr.
当buffer长度不够时,返回的是负数。
而LINUX的说明如下:
Return value
Upon successful return, these functions return the number of characters printed (not
including the trailing '\0' used to end output to strings). The functions snprintf() and
vsnprintf() do not write more than size bytes (including the trailing '\0'). If the out-
put was truncated due to this limit then the return value is the number of characters (not
including the trailing '\0') which would have been written to the final string if enough
space had been available. Thus, a return value of size or more means that the output was
truncated. (See also below under NOTES.) If an output error is encountered, a negative
value is returned.
NOTES
The glibc implementation of the functions snprintf() and vsnprintf() conforms to the C99
standard, i.e., behaves as described above, since glibc version 2.1. Until glibc 2.0.6
they would return -1 when the output was truncated.
在比较新的版本中,其遵守C99的规范,当buffer长度不够时,返回的是超过Buffer长度的正数。
你会发现,如果传递的buf的长度不够的情况下,null-terminator都没有加入。。。。。那么你使用的时候还是可能溢出。而且返回值的判断在不同的平台还可能不一样。
当然我理解使用snprintf的主要好处在于安全性,但是如果使用不对仍然可能有悲剧发生,比如你的更新SQL语句被截断了WHERE条件。所以返回值还是要判断。
那么最简单的方法还是传递的给snprintf的长度参数count应该buf长度-1,然后还要将最后一个字符改为null-terminator。然后再加入相应的判断。
发现返回值小于0或者大于(可能有等于,看你传递的长度参数和Buffer的关系)实际长度时认为出现问题。
经过测试的正确写法:
max_len = sizeof(buf)-1;
len = snprintf(buf, max_len, ...);
if ((len < 0) || (len > max_len))
{
//错误处理
}
else
{
buf[max_len]=0;
do job...
}
代码说明:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> int main(int argc, char** argv) { if (argc < 2) { printf("usage:./snprintf_perfect xxxx\n"); return -1; } char buffer[16]; size_t max_len = sizeof(buffer) - 1; int len = snprintf(buffer, max_len, "%s", argv[1]); if ((len < 0) || (len > max_len)) { printf("overflow!!\n"); } else { buffer[max_len] = 0; printf("%s\n", buffer); } return 0; }
结果说明:
# 长度为buffer长度-1
[root@rocket linux_programming]# ./snprintf_perfect 012345678901234
01234567890123
# 长度为buffer长度,溢出!
[root@rocket linux_programming]# ./snprintf_perfect 0123456789012345