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)
PoC verification
Evidence pack
findings/poc/DF-1634 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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:620dmtc_destroy_mpipe(priv)βmpipe_done(&priv->read_mpipe)on garbage mpipe β KKASSERT / lwkt-token panic inkern_mpipe.c/lwkt_token.c:458dm_target_crypt.c:628priv->status_str(garbage) conditionaldm_target_crypt.c:633priv->ivgen(garbage) conditional + dtor calldm_target_crypt.c:637cryptoapi_cipher_freesession(priv->crypto_session)(garbage)
Severity / impact
- Severity filed: High
- Verified impact:
panic(local DoS). The first dangerous deref (mpipe_doneon 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
privchunk isM_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|FWRITEindm_pdev_insert). On default0640 root:operatordevice 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_cryptKLD 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)
dm_target_crypt.c:489priv = kmalloc(..., M_DMCRYPT, M_WAITOK)β NO M_ZERO.dm_target_crypt.c:492dm_pdev_insert(dev)succeeds (backing device valid).dm_target_crypt.c:498dmtc_find_crypto_cipher("aes","cbc",256)β valid cipher.dm_target_crypt.c:513dm_table_init_target(table_en, priv)β publishes priv.dm_target_crypt.c:515hex2keysucceeds.dm_target_crypt.c:530-533iv_mode="BOGUS"not inivgens[]βgoto notsup. At this point:ivgen,ivgen_priv,crypto_session,status_str,read_mpipe,write_mpipeare all uninitialized garbage.dm_target_crypt.c:584-587notsup:frees status_str, returns ENOTSUP.dm_ioctl.c:783dm_table_initreturns ENOTSUP β error path.dm_ioctl.c:785dm_table_destroyβ walks table entries β for the crypt entry, callstarget->destroy(table_en)=dm_target_crypt_destroy.dm_target_crypt.c:620dmtc_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.
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_DMCRYPTzone) as pointers (mpipe token, function pointerivgen->dtor,crypto_session,status_str). - The
privchunk's residue depends on priorM_DMCRYPTallocations 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) orcryptoapi_cipher_freesessioncan land. - Outcome:
panicon default GENERIC. Nouid0path on default kernel; the uninitialized-heap-deref primitive is real but neutralized by the early KKASSERT and the difficulty of shapingM_DMCRYPTslab 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
fixedvalidated
baseline panic; patched returns EINVAL/ENOTSUP, guest up
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.
No comments yet.