01 — The core idea
Who decides what a process is allowed to do?
Two models answer that question. The difference is the whole point of this lecture — and it's clearest when the attacker already has root.
DAC — Discretionary Access Control
The owner of a resource sets its permissions (chmod, chown). Access is at the owner's discretion. The catch: root is exempt from the checks. This is standard Linux file permissions.
MAC — Mandatory Access Control
A system-wide policy, enforced by the kernel, decides access by the security label of the subject and object. No user — not even root — can waive it. SELinux is the implementation we'll use.
httpd → target: /etc/shadowallow httpd_t shadow_t:file read exists. The kernel refuses regardless of uid.httpd_t has no rule permitting shadow_t:file read, so the kernel denies it even to uid 0. That is "mandatory": the policy is not the owner's to override, and root is not above it.
02 — The mechanics
Every subject and object wears a label
SELinux attaches a security context to every process and resource. A decision is just a lookup: does a rule allow this subject-type to perform this action on this object-type?
Anatomy of a security context
Click a field. This is what ls -Z and ps -Z show you.
The decision engine
Pick a subject, an object, and an action. SELinux is default-deny: if no rule explicitly allows it, it's refused.
allow SUBJECT OBJECT_TYPE:CLASS ACTION; — if that exact line isn't in the loaded policy, the answer is "no". Try httpd_t reading shadow_t, then switch the subject to unconfined_t and watch it change.
03 — The real vulnerability
CVE-2019-9213 — mapping the forbidden zero page
A whole class of kernel exploits needs one thing first: a mapping at virtual address 0x0. Several independent layers exist to stop that. This CVE knocks one of them out.
① Hardware · SMAP
Supervisor Mode Access Prevention stops the kernel from touching user memory at all. That's why the CVE note says "on non-SMAP platforms" — older CPUs have no such guard.
② DAC · mmap_min_addr
/proc/sys/vm/mmap_min_addr (default 0x10000) blocks unprivileged mapping of low memory — you'd need CAP_SYS_RAWIO. A capability check.
③ MAC · SELinux memprotect:mmap_zero
A separate permission, bound to the process domain and decoupled from the tunable. Confined domains like httpd_t are denied the zero page even if an admin sets mmap_min_addr = 0.
expand_downwards() in mm/mmap.c (Linux < 4.20.14) grew a stack VMA without re-checking mmap_min_addr. So instead of calling mmap(0) — which the capability check refuses — an attacker places a VMA just above the guard and lets the stack grow down into the zero page. The DAC guard is simply never consulted. MITRE flags it as a "capability check for the wrong task."
04 — Put it together
Defense Lab: watch the attack meet each layer
Set the system's posture, then launch the exploit. The chain shows exactly where the attack is stopped — or why it isn't.
System posture
Instructor notes — the honest nuance (expand if the class is ready)
SELinux's clean win is the standard route. The memprotect:mmap_zero hook sits on the mmap() syscall path. It reliably stops a confined domain from the ordinary mmap(0) technique — and it does so even when mmap_min_addr has been zeroed (the classic "I disabled it to run WINE" footgun). That decoupling is the designed behaviour.
The CVE's stack-growth route is sneakier. expand_downwards() is not the mmap() path, so neither the capability check nor the mmap_zero hook fires on it. On a vulnerable, non-SMAP kernel the zero page gets mapped regardless of SELinux mode.
And once code runs in ring 0, it's above the reference monitor. A successful kernel-NULL-deref gives kernel-mode execution — at which point SELinux itself can be subverted. So for this bug the decisive controls are patch and SMAP. MAC's role here is to shrink the attack surface beforehand (least privilege on the service) and to own the standard route — not to magically contain ring-0 compromise. That distinction is the real defense-in-depth lesson.
05 — Takeaways
What to remember
DAC answers to the owner
File permissions are discretionary and root is exempt. Useful, but a single misconfig or a root compromise collapses it.
MAC answers to the policy
SELinux decides by label and rule, enforced in the kernel. Default-deny, and binding even on uid 0.
Confinement limits blast radius
A service pinned to httpd_t can only do what its domain allows — shrinking what a compromise can reach.
No single layer is complete
CVE-2019-9213 slipped past a capability check via stack growth. Layers — hardware, DAC, MAC, patching — cover each other's gaps.
The commands behind the demo
setenforce 0, reproduce a denial, then setenforce 1 and show the AVC in ausearch. The simulator mirrors exactly those avc: denied lines.