为什么strncpy不安全?

74

我想了解为什么strncpy被认为是不安全的。有人能提供相关文档或者使用它的漏洞示例吗?


6
据我所知,strcpy存在安全隐患,而strncpy是其安全版本。 - Marc W
33
名字上没有"s"。没有"s"就不能安全... - Shog9
2
如果你正在使用C++,那么就不要使用它,而是使用std::string。 - lothar
9
strncpy并不是作为strcpy的安全版本设计的,但常常被用于此目的。点击这里查看解释:http://www.lysator.liu.se/c/rat/d11.html 。现在有人会争论strncpy是否被错误地设计了。历史是C委员会的一个借口 :) 如果你需要一个安全版本的strcpy,我建议你使用其他函数。 - Johannes Schaub - litb
1
@stimms:当你说你“更感兴趣的是strncpy_s”时,你是指strncpy_s()可能存在的不安全性,还是相对于strncpy()而言,为什么strncpy()是不安全的?strncpy_s()是strncpy()的“安全”版本,它唯一的区别(与strncpy()不同)就是要求你指定目标缓冲区的长度。 - Tim
显示剩余8条评论
5个回答

50

请查看这个网站,它提供了相当详细的解释。基本上,strncpy()不需要以NULL结尾,因此容易受到各种漏洞的攻击。


16
+1,但需要注意的是,只有当您忘记手动添加空字符时,strncpy函数才会不安全。如果您难以记住添加空字符,请考虑使用包装函数,在调用strncpy后始终将最后一个字符设置为'\0'。 - ojrac
2
@RBerteig,注意MS strncpy_s等函数并不是1:1的strncpy替代品,因为它们不会将缓冲区填充为零。虽然它们是更好的strcpy,但在我看来,strl*函数更加灵活和快速。 @ojrac,它仍然容易被误用,首先你必须记住要复制(bufsize-1)而不是(bufsize),否则你的"rm -Rf /a"就会变成"rm -Rf /"。 - user14554
7
“NULL终止”不正确。 ASCII字符\0通常写作“NUL”,以避免与“NULL”指针混淆。 - Chris Lutz
4
@Billy ONeal,http://opengroup.org/onlinepubs/007908775/xsh/strncpy.html,http://www.opengroup.org/onlinepubs/009695399/functions/strncpy.html和C1x草案与您的观点不一致。除非ANSI C另有规定,否则不进行零填充是非标准行为。然而,微软根本不支持C语言;他们只有一个C++编译器。 - user14554
1
那个链接已经失效了。它在其他地方还可以找到吗? - Sionide21
显示剩余10条评论

12
原始问题显然是strcpy(3)不是一个内存安全操作,因此攻击者可以提供比缓冲区更长的字符串,这将覆盖堆栈上的代码,并且如果仔细安排,可以执行来自攻击者的任意代码。
但是,strncpy(3)还有另一个问题,在某些情况下它没有在目标位置提供空值终止符。(想象一下源字符串比目标缓冲区更长的情况。)未来的操作可能会期望在等大小的缓冲区之间提供符合C标准的空值终止字符串,并且当结果被复制到第三个缓冲区时,会在下游出现故障。
使用strncpy(3)比strcpy(3)更好,但像strlcpy(3)这样的东西更好。

2
strncpy 不比 strcpy 更好。strlcpy 在可用的情况下很有用,在不可用的情况下易于重写。 - chqrlie

7

为了安全地使用strncpy,必须手动将空字符粘贴到结果缓冲区中(第一种方法),或者知道缓冲区以空字符结尾,并将(length-1)传递给strncpy(第二种方法),或者知道缓冲区永远不会被使用任何不会将其长度限制为缓冲区长度的方法来复制(第三种方法)。

