Linux Security Module(LSM)とアクセス制御フック
SELinuxやAppArmorはどこでアクセスを止めているのか。カーネル内に張られたLSMフックという共通の差し込み点から、型強制とパス基準の設計思想の違いまで原理で解き明かします。
- 1.LSMはカーネルの要所に置かれた共通フックで、DAC(所有者ベースの権限)を通った操作をさらに任意アクセス制御(MAC)で許可/拒否する第二の関門。
- 2.SELinuxは主体・客体に付けたラベル(型)で許可関係を定義する型強制、AppArmorは実行パスごとにプロファイルを当てるパス基準で、思想と運用が大きく異なる。
- 3.かつては排他だったLSMは現在スタック可能になり、capability等の基盤モジュールと主モジュールを順に呼ぶ。1票でも拒否なら全体が拒否される。
なぜDACの上にもう一段の関門が要るのか
伝統的な UNIX の権限は DAC(任意アクセス制御 / Discretionary Access Control) です。ファイルの所有者・グループ・その他に対する rwx ビットがその代表で、「資源の所有者が裁量で権限を決められる」のが特徴です。だがこの裁量性こそが弱点になります。root(あるいは適切な capability を持つプロセス)は DAC をほぼ素通りでき、ユーザーが誤って自分のファイルを全公開すれば、それを止める仕組みは DAC 内にありません。
そこで必要になるのが MAC(強制アクセス制御 / Mandatory Access Control) です。MAC では許可関係をシステム全体のポリシーとして一元的に定め、個々のプロセスやユーザーはそれを覆せません。root であってもポリシーに反する操作は拒否される——これが DAC との決定的な違いです。Linux で MAC を実装する共通基盤が LSM(Linux Security Module) です。
LSMフック:カーネルに張られた共通の差し込み点
LSM の核心は、カーネルの要所に フック(hook) をあらかじめ埋め込んでおくという設計です。プロセスがファイルを開く、プログラムを実行する、ソケットを bind する——こうしたセキュリティ上意味のある操作の直前に、security_* という形のフック呼び出しが置かれています。
重要なのは呼ばれる順序と位置です。多くのフックは、カーネルが従来の DAC チェックを通過した後に置かれます。つまり LSM は「DAC を緩める」ものではなく、DAC を通った操作をさらに絞り込む第二の関門として働きます。
ユーザープロセス
│ open("/etc/shadow", ...)
▼
[システムコール] sys_openat
│
▼
DACチェック(rwxビット・capability) ──拒否──→ EACCES
│ 通過
▼
LSMフック security_file_open()
│ └→ 登録モジュール(SELinux等)へ問い合わせ
│ 許可 → 0
│ 拒否 → -EACCES
▼
実際のファイルオープン処理
各操作にはカーネルが管理するオブジェクト(プロセスを表す task、開いたファイルを表す構造体、inode など)が結び付いており、フックはそれらを引数として受け取ります。モジュールはオブジェクトに紐づくセキュリティコンテキストを見て、ポリシーに照らして 0(許可)か負のエラーコード(拒否)を返します。この仕組みは システムコール を入口に、カーネルモード の内側で完結します。
セキュリティ理論でいうリファレンスモニタの三条件——(1) 改竄不可能、(2) 常に介在(バイパス不可能)、(3) 検証可能なほど小さい——を、LSMフックは満たそうとします。フックがカーネルの全アクセス経路に網羅的に置かれていること(完全媒介)が、(2) の保証の土台です。フックの抜けは、そのままポリシーの穴になります。
フックは「許可を与えない」:拒否専用という原則
LSM 設計の見落とされがちな原則が、フックは権限を与えられず、奪うことしかできないという非対称性です。DAC が拒否した操作を LSM が許可に転じることは(ごく一部の例外を除き)できません。LSM は常に DAC の後段で、許可済みの操作集合を部分集合へ狭める方向にしか作用しないのです。
この「拒否専用」という性質が、MAC を後付けしても既存の権限モデルを破壊しない安全性を支えています。ポリシーが空・無設定なら、振る舞いは素の DAC と変わりません。逆に言えば、フックの戻り値が許可(0)でも、それは「LSM が反対しない」だけであり、最終的な可否は DAC と LSM の AND で決まります。
SELinux:ラベル(型)に基づく型強制
代表的な LSM 実装の一つが SELinux です。その中核思想は TE(Type Enforcement / 型強制) にあります。
SELinux では、すべての主体(プロセス)と客体(ファイル・ポート・デバイス等)に セキュリティコンテキスト が付与されます。形式は user:role:type:level で、TE で本質的に効くのは type(型) です。プロセスの型をドメイン、資源の型をタイプと呼び分けますが、いずれも内部的には同じ「型」です。
ポリシーは「ある型のドメインが、ある型の客体に対して、どのクラスのどの操作を許すか」を allow ルールの巨大な集合として定義します。
allow httpd_t httpd_sys_content_t : file { read getattr open };
↑主体の型 ↑客体の型 ↑クラス ↑許可される操作
このルールがなければ、たとえ DAC 上は読めるファイルでも、Web サーバープロセス(httpd_t)はそれを読めません。許可をホワイトリストで列挙し、書かれていないものはすべて拒否する——このデフォルト拒否が型強制の骨格です。プロセスが execve で別プログラムを起動する際にドメインを遷移させる type transition の仕組みにより、「この実行ファイルから起動されたプロセスはこのドメインで動く」という連鎖も定義できます。表現力は極めて高い反面、ポリシーは膨大かつ難解になりがちです。
AppArmor:実行パスに基づくプロファイル
もう一つの代表が AppArmor です。設計思想は SELinux と対照的で、ラベルではなく 実行ファイルのパス を基準にします。
AppArmor は「このパスの実行ファイルは、これらのパスにこの権限でアクセスしてよい」という プロファイル をプログラムごとに記述します。
profile /usr/sbin/nginx {
/var/www/** r,
/var/log/nginx/*.log w,
/etc/nginx/** r,
}
主体はプロファイル名(実質はバイナリのパス)で識別され、客体はファイルシステム上のパスで指定されます。ラベルの貼り直しが不要なため導入と理解が容易で、特定のアプリだけを手早く封じ込めたい場合に向きます。一方、パスは可変であり(シンボリックリンクやマウントで指す先が変わり得る)、ラベルのように資源そのものへ不変に結び付くわけではないため、表現の厳密さでは型強制に劣ります。
| 観点 | SELinux(型強制) | AppArmor(パス基準) |
|---|---|---|
| 識別の基準 | 主体・客体に付けたラベル(型) | 実行ファイルのパスとアクセス先パス |
| ポリシーの単位 | システム全体の型の関係グラフ | プログラムごとのプロファイル |
| 客体への結び付き | 資源に不変のラベルを付与 | パス名で間接的に指定 |
| 表現力 | 高い(ドメイン遷移・MLS等) | 中程度(パス前提で簡潔) |
| 導入・運用の容易さ | 難しい(学習コストが大) | 比較的容易 |
| 典型的な採用ディストロ | RHEL系・Fedora・Android | Ubuntu・SUSE系 |
優劣ではなくトレードオフです。ラベル基準(SELinux)は資源そのものに属性が固着するため、パスが変わっても保護が追従し、厳密で網羅的なポリシーを書けます。パス基準(AppArmor)はファイルシステムの構造をそのまま使えるので直感的で導入が速い反面、パスのすり替えに本質的に弱い面があります。「厳密さ」と「運用しやすさ」のどちらを重く見るかが分かれ目です。
スタック型LSM:複数モジュールの共存
歴史的に、LSM は 同時に1つしか有効化できない排他的な仕組み でした。SELinux と AppArmor を同時に使うことはできなかったのです。これはフックの戻り値や、オブジェクトに埋め込むセキュリティコンテキスト用のポインタ領域を、単一モジュールが占有する設計だったためです。
現在は スタック型 LSM(LSM stacking) により、複数モジュールの共存が段階的に実現されています。仕組みの要点は二つあります。
- 呼び出しの連鎖:各フックに登録されたモジュールを順番に呼び出す。
capabilityのような基盤モジュールが先に呼ばれ、続いて主要なモジュールが呼ばれる。 - 拒否優先の合成:あるフックに対し、登録モジュールのいずれか一つでも拒否を返したら、全体として拒否になる。全モジュールが許可(0)を返したときだけ操作が通る。
security_file_open() 呼び出し
├→ capability モジュール → 0(許可)
├→ SELinux モジュール → 0(許可)
└→ BPF LSM モジュール → -EACCES(拒否)
↓
合成結果 = 拒否(1票でも拒否なら全体が拒否)
この 論理 AND による合成 は、前述の「フックは拒否しかできない」原則と整合します。モジュールを足すほど許可される操作集合は狭まるだけで、決して広がらない。だからモジュールを安全に積み重ねられるのです。なお、現状でも selinux と apparmor のように自前のラベル領域を奪い合う大型モジュール同士の完全な同時利用には制約が残り、capability・Yama・Landlock・BPF LSM などとの併用が主な現実解になっています。
LSM フックには、eBPF プログラムを直接アタッチできる BPF LSM という仕組みもあります。ポリシーをカーネル再ビルドなしに、検証器の安全網付きで差し込めるため、コンテナ環境などで動的なアクセス制御を実装する手段として広がっています。スタック型 LSM の一員として、他モジュールと並んで拒否票を投じられます。
seccompとの違い:何を見て止めるか
混同されやすいのが seccomp との関係です。seccomp は システムコール番号と引数の生の値 をフィルタリングする仕組みで、「ptrace を呼ばせない」のように呼び出しの入口を絞ります。対して LSM は、システムコールが処理を進めた先で、カーネルオブジェクトの意味(どの inode か、どの型か) を見て判断します。
つまり seccomp が「どのシステムコールを許すか」という構文レベルの制御なら、LSM は「どの主体がどの客体に何をしてよいか」という意味レベルの制御です。両者は層が異なり、システムコール ABI の入口で seccomp が粗く絞り、その先で LSM が文脈に応じて細かく裁く、という多層防御として併用されます。
頻出の対比を押さえましょう。DAC は所有者の裁量/MAC は覆せないシステムポリシー。LSM フックは DAC 通過後に呼ばれ、許可は与えず拒否しかできない(最終可否は DAC と LSM の AND)。SELinux はラベル(型)に基づく型強制でデフォルト拒否、AppArmor はパス基準のプロファイル。スタック型では 1モジュールでも拒否なら全体拒否。seccomp はシステムコール入口の構文制御、LSM はオブジェクトの意味に基づく制御、という層の違いも問われます。
まとめ
LSM は、カーネルの要所に埋め込んだ 共通フック によって MAC を実装する基盤です。フックは DAC チェックを通過した後に呼ばれ、許可は与えられず拒否しかできない——最終的な可否は DAC と LSM の論理積で決まります。代表実装の SELinux はラベル(型)に基づく型強制でデフォルト拒否のポリシーを全体に課し、AppArmor は実行パスに基づくプロファイルで個々のプログラムを簡潔に封じ込めます。両者は厳密さと運用容易性のトレードオフです。現在は スタック型 LSM により複数モジュールが共存し、1票でも拒否なら全体が拒否という合成規則が「拒否専用」原則と整合します。eBPF を使う BPF LSM はその動的な発展形であり、システムコール 入口の seccomp とは層を分けて多層防御を成します。
OS Article
Linux Security Module(LSM)とアクセス制御フックを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
LSM
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
SELinuxは主体・客体に付けたラベル(型)で許可関係を定義する型強制、AppArmorは実行パスごとにプロファイルを当てるパス基準で、思想と運用が大きく異なる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「LSM / SELinux」に近いか確認する。
- 強みである「LSMはカーネルの要所に置かれた共通フックで、DAC(所有者ベースの権限)を通った操作をさらに任意アクセス制御(MAC)で許可/拒否する第二の関門。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。