ips_copperhead_reinit ignores ips_copperhead_queue_init's return: freed status queue left dangling β interrupt-context UAF read/write; every forced reinit also leaks the previous queue mapping
Summary
On a map failure the queue-init exit path frees the queue but leaves sc->copper_queue dangling and the reinit ignores the result: the next COPPER_SCE_BIT interrupt increments nextstatus (WRITE to freed memory), reads status[statnum] (READ from freed), and writes base_phys_addr from freed memory into the status-queue register. Each successful reinit also leaks the previous queue's tag+mapping. Check-return + NULL-pointer + free-before-realloc fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4615 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| env.txt | β | 273 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| manifest.json | β | 679 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-4615 β ips_copperhead_reinit ignores ips_copperhead_queue_init failure β dangling copper_queue UAF in the interrupt handler; every forced reinit leaks the previous queue
What
sys/dev/raid/ips/ips.c:730 (unchecked call), ips.c:672-675 (error path
frees the queue but leaves sc->copper_queue dangling), ips.c:745-760
(interrupt-driven UAF read/write through the dangling pointer),
ips.c:650 (fresh allocation per reinit without freeing the old one).
UAF path
ips_copperhead_queue_init allocates sc->copper_queue (ips.c:650-654). On
any failure after the allocation (status-queue map load yields phys 0,
ips.c:662-665) the exit path runs bus_dmamem_free(dmatag, sc->copper_queue,
dmamap) (ips.c:673) but neither NULLs sc->copper_queue nor records the
failure for the caller β and the caller ignores the return anyway:
ips_copperhead_queue_init(sc); /* ips.c:730 β return value ignored */
ips_copperhead_reinit then continues as if the queue were live. On the next
COPPER_SCE_BIT interrupt, ips_copperhead_intr (ips.c:770) β
ips_copperhead_cmd_status (ips.c:751-757):
statnum = sc->copper_queue->nextstatus++; /* WRITE to freed memory */
...
value = sc->copper_queue->status[statnum]; /* READ of freed memory */
ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr + 4*statnum);
β use-after-free read and write (nextstatus++) in interrupt context,
plus a card register write driven by freed-memory contents.
Additional defect in the same lines: on successful re-init (every forced
reset from ips_timeout, ips.c:355βips.c:738) a fresh queue is allocated at
ips.c:650 while the previous sc->copper_queue (from the original attach) is
never freed β one DMA tag + DMA-mapped ips_copper_queue_t (~528 B) leaked
per reset; a flapping card bleeds kernel memory until reset succeeds or the
system runs out.
The error path also frees with possibly-NULL handles when the failure happens
before allocation (tag-create failure β bus_dmamem_free(NULL, NULL, NULL),
ips.c:672-674) β NULL-deref panic on the same unchecked path.
Build / run
Requires an IBM ServeRAID copperhead-family controller (0x1014:0x002E et al).
Audit guest has none (env.txt); no QEMU device model exists.
Contents
VERDICT.md, env.txt, fix.diff, manifest.json, verdict.json.
VERDICT β DF-4615
Status: not_reproduced β no copperhead (0x1014:0x002E/0x0250) ServeRAID
controller on the audit guest (env.txt), and QEMU ships no device model for
one, so the interrupt path cannot be entered. Static trace is complete and
line-accurate; confidence certain.
Proof
-
Failure frees the queue but keeps the pointer.
ips_copperhead_queue_init(ips.c:629-676): afterbus_dmamem_allocsucceeds (ips.c:650), any later failure (base_phys_addr == 0at ips.c:662) goes toexit:which callsbus_dmamem_free(dmatag, sc->copper_queue, dmamap)(ips.c:673) andbus_dma_tag_destroy(dmatag)(ips.c:674) βsc->copper_queuekeeps pointing at the freed mapping, the local dmatag/dmamap are destroyed. -
The only caller ignores the result.
ips_copperhead_reinit(ips.c:730):ips_copperhead_queue_init(sc);β no check, reinit returns 0 (success) as long as the card posts. -
The next interrupt walks the freed queue.
ips_copperhead_intr(ips.c:770-777) onCOPPER_SCE_BITβips_copperhead_cmd_status(ips.c:745-760): -sc->copper_queue->nextstatus++β UAF write in ithread context; -sc->copper_queue->status[statnum]β UAF read (the completion status the driver trusts); -ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr + 4*statnum)β card register write driven by freed-memory contents (stale/garbage phys address handed to hardware).
Because the status-queue semantics feed status.fields.command_id into
sc->commandarray[cmdnumber] (ips.c:772, the DF-1758 indexing), a freed
then reused queue chunk corrupts the completion pipeline outright.
-
Leak on success. Each
ips_copperhead_reinit(every forced watchdog reset, ips.c:355) allocates a brand-new queue at ips.c:650 while the oldsc->copper_queuemapping + tag are never released β one ~528-byte DMA-mapped block and one tag leaked per reset. A card that flaps under the watchdog bleeds kernel memory indefinitely. -
NULL-deref variant: if
bus_dma_tag_createitself fails (ips.c:636), the exit path runsbus_dmamem_free(NULL, NULL, NULL)(ips.c:672-674) β NULL tag deref panic.
Impact
Interrupt-context UAF read/write + hardware register write from freed memory (error path), and unbounded DMA-memory leak on repeated watchdog resets (success path). Precondition: copperhead ServeRAID hardware plus (for the UAF) a queue-map failure during reinit. Low severity: resource-failure trigger, no unprivileged reachability, realistic worst case kernel crash / memory exhaustion on failing hardware. (The DF-1758 command-id OOB already gives a malicious card strictly more power on this hardware; DF-4615 matters for non-malicious failing cards.)
Fix
Check the return of ips_copperhead_queue_init in ips_copperhead_reinit
and fail the reinit; NULL sc->copper_queue after freeing; free the previous
queue before allocating a new one. See fix.diff.
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
not_testableNo copperhead hardware or device model on the guest, so no A/B kernel run is possible. fix.diff eliminates the dangling pointer by construction (NULL after free + checked return + free-before-realloc).
fix.diff (ips.c: exit path NULLs sc->copper_queue; queue_free helper called before re-alloc; reinit checks the init result and fails).
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (five-step trace: ips.c:629-676 alloc/free-without-NULL, ips.c:730 ignored return, ips.c:745-760 interrupt-context UAF read+write, ips.c:650 leak-per-reinit, ips.c:672-674 NULL-handle variant); env.txt (device absent)
PoC changes
none - no PoC possible without copperhead hardware (QEMU has no ServeRAID device model); static trace only.
Verified recommended fix
Check ips_copperhead_queue_init's return in ips_copperhead_reinit, NULL sc->copper_queue after freeing, and free the previous queue mapping before allocating a new one on reinit.
Verdict
ips_copperhead_reinit ignores the return of ips_copperhead_queue_init (ips.c:730). On a queue-map failure the init's exit path bus_dmamem_free/bus_dma_tag_destroy's the status queue (ips.c:672-674) but leaves sc->copper_queue dangling, and reinit reports success; the next COPPER_SCE_BIT interrupt runs ips_copperhead_cmd_status (ips.c:751-757) which increments nextstatus on freed memory (UAF write), reads status[statnum] from freed memory (UAF read), and feeds sc->copper_queue->base_phys_addr from freed memory into a hardware register write. Additionally every successful forced reinit allocates a fresh queue (ips.c:650) without freeing the previous one - one DMA tag + ~528-byte mapped block leaked per watchdog reset; and a tag-create failure takes the exit path with NULL handles (bus_dmamem_free(NULL,NULL,NULL) NULL deref). Not reproduced on the audit guest: no copperhead ServeRAID PCI device exists there (env.txt) and QEMU has no device model. Static trace complete; low severity because the trigger is resource failure on failing hardware with no unprivileged reachability.
No comments yet.