HOWTO · C++
C++ 中的雙重釋放或損壞錯誤
一篇關於有效使用動態記憶體和避免 C++ 中的記憶體洩漏錯誤的緊湊文章。
本教程將討論 C++ 中動態記憶體分配中出現的問題。在使用堆記憶體時,我們會遇到許多在進行堆記憶體管理時非常重要的問題。
首先,我們將討論如何分配記憶體以及 C++ 中提供的不同方法;然後,我們將探討 C++ 中出現 double free or corruption 錯誤的原因和解決方案。
C++ 中的動態記憶體分配和釋放
C++ 允許我們在執行時分配變數或陣列記憶體。這稱為動態記憶體分配。
在其他程式語言中,例如 Java 和 Python,編譯器會自動維護變數記憶體。但是,在 C++ 中並非如此。
在 C++ 中,我們必須在不再需要動態分配的記憶體後手動釋放它。我們可以使用 new 和 delete 函式動態分配和釋放記憶體。
還有另外兩個函式,即 malloc 和 free,它們也包含在 C++ 中,用於動態記憶體分配和釋放。
參考下面的示例,演示 new 和 delete 函式的使用。
#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 運算子用於此目的。
它將記憶體返回給作業系統。這稱為記憶體釋放。
下一個示例將向你展示 malloc 和 free 函式的演示。
#include <cstdlib>
#include <iostream>
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 <cstdlib>
#include <iostream>
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() 函式,這意味著我們正在嘗試釋放已經空閒且不再分配的記憶體。這會產生記憶體洩漏問題,並且是導致程式碼崩潰的根本原因。
還有許多其他情況可能會遇到此類錯誤。但是雙重釋放漏洞有三個常見(並且經常重疊)的原因。
- 錯誤情況和其他異常情況
2、記憶體空間釋放後,使用。 - 不清楚程式的哪個部分負責記憶體釋放
雖然一些雙重釋放漏洞並不比前面的例子複雜多少,但它們大多分佈在數百行程式碼甚至多個檔案中。程式設計師似乎特別容易多次釋放全域性變數。
如何避免 C++ 中的雙重釋放或損壞錯誤
這種型別的漏洞可以通過在指標空閒時將其分配給 NULL 來避免(即,該指標指向的記憶體空閒)。之後,大多數堆管理器都會忽略空閒的空指標。
建議將所有已刪除的指標置空,並在釋放它之前檢查引用是否為 null。在我們的程式碼開始處,我們必須在任何情況下使用該指標之前初始化 null 指標。