kernel: ring 0 cannot execute a user page
SMEP turns the classic escalation — divert kernel control flow into a page the attacker wrote — from a silent takeover into an immediate fault with the offending address in the log. The bit is per-core state, so it is set where the syscall MSRs already are: in the per-CPU bring-up both the boot processor and every application processor run on their way in. A core that climbed the trampoline without it would be a hole no boot log would show, which is why the SMP case now reads CR4 on each core it lands on and requires every one of them to be hardened, not just the one that printed the banner. Enabling it that early is only safe because nothing ring 0 executes is mapped for ring 3, and that had to be established rather than assumed: kernel text carries only its ELF flags, the physmap is no-execute, the trampoline page is mapped supervisor and the core running it has not enabled the bit yet, and the boot processor turns it on while still on the loader's tables — which map nothing user-accessible at all. The one indirect call in the kernel takes a kernel address. The CPUID probing that was scattered across the timer code becomes a small shared helper, since the feature question is now asked from two places and each wanted the same maximum-leaf guard. Absence is tolerated and reported, like the IOMMU: danos still boots on a machine without the feature, and says which one it is. The test harness starts asking QEMU for a CPU that has the bit at all — its default model has neither SMEP nor SMAP, so the code would otherwise have been unreachable in every run. No case behaved differently under the richer model. Suite 112/112, with a new case that maps an executable user page, calls into it from the kernel, and requires the fault the CPU is supposed to raise.
This commit is contained in:
@@ -65,6 +65,11 @@ ARCHES = {
|
||||
# Built as a function so we can splice in per-run paths.
|
||||
"qemu_args": lambda a, boot_volume, vars_fd, serial: [
|
||||
"-machine", "q35", "-m", "128M",
|
||||
# The default TCG model (qemu64) predates the supervisor-hardening bits:
|
||||
# no SMEP, no SMAP, so the kernel's probes would find nothing and the
|
||||
# enabled paths would never run in CI. `max` is "everything this
|
||||
# accelerator can emulate", which under TCG includes both.
|
||||
"-cpu", "max",
|
||||
"-drive", f"if=pflash,format=raw,readonly=on,file={a['ovmf_code']}",
|
||||
"-drive", f"if=pflash,format=raw,file={vars_fd}",
|
||||
# Boot off a FAT USB device: the boot volume is a mass-storage device on
|
||||
@@ -338,6 +343,18 @@ CASES = [
|
||||
"expect": r"page fault \(vector 14\)",
|
||||
"fail": r"NX not enforced"},
|
||||
{"name": "fault-null", "expect": r"page fault \(vector 14\)"},
|
||||
# SMEP: ring 0 calls into a page that is present, executable and *user*-accessible
|
||||
# — the ret2usr shape. CR4.SMEP must refuse the instruction fetch: #PF with error
|
||||
# code 0x11 (present | instruction-fetch; the U/S bit reports the *access* ring, so
|
||||
# it is 0 for a ring-0 fetch), taken in ring 0, reported at the page it tried to
|
||||
# fetch from (a fetch #PF is raised at the un-fetchable instruction, so the IP is
|
||||
# the user address, not the kernel-half call site). The boot line is asserted too:
|
||||
# on a CPU without SMEP nothing would fault and the case would prove nothing.
|
||||
{"name": "fault-smep",
|
||||
"expect": r"(?s)(?=.*smep\s+: enabled)(?=.*page fault \(vector 14\))"
|
||||
r"(?=.*ring 0 \(kernel\))(?=.*error code : 0x11)"
|
||||
r"(?=.*fault addr : 0x0000700000000000)",
|
||||
"fail": r"SMEP not enforced|SMEP not enabled|no frame for the probe page"},
|
||||
# Memory grants: the mmap/munmap path hands out user pages into a process's
|
||||
# arena, translate resolves them, munmap frees them, and no frames leak.
|
||||
{"name": "usermem",
|
||||
|
||||
Reference in New Issue
Block a user