为什么strlcpy和strlcat认为不安全?

我知道strlcpystrlcat被devise为strncpystrncat安全替代品。 但是,有些人仍然认为他们是不安全的,只是造成不同types的问题 。

有人可以举一个例子,说明如何使用strlcpystrlcat (即一个总是 null结束其string的函数)会导致安全问题?

Ulrich Drepper和James Antill说这是真实的,但是从来没有提供实例或澄清这一点。

首先, strlcpy从来没有作为strncpy的安全版本( strncpy从来没有打算作为strcpy的安全版本)。 这两个function是完全无关的。 strncpy是一个与Cstring(即以null结尾的string)无关的函数。 事实上,它的名字前缀是一个历史性的错误。 strncpy的历史和目的是众所周知的,并且是有据可查的。 这是一个为在Unix文件系统的某些历史版本中使用的所谓的“固定宽度”string(而不是Cstring)创build的函数。 今天一些程序员被它的名字弄糊涂了,并假设strncpy被认为是有限长度的Cstring复制函数(一个“安全”的strcpy兄弟),实际上这是完全废话,导致不好的编程习惯。 目前forms的C标准库对限制长度的Cstring拷贝没有任何作用。 这是strlcpy适合的地方strlcpy确实是一个真正的限制长度的复制function,用于处理Cstring。 strlcpy正确地做一切有限长度的复制function应该做的。 唯一可以批评的是,这是遗憾的,不是标准的。

其次, strncat另一方面的确是一个与Cstring一起工作的函数,并且执行有限长度的连接(这确实是strcat的“安全的”兄弟)。 为了正确地使用这个函数,程序员必须特别小心,因为这个函数接受的size参数并不是接收结果的缓冲区的大小,而是剩余部分的大小(也就是结束符被隐含计数)。 这可能会令人困惑,因为为了将这个大小与缓冲区的大小关联起来,程序员必须记住执行一些额外的计算,这通常用来批评strncatstrlcat处理这些问题,改变界面,以便不需要额外的计算(至less在调用代码中)。 再次,我看到一个唯一的基础可以批评这个是function不规范。 此外, strcat组的函数是你不会经常在专业代码中看到的,因为基于重新扫描的string连接这个概念的可用性有限。

至于这些function如何导致安全问题…他们根本就做不到。 它们不会比C语言本身能导致安全问题更大程度地导致安全问题。 你看,一段时间以来,有一种强烈的感觉,C ++语言必须朝着发展成为一种怪异的Java的方向发展。 这种情绪也有时会蔓延到C语言领域,导致对C语言特征和C标准库的特征的相当无知和强制的批评。 我怀疑在这种情况下,我们可能也在处理类似的事情,尽pipe我当然希望事情不是那么糟糕。

乌尔里希的批评是基于这样的想法,即程序没有检测到的string截断可能导致安全问题,通过不正确的逻辑。 因此,为了安全起见,您需要检查截断。 要做到这一点的string连接意味着你正在做一个检查沿此行:

 if (destlen + sourcelen > dest_maxlen) { /* Bug out */ } 

现在, strlcat确实有效地做这个检查,如果程序员记得检查结果 – 所以你可以安全地使用它:

 if (strlcat(dest, source, dest_bufferlen) >= dest_bufferlen) { /* Bug out */ } 

Ulrich的观点是,既然你必须有destlendestlen (或重新计算它们,这是strlcat有效的function),不妨使用更高效的memcpy

 if (destlen + sourcelen > dest_maxlen) { goto error_out; } memcpy(dest + destlen, source, sourcelen + 1); destlen += sourcelen; 

(在上面的代码中, dest_maxlen是可以存储在dest中的string的最大长度 – 比dest缓冲区的长度小1, dest_bufferlendest缓冲区的全长)。

当人们说,“ strcpy()是危险的,用strncpy()而不是”(或类似的声明关于strcat()等,但我打算用strcpy()这里作为我的焦点),他们的意思是没有界限检查strcpy() 。 因此,过长的string将导致缓冲区溢出。 他们是正确的。 在这种情况下使用strncpy()将防止缓冲区溢出。

