# 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`:

1. In-vkernel unprivileged user calls `set_tls_area(1, {base:0x0000800000000000, size:-1}, 16)`
   (SYS_set_tls_area = 472).
2. The vkernel's `sys_set_tls_area` stores `info` into
   `curthread->td_tls.info[1]` (vkernel64 tls.c:102) and returns 0.
   `td_tls` is `#define`d as `td_mach.mtd_savevext.vx_tls`
   (vkernel64/include/thread.h:50) — **the same memory** that
   `struct vextframe` wraps (cpu/x86_64/include/vframe.h:44-47).
3. Return to emulated user mode: `go_user()` calls
   `vmspace_ctl(id, VMSPACE_CTL_RUN, tf, &curthread->td_savevext)`
   (vkernel64 trap.c:1407-1408) — a *host* syscall made by the vkernel
   process.
4. Host `sys_vmspace_ctl` copyins the vframe's `vx_tls` into the host
   thread's `td_tls` (sys/vm/vm_vmspace.c:293-295), calls
   `cpu_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") — then `set_user_TLS()`
   (vm_vmspace.c:311), which executes
   `wrmsr(MSR_KGSBASE, 0x0000800000000000)` (pc64 tls.c:163-172).
5. 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 1` inside the vkernel
  (in-vkernel uid=gid=65534), poisoning **GS** (fs left valid).
* Serial transcript (run.log / panic.txt): the `DF5655: uid=65534 ...
  base=0x0000800000000000` marker is immediately followed by
  `Fatal trap 9: general protection fault while in kernel mode` and
  `Stopped at set_user_TLS+0x94: wrmsr`.
* RIP attribution: objdump of the guest `/boot/kernel/kernel` places
  `set_user_TLS+0x94` (0xffffffff80bd4c74) on the wrmsr preceded by
  `mov $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_TRIGGER`
  prints, **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 by `min(sizeof(info), infosize)` = ≤16 bytes (tls.c:87-93,
  134); `infosize==0` copies nothing. No overflow, no uninit read: the
  partial-copy path pre-zeroes `info` (tls.c:88).
* **copyout leak** — `sys_get_tls_area` copies `td_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.
