ciss_notify_complete: fatal-error path leaves ciss_periodic_notify dangling -> double-ownership, double post, xpt_done twice (CAM UAF)
Summary
The fatal-status exit of ciss_notify_complete calls ciss_release_request(cr) WITHOUT clearing sc->ciss_periodic_notify (the two sibling exits clear it). The next 10s ciss_periodic tick restarts the chain via the stale pointer: the request, by then re-allocated from the free list to a CAM I/O, is preened, CDB-clobbered and POSTED a second time; both completions run the CAM callback -> xpt_done(ccb) twice for one ccb and the request is double-released onto ciss_free (STAILQ corruption). VERIFIED: state-machine replica of the free-list/notify ownership - the CAM-owned request re-posted (posts=3), CAM callback running 3x for ONE ccb; -DFIXED replica clean. Adapter returns a fatal status (PROTOCOL_ERROR/HARDWARE_ERROR/CONNECTION_LOST) for the Notify-on-Event command - a real distressed-firmware condition; 10s later the driver corrupts its request pool and double-completes an in-flight CAM CCB. One-line fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3728 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | faithful userspace replica of the cited kernel path (real macros/struct constants) | 7.5 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] | 1.4 KB | view raw |
| run.2.log | run-log | repeat run (stability) | 1.4 KB | view raw |
| run.3.log | run-log | repeat run (stability) | 1.4 KB | view raw |
| fix_run.log | run-log | -DFIXED section (persist_fix reads this) | 481 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, path:line, harness proof, fix | 4.1 KB | β raw |
| fix.diff | suggested-fix | clear sc->ciss_periodic_notify before ciss_release_request in the notify fatal-error path | 621 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 | 3.0 KB | view raw |
| verdict.json | verdict | machine verdict (persist_poc.py schema) | 2.5 KB | view raw |
DF-3728 β ciss_notify_complete fatal-error path leaves ciss_periodic_notify dangling β double-ownership / double completion of a ciss_request
File: sys/dev/raid/ciss/ciss.c:3619-3627 (fatal path releases the
request but does NOT clear sc->ciss_periodic_notify; the two sibling exit
paths DO clear it: ciss.c:3648 and ciss.c:3653).
Class: use-after-free-style request lifetime bug (double ownership β
double post β double completion β xpt_done(ccb) twice β CAM UAF),
CWE-672/DF-3672-family "CCB lifetime" lesson.
Reproduce
./build.sh && ./run.sh
Expected output
Baseline: step4 shows the CAM-owned request re-posted by the notify chain
(posts=3), step5 shows the CAM callback running 3Γ for ONE ccb, and
[BUG REPRODUCED]. Fixed replica (-DFIXED = clear the pointer in the
fatal path): periodic_notify = NULL (cleared), no reuse.
Trigger (kernel)
Adapter returns a fatal status (β SUCCESS/TARGET_STATUS/TIMEOUT β e.g.
CISS_CMD_STATUS_PROTOCOL_ERROR, HARDWARE_ERROR) for the Notify-on-Event
command; 10 s later ciss_periodic (ciss.c:3427-3430) restarts the chain
through the stale pointer.
Fix
fix.diff (clear ciss_periodic_notify before releasing). Compile-validated
with the other 6 ciss fixes at -Werror (fix_build.log); behaviorally
validated by the -DFIXED harness 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-3728 VERDICT
Finding
ciss_notify_complete() (sys/dev/raid/ciss/ciss.c:3598-3663) has three
exit paths for the completed notify request:
- fatal error (ciss.c:3619-3627):
ciss_release_request(cr); sc->ciss_flags &= ~CISS_FLAG_NOTIFY_OK; return;βsc->ciss_periodic_notifyis NOT cleared β dangling pointer to a request that is now on the free queue. - notifier-aborted (ciss.c:3642-3649): releases AND clears (3648).
- event queued (ciss.c:3650-3656): clears (3653).
The consumer, ciss_notify_event() (ciss.c:3517-3595), starts with
cr = sc->ciss_periodic_notify; (ciss.c:3526) and only allocates a fresh
request if (cr == NULL) (ciss.c:3529). After a fatal notify status,
CISS_FLAG_NOTIFY_OK is clear, so the next ciss_periodic() tick
(ciss.c:3427-3430, β€10 s later) calls ciss_notify_event(), which follows
the stale pointer β a request that has been back on ciss_free and can have
been handed to any other submitter (ciss_get_request, ciss.c:2409).
Consequence chain (all steps mirrored in the harness)
ciss_get_requestre-initializes the slot (ciss.c:2412-2416) and a CAM I/O owns it:cr_data = csio->data_ptr,cr_complete = ciss_cam_complete(ciss.c:3078-3081), posted to the adapter.- The stale notify path reuses it:
cr->cr_data != NULLso the 512-byte buffer allocation is skipped (ciss.c:3544-3547 β reads the NEW owner's data pointer),ciss_preen_command(ciss.c:3550) rewrites the header / error-info pointer, the notify CDB overwrites the CAM CDB (ciss.c:3554+), andciss_start(ciss.c:3579) posts the same tag a second time. - The adapter completes each post;
ciss_complete(ciss.c:2164-2204) processes both completions: the second one hits"completing non-busy request"(ciss.c:2169-2170) and then invokes the CAM callback again βciss_cam_completeβxpt_done((union ccb *)csio)(ciss.c:3286) twice for one CCB β CAM-side use-after-free, plusciss_release_requesttwice β the same request linked twice onciss_free(STAILQ cycle/corruption).
Additionally the CAM data buffer keeps being DMA'd per the clobbered SG state β the in-flight command is corrupted mid-execution.
Reachability / threat model
Precondition: the adapter returns a fatal command status for the
Notify-on-Event command (CISS_CMD_STATUS_PROTOCOL_ERROR,
CISS_CMD_STATUS_HARDWARE_ERROR, CONNECTION_LOST, ABORT_FAILED, β¦).
That is a real-world condition on distressed/flaky Smart Array firmware β
precisely the situations that also trip the heartbeat watchdog. No
unprivileged-local trigger: this is a firmware-response-driven defect
(same trust boundary as DF-1189..DF-1192).
Verification (no HP Smart Array on the audit guest)
harness.c models the request pool, free queue, notify state machine and
completion dispatch 1:1 (kernel line cites inline). Baseline run
(run.log, stable across run.2/run.3):
- step4: tag1 now posts=3 (DOUBLE POST) with the CAM buffer retained;
- step5: CAM callback (xpt_done) ran 3 time(s) for ONE ccb;
- [BUG REPRODUCED].
-DFIXED replica (adds sc->ciss_periodic_notify = NULL; to the fatal
path): periodic_notify = NULL (cleared) β the chain restarts on a fresh
request, CAM request untouched.
Fix validation
fix.diff: one statement (sc->ciss_periodic_notify = NULL;) beforeciss_release_request(cr)in the fatal path.fix_apply.log: clean dry-run on guest/usr/src.fix_build.log:ciss.korebuilt with all 7 ciss fixes at-Werror, link rc=0.- Harness
-DFIXEDrun: dangling reuse GONE.
Bottom line: reproduced (harness-level, firmware-gated path); request lifetime defect leading to double completion / CAM CCB UAF; 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
fixedsc->ciss_periodic_notify = NULL added before release in the fatal path. Module rebuilt at -Werror with all 7 fixes; -DFIXED replica clears the pointer and restarts on a fresh request.
fix.diff; fix_apply.log; fix_build.log; fix_run.log (periodic_notify = NULL (cleared))
Confirmed kernel references
Detail
Exploit chain
fatal notify status -> dangling periodic_notify -> free-list reuse by CAM -> double post + command clobber -> double completion -> xpt_done x2+ / double free-queue insertion
Evidence (decisive lines)
run.log step4 posts=3 (DOUBLE POST), step5 CAM callback ran 3x for ONE ccb, [BUG REPRODUCED]; fix_run.log shows pointer cleared
PoC changes
harness written fresh; initial FIXED-path printf dereferenced the NULL pointer - fixed to print the cleared state
Verified recommended fix
clear sc->ciss_periodic_notify before ciss_release_request in the notify fatal-error path
Verdict
REPRODUCED (harness-level; firmware-gated path): the fatal-error exit of ciss_notify_complete (ciss.c:3619-3627) releases the notify request without clearing sc->ciss_periodic_notify (sibling exits clear it at 3648/3653). The next ciss_periodic tick restarts the chain via the stale pointer (ciss.c:3526): the request, by then re-allocated to a CAM I/O, is preened/CDB-clobbered and POSTED A SECOND TIME (ciss.c:3550,3579); both completions run the CAM callback -> xpt_done(ccb) multiple times for one ccb (ciss.c:3286) and double-release the request onto ciss_free -> STAILQ corruption / CAM UAF.
No comments yet.