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

ciss_cam_complete_fixup: padstr writes 16 bytes into the 4-byte inq->revision -> 12-byte heap overflow past short CAM INQUIRY buffers

Summary

padstr(inq->revision, ..., 16) writes 16 bytes at buffer offset 32, but revision is char[4] and the only gate is dxfer_len >= SHORT_INQUIRY_LENGTH (36) - a 36-byte CAM INQUIRY data buffer is overflowed by 12 bytes of status-string content. VERIFIED: the three padstr calls at exact struct offsets against a canary'd 36-byte buffer - 'write at buffer+36', 12 bytes past; -DFIXED (length 4) leaves the canary intact. Root/operator-gated via CAM pass-through (/dev/passN 0600); in-kernel periphs allocate the full struct and are safe. Upstream-inherited.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3730 Β· 16 files
FileTypeDescriptionSize
harness.c trigger-source faithful userspace replica of the cited kernel path (real macros/struct constants) 4.0 KB view raw
build.sh build-script cc -O2 (and -DFIXED replica) 65 B view raw
run.sh run-script baseline + fixed runs with exit codes 89 B view raw
build.log build-log full untrimmed guest compiler output 43 B view raw
run.log run-log decisive run: baseline [BUG REPRODUCED] + fixed [FIXED] 953 B view raw
run.2.log run-log repeat run (stability) 953 B view raw
run.3.log run-log repeat run (stability) 953 B view raw
fix_run.log run-log -DFIXED section (persist_fix reads this) 373 B view raw
VERDICT.md verdict full narrative: mechanism, path:line, harness proof, fix 2.8 KB ↓ raw
fix.diff suggested-fix bound the revision padstr to sizeof(inq->revision) (4 bytes) 438 B view raw
fix_apply.log apply-log patch --dry-run on guest /usr/src (clean) 2.8 KB view raw
fix_build.log build-log ciss.ko rebuilt with all 7 pass-2 fixes at -Werror, link rc=0 1.0 KB view raw
env.txt environment guest uname, no-HW proof, module presence 538 B view raw
README.md readme human reproduce doc 1.2 KB ↓ raw
manifest.json manifest this file 2.9 KB view raw
verdict.json verdict machine verdict (persist_poc.py schema) 2.0 KB view raw
README.md readme human reproduce doc
↓ download raw

DF-3730 β€” ciss_cam_complete_fixup: padstr writes 16 bytes into the 4-byte INQUIRY revision field

File: sys/dev/raid/ciss/ciss.c:3325 (padstr(inq->revision, ciss_name_ldrive_status(...), 16); β€” struct scsi_inquiry_data.revision is char[4] at buffer offset 32, scsi_all.h:738-739). Gate is only csio->dxfer_len >= SHORT_INQUIRY_LENGTH (36) at ciss.c:3305 β†’ 12 bytes past a 36-byte CAM data buffer.

Class: kernel heap OOB write (CWE-787), CAM-pass-through reachable (root/operator; /dev/passN is 0600 root:operator, scsi_pass.c:279-281). Upstream-inherited.

Reproduce

./build.sh && ./run.sh

Expected output

Baseline: write at buffer+36 (>= 36) … [BUG REPRODUCED] 12 bytes written past the end of the 36-byte INQUIRY data buffer. Fixed replica (-DFIXED = sizeof(inq->revision)): canary intact, [FIXED].

Fix

fix.diff (bound the revision padstr to the field size). Compile-validated with the other 6 ciss fixes at -Werror (fix_build.log); behaviorally validated by the -DFIXED run.

Artifacts

VERDICT.md, harness.c, build.sh, run.sh, build.log, run.log, run.2.log, run.3.log, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json, verdict.json.

VERDICT.md verdict full narrative: mechanism, path:line, harness proof, fix
↓ download raw

DF-3730 VERDICT

Finding

