Kernel heap info leak via unchecked request-sense length in Linux-compat ioctl
Summary
amr_linux_ioctl_int AMR_CMD_PASS at amr.c:624: copyout(ap->ap_request_sense_area,...,ap->ap_request_sense_length). Length from user copyin (:583) unchecked. ap_request_sense_area is u8[32]. Length=255 -> reads 223 bytes past into adjacent ccbs in DMA array. Root only. Fix: clamp length to AMR_MAX_REQ_SENSE_LEN.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1357 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | function-level harness: copyout 255B from 32B sense_area -> 223B heap leak | 1.0 KB | view raw |
| fix.diff | suggested-fix | git-apply-able diff that adds the guard verified at the function level | 657 B | view raw |
| build.sh | build-script | exact build: cc -O2 -Wall -o trigger trigger.c | 125 B | view raw |
| run.sh | run-script | exact run: ./trigger | 111 B | view raw |
| run.log | run-log | decisive harness output BEFORE-FIX + AFTER-FIX | 202 B | view raw |
| fix_build.log | build-log | single batched patched-kernel build (rc=0); proves all 15 fixes compile | 5.6 MB | β download |
| env.txt | environment | uname, guest cc version, patch list | 500 B | view raw |
| VERDICT.md | verdict | narrative analysis: mechanism, why not live, fix | 2.1 KB | β raw |
| README.md | readme | human-facing reproduce instructions | 2.1 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 |
DF-1357 β amr_linux_ioctl AMR_CMD_PASS copyout overrun of ap_request_sense_area
Summary
Clamp ap_request_sense_length to AMR_MAX_REQ_SENSE_LEN (0x20) before copyout.
How to reproduce
This bug lives in a device driver not reachable from the booted QEMU guest as
an unprivileged user (maxx) because the required hardware is absent (AMD GPU,
RAID HBA, sound PCI, AMD SCSI) or the trigger requires a malicious hypervisor
(virtio_net, virtio_scsi). The bug is reproduced at the function level by
porting the cited code path into a userspace harness that drives it with the
attacker-controlled inputs the original code fails to validate.
Build
cc -O2 -Wall -o trigger trigger.c
Run
./trigger
Expected
- BEFORE-FIX section shows the bug signature (SIGFPE for div-by-zero, OOB index report for overflows, wraparound count for underflows, over-read length for info leaks).
- AFTER-FIX section shows the guard from
fix.diffcleanly rejecting the attacker input.
The same harness was compiled and run on the patched single-fix kernel
(DragonFly 6.5-DEVELOPMENT #1) β output is identical because the harness
intentionally demonstrates both the unpatched and patched function logic side
by side, and the userspace behavior of those branches is independent of the
kernel. The patched kernel build (fix_build.log) confirms all 15 fix.diffs
compile cleanly in the real kernel / module context.
Impact classification
leak:223 β gated by absent hardware / malicious-hypervisor precondition on
this guest; live trigger from maxx is not possible. See VERDICT.md for
the threat-model analysis.
Files
trigger.cβ function-level harness porting the cited code path.fix.diffβ git-apply-able unified diff againstsys/.build.sh/run.shβ exact repro commands.run.logβ decisive harness output (BEFORE-FIX + AFTER-FIX).fix_build.logβ patched kernel build log (proves all 15 fixes compile).VERDICT.mdβ full narrative analysis.manifest.jsonβ machine-readable catalog.
Host has no gcc; harnesses built in guest as maxx with cc (DragonFly gcc 8.3).
DF-1357 β VERDICT
REPRODUCED at the function level (impact: leak:223).
Mechanism
amr_linux_ioctl_int() in amr.c handles AMR_CMD_PASS at :624 by 'copyout(ap->ap_request_sense_area, &user->ap_request_sense_area, ap->ap_request_sense_length)'. ap is a struct amr_passthrough copied in from user (:583) β ap_request_sense_length is user-controlled. ap_request_sense_area is uint8_t[32] (AMR_MAX_REQ_SENSE_LEN). A user-supplied length of 255 makes copyout read 223 bytes past the 32-byte sense_area into adjacent fields of the ccb / DMA-coherent array, leaking kernel heap contents. The amr Linux-compat ioctl is root-only (the amr device node is operator/wheel-gated), so this is a root->userspace kernel-info-leak, but the bug is real.
Why not live-reproduced on the QEMU guest
AMI MegaRAID hardware absent from QEMU guest. The amr module loads only on matching AMI RAID HW. Reachable from the Linux-compat ioctl path by an already-privileged operator (root).
Recommended fix
In amr_linux_ioctl_int at the AMR_CMD_PASS path, clamp ap_request_sense_length to AMR_MAX_REQ_SENSE_LEN before the copyout: 'uint8_t sense_len = ap->ap_request_sense_length; if (sense_len > AMR_MAX_REQ_SENSE_LEN) sense_len = AMR_MAX_REQ_SENSE_LEN; copyout(ap->ap_request_sense_area, ..., sense_len);'.
Kernel references (confirmed during verification)
- sys/dev/raid/amr/amr.c:624 (copyout with ap_request_sense_length, unchecked)
- sys/dev/raid/amr/amrreg.h:92 (AMR_MAX_REQ_SENSE_LEN = 0x20)
- sys/dev/raid/amr/amrreg.h:491-492 (ap_request_sense_area[32])
Build/run
- Build harness:
cc -O2 -Wall -o trigger trigger.c - Run harness:
./trigger - Apply fix:
cd /usr/src && patch -p1 < fix.diff - Build single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERIC(validated β seefix_build.log; all 15 fixes compile cleanly in one batched build, rc=0).
Tested kernels
- baseline:
DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 - patched :
DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 21:51:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Fix verification
fixedVALIDATED via batched single-fix kernel build: amr.c compiles cleanly with the fix (rc=0). Harness BEFORE-FIX shows 223-byte leak; AFTER-FIX clamps to 32.
baseline #0 BEFORE-FIX: copyout 255B from 32B sense_area -> 223B leak. patched #1 cc6aa06b AFTER-FIX: length clamped to 32; amr built into kernel rc=0.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- a
- m
- r
- /
- a
- m
- r
- .
- c
- :
- 6
- 2
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- a
- m
- r
- /
- a
- m
- r
- r
- e
- g
- .
- h
- :
- 9
- 2
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- a
- m
- r
- /
- a
- m
- r
- r
- e
- g
- .
- h
- :
- 4
- 9
- 1
Detail
Exploit chain
none β pure info leak via unchecked copyout length. No write primitive. The over-read copies kernel heap bytes (adjacent ccbs / DMA pool) back to a user pointer. Operator-gated (amr device is root-only) so it's a root->userspace info leak, not an unpriv->root escalation.
Evidence (decisive lines)
BEFORE-FIX (amr_sense.c): copyout(ap_request_sense_area[32], user, 255) leaks 223 bytes of adjacent ccbs/DMA array. AFTER-FIX: length clamped to AMR_MAX_REQ_SENSE_LEN=32, no over-read. Patched-kernel build rc=0 (amr is in GENERIC kernel β device amr/amrp). See findings/poc/DF-1357/run.log and fix_build.log.
PoC changes
Wrote trigger.c (amr_sense.c) harness demonstrating the 223-byte over-read.
Verified recommended fix
fix.diff adds 'uint8_t sense_len = ap->ap_request_sense_length; if (sense_len > AMR_MAX_REQ_SENSE_LEN) sense_len = AMR_MAX_REQ_SENSE_LEN; copyout(..., sense_len);'. Matches finding proposal. Full diff in findings/poc/DF-1357/fix.diff.
Verdict
REPRODUCED at function level. amr_linux_ioctl_int() at amr.c:624 calls copyout(ap->ap_request_sense_area, user, ap->ap_request_sense_length) where the length is user-controlled (copied in at :583). ap_request_sense_area is uint8_t[32] (AMR_MAX_REQ_SENSE_LEN). A user length of 255 makes copyout read 223 bytes past the 32-byte sense_area into adjacent fields of the ccb / DMA-coherent array, leaking kernel heap to userspace. Harness amr_sense.c demonstrates the 223-byte over-read before fix; fixed path clamps length to 32. AMI MegaRAID HW absent from guest; ioctl is operator-gated (root-only), so this is root->userspace info leak.
No comments yet.