需要注意的是,strncpy会在所复制的字符串后面将缓冲区内所有内容填充为零,而其他长度受限的strcpy变体则不会。这可能在某些情况下会影响性能,但在其他情况下会带来安全优势。例如,如果使用strlcpy将“supercalifragilisticexpalidocious”复制到缓冲区中,然后再复制“it”,则缓冲区将包含“it^ercalifragilisticexpalidocious^”(使用“^”表示零字节)。如果将缓冲区复制到固定大小的格式,则多余的数据可能会随之传送。


5
问题的前提是“负面偏见”的,这使得问题本身无效。
重要的是,`strncpy`未被认为是不安全的,也从未被认为是不安全的。可以将此函数附加到“不安全”的唯一声明是C内存模型和C语言本身的广泛不安全性声明。(但这显然是一个完全不同的话题)。
在C语言领域内,有些人错误地认为`strncpy`存在某种固有的“不安全性”,这源于广泛存在的可疑模式将`strncpy`用作“安全字符串复制”的方式,即该函数并不执行且也没有旨在执行的操作。这种使用方式确实非常容易出错。但即使你将“高度错误倾向”和“不安全”之间放置等号,它仍然是一个使用问题(即教育缺乏问题),而不是`strncpy`本身存在问题。
基本上,我们可以说`strncpy`的唯一问题是名称不当,这使得新手程序员假设他们了解此函数所执行的操作,而实际上他们需要阅读规范。看着函数名,一个无能力的程序员假设`strncpy`是`strcpy`的“安全版本”,而实际上这两个函数是完全不相关的。
同样的论断也适用于除法运算符,例如。正如大多数人所知,关于C语言的最常见问题之一是:“我认为`1/2`将计算为`0.5`,但我得到了`0`。为什么?” 然而,我们不会因为语言初学者倾向于错误地解释其行为而称除法运算符是不安全的。
例如,我们不会因为无能力的程序员经常不愉快地发现它们的输出不是真正随机的事实而称伪随机数生成器函数是“不安全的”。
这正是`strncpy`函数的情况。就像初学者需要时间来了解伪随机数生成器的实际功能一样,他们需要时间来了解`strncpy`的实际功能。需要时间了解`strncpy`是一个转换函数,旨在将以零结尾的字符串转换为固定宽度的字符串。需要时间来了解`strncpy`与“安全字符串复制”完全无关,不能有意义地用于此目的。
当然,对于语言学生来说,了解`strncpy`的目的通常比整理除法运算符要花费更长的时间。然而,这是任何针对`strncpy`的“不安全性”声明的基础。

PS:被接受的答案中链接的 CERT 文档专门用于展示典型的不称职的 strncpy 函数的滥用,作为 strcpy 的“安全”版本而存在的不安全性。这并不意味着 strncpy 本身有什么安全问题。


2
当人们说 "strncpy 不安全" 时,他们的意思是 "它没有一个 成功之坑"。这是真的。 - Ben Voigt

2
Git 2.19(2018年第三季度)的一个补丁发现,滥用系统API函数如strcat()strncpy()太容易了,因此禁止在该代码库中使用这些函数。
请参见提交记录e488b7a提交记录cc8fdae提交记录1b11b64(于2018年7月24日),以及提交记录c8af66a(于2018年7月26日),由Jeff King (peff)撰写。
(由Junio C Hamano -- gitster --提交记录e28daf2中合并)

banned.h:将strcat()标记为禁用

strcat()函数与strcpy()具有相同的溢出问题。
另外,作为额外的奖励,很容易无意中出现二次方,因为每个后续调用都必须遍历现有字符串。

最后一个strcat()调用在提交记录f063d38(守护程序:重新生成时使用cld->env_array,2015-09-24,Git 2.7.0)中消失了。
通常,可以使用动态字符串(strbufxstrfmt)或者如果知道长度受限,则可以使用xsnprintf来替换strcat()


这个答案对之前的回答有什么补充? - chqrlie
2
@chqrlie 只是一个例子,说明在 OP 九年后,这些函数仍然如此危险(并且补丁说明了原因),以至于它们被禁止在 C 代码库中使用(比如,这里的 Git)。 - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接