C/C++自主分配出現double free or corruption問題解決

引言

寫過 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,但是引用的頭文件變成瞭 <cstdlib>。<cstdlib>是 <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 的觸發,平常出現這種報錯的條件並不比上面的情形要復雜多少。比如,釋放同一塊內存的動作在相隔瞭幾百甚至更多行的位置執行,有的還發生在不同源碼文件,這就會讓程序員容易多次釋放。下面嘗試總結一下,來看一下常見的犯錯情形:

  • 釋放前判斷的條件錯誤或者其它不常見的情況
  • 內存被釋放後還在使用
  • 內存釋放的管理責任方混亂

如何避免

其實,細看一下上面總結的幾種常見犯錯情形,我們也可以很好地避免低級錯誤。

有個最佳實踐是,分配的內存地址存儲變量 ptr 在定義聲明時就應該初始化為 NULL,內存被釋放後應立刻將 ptr 置為 NULL,使用這塊內存或者釋放前應該遵循先判斷內存空間是否有效的原則,簡單點可以用 (ptr != NULL)。

另外,負責釋放的管理責任方應該盡量單一,即使橫跨多個源文件或模塊。這裡有個道理就是避免”多龍治水“。

中國在過去一直是個農業大國,有著重農輕商的歷史,各種典故都有著農業的影子。

相傳,幾龍治水、幾牛耕地那是對當年農業收成的預示,不妨翻一下老黃歷看看?“龍”是管雨的神,以五龍治水可獲風調雨順,因東南西北中都有神龍,各施其職。龍少瞭當年就要發大水;龍多瞭當年將要天大旱。原因是管雨的龍神少瞭怕管不過來,就忙忙碌碌四處播雨以至大澇;管雨的龍神多瞭呢,就像“三個和尚無水吃”一樣以至大旱。至於澇到什麼程度還看治水的龍少到什麼程度,龍越少澇得越嚴重。旱的程度亦一樣。

因此就有瞭“龍多不下雨”的諺語。

計算機編程說到底還是程序員的思維體現,人情世故也會反映在代碼的邏輯上。

以上就是C/C++出現double free or corruption問題解決的詳細內容,更多關於C/C++ double free or corruption的資料請關註WalkonNet其它相關文章!

推薦閱讀: