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)
PoC verification
Evidence pack
findings/poc/DF-3731 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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_statusinciss_cam_emulate(ciss.c:3159),sc->ciss_logical[bus][target]inXPT_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.korebuilt with all 7 ciss fixes at-Werror, link rc=0;-DFIXEDharness 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
fixedmax_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]
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).
No comments yet.