# DF-4147 — cuse proc_rwmem_dfly: arbitrary kernel read/write from the CUSE server

## What

`sys/dev/misc/cuse/cuse.c:881-893` (`proc_rwmem_dfly`) implements the
server↔client cross-process data copy for `CUSE_IOCTL_READ_DATA` /
`CUSE_IOCTL_WRITE_DATA` (dispatch at `cuse.c:1352-1380`, fallback at
`cuse.c:949-1030`).  The peer address (`pchk->peer_ptr`, a fully
server-controlled 64-bit value) is passed as the **raw kernel-side pointer**
of `copyin()`/`copyout()`:

```c
if (uio->uio_rw == UIO_WRITE)
        error = copyin(iov->iov_base, (void *)uio->uio_offset, iov->iov_len);
else
        error = copyout((void *)uio->uio_offset, iov->iov_base, iov->iov_len);
```

DragonFly's `std_copyin`/`std_copyout`
(`sys/platform/pc64/x86_64/support.s:228-328`) validate **only the
user-side argument** (≤ `VM_MAX_USER_ADDRESS`); the kernel-side pointer is
used raw.  Upstream FreeBSD's cuse.c uses `proc_vm_rwmem()` which walks the
target process's vm map; the DragonFly port replaced it with this shim and
lost every safety property:

* `CUSE_IOCTL_WRITE_DATA` with `peer_ptr` ≥ `CUSE_BUF_MAX_PTR` (0x20000) and
  outside the client's bounce-buffer window takes
  `cuse_server_data_copy_locked` → `cuse_proc2proc_copy(curproc, local_ptr,
  client_proc, peer_ptr, len)` → `copyin(local_ptr /*validated user*/,
  peer_ptr /*RAW KERNEL DST*/, len)` = **arbitrary kernel write** with
  server-controlled data and length (no length cap on this path).
* `CUSE_IOCTL_READ_DATA` analogously lands in `copyout(peer_ptr /*RAW
  KERNEL SRC*/, local_ptr /*validated user*/, len)` = **arbitrary kernel
  read** into the server's userspace.

Preconditions: an open `/dev/cuse` server fd (mode 0600 → root) plus one
pending client command (any user opening a created `/dev/<name>` node and
blocking in open/read/write/ioctl).  `CUSE_IOCTL_CREATE_DEV` requires
`SYSCAP_NODRIVER` — the intended deployment is exactly a caps-restricted
daemon (webcamd/uhidd style), so this is a **sandbox/caps-jail escape to
full kernel compromise**, not an unprivileged-local primitive.

Functional half of the bug: for a *legitimate* server passing the client's
real user VA, the copy touches the SERVER's own address space at that VA —
normally unmapped → EFAULT, silently corrupting the server otherwise.
All cuse data transfers that miss the ≤4 KiB bounce-buffer fast path are
broken (`fixtest_baseline.log` proves it: `WRITE_DATA returned 14 (Bad
address)` and the client's buffer stays empty).

## Reproduced (4/4 runs, incl. fresh `vm.sh reset with-src` boots)

* kernel READ of `version` at 0xffffffff81144960 returns the kernel version
  string (`run.log`)
* kernel WRITE of the same symbol sticks, readback `CUSE-PWNED ...`
  (`run.log`), self-restored afterwards
* full chain to uid=0 for an unprivileged process: sysctl
  `kern.proc.pid.<child>` → `kp_paddr` (struct proc) → `proc+0x10`
  (`p_ucred`) → zero all uid/gid fields that equal 65534 → child prints
  `getuid=0 geteuid=0`, execs a shell → `uid=0(root) gid=0(wheel)`,
  reads `/etc/spwd.db`

## Fix validation

`fix.diff` replaces `proc_rwmem_dfly` with a `procfs_rwmem()`-style
page-by-page walk (`vm_fault_page` + `pmap_kenter_quick` + `uiomove`) of the
target process's map and rejects peer addresses ≥ `VM_MAX_USER_ADDRESS`.
Rebuilt `cuse.ko` only (incremental kernel-module build, `fix_build.log`),
installed, rebooted:

* `fix_run.log` / `fix_run.2.log`: kernel READ → EFAULT, no uid0, PoC RC=1
* `fix_run_wprobe.log`: kernel WRITE → EFAULT (rc=-1 errno=14)
* `fix_run_fixtest.log`: positive control — a legitimate >4 KiB server→client
  transfer now WORKS (`cross-process fallback copy WORKS`), which the
  unpatched kernel could not do (`fixtest_baseline.log`)

## Files

* `cuse_krw.c` — main PoC (read/write/uid0 chain)
* `wprobe.c` — write-side one-shot probe
* `fixtest.c` — positive functional control of the fallback copy
* `build.sh`, `run.sh` — exact commands
* `run.log`, `run.2.log`, `run.3.log` — baseline reproductions
* `fix_build.log`, `fix_run*.log`, `fixtest_baseline.log` — fix validation
* `env.txt` — guest state

## 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.
