Memory leak of targ_cmd_descr in targread abort_queue drain loop
Summary
When targread() drains abort_queue each targ_cmd_descr is TAILQ_REMOVEd and its user_ccb delivered to caller but descr itself never kfreed. descr allocated by targgetdescr (kmalloc) moved to abort_queue by targstart on send failure or abort_all_pending. Every aborted-then-read descriptor (~80 bytes) permanently leaked. Parallel user_ccb_queue drain path correctly frees via targreturnccb->targfreeccb (kfree at 1012) and targdtor frees remaining abort_queue entries at teardown (547). A RESTRICTEDROOT user can drive this leak unboundedly by submitting CCBs that deterministically fail (dxfer_len>MAXPHYS) then reading them back exhausting kernel memory. [VERIFIED pass2: reproduced - 2000 oversized CTIOs drain 2000 pointer-matched entries while M_TARG grows monotonically 80 bytes per descr permanent; free-in-drain fix-validated delta 0]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2287 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc2287.c | β | 4.1 KB | view raw | |
| targsim.c | β | 13.9 KB | view raw | |
| run.log | β | 920 B | view raw | |
| env.txt | β | 191 B | view raw | |
| fix.diff | β | 466 B | view raw | |
| verdict.json | β | 2.4 KB | view raw | |
| README.md | β | 1.6 KB | β raw |
DF-2287 β targread() abort_queue drain leaks every targ_cmd_descr
What (sys/bus/cam/scsi/scsi_target.c:921-937)
The abort_queue drain loop TAILQ_REMOVEs each targ_cmd_descr, writes
CAM_REQ_ABORTED into the user CCB, hands the user_ccb pointer back via
uiomove β and never kfree()s the descriptor. Descriptors enter
abort_queue via targstart's send-failure path (:674-684) or
abort_all_pending (:1084-1089); the only other free is targdtor's drain
of what is STILL QUEUED at teardown (:545-547) β a drained-and-delivered
descriptor is unreachable garbage.
Rig
Same as DF-2286 (targsim). Deterministic send failure: XPT_CONT_TARGET_IO with dxfer_len > MAXPHYS -> targsendccb -> cam_periph_mapmem (cam_periph.c:702-707) rejects with EPERM -> targstart error path -> abort_queue.
Reproduce (guest, root)
cc -O -Wall -o poc2287 poc2287.c ./poc2287 # 2000 oversized CTIO writes + 2000 reads
Result (run.log; multiple independent runs)
submitted 2000 oversized CTIOs read back 2000 descriptors (pointer match 2000) M_TARG line: TARG 1.95K 156K -> TARG 3.91K 313K ... -> TARG 5.86K 469K ... -> TARG 7.81K 625K (monotone growth) bytes-per-descr = 80 (sizeof(struct targ_cmd_descr))
Each read() drains exactly one descriptor from abort_queue and leaks exactly one ~80-byte M_TARG allocation; growth is linear and permanent. A RESTRICTEDROOT daemon drives it unboundedly.
Fix
fix.diff kfrees user_descr after the successful uiomove in the drain loop. Validated on the fix kernel (VERDICT.md): M_TARG stays flat.
Fix verification
fixedConfirmed kernel references
- sys/bus/cam/scsi/scsi_target.c:921
- sys/bus/cam/scsi/scsi_target.c:924
- sys/bus/cam/scsi/scsi_target.c:931
- sys/bus/cam/scsi/scsi_target.c:937
- sys/bus/cam/scsi/scsi_target.c:674
- sys/bus/cam/scsi/scsi_target.c:681
- sys/bus/cam/scsi/scsi_target.c:545
- sys/bus/cam/scsi/scsi_target.c:1041
- sys/bus/cam/cam_periph.c:702
Detail
Evidence (decisive lines)
run.log: 2000/2000 pointer-matched drains; vmstat TARG 1.95K/156K -> 3.91K/313K -> 5.86K/469K -> 7.81K/625K across runs; ~80 B/descr
PoC changes
Authored from scratch; vmstat -m human-suffix parsing (1.95K/156K) had to be added (plain sscanf read 1 and 156); printf type fixes.
Verified recommended fix
kfree(user_descr, M_TARG) after the successful uiomove in the drain loop (fix.diff).
Verdict
REPRODUCED on the BUSAUDIT kernel with the targsim rig: 2000 deterministically-failing CTIOs (XPT_CONT_TARGET_IO with dxfer_len 1 MiB > MAXPHYS; cam_periph_mapmem rejects at cam_periph.c:702-707, targstart :674-684 parks each descr on abort_queue) followed by 2000 reads drained all descriptors with exact user_ccb pointer matches, while M_TARG grew monotonically across four independent measurement points: 1.95K/156K -> 3.91K/313K -> 5.86K/469K -> 7.81K/625K (vmstat -m TARG) = exactly ~80 bytes (sizeof targ_cmd_descr) leaked per drained descriptor, permanently. targread abort_queue drain (scsi_target.c:921-937) TAILQ_REMOVEs each descr and hands back the pointer without kfree; no other free path can reach a drained descriptor (targfreeccb is only called for user_ccb_queue entries; targdtor :545-547 only drains what is still queued). RESTRICTEDROOT gated; unbounded kernel-memory exhaustion.
No comments yet.