为什么strlcpy和strlcat不安全?


80

据我所知,strlcpystrlcat被设计为安全的替代品strncpystrncat。但是,有些人仍然认为他们没有安全感,只会引起另一种类型的问题

有人可以举一个使用strlcpystrlcat(即始终为null的函数终止其字符串)如何导致安全问题的示例吗?

乌尔里希·德雷珀(Ulrich Drepper)和詹姆斯·安提尔(James Antill)指出,这是事实,但从未提供示例或阐明这一点。

Answers:


112

首先,strlcpy从未被用作的安全版本strncpystrncpy也从未被用作的安全版本strcpy)。这两个功能是完全无关的。strncpy是一个完全与C字符串(即以N结尾的字符串)无关的函数。str...它的名称中带有前缀的事实只是一个历史错误。的历史和目的strncpy是众所周知的且有据可查。这是一个功能,用于处理在某些历史版本的Unix文件系统中使用的所谓的“固定宽度”字符串(不适用于C字符串)。如今,有些程序员对它的名称感到困惑,并认为它strncpy应该以某种方式用作有限长度的C字符串复制功能(strcpy),实际上是完全废话,并会导致不良的编程习惯。当前形式的C标准库没有任何功能可用于有限长度的C字符串复制。这正是strlcpy适合的地方。strlcpy确实是为使用C弦而创建的真正的有限长度复制功能。strlcpy正确执行有限长度复制功能应做的所有事情。可以针对的唯一批评是,遗憾的是,这不是标准的。

其次,strncat另一方面,确实是一个与C字符串一起使用并执行有限长度的串联的函数(它确实是的“安全”兄弟strcat)。为了正确使用此函数,程序员必须格外小心,因为此函数接受的size参数实际上并不是接收结果的缓冲区的大小,而是其剩余部分的大小(也包括终止符)被隐式计算)。这可能会造成混淆,因为为了将该大小与缓冲区的大小绑定在一起,程序员必须记住要执行一些额外的计算,而这些计算通常被用来批评strncatstrlcat解决了这些问题,更改了接口,因此不需要额外的计算(至少在调用代码中)。同样,我看到的唯一可以批评这一点的依据是该功能不是标准的。此外,strcat由于基于重新扫描的字符串连接的基本概念的可用性有限,因此在专业代码中通常不会看到group函数。

至于这些功能如何导致安全问题……它们根本不可能。它们所导致的安全问题的程度不能超过C语言本身“导致安全问题”的程度。您会看到,很长一段时间以来,人们一直强烈认为C ++语言必须朝着发展为某种怪异的Java风格的方向发展。这种情绪有时也会蔓延到C语言的领域,从而导致对C语言功能和C标准库的功能相当无知和被迫批评。我怀疑在这种情况下,我们可能也会处理类似的问题,尽管我当然希望事情并没有那么糟糕。


9
我不同意。如果它们超出目标缓冲区大小限制,strlcpy并且strlcat会报告某种错误情况,那将是很好的。尽管您可以检查返回的长度以进行测试,但这并不明显。但是我认为这是一个小批评。“他们鼓励使用C字符串,所以它们是不好的”的说法很愚蠢。
2012年

7
“这些功能如何导致安全问题” –之前我认为问题在于某些C函数比其他C函数更难正确使用。有些人错误地认为存在一个特殊的难度阈值,低于该阈值的功能是“安全的”,而高于该阈值的则是“不安全的”。这样的人通常也相信的strcpy是高于阈值,因此“不安全”,并且它们的优选字符串复制函数(无论是strlcpystrcpy_s甚至strncpy)低于阈值,因此“安全”。
史蒂夫·杰索普

31
有很多理由不喜欢strlcpy / strlcat,但您没有陈述任何理由。C ++和Java的讨论是无关紧要的。这个答案对问题的实质没有帮助。
约翰·里普利

24
@约翰·里普利(John Ripley):首先,我之所以不“陈述其中任何一个”,仅仅是因为我不知道有什么不喜欢的理由strlcpy/strlcat。人们可能会“不喜欢”零终止字符串的一般概念,但这不是问题所在。如果您知道“有很多不喜欢的理由strlcpy/strlcat”,则可能应该写出自己的答案,而不是期望我能够读懂别人的想法。
2013年

8
@John Ripley:其次,这个问题专门指的是的一些所谓的“安全问题” strlcpy/strlcat。虽然我相信我了解这是什么意思,但我个人拒绝承认这是传统C语言领域内的“安全问题”。我在回答中说过。
2013年

31

Ulrich的批评基于这样的想法,即程序未检测到的字符串截断会通过错误的逻辑导致安全问题。因此,为了安全起见,您需要检查截断。对字符串连接执行此操作意味着您正在执行以下检查:

现在,strlcat如果程序员记得要检查结果,则可以有效地执行此检查-因此您可以安全地使用它:

李晶的观点是,因为你必须有destlensourcelen周围(或重新计算它们,这是strlcat有效的呢),你可能也仅仅使用了更高效memcpy呢:

(在上面的代码中,dest_maxlen是可以存储在dest其中的字符串的最大长度-小于dest缓冲区的大小。 dest_bufferlen是的完整大小dest buffer)。


13
Drepper代码的可读性很差。使用strlcpy(或任何str函数),我直接知道我正在复制0终止的C字符串。有了memcpy它可以是任何类型的内存,我有一个补充维试图理解代码时检查。我有一个旧版应用程序来调试所有用memcpy完成的地方,这是要纠正的真正的PITA。移植到专用的String函数后,它更易于阅读(并且速度更快,因为strlen可以删除很多不必要的东西)。
帕特里克·施吕特(PatrickSchlüter)

