DragonFlyBSD Kernel Audit
← triage · dashboard
DF-1356

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1356 · 12 files
FileTypeDescriptionSize
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
README.md readme summary + reproduce
↓ download 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).

VERDICT.md verdict full mechanism + reachability + fix
↓ download raw

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.

  1. Attacker-controlled lengthamr.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_cmd is the user's struct amr_user_ioctl.au_cmd[32] (amrio.h:99). len is never validated against the destination size.

  2. Undersized destinationamrreg.h:489: c u_int8_t ap_cdb[AMR_MAX_CDB_LEN]; /* AMR_MAX_CDB_LEN = 0x0a = 10 (amrreg.h:90) */ ap_cdb is a fixed 10-byte field inside struct amr_passthrough (amrreg.h:478, 62 bytes __packed), which lives inside union amr_ccb (amrvar.h:110), a 128-byte DMA-coherent allocation.

  3. Overflow — with len = 255, bcopy writes 255 bytes starting at ap_cdb[0]: 245 bytes run past ap_cdb[10], overwriting ap_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-byte ccb union, and finally the adjacent ccb in the command cluster (amr_alloccmd_cluster allocates an array of union amr_ccb). The content is fully attacker-controlled (the user's au_cmd buffer). The same unchecked len is also used at amr.c:837-842 to index au_cmd[len+3..len+6], which overreads the 32-byte au_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_cmd bytes).
  • Target: union amr_ccb 128-byte DMA-coherent slab; overflow reaches the adjacent ccb and DMA/SG memory.
  • Corrupts: ap_data_transfer_address (DMA pointer), sense area, and the next ccb — 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_testable

compile+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.