缓冲区溢出的后果是什么?

发布于 2024-09-10 07:50:10 字数 1017 浏览 1 评论 0原文

所以在这里我相信我在审查别人的代码时发现了一个小的缓冲区溢出问题。我立即觉得这是不正确的,而且有潜在的危险,但不可否认的是,我无法解释这个“错误”的实际后果(如果有的话)。

我编写了一个测试应用程序来演示该错误,但发现(令我沮丧的是)无论溢出如何,它似乎都能正确运行。我想相信这只是偶然,但需要一些反馈来确定我的想法是否错误,或者这里是否确实存在问题而没有在我的测试应用程序中显现出来。

问题代码(无论如何,我认为是这样):

char* buffer = new char[strlen("This string is 27 char long" + 1)];
sprintf(buffer, "This string is 27 char long");

现在,这对我来说很突出并且我想将其标记为可能的缓冲区溢出的原因是因为第一个 strlen。由于指针算术,+ 1 的“不正确”放置将导致 strlen 返回 26 而不是 27 (取“他的字符串长度为 27 个字符”的长度)。 sprintf,我相信,然后将 27 个字符打印到缓冲区中并导致缓冲区溢出。

这是正确的评估吗?

我编写了一个测试应用程序来向我正在查看的代码的人演示这一点,并发现即使在调试器中,字符串也会正确打印。我还尝试在此代码之前和之后将其他变量放入堆栈和堆上,看看是否会影响相邻的内存区域,但仍然收到正确的输出。我意识到我新分配的堆内存可能不相邻,这可以解释缺乏有用的溢出,但我真的想确认其他人的意见,如果这实际上是一个问题。

由于这是一个非常简单的“问题”,因此如果您也能通过某种参考来支持您的答案,那就太好了。虽然我重视并欢迎您的意见,但我不会接受“是的”作为最终答案。预先感谢您。




更新:许多很好的答案,还有很多额外的见解。不幸的是,我无法全部接受。感谢您分享您的知识并成为我的“第二意见”。我很感激你的帮助。

So here I believe I have a small buffer overflow problem I found when reviewing someone else's code. It immediately struck me as incorrect, and potentially dangerous, but admittedly I couldn't explain the ACTUAL consequences of this "mistake", if any.

I had written up a test app to demonstrate the error, but found (to my dismay) that it seems to run correctly regardless of the overflow. I want to believe that this is just by chance, but wanted some feedback to determine if my thinking were wrong, or if there truly is a problem here that just isn't showing its head in my test app.

The problem code (I think it is, anyway):

char* buffer = new char[strlen("This string is 27 char long" + 1)];
sprintf(buffer, "This string is 27 char long");

Now, the reason this stood out to me and I want to flag it as a possible buffer overflow is because of the first strlen. Due to pointer arithmetic, the 'incorrect' placement of the + 1 will cause the strlen to return 26 instead of 27 (taking the length of "his string is 27 char long"). sprintf, I believe, then prints 27 char into the buffer and has caused a buffer overflow.

Is that a correct assessment?

I wrote a test app to demonstrate this for the person who's code I was looking at, and found that even in the debugger the string will print correctly. I also attempting putting other variables on the stack and heap before and after this code to see if I could affect neighboring areas of memory, but was still receiving correct output. I realize that my newly allocated heap memory might not be adjacent, which would explain the lack of useful overflow, but I just really wanted to confirm with others' opinions if this is in fact an issue.

Since this is a pretty simple "question", it'd be nice if you could support your answer with some sort of reference as well. While I value and welcome your input, I'm not going to accept "yes it is" as the final answer. Thank you kindly in advance.


Update: Many good answers with a lot of additional insight. Unfortunately, I can't accept them all. Thank you for sharing your knowledge and for being my 'second opinion'. I appreciate the help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

↘紸啶 2024-09-17 07:50:10

你的评价是正确的。
[编辑]加上 James Curran 提到的修正。[/编辑]

很可能,您的测试应用程序没有显示问题,因为分配被四舍五入到 4、8 或 16 的下一个倍数(这是常见的分配)粒度)。

这意味着您应该能够使用 31 个字符长的字符串进行演示。

或者,使用“检测”本机内存分析器,该分析器可以将保护字节紧密地放置在此类分配周围。

Your assessment is correct.
[edit] with the addition of the correction mentioned by James Curran.[/edit]

Likely, your test app didn't show the problem because the allocation is rounded up to the next multiple of 4, 8 or 16 (which are common allocation granularities).

This means you should be able to demonstrate with a 31 character long string.

Alternatively, use an "instrumenting" native memory profiler that can place guard bytes closely around such an allocation.

一紙繁鸢 2024-09-17 07:50:10

