kfree of uninitialized msg pointer in dm_message_ioctl when DM_MESSAGE_STR absent
Summary
dm_ioctl.c:1006 char *msg; NO initializer. 1028 prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg) only writes *cpp on success. 1053 target->message(table_en, msg) derefs garbage. 1058 kfree(msg, M_TEMP) frees stack garbage pointer. Trigger: operator group issues command=message name=<existing device> sector=0 WITHOUT message key -> target message callback + kfree on garbage. Panic or heap corruption. Fix: msg=NULL + check return value of get_cstring.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1642 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| dm_poc.c | trigger-source | case 1642: message to existing device, no message key | 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; load_zero; ./dm_poc 1642 | 307 B | view raw |
| README.md | readme | kfree-of-stack-garbage mechanism | 2.6 KB | β raw |
| VERDICT.md | verdict | REPRODUCED, uncontrolled-pointer kfree, no reliable escalation | 3.0 KB | β raw |
| build.log | build-log | PoC compile output | 98 B | view raw |
| run.log | run-log | baseline _kfree panic + fix clean return | 569 B | view raw |
| fix_build.log | fix-build-log | patched dm.ko module build output | 1.0 KB | view raw |
| fix_run.log | fix-run-log | patched-module test: EINVAL, no panic | 199 B | view raw |
| panic.txt | panic-signature | Fatal trap 12 in _kfree+0x45 (fault addr 0x2e2e7a4a7054) | 341 B | view raw |
| fix.diff | suggested-fix | init msg=NULL + early EINVAL return on missing message key | 717 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-1642 β kfree of uninitialized msg pointer in dm_message_ioctl
Summary
dm_ioctl.c:1006 declares char *msg; with no initializer (stack garbage).
Line 1028 prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg) only writes
*cpp on success; on failure msg stays as stack garbage. Line 1058
kfree(msg, M_TEMP) then frees whatever garbage pointer was on the stack β either a
page fault in _kfree (most common, observed: fault on 0x2e2e7a4a7054 β ASCII
stack residue) or, if the garbage happens to be a valid slab pointer, silent heap
corruption.
Severity / impact
- Severity filed: High
- Verified impact:
panic(local DoS). Themsgpointer is not attacker-controlled in any reliable way (it is stack residue from the ioctl dispatch path). Most of the time the garbage is an unmapped address β page fault in_kfreeβ panic. This is a valid blocker: the freed pointer is uncontrolled, so there is no reliable exploitation primitive. - Trigger credential: operator group.
- Precondition: admin has loaded
dmKLD + a dm device with a loaded table entry must exist (sofound==1atdm_ioctl.c:1037and the code reacheskfree(msg)).
Reproduce
kldload dm
pw groupmod operator -m <user>
./build.sh
./dm_poc create # create device "pocdev"
./dm_poc load_zero # load a zero target table entry (so found==1)
./run.sh # message command with NO message key
# expected (BUG): Fatal trap 12 in _kfree (fault on garbage addr)
# expected (FIXED): EINVAL, guest stays up
Mechanism (line-accurate)
dm_ioctl.c:1006char *msg;β uninitialized, stack garbage (in the observed run:0x2e2e7a4a7000, ASCII residue).dm_ioctl.c:1022dm_dev_lookupsucceeds (device exists).dm_ioctl.c:1028get_cstring(dm_dict, "message", &msg)β key absent β returns false, msg unchanged.dm_ioctl.c:1034-1038sector==0, table non-empty βfound=1,table_enset.dm_ioctl.c:1051-1053table_en->target->messageβ the zero target has no->messagecallback, so this is skipped.dm_ioctl.c:1058kfree(msg, M_TEMP)β frees the garbage pointer β_kfree+0x45: movl 0x54(%rax),%r13dwhererax = 0x2e2e7a4a7000βFatal trap 12: page fault.
Fix
fix.diff: (1) initialize msg = NULL at declaration; (2) check the return value
of prop_dictionary_get_cstring and return EINVAL early if the message key is
absent (after dm_dev_unbusy). Matches the finding's proposed fix.
Fix validation
Patched dm.ko, re-ran PoC: returns EINVAL (errno 22), no panic, guest stays up.
DF-1642 β VERDICT
Verdict: REPRODUCED (panic / local DoS via kfree of stack garbage)
Root cause
sys/dev/disk/dm/dm_ioctl.c:1006,1028,1058:
char *msg; /* :1006 -- NO init */
...
prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg); /* :1028 */
...
if (found) {
if (table_en->target->message != NULL)
ret = table_en->target->message(table_en, msg); /* :1053 */
}
...
kfree(msg, M_TEMP); /* :1058 */
msg is uninitialized stack garbage. prop_dictionary_get_cstring writes *cpp
only on success; when DM_MESSAGE_STR ("message") is absent from the user dict,
msg stays garbage. kfree(msg) then frees the garbage pointer.
Evidence (baseline, unpatched dm.ko)
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x2e2e7a4a7054 current process = 887 Stopped at _kfree+0x45: movl 0x54(%rax),%r13d db>
The fault address 0x2e2e7a4a7054 is the garbage msg value (0x2e='.',
0x7a='z', 0x70='p' β ASCII stack residue from the ioctl dispatch path) plus the
_kfree internal offset 0x54 (reading slab metadata). Triggered by operator-group
maxx: create + load_zero (so a table_en exists and found==1), then
message with no message key.
Exploit-chain assessment
- Primitive:
kfreeof a stack-garbage pointer (uncontrolled by the attacker). - The freed pointer value is stack residue from the kernel ioctl dispatch path
preceding
dm_message_ioctl. It is not reliably attacker-shaped: the residue depends on kernel build, prior syscalls, and stack layout. In the observed run it was0x2e2e7a4a7000(ASCII dots/letters) β unmapped β page fault. - Even if an attacker could shape the residue to a valid slab pointer,
kfreevalidates the chunk against its slab zone; a type mismatch would corrupt silently (onnoinv) or panic (on GENERIC with INVARIANTS). There is no reliable path touid0. - Valid blocker: the freed pointer is uncontrolled β no reliable write primitive. Impact ceiling = local DoS (panic).
PoC changes
Authored dm_poc.c from scratch. The 1642 case sends command=message with
name=pocdev, sector=0, but omits the message key, leaving msg uninitialized.
A preceding create + load_zero establishes the device + table entry so the code
reaches kfree(msg).
Fix (fix.diff)
-char *msg;
+char *msg = NULL;
...
- prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg);
+ if (prop_dictionary_get_cstring(dm_dict, DM_MESSAGE_STR, &msg) == false) {
+ dmdebug("dm_message_ioctl: missing message string\n");
+ dm_dev_unbusy(dmv);
+ return EINVAL;
+ }
Matches the finding's proposed fix (msg=NULL + check return value of get_cstring).
Fix validation
Patched dm.ko, re-ran PoC:
DF-1642 message(no msg key): rc=-1 errno=22 (Invalid argument) EXIT=0
Guest stayed up. 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_message kfree(garbage msg) -> _kfree fault on stack residue. Operator-group.
No comments yet.