关于C#:为什么strlcpy和strlcat不安全?

Why are strlcpy and strlcat considered insecure?

我了解strlcpystrlcat被设计为strncpystrncat的安全替代品。 但是,有些人仍然认为他们不安全,只会造成另一种类型的问题。

有人可以举一个例子说明如何使用strlcpystrlcat(即始终为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参数实际上并不是接收结果的缓冲区的大小,而是其剩余部分的大小(也包括终止符)被隐式计算)。这可能会造成混淆,因为为了将该大小与缓冲区的大小绑定在一起,程序员必须记住要执行一些额外的计算,这些计算经常被用来批评strncatstrlcat解决了这些问题,更改了接口,因此不需要额外的计算(至少在调用代码中)。同样,我看到的唯一可以批评这一点的依据是该功能不是标准的。同样,由于基于重新扫描的字符串串联的概念的可用性有限,因此在专业代码中通常不会看到strcat组中的函数。

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


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的观点是,由于必须包含destlensourcelen(或重新计算它们,这是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_bufferlendest buffer的完整大小。) 。


当人们说" 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实现与其他实现不同。这使得它的使用性比其他方面更没有用。


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


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

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

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

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


    我不认为strlcpystrlcat被认为是不安全的,或者至少这不是它们不包含在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()。