20

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

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

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

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 char 打印到缓冲区中并导致缓冲区溢出。

这是正确的评价吗?

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

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




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

4

11 回答 11

14

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

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

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

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

于 2010-07-20T14:22:31.997 回答
6

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

请注意,根据我的经验,如果在调试器之外出现故障,但可以在调试器中单步执行,那么在 100% 的情况下,您已经超出了本地缓冲区。调试器将更多的东西压入堆栈,因此重要的东西不太可能被覆盖。

于 2010-07-20T14:21:30.887 回答
3

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

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

并写过去。其他变量会写,如果你真的知道二进制是如何工作的,你也可以添加一些代码来执行。

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

于 2010-07-20T14:24:29.633 回答
1

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

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

于 2010-07-20T14:32:30.293 回答
1

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

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

于 2010-07-20T14:23:13.023 回答
1

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

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

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

于 2010-07-20T14:26:29.133 回答
1

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

购买尝试堆栈溢出

#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 已损坏。

于 2010-07-20T14:38:46.373 回答
0

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

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

于 2010-07-20T14:59:18.730 回答
0

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

于 2010-07-20T14:33:20.007 回答
0

你真正的问题是你在写

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() 一个地址,它不是你的 string 的开头

试试这个代码:

const char szText[] = "This string is 27 char long";
char* buffer = new char[strlen(szText) + 1];
sprintf(buffer, szText);
于 2010-07-20T14:37:08.467 回答
0

正确的说法。由于您将字符串的第二个字符的地址传递给 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() 文章的安全说明。

于 2010-07-20T15:07:42.753 回答