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

Kernel heap info leak in mpr_user_pass_thru: copyout uses user-controlled ReplySize with no upper bound

Summary

mpr_user_pass_thru at mpr_user.c:867/1093: copyout(cm->cm_reply,PTRIN(data->PtrReply),data->ReplySize). ReplySize is uint32_t user-controlled. sz=rpl->MsgLength*4 (actual reply ~96 bytes) but if(sz>ReplySize) only prints warning, does NOT clamp. ReplySize=1MB -> copyout reads 1MB from cm->cm_reply (inside reply_frames DMA pool) -> leaks kernel heap (pointers, DMA addresses, command metadata). Operator group. KASLR bypass. Fix: clamp copyout to min(sz,ReplySize).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1327 Β· 12 files
FileTypeDescriptionSize
poc.c trigger-source MPTIOCTL_PASS_THRU with ReplySize=1MiB; dumps leaked reply-pool bytes 3.5 KB view raw
VERDICT.md verdict full source trace: copyout uses user-controlled ReplySize, no clamp 5.5 KB ↓ raw
fix.diff suggested-fix clamp both copyout sites to real reply length sz 1.2 KB view raw
build.sh build-script cc -o poc poc.c -Wall 381 B view raw
run.sh run-script ./poc 424 B view raw
build.log build-log guest build output (BUILD_EXIT=0) 13 B view raw
run.log run-log guest run: open /dev/mpr0 ENOENT (no HBA) 81 B view raw
fix_build.log build-log full nativekernel build with all 3 mpr fixes, rc=0 5.6 MB ↓ download
env.txt environment guest uname, cc, driver-availability facts 1.5 KB view raw
README.md readme summary + repro + file index 1.2 KB ↓ 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 summary + repro + file index
↓ download raw

DF-1327 β€” mpr_user_pass_thru kernel-heap info leak

Status: INCONCLUSIVE (real bug; needs LSI SAS3+ HBA absent from audit guest) Impact: info leak (kernel heap, operator-group reachable) Driver: mpr (LSI MPT-Fusion 3 SAS) β€” in X86_64_GENERIC but no HBA on guest

Build & run

./build.sh        # cc -o poc poc.c -Wall
./run.sh          # ./poc   (as an operator-group user on an mpr-equipped host)

On the audit guest the PoC fails at open("/dev/mpr0") with ENOENT (no SAS HBA β†’ driver never attaches β†’ no device node). See VERDICT.md for the full source trace and fix.diff for the validated patch.

Files

  • poc.c β€” PoC source (opens /dev/mpr0, MPTIOCTL_PASS_THRU, ReplySize=1MiB)
  • VERDICT.md β€” full mechanism trace with path:line citations
  • fix.diff β€” clamp both copyout sites to the real reply length sz
  • build.sh/run.sh β€” exact repro commands
  • build.log/run.log β€” guest build + run output
  • fix_build.log β€” full nativekernel build with the fix applied (rc=0)
  • env.txt β€” guest environment + driver-availability facts
  • manifest.jsonβ€” machine-readable artifact catalog
VERDICT.md verdict full source trace: copyout uses user-controlled ReplySize, no clamp
↓ download raw

DF-1327 β€” mpr_user_pass_thru kernel-heap info leak

Verdict

INCONCLUSIVE (real bug, needs hardware absent from guest). The vulnerability is confirmed real by a complete source trace; it cannot be executed on this QEMU audit guest because the mpr driver has no LSI SAS3+ HBA to attach to, so /dev/mpr0 does not exist and the PoC fails at open() with ENOENT. The fix was validated to apply and compile in a full nativekernel build.

Mechanism (trigger β†’ primitive β†’ effect)

mpr_user_pass_thru() issues a passthrough command to the IOC firmware and then copies the reply back to userspace. The reply copy is:

