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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1642 Β· 14 files
FileTypeDescriptionSize
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
README.md readme kfree-of-stack-garbage mechanism
↓ download 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). The msg pointer 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 dm KLD + a dm device with a loaded table entry must exist (so found==1 at dm_ioctl.c:1037 and the code reaches kfree(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)

  1. dm_ioctl.c:1006 char *msg; β€” uninitialized, stack garbage (in the observed run: 0x2e2e7a4a7000, ASCII residue).
  2. dm_ioctl.c:1022 dm_dev_lookup succeeds (device exists).
  3. dm_ioctl.c:1028 get_cstring(dm_dict, "message", &msg) β€” key absent β†’ returns false, msg unchanged.
  4. dm_ioctl.c:1034-1038 sector==0, table non-empty β†’ found=1, table_en set.
  5. dm_ioctl.c:1051-1053 table_en->target->message β€” the zero target has no ->message callback, so this is skipped.
  6. dm_ioctl.c:1058 kfree(msg, M_TEMP) β€” frees the garbage pointer β†’ _kfree+0x45: movl 0x54(%rax),%r13d where rax = 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.

VERDICT.md verdict REPRODUCED, uncontrolled-pointer kfree, no reliable escalation
↓ download raw

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: kfree of 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 was 0x2e2e7a4a7000 (ASCII dots/letters) β€” unmapped β†’ page fault.
  • Even if an attacker could shape the residue to a valid slab pointer, kfree validates the chunk against its slab zone; a type mismatch would corrupt silently (on noinv) or panic (on GENERIC with INVARIANTS). There is no reliable path to uid0.
  • 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

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_message kfree(garbage msg) -> _kfree fault on stack residue. Operator-group.