hyperv_md_uninit() frees the reference-TSC DMA page without quiescing in-flight hyperv_tc64 readers β shutdown-time use-after-free read window
Summary
Readers cache the page pointer at entry then loop; md_uninit swaps the fn pointer and immediately frees the page with no cross-CPU quiesce - a CPU already inside the old tc64 (vmbus timer IRQ) can deref freed DMA memory during teardown. Shutdown-only window (builtin SYSUNINIT), no unpriv trigger, KVM-guest dormant. Leak-the-page fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5124 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| fix.diff | β | 719 B | view raw | |
| env.txt | β | 416 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-5124 β hyperv_md_uninit() frees the reference-TSC page while in-flight hyperv_tc64 readers may still be dereferencing it
Severity: Low Β· Confidence: speculative (window is real by construction; not demonstrable on the KVM audit guest) Β· Impact ceiling: UAF read of hypervisor-shared memory / panic during shutdown on Hyper-V guests
What this is
sys/dev/virtual/hyperv/x86_64/hyperv_machdep.c:208-225:
if (hyperv_ref_tsc.tsc_ref != NULL) {
cputimer_deregister(&hyperv_tsc_cputimer); /* :215 */
hyperv_tc64 = hyperv_tc64_saved; /* :217 */
val = rdmsr(MSR_HV_REFERENCE_TSC);
wrmsr(MSR_HV_REFERENCE_TSC, ...); /* :220 */
hyperv_dmamem_free(&hyperv_ref_tsc.tsc_ref_dma,
hyperv_ref_tsc.tsc_ref); /* :222 */
hyperv_ref_tsc.tsc_ref = NULL; /* :224 */
}
The readers are the HYPERV_TSC() macros (:96):
struct hyperv_reftsc *tsc_ref = hyperv_ref_tsc.tsc_ref; β the page
pointer is loaded once, then the page is read in a seqlock loop
(tsc_seq, tsc_scale, tsc_ofs, re-check tsc_seq), including a
possible re-loop.
The race: a CPU that has executed :96 (pointer load) and is inside
the seqlock loop is not stopped by anything hyperv_md_uninit() does.
:217 swaps the hyperv_tc64 function pointer so new callers are safe,
and :215 deregisters the cputimer β but per-CPU vmbus timer code that
already captured/entered the old tc64 (e.g. vmbus_timer_oneshot(),
vmbus/vmbus.c:366,916) keeps running on other CPUs until interrupt
shutdown. Between the pointer swap (:217) and the hyperv_dmamem_free()
(:222) there is no quiesce β an in-flight reader dereferences freed (and
unmapped/remapped) DMA memory: use-after-free read of a
hypervisor-shared page.
Scope constraints (why Low)
- The code is compiled into the kernel (
sys/platform/pc64/conf/files:203-205βhyperv.c,hyperv_busdma.c,x86_64/hyperv_machdep.carestandard), sohyperv_md_uninit()runs only via SYSUNINIT (hyperv.c:331-344) at system shutdown βkldunloadcannot reach it. - Requires a Hyper-V guest (
vmm_guest == VMM_GUEST_HYPERV,hyperv.c:207) with TIME_REFCNT+REFERENCE_TSC features, Intel/AMD CPU with SSE2 (hyperv_machdep.c:162-166). - Window is narrow (reader must be mid-loop during teardown) and the consequence is a garbage-timestamp read or a fault on unmapped memory β panic during reboot. No confidentiality/integrity impact: the page contains only the TSC reference record.
Reproduce
Not possible on the audit guest: it is KVM (Origin="KVMKVMKVM",
env.txt), so hyperv_identify() fails and the entire file is dormant.
Reproducing would require a Hyper-V VM (Gen1/Gen2 with enlightenments)
triggering reboot while a vmbus timer interrupt lands on another CPU
inside hyperv_tsc_lfence() β plausible but a probabilistic shutdown-time
race. Classified untested with the reasoning above.
Recommended fix
The teardown frees a page the system is about to lose anyway. Either
(a) simply do not free (leak one page at shutdown), or (b) add a real
quiesce: after restoring hyperv_tc64, wbinv+lwkt_send_ipq_mask(
smp_active_mask, ipi_func) style cross-call (or reuse the cputimer
framework's systimer_changed() synchronization) to guarantee no CPU is
still executing the old tc64 before hyperv_dmamem_free(). fix.diff
implements (a) as the minimal safe change.
VERDICT β DF-5124
status: untested (guest is KVM; code path dormant β cannot be exercised) reproduced: false Β· impact: none (observed) Β· confidence: speculative
Narrative
Claim: hyperv_md_uninit() (hyperv_machdep.c:208-225) frees the
reference-TSC DMA page (:222) without quiescing CPUs that may currently
be inside the TSC-reading seqlock loop (:96-125) using a pointer loaded
before the teardown began.
Lifetime trace (all path:line verified in-tree):
hyperv_md_init()allocateshyperv_ref_tsc.tsc_ref(hyperv_machdep.c:186-188), enables the MSR, registershyperv_tsc_cputimerand publisheshyperv_tc64 = hyperv_tsc_lfence/ mfence(:201-205).- Runtime readers: vmbus per-CPU timer code calls
hyperv_tc64()(vmbus.c:366in the timer reload path,vmbus.c:916invmbus_timer_oneshot) β executed in interrupt context, on any CPU. The HYPERV_TSC macro caches the page pointer at entry (:96) and then readstsc_seq/tsc_scale/tsc_ofsrepeatedly (:99-118). - Teardown (SYSUNINIT
hyperv_uninit,hyperv.c:331-344; the file is builtin βsys/platform/pc64/conf/files:203-205β so this is shutdown-only):cputimer_deregister()(:215) switchessys_cputimeraway,hyperv_tc64is restored to the MSR reader (:217), then the page is freed (:222). Nothing between :217 and :222 guarantees an in-flight reader on another CPU has left the loop. A reader that loadedtsc_refat:96before:217and is spinning/reading between:217and:222dereferences freed DMA memory: UAF read. - Consequence bounded to: garbage
uint64_ttime values (vmbus timer mis-fire) or a fault if the mapping is torn down β panic during shutdown. No unpriv-user trigger, no info leak (page holds only the TSC record), shutdown-gated.
Why "speculative": the window is real by construction (no synchronization
exists between the free and in-flight readers), but I cannot demonstrate
it β the audit guest is KVM (env.txt), where hyperv_identify() fails
(hyperv.c:207-208) and this code never runs; even on a Hyper-V guest it
is a probabilistic SMP race at reboot.
Sibling asymmetry not re-reported: hyperv_tsc_cpucounter is registered
in md_init (:203) but never deregistered β already noted in the
pass-2 hyperv.c review (no cpucounter_deregister API exists in
kern_cputimer.c).
Fix validation
Not applicable (Low-class, shutdown-only, dormant on the guest).
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 not built: the race cannot be triggered on the KVM audit guest and is shutdown-only on real Hyper-V systems; the proposed fix (skip the free) trivially removes the window since the page outlives any in-flight reader.
findings/poc/DF-5124/fix.diff
Confirmed kernel references
- sys/dev/virtual/hyperv/x86_64/hyperv_machdep.c:96
- sys/dev/virtual/hyperv/x86_64/hyperv_machdep.c:208-225
- sys/dev/virtual/hyperv/x86_64/hyperv_machdep.c:186-205
- sys/dev/virtual/hyperv/hyperv.c:296-329
- sys/dev/virtual/hyperv/hyperv.c:331-344
- sys/dev/virtual/hyperv/vmbus/vmbus.c:366
- sys/dev/virtual/hyperv/vmbus/vmbus.c:916
- sys/platform/pc64/conf/files:203-205
Detail
Evidence (decisive lines)
findings/poc/DF-5124/VERDICT.md (lifetime trace with path:line); README.md; env.txt (guest is KVM, code dormant)
PoC changes
No PoC exists: requires a Hyper-V guest and a probabilistic SMP race during shutdown; the audit guest is KVM where the code never initializes.
Verified recommended fix
Do not free the reference-TSC page at shutdown (leak one page; md_uninit is shutdown-only builtin code), or add a real cross-CPU quiesce after restoring hyperv_tc64 and before hyperv_dmamem_free (see fix.diff).
Verdict
hyperv_md_uninit() (hyperv_machdep.c:208-225) frees the reference-TSC DMA page at :222 with no quiesce of CPUs that may be inside the HYPERV_TSC seqlock loop using the page pointer cached at :96 - a use-after-free read window between the tc64 pointer swap (:217) and hyperv_dmamem_free (:222). The window is real by construction (no synchronization exists), but its trigger is confined to system shutdown on SMP Hyper-V guests: the file is compiled into the kernel (sys/platform/pc64/conf/files:203-205), so md_uninit runs only via SYSUNINIT (hyperv.c:331-344) at reboot, never kldunload; runtime readers are vmbus per-CPU timer interrupt code (vmbus.c:366,916). Consequence ceiling: garbage timestamps or a fault on freed/unmapped DMA memory -> panic during reboot; no unpriv trigger, no info leak (page holds only the TSC record). Cannot be exercised on the audit guest (KVM - hyperv_identify() fails, hyperv.c:207-208, code dormant) - hence untested/speculative.
No comments yet.