最近项目有好几个由于字符串处理函数使用不当引起的崩溃,虽然这些函数很基础也很常用,但也比较容易误用,轻则由安全CRT函数在第一现场崩溃,重则造成Buffer Overflow,带来不可预测的错误。借分析这几个dump的机会,并且参考了上一些网上资料,总结了这几个函数几点常见误用及正确使用方法,希望可以帮助大家减少对这些函数的误用,有兴趣的同学可以看看。
先上一个dump,这个dump比较简单,有经验的一眼就可以看出崩溃的原因来
根据崩溃处指令可以知道是因为wcscpy_s函数检测到了错误而主动抛出了异常,再看看栈回溯
根据栈回溯可以看到wcscpy_s函数的目标缓存大小为260也就是MAX_PATH,再看一下源字符串内容
源字符串大小刚好为260,刚刚好等于目标缓存大小,但加上结尾符实际长度为261,看一下wcscpy_s函数的反汇编代码
根据wcscpy_s函数的反汇编代码可以知道,当目标缓存区大小(这里指参数传进去的大小)无法容纳源字符串时,wcscpy_s函数将主动抛出异常。
接下来总结几点wcscpy系列(strcpy类似)字符串常见误用常见误用及正确使用方法:
1. wcscpy,函数本身没安全性可言,容易造成Buffer Overflow,强烈不建议使用
2. wcsncpy,函数原型 wchar_t *wcsncpy( wchar_t *strDest, const wchar_t *strSource, size_t count ),和wcscpy相比,多了一个count参数,这个参数指定的是要拷贝的字符数,但要注意的是
,这个函数实际上并没有我们认为的那么安全,如果我们指定的count数大于目标缓冲区的大小,那么结果会是灾难性的,因为wcsncpy会完整的写满count个字符(不够的用\0填充),反汇编如下:
另处还有一点要注意的是,当源字符串大于count时, wcsncpy不会往后面添上\0,造成不可预测的结果,比如很容易像以下方法使用该函数:
PS:a. wcsncpy会完整的写满count个字符,不够的用\0填充
b.count无法容纳源字符串时,wcsncpy不会往后面添上\0
3. wcscpy_s,wcscpy_s 函数原型为errno_t __cdecl wcscpy_s(_Out_z_cap_(_DstSize) wchar_t * _Dst, _In_ rsize_t _DstSize, _In_z_ const wchar_t * _Src)
根据前面wcscpy_s函数的反汇编代码可以知道,当目标缓存区大小(这里指参数传进去的大小)无法容纳源字符串时,wcscpy_s函数将主动抛出异常。
4. wcsncpy_s,wcsncpy_s 函数原型为errno_t __cdecl wcsncpy_s(_Out_z_cap_(_SizeInWords) wchar_t * _Dst, _In_ rsize_t _SizeInWords, _In_z_ const wchar_t * _Src, _In_ rsize_t _MaxCount),_MaxCount指定最大拷贝字符数,
根据wcsncpy_s的反汇编,可以很清楚地知道wcsncpy_s的处理逻辑
跳出循环后反汇编如下
总结:
1. 尽量使用wcsncpy_s函数,wcscpy_s跟wcsncpy都是不安全的
2. 尽量将判断逻辑放在程序里面,而不要让wcsncpy_s抛出异常
PS:wcscat系列函数跟wcscyp类似
但有点需要注意的是errno_t __cdecl wcsncat_s(_Inout_z_cap_(_SizeInWords) wchar_t * _Dst, _In_ rsize_t _SizeInWords, _In_z_ const wchar_t * _Src, _In_ rsize_t _MaxCount)中的_SizeInWords指的目标字符串的大小,包括字符串中已经存在字符,线上有个dump因为没考虑已经存在的字符而导致该函数抛出异常。