vkernel sys_set_tls_area accepts a non-canonical TLS base that crosses to the host kernel via go_user()/vmspace_ctl() and panics the HOST in wrmsr(MSR_KGSBASE) β unprivileged in-vkernel user β host kernel panic (DF-5639 vkernel twin, boundary-crossing)
Summary
The vkernel validates which and size but never base: set_user_TLS is a NOP here so the stored td_tls aliases td_mtd.mtd_savevext.vx_tls, which go_user hands verbatim to the HOST's vmspace_ctl on every return to emulated user mode - the host's set_user_TLS executes wrmsr(MSR_FSBASE/KGSBASE) and a non-canonical base raises #GP(0) IN HOST KERNEL MODE. Verified: in-vkernel uid=65534 single syscall -> host 'Fatal trap 9 ... Stopped at set_user_TLS+0x94: wrmsr' (RIP objdump-attributed to MSR_KGSBASE), 2/2 deterministic - the vkernel containment boundary is the only separation and it is crossed. Pair with DF-5639's pc64 fixes to seal both sides. canonicality-check fix validated (both variants EINVAL, vkernel+host survive, rtld TLS unregressed).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5655 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| vk_tls_noncanon.c | β | 2.7 KB | view raw | |
| as_nobody.c | β | 551 B | view raw | |
| vk_image.sh | β | 1.2 KB | view raw | |
| vk_run.sh | β | 1.9 KB | view raw | |
| fix.diff | β | 970 B | view raw | |
| run.log | β | 2.0 KB | view raw | |
| panic.txt | β | 701 B | view raw | |
| run.2.log | β | 856 B | view raw | |
| run.3.log | β | 856 B | view raw | |
| run.3.console.log | β | 2.1 KB | view raw | |
| build.log | β | 934.4 KB | β download | |
| env.txt | β | 2.1 KB | view raw | |
| VERDICT.md | β | 6.6 KB | β raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 7.0 KB | view raw |
DF-5655 β VERDICT
Status: reproduced (host-kernel panic, deterministic; 2/2 baseline fires). Impact: panic β an unprivileged user inside a vkernel panics the host kernel (the kernel running the vkernel process), with one syscall. Confidence: certain. Fix validated (vkernel rejects the value, everything survives).
The finding
sys/platform/vkernel64/x86_64/tls.c β the VKERNEL's sys_set_tls_area
validates which (tls.c:79) and info.size (tls.c:96) but never
validates info.base before storing it (tls.c:102). This is the vkernel
twin of the pc64 gap filed as DF-5639, but the sink is different β and the
threat model is worse: the vkernel's set_user_TLS() is a NOP
(tls.c:142-145), so the poisoned base is not programmed locally; it is
handed to the host kernel on the very next return to emulated user
mode and programmed by the host's wrmsr:
- In-vkernel unprivileged user calls
set_tls_area(1, {base:0x0000800000000000, size:-1}, 16)(SYS_set_tls_area = 472). - The vkernel's
sys_set_tls_areastoresinfointocurthread->td_tls.info[1](vkernel64 tls.c:102) and returns 0.td_tlsis#defined astd_mach.mtd_savevext.vx_tls(vkernel64/include/thread.h:50) β the same memory thatstruct vextframewraps (cpu/x86_64/include/vframe.h:44-47). - Return to emulated user mode:
go_user()callsvmspace_ctl(id, VMSPACE_CTL_RUN, tf, &curthread->td_savevext)(vkernel64 trap.c:1407-1408) β a host syscall made by the vkernel process. - Host
sys_vmspace_ctlcopyins the vframe'svx_tlsinto the host thread'std_tls(sys/vm/vm_vmspace.c:293-295), callscpu_sanitize_tls()β a no-op on pc64 (sys/platform/pc64/x86_64/machdep.c:924-927, "For x86_64 we don't have to do anything") β thenset_user_TLS()(vm_vmspace.c:311), which executeswrmsr(MSR_KGSBASE, 0x0000800000000000)(pc64 tls.c:163-172). - WRMSR with a non-canonical FS.base/GS.base raises #GP(0) in host
kernel mode (Intel SDM Vol.3 17.17) β
Fatal trap 9β the host kernel halts in DDB. The vkernel's containment is fully defeated: code with no privilege on the host (any account inside the vkernel) kills the host kernel.
Proof (baseline, stock kernels)
- Trigger:
/bin/as_nobody /bin/vk_tls_noncanon 1inside the vkernel (in-vkernel uid=gid=65534), poisoning GS (fs left valid). - Serial transcript (run.log / panic.txt): the
DF5655: uid=65534 ... base=0x0000800000000000marker is immediately followed byFatal trap 9: general protection fault while in kernel modeandStopped at set_user_TLS+0x94: wrmsr. - RIP attribution: objdump of the guest
/boot/kernel/kernelplacesset_user_TLS+0x94(0xffffffff80bd4c74) on the wrmsr preceded bymov $0xc0000102,%ecxβ MSR_KGSBASE, i.e. pc64 tls.c:171, the emulated-user GS install. The FSBASE wrmsr (+0x68) was skipped because only GS was poisoned β proving the fault is the vframe-supplied base, not the vkernel's own rtld TLS. - 2/2 deterministic baseline fires (two separate boots); guest wedged in
DDB both times, ssh dead,
vm.sh status => down. - The fault is a fixed #GP β no write primitive, no resumable path β so the impact ceiling is a reliable host-kernel DoS from inside the virtualization boundary (no uid=0 route exists for this class).
Fix validation
fix.diff (this pack) adds the canonicality check to the vkernel's
sys_set_tls_area (reject non-canonical info.base with EINVAL), so the
value is stopped at the source, before any vmspace_ctl handoff:
- Patched-vkernel run (run.2.log): identical trigger, identical
unprivileged uid β
DF5655: RETURNED rc=22(EINVAL),MARKER_AFTER_TRIGGERprints, vkernel stays alive, host stays alive (ssh fine). - FS variant (which=0) equally rejected (run.3.log).
- Legit TLS regression: the fixed vkernel booted normally β every binary exec'd inside it (sh, vkinit, the static trigger) performs rtld TLS bootstrap through this same syscall with canonical bases.
The host-side counterpart (real validation in pc64
cpu_sanitize_tls, covering hostile/other vkernel binaries) is the
second hunk of DF-5639's fix; the two fixes are complementary: DF-5639's
seals the host boundary against any vkernel, this one stops the
stock DragonFly vkernel from ever carrying an in-vkernel user's poison
across it (and avoids the vkernel's own panic("vmspace_ctl failed") at
trap.c:1427-1430 that would otherwise fire once hosts adopt DF-5639's fix).
Rig notes (DF-5474)
The stock VKERNEL64 platform does not link (known finding DF-5474:
vkernel64 SYSTEM_LD drops ${ELDFLAGS}, so __build_id_start/end are
unresolved). The DF-5474 defsym workaround was applied to the guest's
/usr/src copy, identically in baseline and fixed phases; it does not
touch this finding's code paths.
Negative results (checked and killed)
- which/index bounds β
i = uap->which; 0..1 else ERANGE(tls.c:78-80, 126-128);syscallarg(int)is signed; no truncation. - infosize β
uap->infosize < 0(tls.c:81-82, 129-130) is dead code (size_t, sysproto.h:1045,1050 β always false), but every copy is bounded bymin(sizeof(info), infosize)= β€16 bytes (tls.c:87-93, 134);infosize==0copies nothing. No overflow, no uninit read: the partial-copy path pre-zeroesinfo(tls.c:88). - copyout leak β
sys_get_tls_areacopiestd_tls.info[i], which is only ever user-set, fork-inherited (vkernel64 vm_machdep.c:182-183) or zero-allocated; no kernel pointers are reachable through it. - cothread / %gs=0 discipline (DF-5607 interplay) β this file never
touches MSRs (
set_user_TLS()is a NOP, tls.c:142-145) and the swtch.s call site for it is inside#if 0(vkernel64 swtch.s:481-486); no clobber of cothread %gs is possible from this file. - vmspace_ctl TLS save/restore β host saves/restores the vkernel's kernel TLS around VMSPACE_CTL_RUN (vm_vmspace.c:288-290, 301-306, 884) and the error path restores both frame and TLS; the bug is solely the unvalidated input accepted by this file, not the save/restore.
- Races β the td_tls store is same-thread; consumers (go_user on the same thread, fork copy under fork's serialization) cannot observe a torn 16-byte struct in a way this file controls.
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
fixedfixed: baseline (stock vkernel) β in-vkernel uid-65534 trigger panics the host at set_user_TLS+0x94 (MSR_KGSBASE wrmsr), guest dead in DDB; patched vkernel (fix.diff, canonicality check in vkernel64 tls.c) β identical trigger returns rc=22/EINVAL for both gs and fs variants, vkernel stays up (MARKER_AFTER_TRIGGER), host stays up (ssh fine), and legitimate TLS (rtld bootstrap of every binary exec'd in the vkernel) is unaffected.
run.log + panic.txt (baseline panic) vs run.2.log / run.3.log (patched: 'DF5655: RETURNED rc=22', alive); build.log (patched VKERNEL64 build incl. tls.c recompile); env.txt (RIP->wrmsr attribution).
Confirmed kernel references
- sys/platform/vkernel64/x86_64/tls.c:96-102
- sys/platform/vkernel64/x86_64/tls.c:142-145
- sys/platform/vkernel64/include/thread.h:50
- sys/platform/vkernel64/x86_64/trap.c:1407-1408
- sys/vm/vm_vmspace.c:293-311
- sys/platform/pc64/x86_64/machdep.c:924-927
- sys/platform/pc64/x86_64/tls.c:163-172
- sys/cpu/x86_64/include/vframe.h:44-47
Detail
Exploit chain
in-vkernel unprivileged user (uid 65534) -> syscall 472 set_tls_area(1, {base=0x0000800000000000, size=-1}, 16) -> vkernel stores td_tls.info[1] with no base validation (vkernel64 tls.c:102; set_user_TLS is a NOP, tls.c:142-145) -> syscall return: go_user() passes &td_savevext (== td_tls) to host vmspace_ctl(VMSPACE_CTL_RUN) (trap.c:1407-1408) -> host copyin vx_tls (vm_vmspace.c:293) -> cpu_sanitize_tls NOP (machdep.c:924) -> host set_user_TLS wrmsr(MSR_KGSBASE, non-canonical) (pc64 tls.c:171) -> #GP(0) in host kernel mode -> Fatal trap 9 -> host kernel dead in DDB. Stop at panic: no memory-corruption primitive (#GP is a fault, not a controlled write); impact ceiling = unprivileged in-vkernel -> host-kernel DoS, crossing the virtualization boundary.
Evidence (decisive lines)
panic.txt (verbatim serial capture: DF5655 uid=65534 marker then 'Fatal trap 9 ... Stopped at set_user_TLS+0x94: wrmsr'); run.log (baseline narrative + RIP attribution via objdump: +0x94 == wrmsr after mov $0xc0000102,%ecx = MSR_KGSBASE); run.2.log (fixed vkernel, gs variant: 'RETURNED rc=22' = EINVAL, MARKER_AFTER_TRIGGER prints, vkernel+host alive); run.3.log (fixed, fs variant: rc=22); env.txt (kernel uname, KVM/GenuineIntel, rig + DF-5474 build-workaround notes, sha256s); build.log (full VKERNEL64 nativekernel build incl. tls.c recompile with the fix).
PoC changes
No seed PoC existed (pass-2 discovery). Trigger written from scratch as a static in-vkernel binary using a raw syscall-472 stub (no libc TLS dependence, raw write(2) + manual hex so nothing reads %fs/%gs after the poisoning); as_nobody wrapper proves in-vkernel uid/gid 65534; rig (vkroot image + VKERNEL64 boot) adapted from the DF-5607/DF-5579 rigs, with the vkernel console tee'd onto the guest serial console so the unprivileged marker and the host panic land in one transcript. The stock VKERNEL64 does not link without the DF-5474 defsym workaround (applied to the guest's /usr/src only, identically in baseline and fixed phases).
Verified recommended fix
Reject non-canonical info.base in the vkernel's sys_set_tls_area with EINVAL (see fix.diff); pair with DF-5639's pc64 cpu_sanitize_tls validation to seal the host boundary against any vkernel binary.
Verdict
REPRODUCED (panic, 2/2 deterministic): the vkernel's sys_set_tls_area performs no canonicality validation of the user-supplied 64-bit tls_info.base (sys/platform/vkernel64/x86_64/tls.c:96-102 β only info.size < -1 is checked) and stores it into td_tls, which aliases td_mach.mtd_savevext.vx_tls (vkernel64/include/thread.h:50). On the syscall-return path go_user() hands &curthread->td_savevext verbatim to the HOST vmspace_ctl(VMSPACE_CTL_RUN) (vkernel64 trap.c:1407-1408); the host copies it in (vm/vm_vmspace.c:293-295), pc64 cpu_sanitize_tls() is a no-op (pc64 machdep.c:924-927), and host set_user_TLS() executes wrmsr(MSR_KGSBASE, 0x0000800000000000) (pc64 tls.c:163-172) -> #GP(0) in HOST kernel mode (Intel SDM Vol.3 17.17) -> 'Fatal trap 9 ... Stopped at set_user_TLS+0x94: wrmsr', host halts in DDB. RIP 0xffffffff80bd4c74 is the wrmsr after 'mov $0xc0000102,%ecx' (MSR_KGSBASE) per objdump of the guest kernel; the FSBASE wrmsr was skipped because the trigger poisoned only GS. Consequence: ANY unprivileged user INSIDE a vkernel (demonstrated uid=65534 in-vkernel) panics the HOST kernel with one syscall β the vkernel's containment is defeated; this is the vkernel twin of DF-5639 (pc64 direct syscall, RIP +0x68/FSBASE) reached through a different file and a different entry chain. Ceiling is a reliable host-kernel DoS: #GP is a fixed fault with no write primitive and no resumable path, so no uid=0 route exists. fix.diff (canonicality check in this file) was built into the vkernel in-guest and re-run: identical trigger now returns EINVAL (rc=22), vkernel and host stay alive, and legitimate TLS (rtld bootstrap of every exec, both fs and gs variants) is unaffected.
No comments yet.