kernel: a hostile return address cannot fault the kernel

SYSRETQ with a non-canonical RIP raises a general protection fault in ring
0 — on the kernel stack, an instruction after the swapgs that installed the
user's GS base. It is one of the better-known escalation primitives, and
ring 3 reaches it without any kernel bug at all: the processor saves the
address of the instruction after SYSCALL, so a program whose SYSCALL is the
last two bytes of the last canonical page returns to the first
non-canonical address. The new test does exactly that.

The exit path now sign-extends the return address from bit 47 and compares;
if the value changed, it returns through IRETQ instead, which commits the
privilege change before fetching the new address, so the fault arrives from
ring 3 and the process dies like any other. Four register-only operations
and a branch that a correct program can never take — it could not have
executed at a non-canonical address in the first place. Bit 47 is the right
pivot because danos builds four-level page tables and nothing sets the
five-level bit; a future port must move the pivot, and the comment says so.

SFMASK grows one bit while we are here. SYSCALL, unlike an interrupt gate,
does not clear the nested-task flag, so the kernel had been running every
system call with whatever ring 3 last chose — harmless while the only exit
was SYSRETQ, and a question worth not having now that one exit is IRETQ.
The kernel is never nested; ring 3 still gets its own flag back.

Suite 113/113. The new case asserts the refusal counter rather than the
dying process: the emulator we test on kills it either way, so only the
counter distinguishes a guard that ran from one that did not.
This commit is contained in:
Daniel Samson
2026-08-01 11:22:07 +01:00
parent 36a7cc5fe9
commit cb30faf15f
8 changed files with 257 additions and 13 deletions
+7 -1
View File
@@ -1,6 +1,7 @@
# SMEP and SMAP — supervisor-mode hardening
*Design, 2026-07-31. H2 (SMEP) has landed; HS and H3 are the remaining work. Companion to
*Design, 2026-07-31. H2 (SMEP) and HS (the SYSRET guard) have landed; H3 is the
remaining work. Companion to
[protocol-namespace.md](protocol-namespace.md) on the security track — this is
the hardware half; that is the namespace half.*
@@ -136,6 +137,11 @@ Broadwell+ for SMAP; AMD Zen+ for both). The test images should run with
hardening bucket, independent fix (validate RCX before `sysretq`, fall
back to `iretq`), should ride the same branch as H2/H3 but is not
SMEP/SMAP.
**Status — landed 2026-08-01 (HS).** The syscall exit sign-extends the
return RIP from bit 47 (danos is 4-level only; nothing sets CR4.LA57) and
falls back to `iretq` when that changes it, counting each refusal for the
`sysret-canonical` case. Ring 3 could reach it: `syscall` as the last two
bytes of the last canonical page returns to `user_half_end`.
- **KPTI / Meltdown-class leaks are out of scope.** SMEP/SMAP police
architectural accesses, not speculative ones. danos runs one kernel
mapping in every address space and accepts that on affected hardware;
+20 -5
View File
@@ -36,11 +36,11 @@ plain `main` checkout always tells the truth about where the work is.**
| | |
|---|---|
| Working on | **HS** — SYSRET canonical-RIP guard |
| Working on | **H3** — SMAP (the last phase) |
| Branch carrying it | `feat/security-group-4` (pushed to origin) |
| On `main` | everything through P4c — groups 1, 2 and 3 merged |
| Awaiting merge | H2 — lands with the group 4 merge |
| Suite | 112 cases, all passing |
| Awaiting merge | H2, HS — land with the group 4 merge |
| Suite | 113 cases, all passing |
| Last updated | 2026-08-01 |
A checkbox below means the phase met its definition of green and was
@@ -81,7 +81,20 @@ group boundary.
refusal paired with a control; suite 111/111)
- [x] **merge** group 3 → main, push
- [x] **H2** — SMEP on every core (shared CPUID helper; CR4 bit 20 set in the per-CPU bring-up both the BSP and every AP run, asserted per core by the smp case; ring-0-executes-user-pages audit clean incl. the pre-paging window on the loader's tables; fail-open with a posture line; `-cpu max` added to the harness since QEMU's default model has neither bit; new `fault-smep` case; suite 112/112)
- [ ] **HS** — SYSRET canonical-RIP guard
- [x] **HS** — SYSRET canonical-RIP guard (the syscall exit sign-extends the
return RIP from bit 47 and returns through `iretq` when that changes it —
four register ALU ops and a never-taken branch on the hot path, no load; the
fallback un-pops the rip slot so `iretq` consumes the frame entry already
built, reloads R11 from the rflags slot, and keeps the `swapgs` in the same
place relative to the ring change. 4-level is not an assumption but a fact:
nothing sets CR4.LA57, and the comment says what a 5-level port must change.
Ring 3 **can** reach the hazard — `syscall` as the last two bytes of the last
canonical page returns to `user_half_end` — so the new `sysret-canonical`
case is a real ring-3 probe doing exactly that, and dies of a ring-3 #GP at
`0x0000800000000000` while the kernel runs on. Its teeth are the refusal
counter, not the outcome: measured with the guard's branch removed, TCG does
not model Intel's ring-0 #GP and every outcome check still passed. Suite
113/113)
- [ ] **H3** — SMAP + boot-patched `clac`; `-cpu max` in the harness; negative tests
- [ ] **merge** group 4 → main, push
@@ -518,6 +531,8 @@ put the text in `expect`, per `qemu_test.py:189`). Suite 112.
**Test:** kernel unit case driving a thread whose return RIP is forged
non-canonical via the syscall path if constructible cheaply; otherwise the
review-level proof plus the existing fault cases regression. Suite 112.
*(Landed: it was constructible, and from ring 3 rather than by forgery — the
`sysret-canonical` case, suite 113.)*
## H3 — SMAP
@@ -532,7 +547,7 @@ review-level proof plus the existing fault cases regression. Suite 112.
**Test:** new QEMU case `fault-smap`: ring-0 deliberate read of a mapped
user page; expect vector 14 + `error code : 0x1` + kernel IP. And the
whole suite becomes the tripwire — any missed straggler now fails loudly.
Suite 113.
Suite 114 (HS added one).
---