HOWTO · C++

C++ での double free or corruption エラー

動的メモリの効率的な使用と C++ でのメモリリークエラーの回避に関するコンパクトな記事。

このチュートリアルでは、C++ の動的メモリ割り当てで発生する問題について説明します。ヒープメモリを使用する場合、ヒープメモリ管理を行う際に対処することが非常に重要な多くの問題に遭遇します。

最初に、メモリの割り当て方法と C++ で提供されるさまざまな方法について説明します。次に、C++ での double free or corruption エラーの原因と解決策に移ります。

C++ での動的メモリ割り当てと割り当て解除

C++ を使用すると、実行時に変数またはアレイメモリを割り当てることができます。これは、動的メモリ割り当てと呼ばれます。

Java や Python などの他のプログラミング言語では、コンパイラは可変メモリを自動的に維持します。ただし、これは C++ には当てはまりません。

C++ では、動的に割り当てられたメモリが不要になった後、手動で割り当てを解除する必要があります。new および delete 関数を使用して、メモリの動的な割り当てと割り当て解除を行うことができます。

他の 2つの機能、つまり、mallocfree があります。これらは、動的なメモリ割り当てと割り当て解除のために 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++ での double free or corruption エラー

free() が入力と同じメモリアドレスで複数回使用されると、ダブルフリーエラーが発生します。

同じ変数で free() を 2 回呼び出すと、メモリリークが発生する可能性があります。ソフトウェアが同じパラメータで free() を 2 回実行すると、アプリケーションのメモリ管理データ構造が破損し、悪意のあるユーザーが任意のメモリ領域に値を書き込むことができます。

この破損により、プログラムがクラッシュしたり、場合によっては実行フローが変更されたりする可能性があります。攻撃者は、特定のレジスタまたはメモリ領域を上書きすることにより、プログラムを誤解させて選択したコードを実行させ、権限を昇格させたインタラクティブなシェルを作成する可能性があります。

空きバッファのリンクされたリストは、バッファが free() されたときに読み取られ、空きメモリの断片を再編成して結合します(将来、より大きなバッファを割り当てるため)。これらのチャンクは、前後のチャンクへのリンクを含む二重リンクリストに編成されています。

攻撃者は、未使用のバッファのリンクを解除し(free() が呼び出されたときに発生します)、貴重なレジスタを効果的に上書きし、そのバッファからシェルコードを起動することにより、メモリに任意の値を書き込む可能性があります。

以下の例を考えてみましょう。

#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 回使用しました。これは、すでに解放されていて、割り当てられていないメモリを解放しようとしていることを意味します。これはメモリリークの問題を引き起こし、コードをクラッシュさせる根本的な原因です。

このようなエラーが発生する可能性のある状況は他にもたくさんあります。しかし、ダブルフリーの脆弱性には 3つの一般的な(そしてしばしば重複する)理由があります。

  1. エラー状態およびその他の異常な状況
  2. メモリスペースが解放された後、それが使用されます。
  3. プログラムのどのセクションがメモリ解放を担当しているかについての混乱

一部のダブルフリーの脆弱性は前の例よりもそれほど複雑ではありませんが、それらのほとんどは数百行のコードまたは複数のファイルに分散しています。プログラマーは、グローバル変数を複数回解放する傾向があるようです。

C++ での double free or corruption エラーを回避する方法

このタイプの脆弱性は、ポインタが解放されるたびに NULL を割り当てることで回避できます(つまり、このポインタが指すメモリが解放されます)。その後、ほとんどのヒープマネージャは空き null ポインタを無視します。

削除するすべてのポインタを null にして、参照を解放する前に参照が null であるかどうかを確認することをお勧めします。コードの開始時に、null ポインターを初期化してから、そのポインターを使用する必要があります。