3
@domen:因为要复制的大小是已知的,所以memcpy()就足够了(并且可能比效率更高strcpy())。
咖啡馆

1
好吧,在字符串操作中使用它会令人困惑。据我所知,效率取决于实现方式,并且不是标准化的。
domen 2014年

8
@domen:memcpy() 一个字符串操作-毕竟在中声明的<string.h>
caf 2014年

2
@domen我同意存在混淆的可能性,但是事实是,无论如何,使用C字符串还是可以处理原始内存。可以说,如果人们根本不再认为C具有“字符串”(与任何其他连续的内存块不同),那么我们所有人都会变得更好。
mtraceur '18

19

当人们说“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实现与其他实现不同。这使得它的实用性甚至比其他方面低。


3
strlcpy速度比memcpy许多体系结构都要快,尤其是在memcpy复制不必要的尾随数据时。strlcpy还返回您错过了多少数据,这可能使您可以更快地恢复并且使用更少的代码。

1
@jbcreix:我的意思是应该没有任何数据丢失,n在我的memcpy调用中是要写入的确切字节数,因此效率也不是什么大问题。
Alok Singhal'1

5
那你怎么得到那个n?您可以事先知道的唯一n是缓冲区大小。当然,如果您建议strlcpy每次使用时都需要重新实现memcpystrlen那也可以,但是为什么要在处停止strlcpy,您也不需要任何memcpy功能,因此可以逐个复制字节。在通常情况下,参考实现只循环一次数据,这对于大多数体系结构来说更好。但是,即使最好的实现使用strlen+ memcpy,也仍然没有理由不必一次又一次地重新实现安全的strcpy。

1
Alok,strlcpy在好情况下(应为多数)只扫过输入字符串一次,在坏情况下只扫两次,而strlen+memcpy则总是扫过两次。如果在实践中有所作为,则另当别论。
PatrickSchlüter2010年

4
“ *((char *)mempcpy(dst,src,n))= 0;” -正确-是,显然正确,没有。代码审查失败……
mattnz

12

我认为Ulrich和其他人认为这会给人一种错误的安全感。意外地将字符串截断可能会对代码的其他部分产生安全影响(例如,如果文件系统路径被截断,则程序可能未在预期的文件上执行操作)。


8
例如,电子邮件客户端可能会将电子邮件附件的文件名从截断malware.exe.jpgmalware.exe
克里斯·彼得森

1
@ChrisPeterson这就是为什么优秀的开发人员总是检查返回值的原因,对于strl *函数,要知道数据是否被截断并采取相应的措施。
汤姆·林特

“乌尔里希和其他人认为这会给人一种虚假的安全感。他们应该使用更安全的功能,并避免大多数问题。然后他们可以开始告诉其他人如何编写更安全的代码...
jww

7

有两个与使用strl函数有关的“问题”:

  1. 您必须检查返回值以避免被截断。

c1x标准草拟者和Drepper认为,程序员不会检查返回值。Drepper说我们应该以某种方式知道长度并使用memcpy并完全避免使用字符串函数。标准委员会认为,除非有另外声明,否则安全的strcpy应该在截断时返回非零值_TRUNCATE。这个想法是人们更可能使用if(strncpy_s(...))。

  1. 不能用于非字符串。

有人认为,即使输入虚假数据,字符串函数也绝不会崩溃。这会影响标准功能,例如在正常情况下会断断续续的strlen。新标准将包括许多此类功能。这些检查当然会影响性能。

建议的标准函数的好处是,您可以知道strl函数遗漏了多少数据。


请注意,这strncpy_s不是安全版本,strncpy但基本上是strlcpy替代版本。

6

我不认为strlcpy并且strlcat被认为是不安全的,或者至少这不是为什么它们不包含在glibc中的原因-毕竟,glibc包含strncpy甚至strcpy。

他们得到的批评是据称他们效率低下,而不是没有安全感

根据Damien Miller的《安全可移植性》论文:

strlcpy和strlcat API会正确检查目标缓冲区的边界,在所有情况下均以nul结尾,并返回源字符串的长度,从而可以检测到截断。该API已被大多数现代操作系统和许多独立软件包所采用,包括OpenBSD(起源于此),Sun Solaris,FreeBSD,NetBSD,Linux内核,rsync和GNOME项目。值得注意的例外是GNU标准C库glibc [12],其维护者坚定地拒绝包含这些改进的API,并对其进行标记 “效率极低的BSD废话”。[4],尽管有以前的证据表明它们在大多数情况下比它们替代的API更快[13]。结果,OpenBSD端口树中存在的100多个软件包维护着自己的strlcpy和/或strlcat替代品或等效的API,这不是理想的状态。

这就是为什么它们在glibc中不可用,但是在Linux上它们不可用并不是事实。它们可在Linux的libbsd中获得:

它们打包在Debian和Ubuntu和其他发行版中。您也可以只获取一份副本并在您的项目中使用-它是简短的,并得到许可:


3

安全性不是布尔值。C函数并非完全“安全”或“不安全”,“安全”或“不安全”。如果使用不正确,C中的简单分配操作可能会“不安全”。当程序员提供正确使用的必要保证时,可以安全地(安全地)使用strlcpy()和strlcat(),就像可以安全地使用strcpy()和strcat()一样。

所有这些C字符串函数(标准的和非标准的)的要点是它们使安全/安全使用变得容易的级别。安全使用strcpy()和strcat()并非易事;多年来,C程序员将其弄错的次数证明了这一点,随之而来的是令人讨厌的漏洞和利用。strlcpy()和strlcat()以及就此而言,安全使用strncpy()和strncat(),strncpy_s()和strncat_s()有点容易,但是仍然很简单。他们不安全/不安全吗?如果使用不当,只不过是memcpy()。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.