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)
PoC verification
Evidence pack
findings/poc/DF-4984 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
- Not in any build.
sys/dev/disk/dm/MakefileSUBDIR =linear striped crypt delay flakey;sys/conf/files:48-60lists the dm core + those five targets β nosnapshot/entry. The directorysys/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. - Zero references. No file in
sys/referencesdm_target_snapshot_init,dm_target_snapshot_orig_init, or any other symbol from this file (grep over the whole tree). - 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 nodm_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 |
Recommended fix
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
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)
- 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.) - Zero external references.
grep -rn 'dm_target_snapshot' sys/matches only the file itself. Nothing declares, registers, or calls these functions. - 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_testablenot_testable - dead code; nothing to build a fix kernel against (file is not compiled)
Confirmed kernel references
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:113-154
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:140-141
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:146-149
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:192-194
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:313
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:319
- sys/dev/disk/dm/snapshot/dm_target_snapshot.c:359
- sys/dev/disk/dm/Makefile:7
- sys/conf/files:48-60
- sys/dev/disk/dm/dm_ioctl.c:824
- sys/dev/disk/dm/dm_ioctl.c:993
- sys/dev/disk/dm/dm_table.c:157
- sys/dev/disk/dm/dm_table.c:277
- sys/dev/disk/dm/dm_target.c:57-59
- sys/dev/disk/dm/dm_pdev.c:121
- sys/dev/disk/dm/device-mapper.c:181
- sys/dev/disk/dm/device-mapper.c:584-597
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).
No comments yet.