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

Use of uninitialized heap memory in dm_target_crypt_destroy after failed init

Summary

dm_target_crypt.c:489 priv=kmalloc NO M_ZERO. Published via dm_table_init_target at 513. Multiple reachable error paths (533 unsupported iv_mode, 543 ivgen ctor fail, 521 hex2key fail, 552 newsession fail) goto notsup with ivgen/ivgen_priv/crypto_session/status_str/read_mpipe/write_mpipe UNINITIALIZED heap garbage. dm_table_load_ioctl (dm_ioctl.c:783-786) calls dm_table_destroy -> dm_target_crypt_destroy which derefs: dmtc_destroy_mpipe (620) on garbage mpipe -> KKASSERT panic or wild function ptr call via mpipe->deconstruct (kern_mpipe.c:147-148). status_str (628), ivgen (633), crypto_session (637) garbage derefs. Trigger: operator group DM_TABLE_LOAD with unsupported iv_mode (aes-cbc-foo). Fix: kmalloc M_ZERO.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1634 Β· 14 files
FileTypeDescriptionSize
dm_poc.c trigger-source case 1634: reload type=crypt with unsupported iv_mode=BOGUS 10.5 KB view raw
build.sh build-script cc -o dm_poc dm_poc.c 117 B view raw
run.sh run-script ./dm_poc create; sudo ./dm_poc 1634 462 B view raw
README.md readme uninit-heap deref mechanism + backing-device credential note 4.3 KB ↓ raw
VERDICT.md verdict REPRODUCED, uninit heap deref neutralized by KKASSERT on GENERIC 4.4 KB ↓ raw
build.log build-log PoC compile output 98 B view raw
run.log run-log baseline mpipe_done panic + fix clean ENOTSUP 883 B view raw
fix_build.log fix-build-log patched dm_target_crypt.ko (M_ZERO) build output 771 B view raw
fix_run.log fix-run-log patched-module test: ENOTSUP, no panic 308 B view raw
panic.txt panic-signature lwkt_reltokref assertion panic via mpipe_done via dm_target_crypt_destroy 588 B view raw
fix.diff suggested-fix add M_ZERO flag to priv kmalloc 513 B view raw
env.txt environment uname, cc, module list, test user 809 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme uninit-heap deref mechanism + backing-device credential note
↓ download raw

DF-1634 β€” Uninitialized heap memory in dm_target_crypt_destroy after failed init

Summary

dm_target_crypt.c:489 priv = kmalloc(sizeof(dm_target_crypt_config_t), M_DMCRYPT, M_WAITOK) allocates without M_ZERO, so all fields are heap garbage. Line 513 dm_table_init_target(table_en, priv) publishes priv into table_en->target_config BEFORE any of the later error paths. When init fails (e.g. unsupported iv_mode at line 530-533 β†’ goto notsup), the caller dm_table_load_ioctl (dm_ioctl.c:783-785) calls dm_table_destroy β†’ dm_target_crypt_destroy, which dereferences the uninitialized fields:

  • dm_target_crypt.c:620 dmtc_destroy_mpipe(priv) β†’ mpipe_done(&priv->read_mpipe) on garbage mpipe β†’ KKASSERT / lwkt-token panic in kern_mpipe.c / lwkt_token.c:458
  • dm_target_crypt.c:628 priv->status_str (garbage) conditional
  • dm_target_crypt.c:633 priv->ivgen (garbage) conditional + dtor call
  • dm_target_crypt.c:637 cryptoapi_cipher_freesession(priv->crypto_session) (garbage)

Severity / impact

  • Severity filed: High
  • Verified impact: panic (local DoS). The first dangerous deref (mpipe_done on the garbage mpipe) trips a KKASSERT in the lwkt token layer (panic: assertion "count & TOK_COUNTMASK" failed in _lwkt_reltokref) before any useful corruption can land.
  • Primitive class: uninitialized-heap deref (effectively a read/use of attacker-uncontrolled heap residue). The priv chunk is M_DMCRYPT-typed; the residue depends on prior allocations in that zone and is not reliably shaped by the attacker from userspace. On GENERIC the KKASSERT fires immediately.
  • Trigger credential: operator group for /dev/mapper/control, plus the crypt init requires a writable backing block device (vn_open FREAD|FWRITE in dm_pdev_insert). On default 0640 root:operator device perms, only root can supply the backing device; an operator-group trigger requires the admin to have chmod'd a block device group-writable (a realistic dm-crypt deployment step). The bug itself is in the kernel code path regardless of caller; confirmed here by triggering as root.
  • Precondition: admin has loaded dm + dm_target_crypt KLD modules.

