ぶら下がっているポインタ

ぶら下がっているポインタ

コンピュータプログラミングにおけるダングリングポインタワイルドポインタとは、適切な型の有効なオブジェクトを指していないポインタのことです。これらはメモリ安全性違反の特殊なケースです。より一般的には、ダングリング参照ワイルド参照は、有効な参照先に解決されない 参照のことです。

ダングリング ポインタは、オブジェクトの破壊中、つまり、特定のポインタによって指されているオブジェクトが削除または解放されたときに、そのポインタの値が変更されず、ポインタが解放されたメモリのメモリ位置を指したままになるときに発生します。システムが以前に解放されたメモリを再割り当てする場合があり、その後プログラムが(現在の) ダングリング ポインタを逆参照すると、メモリにまったく異なるデータが含まれるようになるため、予期しない動作が発生する可能性があります。プログラムがダングリング ポインタによって参照されているメモリに書き込むと、無関係なデータが静かに破損し、見つけるのが非常に難しい微妙なバグが発生する可能性があります。メモリが別のプロセスに再割り当てされている場合、ダングリング ポインタを逆参照しようとすると、セグメンテーション エラー(UNIX、Linux) または一般保護違反(Windows) が発生する可能性があります。プログラムが、カーネルのメモリ アロケータによって使用されるブックキーピング データを上書きできるだけの権限を持っている場合、破損によってシステムが不安定になる可能性があります。ガベージコレクションを備えたオブジェクト指向言語では、到達不能なオブジェクト(つまり、入力ポインタを持たないオブジェクト)のみを破棄することで、ダングリング参照を防止します。これは、トレースまたは参照カウントによって保証されます。ただし、ファイナライザはオブジェクトへの新たな参照を作成する場合があり、ダングリング参照を防止するにはオブジェクトの復活が必要になります。

ワイルドポインタ(未初期化ポインタとも呼ばれる)は、ポインタが既知の状態に初期化される前に使用される場合に発生します。これは一部のプログラミング言語で発生する可能性があります。ワイルドポインタはダングリングポインタと同様の不安定な動作を示しますが、多くのコンパイラは宣言された変数が初期化される前にアクセスするとコンパイル時に警告を発するため、検出されない可能性は低くなります。[ 1 ]

ダングリングポインタの原因

多くの言語(例えばC言語)では、オブジェクトをメモリから明示的に削除したり、戻り時にスタックフレームを破棄したりしても、関連付けられたポインタは変更されません。その位置が他の目的に使用されている場合でも、ポインタはメモリ内の同じ位置を指し続けます。

簡単な例を以下に示します。

{ char * dp = NULL ; // ... { char c ; dp = & c ; } // c はスコープ外になります// dp はダングリングポインタになります}

オペレーティングシステムが実行時にヌルポインタへの参照を検出できる場合、上記の問題の解決策としては、内部ブロックを終了する直前にdpに0(ヌル)を代入することが挙げられます。別の解決策としては、dpが更なる初期化なしに再利用されないことを何らかの方法で保証することが挙げられます。

malloc()ダングリングポインタのよくある原因として、ライブラリ呼び出しと関数呼び出しの組み合わせが挙げられますfree()。ポインタは、それが指しているメモリブロックが解放されると、ダングリング状態になります。前の例と同様に、これを回避する方法の一つは、参照を解放した後、ポインタを必ず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 に初期化され、 その後は最後の呼び出し からの値が保持されます。 * コメントされていない場合、この機能の使用は悪いスタイル とみなされる可能性があります*/ }

ダングリングポインタに関連するセキュリティホール

バッファオーバーフローバグと同様に、ダングリングポインタ/ワイルドポインタバグもセキュリティホールとなることがよくあります。例えば、仮想関数呼び出しにポインタが使用される場合、仮想テーブルポインタが上書きされることにより、別のアドレス(エクスプロイトコードを指している可能性もある)が呼び出される可能性があります。また、ポインタがメモリへの書き込みに使用される場合、他のデータ構造が破損する可能性があります。ポインタがダングリング状態になった後にメモリが読み取られるだけでも、情報漏洩(そこに割り当てられた次の構造体に興味のあるデータが格納された場合)や権限昇格(無効になったメモリがセキュリティチェックに使用された場合)につながる可能性があります。ダングリングポインタが解放された後、新しいメモリチャンクを割り当てずに使用される場合、これは「解放後使用(use-after-free)」脆弱性として知られています。[ 4 ]例えば、CVE - 2014-1776は、Microsoft Internet Explorer 6から11に存在する解放後使用(use-after-free)の脆弱性であり[ 5 ]、高度な持続的脅威によるゼロデイ攻撃に利用されました。[ 6 ]

ダングリングポインタエラーの回避

C言語では、最もシンプルな手法は、free()ポインタのリセットを保証する関数(または類似の関数)の代替バージョンを実装することです。ただし、この手法では、ポインタのコピーを含む可能性のある他のポインタ変数はクリアされません。

