β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1237

Scatter/gather list copy has no bounds vs the 32-entry hardware SG buffer

Summary

trm_ExecuteSRB at trm.c:434-440: copy loop psg++ dm_segs++ for nseg entries into pSRBSGL (DMA buffer of TRM_MAX_SG_LISTENTRY=32 entries, trm.h:92). DMA tag buffer_dmat nsegments=TRM_NSEG=33 (trm.h:98 btoc(MAXPHYS)+1) -> 33 segments legitimate -> 8B overflow. pass(4) CAM_SCATTER_VALID passes pcsio->sglist_cnt (u_int16_t 0-65535) as nseg at :666-669 with NO cap -> unbounded heap overflow of DMA-coherent SG allocation. Fix: check nseg<=TRM_MAX_SG_LISTENTRY, change buffer_dmat nsegments to 32, validate sglist_cnt.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1237 Β· 12 files
FileTypeDescriptionSize
VERDICT.md verdict full path:line trace, threat model, fix rationale 5.0 KB ↓ raw
README.md readme claim, verdict, runnable-PoC instructions 2.4 KB ↓ raw
trm_sg_overflow.c trigger-source PoC: reachability check (kldstat grep trm) 2.3 KB view raw
build.sh build-script cc -O -Wall -o trm_sg_overflow trm_sg_overflow.c 188 B view raw
run.sh run-script ./trm_sg_overflow 102 B view raw
build.log build-log PoC build, full output 124 B view raw
run.log run-log PoC run on this guest (trm not loaded) 508 B view raw
fix.diff suggested-fix reject nseg > TRM_MAX_SG_LISTENTRY in trm_ExecuteSRB; change buffer_dmat nsegments TRM_NSEG -> TRM_MAX_SG_LISTENTRY 1.1 KB view raw
fix_build.log build-log kernel build rc=0 with all 5 fixes applied; trm.c compiled clean as trm.ko under -Werror 2.1 KB view raw
env.txt environment guest uname, cc version, PCI topology 2.0 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme claim, verdict, runnable-PoC instructions
↓ download raw

DF-1237 β€” README

Finding

trm_ExecuteSRB at sys/dev/disk/trm/trm.c:434-440 copies nseg scatter/gather entries into pSRB->pSRBSGL (a DMA-coherent buffer of TRM_MAX_SG_LISTENTRY=32 entries β€” trm.h:92, trm.c:3498) with no bounds check. Two paths reach it:

  • CAM_SCATTER_VALID at trm.c:666-669 passes pcsio->sglist_cnt (a u_int16_t, 0..65535) straight from userspace β€” up to 65535 Γ— 8B = ~512 KB DMA-coherent heap overflow.
  • bus_dmamap_load callback at trm.c:625: buffer_dmat is created with nsegments=TRM_NSEG=33 (trm.h:98, trm.c:3429) on x86_64 (MAXPHYS=128KB β†’ btoc=32 β†’ TRM_NSEG=33) β†’ legitimate 33rd segment overflows by 1 entry (8 bytes).

Verdict

NOT REPRODUCED on this guest: trm is not in X86_64_GENERIC (sys/config/X86_64_GENERIC has no device trm) and no Tekram DC-395 controller is present in pciconf -lv. trm_ExecuteSRB is dead code on this kernel. PoC prints the reachability status.

Confidence (bug is real): certain β€” traced line-by-line in sys/. Impact ceiling: operator-group β†’ kernel heap corruption via DMA coherent slab overflow, on any host with a Tekram DC-395U/UW adapter and the trm module loaded.

How to reproduce

./build.sh && ./run.sh

Expected on this guest: PoC builds clean, prints "trm driver NOT loaded (no 'device trm' in X86_64_GENERIC)" and "trm_ExecuteSRB is dead code on this kernel". On a host with the trm module loaded and a Tekram adapter, pass(4) CAMIOCOMMAND XPT_SCSI_IO with CAM_SCATTER_VALID and sglist_cnt > 32 overflows pSRBSGL.

Files

Path Purpose
trm_sg_overflow.c PoC: reachability check (kldstat grep trm)
build.sh / run.sh exact build/run commands
fix.diff reject nseg > TRM_MAX_SG_LISTENTRY; change buffer_dmat nsegments TRM_NSEP -> TRM_MAX_SG_LISTENTRY
VERDICT.md full path:line trace, threat model, fix rationale
build.log / run.log PoC build + run outputs
fix_build.log module-build compile validation of fix.diff
env.txt guest uname / cc / device topology
VERDICT.md verdict full path:line trace, threat model, fix rationale
↓ download raw

DF-1237 β€” VERDICT

Finding: trm_ExecuteSRB at sys/dev/disk/trm/trm.c:434-440 copies nseg scatter/gather entries into pSRB->pSRBSGL (a DMA-coherent allocation of TRM_MAX_SG_LISTENTRY=32 entries) with no bounds check. Two paths feed nseg:

  • trm_action CAM_SCATTER_VALID branch at trm.c:666-669 passes pcsio->sglist_cnt (a u_int16_t, 0..65535) verbatim β€” fully user-controlled.
  • The bus_dmamap_load callback path at trm.c:625 is bounded by buffer_dmat's nsegments=TRM_NSEG=btoc(MAXPHYS)+1 β€” but on x86_64 MAXPHYS = 128 KB, so TRM_NSEG = 33 while pSRBSGL has only 32 slots β†’ 1-entry (8-byte) overflow even via the legitimate path.