您的评估是正确的,除了 springf 会在缓冲区中放入 28 个字符,计算末尾的字符串结尾 NUL (这就是为什么您首先需要放错位置的“+1”)

请注意,根据我的经验,如果有些东西在调试器之外失败了,但可以在调试器中单步执行,在 100% 的情况下,您已经超出了本地缓冲区。调试器将更多内容推送到堆栈上,因此重要的内容不太可能被覆盖。

You assessment is correct, except that the springf will put 28 characters in the buffer counting the end-of-string NUL at the end (That's why you needed the misplaced "+1" in the first place)

Note that in my experiences, if something fails outside of a debugger, but works with stepping through in the debugger, in 100% of the time, you've overrun a local buffer. Debuggers push a lot more onto the stack, so it's less likely the something important was overwritten.

凝望流年 2024-09-17 07:50:10

问题是你正在内存中的某个地方写入,而不是在堆栈上。
因此,很难真正看出问题所在。
如果您想查看损坏情况,请尝试在堆栈上分配字符串

char buffer[strlen("This string is 27 char long" + 1)];

并写入它。
其他变量将被写入,如果您确实知道二进制文件是如何工作的,您还可以添加一些要执行的代码。

要利用这样的缓冲区溢出,您需要写入所需的数据,然后找到一种方法“跳转”到要执行的数据。

The problem is that you are writing somewhere in the memory, but not on the stack.
Therefore, it's hard to actually see what's wrong.
If you want to see the damages, try allocating the string on the stack

char buffer[strlen("This string is 27 char long" + 1)];

and the write past it.
Other variables will be written, you can also add some code to be executed if you really know how the binary works.

To exploit a buffer overflow like that, you need to write the data you want, then find a way to "jump" to this data to be executed.

℉絮湮 2024-09-17 07:50:10

是的,你是对的。分配的缓冲区将小 2 个字节,无法容纳该字符串。

由于这是在堆上分配的,因此可能会导致堆损坏。然而,这种情况的可能性取决于在此之前发生的其他内存分配和释放以及所使用的堆管理器。有关详细信息,请参阅堆溢出

Yes, you are correct. The buffer allocated will be 2 bytes too small to hold the string.

Since this is being allocated on the heap, it would be possible for this to result in a heap corruption. However, the liklihood of that depends on the what other allocations and releases of memory have occurred prior to this point and also on heap manager being used. See Heap Overflow for more.

饭团 2024-09-17 07:50:10

许多历史上的malloc实现将簿记数据紧接在分配的块之前和/或之后。您可能会覆盖此类数据,在这种情况下,您不会看到任何错误/崩溃,直到您尝试释放内存(或者可能释放下一个块发生的任何内容)。同样,后续分配的簿记信息也可能会覆盖您的字符串。

我怀疑现代的 malloc 实现通过用完整性检查数据填充分配来努力防止堆损坏,所以如果你幸运的话,不会发生任何不好的事情,或者你可能会在稍后收到警告消息分配/自由操作。

Many historic malloc implementations put bookkeeping data immediately before and/or after the allocated block. It's possible that you're overwriting such data, in which case you would not see any error/crash until you try to free the memory (or perhaps free whatever the next block happens to be). Likewise, it's possible that the bookkeeping information for a subsequent allocation will later overwrite your string.

I suspect modern malloc implementations make some effort to protect against heap corruption by padding allocations with integrity-check data, so if you're lucky, nothing bad will happen or you might get a warning message during a later allocation/free operation.

心奴独伤 2024-09-17 07:50:10

您是正确的,本例中的指针算术会产生传递给 new 的不正确(较短)的长度。无法发生此崩溃的最可能原因是内存分配实际提供了多少缓冲区空间存在一些不确定性。

允许库提供比请求更大的缓冲区。此外,缓冲区后面的任何内容也可能以受机器字对齐规则约束的分配标头为前缀。这意味着在下一个分配标头之前最多可以有三个填充字节(取决于平台)。

即使您覆盖了下一个分配标头(用于管理分配的内存块),在下一个块的所有者尝试将其返回到堆之前,它也不会表现为问题。

You are correct that pointer arithmetic in this example would produce an incorrect (shorter) length passed to new. The most probable reason why you are not able to make this crash is because there is some uncertainty as to how much buffer space is actually provided by the memory allocation.

The library is allowed to provide a larger buffer than was requested. Furthermore, it is also possible that whatever follows your buffer is prefixed by an allocation header that is subject to machine word alignment rules. This means there could be up to three padding bytes (depending on platform) before the very next allocation header.

Even if you overwrote the next allocation header (which is used to manage the allocated memory blocks) it would not manifest itself as a problem until the owner of that next block attempted to return it to the heap.

哆兒滾 2024-09-17 07:50:10

我尝试使用堆分配,在这种情况下变量在内存中不是连续的。这就是为什么在这种情况下很难发生缓冲区溢出。

购买并尝试使用堆栈溢出,

#include "stdio.h"
#include "string.h"

int main()
{
     unsigned int  y      = (0xFFFFFFFF);
     char buffer[strlen("This string is 27 char long" + 1)];
      unsigned int  x      = (0xFFFFFFFF);
      sprintf(buffer, "This string is 27 char long");

      printf("X (%#x) is %#x, Y (%#x) is %#x, buffer '%s' (%#x) \n", &x, x,&y, y, buffer, buffer);
      return 0;
  }

您将看到 Y 已损坏。

I tried it with heap allocations, variables are not continuous in memory in this case. That is why it is hard to make buffer overflow in this case.

Buy try it with stack overflow

#include "stdio.h"
#include "string.h"

int main()
{
     unsigned int  y      = (0xFFFFFFFF);
     char buffer[strlen("This string is 27 char long" + 1)];
      unsigned int  x      = (0xFFFFFFFF);
      sprintf(buffer, "This string is 27 char long");

      printf("X (%#x) is %#x, Y (%#x) is %#x, buffer '%s' (%#x) \n", &x, x,&y, y, buffer, buffer);
      return 0;
  }

You will see that Y is corrupted.

独守阴晴ぅ圆缺 2024-09-17 07:50:10

正如其他人所说,您认为这不好是完全正确的,而您没有看到这一点的原因是填充。尝试valgrind,这应该能明确地找到该错误。

As stated by others, you are completely correct in assuming that this is no good, and the reason you don't see this is padding. Try valgrind on this, this should definitively find that error.

抱着落日 2024-09-17 07:50:10

你真正的问题是你正在写

char* buffer = new char[strlen("This string is 27 char long" + 1)];

而不是

char* buffer = new char[strlen("This string is 27 char long") + 1];

意味着在第一个你给 strlen() 一个不是字符串开头的地址

试试这个代码:

const char szText[] = "This string is 27 char long";
char* buffer = new char[strlen(szText) + 1];
sprintf(buffer, szText);

Your real problem is that you're writing

char* buffer = new char[strlen("This string is 27 char long" + 1)];

instead of

char* buffer = new char[strlen("This string is 27 char long") + 1];

Meaning that on the first one you're giving strlen() an address which isn't the beginning of your string.

Try this code:

const char szText[] = "This string is 27 char long";
char* buffer = new char[strlen(szText) + 1];
sprintf(buffer, szText);
最终幸福 2024-09-17 07:50:10

字符串在调试器中打印良好的原因是,作为 sprintf 的一部分,尾随 NULL 字符被写入内存(在本例中超出您分配的缓冲区),并且在读取字符串时出现 NULL 字符按预期终止字符串。

问题是包含 NULL 字符的字节尚未作为原始 new 的一部分进行分配,因此稍后可用于不同的分配。在这种情况下,当您随后读取该字符串时,您可能会得到附加了垃圾的原始字符串。

The reason the string is printing fine in the debugger is that as part of the sprintf, the trailing NULL character is being written to memory (in this case beyond the buffer you allocated) and when it comes to reading the string the NULL character is present to terminate the string as expected.

The problem is that the byte containing the NULL character hasn't been allocated as part of the original new and so could be used for a different allocation later. In this case, when you come to read the string afterwards you will likely get your original string with garbage appended.

遗弃M 2024-09-17 07:50:10

正确的说法。由于您将字符串的第二个字符的地址传递给 strlen(),因此您得到的长度会少一个字符。除此之外,主要问题是 sprintf(),这是它不安全的原因之一。

即使这样也能编译并执行(也可能崩溃)。

    char* x = new char;
    sprintf(x, "This is way longer than one character");
    printf("%s", x);

为了避免这个危险问题,您应该使用此函数的安全版本,例如 GCC 下的 snprintf() 或 asprintf() 或 MSVC 下的 sprintf_s()。

作为参考,请查看 GNU这方面的 C 库文档以及 MSDN 的 sprintf() 文章的安全说明。

Correct statement. Since you are passing address of the second character of the string to strlen(), you are getting the length one character less as a result. Aside from that, the main problem is with sprintf(), that's one of the reasons that it's not safe.

Even this compiles and executes (may also crash).

    char* x = new char;
    sprintf(x, "This is way longer than one character");
    printf("%s", x);

In order to avoid this dangerous issue, you should use safe versions of this function like snprintf() or asprintf() under GCC or sprintf_s() under MSVC.

As references, please have a look at The GNU C Library documentation in this regard and also security note of MSDN's sprintf() article.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文