#include <assert.h> #include <stdlib.h>// free() の安全バージョンstatic void safeFree ( void ** pp ) { // デバッグモードでは、pp が NULL の場合は中止しますassert ( pp ); // free(NULL) は正常に動作するため、デバッグモードではアサート以外のチェックは必要ありません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++の標準的なメモリ割り当て関数をガベージコレクタに置き換える保守的なガベージコレクタです。このアプローチでは、解放を無効化し、ガベージコレクションによってオブジェクトを回収することで、ダングリングポインタエラーを完全に排除します。

別のアプローチとしては、 CHERIのようなシステムを使用することです。CHERIは、ポインタに追加のメタデータを保存し、ポインタに有効期間情報を含めることで不正なアクセスを防止します。CHERIは通常、これらの追加チェックを実行するためにCPUのサポートを必要とします。

Javaのような言語では、明示的にメモリを解放するメカニズムがないため、ダングリングポインタは発生しません。ガベージコレクタがメモリを解放することはありますが、それはオブジェクトがどの参照からもアクセスできなくなった場合に限られます。

Rust言語では、型システムが拡張され、変数の有効期間やリソースの取得は初期化時にも含まれるようになりました。これらの機能を無効にしない限り、ダングリングポインタはコンパイル時に検出され、プログラミングエラーとして報告されます。

ぶら下がっているポインタの検出

ダングリングポインタエラーを発見するための一般的なプログラミング手法の一つは、ポインタが指す記憶域が解放された後に、ポインタをヌルポインタまたは無効なアドレスに設定することです。ヌルポインタが参照されると(ほとんどの言語では)、プログラムは直ちに終了します。データ破損や予期しない動作の可能性はありません。これにより、根本的なプログラミングミスの発見と解決が容易になります。ただし、この手法はポインタが複数存在する場合には役に立ちません。

一部のデバッガは、解放されたデータを自動的に上書きして破棄します。通常は特定のパターン、例えば0xDEADBEEF(MicrosoftのVisual C/C++デバッガは、解放されたデータに応じて0xCC、 、0xCD、を使用します[ 7 ])。これにより、データが無用になり、また非常に目立つようになります(このパターンは、プログラマにメモリが既に解放されていることを示すのに役立ちます)。 0xDD

PolyspaceTotalViewValgrind、Mudflap、[ 8 ] AddressSanitizerなどのツールや、 LLVMベースのツール[ 9 ]もダングリングポインタの使用を検出するために使用できます。

その他のツール ( SoftBoundInsure++CheckPointer ) は、ソース コードをインストルメント化して、ポインターの正当な値 (「メタデータ」) を収集および追跡し、各ポインター アクセスの有効性をメタデータと照合してチェックします。

少数のクラスが疑われる場合の別の戦略は、そのクラスのすべてのメンバー関数を一時的に仮想にすることです。クラス インスタンスが破棄/解放された後、仮想メソッド テーブルへのポインタが に設定されNULL、メンバー関数へのすべての呼び出しによってプログラムがクラッシュし、デバッガーに問題のあるコードが表示されます。

ARM64メモリタグ付け拡張機能(MTE)は、Linuxシステムではデフォルトで無効になっていますが、Android 16では有効にすることができます。これは解放後使用とバッファオーバーフローを検出するとセグメンテーション違反を引き起こします。[ 10 ] [ 11 ]

参照

参考文献

  1. ^ 「警告オプション - GNU コンパイラ コレクション (GCC) の使用」
  2. ^ Gianna Cioni、Antoni Kreczmar、「ぶら下がり参照のないプログラムによる解放」 Information Processing Letters、v. 18、 1984年、pp. 179–185
  3. ^ a b C. N. Fisher、RJ Leblanc、「Pascal における実行時診断の実装」IEEE Transactions on Software Engineering、6(4):313–319、1980。
  4. ^ Dalci, Eric; 匿名著者; CWEコンテンツチーム (2012年5月11日). 「CWE-416: 解放後使用」 .共通脆弱性一覧. Mitre Corporation . 2014年4月28日閲覧.{{cite web}}:|author2=一般的な名前があります(ヘルプ
  5. ^ "CVE-2014-1776" . Common Vulnerabilities and Exposures (CVE) . 2014年1月29日. 2017年4月30日時点のオリジナルよりアーカイブ。 2017年5月16日閲覧
  6. ^ Chen, Xiaobo; Caselden, Dan; Scott, Mike (2014年4月26日). 「標的型攻撃でInternet Explorerバージョン9~11を狙った新たなゼロデイ脆弱性が確認」 . FireEyeブログ. FireEye . 2014年4月28日閲覧.
  7. ^ Visual C++ 6.0 のメモリフィルパターン
  8. ^マッドフラップポインタのデバッグ
  9. ^ Dhurjati, D. および Adve, V.実稼働サーバーにおけるすべてのダングリングポインタの使用を効率的に検出する
  10. ^ 「Armメモリタグ付け拡張機能」 . Androidオープンソースプロジェクト. 2025年6月11日閲覧
  11. ^ Goodin, Dan (2025年5月13日). 「Google、最もリスクの高いAndroidユーザー向けに高度な保護モードを導入」 Ars Technica . 2025年6月11日閲覧