cpuctl microcode update paths ignore rdmsr_safe() failures: uninitialized stack rev0/rev1/res printed to dmesg and used in the success decision
Summary
rdmsr_safe leaves *val untouched on fault, so on a hypervisor that #GPs MSR_BIOS_SIGN/VIA 0x1205 the kprintf discloses up to 64 bits of kernel stack to dmesg and the rev comparison drives the success return on garbage. Root-only triple-gated (priv_check + 0640 root:kmem + securelevel); QEMU implements 0x8B so not triggerable there (probe shows initialized rev=0x1). Zero-init fix. NOTE: the MSR ioctls themselves are safe (rdmsr_safe/wrmsr_safe + msr_onfault verified live: EFAULT no panic) - the DF-4735/4825 #GP family does NOT apply here.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4865 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| cpuctl_probe.c | β | 4.6 KB | view raw | |
| build.sh | β | 101 B | view raw | |
| run.sh | β | 176 B | view raw | |
| build.log | β | 96 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| run.2.log | β | 164 B | view raw | |
| env.txt | β | 336 B | view raw | |
| fix.diff | β | 579 B | view raw | |
| VERDICT.md | β | 3.9 KB | β raw |
DF-4865 VERDICT β not_reproduced (trigger precondition absent on this guest); code-level defect certain
What was tested
Live verification on the QEMU guest (DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC,
QEMU Virtual CPU version 2.5+, 6 CPUs, securelevel -1, gcc 8.3):
-
Device perms (task item g):
cpuctlis an optional module (sys/conf/files:408), not present in X86_64_GENERIC; afterkldload cpuctlthe nodes/dev/cpuctl0..5appear ascrw-r----- root kmemβ mode 0640 (cpuctl.c:480-481). An unprivileged user (maxx, uid 1001, no kmem group) cannot open the device at all (Permission denied). The MSR porthole is root:kmem-gated exactly as coded; identical to upstream FreeBSD. -
The #GP family question (DF-4735/DF-4825 context): all four MSR ioctls (RDMSR/WRMSR/MSRSBIT/MSRCBIT) issued against invalid MSR 0x11111111 returned
-1 / EFAULTand the guest stayed alive. cpuctl.c usesrdmsr_safe/wrmsr_safe(cpuctl.c:197,199,202,204,208,210) whose #GP is fielded bymsr_onfault(sys/platform/pc64/x86_64/support.s:861-866). No bare rdmsr/wrmsr exists in this file β the coretemp/clockmod panic family does not reach here. -
The finding itself (uninit stack print):
CPUCTL_UPDATEwith a garbage 64-byte blob returnedEEXIST(rev1 == rev0, cpuctl.c:307-310) and the kernel printed[cpu 0]: updated microcode from rev=0x1 to rev=0x1β initialized values. Root cause analysis is code-certain: - cpuctl.c:289rdmsr_safe(MSR_BIOS_SIGN, &rev0);β return ignored; - cpuctl.c:302rdmsr_safe(MSR_BIOS_SIGN, &rev1);β return ignored; - cpuctl.c:400/422/425 same pattern forrev0/res/rev1inupdate_via(); -rdmsr_safewrites*valonly on success (support.s:820-836: the store atmovq %rax,(%rsi)is skipped whenrdmsrfaults intomsr_onfault). On fault,rev0/rev1/resremain uninitialized stack; cpuctl.c:304 kprintf's(unsigned)(rev0>>32)/(unsigned)(rev1>>32)(β€64 bits of kernel stack to dmesg) and cpuctl.c:307/431 make the success decision on garbage. The branch requires MSR 0x8B (architectural since P6) or 0x1205 to #GP β cannot be made to fault on this QEMU CPU, so not_reproduced / impact none here; classified Info (root-only, exotic-fault precondition). -
Negative results (pass-2 classes killed on this file) β see the JSON
negative_notesfor the full list: MSR RMW race serialized (global cpuctl_lock cpuctl.c:103 + crit_enter cpuctl.c:201/207); CPUID copyout framing exact and pre-zeroed (cpuctl.c:153; sys_generic.c:668-724); UPDATE bounds 0 < size β€ 4MB with no size_t overflow and single clean free (cpuctl.c:265-313); negative dev2unit unreachable (units only 0..ncpus-1, cpuctl.c:480).
Exploit chain
None β no memory corruption; worst case is β€64 bits of kernel stack to the console/dmesg, readable only by root-equivalent users (the ioctl requires SYSCAP_NOCPUCTL_UPDATE and an FWRITE open of a 0640 root:kmem node).
Fix
fix.diff: zero-initialize rev0/rev1 (update_intel) and rev0/rev1/res
(update_via). Fix validation (kernel rebuild) not performed: the finding did not
reproduce on this guest (no observable misbehavior to diff against) β
fix_status: not_testable.
Artifacts
cpuctl_probe.cβ probe source (sha256 2a7d932d6d99d028fa8e59a1c9323385e9c628f1f7700a1557ef1b7a421f39a3)build.log/run.log(root run incl. dmesg kprintf) /run.2.log(unpriv user)env.txtβ guest uname, securelevel, module state, node perms
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableFix validation moot: the uninit branch could not be triggered on this guest (MSR_BIOS_SIGN never faults under QEMU), so there is no observable baseline misbehavior to confirm fixed. fix.diff is a one-line-per-function zero-init, trivially correct by inspection.
fix.diff; VERDICT.md
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (root probe: sections 2/3/5 - EFAULT family, UPDATE validation, kprintf rev=0x1 initialized); run.2.log (unprivileged Permission denied); env.txt (perms 0640 root:kmem, securelevel -1, optional module); VERDICT.md (full trace); cpuctl_probe.c sha256 2a7d932d6d99d028fa8e59a1c9323385e9c628f1f7700a1557ef1b7a421f39a3
PoC changes
Seed was a fresh probe authored for this run (no prior seed existed): covers perms gate, all 4 MSR ioctls on an invalid MSR, CPUID framing with poisoned buffers, and UPDATE argument validation incl. the kprintf observation.
Verified recommended fix
Zero-initialize rev0/rev1 (update_intel) and rev0/rev1/res (update_via) - see fix.diff.
Verdict
Code-certain CWE-457: update_intel (cpuctl.c:289,302) and update_via (cpuctl.c:400,422,425) ignore rdmsr_safe() returns; rdmsr_safe leaves val untouched on #GP (support.s:820-836), so rev0/rev1/res would be uninitialized stack, printed by kprintf (cpuctl.c:304, <=64 bits of kernel stack to dmesg) and fed into the rev1>rev0 / res!=0x01 decisions. On the audit guest the branch cannot fire: MSR_BIOS_SIGN (0x8B) reads fine under QEMU (probe shows 'updated microcode from rev=0x1 to rev=0x1' with initialized values), so not_reproduced/impact none here. Root-only path (SYSCAP_NOCPUCTL_UPDATE + FWRITE on 0640 root:kmem node) -> Info severity. The same guest run positively confirmed: /dev/cpuctl = 0640 root:kmem (cpuctl.c:481), unprivileged user cannot open at all, and all four MSR ioctls on an invalid MSR return EFAULT without panic (rdmsr_safe/wrmsr_safe + msr_onfault, support.s:861) - the DF-4735/DF-4825 #GP-panic family does not apply to the cpuctl ioctl path.
No comments yet.