Heap overflow in AMR_CMD_PASS ioctl via unchecked cdb length
Summary
amr_ioctl AMR_CMD_PASS at amr.c:832: len=au_cmd[2] (user u8 0-255). bcopy(au_cmd+3,_ap->ap_cdb,len). ap_cdb is u8[AMR_MAX_CDB_LEN=10] inside 128-byte DMA-coherent ccb in 255-entry array. len>10 -> overflow into adjacent ccb DMA/SG pointers. Root only (/dev/amrN mode 0600). Sibling of DF-1235/DF-1270/DF-1348 CDB overflow. Fix: check len<=AMR_MAX_CDB_LEN.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1356 · 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | object-level proof: replays amr_ioctl AMR_CMD_PASS bcopy with au_cmd[2]=255 into ap_cdb[10] | 5.9 KB | view raw |
| fix.diff | suggested-fix | clamp len to AMR_MAX_CDB_LEN after au_cmd[2] read | 335 B | view raw |
| build.sh | repro-script | cc -O2 -o harness harness.c | 125 B | view raw |
| run.sh | repro-script | ./harness | 60 B | view raw |
| build.log | build-log | harness build, full output | 95 B | view raw |
| run.log | run-log | harness decisive run: 245B overflow, adjacent ccb1 + guard corrupted | 803 B | view raw |
| fix_build.log | fix-build-log | clean amr.ko module build with fix applied, rc=0 | 29.5 KB | view raw |
| env.txt | environment | uname + cc version + dev-node check | 520 B | view raw |
| README.md | readme | summary + reproduce | 949 B | ↓ raw |
| VERDICT.md | verdict | full mechanism + reachability + fix | 5.3 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-1356 — AMR_CMD_PASS CDB heap overflow (amr)
Summary
amr_ioctl (sys/dev/raid/amr/amr.c:834) copies a user-supplied CDB of
unchecked length (au_cmd[2], 0..255) into the fixed 10-byte ap_cdb field of
a 128-byte DMA-coherent union amr_ccb via bcopy, overflowing into the
sense area, the DMA transfer-address pointer, and the adjacent ccb. Root-only
device (/dev/amrN mode 0600); no MegaRAID HBA on the audit guest.
Reproduce
./build.sh # cc -O2 -o harness harness.c ./run.sh # ./harness
Expected: harness prints 245 bytes overflow past ap_cdb, ADJACENT ccb1
corrupted: YES, guard region past ccb1 corrupted: YES. This is an
object-level proof — the amr driver cannot attach on the QEMU guest (no
MegaRAID HBA), so the in-kernel path is not runtime-reachable here.
Fix
fix.diff clamps len to AMR_MAX_CDB_LEN after len = au_cmd[2];.
Validated to apply + compile (amr.ko, clean build rc=0).
DF-1356 — VERDICT
Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest; the device is root-only even with hardware).
Mechanism (source trace)
amr_ioctl() handles AMR_CMD_PASS (SCSI passthrough) at
sys/dev/raid/amr/amr.c:825-849.
-
Attacker-controlled length —
amr.c:832:c len = au_cmd[2]; /* u8, user-supplied, 0..255 */ _ap->ap_cdb_length = len; bcopy(au_cmd + 3, _ap->ap_cdb, len); /* amr.c:834 — the overflow */au_cmdis the user'sstruct amr_user_ioctl.au_cmd[32](amrio.h:99).lenis never validated against the destination size. -
Undersized destination —
amrreg.h:489:c u_int8_t ap_cdb[AMR_MAX_CDB_LEN]; /* AMR_MAX_CDB_LEN = 0x0a = 10 (amrreg.h:90) */ap_cdbis a fixed 10-byte field insidestruct amr_passthrough(amrreg.h:478, 62 bytes__packed), which lives insideunion amr_ccb(amrvar.h:110), a 128-byte DMA-coherent allocation. -
Overflow — with
len = 255,bcopywrites 255 bytes starting atap_cdb[0]: 245 bytes run pastap_cdb[10], overwritingap_request_sense_length,ap_request_sense_area[32],ap_data_transfer_address(the DMA pointer),ap_data_transfer_length, then the remainder of the 128-byteccbunion, and finally the adjacentccbin the command cluster (amr_alloccmd_clusterallocates an array ofunion amr_ccb). The content is fully attacker-controlled (the user'sau_cmdbuffer). The same uncheckedlenis also used atamr.c:837-842to indexau_cmd[len+3..len+6], which overreads the 32-byteau_cmd— a secondary source overread.
Sibling of DF-1235/DF-1270/DF-1348 (the same CDB-overflow class in the amr direct/ext passthrough paths).
Reachability on this guest
amr is a device in X86_64_GENERIC (compiled into the kernel), but it is a
PCI driver for LSI/AMI MegaRAID controllers. The audit guest is a QEMU/KVM VM
with no MegaRAID HBA (pciconf -l shows only virtio devices), so the driver
never attaches, no /dev/amrN node is created (amr.c:261 make_dev is never
called), and amr_ioctl is unreachable at runtime.
Privilege model (amr.c:261):
make_dev(&amr_ops, ..., UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "amr%d", ...);
Mode is S_IRUSR|S_IWUSR (0600) — owner (root) only; the GID_OPERATOR
group is granted no bits, so even operator-group users cannot open it. This is
therefore a root→kernel path: on an amr-equipped host only root can reach the
overflow. Root→kernel corruption is a hardening gap, not an unprivileged
escalation (root already wins). There is no unprivileged path to amr_ioctl on
this guest (no device), and the device is root-only even with hardware.
Phase-4(d): real code path, unreachable on this guest due to absent hardware; primitive proven at the object/harness level.
Primitive characterization
- Write size: up to 255 bytes from a 10-byte field (245-byte overflow).
- Content control: fully attacker-controlled (user's
au_cmdbytes). - Target:
union amr_ccb128-byte DMA-coherent slab; overflow reaches the adjacentccband DMA/SG memory. - Corrupts:
ap_data_transfer_address(DMA pointer), sense area, and the nextccb— on a real controller a malicious CDB could redirect DMA or corrupt a sibling command.
Harness proof
harness.c allocates two adjacent 128-byte union amr_ccbs plus a canary
guard, replays amr.c:832-834 with au_cmd[2]=255, and reports the overflow.
Output (run.log):
[DF-1356] attacker len=255 (au_cmd[2]); AMR_MAX_CDB_LEN=10 -> 245 bytes overflow past ap_cdb [DF-1356] ccb0.ap_data_transfer_address now = 0x41414141 (DMA ptr corrupted) [DF-1356] bytes written past ap_passthrough but inside ccb0: 66 [DF-1356] ADJACENT ccb1 corrupted: YES -> next DMA ccb overwritten (128 attacker bytes landed in it) [DF-1356] guard region past ccb1 corrupted: YES (5 bytes)
Exploit chain / escalation
Write-capable primitive, but it fires only inside a running kernel with an
amr-attached controller opened by root. On this guest there is no controller
(root or otherwise), so the chain cannot be demonstrated in-kernel. Even on a
host with the controller the path is root-only (0600), so there is no
privilege boundary to cross (root→root). The honest reported impact is the
corruption primitive itself, confirmed at the object/harness level.
Fix
fix.diff clamps len to AMR_MAX_CDB_LEN immediately after reading
au_cmd[2], before both the bcopy (closes the destination overflow) and the
au_cmd[len+3..] reads (closes the source overread):
len = au_cmd[2];
_ap->ap_cdb_length = len;
if (len > AMR_MAX_CDB_LEN)
len = AMR_MAX_CDB_LEN;
bcopy(au_cmd + 3, _ap->ap_cdb, len);
Validated: patch -p1 --dry-run succeeds (hunk @831), and amr.ko builds
clean with a clean module build (rc=0, no errors/warnings — fix_build.log).
Matches the finding's proposed fix (check len <= AMR_MAX_CDB_LEN).
Fix-validation status
not_testable for a live kernel before/after (the driver cannot attach on the
guest). Evidence the fix is correct: (1) the harness shows clamping keeps the
write inside ap_cdb[10]; (2) the fix compiles cleanly in-tree (amr.ko
produced); (3) the clamp also closes the secondary au_cmd[len+N] source
overread.
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
—
Detail
Exploit chain
none
Evidence (decisive lines)
—
Verdict
REPRODUCED (harness). amr AMR_CMD_PASS len=au_cmd[2] no bounds vs ap_cdb[10] -> 245B overflow. amr in GENERIC, no MegaRAID HBA, root-only.
No comments yet.