簡単な答え:コンパイラ、プロセッサアーキテクチャ、特定のプロセッサモデル、OSなどの多くの要因によって異なります。
長い答え(x86およびx86-64):最も低いレベルであるCPUに行きましょう。x86およびx86-64では、そのコードは通常、次のような命令または命令シーケンスにコンパイルされます。
movl $10, 0x00000000
これは、「定数整数10を仮想メモリアドレス0に格納する」という意味です。インテル®64およびIA-32アーキテクチャーのソフトウェア開発者マニュアルには、この命令が実行されたときに何が起こるかが詳細に説明されているので、要約します。
CPUはいくつかの異なるモードで動作でき、そのうちのいくつかははるかに古いCPUとの下位互換性のためのものです。最新のオペレーティングシステムは、保護モードと呼ばれるモードでユーザーレベルのコードを実行します。このモードでは、ページングを使用して仮想アドレスを物理アドレスに変換します。
プロセスごとに、OSはアドレスのマッピング方法を指示するページテーブルを保持します。ページテーブルは、CPUが理解できる特定の形式でメモリに保存されます(ユーザーコードで変更できないように保護されます)。発生するすべてのメモリアクセスについて、CPUはページテーブルに従ってそれを変換します。変換が成功すると、物理メモリ位置への対応する読み取り/書き込みが実行されます。
アドレス変換が失敗すると、興味深いことが起こります。すべてのアドレスが有効であるとは限りません。メモリアクセスによって無効なアドレスが生成されると、プロセッサはページフォールト例外を発生させます。これにより、ユーザーモード(x86 / x86-64の現在の特権レベル(CPL)3 )からカーネルモード(別名CPL 0)への移行がトリガーされ、割り込み記述子テーブル(IDT)で定義されているカーネルのコード内の特定の場所に移動します。。
カーネルは制御を取り戻し、例外とプロセスのページテーブルからの情報に基づいて、何が起こったかを把握します。この場合、ユーザーレベルのプロセスが無効なメモリ位置にアクセスしたことを認識し、それに応じて反応します。Windowsでは、構造化例外処理を呼び出して、ユーザーコードが例外を処理できるようにします。POSIXシステムでは、OSがSIGSEGV
プロセスに信号を配信します。
その他の場合、OSはページフォールトを内部で処理し、何も起こらなかったかのように現在の場所からプロセスを再開します。たとえば、ガードページはスタックの一番下に配置され、スタックに大量のメモリを事前に割り当てる代わりに、スタックをオンデマンドで制限まで拡張できるようにします。コピーオンライトメモリを実現するために、同様のメカニズムが使用されます。
最近のOSでは、ページテーブルは通常、アドレス0を無効な仮想アドレスにするように設定されています。ただし、Linuxでは疑似ファイルに0を書き込むことで変更できる場合があります/proc/sys/vm/mmap_min_addr
。その後、仮想アドレス0をマップするために使用できmmap(2)
ます。その場合、nullポインターを逆参照しても、ページフォールトは発生しません。
上記の説明は、元のコードがユーザースペースで実行されているときに何が起こるかについてのすべてです。しかし、これはカーネル内でも発生する可能性があります。カーネルは仮想アドレス0をマップできる(そして確かにユーザーコードよりもはるかに可能性が高い)ので、そのようなメモリアクセスは正常です。しかし、マップされていない場合、何が起こるかはほぼ同じです。CPUはページフォールトエラーを発生させ、カーネルの事前定義されたポイントにトラップし、カーネルは何が起こったかを調べ、それに応じて反応します。カーネルが例外から回復できない場合は、通常、デバッグ情報をコンソールまたはシリアルポートに出力して停止することにより、何らかの方法でパニックになります(カーネルパニック、カーネルoops、WindowsのBSODなど)。
Linuxマシンでroot権限を取得するために、攻撃者がカーネル内からnullポインター逆参照バグを悪用する方法の例については、NULLについての多くの騒ぎ:カーネルのNULL逆参照の悪用も参照してください。