Reproduce

kldload dm
kldload dm_target_crypt
# backing device must be writable by the caller; on default perms, run as root
./build.sh
./dm_poc create
./run.sh                 # reload type=crypt with iv_mode=BOGUS
# expected (BUG): panic in mpipe_done via dm_target_crypt_destroy
# expected (FIXED): ENOTSUP (45), guest stays up

Mechanism (line-accurate)

  1. dm_target_crypt.c:489 priv = kmalloc(..., M_DMCRYPT, M_WAITOK) β€” NO M_ZERO.
  2. dm_target_crypt.c:492 dm_pdev_insert(dev) succeeds (backing device valid).
  3. dm_target_crypt.c:498 dmtc_find_crypto_cipher("aes","cbc",256) β†’ valid cipher.
  4. dm_target_crypt.c:513 dm_table_init_target(table_en, priv) β€” publishes priv.
  5. dm_target_crypt.c:515 hex2key succeeds.
  6. dm_target_crypt.c:530-533 iv_mode="BOGUS" not in ivgens[] β†’ goto notsup. At this point: ivgen, ivgen_priv, crypto_session, status_str, read_mpipe, write_mpipe are all uninitialized garbage.
  7. dm_target_crypt.c:584-587 notsup: frees status_str, returns ENOTSUP.
  8. dm_ioctl.c:783 dm_table_init returns ENOTSUP β†’ error path.
  9. dm_ioctl.c:785 dm_table_destroy β†’ walks table entries β†’ for the crypt entry, calls target->destroy(table_en) = dm_target_crypt_destroy.
  10. dm_target_crypt.c:620 dmtc_destroy_mpipe(priv) β†’ mpipe_done(&priv->read_mpipe) β†’ lwkt_gettoken(&mpipe->token) on garbage token β†’ panic: assertion "count & TOK_COUNTMASK" failed in _lwkt_reltokref.

Fix

fix.diff: add M_ZERO to the kmalloc at line 489. With zero initialization, all pointer fields are NULL and the mpipe structs are zeroed; mpipe_done is explicitly documented safe on zeroed mpipe (kern_mpipe.c:118-120), and the NULL checks in dm_target_crypt_destroy (lines 616, 628, 633) skip the garbage derefs. Matches the finding's proposed fix.

Fix validation

Patched dm_target_crypt.ko (M_ZERO fix), re-ran PoC as root: returns ENOTSUP (errno 45), dmesg shows the same iv_mode='BOGUS' unsupported + ENOTSUP messages but no panic, guest stays up. The dm_table_destroy β†’ destroy path now safely no-ops on the zeroed fields.

VERDICT.md verdict REPRODUCED, uninit heap deref neutralized by KKASSERT on GENERIC
↓ download raw

DF-1634 β€” VERDICT

Verdict: REPRODUCED (panic / local DoS via uninitialized heap deref)

Root cause

sys/dev/disk/dm/crypt/dm_target_crypt.c:489:

priv = kmalloc(sizeof(dm_target_crypt_config_t), M_DMCRYPT, M_WAITOK);  /* NO M_ZERO */

The priv structure is published into table_en->target_config at line 513 (dm_table_init_target) before any of the error paths at lines 521, 533, 543, 552 (goto notsup). When init fails, dm_table_load_ioctl (dm_ioctl.c:783-785) calls dm_table_destroy β†’ dm_target_crypt_destroy, which dereferences the uninitialized fields: - :620 dmtc_destroy_mpipe(priv) β†’ mpipe_done(&priv->read_mpipe) on garbage mpipe - :628 if (priv->status_str) β€” garbage conditional - :633 if ((priv->ivgen) && (priv->ivgen->dtor != NULL)) β€” garbage deref + call - :637 cryptoapi_cipher_freesession(priv->crypto_session) β€” garbage arg

