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

Unconditional memset on FREEBLKS bios writes to unmapped KVA causing kernel panic

Summary

dm_target_zero_strategy() calls memset(bp->b_data 0 bp->b_bcount) for ALL bio commands including BUF_CMD_FREEBLKS. For FREEBLKS buffer from getnewbuf() has b_data pointing to valid-but-unmapped KVA (pmap entries stripped by allocbuf(bp,0) during buffer recycling b_data reset to b_kvabase vfs_bio.c:2196 no pages mapped). memset writes b_bcount bytes to unmapped KVA causing fatal kernel page fault panic. Reachable when FFS mounted with MNT_TRIM on dm-zero device: any unprivileged user deleting file triggers ffs_blkfree() which issues FREEBLKS bio reaching zero target unchanged. Call chain: ffs_blkfree() getnewbuf(0 0 0 1) sets b_bcount=size no KVA remap vn_strategy devfs_spec_strategy (FREEBLKS shortcut b_cmd!=READ&&b_cmd!=WRITE) dev_dstrategy_chain dmstrategy non-bypass nestiobuf_add copies dangling b_data dm_target_zero_strategy memset panic. Linux dm-zero only calls zero_fill_bio for REQ_OP_READ confirming correct behavior. Attacker: any unprivileged local user with write+delete on FFS filesystem mounted trim on dm-zero. Impact: immediate kernel panic system-wide DoS. No privilege escalation or info leak.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2215 Β· 10 files
FileTypeDescriptionSize
poc.c trigger-source libprop PoC: creates dm-zero, shows mount -o trim fails (D_CANFREE absent -> FREEBLKS unreachable) 8.4 KB view raw
build.sh build-script cc -O2 -o poc poc.c -lprop 89 B view raw
run.sh run-script kldload dm; ./poc 236 B view raw
build.log build-log successful build output 86 B view raw
run.log run-log PoC run showing mount -o trim failure 1.4 KB view raw
fix_run.log fix-run-log fixed dm.ko: same behavior (path still unreachable, fix is defense-in-depth) 603 B view raw
fix.diff suggested-fix guard memset against non-READ/WRITE bios in dm_target_zero_strategy 728 B view raw
VERDICT.md verdict full analysis: latent bug, unreachability proof, defense-in-depth fix 2.9 KB ↓ raw
README.md readme build/run instructions 974 B ↓ raw
env.txt environment uname, cc version, dm_ops flags 430 B view raw
README.md readme build/run instructions
↓ download raw

DF-2215: dm_target_zero FREEBLKS memset (LATENT)

Build & Run

Setup (as root)

kldload dm
cc -O2 -o poc poc.c -lprop

Run (as root β€” control dev is 0640 root:operator)

./poc

Expected Output

The PoC creates a dm-zero device, then attempts newfs + mount -o trim. The mount fails:

Device:/dev/mapper/df2215dev does not support the TRIM command

This proves the FREEBLKS path is unreachable: dm_ops lacks D_CANFREE, so the dm device never claims TRIM support. Without MNT_TRIM, ffs_blkfree never generates FREEBLKS bios, and VOP_FREEBLKS returns early for dm devices.

The code-level bug IS real: dm_target_zero_strategy (dm_target_zero.c:43) calls memset(bp->b_data, ...) unconditionally β€” if a FREEBLKS bio ever reached this function, bp->b_data would be NULL β†’ panic. But on this kernel, FREEBLKS bios never reach a dm device. This is a latent / defense-in-depth hardening gap. See fix.diff for the guard.

VERDICT.md verdict full analysis: latent bug, unreachability proof, defense-in-depth fix
↓ download raw

DF-2215 VERDICT β€” dm_target_zero unconditional memset on FREEBLKS bios

Verdict: LATENT / UNREACHABLE on this kernel (code-level bug is REAL)

Summary

dm_target_zero_strategy (sys/dev/disk/dm/dm_target_zero.c:43) unconditionally calls memset(bp->b_data, 0, bp->b_bcount) for every bio, including BUF_CMD_FREEBLKS (discard/TRIM). For a FREEBLKS bio, bp->b_data is NULL (there is no data buffer for a discard operation β€” only an offset and length). This would cause a NULL-deref page fault.

However, on this kernel the FREEBLKS path through dm is unreachable: dm_ops (device-mapper.c:73) does not set D_CANFREE, so the dm device never claims TRIM support. Both paths that generate FREEBLKS bios to the dm device are blocked:

  1. VOP_FREEBLKS β†’ devfs_spec_freeblks (devfs_vnops.c:2036): checks SI_CANFREE (propagated from D_CANFREE) and returns early for dm devices.
  2. ffs_blkfree TRIM path (ffs_alloc.c:1673): only taken when MNT_TRIM is set, but mount -o trim FAILS for dm devices: "Device:/dev/mapper/xxx does not support the TRIM command".

The code-level bug is REAL but LATENT. It would become reachable if a future commit adds D_CANFREE to dm_ops or if a dm target propagates FREEBLKS bios from a TRIM-capable backing device.

