看看下面的代码有什么问题?
int main( void ) { size_t MAX = 256; wchar_t * src = new wchar_t[MAX]; src[0] = L'H'; src[1] = L'i'; char *dest = new char[MAX]; size_t len = 0; wcstombs_s(&len,dest,MAX,src,MAX); }
如果我们使用strlen(dest),得到的结果会是0,但是如果我们访问dst[1],其结果是i,是我们期望的,而且如果src的有效字符更多一些,除了第一个字符不在外,其他的都是有的,为什么第一个字符消失了呢?
这是我碰到的一个使用New CRT的一个bug,觉得New CRT还挺有意思的,所以找来研究一下:
New CRT最显著的变化叫做Paramter Validation和Sized Buffers。New CRT中_s的若干API并不是要替代旧的API,也就是说,strcpy还是strcpy,New CRT会引入一个strcpy_s。但是如果编译时使用了新的library,旧的方法就会被标注为 过时的,也就是会得到编译器的警告信息。下面借用Michael的例子(strncat,strncat_s)来说说NEW CRT的常见错误用法。
char *strncat( char *strDest, const char *strSource, size_t count ); errno_t strcat_s( char *strDestination, size_t sizeInBytes, const char *strSource );
strncat最后一个参数是Number of characters to append,也就是strDest中剩余的空间或者是要append的数量,而不是strDest的 buffer size。这很容易造成Off-by-one或者Off-by-many的错误。看下面的例子:
int main( void ) { size_t MAX = 256; wchar_t * src = new wchar_t[MAX]; src[0] = L'H'; src[1] = L'i'; char *dest = new char[MAX]; size_t len = 0; wcstombs_s(&len,dest,MAX,src,MAX); char szTarget[12]; char *s = "Hello, World"; strncpy(szTarget,s,sizeof(szTarget)); strncat(szTarget,s,sizeof(szTarget)); }
看看在strncat执行之前的内存:
strncat发现从0040FAF0处为0(字符串结束的位置),故从此处开始做append。最后得到的结果如图。但是,对比之前的内存,可以发现原来len的空间被覆盖了,也就是发生了stack corrupt。
一种解决的办法如下所示:
char szTarget[12]; char *s = "Hello, World"; strncpy(szTarget,s,sizeof(szTarget)); strncat(szTarget,s,strlen(szTarget) – strlen(s));
这种方法的问题在于, If the length of the destination buffer is exactly the same length as the source buffer, many n-functions do not null-terminate the destination buffer, so the call to strlen(szTarget)
could return a length greater than the length of the target because there is no trailing '\0' character. 上面的代码中strlen(szTarget)返回20。
更好的方式是用如下的代码:
char szTarget[12]; char *s = "Hello, World"; size_t cSource = strnlen_s(s,20); strncpy_s(szTarget,sizeof(szTarget),s,cSource); strncat_s(szTarget,sizeof(szTarget),s,cSource);
如果发现空间不够,就会Assert,而不会造成memory corruption。
下面是一段来源于某公司的代码,本意是想写出安全的代码。但是它的问题在于,如果输入的字符串是NULL,strncpy就会fail了。
void noOverflow(char *str) { char buffer[10]; strncpy(buffer,str,(sizeof(buffer)-1)); buffer[(sizeof(buffer)-1)]=0; /* Avoiding buffer overflow with the above two lines */ }
strncat_s不会fail的原因是更新的runtime 执行更加严格的parameter check,下面就是strcat_s的参数检查部分的代码:
/* validation section */ _VALIDATE_RETURN_ERRCODE(front != NULL, EINVAL); _VALIDATE_RETURN_ERRCODE(sizeInTChars > 0, EINVAL); _VALIDATE_RETURN_ERRCODE(back != NULL || count == 0, EINVAL);
需要注意的另外一点是*_s函数和strsafe函数的区别:*_s函数在检测到一个error时,会将结果字符串设置为NULL,而strsafe函数比如StringCchCopy 和StringCchCat在碰到任何错误的时候,其默认行为是尽量填充结果字符串,并最后使用NULL结束之。可以使用下面函数来模仿*_s函数:
StringCchCatEx(dst,sizeof(dst)/sizeof(dst[0]),src,NULL,NULL,STRSAFE_NULL_ON_FAILURE)
Reference: Saying Goodbye to an Old Friend, by Michael Howard, http://blog.yezhucn.com/dncode/secure03102004.htm