Status: NOT REPRODUCED on this guest β€” trm is not in X86_64_GENERIC and no Tekram DC-395 controller is present. Confidence (bug is real): certain (traced line-by-line in sys/). Impact ceiling: unbounded DMA-coherent heap overflow (8 bytes Γ— user-supplied nseg) β€” exploitable by any local user with write access to /dev/passN (operator group) once a Tekram adapter and trm module are present.

Mechanism (confirmed line-by-line in sys/)

  1. pSRBSGL is allocated at sys/dev/disk/trm/trm.c:3027 from pACB->sg_dmat, which is created at trm.c:3498 with c /*maxsize*/ TRM_MAX_SG_LISTENTRY * sizeof(SGentry), TRM_MAX_SG_LISTENTRY = 32 (trm.h:92), sizeof(SGentry) = 8 (trm.h:80-83). So pSRBSGL holds exactly 32 entries.

  2. The copy loop in trm_ExecuteSRB (trm.c:431-440): ```c if (nseg != 0) { PSEG psg; bus_dma_segment_t *end_seg; bus_dmasync_op_t op;

    end_seg = dm_segs + nseg; psg = pSRB->pSRBSGL; while (dm_segs < end_seg) { psg->address = dm_segs->ds_addr; psg->length = (u_long)dm_segs->ds_len; totalxferlen += dm_segs->ds_len; psg++; dm_segs++; } `` **No bounds check onnseg.** Each iteration writes 8 bytes past the previouspsg, sonseg > 32` overflows the DMA-coherent slab.

  3. nseg sources: - bus_dmamap_load callback (trm_action CAM_DIR_IN/OUT non-scatter path, trm.c:620-627). buffer_dmat is created with c /*nsegments*/ TRM_NSEG, /* trm.c:3429 */ where TRM_NSEG = btoc(MAXPHYS) + 1 (trm.h:98). On x86_64 MAXPHYS = 128*1024, btoc(128K) = 32, so TRM_NSEG = 33. bus_dma can legitimately produce up to 33 segments for a 128 KB buffer that straddles 33 pages β€” 1 entry (8 bytes) more than pSRBSGL can hold. Off-by-one overflow even without CAM_SCATTER_VALID. - CAM_SCATTER_VALID (trm.c:666-669): c segs = (struct bus_dma_segment *) pcsio->data_ptr; trm_ExecuteSRB(pSRB, segs, pcsio->sglist_cnt, 1); pcsio->sglist_cnt is a u_int16_t from userspace. pass(4) honors CAM_SCATTER_VALID and lets the caller supply a complete SG list. No bounds check. A caller passing sglist_cnt=65535 triggers an 8 Γ— 65535 = 512 KB DMA-coherent overflow.

  4. The overflow corrupts whatever follows pSRBSGL in the DMA-coherent segment pool β€” adjacent SRBs' pSRBSGL/pNextSRB/ pccb pointers, depending on slab layout. On any host with a Tekram adapter, this is a real operator-group β†’ kernel heap corruption vector.

Why it is NOT REPRODUCED on this guest

  • sys/config/X86_64_GENERIC does not contain device trm. The driver is built only as the loadable module trm.ko.
  • The audit guest has no Tekram DC-395U/UW controller in pciconf -lv.
  • PoC trm_sg_overflow.c confirms kldstat -v | grep trm returns empty.

Threat model & privilege boundary

Same as DF-1235: /dev/passN is operator-group on stock devfs; any user in operator can issue CAMIOCOMMAND with XPT_SCSI_IO, CAM_SCATTER_VALID, and a forged ccb_scsiio.sglist_cnt. Loading the trm module is a one-time admin action; the exploit itself is fully unprivileged once the module is loaded.

Fix (authored in fix.diff, applied + compile-validated)

Two-part fix:

  1. Reject nseg > TRM_MAX_SG_LISTENTRY at the top of trm_ExecuteSRB's SG-copy block (trm.c:431-440), returning CAM_REQ_TOO_BIG and the SRB to the free list (mirrors existing failure paths in the file). This closes the CAM_SCATTER_VALID user-controlled overflow.

  2. Change buffer_dmat's nsegments from TRM_NSEG (33) to TRM_MAX_SG_LISTENTRY (32) at trm.c:3429, so bus_dmamap_load cannot produce a 33rd segment that overflows by one entry. With both fixes, the SRB's 32-entry SG buffer cannot be overrun on any path.

Validation

  • fix.diff applies cleanly with patch -p1 --forward (verified).
  • All 5 audit fixes applied together; make -j6 nativekernel KERNCONF=X86_64_GENERIC returned rc=0 with no errors / warnings under -Werror. trm.c was compiled cleanly as the loadable module trm.ko.
  • Fix is not_testable at runtime on this guest (trm not in GENERIC, no Tekram HW).

Fix verification

not_testable

compile validated

nativekernel rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. trm SG list no bounds vs [32] -> DMA heap overflow. trm NOT in GENERIC, no Tekram HW.