Mechanism (why the code IS buggy)

dm_target_zero_strategy(dm_table_entry_t *table_en, struct buf *bp)
{
    memset(bp->b_data, 0, bp->b_bcount);   // <-- NULL deref for FREEBLKS
    bp->b_resid = 0;
    biodone(&bp->b_bio1);
    return 0;
}

If a BUF_CMD_FREEBLKS bio reached this function: - The master buf from getpbuf/getnewbuf has b_data == NULL - nestiobuf_add (vfs_bio.c:4581) propagates: bp->b_data = mbp->b_data + offset = NULL + offset - memset(NULL+offset, 0, size) β†’ page fault β†’ kernel panic

dmstrategy (device-mapper.c:385) explicitly routes FREEBLKS through the table strategy (it is NOT bypass), so if FREEBLKS were ever generated for a dm device, it WOULD reach the zero target.

Proof of unreachability (from PoC run)

Device:/dev/mapper/df2215dev does not support the TRIM command

This is because dm_ops (device-mapper.c:73) has flags D_DISK | D_MPSAFE but NOT D_CANFREE.

Exploit chain

N/A β€” latent bug, unreachable on this kernel. No panic, no escalation.

Fix (defense-in-depth)

Guard the memset in dm_target_zero_strategy against non-READ/WRITE bios. FREEBLKS bios should complete as no-ops (the zero target has no backing store to discard). See fix.diff.

Fix validation

  • fix_status: not_testable β€” the FREEBLKS path is unreachable on this kernel (dm_ops lacks D_CANFREE), so there is no runtime behavior to compare.
  • Validated: fix.diff applies cleanly, compiles without errors (-Werror), dm.ko installs and loads, dm device operations (create/reload/resume) all work normally. The guard does not affect normal READ/WRITE operations.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: FREEBLKS path unreachable on this kernel (dm_ops lacks D_CANFREE), so no runtime behavior to compare. Validated: fix.diff applies cleanly, compiles without errors (-Werror), dm.ko installs and loads, dm device operations (create/reload/resume) all work normally. Guard does not affect normal READ/WRITE operations.

Fix compiles: cc -O2 -pipe ... -Werror ... -c dm_target_zero.c (no errors). dm.ko builds, installs, loads. PoC runs identically on fixed dm.ko: mount -o trim still fails (path unreachable). No runtime regression.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 with fixed dm.ko (SHA256 3ed52cca2f6b48a5993c1237d31d42c63f03d9b662410f77b2118ca62bbcdc92)

Confirmed kernel references

Detail

Exploit chain

none β€” non-corruption class (latent/unreachable code path). Bug real at code level but cannot be triggered on this kernel. No panic, no leak, no escalation. memset would write to NULL if a FREEBLKS bio reached the function, but FREEBLKS bios never reach dm devices.

Evidence (decisive lines)

mount -o trim output: 'Device:/dev/mapper/df2215dev does not support the TRIM command' | dm_ops flags at device-mapper.c:73: D_DISK | D_MPSAFE (NO D_CANFREE) | devfs_spec_freeblks (devfs_vnops.c:2036): if ((si_flags & SI_CANFREE) == 0) return 0; | ffs_blkfree (ffs_alloc.c:1673): if (!(mnt_flag & MNT_TRIM)) -> non-TRIM path, no FREEBLKS bio generated.

PoC changes

Replaced trigger.c (dmsetup + mount approach that won't work on zero target) with poc.c using libprop NETBSD_DM_IOCTL. PoC creates dm-zero device, demonstrates unreachability by showing mount -o trim fails (D_CANFREE absent). Authored fix.diff as defense-in-depth: guard memset against non-READ/WRITE bios.

Verified recommended fix

In dm_target_zero.c dm_target_zero_strategy, guard the memset: add 'if (bp->b_cmd != BUF_CMD_READ && bp->b_cmd != BUF_CMD_WRITE) { bp->b_resid = 0; biodone(&bp->b_bio1); return 0; }' before memset. FREEBLKS bios should complete as no-ops (zero target has no backing store to discard). Defense-in-depth β€” path unreachable on current kernels but would crash if D_CANFREE ever added to dm_ops.

Verdict

NOT REPRODUCED (LATENT / UNREACHABLE). The code-level bug is REAL: dm_target_zero_strategy (dm_target_zero.c:43) calls memset(bp->b_data, 0, bp->b_bcount) unconditionally, including for BUF_CMD_FREEBLKS where bp->b_data is NULL. However, the FREEBLKS path is UNREACHABLE on this kernel because dm_ops (device-mapper.c:73) lacks D_CANFREE. This blocks both FREEBLKS generation paths: (1) VOP_FREEBLKS -> devfs_spec_freeblks checks SI_CANFREE and returns early; (2) mount -o trim fails ('does not support the TRIM command') so MNT_TRIM never set and ffs_blkfree never takes TRIM path. Traced all paths line-by-line. Bug is latent β€” would become exploitable if a future commit adds D_CANFREE to dm_ops.