メモリ保護
メモリ保護(めもりほご)とは、コンピュータのメモリアクセス制御の方式であり、多くのオペレーティングシステム (OS) の一部となっている。主な目的は、プロセスが自身に割り当てられていないメモリにアクセスすることを防ぐことである。例えばプログラムにバグがあって暴走しても、他のプロセスやオペレーティングシステムのメモリの領域を破壊することが無いように保護することである。通常、ハードウェア(メモリ管理ユニット)とOSが協調して、多重仮想記憶などを用いて保護する。コンピュータセキュリティのためのメモリ保護としては、アドレス空間配置のランダム化や実行保護といった追加技法が含まれる。
メモリ保護を実現する方法はいくつかある。特定の実装における保護レベルは、最小権限の原則をどれだけ守っているかで判断できる。
セグメント方式は、コンピュータのメモリをセグメントと呼ばれる領域に分割するものである。
セグメント方式では、セグメントと呼ばれるメモリのかたまりをいくつか割り当てられてプロセスが動作し、それ以外のメモリにはアクセスできない。セグメントはハードウェアのレジスタによって定義され、アクセス可能なメモリアドレスの範囲を限定する。データを許されている範囲外に読もうとしたときや書き込もうとしたときに例外が発生する。(これはx86のリアルモードのセグメント機能と混同されるべきではない。)
x86アーキテクチャには複数セグメント機能があり、保護されたメモリの使用に役立つ。x86 CPUアーキテクチャにおいてはメモリ内のセグメント群を参照するのにグローバル・ディスクリプタ・テーブルとローカル・ディスクリプタ・テーブルを使用できる。x86プロセッサではメモリセグメントへのポインタをセグメントレジスタ群に格納することもできる。初期のx86プロセッサには4つのセグメントレジスタ、CS(コードセグメント)、SS(スタックセグメント)、DS(データセグメント)、ES(エクストラセグメント)があった。その後2つのセグメントレジスタ FS と GS が追加された。
ページング方式は、メモリ保護を実現する方法としてよく使われる。ページング方式では、メモリはページと呼ばれる同じ大きさの小さな断片に分割される。仮想記憶機構を使い、ページは任意の物理メモリに対応付けられるか、保護状態であることがフラグで示される。ページング方式では連続した仮想記憶空間をページ単位にばらばらな物理メモリ空間に対応付けることで仮想記憶を実現する。
x86アーキテクチャなどほとんどのコンピュータ・アーキテクチャはページングを採用しており、メモリ保護に活用できる。
各プロセスはページテーブルを持っていて、アクセス可能な仮想アドレスに物理メモリをマップする。ページテーブルはプロセスからは見えない。ページテーブルを使うことによって、新たなメモリをプロセスに割り当てる必要が生じたときには、適当な物理メモリをどこからでも持ってきて新しいページとして割り当てることができる。
ページテーブルは仮想アドレスをインデックスとした配列の形式が一般的である。各エントリの形式はプラットフォームのメモリ管理ユニット (MMU) によって決められており、対応する物理メモリのアドレスの他にアクセス権を設定する。アクセス権としては、アクセス可能な特権レベルとアクセスの種別(読み出し、書き込み、実行など)の組み合わせで表される。これによりMMUは当該ページへのアクセスが妥当であるかを判断する。不正なアクセスと判断された場合、例外が発生する。
そのような設計により、アプリケーションは明示的に割り当てられていないページにアクセスできず、物理ページがマッピングされていないアドレスにアクセスしようとするとページフォールトが発生する。割り当てられていない物理ページや他のアプリケーションに割り当てられた物理ページは、そのアプリケーションから見ればアドレスが指定できず、参照もできない。
なお、ページフォールトは必ずしも不正とは限らない。ページフォールトはメモリ保護に使われるだけでなく、デマンドページングの実現手段としてOSが利用する。例えばディスクにスワップアウトされたページにアクセスしようとしてページフォールトが発生した場合、OSがそれを捉えてページを再ロードし、ページフォールトが発生したところからアプリケーションを実行再開させる。仮想記憶では、このようにある時点で使われていないページをディスクに移し、アプリケーションに気付かれないように元にもどすことで、実質的なメモリ容量を増大させている。
一部のシステムではページフォールト機構がW^Xなどの実行保護にも使われている。
IA-32アーキテクチャのように、ページング方式とセグメント方式を同時に使用している場合、セグメントは物理メモリに直接マップせず、リニアアドレス空間にマップされ、リニアアドレス空間から物理アドレス空間へのマッピングをページング方式で行う。もちろん、ページングを使わずにセグメント方式だけを使うことも可能である。両方を使用した場合、どちらにもアクセス権の設定機能があるため、注意が必要となる。
保護キー機構では、メモリはある一定のサイズのかたまりに分割される(例えば、4Kバイト)。そして、それぞれに保護キー (protection key) と呼ばれる数値が対応付けられる。また、プロセスもひとつの保護キーを割り当てられている。メモリにアクセスする際、ハードウェアは現在のプロセスの保護キーとアクセスしようとしているメモリの保護キーが合っているかをチェックする。もし合っていない場合、例外が発生する。この機構はSystem/360アーキテクチャで使用されている。これは最近のメインフレーム System z でも利用可能で、そのOSやサブシステムがよく使用している。
今日のメインフレームは本質的に、PCやミッドレンジのサーバ(Windows、Linux、UNIXなど)に見られる欠陥である特権エスカレーションに耐性がある。これはメインフレームにおけるメモリ保護機構が、ハードウェアで実装された複数のCPU保護リングや暗号機構で支えられた保護キーなどを使っているためである。そういった機構により、ユーザープロセスが直接ハードウェアにアクセスすることが防がれ、カーネルレベルのサービスもほとんど不要となっている。例えばアプリケーションの脆弱性を利用してシェルコードを実行させようとしても、より高い特権レベルのプロセス(ドライバやカーネル)に影響を及ぼすことはできないし、保護キーの異なる他のプロセスにも影響を及ぼせない。CPU保護リングはPCやミッドレンジサーバのOSでは限定的にしか使われておらず(例えば、x86ではカーネルがリング0、ユーザーがリング3だけを使用することが多い)、結果としてオールオアナッシング的な設計になっている。すなわち、ハードウェアにアクセスするにはカーネルが必須であり、その中で何か悪さをしようとすると悪意あるコードでシステム全体を乗っ取ることになる。
上述のSystem/360の保護キーは物理アドレスと対応している。インテルのItaniumやヒューレット・パッカードのPA-RISCなどでは、それとは異なる保護キー機構が使われており、仮想アドレスと保護キーが対応付けられていて、プロセスに複数の保護キーを割り当て可能である。
ItaniumやPA-RISCのアーキテクチャでは、論理アドレスから物理アドレスへの変換(TLBエントリ)にキー (Itanium) またはプロテクションID (PA-RISC) が付属している。ある時点で動作中のプロセスには保護キーレジスタ群がある(Itaniumでは16本、PA-RISCでは4本)。物理アドレスへの変換で選ばれたTLBエントリにあるキーはそれらレジスタ群にある保護キーと比較される。他のチェックと同時にキーが一致しないとアクセスは許可されない。キーがどのレジスタとも一致しない場合、例外が発生する。その例外処理では、必要に応じて実行中プロセスが他に保護キーを持っていないかチェックする。すなわち、保護キーレジスタ群はソフトウェアが管理するキャッシュのように扱われる。
PA-RISCの保護キーは15ビットから18ビットで、Itaniumでは最小でも18ビットである。保護キーは保護領域 (protection domain) に対応しており、一般的にはライブラリやモジュールなどに対応する。
ケイパビリティベースドアドレッシングは現代の商用コンピュータでは使われていないメモリ保護技法である。この技法では、ポインタの代わりに「ケイパビリティ」と呼ばれる保護されたオブジェクトを使用する。ケイパビリティは特権命令を使わないと生成できず、カーネルや何らかの信頼されたプロセスが担当する。それによってカーネルはプロセス群がメモリ上のどのオブジェクトにアクセスできるかを効率的に制御でき、プロセス毎にアドレス空間を分離したりコンテキストスイッチしたりする必要がなくなる。この技法は商用ハードウェアでは主流になっていないが、研究用システムとしてはKeyKOSがあり、概念としてはSmalltalkやJavaなどの仮想機械の基盤となっている。
メモリ保護・分離の形態はOSによって異なる。パーソナルコンピュータでは、1987年にリリースされたOS/2まで、真のメモリ分離は使用されなかった。それ以前のOSでは、メモリ保護が欠如していることをプロセス間通信で利用することさえあり、ポインタをプロセス間でやりとりすることもあった。Windows 9x系ではプロセスがシステムメモリにアクセス可能である。
メモリ保護を実装したOSの例を以下に挙げる。
OS-9 - オプションで提供
Plan 9 from Bell Labs と Inferno - ベル研究所がUnixの後継として開発したOS
Unix系システムでは、メモリ保護制御のシステムコールとして mprotect() がある。