```
██╗ ██╗██████╗
██║ ██║██╔══██╗
██║ ██║██║ ██║
██║ ██║██║ ██║
███████╗██║██████╔╝
╚══════╝╚═╝╚═════╝
```
— 「Linuxは死にかけている」 —
LSMのセキュリティ保証を迂回するカーネルコードパスの体系的な発見
門は破られたのではない。迂回されたのだ。
Linux Security Moduleフレームワークには、20年以上にわたって維持されてきた中核的な保証が1つあります:
セキュリティモジュールは制限を追加することしかできない。制限を削除することは決してできない。
この保証は正しい。LIDはそれを破らない。
LIDはLSMフックを完全に迂回するカーネルコードパスを発見する — LSMフレームワークに相談せずにセキュリティ上重要な操作を実行するサブシステム。セキュリティチェック自体は正しい。問題は、カーネルがそもそも問い合わせないことにある。
各発見には、混同すべきではない2つの異なる側面があります:
アーキテクチャ上の盲点。「攻撃者はこれを悪用できるか?」という問いではなく:
セキュリティ上重要な操作が発生し、強制レイヤーがそれを評価すらしない場合、攻撃者が今日実際に悪用できるかどうかに関係なく、可視性ギャップが存在します。これはコンプライアンス、フォレンジック、多層防御の前提にとって重要です。
現実世界での悪用可能性に関する問い:
これら2つは別物です。 ある発見が重大な可視性ギャップ(あなたの監視が盲目である)であっても、実用的な権限昇格(攻撃者がすでにrootを必要とする)であるとは限りません。逆に、ある発見が最小限の前提条件で直接的な昇格経路になることもあります。
各発見には特定のカーネル/設定/特権要件があります。環境が一致しない場合、その発見は再現しません。
すべての発見は同じパターンに従います:``` ┌─────────────────────────────────────────────────────────────┐ │ THE LID PATTERN │ │ │ │ Kernel Subsystem A LSM Framework │ │ (eBPF / io_uring / mount API) (AppArmor / SELinux) │ │ │ │ ┌───────────────────┐ │ │ │ Performs operation │──── should call ──── security_*() │ │ │ or manipulates │ but │ │ │ security input │ DOESN'T ██ │ │ └───────────────────┘ │ │ │ │ The LSM framework is correct. │ │ The kernel just doesn't always use it. │ └─────────────────────────────────────────────────────────────┘
二つのカーネルサブシステム。相容れない信頼の前提。一つのギャップ。
<br>
---
## LID-001: eBPF パス名書き換え
**最重要の発見。** `do_sys_openat2` 上の BPF kprobe は、カーネルがコピーする前にユーザーメモリ内のファイル名を書き換える。AppArmor は書き換えられたパスをチェックし、アクセスを許可する。監査トレースはゼロ。```
process: open("/tmp/secret.txt")
│
▼
do_sys_openat2()
│
★ LID kprobe fires here
│ bpf_probe_write_user()
│ rewrites "/tmp/secret.txt" → "/tmp/.bypass_link"
│
▼
getname_flags() ← kernel copies the (rewritten) path
│
▼
security_file_open() ← LSM hooks check "/tmp/.bypass_link"
│ AppArmor: ALLOW ✓
▼
VFS opens inode ← same file content (hard link)
│
▼
return fd to process ← success, zero audit trace
sudo ./scripts/check_prerequisites.sh sudo ./scripts/setup_env.sh make sudo ./scripts/setup_demo.sh sudo ./scripts/run_demo.sh
### デモ出力```
╔══════════════════════════════════════════════════════════╗
║ Phase 1: AppArmor ENFORCING — access should be DENIED ║
╚══════════════════════════════════════════════════════════╝
$ /tmp/test_reader
[-] DENIED: open() failed: Permission denied (errno=13)
╔══════════════════════════════════════════════════════════╗
║ Phase 2: Loading LID — BPF kprobe pathname rewrite ║
╚══════════════════════════════════════════════════════════╝
[*] BPF kprobe attached to do_sys_openat2
╔══════════════════════════════════════════════════════════╗
║ Phase 3: With LID active — access should be GRANTED ║
╚══════════════════════════════════════════════════════════╝
$ /tmp/test_reader
[+] SUCCESS: Read 44 bytes: SECRET_DATA=this_is_protected_content_12345
╔══════════════════════════════════════════════════════════╗
║ Phase 4: Stealth check — audit log inspection ║
╚══════════════════════════════════════════════════════════╝
$ dmesg | grep apparmor | grep DENIED
(empty — no denial was ever generated)
IORING_OP_MSG_RING と IORING_MSG_SEND_FD を組み合わせると、io_uring リング間でファイルディスクリプタが転送されますが、security_file_receive() が呼び出されません。他のすべての fd 転送機構ではこの関数が呼び出されます。```
MSG_RING SEND_FD: __io_fixed_fd_install() ← NO security_file_receive()
FIXED_FD_INSTALL: receive_fd() ← security_file_receive() ✓
SCM_RIGHTS: receive_fd() ← security_file_receive() ✓
binder: security_binder_transfer_file() ✓
**バグ箇所:** `io_uring/msg_ring.c` の `io_msg_install_complete()` — `__io_fixed_fd_install()` を直接呼び出し、LSMフックをスキップします。
**ftraceで検証済み:** `security_file_receive` は SCM_RIGHTS では発火するが、MSG_RING では発火しない。
**影響を受けるバージョン:** Linux 5.18+ から v7.1-rc3 まで(2026-05-17 時点では未修正)。
**PoC:** [`findings/lid-002-iouring-msgring/msg_ring_bypass.c`](https://github.com/azqzazq1/lid/blob/HEAD/findings/lid-002-iouring-msgring/)
<br>
---
## LID-003: 新しいマウント API が AppArmor をバイパスする
新しいマウント API(`fsopen` + `fsconfig` + `fsmount` + `move_mount`)は **決して `security_sb_mount()` を呼び出しません** — これは AppArmor が実装している唯一のマウントフックです。```
OLD API: mount("proc", "/mnt", "proc", 0, NULL)
→ security_sb_mount() → AppArmor: DENY ✗
NEW API: fsopen("proc") → fsconfig(CMD_CREATE) → fsmount() → move_mount()
→ security_sb_kern_mount() → AppArmor: (not implemented) → ALLOW ✓
AppArmor は 4 個のマウントフックを登録しています。SELinux は 14 個を登録しています。新しいマウント API は、SELinux だけが実装しているフックを使用します。
追加で見つかったギャップ:
open_tree(OPEN_TREE_CLONE): セキュリティフックがゼロ — バインドマウントポリシーをバイパスしますmount_setattr(): カーネルには LSM フックが一切存在しませんmove_mount() (デタッチされたマウント): AppArmor は NULL ソースパスを認識します詳細: findings/lid-003-mount-api/
BPF トークンサブシステム (Linux 6.9+) は、BPF ケーパビリティをユーザー名前空間内の非特権プロセスに委任します。カーネルは 9 個の BPF LSM フックを定義しています。SELinux は実際の avc_has_perm() による強制で 9 個すべてを実装しています。AppArmor は ゼロ を実装しています。```
Kernel defines 9 BPF LSM hooks:
┌─────────────────────────────────────────────────────────────────┐
│ bpf, bpf_map, bpf_prog, bpf_map_create, bpf_prog_load, │
│ bpf_token_create, bpf_token_free, bpf_token_cmd, │
│ bpf_token_capable │
└─────────────────────────────────────────────────────────────────┘
SELinux: ████████████████████████████ 9/9 implemented AppArmor: 0/9 implemented Smack: 0/9 implemented
Ubuntu/Debian では、bpffs の委任を持つコンテナは次のことが可能です:
- BPF トークンを作成 → AppArmor は何も検知しない
- トークンを使ってトレーシングプログラムをロード → AppArmor は何も検知しない
- CAP_PERFMON を委任 → Spectre 緩和策を無効化 → AppArmor は何も検知しない
**詳細:** [`findings/lid-004-bpf-token/`](https://github.com/azqzazq1/lid/blob/HEAD/findings/lid-004-bpf-token/)
<br>
---
## LID-005: デフォルト Docker コンテナからの AF_XDP tc Egress バイパス
AF_XDP のコピーモード送信パス(`xsk_generic_xmit` → `__dev_direct_xmit`)は、コンテナのインターフェース上で **tc egress classifier をバイパスします**。tc u32 の DROP-ALL で検証済み — AF_PACKET はブロックされ、AF_XDP は通過します。
**ただし:** Cilium v1.19(kind クラスタ、全て拒否の egress NetworkPolicy)でテスト済み — **Cilium はバイパスされませんでした。** Cilium はノード側の veth ピア ingress で強制し(`cil_from_container` を tcx/ingress 経由で)、さらに送信元 IP 検証を行います。AF_XDP パケットは `bpf_lxc.c:1603` で「Invalid source ip」としてドロップされました。影響は、ネットワークポリシーを tc egress classifier のみに依存する環境(プレーンな Docker + tc ルール、CNI なし)に限定されます。```
Normal TX path:
socket → dev_queue_xmit() → sch_handle_egress() → tc classifiers → driver
↑
ENFORCED (Cilium eBPF, Calico, tc u32)
AF_XDP copy-mode TX:
xsk_sendmsg() → xsk_generic_xmit() → __dev_direct_xmit() → driver
↑
SKIPPED (tc never runs)
カーネル 6.8.0 上の Docker デフォルトコンテナで動的に検証済み:``` tc egress DROP-ALL on container eth0:
AF_PACKET sendto → ENOBUFS (BLOCKED by tc) AF_XDP sendto → 0 (BYPASS — 3 spoofed packets reached docker0 bridge)
パケットは攻撃者が制御するEthernetヘッダー — 偽装されたMAC、偽装されたIP — を運び、有効なDROPポリシーにもかかわらずDockerブリッジに到達します。
**PoC + 詳細:** [`findings/lid-005-afxdp-tc-bypass/`](https://github.com/azqzazq1/lid/blob/HEAD/findings/lid-005-afxdp-tc-bypass/)
<br>
---
## アーキテクチャ: BPF LSMがこれを修正できない理由```
LSM Hook Chain: call_int_hook(file_open, ...)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Lockdown │──>│ Capability │──>│ AppArmor │──>│ BPF LSM │
│ return 0 │ │ return 0 │ │ return -13 │ │ (never │
│ (allow) │ │ (allow) │ │ (DENY) ██ │ │ reached) │
└─────────────┘ └─────────────┘ └──────┬───────┘ └─────────────┘
│
loop breaks
RC = -EACCES
★ The LSM framework is correct. No module can undo another's denial.
★ LID operates OUTSIDE the framework — before hooks run, or where hooks don't exist.
┌───────────────────────────────────────┐
│ THE ATTACK TIMELINE │
│ │
Syscall Entry │ ★ LID ─── manipulates input │ │ │ (security check sees wrong data) │ ▼ │ │ LSM Check │ LSM allows (or never runs) ✓ │ │ │ │ ▼ │ │ Syscall Exit │ ★ SunnyDayBPF ─── rewrites telemetry │ │ │ (monitoring sees wrong data) │ ▼ │ │ Audit/Log │ SIEM sees nothing ✓ │ │ │ │ Combined: ghost access │ └───────────────────────────────────────┘
<br>
## プロジェクト構造```
LID/
├── src/
│ ├── bpf/
│ │ └── lid.bpf.c # BPF kprobe — pathname rewriter (LID-001)
│ └── loader/
│ └── lid_loader.c # Userspace loader + event monitor
├── findings/
│ ├── lid-001-ebpf-pathname/ # eBPF pathname rewriting bypass
│ ├── lid-002-iouring-msgring/ # io_uring MSG_RING missing LSM hook
│ │ ├── msg_ring_bypass.c # PoC with ftrace verification
│ │ └── ADVISORY.md # Technical advisory
│ ├── lid-003-mount-api/ # New mount API AppArmor bypass
│ ├── lid-004-bpf-token/ # BPF token AppArmor/Smack zero coverage
│ └── lid-005-afxdp-tc-bypass/ # AF_XDP tc egress bypass from container
├── tests/
│ └── test_reader.c # Victim binary for LID-001 demo
├── scripts/
│ ├── check_prerequisites.sh # Verify system requirements
│ ├── setup_env.sh # Install build dependencies
│ ├── setup_demo.sh # Create demo environment
│ ├── run_demo.sh # Run full LID-001 demonstration
│ └── teardown.sh # Clean up everything
├── docs/
│ └── RESEARCH.md # Full technical research paper
├── publish/ # Articles for Medium, dev.to, etc.
├── Makefile
├── LICENSE
├── SECURITY.md
└── README.md
緩和策の詳細なガイダンスについては、docs/RESEARCH.md を参照してください。
各検証結果の完全な詳細については、再現性マトリックス を参照してください。概要:
|
Azizcan Daştan
|
このツールは、認可されたセキュリティテスト、研究、教育目的のみで公開されています。所有していない、または明示的な書面によるテスト許可を得ていないシステムに対して使用しないでください。
LID — なぜなら、整合性は決してロックされていなかったからです。
| Finding | Visibility Gap | Practical Escalation |
|---|
| LID-001 | 重大 — AppArmorは何も見ず、監査ログは空、フォレンジックの痕跡ゼロ | 限定的 — rootまたはCAP_BPF+CAP_PERFMONを必要とする(すでに特権あり)。権限昇格ではない。影響:ポリシー回避+監査の盲目化。 |
| LID-002 | 高 — security_file_receive()が発火せず、fd転送がすべてのLSMから不可視 | 高 — io_uring経由で非特権ユーザースペースから動作。いかなる特権もなしにLSM強制境界を越える。 |
| LID-003 | 高 — security_sb_mount()が迂回され、AppArmorのマウントポリシーはデッドコード | 中 — マウント名前空間へのアクセスを必要とする(ユーザー名前空間内のCAP_SYS_ADMIN)。多くのコンテナ設定で利用可能。 |
| LID-004 | 重大 — AppArmorは何も見ず、BPFフックはゼロ(0/9)、BPFトークン操作の監査トレースなし | 中 — ホストによるbpffs委任+ユーザー名前空間内のCAP_BPFを必要とする。BPF委任を備えたコンテナランタイム(LXD/Incus)で利用可能。 |
| LID-005 | 中 — コンテナインターフェース上のtc egress分類子がAF_XDPトラフィックを評価しない | 限定的 — コンテナeth0上のtc egressを迂回(検証済み)。Ciliumは迂回されない — ノード側veth ingressで強制+送信元IP検証(テスト済み)。影響はtc専用egressフィルタリングの素のDocker構成に限定。 |
| Condition | Required | Notes |
|---|
| カーネルバージョン | 5.x+ | 5.15、6.1、6.6、6.8でテスト済み |
CONFIG_BPF_SYSCALL | =y | 主要な全ディストロでデフォルト |
CONFIG_BPF_KPROBE_OVERRIDE | =y | Ubuntu/Debianは有効、RHELは無効 |
CONFIG_SECURITY_APPARMOR | =y | 対象LSMはAppArmorである必要あり |
| AppArmorプロファイル | Enforcing、対象パスへのdenyルール | 任意のパスベースdenyルールで動作 |
| 特権 | rootまたはCAP_BPF+CAP_PERFMON | 非特権では実行不可 |
kernel.lockdown | noneまたはintegrity | confidentialityモードはkprobeアタッチをブロック |
kernel.unprivileged_bpf_disabled | 無関係 | いずれにせよCAP_BPFが必要 |
fs.protected_hardlinks | クロスユーザーリンクでは0 | 1(デフォルト)でも同ユーザーのハードリンクは許可 |
| SELinuxをAppArmorの代わりに使用 | 動作しない | SELinuxはinodeベースであり、パス名ベースではない |
| Condition | Required | Notes |
|---|
| カーネルバージョン | 6.0+ | IORING_MSG_SEND_FDは6.0で追加 |
CONFIG_IO_URING | =y | 主要な全ディストロでデフォルト |
| 特権 | 不要 | 非特権ユーザースペースから動作 |
io_uring_disabled sysctl | 0(デフォルト) | 2は非特権をブロック、1はすべてをブロック |
| 対象LSM | 任意(SELinux、AppArmor、Smack) | security_file_receive()は汎用LSMフック |
kernel.lockdown | 無関係 | BPFは関与しない |
| Condition | Required | Notes |
|---|
| カーネルバージョン | 6.9+ | BPFトークンは6.9で導入 |
CONFIG_BPF_SYSCALL | =y | 主要な全ディストロでデフォルト |
CONFIG_SECURITY_APPARMOR | =y | Ubuntu/Debianのデフォルト |
| 委任付きbpffs | あり | ホストがdelegate_*オプションでマウントする必要あり |
| 特権 | ユーザー名前空間内のCAP_BPF | userns rootには簡単に利用可能 |
| SELinuxをAppArmorの代わりに使用 | 影響なし | SELinuxは9個すべてのBPFフックを実装 |
| Condition | Required | Notes |
|---|
| カーネルバージョン | 5.2+ | fsopen/fsmountは5.2で導入 |
CONFIG_SECURITY_APPARMOR | =y | 影響を受けるのはAppArmorのみ |
| 特権 | ユーザー名前空間内のCAP_SYS_ADMIN | unshare -mで利用可能 |
| SELinuxをAppArmorの代わりに使用 | 動作しない | SELinuxはsecurity_sb_kern_mount()を実装 |
| コンテナランタイム | seccompフィルタに依存 | Dockerデフォルトのseccompはfsopenをブロック — Podman/LXCはブロックしない場合あり |
| Condition | Required | Notes |
|---|
| カーネルバージョン | 4.18+ | AF_XDPは4.18で導入 |
CONFIG_XDP_SOCKETS | =y | 主要な全ディストロでデフォルト |
| 特権 | CAP_NET_RAWのみ | Docker、Kubernetesポッドでデフォルト |
| コンテナランタイム | Docker、K8s、LXC | デフォルトのケーパビリティセット |
| tcベースのネットワークポリシー | あり | Cilium eBPF、Calico、tc u32/flower |
| Environment | LID-001 | LID-002 | LID-003 | LID-004 | LID-005 |
|---|
| Ubuntu 22.04+(AppArmor、デフォルト) | 動作 | 動作 | 動作 | 動作(6.9+) | 動作 |
| Debian 12+(AppArmor) | 動作 | 動作 | 動作 | 動作(6.9+) | 動作 |
| RHEL/Fedora(SELinux) | 不可 | 動作 | 不可 | 不可 | 動作 |
lockdown=confidentiality | 不可 | 動作 | 動作 | 部分的 | 動作 |
| 非特権ユーザー | 不可 | 動作 | ユーザーns次第 | bpffs委任次第 | 不可 |
| コンテナ(CAP_BPFなし) | 不可 | io_uring次第 | seccomp次第 | 不可 | 動作 |
| コンテナ(CAP_NET_RAW削除) | 不可 | 次第 | 次第 | 不可 | 不可 |
| ID | Vector | Target | What Happens |
|---|
| LID-001 | eBPF kprobeパス名書き換え | AppArmor | kprobeがcopy_from_userの前にファイル名を書き換える → AppArmorは誤ったパスをチェック |
| LID-002 | io_uring MSG_RING SEND_FD | SELinux、AppArmor、Smack | fd転送がsecurity_file_receive()をスキップ — 他のすべてのfd転送はそれを呼ぶ |
| LID-003 | 新しいマウントAPI(fsopen/fsmount) | AppArmor | security_sb_mount()が呼ばれない — AppArmorの唯一のマウントフックが迂回される |
| LID-004 | BPFトークン委任 | AppArmor、Smack | BPFフックがゼロ — トークン作成、使用、ケーパビリティ委任が完全に不可視 |
| LID-005 | AF_XDP __dev_direct_xmit | tc egress、Cilium、Calico | デフォルトDockerからのコピーモードTXがtc分類子を迂回 — 偽造パケットがブリッジに到達 |
| 指標 | 可視性 | 備考 |
|---|
| AppArmor監査ログ | なし | 拒否は発生しない |
auditd / journald | なし | セキュリティイベントは生成されない |
dmesg | 一度きりの警告 | 一般的な bpf_probe_write_user メッセージ |
bpftool prog list | 表示される | アタッチされたkprobeを表示する(確認した場合) |
| ディスク上のハードリンク | 検出可能 | find -samefile (遅い、ノイズが多い) |
| 緩和策 | 効果 | トレードオフ |
|---|
kernel.lockdown=confidentiality | BPFを完全にブロック | 正当なモニタリングを無効化 |
bpf_probe_write_user を無効化 | LID-001のパス書き換えを防止 | カーネルの再ビルドが必要 |
fs.protected_hardlinks=1 | ハードリンクの作成を制限 | 最近のカーネルではデフォルト |
bpftool prog list を監視 | アタッチされたプローブを検出 | 能動的なポーリングが必要 |
| SELinuxへの移行 | inodeベースで、パス書き換えを無効化 | 複雑な移行 |
| io_uringを制限 | LID-002をブロック | アプリケーションを壊す可能性がある |
| 検証結果 | 最小カーネル | 権限 | 対象 |
|---|
| LID-001 | 5.x+ | root / CAP_BPF+CAP_PERFMON | AppArmorのみ |
| LID-002 | 6.0+ | なし | すべて (SELinux、AppArmor、Smack) |
| LID-003 | 5.2+ | CAP_SYS_ADMIN (ユーザー名前空間OK) | AppArmorのみ |
| LID-004 | 6.9+ | CAP_BPF (ユーザー名前空間OK) | AppArmor、Smack |
| LID-005 | 4.18+ | CAP_NET_RAW (Dockerデフォルト) | tc egress (Cilium、Calicoなど) |