t3486784401 发表于 2022-7-25 00:56:03

【C语言】发现 strncpy 函数是个大坑,提醒大家慎用

最近在 C 环境下编写一些字串转换识别代码,偶然间看到 strncpy 这个函数,感觉 string.h 还挺完善。

随手翻看 MSDN,函数的原型也不意外,比起标准的 strcpy 多了一个拷贝数目,想来可以拷贝指定字节:

char *strncpy( char *dst, const char *src, size_t count );

然而细看 MSDN 才发现这函数并不像表面那样人畜无害,以下分情况讨论:

【count <= strlen(src)】

此时只是拷贝了 src 前 count 个字符,并且不追加 ASCII-NUL(0x00),相当于:

memcpy(dst, src, count);

拷贝完字串依旧是 dst 的长度,只是复制了 src 前 count 个字符。

【count > strlen(src)】

此时的确拷贝了 src 整串,并且向 dst 追加了 count-strlen(src) 个 ASCII-NUL(0x00),相当于:

memset(dst, 0x00, count);
strcpy(dst, src);

拷贝完的确是 src 长度了,但写入一堆没用的 ASCII-NUL 要作何?

-------------------------------------------------------------------

综合上述两种情况,要么是可以被 memcpy 替代,要么是写一堆没用的 NUL,两种情况处理字串都不妥。

搜了搜典型用法: strncpy(dst, src, sizeof(dst)-1)
目标是确保 dst 不发生溢出,但显然依旧有风险:dst非空时,src足够长就会触发“无NUL”的BUG,导致 dst 放飞。

甚至看到坛友用: strncpy(dst, src, strlen(src))
这就不吐槽了,一样有“无NUL”的BUG.

以上,发现 strncpy 是个大坑,发来供大家参考。

鲜衣怒马 发表于 2022-7-25 09:13:48

据说大名鼎鼎的缓冲区溢出漏洞大多数都是strcpy造成的

gonboy 发表于 2022-7-25 09:29:04

自己调用strncpy , 难道你不考虑 数组的长度范围??? 你的讨论没有意义。 编程界一直存在这两种争议。

1. 所有程序必须含有完成的输入检测和内容安全检测,优点是安全,缺点是效率低,代码量大。
2. 所有程序仅含关键检测,优点是效率高,代码量少,缺点是不安全。

只要你的代码正确,两种库都是安全的。 针对使用场合,你可以自己追加检测

t3486784401 发表于 2022-7-25 09:38:46

gonboy 发表于 2022-7-25 09:29
自己调用strncpy , 难道你不考虑 数组的长度范围??? 你的讨论没有意义。 编程界一直存在这两种争议。   ...
(引用自3楼)

并未表达对溢出的担心,只提及无NUL的设计容易歧义。

何以出此言?

t3486784401 发表于 2022-7-25 09:39:12

鲜衣怒马 发表于 2022-7-25 09:13
据说大名鼎鼎的缓冲区溢出漏洞大多数都是strcpy造成的
(引用自2楼)

所以有了 _s 版本

gonboy 发表于 2022-7-25 09:56:53

t3486784401 发表于 2022-7-25 09:38
并未表达对溢出的担心,只提及无NUL的设计容易歧义。

何以出此言?
(引用自4楼)

代码规范即可

wudicgi 发表于 2022-7-25 10:01:26

snprintf() 也需要注意,虽然最多只写入 (n - 1) 个字符,结尾会写入一个 '\0'
但你依然需要对返回值进行检查,判断是否已经写入完整了

https://cplusplus.com/reference/cstdio/snprintf/

If the resulting string would be longer than n-1 characters, the remaining characters are discarded and not stored, but counted for the value returned by the function.

A terminating null character is automatically appended after the content written.

Notice that only when this returned value is non-negative and less than n, the string has been completely written.

wudicgi 发表于 2022-7-25 10:05:16

像有 strncpy() 这类问题的函数,可以封装一层,调用完之后都执行一下 arr = '\0';
这样在不在意字符串超长被截断时,起码结尾有个 '\0' 是安全的

如果在意被截断,那调用之前就要先自己判断长度

zdg102 发表于 2022-7-26 10:37:50

这是我前公司面试基本题,也是基本代码规范了
memset(dst,0,sizeof(dst));
strncpy(dst,src,sizeof(dst)-1);

zdg102 发表于 2022-7-26 10:42:33

遇到要做性能调优时,就把memset改 dst=0;

初音之恋 发表于 2022-7-26 12:23:47

我觉得没问题,不然拼接的时候字符串全断了,我都是做完最后赋0,还有字符串长度最好使用strlen

qinxg 发表于 2022-7-27 09:07:18

一个是死机问题, 一个是代码逻辑问题.
比如服务器, 一个是让整个服务退出;一个是某个客户的请求出错.

安替比邻 发表于 2022-8-6 01:48:23

一般我在操作strncpy的时候都是预先dest尾段强制封0,不过仅限于桌面端,MCU限于处理速度难说,我还没相关开发经验

安替比邻 发表于 2022-8-6 01:50:37

最不理想的情况就是自己用C写一个结构体,包括载荷和长度两个私有成员,但是这样,再加个gc,感觉就偏向于Python那种虚拟机了,MCU也吃不消

t3486784401 发表于 2022-8-6 02:10:35

安替比邻 发表于 2022-8-6 01:50
最不理想的情况就是自己用C写一个结构体,包括载荷和长度两个私有成员,但是这样,再加个gc,感觉就偏向于P ...
(引用自14楼)

VS 是推荐了一系列 _s 版本函数,用于解决内存溢出问题,形如:

strcpy_s( dst, dsize, src )
strcat_s
sprintf_s

可惜单片机环境没有这么细的安全函数,只能自己移植

安替比邻 发表于 2022-8-6 05:30:59

t3486784401 发表于 2022-8-6 02:10
VS 是推荐了一系列 _s 版本函数,用于解决内存溢出问题,形如:

strcpy_s( dst, dsize, src )
(引用自15楼)

我之前写C的时候全是Linux arm环境(树莓派),所以也就没关注过_s的那群函数,VS那个我回头看看源码怎么实现的吧

安替比邻 发表于 2022-8-6 05:33:10

安替比邻 发表于 2022-8-6 05:30
我之前写C的时候全是Linux arm环境(树莓派),所以也就没关注过_s的那群函数,VS那个我回头看看源码怎么 ...
(引用自16楼)

淦,前后矛盾了,严格来说我是学C的时候用的树莓派,然后交作业的时候用的VS,但是没提示手敲习惯了所以就延续了在树莓派上使用原版函数的习惯

bigharpoon 发表于 2022-8-6 10:13:44

strncpy经常用的,感觉还可以,使用时确实需要注意楼主提到的问题。
我通常的做法是:
(1)定义一个足够长的dst数组;
(2)memset(dst, 0x00, count);
(3)strncpy(dst, src, sizeof(dst)-1);
当然,前提是,dst的长度足够长,避免src长度过长不能完整复制的问题。

flash3g 发表于 2022-8-6 12:30:00

不敢用C库的,都是自己写放心
页: [1]
查看完整版本: 【C语言】发现 strncpy 函数是个大坑,提醒大家慎用