84

我理解这一点,strlcpy并且被设计为和strlcat的安全替代品。但是,有些人仍然认为他们不安全,并且只会导致不同类型的问题strncpystrncat

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

Ulrich Drepper 和 James Antill 声明这是真的,但从未提供示例或澄清这一点。

4

8 回答 8

116

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

其次,strncat另一方面,它确实是一个使用 C 字符串并执行有限长度连接的函数(它确实是 的“安全”兄弟strcat)。为了正确使用这个函数,程序员必须特别小心,因为这个函数接受的大小参数实际上并不是接收结果的缓冲区的大小,而是它剩余部分的大小(也是终止符被隐式计算)。这可能会令人困惑,因为为了将该大小与缓冲区的大小联系起来,程序员必须记住执行一些额外的计算,这通常用于批评strncat.strlcat处理这些问题,更改接口以便不需要额外的计算(至少在调用代码中)。同样,我看到有人可以批评这一点的唯一依据是该功能不标准。此外,strcat由于基于重新扫描的字符串连接的想法的可用性有限,因此您在专业代码中不会经常看到来自组的函数。

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

于 2010-01-22T04:43:35.793 回答
32

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

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

现在,strlcat如果程序员记得检查结果,确实有效地进行了检查 - 所以你可以安全地使用它:

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

乌尔里希的观点是,既然你必须拥有destlensourcelen左右(或重新计算它们,这是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 buffer)。

于 2010-01-22T04:54:30.887 回答
20

当人们说“strcpy()很危险,请使用strncpy()”(或类似的陈述strcat()等,但我将在strcpy()这里作为我的重点)时,他们的意思是没有边界检查strcpy()。因此,过长的字符串会导致缓冲区溢出。他们是正确的。在这种情况下使用strncpy()将防止缓冲区溢出。

我觉得这strncpy()真的不能修复错误:它解决了一个优秀程序员可以轻松避免的问题。

作为 C 程序员,在尝试复制字符串之前,您必须知道目标大小。这也是strncpy()andstrlcpy()的最后一个参数中的假设:您向它们提供该大小。您还可以在复制字符串之前知道源大小。然后,如果目的地不够大,请不要调用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 实现与其他实现不同。这使得它比其他实现更不有用。

于 2010-01-22T04:54:11.573 回答
12

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

于 2010-01-22T05:50:52.017 回答
7

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

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

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

  1. 不能用于非字符串。

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

建议的标准函数的好处是您可以知道使用strl函数错过了多少数据。

于 2010-01-22T04:55:15.597 回答
6
于 2016-06-26T05:27:13.013 回答
3

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

所有这些 C 字符串函数(标准和不那么标准)的要点是它们使安全/安全使用变得容易的程度。strcpy() 和 strcat() 安全使用并非易事;多年来,C 程序员犯错的次数已经证明了这一点,并且随之而来的是令人讨厌的漏洞和漏洞利用。strlcpy() 和 strlcat() 以及 strncpy() 和 strncat()、 strncpy_s () 和 strncat_s()更容易安全使用,但仍然不平凡。他们不安全/不安全吗?当使用不正确时,不超过 memcpy() 。

于 2019-03-21T00:53:01.137 回答
0

strlcpySIGSEGV如果srcNUL终止,则可能触发。

/* 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 */
于 2021-06-03T22:52:52.657 回答