# DF-4984 VERDICT — dead scratch code, zero attack surface (certain)

**Status: untested** (Info dead-code catalog — no guest PoC run; skipped per contract for
Info findings, and unnecessary: the dead-code proof is complete statically). All line
references verified against this tree on 2026-09-25.

## 1. Dead-code proof (three independent, host-verifiable facts)

1. **Excluded from every build path.**
   - `sys/dev/disk/dm/Makefile:7` — `SUBDIR= linear striped crypt delay flakey` (no snapshot).
   - `sys/conf/files:48-60` — dm core + linear/striped/crypt/delay/flakey only.
   - `sys/dev/disk/dm/snapshot/` contains a single file, no Makefile → not kmod-buildable.
   (Contrast: `sys/dev/disk/dm/dmirror/` at least has a Makefile; this is deader than dmirror.)
2. **Zero external references.** `grep -rn 'dm_target_snapshot' sys/` matches only the file
   itself. Nothing declares, registers, or calls these functions.
3. **No self-registration.** Live targets call `dm_target_alloc()` + `dm_target_insert()`
   (`sys/dev/disk/dm/linear/dm_target_linear.c:176,186`; `sys/dev/disk/dm/flakey/dm_target_flakey.c:411,421`).
   This file has no module handler and no insert call → even a hand-built kmod would be inert.

**Plus non-linking:** `dm_target_snapshot.c:319` uses bare `snprintf()`; the DFly kernel only
declares `ksnprintf` (`sys/sys/systm.h:222`) and nothing `#define`s snprintf → implicit
declaration + undefined symbol at link. (Only dm file using bare snprintf.)

Conclusion: the file cannot be compiled into the kernel or a module by any supported path, and
even if force-compiled it registers nothing. **Zero attack surface today.**

## 2. Latent shapes (proven by code reading; relevant only if revived)

Access model if revived: `/dev/mapper/control` = `make_dev(&dmctl_ops, 0, UID_ROOT,
GID_OPERATOR, 0640, "mapper/control")` (`sys/dev/disk/dm/device-mapper.c:181`), no
`priv_check` in the dispatcher → root and the operator group.

### L1 — missing argc validation → NULL-deref panic (dm_target_snapshot.c:113-154)
`dm_table_init` (`sys/dev/disk/dm/dm_ioctl.c:807-844`) builds `argv` with
`kmalloc(sizeof(*argv) * n, M_DM, M_WAITOK | M_ZERO)` (:824) and fills only `argc` slots;
everything past `argc` stays NULL. `dm_target_snapshot_init` reads `argv[0..3]`
unconditionally. Live targets validate first (`linear:61 if (argc != 2) return EINVAL`).
- argc=0/1 → `dm_pdev_insert(NULL)` → `KKASSERT(dev_name != NULL)` (`dm_pdev.c:121`) panic.
- argc=2 → `strcmp(argv[2], "p")` with NULL → NULL-deref panic. (The `kprintf` at :120-122
  survives NULL: DFly `kvprintf` prints `"(null)"`, `sys/kern/subr_prf.c:753`.)
- argc=3 → `atoi64(argv[3])` with NULL → `*s` deref (`device-mapper.c:584-597`) panic.

### L2 — non-persistent snapshot panics at load (:146,:149)
For argv[2] != "p", `dmp_cow` stays NULL, then `dm_table_add_deps(table_en, dmp_cow)`
(:149) hits `KKASSERT(pdev)` (`sys/dev/disk/dm/dm_table.c:277`) → panic during
`dm_table_load_ioctl`. All live targets pass non-NULL pdevs (checked: crypt/delay/flakey/
linear/striped call sites).

### L3 — kprintf NULL-deref in `dm_target_snapshot_table` (:192-194)
`tsc->tsc_cow_dev` is NULL for non-persistent snapshots (:118,:146), yet the debug kprintf
at :192-194 dereferences `tsc->tsc_cow_dev->name`. `name` is the first member of
`dm_pdev_t` (`sys/dev/disk/dm/dm.h:114`) → literal NULL-page read → panic on
`dmsetup table`. **Doubly unreachable: L2 panics first at load, so a non-persistent
snapshot can never reach status.** (The ksnprintf at :196-199 is guarded by the ternary.)

### L4 — error-path leaks (:140-141)
`dm_pdev_insert(argv[1])` failure returns ENOENT without freeing `tsc` (kmem leak) and
without dropping the `dmp_snap` reference taken at :125 — `dm_table_free_deps` never sees it
(deps not yet added), so the pdev (and its held vnode) is pinned forever on the global list.
Live-twin already filed on flakey: DF-4922.

### L5 — cross-allocator free (heap corruption if revived) (:313 vs dm_ioctl.c:993)
`dm_target_snapshot_orig_table` allocates with `kmem_alloc(prm_len, VM_SUBSYS_DM, KM_NOSLEEP)`
(:313) — the DFly kmem/slab allocator. The core consumer frees every `->table()` string with
`kfree(params, M_DM)` (`sys/dev/disk/dm/dm_ioctl.c:993`, in `dm_table_status`). kmem_alloc
blocks must be freed with `kmem_free`; handing one to `kfree(M_DM)` corrupts the malloc-zone
free lists → latent kernel heap corruption on every `dmsetup table` of a snapshot-origin
device. (Contrast: `dm_target_snapshot_table` correctly uses `dm_alloc_string`, which is
`kmalloc(len, M_DM, ...)` — `sys/dev/disk/dm/device-mapper.c:601-606`.)

### L6 — double-unbusy refcount underflow (:359 vs dm_table.c:157)
`dm_table_destroy` calls `target->destroy(table_en)` and **then itself** calls
`dm_target_unbusy(table_en->target)` (`sys/dev/disk/dm/dm_table.c:150-157`).
`dm_target_snapshot_orig_destroy` adds its own `dm_target_unbusy(table_en->target)` at :359 →
net −2 on a +1 reference → next legitimate unbusy trips
`KKASSERT(target->ref_cnt > 0)` (`sys/dev/disk/dm/dm_target.c:57-59`) → panic; or, with
KKASSERT compiled out, ref_cnt wraps to allow premature module unload → UAF-shaped.
(Note the asymmetry: `dm_target_snapshot_destroy` correctly does *not* unbusy.)

### L7 — never functional
Both strategy handlers return EIO for all I/O (:205-217, :325-337). A 2010 NetBSD-derived
mid-implementation stub (`$NetBSD: dm_target_snapshot.c,v 1.12 2010/01/04`).

## 3. Why no guest run

Both my contract (Info findings → skip PoC) and the nature of the bug (code cannot be built,
loaded, or registered by any supported path — a guest run can only re-demonstrate a compiler
error, as DF-4973 did for dmirror). The structural proof above is stronger than a build
transcript here: not in SUBDIR, not in conf/files, no Makefile, no registration call,
undefined `snprintf` symbol.

## 4. Fix

Delete `sys/dev/disk/dm/snapshot/` (preferred — matches the DF-4973 dmirror resolution), or
if the target is ever revived: validate `argc == 4`, only add cow deps when persistent (or
drop the KKASSERT contract), use `dm_alloc_string` in both table functions, remove the extra
`dm_target_unbusy` in orig_destroy, and add error-path cleanup (`kmem_free(tsc)`,
`dm_pdev_decr(dmp_snap)`).

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