我觉得strncpy()确实不能修复bug:它解决了一个好程序员可以轻松避免的问题。

作为C程序员,在尝试复制string之前, 必须知道目标大小。 这是strncpy()strlcpy()的最后一个参数中的假设:您将这个大小提供给它们。 您还可以在复制string之前知道源大小。 那么,如果目的地不够大, 请不要调用strcpy() 。 重新分配缓冲区,或者做其他事情。

为什么我不喜欢strncpy()

  • 在大多数情况下, strncpy()是一个不好的解决scheme:你的string会被截断而没有任何的注意 – 我宁愿写额外的代码来自己弄清楚,然后采取我想采取的行动,而不是让一些function决定了我该怎么做。
  • strncpy()是非常低效的。 它写入目标缓冲区中的每个字节。 在您的目的地结束时,您不需要成千上万个'\0'
  • 如果目的地不够大,它不会写入终止的'\0' 。 所以,无论如何,你必须这样做。 这样做的复杂性是不值得的。

现在,我们来strlcpy() 。 从strncpy()的变化使它更好,但我不确定是否strl*的具体行为保证它们的存在:它们太具体了。 你仍然需要知道目的地的大小。 它比strncpy()更有效,因为它不一定会写入目标中的每个字节。 但它解决了一个问题,可以通过这样做来解决: *((char *)mempcpy(dst, src, n)) = 0;

我不认为有人说strlcpy()strlcat()可能导致安全问题,他们(和我)说他们可能导致错误,例如,当你期望写完整的string而不是它的一部分。

这里的主要问题是:要复制多less个字节? 程序员必须知道这一点,如果他不知道, strncpy()strlcpy()不会救他。

strlcpy()strlcat()不是标准的,既不是ISO C也不是POSIX。 所以,在便携式程序中的使用是不可能的。 事实上, strlcat()有两种不同的变体: Solaris的实现与其他的长度为0的边缘情况的实现不同。这使得它更加有用。

我认为乌尔里希和其他人认为这会给人一种虚假的安全感。 无意中截断string可能会对代码的其他部分产生安全隐患(例如,如果文件系统path被截断,程序可能不会对预期文件执行操作)。

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

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

c1x标准草案作者和Drepper认为,程序员不会检查返回值。 Drepper说,我们应该知道长度,并使用memcpy,并避免使用string函数。标准委员会认为,除非_TRUNCATE标志另有说明,否则安全strcpy应该在截断时返回非零_TRUNCATE 。 这个想法是人们更可能使用if(strncpy_s(…))。

  1. 不能在非string上使用。

有些人认为string函数应该永远不会崩溃,即使是伪造的数据。 这会影响标准function,例如在正常情况下会发生段错误的strlen。 新标准将包括许多这样的function。 当然检查有性能损失。

build议的标准function的好处是你可以知道你用strl函数丢失了多less数据。

我不认为strlcpystrlcat被认为是不安全的 ,至less它不是glibc中不包括的原因 – 毕竟glibc包含strncpy甚至strcpy。

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

根据Damien Miller的Secure Portability论文:

strlcpy和strlcat API正确地检查目标缓冲区的边界,在所有情况下都终止,并返回源string的长度,允许检测截断。 这个API已被大多数现代操作系统和许多独立的软件包所采用,包括OpenBSD(起源),Sun Solaris,FreeBSD,NetBSD,Linux内核,rsync和GNOME项目。 值得注意的例外是GNU标准C库glibc [12],其维护者坚决拒绝包含这些改进的API,并将其标记为“非常低效的BSD垃圾” [4],尽pipe之前有证据表明它们比API更快他们取代[13]。 因此,OpenBSD ports树中的100多个软件包保留了自己的strlcpy和/或strlcat替代品或等效的API–这不是一个理想的状态。

这就是为什么他们不能在glibc中使用,但是它们在Linux上不可用。 它们在Linux上的libbsd中可用:

他们打包在Debian和Ubuntu和其他发行版。 您也可以只抓取一份并在您的项目中使用 – 它很短,并在许可证下: