*以下内容为本人的学习笔记,如需求转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/IwSVImp5c…

写过 C/C++ 的都知道,内存答应程序员自主分配,用完了这些资源也得开释出来,这种在系统运转进程中动态请求的内存,称为动态内存。

常言道,借东西好借好还,下次再借也不难,但是有的人有时分还真的忘了还回去。这要是发生在程序运转时,请求的内存没正常开释,没办理好,就防止不了会面对内存报错的问题。

内存都答应你自由操作了,灵活性是真的大,恰恰这也是它的弊端。

今日就来聊聊 C/C++ 的报错 double free or corruption

怎样分配和开释内存?

C 言语供给了两个函数用于分配和开释内存 malloc 和 free,需求引证头文件 <stdlib.h>。<stdlib.h> 是 C 规范库头文件 为 C 言语程序员供给牢靠、高效的函数,以实现动态内存分配、数据类型转化、伪随机数生成、进程操控、查找和排序、数学以及多字节或宽字符函数,还包括一些常用常数,目的是促进组织和渠道间的代码规范化。

#include <stdlib.h>
#include <stdio.h>
int main()
{
    int *ptr = malloc(sizeof(int));
    *ptr = 100;
    printf("%d", *ptr);
    free(ptr);
    return 0;
}

输出:

100

调用 malloc 会分配一块内存空间,并将这块内存空间的首地址返回。调用时,需求传入方针内存空间的巨细,单位依照字节(Byte)算,而返回的地址数据类型是 void*,所以,根据方针空间的具体用途转化即可。

这块内存空间在分配之后还归于未初始化的状况,如果对内存空间的运用比较复杂,建议先用 memset 初始化一下。

内存空间运用完,需求运用 free 开释掉,防止搁置糟蹋,不然就算是内存泄漏了。内存泄露会直到程序进程完毕停止。

在其它的高级言语里,比方 Java、Python 等,出于内存安全的考虑,都不会答应用户自己办理内存,而 C++ 是个例外,这可能来自于 C 言语的传承。

C++ 里相同供给了 malloc 和 free,但是引证的头文件变成了 。 是 <stdlib.h> 增强版,而且一切内容都在命名空间内声明,所以运用前有必要经过命名空间引证。

别的 C++ 还供给了两个额外的操作符用于分配和开释内存,分别是 new 和 delete。

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

输出:

100

关键词 new 后接上一个数据类型,然后分配和数据类型 int 对应巨细的内存空间,并返回忆地址。对应地,new 请求的内存空间被运用完不再需求时,应该运用关键词 delete 开释,delete 直接操作内存空间首地址。

呈现 double free or corruption Error

借来的钱用得能够很爽,是的,常人都这样。不过,每到要还钱的时分就特别不甘愿,要么推三推四,要么直接狡赖,一不留神就忘了是否有还过这事。

比方,张三原本一向在外租房将就着过日子,随着家里人口逐渐增多,就和老婆合计着从银行贷了一笔资金预备买房嘛,贷了款之后,银行借款经理就告诉他,“张先生,你们家以后每月就得由一名代表人来还借款,不需求几个人一起还的,记住了哈!”

好了,这个故事给了我们什么启示呢?便是资金或许资源的借入借出需求有一个办理人,这样能够防止紊乱进而犯错。

相同的,在 C/C++ 的编程里边,经常会呈现一些内存资源办理紊乱而呈现的报错甚至运转时溃散的问题,比方 double free or corruption。

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

履行

100
free(): double free detected in tcache 2
Aborted (core dumped)

程序履行溃散并报错 double free,根本原因是对同一内存地址调用了屡次的 free 或 delete 履行开释,这会导致应用的内存办理数据结构被损坏,甚至会答应歹意用户在内存恣意区域写入数据。这类损坏会导致程序溃散或许程序的部分履行流程被改变。如果攻击者这个时分特意掩盖特定的寄存器或许内存区域来引导履行他们的代码,进而能够发生提高权限的交互式 shell,这样就完全被破防了。

这也算是内存泄漏的一种,系统一旦检测到 double free 也会终止进程持续履行(Aborted)。

内存被开释之后会发生什么?

一块内存被开释之后,闲暇的内存会被放入链表中,用于重新办理和组合不同的闲暇内存碎片,便于将来用于分配更大的内存空间。这个链表归于双向链表,每个闲暇的内存空间都能够往前和往后查找其它内存空间。

那么攻击者能够运用这个进程吗?

答案是肯定的。当 free 被调用时,攻击者能够让原本需求被链表办理的闲暇内存撤销链接,掩盖寄存器值并从缓冲区载入shell代码,终究往内存写入恣意值。

常见的触发景象

上面的示例代码简略演示了 double free 的触发,平常呈现这种报错的条件并不比上面的景象要复杂多少。比方,开释同一块内存的动作在相隔了几百甚至更多行的位置履行,有的还发生在不同源码文件,这就会让程序员容易屡次开释。下面尝试总结一下,来看一下常见的犯错景象:

  1. 开释前判别的条件过错或许其它不常见的状况
  2. 内存被开释后还在运用
  3. 内存开释的办理责任方紊乱

怎么防止

其实,细看一下上面总结的几种常见犯错景象,我们也能够很好地防止初级过错。

有个最佳实践是,分配的内存地址存储变量 ptr 在定义声明时就应该初始化为 NULL,内存被开释后应马上将 ptr 置为 NULL,运用这块内存或许开释前应该遵从先判别内存空间是否有用的原则,简略点能够用 (ptr != NULL)。

别的,负责开释的办理责任方应该尽量单一,即便横跨多个源文件或模块。这里有个道理便是防止”多龙治水“。

C/C++ 恨透了 double free or corruption

我国在曩昔一向是个农业大国,有侧重农轻商的前史,各种典故都有着农业的影子。

相传,几龙治水、几牛犁地那是对当年农业收成的预示,不妨翻一下老黄历看看? “龙”是管雨的神,以五龙治水可获风调雨顺,因东南西北中都有神龙,各施其职。龙少了当年就要发大水;龙多了当年即将天大旱。原因是管雨的龙神少了怕管不过来,就忙忙碌碌四处播雨以致大涝;管雨的龙神多了呢,就像“三个和尚无水吃”相同以致大旱。至于涝到什么程度还看治水的龙少到什么程度,龙越少涝得越严重。旱的程度亦相同。

因而就有了“龙多不下雨”的谚语。

计算机编程说到底还是程序员的思想表现,人情世故也会反映在代码的逻辑上。