Kernel heap info leak via u_int16_t truncation in ASR_fillMessage on user-controlled reply size
Summary
ASR_queue_i (I2OUSRCMD ioctl) at asr.c:3262: ReplySizeInBytes=msgSize<<2 from user-controlled reply MessageSize (up to 0x3FFFC=256KB). kmalloc(M_WAITOK, no M_ZERO) at :3273. ASR_fillMessage(Reply_Ptr,ReplySizeInBytes) at :3273 BUT ASR_fillMessage size param is u_int16_t (asr.c:486) -> ReplySizeInBytes>=0x10000 silently truncated for bzero. Only first 30-60 bytes initialized by header writes. copyout(Reply_Ptr,Reply,ReplySizeInBytes) at :3305/:3575 ships uninitialized slab to userspace. Requires SYSCAP_RESTRICTEDROOT (asr_open :3107). Leaks up to 256KB stale heap: KASLR defeat, slab grooming. Fix: cap ReplySizeInBytes<=MAX_INBOUND_SIZE, change ASR_fillMessage to size_t, add M_ZERO.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1234 · 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full path:line trace, threat model, fix rationale | 5.3 KB | ↓ raw |
| README.md | readme | claim, verdict, runnable-PoC instructions | 2.4 KB | ↓ raw |
| asr_fillmsg_leak.c | trigger-source | PoC: reachability check for /dev/asrN | 2.9 KB | view raw |
| build.sh | build-script | cc -O -Wall -o asr_fillmsg_leak asr_fillmsg_leak.c | 193 B | view raw |
| run.sh | run-script | ./asr_fillmsg_leak | 104 B | view raw |
| build.log | build-log | PoC build, full output | 127 B | view raw |
| run.log | run-log | PoC run on this guest (open /dev/asrN ENOENT) | 514 B | view raw |
| fix.diff | suggested-fix | cap ReplySizeInBytes to MAX_INBOUND_SIZE; widen ASR_fillMessage size to u_int32_t | 988 B | view raw |
| fix_build.log | build-log | kernel build rc=0 with all 5 fixes applied; asr.c compiled clean under -Werror | 2.1 KB | view raw |
| env.txt | environment | guest uname, cc version, PCI topology | 2.0 KB | 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-1234 — README
Finding
ASR_queue_i (the I2OUSRCMD ioctl handler) at
sys/dev/raid/asr/asr.c ships up to ~256 KB of uninitialized kernel
slab to userspace because:
ASR_fillMessage(void *Message, u_int16_t size)atasr.c:486truncatessizeto 16 bits beforebzero(asr.c:491).ReplySizeInBytesatasr.c:3262is(userMsgSize << 2), up to 0xFFFF<<2 = ~256 KB.- The reply buffer is
kmalloc(M_WAITOK)withoutM_ZERO(asr.c:3273-3276). ASR_fillMessage(Reply_Ptr, ReplySizeInBytes)atasr.c:3282silently truncates -> bzero does nothing forReplySizeInBytes≥ 64 KB.copyout(Reply_Ptr, Reply, ReplySizeInBytes)atasr.c:3305/:3575ships the unzeroed slab to userspace.
Verdict
NOT REPRODUCED on this guest (latent): no DPT SmartRAID controller on
the audit guest → no /dev/asrN device node → ioctl not reachable. The
driver is statically linked into X86_64_GENERIC (kldstat -v shows
pci/asr), so the bug path is in the running kernel and fires on real
DragonFly installs with the matching controller.
Confidence (bug is real): certain — traced line-by-line in sys/.
Impact ceiling: root→kernel info-leak (gated by SYSCAP_RESTRICTEDROOT
at asr.c:3107); up to ~256 KB of stale kernel heap per call. Useful for
slab-grooming state disclosure / KASLR-defeat, not unprivileged.
How to reproduce
./build.sh && ./run.sh
Expected on this guest: PoC builds clean, prints "No /dev/asrN found: No
such file or directory" and reachability analysis. On a host with an
asr(4) controller and SYSCAP_RESTRICTEDROOT, sending I2OUSRCMD with
a crafted reply frame leaks up to 256 KB of kernel slab.
Files
| Path | Purpose |
|---|---|
asr_fillmsg_leak.c |
PoC: reachability check for /dev/asrN |
build.sh / run.sh |
exact build/run commands |
fix.diff |
cap ReplySizeInBytes to MAX_INBOUND_SIZE; widen ASR_fillMessage size param |
VERDICT.md |
full path:line trace, threat model, fix rationale |
build.log / run.log |
PoC build + run outputs |
fix_build.log |
kernel-build compile validation of fix.diff |
env.txt |
guest uname / cc / device topology |
DF-1234 — VERDICT
Finding: ASR_queue_i (I2OUSRCMD ioctl) at
sys/dev/raid/asr/asr.c ships uninitialized kernel slab memory to
userspace because ASR_fillMessage() takes a u_int16_t size parameter
that silently truncates the (user-controlled) ReplySizeInBytes, which
can be up to ~256 KB.
Status: NOT REPRODUCED (latent — no DPT SmartRAID controller on this guest).
Confidence (bug is real): certain (traced line-by-line in sys/).
Impact ceiling: up to ~256 KB of stale kernel heap leaked to a
privileged caller per I2OUSRCMD invocation. Useful for slab-grooming
state disclosure / KASLR defeat — but gated by SYSCAP_RESTRICTEDROOT
(asr_open at asr.c:3107), so this is root→kernel info-leak, not
unprivileged.
Mechanism (confirmed line-by-line in sys/)
-
ASR_fillMessageis declared atsys/dev/raid/asr/asr.c:485-486:c static PI2O_MESSAGE_FRAME ASR_fillMessage(void *Message, u_int16_t size) { PI2O_MESSAGE_FRAME Message_Ptr; Message_Ptr = (I2O_MESSAGE_FRAME *)Message; bzero(Message_Ptr, size); /* :491 */ ...Thesizeargument isu_int16_t. Any caller passing a value > 0xFFFF has it silently truncated to the low 16 bits beforebzeroruns. -
ASR_queue_i(servicingI2OUSRCMDioctl,asr.c:3788) computes the reply-buffer size from the user-supplied reply message-size field:c /* asr.c:3262-3263 */ ReplySizeInBytes = (I2O_MESSAGE_FRAME_getMessageSize( &(Reply_Ptr->StdReplyFrame.StdMessageFrame)) << 2);MessageSizeis a U16 in the I2O frame, soReplySizeInBytescan be up to0xFFFF << 2 = 0x3FFFC(~256 KB).Message_Ptr(the request) is bounded toMAX_INBOUND_SIZE = 512atasr.c:3223-3224, but the reply size is not bounded. -
The reply buffer is allocated without
M_ZEROatasr.c:3273-3276:c if ((Reply_Ptr = (PI2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)kmalloc ( ((ReplySizeInBytes > sizeof(I2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)) ? ReplySizeInBytes : sizeof(I2O_SCSI_ERROR_REPLY_MESSAGE_FRAME)), M_TEMP, M_WAITOK)) == NULL) { ... }ThenASR_fillMessageis called atasr.c:3282:c (void)ASR_fillMessage((void *)Reply_Ptr, ReplySizeInBytes);WhenReplySizeInBytes == 0x10000(e.g.MessageSize == 0x4000), theu_int16_tparameter truncates to 0 andbzerodoes nothing. -
Only the I2O header fields are subsequently written:
InitiatorContext,TransactionContext,MsgFlags | REPLY,MessageSize(asr.c:3283-3291). These touch only the first ~30-60 bytes. The remaining ~64 KB of theM_TEMPslab allocation contains uninitialized kernel heap residue. -
copyout((caddr_t)Reply_Ptr, (caddr_t)Reply, ReplySizeInBytes)atasr.c:3305(special-case path) andasr.c:3575(general path) ships the fullReplySizeInBytesback to userspace — leaking the unzeroed slab. -
The user-supplied
ReplySizeInBytesreachescopyoutverbatim, so the leak size is attacker-controlled up to ~256 KB per call.
Why it is NOT REPRODUCED on this guest
pciconf -lvshows no DPT SmartRAID adapter.asr_attachnever runs, so no/dev/asrNcdev is created.kldstat -vconfirmspci/asris statically linked into X86_64_GENERIC (the driver IS in the running kernel), so the bug path exists and would fire on a host with the matching controller.ls /dev/asr*returns no device nodes. PoCasr_fillmsg_leak.cconfirms this at runtime.
Threat model & privilege boundary
asr_open at sys/dev/raid/asr/asr.c:3107 enforces
caps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT). Only root (or a
process explicitly granted the restricted-root capability) can open the
device. So this is a root→kernel info-leak primitive, useful for:
- defeating slab randomization (leak adjacent object pointers),
- leaking previously-freed slab contents (e.g. crypto keys, auth tokens
freed into the same
M_TEMPbucket), - bridging into a separate write-capable primitive via the leaked pointers.
It is not an unprivileged-user vector. The hardening gap is that a
root operator expects I2OUSRCMD to round-trip a well-defined reply
frame, not to be handed the kernel heap.
Fix (authored in fix.diff, applied + compile-validated)
Two-part defense-in-depth:
-
Cap
ReplySizeInBytestoMAX_INBOUND_SIZE(512) right after computation atasr.c:3264. This matches the cap already applied to the request size atasr.c:3223-3224, is consistent with the I2O limit, and immediately eliminates both the truncation and the leak. -
Widen
ASR_fillMessage'ssizefromu_int16_ttou_int32_tatasr.c:486, so the (now bounded but still up to 512) value cannot be silently truncated even if a future caller passes a larger size. This is signature-only — the body is unchanged becausebzeroand thesetMessageSizemath both accept any unsigned int.
Validation
fix.diffapplies cleanly withpatch -p1 --forward(verified).- All 5 audit fixes applied together;
make -j6 nativekernel KERNCONF=X86_64_GENERICreturned rc=0 with no errors / warnings under-Werror.asr.cwas compiled cleanly into both the kernel and theasr.komodule. - Fix is not_testable at runtime on this guest (no DPT controller).
Fix verification
not_testablecompile validated
nativekernel rc=0
Confirmed kernel references
—
Detail
Exploit chain
none
Evidence (decisive lines)
—
Verdict
Source-confirmed. ASR_fillMessage u16 truncation -> ~256KB heap info leak. asr in GENERIC, no DPT HW. Root-only.
No comments yet.