Evidence (baseline, unpatched dm_target_crypt.ko)

dm_target_crypt: using crypto_cipher: AES-CBC (Rijndael-128) in software
dm_target_crypt: iv_mode='BOGUS' unsupported
dm_target_crypt: ENOTSUP
panic: assertion "count & TOK_COUNTMASK" failed in _lwkt_reltokref at /usr/src/sys/kern/lwkt_token.c:458
cpuid = 2
mpipe_done() at mpipe_done+0x37 0xffffffff8065c0b7
dm_target_crypt_destroy() at dm_target_crypt_destroy+0x29 0xffffffff826a2129
Stopped at      Debugger+0x7c:  movb    $0,0xbdaf09(%rip)
db>

The mpipe_done call on the garbage read_mpipe token trips the lwkt-token assertion before the kern_mpipe.c:147 free_count==total_count KKASSERT can fire. Triggered via reload type=crypt params="aes-cbc-BOGUS <256bit-key> 0 /dev/vbd0 0" (unsupported iv_mode=BOGUS).

Note on trigger credential: the dm_pdev_insert path (dm_pdev.c:78) opens the backing device with vn_open(FREAD|FWRITE) using the calling process's credentials. On default 0640 root:operator block-device perms, operator-group users get EPERM on FWRITE to block devices (driver-level restriction), so the unprivileged trigger requires the admin to have made a block device group-writable β€” a realistic dm-crypt deployment step. The bug itself is confirmed in the kernel code path regardless of caller; reproduced here as root (the only practical way to supply a writable backing device on this guest without a custom chmod policy).

Exploit-chain assessment

  • Primitive: use of uninitialized heap memory (M_DMCRYPT zone) as pointers (mpipe token, function pointer ivgen->dtor, crypto_session, status_str).
  • The priv chunk's residue depends on prior M_DMCRYPT allocations and is not reliably shaped from userspace (the zone is dedicated to dm-crypt configs; no cross-type spray is readily available to an operator-group user).
  • On GENERIC the lwkt-token KKASSERT fires on the first dangerous deref (mpipe_done β†’ garbage token), panicking before any function-pointer call (ivgen->dtor) or cryptoapi_cipher_freesession can land.
  • Outcome: panic on default GENERIC. No uid0 path on default kernel; the uninitialized-heap-deref primitive is real but neutralized by the early KKASSERT and the difficulty of shaping M_DMCRYPT slab residue from operator-group userspace.

PoC changes

Authored dm_poc.c from scratch. The 1634 case sends reload with type=crypt and params="aes-cbc-BOGUS <64-hex-char-key> 0 /dev/vbd0 0", which passes dm_pdev_insert and dmtc_find_crypto_cipher but fails at the ivgens[] lookup (BOGUS not recognized) β†’ goto notsup after priv is published.

Fix (fix.diff)

-priv = kmalloc(sizeof(dm_target_crypt_config_t), M_DMCRYPT, M_WAITOK);
+priv = kmalloc(sizeof(dm_target_crypt_config_t), M_DMCRYPT, M_WAITOK | M_ZERO);

Matches the finding's proposed fix (kmalloc M_ZERO). With zero initialization, all pointer fields are NULL and mpipe structs are zeroed; mpipe_done is documented safe on zeroed mpipe (kern_mpipe.c:118-120), and the NULL-conditional checks in dm_target_crypt_destroy (:616, :628, :633) skip the garbage derefs.

Fix validation

Patched dm_target_crypt.ko (M_ZERO fix), re-ran PoC as root:

DF-1634 crypt(unsupported iv_mode): rc=-1 errno=45 (Operation not supported)
EXIT=0

dmesg still shows the expected iv_mode='BOGUS' unsupported + ENOTSUP messages, but no panic, guest stayed up. The dm_table_destroy β†’ destroy path now safely no-ops on the zeroed fields. fix_status = fixed.

Fix verification

fixed

validated

baseline panic; patched returns EINVAL/ENOTSUP, guest up
↓ fix.diffdm.ko/dm_target_crypt.ko module rebuild atop 6.5-DEVELOPMENT #0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live panic). dm_target_crypt kmalloc no M_ZERO -> destroy derefs garbage mpipe token -> KKASSERT panic. Operator-group trigger.