β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4984

dm_target_snapshot.c is unbuildable, unregistered dead scratch code (dmirror DF-4973 class) whose latent memory-safety bugs β€” cross-allocator free, double-unbusy refcount underflow, NULL-deref panics β€” must never be revived; delete it

Summary

Deader than dmirror: no SUBDIR/conf-files entry, no Makefile in the dir, zero symbol refs, no dm_target_insert, and it cannot even LINK (bare snprintf :319 - the kernel has only ksnprintf). Latent catalogue: cross-allocator free (kmem_alloc ptr kfree'd M_DM), double-unbusy refcount underflow, NULL-dep KKASSERT at load masking a NULL-deref kprintf, the :140 leak. Structural dead-code proof in pack. Delete-the-directory fix - completes the dm family (every dm file now audited).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4984 Β· 5 files
FileTypeDescriptionSize
README.md β€” 4.3 KB ↓ raw
VERDICT.md β€” 6.6 KB ↓ raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 1021 B view raw
deadcode_proof.log β€” 1.6 KB view raw

DF-4984 β€” dm_target_snapshot.c is unbuildable, unregistered dead scratch code β€” latent memory-safety bug catalog

File: sys/dev/disk/dm/snapshot/dm_target_snapshot.c (364 LOC) Severity: Info (dead code β€” zero attack surface today) Β· Confidence: certain Pass 2 (GLM 5.3) Β· dmirror precedent: DF-4973 Verdict: untested (Info hardening/dead-code catalog β€” no guest PoC required; dead-code proof is structural and host-side, see VERDICT.md)

Why it is dead (three independent proofs)

  1. Not in any build. sys/dev/disk/dm/Makefile SUBDIR = linear striped crypt delay flakey; sys/conf/files:48-60 lists the dm core + those five targets β€” no snapshot/ entry. The directory sys/dev/disk/dm/snapshot/ contains only the .c file β€” no Makefile, so it cannot even be built as a kmod by hand without adding files.
  2. Zero references. No file in sys/ references dm_target_snapshot_init, dm_target_snapshot_orig_init, or any other symbol from this file (grep over the whole tree).
  3. No self-registration. Live targets register via dm_target_alloc("linear") + dm_target_insert(dmt) (linear: dm_target_linear.c:176,186; flakey: :411,421). This file contains no dm_target_alloc/dm_target_insert/module machinery at all β€” even if compiled and kldloaded it would register nothing.

Additionally non-compiling: line 319 calls bare snprintf(), which does not exist in the DFly kernel (only ksnprintf, sys/sys/systm.h:222; no #define snprintf anywhere in sys/). It is the only dm file using it β†’ undefined symbol at link, exactly the dmirror DF-4973 class.

Latent bug catalog (must be fixed if the file is ever revived)

Reachability if the file were wired in: any member of group operator or root (/dev/mapper/control is UID_ROOT, GID_OPERATOR, 0640, device-mapper.c:181; no priv_check in the ioctl path β€” the established DF-1959 threat model).

# Shape Where Consequence
L1 No argc validation in _init; argv slots beyond argc are NULL (dm_ioctl.c:824 kmalloc M_ZERO) :125/:136/:140/:143 strcmp(argv[2],"p") / atoi64(argv[3]) (device-mapper.c:584 derefs *s) / dm_pdev_insert(NULL) (KKASSERT dm_pdev.c:121) β†’ NULL-deref panic on short table line
L2 Non-persistent snapshot passes dmp_cow == NULL to dm_table_add_deps :146,:149 β†’ KKASSERT(pdev) dm_table.c:277 Panic at table load for "n" flag β€” masks L3
L3 dm_target_snapshot_table kprintf derefs tsc->tsc_cow_dev->name unconditionally; name is at offset 0 (dm.h:114) so NULL+0 :192-194 NULL-deref panic on dmsetup table of a non-persistent snapshot (doubly unreachable: L2 fires first)
L4 Error path returns without freeing tsc and without dropping the dmp_snap pdev reference :140-141 kmem leak + permanent pdev/vnode pin per failed load (live-twin: DF-4922 on flakey)
L5 Cross-allocator free: dm_target_snapshot_orig_table returns a kmem_alloc(..., VM_SUBSYS_DM, ...) pointer while the core frees it with kfree(params, M_DM) :313 vs dm_ioctl.c:993 Freeing a kmem-slab pointer through the malloc zone β†’ heap corruption (latent memcorrupt)
L6 Double-unbusy: dm_target_snapshot_orig_destroy calls dm_target_unbusy(table_en->target) while the core also unbussies every target in dm_table_destroy :359 vs dm_table.c:157 refcount underflow β†’ KKASSERT(target->ref_cnt > 0) (dm_target.c:58) panic on next unbusy
L7 Strategy handlers are EIO stubs :205-217, :325-337 The target was never functional β€” mid-implementation scratch

Delete the directory (dmirror precedent, DF-4973), or complete the target (argc validation, persistent-only cow deps, dm_alloc_string for both table functions, drop the extra unbusy, error-path cleanup) before ever wiring it into SUBDIR/conf/files.

Reproduce (dead-code proof, host-side)

grep -rn snapshot sys/dev/disk/dm/Makefile sys/conf/files          # nothing
ls sys/dev/disk/dm/snapshot/                                       # only the .c file
grep -rn dm_target_snapshot_init sys/ --include=*.c                # only the defining file
grep -rn "define snprintf" sys/sys/ sys/kern/                      # nothing -> :319 cannot link
VERDICT.md
↓ download raw

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

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable - dead code; nothing to build a fix kernel against (file is not compiled)

per-fix-DF-4984

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4984/VERDICT.md sections 1-2 (three independent dead-code proofs + seven latent shapes, each with path:line); README.md reproduction greps

PoC changes

not applicable - no PoC; static dead-code proof (stronger than dmirror's build transcript: file is not merely non-compiling, it is unbuildable and unregistered by construction)

Verified recommended fix

Delete sys/dev/disk/dm/snapshot/ (or, if revived: argc==4 validation, persistent-only cow deps, dm_alloc_string in both table funcs, remove extra dm_target_unbusy in orig_destroy, error-path kmem_free+dm_pdev_decr).

Verdict

Dead scratch code with zero attack surface: absent from dm Makefile SUBDIR and sys/conf/files, no Makefile in snapshot/, zero references to any of its symbols in sys/, no dm_target_alloc/dm_target_insert self-registration, and it cannot even link if force-built (bare snprintf at :319; DFly kernel only has ksnprintf, systm.h:222). Latent bugs catalogued so they are never revived: (L1) init reads argv[0..3] with no argc check while dm_table_init M_ZERO-fills argv so short table lines put NULL into strcmp/atoi64/dm_pdev_insert -> NULL-deref panic; (L2) non-persistent snapshot passes NULL pdev to dm_table_add_deps -> KKASSERT(dm_table.c:277) panic at load, masking (L3) the unconditional tsc_cow_dev->name kprintf deref (:192-194, name at dm.h:114 offset 0); (L4) error path :140-141 leaks tsc and pins the snap pdev vnode (flakey twin DF-4922); (L5) dm_target_snapshot_orig_table returns a kmem_alloc(VM_SUBSYS_DM) pointer which the core kfrees with M_DM (dm_ioctl.c:993) -> cross-allocator heap corruption; (L6) orig_destroy's extra dm_target_unbusy (:359) plus the core's unbusy (dm_table.c:157) double-decrements -> KKASSERT panic / unload UAF; (L7) strategy handlers are EIO stubs - the target was never functional. Delete-the-directory fix (dmirror DF-4973 precedent).