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

mpr_user_pass_thru leaks the user-sized cm->cm_data buffer and its dmamap mapping across the timeout/reinit path

Summary

On pass-thru timeout with reinit-realloc, mpr_wait_command sets *cmp=NULL; the error branch then skips kfree(cm->cm_data) while mpr_free_command only NULLs cm_data - the kmalloc buffer and its loaded dmamap leak per timed-out pass-thru (contrast mpr_user_command which frees unconditionally). Slow unbounded kmem + dmamap exhaustion DoS. Reclaim fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4010 Β· 2 files
FileTypeDescriptionSize
README.md readme path:line trace + fix sketch 2.7 KB ↓ raw
verdict.json verdict machine verdict 2.2 KB view raw
README.md readme path:line trace + fix sketch
↓ download raw

DF-4010 β€” mpr_user_pass_thru leaks cm->cm_data (and its dmamap mapping) across the reinit path

What

When a pass-thru command times out, mpr_wait_command() (sys/dev/raid/mpr/ mpr.c:3604-3618) performs an IOC reinit; if the reinit reallocated the queues it sets *cmp = NULL to tell the caller the command was freed internally. mpr_user_pass_thru() then takes the error branch:

err = mpr_wait_command(sc, &cm, 30, CAN_SLEEP);   /* mpr_user.c:1050 */
if (err || (cm == NULL)) {
        ...
        goto RetFree;                             /* mpr_user.c:1052-1056 */
}
...
RetFree:
if (cm != NULL) {
        if (cm->cm_data)
                kfree(cm->cm_data, M_MPRUSER);    /* mpr_user.c:1149-1151 */
        mpr_free_command(sc, cm);
}

With cm == NULL, the kfree(cm->cm_data) is skipped β€” but the reinit path that freed the command does not free cm->cm_data either: mpr_free_command() (mprvar.h:568-611) merely sets cm->cm_data = NULL without freeing. The user-controlled buffer (kmalloc(MAX(DataSize, DataOutSize)), mpr_user.c:904-909 β€” DataSize/DataOutSize are raw user u32s) is therefore leaked, together with any bus_dmamap mapping loaded onto it (mpr_map_command β†’ bus_dmamap_load during execution; the unload at mpr_user.c:1067 only runs on the success path).

Impact

Kernel-memory + dmamap resource leak from the operator-group ioctl surface. Each cycle costs a 30 s timeout plus a full IOC reset, so exhaustion is slow but unbounded; with large DataSize values (e.g. 64 MB) dozens of iterations materially consume kmem and dmamap segments, degrading the driver for all users (resource-exhaustion DoS). No memory-corruption shape found on this path (contrast mpr_user_command(), which frees its buf unconditionally at mpr_user.c:734-735 β€” the pass-thru path is the one that leaks).

Verification posture

Source-level certain; live trigger needs an mpr controller whose commands time out (30 s) followed by a reallocation-triggering reinit β€” no LSI SAS hardware on the audit guest, so not live-reproduced (pack documents the path:line trace; no replication needed for a Low resource-leak finding).

Fix sketch

--- a/sys/dev/raid/mpr/mpr_user.c
+++ b/sys/dev/raid/mpr/mpr_user.c
@@
    if (err || (cm == NULL)) {
        mpr_printf(sc, "%s: invalid request: error %d\n", __func__,
            err);
+       if ((cm == NULL) && (data != NULL)) {   /* command freed by reinit */
+           /* cm_data was kmalloc'd here and is not freed by
+            * mpr_free_command(); free it to avoid leaking it
+            * (and its dmamap mapping) across the reset. */
+           ...
+       }
        goto RetFree;
    }

(The clean fix requires mpr_wait_command/mpr_realloc to hand back the orphaned cm_data pointer, or pass-thru to keep its own reference for the error path.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not attempted (Low; HW-gated)

per-fix-DF-4010

Confirmed kernel references

Detail

Evidence (decisive lines)

path trace in README.md: mpr_user.c:1050-1056 (error branch skips free), mpr_user.c:1149-1151 (free gated on cm!=NULL), mprvar.h:568-611 (mpr_free_command NULLs cm_data without kfree), mpr.c:3604-3618 (*cmp=NULL protocol), contrast mpr_user.c:734-735 (mpr_user_command frees buf unconditionally β€” no leak there)

PoC changes

n/a

Verified recommended fix

Free (or reclaim and unload) the orphaned cm_data on the cm==NULL error path β€” e.g. have mpr_realloc hand back orphaned cm_data pointers, or keep a local reference in mpr_user_pass_thru and kfree/bus_dmamap_unload it on all error paths.

Verdict

Source-certain resource leak, HW-gated live: on pass-thru timeout with reinit-realloc, mpr_wait_command sets *cmp=NULL (mpr.c:3611-3617) and mpr_user_pass_thru's error branch skips kfree(cm->cm_data) (mpr_user.c:1052-1056 vs 1149-1151) while mpr_free_command never frees cm_data (mprvar.h:568 NULLs it) β€” the user-sized M_MPRUSER buffer and its loaded dmamap mapping leak per timed-out pass-thru. Low severity (slow kmem/dmamap exhaustion from the operator-group surface, 30s+IOC-reset per iteration). Not live-reproduced: no mpr controller on the guest.