strlcpy
和strlcat
是为了替代不安全的strncpy
和strncat
而设计的。然而,一些人仍然认为它们存在安全问题,并只是引起了不同类型的问题。有人能举个例子来说明使用
strlcpy
或strlcat
(即总是在字符串末尾添加空字符的函数)如何会导致安全问题吗?Ulrich Drepper和James Antill表示这是真实存在的,但从未提供过例子或澄清此观点。
strlcpy
和strlcat
是为了替代不安全的strncpy
和strncat
而设计的。然而,一些人仍然认为它们存在安全问题,并只是引起了不同类型的问题。strlcpy
或strlcat
(即总是在字符串末尾添加空字符的函数)如何会导致安全问题吗?首先,strlcpy
从未被设计成strncpy
的安全版本(而strncpy
也从未被设计为strcpy
的安全版本)。这两个函数完全没有关联。 strncpy
是一个与C字符串(即以null结尾的字符串)毫无关系的函数。它的名称中带有str...
前缀只是历史上的失误。 strncpy
的历史和目的是众所周知并有充分的记录。它是一个为处理某些历史版本的Unix文件系统中使用的“固定宽度”字符串(而非C字符串)而创建的函数。今天一些程序员会因其名称而感到困惑,并假设strncpy
应该作为有限长度的C字符串复制函数(strcpy
的“安全”姊妹函数),但实际上这完全是胡说八道,会导致糟糕的编程实践。 C标准库在其当前形式中根本没有任何有限长度的C字符串复制函数。这就是strlcpy
的用处所在。 strlcpy
确实是一个真正的有限长度复制函数,用于处理C字符串。 strlcpy
正确地执行了所有有限长度复制函数应该执行的操作。唯一可以批评它的是,遗憾的是它不是标准函数。
strncat
的确是一个用于处理C字符串并执行有限长度连接的函数(它是strcat
的“安全”兄弟)。为了正确使用此函数,程序员必须特别小心,因为此函数接受的大小参数实际上并不是接收结果的缓冲区的大小,而是其剩余部分的大小(终止字符也隐含计算在内)。这可能会令人困惑,因为为了将该大小与缓冲区的大小联系起来,程序员必须记住执行一些额外的计算,这通常被用来批评strncat
。strlcat
解决了这些问题,改变了接口,使得不需要进行额外的计算(至少在调用代码中不需要)。再次强调,我认为唯一可以批评的基础是该函数不是标准函数。此外,strcat
组的函数由于基于重新扫描的字符串连接的有限可用性,在专业代码中很少出现。Ulrich批评的基础是,程序没有检测到的字符串截断可能会通过不正确的逻辑导致安全问题。因此,为了安全起见,您需要检查截断。对于字符串连接,要执行这个检查意味着你需要进行类似以下的检查:
if (destlen + sourcelen > dest_maxlen)
{
/* Bug out */
}
现在,strlcat
实际上确实进行了这个检查,如果程序员记得检查结果,那么你是可以安全使用它的:
if (strlcat(dest, source, dest_bufferlen) >= dest_bufferlen)
{
/* Bug out */
}
Ulrich的观点是,由于您必须拥有destlen
和sourcelen
(或重新计算它们,这正是strlcat
有效地执行的操作),因此您可能还是更好地使用更高效的memcpy
:
if (destlen + sourcelen > dest_maxlen)
{
goto error_out;
}
memcpy(dest + destlen, source, sourcelen + 1);
destlen += sourcelen;
在上述代码中,dest_maxlen
是能够存储在 dest
中的字符串的最大长度 - 比 dest
缓冲区的大小小一。 dest_bufferlen
是 dest 缓冲区
的完整大小。
strcpy
而是使用memcpy
? - domenmemcpy()
足以实现(并且潜在地比strcpy()
更有效率)。 - cafmemcpy()
是一个字符串操作 - 毕竟它在 <string.h>
中声明了。 - cafstrcpy()
是危险的,请使用strncpy()
代替”(或类似strcat()
等语句,但我将以strcpy()
为重点),他们的意思是strcpy()
中没有边界检查。因此,过长的字符串会导致缓冲区溢出。他们是正确的。在这种情况下使用strncpy()
将防止缓冲区溢出。strncpy()
并不能真正解决问题:优秀的程序员可以很容易避免这个问题。strncpy()
和strlcpy()
的最后一个参数所假设的:你向它们提供了该大小。你也可以在复制字符串之前知道源大小。然后,如果目标不够大,就不要调用strcpy()
。要么重新分配缓冲区,要么采取其他措施。strncpy()
?
strncpy()
在大多数情况下都是一个糟糕的解决方案:你的字符串将被截断而没有任何提示- 我宁愿编写额外的代码自己解决这个问题,然后采取我想要采取的行动,而不是让某些函数为我决定该做什么。strncpy()
非常低效。它会写入目标缓冲区中的每个字节。你不需要在目标末尾有成千上万个'\0'
。'\0'
。因此,你必须自己完成这一任务。做这件事的复杂性不值得麻烦。strlcpy()
。与strncpy()
相比,它进行了改进,但我不确定strl*
的特定行为是否值得存在:它们过于特定。您仍然需要知道目标大小。它比strncpy()
更有效,因为它不一定会写入目标中的每个字节。但它解决了一个可以通过执行以下操作来解决的问题:*((char*)mempcpy(dst, src, n)) = 0;
。strlcpy()
或strlcat()
会导致安全问题,他们(和我)所说的是,它们可能会导致错误,例如当您期望完整字符串被写入而不是其中的一部分时。strncpy()
或strlcpy()
也无济于事。
strlcpy()
和strlcat()
都不是标准的ISO C或POSIX。因此,在便携式程序中使用它们是不可能的。事实上,strlcat()
有两个不同的变体:Solaris实现与其他实现在长度为0的边缘情况下不同。这使得它比其他情况下还要没用。memcpy
复制不必要的尾随数据时,strlcpy
比memcpy
更快。 strlcpy
还返回您错过了多少数据,这可能使您能够更快地进行恢复并减少代码量。 - user14554n
是要写入的确切字节数,因此效率也不是太大的问题。 - Alok Singhalmemcpy
和strlen
重新实现strlcpy
,那也可以,但是为什么要停留在 strlcpy
呢?您也不需要一个 memcpy
函数,可以逐个字节复制。参考实现在正常情况下只循环一次数据,对大多数体系结构来说更好。但即使最佳实现使用 strlen
+ memcpy
,仍然没有理由不需要一遍又一遍地重新实现安全的 strcpy。 - user14554strlcpy
在好的情况下(这应该是大多数情况),您只需扫描一次输入字符串,在坏的情况下需要扫描两次;而使用strlen
+memcpy
则总是需要扫描两次。实际上是否有差别是另一回事。 - Patrick Schlüter我认为Ulrich和其他人认为这会给人一种虚假的安全感。意外截断字符串可能会对代码的其他部分产生安全影响(例如,如果文件系统路径被截断,程序可能无法在预期的文件上执行操作)。
使用strl函数有两个相关的“问题”:
c1x标准草案作者和Drepper认为程序员不会检查返回值。Drepper说我们应该知道长度并使用memcpy,完全避免使用字符串函数,标准委员会认为安全的strcpy应该在截断时返回非零值,除非由_TRUNCATE
标志另有说明。这样做的想法是人们更有可能使用if(strncpy_s(...))。
有些人认为字符串函数即使收到错误数据也不应崩溃。这影响标准函数,例如strlen,在正常情况下会导致段错误。新标准将包括许多这样的函数。当然,这些检查会带来性能损失。
与建议中的标准函数相比,使用strl函数的好处是您可以知道错过了多少数据。
strncpy_s
不是 strncpy
的安全版本,而基本上是 strlcpy
的替代品。 - user14554我认为strlcpy
和strlcat
并不被认为是不安全的,或者至少这不是它们没有被包含在glibc中的原因 - 毕竟,glibc包括strncpy甚至strcpy。
他们受到的批评是它们据称是低效的,而不是不安全的。
根据Damien Miller的Secure Portability论文:
strlcpy和strlcat API会正确检查目标缓冲区的边界,无论何时都会以空字符结尾,并返回源字符串的长度,从而可以检测到截断。这个API已经被大多数现代操作系统和许多独立软件包所采用,包括OpenBSD(其起源地)、Sun Solaris、FreeBSD、NetBSD、Linux内核、rsync和GNOME项目。值得注意的例外是GNU标准C库glibc [12],它的维护者坚定地拒绝包含这些改进的API,并将它们称为“可怕低效的BSD垃圾”[4],尽管之前有证据表明它们在大多数情况下比它们替换的API更快[13]。因此,在OpenBSD端口树中存在的100多个软件包维护自己的strlcpy和/或strlcat替代品或等效API,这不是一个理想的状态。它们已经被打包在Debian、Ubuntu和其他发行版中。您也可以直接获取一份副本并在您的项目中使用——它很短且受到宽松许可证的保护:
strlcpy
可能会触发 SIGSEGV
,如果 src
没有以 NUL
结尾。
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
strlcpy
和strlcat
达到了目标缓冲区大小的限制,报告某种错误状态会很好。虽然你可以检查返回的长度来测试此条件,但这不是很明显。但我认为这只是一个小的批评。声称它们鼓励使用C字符串,因此它们是不好的的观点是愚蠢的。 - Omnifariousstrcpy
的难度超过了门槛,因此是“不安全”的;他们更喜欢的字符串复制函数(无论是strlcpy
、strcpy_s
还是strncpy
)的难度低于门槛,因此是“安全”的。 - Steve Jessopstrlcpy/strlcat
。有人可能“不喜欢”零终止字符串的一般概念,但这不是问题的重点。如果你知道“很多不喜欢strlcpy/strlcat
的理由”,你应该自己编写答案,而不是期望我能读懂别人的想法。 - AnT stands with Russiastrlcpy/strlcat
存在的一些所谓“安全问题”。虽然我相信我理解这是关于什么的,但我个人拒绝承认这是传统C语言领域内的“安全问题”,因为我知道的是如此。这正是我在我的回答中所陈述的。 - AnT stands with Russia