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

twe_done indexes twe_lookup[TWE_Q_LENGTH=255] with the controller-supplied 8-bit response_id β€” id 255 reads one pointer past the array (into twe_drive[0]) and dereferences it as a request (wild read + write)

Summary

TWE_Q_LENGTH=255 and twe_setup fills only lookup[0..254], but the response_id is an 8-bit field (0..255). twe_done does tr = twe_lookup[response_id] with no range check: id 255 reads the first 8 bytes of twe_drive[0] (controller/drive-groomable) treated as a request pointer, then reads tr_status at that wild address and writes TWE_CMD_COMPLETE there. Firmware-malfunction class; no userland trigger. Bounds-check-and-drop fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4180 Β· 4 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict + full path:line trace 2.6 KB ↓ raw
env.txt env guest environment 621 B view raw
fix.diff suggested-fix response-id bounds check (git-apply-able) 889 B view raw
verdict.json verdict machine verdict 2.4 KB view raw
VERDICT.md verdict verification verdict + full path:line trace
↓ download raw

DF-4180 β€” Verification Verdict

Status: inconclusive (hardware-gated: no 3ware controller in the QEMU guest; trigger input comes from the controller response queue, not from userland) Impact: none on this guest (wild pointer read+write via HW-controlled index on affected hardware) Confidence: certain (bounds violation provable from source)

Mechanism (source trace)

  1. struct twe_softc layout (twevar.h:135-136): struct twe_request *twe_lookup[TWE_Q_LENGTH]; struct twe_drive twe_drive[TWE_MAX_UNITS]; β€” twe_drive begins exactly where twe_lookup ends (both pointer-aligned).
  2. TWE_Q_LENGTH == TWE_INIT_MESSAGE_CREDITS == 0xff == 255 (twereg.h:146,152). twe_setup() fills sc->twe_lookup[i] for i = 0..254 only (i < TWE_Q_LENGTH, twe.c:140-157); legal request ids are 0..254.
  3. TWE_Response_Queue.u.response_id is an 8-bit field (twereg.h:331-340) β€” the controller can return 0..255.
  4. twe_done() (twe.c:1196-1210): rq.value = TWE_RESPONSE_QUEUE(sc); tr = sc->twe_lookup[rq.u.response_id]; β€” no range check. response_id == 255 reads twe_lookup[255], i.e. the first 8 bytes of twe_drive[0] = td_size (controller-reported capacity, u32) and td_cylinders (derived, int) β†’ a 64-bit, partially groomable bogus pointer.
  5. if (tr->tr_status != TWE_CMD_BUSY) (twe.c:1203) β€” wild read; tr->tr_status = TWE_CMD_COMPLETE (twe.c:1205) β€” wild write of the constant 2 at offset offsetof(struct twe_request, tr_status); then twe_remove_busy/twe_enqueue_complete chase the bogus pointer further (twe.c:1209-1210).

Preconditions: a 3ware Escalade controller that emits a response id of 255 (firmware malfunction, malicious device, or hostile DMA β€” e.g. Thunderbolt-attached FPGA). Realistically a robustness/panic bug; the groomed-pointer variant requires drive-size grooming of twe_drive[0].

Why it cannot be reproduced on this guest

No 3ware PCI device; twe never attaches; the response queue is only readable from the controller itself. There is no userland-reachable trigger by construction.

Fix validation

Bounds-check rq.u.response_id >= TWE_Q_LENGTH β†’ consume + drop (fix.diff); applies cleanly, batch-compiled into X86_64_GENERIC with the rest of this batch (fix_build.log).

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

inconclusive
baseline no→ patch + rebuild →patched clean

Compile-validated only: fix.diff applied cleanly and batch-built into X86_64_GENERIC with -Werror, rc=0 (fix_build.log). Behavioral test impossible (requires a controller that emits id 255).

fix_build.log excerpt (twe.c -Werror in-kernel + module).
↓ fix.diffper-fix-DF-4180

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (array layout + id-width trace); no runnable trigger by construction; fix.diff + fix_build.log

PoC changes

n/a (no userland trigger - response ids are produced by the controller)

Verified recommended fix

Bounds-check rq.u.response_id >= TWE_Q_LENGTH in twe_done and consume-and-drop such responses.

Verdict

Source-certain OOB index: twe_done uses the controller-supplied 8-bit response_id (TWE_Response_Queue.u.response_id, twereg.h:336) to index sc->twe_lookup[TWE_Q_LENGTH] (twe.c:1199) but TWE_Q_LENGTH==255 (twereg.h:152) and twe_setup fills only ids 0..254 (twe.c:140); a response_id of 255 reads twe_lookup[255] = the first 8 bytes of twe_drive[0] (td_size|td_cylinders, controller-groomable) as a struct twe_request *, then reads tr->tr_status and writes TWE_CMD_COMPLETE (twe.c:1203-1205) through that wild pointer. No userland trigger exists (the id comes from the controller response queue); not reproducible on this guest (no 3ware device at all). Robustness/panic-class on affected hardware; requires firmware malfunction or a malicious DMA device.