sys/dev/raid/mpr/mpr_user.c:857   if ((cm != NULL) && (cm->cm_reply != NULL)) {
sys/dev/raid/mpr/mpr_user.c:858       rpl = (MPI2_DEFAULT_REPLY *)cm->cm_reply;
sys/dev/raid/mpr/mpr_user.c:859       sz = rpl->MsgLength * 4;            // real reply size (~96 B)
sys/dev/raid/mpr/mpr_user.c:861       if (sz > data->ReplySize) {         // WARN only, no clamp
sys/dev/raid/mpr/mpr_user.c:862           mpr_printf(... "user reply buffer (%d) "
sys/dev/raid/mpr/mpr_user.c:864               "smaller than returned buffer (%d)\n", ...);
sys/dev/raid/mpr/mpr_user.c:865       }
sys/dev/raid/mpr/mpr_user.c:866       mpr_unlock(sc);
sys/dev/raid/mpr/mpr_user.c:867       copyout(cm->cm_reply, PTRIN(data->PtrReply),
sys/dev/raid/mpr/mpr_user.c:868           data->ReplySize);               // *** LEAK: user-controlled len ***
sys/dev/raid/mpr/mpr_user.c:869       mpr_lock(sc);

A second, identical site exists in the main pass-thru return path:

sys/dev/raid/mpr/mpr_user.c:1085      sz = rpl->MsgLength * 4;
sys/dev/raid/mpr/mpr_user.c:1087      if (sz > data->ReplySize) { mpr_printf(...); }
sys/dev/raid/mpr/mpr_user.c:1093      copyout(cm->cm_reply, PTRIN(data->PtrReply), data->ReplySize);

data->ReplySize is a fully user-controlled uint32_t in the mpr_pass_thru_t ioctl argument (sys/dev/raid/mpr/mpr_ioctl.h:194). The check at 861/1087 only warns when the user buffer is smaller than the reply β€” it never clamps the copyout length. Setting ReplySize much larger than the actual reply (sz, ~96 bytes) makes copyout() read ReplySize bytes from cm->cm_reply, which sits inside the reply_frames DMA pool. Every byte past the real reply is kernel heap: function pointers, DMA bus addresses, command metadata, and the contents of adjacent reply frames. This is a deterministic, repeatable kernel-heap information leak β†’ KASLR-bypass / heap-layout disclosure.

Reachability on this guest

  • mpr is compiled into X86_64_GENERIC (kldstat -v β†’ pci/mpr).
  • But there is no LSI/Avogo SAS3+ HBA on the QEMU guest (only Intel PIIX3 IDE + Virtio block). mpr_attach_user() therefore never runs, so make_dev() is never called and no /dev/mpr0 exists: $ ls /dev/mpr* -> No such file or directory
  • The PoC (poc.c) opens /dev/mpr0 and issues MPTIOCTL_PASS_THRU. Run output: poc: open /dev/mpr0: No such file or directory (RUN_EXIT=1)
  • Privilege model (for a hardware-equipped host): the device node is created UID_ROOT, GID_OPERATOR, 0640 (mpr_user.c:205); mpr_open() (mpr_user.c:225) returns 0 unconditionally; mpr_ioctl() (mpr_user.c:2259) does no priv_check()/suser(). So the bug is reachable by root or any operator-group member β€” not a fully-unprivileged user β€” on a host that has an mpr HBA. The finding's "operator group" framing is correct.

This is the Phase-4(d) case: genuinely not reachable on this kernel guest because the required hardware is absent. It is not a false-positive, not already-fixed, and not a missing-setup we can supply (the setup is physical hardware + operator group, both absent).

Exploit chain

None developed β€” the primitive is an information leak (read-only OOB), not a memory-corruption write, so there is no uid=0 chain to pursue per Phase 6. The realistic impact ceiling is a deterministic kernel-heap disclosure: leak N bytes of the reply DMA pool per call, including kernel text/data pointers and DMA bus addresses β†’ defeats KASLR and reveals heap layout for a follow-on corruption exploit. N is bounded only by the user's ReplySize (up to ~4 GiB before copyout faults on an unmapped user page).

PoC changes

The PoC folder was empty (no seeded scaffolding). I authored: - poc.c β€” opens /dev/mpr0, issues MPTIOCTL_PASS_THRU with ReplySize = 1 MiB, hexdumps the leaked reply-pool bytes. The on-wire mpr_pass_thru_t and MPTIOCTL_PASS_THRU are reproduced inside the PoC because <dev/raid/mpr/mpr_ioctl.h> is not installed under /usr/include. - build.sh / run.sh β€” exact build/run commands.

Fix

fix.diff clamps both copyout sites to the actual reply length sz (after clamping sz down to ReplySize when the user buffer is smaller), so the copyout never reads past cm->cm_reply. The diff applies cleanly (git apply --check) and was built in a full make -j6 nativekernel KERNCONF=X86_64_GENERIC on the audit guest (mpr_user.o recompiled, rc=0, no errors) β€” see fix_build.log.

Fix validation

fix_status: not_testable. The PoC cannot run on this guest (no /dev/mpr0), so a runtime before/after comparison is impossible. The fix was validated to (a) apply to the audit source, (b) compile in a full nativekernel build, and (c) close the code path by inspection β€” the copyout length is now sz (real reply size), not the user-controlled ReplySize.

Fix verification

not_testable

compile validated

nativekernel rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. mpr_user_pass_thru copyout uses user ReplySize not real sz -> heap info leak. mpr in GENERIC, no SAS HBA.