Heap overflow via unbounded device-supplied event_info_size in virtio-scsi
Summary
vtscsi_read_config at virtio_scsi.c:301: event_buf_size=event_info_size from device config (no upper bound). vtscsi_enqueue_event_buf :1888-1890: bzero(event,size) and sglist_append(event,size) where event=&event_bufs[i] (16 bytes). size>16 -> bzero overflows past slot. size>64 -> past array into softc/heap. Device then writes event of announced size -> attacker-controlled heap write. Only lower-bound checked at :1922. Sibling of DF-1213/DF-1310 virtual device patterns. Malicious virtio-scsi hypervisor. Fix: clamp event_buf_size to sizeof(struct virtio_scsi_event).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1352 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | function-level harness: unbounded event_info_size heap overflow in bzero/sglist_append | 1.9 KB | view raw |
| fix.diff | suggested-fix | git-apply-able diff that adds the guard verified at the function level | 938 B | view raw |
| build.sh | build-script | exact build: cc -O2 -Wall -o trigger trigger.c | 125 B | view raw |
| run.sh | run-script | exact run: ./trigger | 111 B | view raw |
| run.log | run-log | decisive harness output BEFORE-FIX + AFTER-FIX | 394 B | view raw |
| fix_build.log | build-log | single batched patched-kernel build (rc=0); proves all 15 fixes compile | 5.6 MB | β download |
| env.txt | environment | uname, guest cc version, patch list | 500 B | view raw |
| VERDICT.md | verdict | narrative analysis: mechanism, why not live, fix | 2.5 KB | β raw |
| README.md | readme | human-facing reproduce instructions | 2.1 KB | β 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 |
DF-1352 β virtio_scsi heap overflow via unbounded event_info_size
Summary
Clamp event_buf_size to sizeof(struct virtio_scsi_event) after reading it from device config.
How to reproduce
This bug lives in a device driver not reachable from the booted QEMU guest as
an unprivileged user (maxx) because the required hardware is absent (AMD GPU,
RAID HBA, sound PCI, AMD SCSI) or the trigger requires a malicious hypervisor
(virtio_net, virtio_scsi). The bug is reproduced at the function level by
porting the cited code path into a userspace harness that drives it with the
attacker-controlled inputs the original code fails to validate.
Build
cc -O2 -Wall -o trigger trigger.c
Run
./trigger
Expected
- BEFORE-FIX section shows the bug signature (SIGFPE for div-by-zero, OOB index report for overflows, wraparound count for underflows, over-read length for info leaks).
- AFTER-FIX section shows the guard from
fix.diffcleanly rejecting the attacker input.
The same harness was compiled and run on the patched single-fix kernel
(DragonFly 6.5-DEVELOPMENT #1) β output is identical because the harness
intentionally demonstrates both the unpatched and patched function logic side
by side, and the userspace behavior of those branches is independent of the
kernel. The patched kernel build (fix_build.log) confirms all 15 fix.diffs
compile cleanly in the real kernel / module context.
Impact classification
panic β gated by absent hardware / malicious-hypervisor precondition on
this guest; live trigger from maxx is not possible. See VERDICT.md for
the threat-model analysis.
Files
trigger.cβ function-level harness porting the cited code path.fix.diffβ git-apply-able unified diff againstsys/.build.sh/run.shβ exact repro commands.run.logβ decisive harness output (BEFORE-FIX + AFTER-FIX).fix_build.logβ patched kernel build log (proves all 15 fixes compile).VERDICT.mdβ full narrative analysis.manifest.jsonβ machine-readable catalog.
Host has no gcc; harnesses built in guest as maxx with cc (DragonFly gcc 8.3).
DF-1352 β VERDICT
REPRODUCED at the function level (impact: panic).
Mechanism
vtscsi_read_config() at virtio_scsi.c:301 copies scsicfg.event_info_size (read from the virtio-scsi PCI config space, controlled by the hypervisor) into sc->vtscsi_event_buf_size with no upper bound. vtscsi_enqueue_event_buf() at :1888-1890 then does bzero(event, size) and sglist_append(event, size) where event = &sc->vtscsi_event_bufs[i] (each slot is exactly sizeof(struct virtio_scsi_event) = 16 bytes; the array has 4 slots = 64 bytes). size > 16 overflows the slot; size > 64 overflows the array into adjacent softc / heap. A malicious virtio-scsi hypervisor then writes events of the announced size -> attacker-controlled heap write. The lower-bound check at :1922 (size < sizeof(struct virtio_scsi_event)) only prevents under-size, not over-size.
Why not live-reproduced on the QEMU guest
The QEMU guest has virtio_blk (vtblk0) but no virtio_scsi device attached, so the virtio_scsi driver does not probe. The bug is real and reachable on any system with a malicious/compromised virtio-scsi backend; it is a guest-robustness / hypervisor-trust-boundary issue, not a maxx-reachable syscall.
Recommended fix
In vtscsi_attach_after_features (after reading event_info_size into sc->vtscsi_event_buf_size), clamp: 'if (sc->vtscsi_event_buf_size > sizeof(struct virtio_scsi_event)) sc->vtscsi_event_buf_size = sizeof(struct virtio_scsi_event);'. This bounds the subsequent bzero/sglist_append to the actual struct the host code uses.
Kernel references (confirmed during verification)
- sys/dev/virtual/virtio/scsi/virtio_scsi.c:301 (event_buf_size = scsicfg.event_info_size, no upper bound)
- sys/dev/virtual/virtio/scsi/virtio_scsi.c:1888-1890 (bzero(event, size) and sglist_append(event, size) with event in 16-byte slots)
- sys/dev/virtual/virtio/scsi/virtio_scsi.c:1922 (only lower-bound checked)
- sys/dev/virtual/virtio/scsi/virtio_scsivar.h:80-82 (event_bufs[4] array, 64 bytes total)
Build/run
- Build harness:
cc -O2 -Wall -o trigger trigger.c - Run harness:
./trigger - Apply fix:
cd /usr/src && patch -p1 < fix.diff - Build single-fix kernel:
make -j6 nativekernel KERNCONF=X86_64_GENERIC(validated β seefix_build.log; all 15 fixes compile cleanly in one batched build, rc=0).
Tested kernels
- baseline:
DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 - patched :
DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 21:51:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Fix verification
fixedVALIDATED via batched single-fix kernel build: virtio_scsi.c (in GENERIC β device virtio_scsi) compiles cleanly with the fix (rc=0). Harness BEFORE-FIX shows 240-byte/1024-byte overflow; AFTER-FIX clamps to 16.
baseline #0 BEFORE-FIX: bzero(&event_bufs[0], 256) overflows 16-byte slot by 240B; 4 slots overflow 64-byte array by 1024B into heap. patched #1 cc6aa06b AFTER-FIX: event_buf_size clamped to 16; virtio_scsi built into kernel rc=0.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- v
- i
- r
- t
- u
- a
- l
- /
- v
- i
- r
- t
- i
- o
- /
- s
- c
- s
- i
- /
- v
- i
- r
- t
- i
- o
- _
- s
- c
- s
- i
- .
- c
- :
- 3
- 0
- 1
- s
- y
- s
- /
- d
- e
- v
- /
- v
- i
- r
- t
- u
- a
- l
- /
- v
- i
- r
- t
- i
- o
- /
- s
- c
- s
- i
- /
- v
- i
- r
- t
- i
- o
- _
- s
- c
- s
- i
- .
- c
- :
- 1
- 8
- 8
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- v
- i
- r
- t
- u
- a
- l
- /
- v
- i
- r
- t
- i
- o
- /
- s
- c
- s
- i
- /
- v
- i
- r
- t
- i
- o
- _
- s
- c
- s
- i
- .
- c
- :
- 1
- 9
- 2
- 2
Detail
Exploit chain
none β primitive lives in virtio_scsi attach path driven by the virtio PCI config space (hypervisor). Not reachable from a guest user. Heap overflow would in principle be a write primitive, but the trigger requires a malicious hypervisor; on this guest, virtio_scsi does not attach at all.
Evidence (decisive lines)
BEFORE-FIX (vtscsi_event.c): event_buf_size=256, bzero(&event_bufs[0], 256) writes 256-16=240 bytes past slot; bzero of all 4 slots spills 4*256=1024 bytes past event_bufs[] into softc/heap; sglist_append(event, 256) likewise over-describes for DMA. AFTER-FIX: event_buf_size clamped to sizeof(struct virtio_scsi_event)=16, no overflow. Patched-kernel build rc=0 (virtio_scsi is in GENERIC kernel). See findings/poc/DF-1352/run.log and fix_build.log.
PoC changes
Wrote trigger.c (vtscsi_event.c) harness demonstrating 240-byte slot overflow + 1024-byte array overflow.
Verified recommended fix
fix.diff adds 'if (sc->vtscsi_event_buf_size > sizeof(struct virtio_scsi_event)) sc->vtscsi_event_buf_size = sizeof(struct virtio_scsi_event);' after reading event_info_size in vtscsi_attach_after_features. Matches finding proposal (only lower bound was checked at :1922). Full diff in findings/poc/DF-1352/fix.diff.
Verdict
REPRODUCED at function level. vtscsi_read_config() at virtio_scsi.c:301 copies scsicfg.event_info_size (read from virtio-scsi PCI config space, controlled by the hypervisor) into sc->vtscsi_event_buf_size with no upper bound. vtscsi_enqueue_event_buf() at :1888-1890 does bzero(event, size) and sglist_append(event, size) where event = &sc->vtscsi_event_bufs[i] (each slot is sizeof(struct virtio_scsi_event) = 16 bytes; array is 4 slots = 64 bytes). size > 16 overflows the slot; size > 64 overflows the array into adjacent softc/heap. The lower-bound check at :1922 only prevents under-size. Harness vtscsi_event.c demonstrates 240 bytes past slot, 1024 bytes past array before fix; fixed path clamps to sizeof(struct virtio_scsi_event). The QEMU guest has no virtio_scsi device attached.
No comments yet.