C语言 为什么这段代码容易受到缓冲区溢出攻击?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29910520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 11:53:50  来源:igfitidea点击:

Why is this code vulnerable to buffer overflow attacks?

csecuritybuffer-overflow

提问by Jason

int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

This code is vulnerable to a buffer overflow attack, and I'm trying to figure out why. I'm thinking it has to do with lenbeing declared a shortinstead of an int, but I'm not really sure.

这段代码容易受到缓冲区溢出攻击,我正在尝试找出原因。我认为这与len被声明为 ashort而不是 an 有关int,但我不确定。

Any ideas?

有任何想法吗?

回答by orlp

On most compilers the maximum value of an unsigned shortis 65535.

在大多数编译器上,an 的最大值unsigned short是 65535。

Any value above that gets wrapped around, so 65536 becomes 0, and 65600 becomes 65.

任何高于该值的值都会被环绕,因此 65536 变为 0,而 65600 变为 65。

This means that long strings of the right length (e.g. 65600) will pass the check, and overflow the buffer.

这意味着正确长度的长字符串(例如 65600)将通过检查,并溢出缓冲区。



Use size_tto store the result of strlen(), not unsigned short, and compare lento an expression that directly encodes the size of buffer. So for example:

使用size_t存储的结果strlen(),而不是unsigned short,并比较len以直接编码的大小的表达buffer。例如:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

回答by Daniel Rudy

The problem is here:

问题在这里:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

If the string is greater than the length of the target buffer, strncpy will still copy it over. You are basing the number of characters of the string as the number to copy instead of the size of the buffer. The correct way to do this is as follows:

如果字符串大于目标缓冲区的长度,strncpy 仍会复制它。您将字符串的字符数作为要复制的数字而不是缓冲区的大小。正确的方法如下:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '
int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '
 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.
'; return(0); }
';

What this does is limit the amount of data copied to the actual size of the buffer minus one for the null terminating character. Then we set the last byte in the buffer to the null character as an added safeguard. The reason for this is because strncpy will copy upto n bytes, including the terminating null, if strlen(str) < len - 1. If not, then the null is not copied and you have a crash scenario because now your buffer has a unterminated string.

这样做是将复制的数据量限制为缓冲区的实际大小减去空终止字符的一。然后我们将缓冲区中的最后一个字节设置为空字符作为附加保护。这样做的原因是因为 strncpy 将复制最多 n 个字节,包括终止空值,如果 strlen(str) < len - 1。如果不是,则不会复制空值并且您会遇到崩溃情况,因为现在您的缓冲区有一个未终止的细绳。

Hope this helps.

希望这可以帮助。

EDIT: Upon further examination and input from others, a possible coding for the function follows:

编辑:经过进一步检查和其他人的输入,该功能的可能编码如下:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

Since we already know the length of the string, we can use memcpy to copy the string from the location that is referenced by str into the buffer. Note that per the manual page for strlen(3) (on a FreeBSD 9.3 system), the following is stated:

由于我们已经知道字符串的长度,我们可以使用 memcpy 将字符串从 str 引用的位置复制到缓冲区中。请注意,根据 strlen(3) 的手册页(在 FreeBSD 9.3 系统上),声明如下:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}
char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

Which I interpret to be that the length of the string does not include the null. That is why I copy len + 1 bytes to include the null, and the test checks to make sure that the length < size of buffer - 2. Minus one because the buffer starts at position 0, and minus another one to make sure there's room for the null.

我解释为字符串的长度不包括空值。这就是为什么我复制 len + 1 个字节以包含空值,并且测试检查以确保长度 < 缓冲区大小 - 2。减去 1,因为缓冲区从位置 0 开始,减去另一个以确保有空间为空。

EDIT: Turns out, the size of something starts with 1 while access starts with 0, so the -2 before was incorrect because it would return an error for anything > 98 bytes but it should be > 99 bytes.

编辑:结果是,某些东西的大小从 1 开始,而访问从 0 开始,所以之前的 -2 是不正确的,因为它会返回任何> 98 字节的错误,但它应该是> 99 字节。

EDIT: Although the answer about a unsigned short is generally correct as the maximum length that can be represented is 65,535 characters, it doesn't really matter because if the string is longer than that, the value will wrap around. It's like taking 75,231 (which is 0x000125DF) and masking off the top 16 bits giving you 9695 (0x000025DF). The only problem that I see with this is the first 100 chars past 65,535 as the length check will allow the copy, but it will only copy up to the first 100 characters of the string in all cases and null terminate the string. So even with the wraparound issue, the buffer still will not be overflowed.

编辑:虽然关于 unsigned short 的答案通常是正确的,因为可以表示的最大长度是 65,535 个字符,但这并不重要,因为如果字符串长于该值,则该值将环绕。这就像取 75,231(即 0x000125DF)并屏蔽掉前 16 位给您 9695(0x000025DF)。我看到的唯一问题是超过 65,535 的前 100 个字符,因为长度检查将允许复制,但在所有情况下它最多只能复制字符串的前 100 个字符,并且 null 终止 string。所以即使有环绕问题,缓冲区仍然不会溢出。