After every completed INQUIRY against a logical volume, ciss_cam_complete_fixup() rewrites the inquiry data to advertise the volume's organisation and status:

    /* sys/dev/raid/ciss/ciss.c:3323-3325 */
    padstr(inq->vendor,  "COMPAQ", 8);                                    /* field is 8  β€” ok */
    padstr(inq->product, ciss_name_ldrive_org(cl->cl_ldrive->fault_tolerance), 8);   /* field is 16 β€” ok */
    padstr(inq->revision, ciss_name_ldrive_status(cl->cl_lstatus->status), 16);       /* field is 4! */

padstr (cissvar.h:374-384) writes exactly len bytes. The revision field is char revision[SID_REVISION_SIZE=4] at buffer offset 32 (sys/bus/cam/scsi/scsi_all.h:738-739), so the third call writes bytes 32..47 β€” 12 bytes past the end of any inquiry buffer smaller than 48 bytes. The only size gate on this path is csio->dxfer_len >= SHORT_INQUIRY_LENGTH (36) at ciss.c:3305, so a standard 36-byte INQUIRY data buffer is smashed.

Reachability

Any CAM client issuing INQUIRY with a 36..44-byte data buffer against an ONLINE logical volume. In-kernel periphs allocate sizeof(struct scsi_inquiry_data) (β‰₯ 96) and are unaffected; the CAM pass-through (/dev/passN, 0600 root:operator β€” scsi_pass.c:279-281) allocates exactly the user-requested dxfer_len bytes, so a root/operator camcontrol-style INQUIRY with a 36-byte buffer takes the 12-byte heap overflow. Content is a NUL-padded fixed-set status string (limited attacker control: e.g. "failed", "interim recovery", …). FreeBSD's driver has the identical line (upstream-inherited).

Severity assessment: Low β€” requires CAM pass-through privileges (root / operator group), overflow is 12 bytes of semi-fixed string content past a kmalloc'd buffer.

Verification (no HP Smart Array on the audit guest)

harness.c replicates the three padstr calls at the exact struct offsets with a canary'd 36-byte buffer: - baseline (run.log, stable in run.2/run.3): write at buffer+36, buffer[36..47] = 0x20 padding ("failed ..."), [BUG REPRODUCED] 12 bytes written past the end; - -DFIXED (revision padstr bounded to 4): canary intact, [FIXED].

Fix validation

  • fix.diff: padstr(inq->revision, ..., sizeof(inq->revision));
  • fix_apply.log: clean dry-run; fix_build.log: ciss.ko rebuilt with all 7 ciss fixes at -Werror, link rc=0; -DFIXED harness run clean.

Bottom line: reproduced (harness-level); 12-byte heap overflow, root/operator -gated; fix validated at compile + replica level.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

revision padstr bounded to sizeof(inq->revision) (4). -Werror module build + -DFIXED replica shows canary intact.

fix.diff; fix_apply.log; fix_build.log; fix_run.log [FIXED]
↓ fix.diffper-fix-DF-3730

Confirmed kernel references

Detail

Exploit chain

CAM pass INQUIRY with 36-byte buffer on an ONLINE logical target -> completion fixup padstr(16) at offset 32 -> 12 bytes past kmalloc

Evidence (decisive lines)

run.log: write at buffer+36, [BUG REPRODUCED] 12 bytes past the 36-byte buffer; fix_run.log canary intact

PoC changes

harness written fresh; padstr instrumented with offset-checking stores

Verified recommended fix

bound the revision padstr to sizeof(inq->revision) (4 bytes)

Verdict

REPRODUCED (harness-level): ciss_cam_complete_fixup pads inq->revision (4-byte field, offset 32) with 16 bytes (ciss.c:3325) gated only by dxfer_len >= 36 (ciss.c:3305) -> 12-byte heap overflow past a 36-byte CAM INQUIRY buffer, reachable via CAM pass-through (/dev/passN 0600 root:operator). Severity Low (privileged reach, semi-fixed string content); upstream-inherited.