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

XPT_PATH_INQ max_target off-by-one: CAM probes target 15 into the 15-entry ciss_logical rows (recurring OOB read)

Summary

cpi->max_target = CISS_MAX_LOGICAL (15) while ciss_logical[bus] rows hold exactly 15 entries (0..14). CAM scans targets 0..max_target INCLUSIVE, so every boot/rescan reads sc->ciss_logical[bus][15].cl_status one struct past the row kmalloc. VERIFIED: inclusive CAM scan over a canary-backed 15-entry row - 'probe target 15 <== OOB (past row alloc)'; -DFIXED (max_target=14) probes stay in bounds. Certain recurring heap OOB read on every CAM bus scan of every machine with this controller; normally reads adjacent zeros (clean SEL_TIMEOUT), but a 1 at the cl_status offset of the phantom row would route I/O and deref phantom pointers. Distinct from DF-1194 (bus-row off-by-one); this is the target-column off-by-one. One-line fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3731 Β· 16 files
FileTypeDescriptionSize
harness.c trigger-source faithful userspace replica of the cited kernel path (real macros/struct constants) 3.1 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] 2.7 KB view raw
run.2.log run-log repeat run (stability) 2.7 KB view raw
run.3.log run-log repeat run (stability) 2.7 KB view raw
fix_run.log run-log -DFIXED section (persist_fix reads this) 1.2 KB view raw
VERDICT.md verdict full narrative: mechanism, path:line, harness proof, fix 2.4 KB ↓ raw
fix.diff suggested-fix set cpi->max_target = CISS_MAX_LOGICAL - 1 502 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.5 KB ↓ raw
manifest.json manifest this file 2.8 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-3731 β€” XPT_PATH_INQ max_target off-by-one: CAM probes target 15 into the 15-entry ciss_logical rows

File: sys/dev/raid/ciss/ciss.c:2956 (cpi->max_target = CISS_MAX_LOGICAL; = 15) vs row allocation of exactly CISS_MAX_LOGICAL entries, indices 0..14 (ciss.c:1377-1379). CAM's bus scan iterates for (i = 0; i <= max_target; i++) β€” INCLUSIVE (sys/bus/cam/cam_xpt.c:5382), so target 15 is probed on every scan and sc->ciss_logical[bus][15].cl_status is read one struct past the row allocation (ciss_cam_emulate, ciss.c:3159; XPT_CALC_GEOMETRY, ciss.c:2923).

Class: recurring heap OOB read (CWE-125) on every bus scan/boot; heap-layout-dependent deeper impact (garbage ONLINE β†’ wild cl_ldrive/cl_lstatus deref in ciss_cam_complete_fixup, ciss.c:3321-3325). Distinct from DF-1194 (that one is the bus-row <= in init/free; this one is the target-column off-by-one against CAM's inclusive scan).

Reproduce

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

Expected output

Baseline: probe target 15: read ciss_logical[0][15].cl_status ... <== OOB and [BUG REPRODUCED] 1 OOB read(s). Fixed replica (-DFIXED = max_target = CISS_MAX_LOGICAL - 1): all probes in bounds.

Fix

fix.diff (one line). 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-3731 VERDICT

Finding

ciss_cam_action(XPT_PATH_INQ) advertises

    /* sys/dev/raid/ciss/ciss.c:2956 */
    cpi->max_target = CISS_MAX_LOGICAL;      /* 15 */

but each sc->ciss_logical[bus] row is allocated with exactly CISS_MAX_LOGICAL entries (ciss.c:1377-1379) β€” valid indices 0..14, with target 15 being the driver's own initiator_id (ciss.c:2958). CAM semantics treat max_target as INCLUSIVE: the bus scan is

    /* sys/bus/cam/cam_xpt.c:5382 */
    for (i = 0; i <= max_target; i++)

so every rescan probes target 15, and the SIM reads

  • sc->ciss_logical[bus][15].cl_status in ciss_cam_emulate (ciss.c:3159),
  • sc->ciss_logical[bus][target] in XPT_CALC_GEOMETRY (ciss.c:2923),

one struct ciss_ldrive past the row kmalloc β€” a recurring heap OOB read executed at every boot/rescan on every machine with this controller.

Impact ceiling (speculative, heap-layout dependent)

If the adjacent heap object happens to contain CISS_LD_ONLINE (1) at the cl_status offset of the phantom row, the probe proceeds to real I/O and ciss_cam_complete_fixup (ciss.c:3321-3325) dereferences the phantom row's cl_ldrive/cl_lstatus garbage pointers. With kmalloc(..., M_ZERO) neighbours the value is 0 (NONEXISTENT β†’ clean SEL_TIMEOUT), so the observable effect is normally benign β€” hence Low severity with the OOB read itself certain.

Verification (no HP Smart Array on the audit guest)

harness.c models the inclusive CAM scan over a canary-backed 15-entry row: - baseline (run.log, stable in run.2/run.3): probe target 15 ... <== OOB, [BUG REPRODUCED] 1 OOB read(s); - -DFIXED (max_target = CISS_MAX_LOGICAL - 1): 15 probes, all in bounds.

Fix validation

  • fix.diff: cpi->max_target = CISS_MAX_LOGICAL - 1;
  • 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); certain recurring OOB read, speculative deeper impact; 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

max_target = CISS_MAX_LOGICAL - 1. -Werror module build + -DFIXED replica: all probes in bounds.

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

Confirmed kernel references

Detail

Exploit chain

every CAM scan -> probe target 15 -> OOB read of phantom row -> normally SEL_TIMEOUT; garbage ONLINE would deref phantom cl_ldrive

Evidence (decisive lines)

run.log: probe target 15 ... <== OOB (past row alloc), [BUG REPRODUCED] 1 OOB read; fix_run.log all probes in bounds

PoC changes

harness written fresh

Verified recommended fix

set cpi->max_target = CISS_MAX_LOGICAL - 1

Verdict

REPRODUCED (harness-level): cpi->max_target = CISS_MAX_LOGICAL (15) (ciss.c:2956) while ciss_logical rows hold exactly 15 entries (ciss.c:1377-1379); CAM scans targets 0..max_target INCLUSIVE (cam_xpt.c:5382), so every bus scan reads ciss_logical[bus][15].cl_status one struct past the row kmalloc (ciss.c:3159/2923). Certain recurring OOB read; deeper wild-pointer deref in ciss_cam_complete_fixus is heap-layout-dependent -> Low severity. Distinct from DF-1194 (bus-row <= off-by-one).