ciss_disable_adapter: uninitialized STAILQ head qh -> wild kernel write/read (missing STAILQ_INIT)
Summary
ciss_disable_adapter() declares cr_qhead_t qh without STAILQ_INIT. The first ciss_enqueue_complete(cr, &qh) expands to STAILQ_INSERT_TAIL (no NULL guard): *(qh.stqh_last) = cr writes the ciss_request kernel pointer through the uninitialized stack slot; the dequeue loop then dereferences the garbage qh.stqh_first. Reached from ciss_periodic after 3 failed 10s heartbeat checks with >=1 BUSY request. FreeBSD upstream HAS the init, making this a DragonFly-only divergence. VERIFIED: faithful queue.h-macro replica with stack grooming - '[WILD WRITE] *(0x51000000)=<request ptr>' 3/3 runs; gcc itself warns 'qh.stqh_last is used uninitialized'. Any system with an HP Smart Array whose adapter wedges (heartbeat watchdog trip) with queued I/O: uncontrolled write of a kernel heap pointer to a stack-garbage (groomable) address plus a wild read - panic to potential exploitation. One-line fix matching upstream.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3727 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | faithful userspace replica of the cited kernel path (real macros/struct constants) | 6.0 KB | view raw |
| build.sh | build-script | cc -O2 (and -DFIXED replica, -O0 variant) | 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 | 496 B | view raw |
| run.log | run-log | decisive run: baseline [BUG REPRODUCED] + fixed [FIXED] | 2.9 KB | view raw |
| run.2.log | run-log | repeat run (stability) | 2.9 KB | view raw |
| run.3.log | run-log | repeat run (stability) | 2.9 KB | view raw |
| fix_run.log | run-log | -DFIXED section (persist_fix reads this) | 823 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, path:line, harness proof, fix | 4.1 KB | β raw |
| fix.diff | suggested-fix | add STAILQ_INIT(&qh) in ciss_disable_adapter (matches FreeBSD upstream) | 415 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.9 KB | β raw |
| manifest.json | manifest | this file | 2.9 KB | view raw |
| verdict.json | verdict | machine verdict (persist_poc.py schema) | 2.6 KB | view raw |
DF-3727 β ciss_disable_adapter: uninitialized STAILQ head β wild kernel write
File: sys/dev/raid/ciss/ciss.c:3465 (cr_qhead_t qh; never STAILQ_INIT'd),
used at ciss.c:3483 (ciss_enqueue_complete(cr, &qh) == STAILQ_INSERT_TAIL,
sys/sys/queue.h:306 β no NULL guard: *(head)->stqh_last = (elm)).
Class: kernel memory corruption via uninitialized stack variable
(CWE-457 β CWE-787/CWE-824). DragonFly-only divergence: FreeBSD's
ciss_disable_adapter has STAILQ_INIT(&qh); (upstream ciss.c:3581).
Trigger: adapter heartbeat unchanged for 3 consecutive 10 s checks
(ciss_periodic, ciss.c:3389-3398) with β₯1 BUSY request β
ciss_disable_adapter (ciss.c:3395). The guest has no HP Smart Array
hardware, so β per the established methodology (cf. DF-1190) β the primitive
is proven with a faithful userspace harness replica using the exact
queue.h macro text and stack grooming.
Reproduce
./build.sh && ./run.sh
Expected output
Baseline: [WILD WRITE] *(0x51000000) = <request ptr> and
[BUG REPRODUCED] β STAILQ_INSERT_TAIL wrote the request pointer through
the uninitialized qh.stqh_last (stack garbage groomed to 0x51000000).
The -O0 build lands the groom deterministically (3/3 runs); gcc itself
warns 'qh.stqh_last' is used uninitialized on the -O2 build (see build.log).
Fixed replica (-DFIXED = STAILQ_INIT(&qh)): no write through garbage,
list consistent, [FIXED] marker.
Fix
fix.diff (one line, matches FreeBSD upstream). Validated: applies cleanly,
ciss.ko rebuilt with all 7 ciss fixes at -Werror (fix_build.log), and the
-DFIXED harness run shows the wild write gone. No live-kernel trigger
exists on the audit guest (no CISS controller).
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-3727 VERDICT
Finding
ciss_disable_adapter() (sys/dev/raid/ciss/ciss.c:3463-3507) declares
cr_qhead_t qh; /* ciss.c:3465 */
and β unlike every other user of a local cr_qhead_t in this driver
(ciss_intr ciss.c:2109, ciss_perf_msi_intr ciss.c:2136, ciss_cam_poll
ciss.c:3194, ciss_poll_request ciss.c:2304) β never calls
STAILQ_INIT(&qh). The first busy request is then enqueued:
ciss_enqueue_complete(cr, &qh); /* ciss.c:3483 */
which expands (sys/sys/queue.h:306-310) to
(elm)->cr_link = NULL;
*(head)->stqh_last = (elm); /* WRITE through garbage pointer */
STAILQ_INSERT_TAIL in DragonFly has no NULL/stqh_last guard, so the
very first store writes the kernel address of the ciss_request through
whatever value the uninitialized stack slot holds. The subsequent
ciss_dequeue_complete(sc, &qh) (ciss.c:3487) additionally dereferences the
equally-uninitialized qh.stqh_first (STAILQ_REMOVE_HEAD reads
STAILQ_NEXT(STAILQ_FIRST(head)) β wild read).
Upstream divergence (proof this is a bug, not intent): FreeBSD's
ciss_disable_adapter() contains STAILQ_INIT(&qh); right after the
declaration block (FreeBSD src sys/dev/ciss/ciss.c:3581). DragonFly's copy
(imported 2012 from FreeBSD 1.113 and diverging since) is missing it.
Reachability
ciss_disable_adapter is called from ciss_periodic() (ciss.c:3395) when
the config-table heartbeat value is unchanged for 3 consecutive 10-second
ticks (ciss.c:3389-3398) β i.e. the adapter wedged/locked up β and at least
one request is BUSY (ciss.c:3477). On a system with real HP Smart Array
hardware under I/O load this is the standard "failing adapter" path, not an
exotic one: every hung controller with queued I/O that trips the 30 s
heartbeat watchdog executes this function.
Primitive
- Write:
*(uint64_t*)GARBAGE = &sc->ciss_request[i]β an uncontrolled-but-groomable kernel pointer value written to a stack-garbage-controlled kernel address. The stack slot inciss_disable_adapter's frame inherits whatever the prior user of that callout-thread stack depth left there (callout callbacks, earlier syscalls on the softclock thread); the harness demonstrates exactly this grooming. - Read: list traversal through garbage
stqh_firston dequeue.
Impact ceiling: kernel memory corruption β panic to potential exploitation depending on the groomed value (the write VALUE is a kernel heap pointer, limiting but not eliminating exploitation; the wild READ is a near-certain panic).
Verification (no HP Smart Array on the audit guest)
Faithful userspace replica (harness.c) with the exact queue.h macro text:
- build.log: gcc 8.3 emits 'qh.stqh_last' is used uninitialized in this
function [-Wuninitialized] on the -O2 build β the compiler independently
confirms the defect.
- run.log (also run.2/run.3, 3/3 stable): the -O0 build lands the groom
deterministically; output shows [WILD WRITE] *(0x51000000) = 0x4023c0
(request pointer written at the groomed address) and
[BUG REPRODUCED].
- -DFIXED replica (adds STAILQ_INIT(&qh)): no write through garbage,
list head consistent, [FIXED].
Fix validation
fix.diff: one-line addition ofSTAILQ_INIT(&qh);β identical to FreeBSD upstream.fix_apply.log:patch --dry-runsucceeds on the guest's clean/usr/srccopy.fix_build.log: with all 7 ciss pass-2 fixes applied,ciss.korebuilt at-Werror, link rc=0.- Harness
-DFIXEDrun inrun.log: wild write GONE (baseline reproduces, patched replica does not).
Bottom line: reproduced (harness-level, hardware-gated path), kernel wild-write primitive via uninitialized stack variable; fix is the missing one-line STAILQ_INIT, 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
fixedSTAILQ_INIT(&qh) added (matches FreeBSD upstream ciss.c:3581). Module rebuilt with all 7 ciss fixes at -Werror (link rc=0); -DFIXED harness replica shows no write through garbage and consistent list head.
fix.diff; fix_apply.log (clean dry-run); fix_build.log (-Werror, ciss.ko link ok); fix_run.log ([FIXED] marker)
Confirmed kernel references
Detail
Exploit chain
adapter wedge (heartbeat x3) -> ciss_disable_adapter -> *(garbage stqh_last)=&ciss_request[i] wild write -> panic..exploitation depending on stack grooming; wild read on dequeue
Evidence (decisive lines)
run.log lines with [WILD WRITE] *(0x51000000)=0x4023c0 and [BUG REPRODUCED]; build.log gcc -Wuninitialized; fix_run.log [FIXED]; VERDICT.md
PoC changes
harness written fresh (no seed); -O2 build shifts the frame so the deterministic -O0 build is the default demonstration
Verified recommended fix
add STAILQ_INIT(&qh) in ciss_disable_adapter (matches FreeBSD upstream)
Verdict
REPRODUCED (harness-level; hardware-gated path): ciss_disable_adapter (ciss.c:3465) never STAILQ_INITs its local cr_qhead_t qh; the first ciss_enqueue_complete (ciss.c:3483 == STAILQ_INSERT_TAIL, queue.h:306, no NULL guard) writes the ciss_request kernel pointer through the uninitialized stack slot qh.stqh_last, and the dequeue loop dereferences the garbage stqh_first. Reached from ciss_periodic after 3 failed 10s heartbeats with >=1 BUSY request (ciss.c:3389-3398,3395). FreeBSD upstream has STAILQ_INIT(&qh) (upstream ciss.c:3581) - DF-only divergence. Harness: 3/3 runs land a request-pointer wild write at the stack-groomed address; gcc itself warns -Wuninitialized (build.log).
No comments yet.