我了解strlcpy和strlcat被设计为strncpy和strncat的安全替代品。 但是,有些人仍然认为他们不安全,只会造成另一种类型的问题。
有人可以举一个例子说明如何使用strlcpy或strlcat(即始终为null的函数终止其字符串)会导致安全问题吗?
乌尔里希·德雷珀(Ulrich Drepper)和詹姆斯·安提尔(James Antill)指出,这是事实,但从未提供示例或阐明这一点。
首先,strlcpy从未被用作strncpy的安全版本(并且strncpy从未被作为strcpy的安全版本)。这两个功能是完全无关的。 strncpy是一个完全与C字符串(即以null终止的字符串)无关的函数。它的名称中带有str...前缀的事实只是一个历史错误。 strncpy的历史和用途是众所周知的且有据可查。这是一个功能,用于处理在某些历史版本的Unix文件系统中使用的所谓的"固定宽度"字符串(不适用于C字符串)。今天,有些程序员对它的名称感到困惑,并认为strncpy应该以某种方式用作有限长度的C字符串复制功能(strcpy的"安全"同级),实际上,这完全是胡说八道,并导致不良后果。编程实践。当前形式的C标准库没有任何功能可用于有限长度的C字符串复制。这就是strlcpy适用的地方。strlcpy实际上是为使用C字符串而创建的真正的有限长度复制功能。 strlcpy正确执行了有限长度复制功能应做的所有事情。可以针对的唯一批评是,遗憾的是,这不是标准的。
其次,另一方面,strncat确实是可与C字符串一起使用并执行有限长度的串联的函数(实际上是strcat的"安全"同级)。为了正确使用此函数,程序员必须格外小心,因为该函数接受的size参数实际上并不是接收结果的缓冲区的大小,而是其剩余部分的大小(也包括终止符)被隐式计算)。这可能会造成混淆,因为为了将该大小与缓冲区的大小绑定在一起,程序员必须记住要执行一些额外的计算,这些计算经常被用来批评strncat。 strlcat解决了这些问题,更改了接口,因此不需要额外的计算(至少在调用代码中)。同样,我看到的唯一可以批评这一点的依据是该功能不是标准的。同样,由于基于重新扫描的字符串串联的概念的可用性有限,因此在专业代码中通常不会看到strcat组中的函数。
至于这些功能如何导致安全问题……它们根本不可能。它们不会导致比C语言本身"导致安全问题"更大的程度的安全问题。您会看到,很长一段时间以来,人们一直强烈认为C ++语言必须朝着发展成某种怪异的Java风格的方向发展。这种情绪有时也会扩散到C语言的领域,从而导致对C语言功能和C标准库的功能相当无知和被迫批评。我怀疑在这种情况下,我们可能也会处理类似的问题,尽管我当然希望事情并没有那么糟糕。
-
我不同意。如果strlcpy和strlcat如果超出目标缓冲区大小限制,将报告某种错误情况,那就很好了。尽管您可以检查返回的长度以进行测试,但这并不明显。但是我认为那是一个小批评。它们鼓励使用C字符串,因此它们不好的说法是愚蠢的。
-
"这些功能如何导致安全问题"-之前我认为这里的问题是某些C函数比其他C函数更难正确使用。有些人错误地认为存在一个特殊的难度阈值,低于该阈值的功能是"安全的",而高于该阈值的则是"不安全的"。这些人通常还认为strcpy高于阈值,因此是"不安全的",并且他们偏爱的字符串复制功能(无论是strlcpy,strcpy_s甚至是strncpy)都在阈值以下。因此是"安全的"。
-
当然,实际上很少有功能(例如gets)无法正确使用,因此可以在没有进一步限定不安全之处的情况下将其合理地称为"不安全" :-)
-
有很多理由不喜欢strlcpy / strlcat,但您没有陈述任何理由。 C ++和Java的讨论是无关紧要的。这个答案只是对问题的实质没有帮助。
-
@约翰·里普利(John Ripley):首先,我不仅仅因为我不知道任何不喜欢strlcpystrlcat的原因而"声明了其中任何一个"。人们可能会"不喜欢"零终止字符串的一般概念,但这并不是问题所在。如果您知道"有很多理由不喜欢strlcpystrlcat的原因",则可能应该写出自己的答案,而不是期望我能够读懂别人的想法。
-
@John Ripley:第二,这个问题专门指的是strlcpystrlcat的一些所谓的"安全问题"。尽管我相信我了解这是什么意思,但我个人拒绝将其视为传统C语言领域内的"安全问题"。我在回答中说过。
-
"当前形式的C标准库没有任何功能可用于有限长度的C字符串复制。"只是说,snprintf ...
-
这个答案大多数都是有用的,但是关于安全问题的冗长的句子充其量只能是一个人(相当极端)的意见,而最坏的话就是废话。您对"安全性问题"的含义的定义很容易理解,但是通常认为缓冲区溢出是软件漏洞的主要来源,而没有长度检查的C字符串函数是缓冲区溢出的主要来源。我不知道为什么有人会粉饰它,但是它没有帮助。
-
似乎Linus Torvalds对strlcpy有相当强烈的意见。 @AnT:您认为这相关吗?
Ulrich的批评基于这样的想法,即程序未检测到的字符串截断会通过错误的逻辑导致安全问题。因此,为了安全起见,您需要检查截断。对字符串连接执行此操作意味着您正在执行以下检查:
1 2 3 4
| if (destlen + sourcelen > dest_maxlen)
{
/* Bug out */
} |
现在,如果程序员记得要检查结果,则strlcat会有效地执行此检查-因此您可以安全地使用它:
1 2 3 4
| if (strlcat(dest, source, dest_bufferlen) >= dest_bufferlen)
{
/* Bug out */
} |
Ulrich的观点是,由于必须包含destlen和sourcelen(或重新计算它们,这是strlcat有效的作用),因此您最好还是使用效率更高的memcpy:
1 2 3 4 5 6
| if (destlen + sourcelen > dest_maxlen )
{
goto error_out ;
}
memcpy(dest + destlen , source , sourcelen + 1);
destlen += sourcelen ; |
(在上面的代码中,dest_maxlen是可以存储在dest中的字符串的最大长度-比dest缓冲区的大小小一倍。dest_bufferlen是dest buffer的完整大小。) 。
-
Dreppers代码的可读性很差。使用strlcpy(或任何str函数),我直接知道Im正在复制0终止的C字符串。使用memcpy,它可以是任何类型的内存,在尝试理解代码时,我还有一个补充维要检查。我有一个旧版应用程序可以调试所有使用memcpy完成的地方,这是要纠正的真正的PITA。移植到专用的String函数后,它更易于阅读(并且速度更快,因为可以删除很多不必要的strlen)。
-
在上一个示例中,为什么不使用strcpy而不是memcpy?
-
@domen:因为已经知道要复制的大小,所以memcpy()就足够了(并且可能比strcpy()更有效)。
-
好吧,它在字符串操作中令人困惑。据我所知,效率取决于实现方式,并且不是标准化的。
-
@domen:memcpy()是字符串操作-毕竟是在中声明的。
-
@domen我同意存在混淆的可能性,但是事实是,无论如何,使用C字符串还是可以处理原始内存。可以说,如果人们只是停止认为C完全具有"字符串"(不同于任何其他连续的内存块),那么我们所有人都会变得更好。
当人们说" strcpy()是危险的,而使用strncpy()代替"(或关于strcat()的类似声明,等等,但是我将在这里使用strcpy()作为我的重点)时,它们表示没有界限检入strcpy()。因此,过长的字符串将导致缓冲区溢出。他们是正确的。在这种情况下,使用strncpy()可以防止缓冲区溢出。
我觉得strncpy()确实不能解决错误:它解决了一个好的程序员可以轻松避免的问题。
作为C程序员,在尝试复制字符串之前,您必须知道目标大小。这也是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()具有两个不同的变体:对于长度为0的边缘情况,Solaris实现与其他实现不同。这使得它的使用性比其他方面更没有用。
-
在许多体系结构上,strlcpy比memcpy更快,尤其是如果memcpy复制不必要的尾随数据时。 strlcpy还会返回您丢失了多少数据,这可能使您可以更快地恢复并且使用更少的代码。
-
@jbcreix:我的意思是应该没有数据丢失,并且在memcpy调用中n是要写入的确切字节数,因此效率也不是什么大问题。
-
那你怎么得到那个n?您可以事先知道的唯一n是缓冲区大小。当然,如果您建议每次使用memcpy和strlen重新实现strlcpy时也可以,但是为什么要在strlcpy停止,您也不需要memcpy函数,您可以复制字节一一对应。在通常情况下,参考实现只循环一次数据,这对于大多数体系结构来说更好。但是,即使最好的实现使用strlen + memcpy,也仍然没有理由不必一次又一次地重新实现安全的strcpy。
-
@jbcreix:通过在源字符串上执行strlen()来获得n,并且正如我所说,我确保目标有足够的空间。当然,如果您可以将字符串截断,那么可以使用strlcpy()。就个人而言,我宁愿在源文件上调用strlen()并确保目标文件有足够的空间。因此,我根本没有重新实现strlcpy()。权衡是在速度和复制所有数据之间。我的观点是,如果程序员注意strcpy()是/可以安全的。我并不是说strlcpy()完全没有用。 (继续...)
-
...但这也不是标准的。 strncpy(),尽管是标准的,但仍有不足之处。
-
Alok,使用strlcpy时,只在好情况下(应为多数)扫过输入字符串一次,在坏情况下扫过两次,而strlen + memcpy则总是扫过输入两次。如果在实践中有所作为,则另当别论。
-
" *((char *)mempcpy(dst,src,n))= 0;" -正确-是,显然正确,否。代码审查失败...
我认为Ulrich和其他人认为这会给人一种错误的安全感。意外地将字符串截断可能会对代码的其他部分产生安全影响(例如,如果文件系统路径被截断,则程序可能未在预期的文件上执行操作)。
-
例如,电子邮件客户端可能会将电子邮件附件的文件名从malware.exe.jpg截断为malware.exe。
-
@ChrisPeterson这就是为什么优秀的开发人员总是检查返回值的原因,对于strl *函数,要知道数据是否被截断并采取相应的措施。
-
"乌尔里希(Ulrich)和其他人认为这会给人一种错误的安全感。..."-大声笑...同时,乌尔里希(Ulrich)和他的朋友定期出现在BugTraq和Full Disclosure上,以证明他们的一次成功。他们应该使用更安全的功能,并避免大多数问题。然后他们可以开始告诉其他人如何编写更安全的代码...
有两个与使用strl函数有关的"问题":
您必须检查返回值
避免被截断。
c1x标准草案编写者和Drepper认为,程序员不会检查返回值。 Drepper说我们应该以某种方式知道长度并使用memcpy并完全避免使用字符串函数。标准委员会认为,除非_TRUNCATE标志另有说明,安全的strcpy应该在截断时返回非零值。这个想法是人们更可能使用if(strncpy_s(...))。
不能用于非字符串。
有人认为,即使输入虚假数据,字符串函数也绝不会崩溃。这会影响标准功能,例如在正常情况下会断断续续的strlen。新标准将包含许多此类功能。这些检查当然会影响性能。
建议的标准函数的好处是,您可以知道strl函数遗漏了多少数据。
-
请注意,strncpy_s不是strncpy的安全版本,但基本上是strlcpy的替代版本。
我不认为strlcpy和strlcat被认为是不安全的,或者至少这不是它们不包含在glibc中的原因-毕竟,glibc包含strncpy甚至strcpy。
他们受到的批评是据称他们效率低下,而不是没有安全感。
根据Damien Miller的《安全可移植性》论文:
The strlcpy and strlcat API properly check the target buffer’s bounds,
nul-terminate in all cases and return the length of the source string,
allowing detection of truncation. This API has been adopted by most
modern operating systems and many standalone software packages,
including OpenBSD (where it originated), Sun Solaris, FreeBSD, NetBSD,
the Linux kernel, rsync and the GNOME project. The notable exception
is the GNU standard C library, glibc [12], whose maintainer
steadfastly refuses to include these improved APIs, labelling them
"horribly inefficient BSD crap" [4], despite prior evidence that they
are faster is most cases than the APIs they replace [13]. As a result,
over 100 of the software packages present in the OpenBSD ports tree
maintain their own strlcpy and/or strlcat replacements or equivalent
APIs - not an ideal state of affairs.
这就是为什么它们在glibc中不可用,但是在Linux上它们不可用并不是事实。它们在libbsd的Linux上可用:
-
https://libbsd.freedesktop.org/
它们打包在Debian,Ubuntu和其他发行版中。您也可以只获取一份副本并在您的项目中使用-它是简短的,并得到许可:
-
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/lib/libc/string/strlcpy.c?rev=1.11
安全性不是布尔值。 C函数并非完全"安全"或"不安全","安全"或"不安全"。如果使用不正确,则C中的简单分配操作可能会"不安全"。当程序员提供正确使用的必要保证时,可以安全地(安全地)使用strlcpy()和strlcat(),就像可以安全地使用strcpy()和strcat()一样。
所有这些C字符串函数(标准的和非标准的)的要点是它们使安全/安全使用变得容易的级别。安全使用strcpy()和strcat()并非易事;多年来,C程序员将其弄错的次数证明了这一点,随之而来的是令人讨厌的漏洞和利用。 strlcpy()和strlcat()以及就此而言,安全使用strncpy()和strncat(),strncpy_s()和strncat_s()有点容易,但是仍然很简单。他们不安全/不安全吗?如果使用不当,只不过是memcpy()。