RESEARCH ARCHIVE / LID
LID | Linux Integrity Drift
The Core Idea
The Linux Security Module framework has maintained a single guarantee for over 20 years: security modules can only add restrictions, never remove them. This guarantee is correct. LID does not break it.
LID finds something different. It identifies kernel subsystems that perform security-sensitive operations without consulting the LSM framework at all. The gate was never breached. It was walked around.
LSM Hook Architecture
The kernel uses the call_int_hook macro to iterate through registered security modules. Each module returns 0 (allow) or nonzero (deny). The loop short-circuits on the first denial, ensuring modules can only add restrictions.
Five Findings
Detailed Findings
eBPF Pathname Rewriting
A BPF kprobe attached to do_sys_openat2 uses bpf_probe_write_user to rewrite the filename in the user space buffer before the kernel copies it. Combined with a hard link at an AppArmor-allowed path, the kernel resolves the rewritten path, AppArmor evaluates the allowed path instead of the denied one, and the access succeeds with zero audit trace.
The attack exploits a fundamental gap between AppArmor's pathname-based access control model and eBPF's ability to operate before any LSM hook fires. Two hard links to the same inode are treated as entirely different files by AppArmor policy. The BPF program selectively targets specific process and path combinations, making the bypass transparent, selective, and audit-invisible.
| Property | Value |
|---|---|
| Hook bypassed | All pathname-based LSM checks |
| Prerequisite | CAP_BPF + CAP_PERFMON (or root) |
| AppArmor visibility | None (zero audit entries) |
| SELinux visibility | Sees bypass path, not original |
| Selectivity | Per process, per path |
| Reversibility | Removing BPF program restores enforcement |
io_uring MSG_RING File Descriptor Transfer
The io_uring MSG_RING operation allows one io_uring instance to inject a file descriptor into another ring's fixed file table. This fd transfer completely bypasses security_file_receive(), the LSM hook that AppArmor and SELinux rely on for cross-process file descriptor validation.
Unlike other LID findings, this vector operates from unprivileged userspace with no special capabilities required. A regular user can transfer arbitrary file descriptors between io_uring instances without any LSM evaluation. The transferred fd is invisible to every registered security module.
| Property | Value |
|---|---|
| Hook bypassed | security_file_receive() |
| Prerequisite | None (unprivileged) |
| All LSMs blind | Yes |
| Crosses privilege boundary | Yes |
| Kernel version | 5.18+ (MSG_RING introduction) |
New Mount API Bypass
The new mount API introduced with fsopen(), fsmount(), and move_mount() bypasses security_sb_mount(), which is the primary hook that AppArmor and SELinux use to enforce mount policy. The new API path performs mount operations through a different code path that was never wired into the existing LSM mount hook.
| Property | Value |
|---|---|
| Hook bypassed | security_sb_mount() |
| Syscalls | fsopen, fsmount, move_mount |
| Impact | Mount policy enforcement bypassed |
| Prerequisite | Mount privileges in namespace |
BPF Token AppArmor Blindness
The BPF token subsystem (kernel 6.9+) introduces nine security hooks for token creation, delegation, and usage. AppArmor implements zero of these nine hooks. Any BPF token operation occurs with complete AppArmor blindness, no policy evaluation, and no audit logging.
| Property | Value |
|---|---|
| Hooks missing | 9 out of 9 BPF token hooks |
| AppArmor coverage | 0% |
| Kernel version | 6.9+ |
| Impact | Full BPF token delegation without MAC |
AF_XDP tc Egress Bypass
Raw network frames transmitted through AF_XDP sockets bypass the tc (traffic control) egress path entirely. Container environments that rely on tc filters for network policy enforcement (including some configurations of Docker and plain iptables setups) are blind to AF_XDP transmitted traffic. The frames go directly to the NIC driver queue, skipping all tc qdisc and filter processing.
| Property | Value |
|---|---|
| Bypassed layer | tc egress filters |
| Prerequisite | CAP_NET_RAW + AF_XDP support |
| Cilium affected | No (uses XDP/BPF hooks) |
| Plain Docker affected | Yes (tc based filtering) |
Visibility Gap Matrix
| Finding | Visibility Gap | Practical Escalation | Privilege Required | LSMs Affected |
|---|---|---|---|---|
| LID-001 | Critical | Limited (already privileged) | CAP_BPF + CAP_PERFMON | AppArmor, audit |
| LID-002 | Critical | High (unprivileged) | None | All LSMs |
| LID-003 | High | Medium | Mount privileges | AppArmor, SELinux |
| LID-004 | High | Limited | CAP_BPF | AppArmor (0/9 hooks) |
| LID-005 | Medium | Situational | CAP_NET_RAW | tc based filters |
Architectural Implications
LID reveals a design tension in the Linux security architecture. The LSM framework correctly enforces that modules can only add restrictions. The eBPF subsystem provides unrestricted hook points that operate before LSM checks. Newer kernel subsystems (io_uring, the new mount API, BPF tokens) introduce operations that were never connected to existing LSM hooks.
The result is that the security perimeter, as understood by AppArmor and SELinux policy, does not match the actual kernel attack surface. Defenders who rely on LSM-based policy enforcement and audit logging have blind spots in their visibility model. Operations can occur, data can be accessed, and file descriptors can be transferred without any security module being consulted.
This is not a bug in any individual component. It is the natural consequence of a kernel that evolves faster than its security framework.