ある領域を読み取り専用にしたいとき、プログラム上の「この変数だけ」という境界が、そのままOSの保護の境界になるとは限りません。Linuxのmprotectは、呼び出したプロセスのメモリ領域について、読み取り・書き込み・実行などのアクセス保護を変更するシステムコールです。
鍵になるのは、保護を変える単位がメモリページであることです。バイト数で範囲を渡しても、その範囲と重なるページが対象になります。ここではページサイズを4096バイトと仮定した独自例で、指定した範囲と実際に保護される範囲を分け、隣のデータへ影響を及ぼさない設計を考えます。4096は説明用の仮定で、全環境の固定値ではありません。
簡単にまとめると?
- mprotectの開始アドレスはページ境界にそろえます。指定範囲に少しでも重なるページ全体が保護変更の対象です。
- ページを読み取り専用にすることと、そのページ内のデータを消すことは別です。同じページにある別のデータも、同じ保護の影響を受けます。
- 戻り値で設定の成否を確認し、アドレス・範囲・現在の対応付けを照合します。読み取り、書き込み、実行の関係には環境差があり、フラグの名前だけで実環境の全てを決めつけません。
バイトで指定し、ページへ広がる
mprotectへ渡す主な情報は、開始アドレスaddr、長さsize、保護を表すprotです。開始アドレスはページ境界にそろえる必要があります。長さを指定すると、addrからaddr+size−1までの範囲を含むページが対象になります。
独自の計算例として、ページサイズを4096バイト、ページ境界にある基準アドレスをBとします。Bから始まる第1ページの相対範囲は0〜4095、第2ページは4096〜8191、第3ページは8192〜12287です。B+4096から1バイトだけを指定したとき、対象はその1バイトだけではなく第2ページ全体になります。
同じ開始位置から4096バイトを指定しても、対象は第2ページだけです。しかし4097バイトなら、最後の1バイトが第3ページへ入ります。そのため第2・第3ページの二つが対象になります。「長さが1増えただけ」という差が、保護対象では1ページ分の差になるわけです。

境界を丸める前に、巻き込むものを調べる
では、B+5000にある100バイトのデータを読み取り専用にしたい場合はどうでしょうか。そのアドレスをそのまま開始位置にすると、ページ境界にそろっていません。ページサイズ4096の仮定では、B+5000は第2ページの途中です。
対象を含むページへ広げるなら、開始はB+4096、終わりはB+8192の直前になります。元の100バイト以外に、同じページにある残りの領域も保護されます。境界を丸めて呼び出しを成立させることと、目的のデータだけに影響を限定できることは違います。
たとえば、一つのページの前半に公開用の設定、後半に更新中の作業データを置いたとします。設定だけを読み取り専用にするつもりでも、後半への書き込みも影響を受けます。個々のオブジェクトの意味は、ページ単位の保護には反映されません。保護を切り替える予定のあるデータは、配置の段階でページの共有を考える必要があります。
この例の開始位置や長さは、全て相対位置です。実装では実際のページサイズを取得し、対象アドレスと長さの計算があふれないか、広げた範囲が対応付け済みの領域に収まるかも確認します。図の4096をそのままコードの固定値にすることが、ここでの結論ではありません。
保護の変更と、内容の変更を分ける
代表的な指定は、読み取りのPROT_READ、書き込みのPROT_WRITE、実行のPROT_EXECと、アクセスを許さないPROT_NONEです。複数の許可を組み合わせるときはビット単位のORを使います。変更後の保護に反するアクセスを試みると、LinuxはプロセスへSIGSEGVを発生させます。
これはデータを別の内容へ書き換える操作を指定しているわけではありません。たとえば読み取り専用へ切り替えたページの文字列が、それだけでゼロになるという意味ではありません。データの内容、ページへのアクセス許可、アドレス空間への対応付けは、別々に追います。
独自例で第2ページを読み取り専用へ変更できたとすると、読み取りを行う処理と、同じ場所を更新しようとする処理は違う結果になります。設定後も更新する設計なら、どの段階でどの処理がそのページを使うかを先に決める必要があります。保護を変更する呼び出しを一つ足しても、処理同士の順序やデータの共有設計まで自動的に整うわけではありません。
呼び出しが成功したことを確認する
mprotectは成功時に0、失敗時に−1を返し、失敗理由をerrnoで示します。開始アドレスの整列が不適切、範囲に対応付けられていないページがある、要求する保護を許せない、といった問題を区別して調べます。呼び出したという事実だけで、保護が希望どおりに変わったと扱ってはいけません。
切り分けには、先ほどの図のような範囲表が役立ちます。渡したaddrとsizeから末尾を求め、どのページにかかるかを確認します。次に、その範囲が対象のマッピング内か、要求した許可とマッピングの条件が合っているかを見ます。アドレスを印字しただけでは、これらの条件を全て確認したことにはなりません。
また、アクセスフラグ間の関係にはCPUやプロセスの状態による違いがあります。資料では、書き込み許可が読み取り許可を伴うハードウェアや、読み取り指定が実行許可を伴う条件も説明されています。「PROT_WRITEだけなら読み取りは禁止」といった解釈を、全環境へ一般化しないことが大切です。本稿はLinux man-pages 6.19のmprotectを対象とし、保護キーを扱うpkey_mprotectの設計には踏み込みません。
変数の境界とOSの境界をそろえる
mprotectを検討するときには、保護したいデータを囲むだけでなく、そのデータがどのページを共有しているかまで見ます。1バイトの指定でも一つのページへ及び、境界を一つまたげば対象ページも増える。この対応を設計と検証の両方へ持ち込むことが、思わぬ保護違反を理解する助けになります。
図で確認したのは、明示したページサイズと区間に対する計算です。実際の環境では、戻り値、マッピング、保護変更後の処理を別々に確認します。計算上の対象範囲が正しいことと、実機で設定・動作が確認できたことを分けて記録します。
出典
- Linux man-pages 6.19、mprotect(2)(ページ日付2026年2月11日)。DESCRIPTION、RETURN VALUE、ERRORS、VERSIONS。範囲の数値・配置例・図は本稿の独自計算。
