# DF-3005 — Read-after-free of `cdev->si_name` in `devfs_destroy_related_without_flag_worker`

- **File:** `sys/vfs/devfs/devfs_core.c:1499-1520` (bug at 1511-1513)
- **Class:** CWE-416 use-after-free (read), kernel
- **Severity:** Low
- **Reach:** root via disk ioctls (DIOCSDINFO / DIOCSYNCSLICEINFO), or any
  physical disk hotplug/media change that triggers a disk reprobe. Not directly
  unprivileged (requires writing a disklabel / MBR).

## Build

Nothing to compile: `trigger.sh` uses stock tools (vnconfig, fdisk, disklabel).

```sh
sh trigger.sh        # as root on the guest
```

## Expected

With `vfs.devfs.debug=3`, the serial console shows, per destroyed related
device:

    make_dev called for vn0s1
    related_wo_flag: vn0            <- pre-destroy trace (safe)
    related_wo_flag: vn0s0          <- pre-destroy trace (safe)
    related_wo_flag: vn0s0 restart  <- POST-FREE READ (devfs_core.c:1512)
    related_wo_flag: vn0
    related_wo_flag: vn0s1

The `restart` line is emitted by code that executes **after**
`devfs_destroy_dev_worker(dev)` has released the last three references of a
never-opened slice device (`make_dev` = exactly 3 refs: sysref_activate +
reference_dev in `devfs_new_cdev`, + reference_dev in `devfs_create_dev`;
the destroy worker releases exactly those 3), so the cdev has been through
`devfs_cdev_terminate` -> `sysref_put` -> back to the sysref objcache.
`dev->si_name` is then loaded as a function argument — this argument
evaluation happens at *any* debug level (`devfs_debug` is an ordinary
function, not a macro), so production kernels perform the same load.

Observed output for this run is in `run.log`.

## Why it matters / why only Low

The read is 1..(SPECNAMELEN+1) bytes out of a freed-but-still-mapped objcache
chunk. No crash and no user-visible disclosure on the stock kernel: the freed
chunk normally retains its old contents, so the printed name looks normal.
If the chunk is reused before the read, the console (and msgbuf, if readable
by the invoking context) receives whatever the new occupant put there up to
the first NUL. There is no write primitive and no direct unprivileged trigger,
hence Low.

## Fix

`fix.diff` moves the `devfs_debug()` ahead of `devfs_destroy_dev_worker()`
(argument then refers to a live cdev). Validated in the fix build (see
VERDICT.md).
