C++ 中的双重释放或损坏错误

本教程将讨论 C++ 中动态内存分配中出现的问题。在使用堆内存时,我们会遇到许多在进行堆内存管理时非常重要的问题。

首先,我们将讨论如何分配内存以及 C++ 中提供的不同方法;然后,我们将探讨 C++ 中出现 double free or corruption 错误的原因和解决方案。

C++ 中的动态内存分配和释放

C++ 允许我们在运行时分配变量或数组内存。这称为动态内存分配。

在其他编程语言中,例如 Java 和 Python,编译器会自动维护变量内存。但是,在 C++ 中并非如此。

在 C++ 中,我们必须在不再需要动态分配的内存后手动释放它。我们可以使用 newdelete 函数动态分配和释放内存。

还有另外两个函数,即 mallocfree,它们也包含在 C++ 中,用于动态内存分配和释放。

参考下面的示例,演示 newdelete 函数的使用。

#include <iostream>using namespace std;
int main() {
  int* ptr;
  ptr = new int;
  *ptr = 40;
  cout << *ptr;
  delete ptr;
  return 0;
}

我们使用’new’操作符为一个 int 变量动态分配内存。值得注意的是,我们使用了引用变量 ptr 来动态分配内存。

new 运算符返回内存位置的地址。在数组的情况下,new 运算符返回数组第一个元素的地址。

在我们不再需要使用动态声明的变量后,我们可以释放它所使用的内存。delete 运算符用于此目的。

它将内存返回给操作系统。这称为内存释放。

下一个示例将向你展示 mallocfree 函数的演示。

#include <iostream>#include <cstdlib>using namespace std;
int main() {
int* ptr2 = (int*) malloc(sizeof(int));
*ptr2 = 50;
cout << *ptr2;
free(ptr2);
return 0;
}

在 C++ 中,malloc() 方法分配一个指向未初始化内存块的指针。cstdlib 头文件定义它。

malloc 将需要分配的内存大小作为参数,并返回一个指向已分配内存块的 void 指针。因此可以根据我们的要求进行类型转换。

另一方面,free() 方法释放使用 malloc 函数分配的内存。它将内存返回给操作系统并避免内存泄漏问题。

C++ 中的双重释放或损坏错误

当多次使用 free() 且内存地址作为输入时,会发生双重释放错误。

在同一个变量上调用 free() 两次可能会导致内存泄漏。当软件使用相同的参数运行两次 free() 时,应用程序中的内存管理数据结构会损坏,从而允许恶意用户在任何内存区域中写入值。

在某些情况下,这种损坏可能导致程序崩溃或更改执行流程。攻击者可以通过覆盖特定的寄存器或内存区域来误导程序执行他们选择的代码,从而产生具有提升权限的交互式 shell。

当缓冲区被 free() 重新组织和组合空闲内存块(以在将来分配更大的缓冲区)时,将读取空闲缓冲区的链接列表。这些块被组织在一个双链表中,其中包含指向它们之前和之后的块的链接。

攻击者可能通过断开未使用的缓冲区(在调用 free() 时发生)、有效地覆盖有价值的寄存器并从其缓冲区启动 shellcode 来在内存中写入任意值。

参考下面的一个例子。

#include <iostream>#include <cstdlib>using namespace std;
int main() {
  int* ptr2 = (int*) malloc(sizeof(int));
  *ptr2 = 50;
  cout << *ptr2;
  free(ptr2);
  free(ptr2);
  return 0;
}

输出:

free(): double free detected in cache 2
Aborted

在上面的代码片段中,我们两次使用了 free() 函数,这意味着我们正在尝试释放已经空闲且不再分配的内存。这会产生内存泄漏问题,并且是导致代码崩溃的根本原因。

还有许多其他情况可能会遇到此类错误。但是双重释放漏洞有三个常见(并且经常重叠)的原因。

  1. 错误情况和其他异常情况
    2、内存空间释放后,使用。
  2. 不清楚程序的哪个部分负责内存释放

虽然一些双重释放漏洞并不比前面的例子复杂多少,但它们大多分布在数百行代码甚至多个文件中。程序员似乎特别容易多次释放全局变量。

如何避免 C++ 中的双重释放或损坏错误

这种类型的漏洞可以通过在指针空闲时将其分配给 NULL 来避免(即,该指针指向的内存空闲)。之后,大多数堆管理器都会忽略空闲的空指针。

建议将所有已删除的指针置空,并在释放它之前检查引用是否为 null。在我们的代码开始处,我们必须在任何情况下使用该指针之前初始化 null 指针。