# DF-4274 VERDICT — double free-queue insertion in the deferred-busdma passthru error path (latent UAF; currently unreachable on default x86-64)

## Bottom line

A passthrough request whose `bus_dmamap_load()` defers (`EINPROGRESS`) and
whose deferred CL submission subsequently fails is inserted into
`TW_OSLI_FREE_Q` **twice** — once by `twa_map_load_data_callback()`'s error
path and once by the ioctl thread's own unwind — corrupting the request queue
and enabling cross-thread reuse of one `tw_osli_req_context` (double `kfree`
of `req->data` / use-after-free). The defect is deterministic once its two
preconditions hold; on this DragonFly/x86-64 build the deferral precondition
cannot currently be produced by the twa tags, making this a **latent** bug
that turns live the day busdma bouncing applies to these loads.

## Proof (code path, all in `sys/dev/raid/twa/tw_osl_freebsd.c`)

1. `tw_osli_fw_passthru()` calls `tw_osli_map_request(req)` (`:953`).
2. `tw_osli_map_request()` loads the shared `sc->ioctl_map` with
   `BUS_DMA_WAITOK` (`:1371-1374`). DragonFly `bus_dmamap_load()` queues the
   map and returns `EINPROGRESS` when the bounce zone cannot satisfy
   `map->pagesneeded` (`sys/platform/pc64/x86_64/busdma_machdep.c:640-657,
   793-805`). The driver marks the request
   `TW_OSLI_REQ_FLAGS_IN_PROGRESS` (`:1397-1398`), reports success, and the
   ioctl thread enters its wait loop (`:957`).
3. The deferred load is resumed from `busdma_swi()` (softint context) which
   re-invokes the stored callback `twa_map_load_data_callback`
   (`busdma_machdep.c:1436-1448`) — note: *outside* the driver's `io_lock`,
   on the shared map.
4. If `tw_cl_fw_passthru()` returns an error synchronously inside that
   callback (`tw_cl_io.c:283` EBUSY when the CL request pool is exhausted;
   `tw_cl_io.c:341-348` submit failure — both reachable under load/controller
   fault), the `out:` error path (`:1273-1284`) runs:
   - `req_pkt->tw_osl_callback()` → `tw_osl_complete_passthru()` (`:1064`):
     sets `state = COMPLETE`, removes from `TW_OSLI_BUSY_Q`, unmaps, and —
     because `TW_OSLI_REQ_FLAGS_IN_PROGRESS` *is* set, the early-return at
     `:1098` does not fire — wakes the sleeping originator (`:1101-1107`).
   - then `if (req->flags & TW_OSLI_REQ_FLAGS_IN_PROGRESS)
     tw_osli_req_q_insert_tail(req, TW_OSLI_FREE_Q);` (`:1282-1283`) —
     **first insertion**.
5. The woken ioctl thread sees `state == TW_OSLI_REQ_STATE_COMPLETE`, exits
   the loop (`:957`), runs its `copyout` and then unwinds through
   `fw_passthru_err:` `:1040-1049` → `tw_osli_req_q_insert_tail(req,
   TW_OSLI_FREE_Q)` — **second insertion of the same link**.

Effect of inserting an already-queued `tw_cl_link`: with the request as the
only element, the second `INSERT_TAIL` sets `req->link.prev = &req->link`
(self-ring); subsequent `remove_head` operations then hand out the *same*
request to two concurrent ioctl/CAM threads while the first owner still
executes its error path (`kfree(req->data)` at `:1047-1048` may free a buffer
the second owner has already re-assigned). Class: queue corruption →
request-context UAF / double-free.

The synchronous (non-deferred) failure path is *not* affected:
`IN_PROGRESS` is clear there, `tw_osl_complete_passthru` early-returns at
`:1098` without waking, and only the ioctl thread inserts.

## Reachability (why it is latent here)

For `EINPROGRESS`, `_bus_dmamap_load_buffer` must count bounce pages
(`busdma_machdep.c:617-632`), which requires `addr_needs_bounce()` to fire
(`:186-193`): either `paddr > lowaddr` (the twa tags use
`lowaddr = highaddr = BUS_SPACE_MAXADDR`, so never) or the global
`bounce_alignment` with `paddr & (alignment-1)` — the tags' alignment is
`sc->alignment = TWA_ALIGNMENT() = 4`, and the bounce test is applied at
*page* granularity, so a page-aligned physical address can never be
misaligned mod 4. Hence `pagesneeded == 0` always for twa loads on default
x86-64 (verified `hw.busdma.bounce_alignment: 1`, `max_bpages: 1024` on the
guest — see `../DF-4273/env.txt`), and the `EINPROGRESS` path is dead *in
this configuration*. It becomes live under any future busdma change that
makes these tags bounce (lowaddr constraints, sub-page alignment checks) or
on platforms with real DMA restrictions. The double-insert itself is
unconditional code that will execute the moment deferral occurs, and the
`io_lock` serialization that normally protects the shared `ioctl_map`
(`:1368-1375`, `:1463-1486`) is also bypassed in the SWI-resumed path — the
map can be loading for two requests at once.

## Verification

No runtime PoC is possible on the audit guest: it needs the 3ware HBA
(absent; `/dev/twa0` does not exist) *and* a busdma deferral this platform
cannot generate for these tags. The finding is a static proof; status
`untested`, severity Low (latent, hardware-gated, root-gated device).

## Fix

`fix.diff`: in `twa_map_load_data_callback()`'s error path, only return the
request to the free queue for *asynchronous* owners (CAM I/O, whose
originator `twa_action` has already returned — `tw_osl_cam.c:335-342`
confirmed); a PASSTHRU request's originator is still blocked in
`tw_osli_fw_passthru()` and always returns it itself at `fw_passthru_err:`
(`:1049`). One-line conditional; not build-validated (driver cannot attach
on this guest) → `fix_status: not_testable`.

## 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.