This may or may not in itself pose a security risk depending on the content of the string and what you are using it for. If it's just straight text that is human readable, then there is generally no problem. You just get a truncated string. However, if it's something like a URL or even a SQL command sequence, you could have a problem.

这本身可能会也可能不会带来安全风险,具体取决于字符串的内容以及您使用它的目的。如果它只是人类可读的纯文本,那么通常没有问题。你只会得到一个截断的字符串。但是,如果它是类似 URL 或什至是 SQL 命令序列的内容,您可能会遇到问题。

回答by Patrick Roberts

Even though you're using strncpy, the length of the cutoff is still dependent on the passed string pointer. You have no idea how long that string is (the location of the null terminator relative to the pointer, that is). So calling strlenalone opens you up to vulnerability. If you want to be more secure, use strnlen(str, 100).

即使您正在使用strncpy,截止的长度仍然取决于传递的字符串指针。您不知道该字符串有多长(即空终止符相对于指针的位置)。所以strlen单独打电话会让你容易受到伤害。如果您想更安全,请使用strnlen(str, 100).

Full code corrected would be:

更正的完整代码将是:

##代码##

回答by Friedrich

The answer with the wrapping is right. But there is a problem I think was not mentioned if(len >= 100)

包装的答案是正确的。但是有一个我认为没有提到的问题 if(len >= 100)

Well if Len would be 100 we'd copy 100 elements an we'd not have trailing \0. That clearly would mean any other function depending on proper ended string would walk way beyond the original array.

好吧,如果 Len 是 100,我们将复制 100 个元素,并且我们不会有尾随 \0。这显然意味着任何其他依赖于正确结束字符串的函数都会超出原始数组。

The string problematic from C is IMHO unsolvable. You'd alway better have some limits before the call, but even that won't help. There is no bounds checking and so buffer overflows always can and unfortunately will happen....

来自 C 的字符串有问题恕我直言无法解决。你最好在通话前设置一些限制,但即使这样也无济于事。没有边界检查,因此缓冲区溢出总是可能发生,但不幸的是会发生......

回答by supercat

Beyond the security issues involved with calling strlenmore than once, one should generally not use string methods on strings whose length is precisely known [for most string functions, there's only a really narrow case where they should be used--on strings for which a maximum length can be guaranteed, but the precise length isn't known]. Once the length of the input string is known and the length of the output buffer is known, one should figure out how big a region should be copied and then use memcpy()to actually perform the copy in question. Although it's possible that strcpymight outperform memcpy()when copying a string of only 1-3 bytes or so, on many platforms memcpy()is likely to be more than twice as fast when dealing with larger strings.

除了涉及strlen多次调用的安全问题之外,通常不应该在长度精确已知的字符串上使用字符串方法[对于大多数字符串函数,只有一种非常狭窄的情况应该使用它们——对最大可以保证长度,但不知道确切的长度]。一旦知道输入字符串的长度和输出缓冲区的长度,就应该弄清楚应该复制多大的区域,然后用它memcpy()来实际执行有问题的复制。尽管在复制仅 1-3 个字节左右的字符串时可能strcpy会表现得更好memcpy(),但在许多平台memcpy()上处理较大的字符串时可能会快两倍以上。

Although there are some situations where security would come at the cost of performance, this is a situation where the secure approach is alsothe faster one. In some cases, it may be reasonable to write code which is not secure against weirdly-behaving inputs, if code supplying the inputs can ensure they will be well-behaved, and if guarding against ill-behaved inputs would impede performance. Ensuring that string lengths are only checked once improves bothperformance and security, though one extra thing can be done to help guard security even when tracking string length manually: for every string which is expected to have a trailing null, write the trailing null explicitly rather than expecting the source string to have it. Thus, if one were writing an strdupequivalent:

尽管在某些情况下安全性会以牺牲性能为代价,但在这种情况下,安全方法也是更快的方法。在某些情况下,如果提供输入的代码可以确保它们的行为良好,并且如果防止不良行为的输入会妨碍性能,那么编写对异常行为输入不安全的代码可能是合理的。确保字符串长度只检查一次提高性能和安全性,但一个额外的东西可以手动跟踪字符串长度,即使做帮助守卫安全:每预计将尾随的空字符串,写结尾的空明确而而不是期望源字符串拥有它。因此,如果有人在写一个strdup等价的:

##代码##

Note that the last statement could generally be omitted if the memcpy had processed len+1bytes, but it another thread were to modify the source string the result could be a non-NUL-terminated destination string.

请注意,如果 memcpy 已处理len+1字节,通常可以省略最后一条语句,但另一个线程要修改源字符串,结果可能是非 NUL 终止的目标字符串。