
コンピュータプログラミングにおけるダングリングポインタとワイルドポインタとは、適切な型の有効なオブジェクトを指していないポインタのことです。これらはメモリ安全性違反の特殊なケースです。より一般的には、ダングリング参照とワイルド参照とは、有効な宛先に解決されない参照のことです。
ダングリングポインタは、オブジェクト破棄時に発生します。特定のポインタが指すオブジェクトが削除または解放された際に、そのポインタの値が変更されないため、ポインタは解放されたメモリのメモリ位置を指したままになります。システムは以前に解放されたメモリを再割り当てする可能性があり、プログラムが(現在)ダングリングポインタを逆参照すると、メモリにまったく異なるデータが含まれている可能性があるため、予期しない動作が発生する可能性があります。プログラムがダングリングポインタによって参照されるメモリに書き込むと、無関係なデータが静かに破損し、発見が非常に困難な微妙なバグにつながる可能性があります。メモリが別のプロセスに再割り当てされている場合、ダングリングポインタを逆参照しようとすると、セグメンテーション違反(UNIX、Linux)または一般的な保護違反(Windows)が発生する可能性があります。プログラムがカーネルのメモリ割り当てに使用される管理データを上書きするのに十分な権限を持っている場合、破損によってシステムが不安定になる可能性があります。ガベージコレクションを備えたオブジェクト指向言語では、到達不能なオブジェクト(つまり、入力ポインタを持たないオブジェクト)のみを破棄することで、ダングリング参照を防止します。これは、トレースまたは参照カウントによって保証されます。ただし、ファイナライザがオブジェクトへの新しい参照を作成する場合、ダングリング参照を防ぐためにオブジェクトの復元が必要になります。
ワイルドポインタ(未初期化ポインタとも呼ばれる)は、一部のプログラミング言語で可能な、既知の状態への初期化前にポインタが使用される場合に発生します。ワイルドポインタはダングリングポインタと同様の不安定な動作を示しますが、宣言された変数が初期化される前にアクセスされた場合、多くのコンパイラがコンパイル時に警告を出すため、ワイルドポインタは検出されない可能性が低くなります。[ 1 ]
多くのプログラミング言語(例えばC言語)では、メモリからオブジェクトを明示的に削除したり、戻り時にスタックフレームを破棄したりしても、関連付けられたポインタは変更されません。ポインタは、たとえその場所が他の用途に使用されるようになったとしても、依然として同じメモリ上の場所を指しています。
以下に分かりやすい例を示します。
{ char * dp = NULL ; // ... { char c ; dp = & c ; } // c はスコープ外になる// dp はダングリングポインタになる}オペレーティングシステムがヌルポインタへの実行時参照を検出できる場合、上記の問題に対する解決策は、内部ブロックが終了する直前にdpに0(ヌル)を割り当てることです。別の解決策としては、dpが再初期化なしに再度使用されないように何らかの方法で保証することです。
malloc()ダングリングポインタが発生するもう一つのよくある原因は、とライブラリ呼び出しの混在ですfree()。ポインタが指すメモリブロックが解放されると、ポインタはダングリングポインタになります。前の例と同様に、これを回避する方法の1つは、参照を解放した後にポインタをnullにリセットすることです。以下でその方法を示します。
#include <stdlib.h>void func () { char * dp = ( char * ) malloc ( sizeof ( char ) * 10 ); // ... free ( dp ); // dp はダングリングポインタになりますdp = NULL ; // dp はダングリングポインタではなくなります// ... }よくある間違いの一つに、スタックに割り当てられたローカル変数のアドレスを返すというものがあります。呼び出された関数が戻ると、これらの変数の領域は解放され、技術的には「ゴミ値」を持つことになります。
int * func ( void ) { int num = 1234 ; // ... return & num ; }を呼び出した後しばらくの間は、ポインタから読み取ろうとすると正しい値 (1234) が返される可能性がありますfuncが、その後呼び出された関数は に割り当てられたスタック ストレージをnum他の値で上書きする可能性があり、ポインタは正しく機能しなくなります。 へのポインタをnum返す必要がある場合は、numは関数のスコープを超えてスコープを持つ必要があります。 として宣言することができますstatic。
アントニ・クレツマー(1945~1996)は、ダングリング参照現象のない完全なオブジェクト管理システムを作成しました。[ 2 ]同様のアプローチは、フィッシャーとルブランによって「鍵と鍵」という名前で提案されました。[ 3 ]
ワイルドポインタは、最初の使用前に必要な初期化処理を省略することによって生成されます。したがって、厳密に言えば、初期化を強制しないプログラミング言語におけるすべてのポインタは、最初はワイルドポインタとして始まります。
これは、初期化処理を省略するのではなく、初期化処理をスキップしてしまうことが原因であることがほとんどです。ほとんどのコンパイラは、このような場合に警告を発することができます。
int f ( int i ) { char * dp ; // dp はワイルド ポインタですstatic char * scp ; /* scp はワイルド ポインタではありません。 * 静的変数は開始時に 0 に初期化され、 * その後は最後の呼び出し から値を保持します。 * コメントを付けずにこの機能を使用すると、 * 悪いスタイルと見なされる可能性があります */ }バッファオーバーフローのバグと同様に、ダングリング/ワイルドポインタのバグはセキュリティホールになることがよくあります。たとえば、ポインタが仮想関数呼び出しに使用される場合、 vtableポインタが上書きされるため、別のアドレス (おそらくエクスプロイトコードを指している) が呼び出される可能性があります。あるいは、ポインタがメモリへの書き込みに使用される場合、他のデータ構造が破損する可能性があります。ポインタがダングリングになった後にメモリが読み取られるだけでも、情報漏洩 (そこに割り当てられた次の構造に興味深いデータが格納されている場合) や権限昇格(無効になったメモリがセキュリティチェックで使用される場合) につながる可能性があります。ダングリングポインタが解放された後に新しいメモリチャンクを割り当てずに使用されると、これは「解放後使用」の脆弱性として知られています。[ 4 ]たとえば、CVE - 2014-1776は、Microsoft Internet Explorer 6 から 11 の解放後使用の脆弱性です[ 5 ] 。これは、高度な持続的脅威によるゼロデイ攻撃で使用されました。[ 6 ]
C言語では、ポインタのリセットを保証する代替関数free()(または類似の関数)を実装するのが最も簡単な方法です。ただし、この方法では、ポインタのコピーを含む可能性のある他のポインタ変数はクリアされません。
#include <assert.h> #include <stdlib.h>// free() の安全なバージョンstatic void safeFree ( void ** pp ) { // デバッグモードでは、pp が NULL の場合は中止するassert ( pp ); // free(NULL) は正しく動作するため、デバッグモードでは assert 以外のチェックは不要free ( * pp ); // チャンクを解放する。free(NULL) が有効であることに注意* pp = NULL ; // 元のポインタをリセットする}int f ( int i ) { char * p = NULL ; char * p2 ; p = ( char * ) malloc ( 1000 ); // チャンクを取得p2 = p ; // ポインタをコピー// ここでチャンクを使用safeFree (( void ** ) & p ); // 安全な解放。p2 変数には影響しませんsafeFree (( void ** ) & p ); // p が NULL にリセットされるため、この 2 番目の呼び出しは失敗しませんchar c = * p2 ; // p2 はまだダングリング ポインタなので、これは未定義の動作です。return i + c ; }代替バージョンは、呼び出し前に空のポインタの有効性を保証するためにも使用できますmalloc()。
safeFree ( & p ); // チャンクが解放されたかどうかは不明 */ p = ( char * ) malloc ( 1000 ); // 今すぐ割り当てる#defineこれらの使用は、便利なマクロ(一般的なものとしては#define XFREE(ptr) safeFree((void**)&(ptr)))を作成するためのディレクティブによって隠蔽することができ、メタ言語のようなものを作成したり、ツールライブラリに組み込んだりすることができます。いずれの場合も、この手法を使用するプログラマーはfree()、 が使用されるすべての箇所で安全なバージョンを使用する必要があります。そうしないと、再び問題が発生します。また、この解決策は単一のプログラムまたはプロジェクトの範囲に限定され、適切に文書化する必要があります。
より構造化された解決策としては、C++ でダングリング ポインタを回避する一般的な手法としてスマート ポインタを使用する方法があります。スマート ポインタは通常、参照カウントを使用してオブジェクトを解放します。その他の手法としては、墓石メソッドや鍵と錠メソッドなどがあります。[ 3 ]
別の方法として、 Boehmガベージコレクタを使用する方法があります。これは、CおよびC++の標準的なメモリ割り当て関数をガベージコレクタに置き換える、保守的なガベージコレクタです。この方法は、freeを無効にし、ガベージコレクションによってオブジェクトを再利用することで、ダングリングポインタエラーを完全に排除します。
別の方法としては、 CHERIのようなシステムを使用する方法があります。CHERIはポインタに有効期間情報などの追加メタデータを格納し、不正アクセスを防止する可能性があります。CHERIは通常、これらの追加チェックを実行するためにCPUのサポートを必要とします。
Javaのような言語では、明示的にメモリを解放する仕組みがないため、ダングリングポインタは発生しません。ガベージコレクタがメモリを解放することはありますが、それはオブジェクトがどの参照からも到達できなくなった場合に限られます。
Rust言語では、型システムが拡張され、変数のライフタイムやリソースの取得(初期化)も含まれるようになりました。言語の機能を無効にしない限り、ダングリングポインタはコンパイル時に検出され、プログラミングエラーとして報告されます。
ダングリングポインタエラーを検出する一般的なプログラミング手法の一つは、ポインタが指すメモリ領域が解放された後、ポインタをヌルポインタまたは無効なアドレスに設定することです。ヌルポインタが逆参照されると(ほとんどの言語では)、プログラムは即座に終了するため、データ破損や予期せぬ動作が発生する可能性はありません。これにより、根本的なプログラミングミスを容易に発見し、解決することができます。ただし、この手法はポインタのコピーが複数存在する場合には役に立ちません。
デバッガーによっては、解放されたデータを自動的に上書きして破棄するものがあり、通常は特定のパターンを0xDEADBEEF使用します(たとえば、Microsoft の Visual C/C++ デバッガーは、解放されたデータに応じて0xCC、0xCDまたはを使用します[ 7 ])。これにより、データが使用できなくなるため再利用が防止され、また非常に目立つようになります(このパターンは、メモリが既に解放されていることをプログラマに示す役割を果たします)。0xDD
Polyspace、TotalView、Valgrind、Mudflap [ 8 ] AddressSanitizerなどのツール、またはLLVM [ 9 ]に基づくツールも、ダングリングポインタの使用を検出するために使用できます。
その他のツール(SoftBound、Insure++、CheckPointerなど)は、ソースコードに計測器を挿入してポインタの正当な値(「メタデータ」)を収集および追跡し、各ポインタアクセスをメタデータと照合して有効性を確認します。
少数のクラスに疑いがある場合の別の戦略は、それらのクラスのメンバー関数を一時的にすべて仮想関数にすることです。クラスインスタンスが破棄/解放された後、仮想メソッドテーブルへのポインタがに設定されNULL、メンバー関数への呼び出しはすべてプログラムをクラッシュさせ、デバッガに問題のあるコードを表示します。
ARM64メモリ タギング拡張機能 (MTE)は、Linux システムではデフォルトで無効になっていますが、 Android 16では有効にできます。解放後使用とバッファ オーバーフローを検出すると、セグメンテーション違反が発生します。[ 10 ] [ 11 ]
{{cite web}}:|author2=一般的な名前を持っています (ヘルプ)