hw/usb/hcd-ohci.c: Memory leak in `ohci_service_iso_td`
## Host environment
- Operating system: Linux
- OS/kernel version: Linux 6.8.0-106-generic x86_64
- Architecture: x86_64
- QEMU flavor: qemu-system-i386
- QEMU version: QEMU emulator version 10.2.50 (v10.2.0-1816-g3fb456e9a0)
- QEMU command line:
```
LSAN_OPTIONS=fast_unwind_on_malloc=0 qemu-system-i386 \
-display none -machine accel=qtest, -m 512M -machine q35 -nodefaults \
-device pci-ohci,id=fdev_ohci -device usb-kbd,id=fdev_usb_kbd \
-qtest stdio --trace "*hci*" --trace "*usb*"
```
## Emulated/Virtualized environment
- Operating system: N/A
- OS/kernel version: N/A
- Architecture: N/A
## Description of problem
A memory leak occurs in `ohci_service_iso_td` at `hw/usb/hcd-ohci.c:743` when a guest submits a malformed isochronous transfer descriptor to the OHCI controller. The function allocates an `IOVector` via `qemu_iovec_init()` but exits early on error without calling `qemu_iovec_destroy()`, leaking 16 bytes per malformed TD.
Confirmed that the bug manifests with `QEMU emulator version 11.0.50 (v11.0.0-427-g282771e1f9)`.
## Steps to reproduce
1. Run the below qtest reproducer (LeakSanitizer build required):
```sh
cat <<'EOF' | LSAN_OPTIONS=fast_unwind_on_malloc=0 $TARGET \
-display none -machine accel=qtest, -m 512M -device pci-ohci,id=fdev_ohci \
-device usb-kbd,id=fdev_usb_kbd -machine q35 -nodefaults \
-qtest stdio --trace "*hci*" --trace "*usb*"
outl 0xcf8 0x80000800
inw 0xcfc
outl 0xcf8 0x80000810
outl 0xcfc 0xffffffff
outl 0xcf8 0x80000810
inl 0xcfc
outl 0xcf8 0x80000810
outl 0xcfc 0xe0000000
outl 0xcf8 0x80000804
inw 0xcfc
outl 0xcf8 0x80000804
outw 0xcfc 0x7
outl 0xcf8 0x80000804
inw 0xcfc
outl 0xcf8 0x80000834
inb 0xcfc
outl 0xcf8 0x80000800
inb 0xcfc
outl 0xcf8 0x80000801
inb 0xcfc
outl 0xcf8 0x80000810
inb 0xcfc
outl 0xcf8 0x80000811
inb 0xcfc
writeq 0xe0000062 0x3def5ad7
writew 0xe000001f 0xc59b
writeb 0xe0000054 0x36
readl 0xe0000013
readl 0xe00000ee
readl 0xe0000013
readl 0xe0000019
write 0x0 0x1 0xa0
writeq 0xa0 0x8f00
writeq 0xa4 0xd0
writeq 0xa8 0xc0
writeq 0xac 0x0
writeq 0xc4 0x1
writeq 0xcc 0x1
writel 0xd0 0xe000
writeq 0xe0000002 0x60951da7
clock_step
EOF
```
Execution output:
```bash
$ qemu-system-i386 --version
==129124==WARNING: ASan doesn't fully support makecontext/swapcontext functions and may produce false positives in some cases!
QEMU emulator version 10.2.50 (v10.2.0-1816-g3fb456e9a0)
Copyright (c) 2003-2026 Fabrice Bellard and the QEMU Project developers
$ cat <<'EOF' | LSAN_OPTIONS=fast_unwind_on_malloc=0 qemu-system-i386 ...
==129128==WARNING: ASan doesn't fully support makecontext/swapcontext functions and may produce false positives in some cases!
[I 0.000000] OPENED
usb_ohci_init_time usb_bit_time=1000000 usb_frame_time=83
usb_port_claim bus 0, port 1
usb_port_attach bus 0, port 1, devspeed full+high, portspeed full
usb_ohci_port_attach port #0
usb_ohci_reset pci-ohci
usb_ohci_stop pci-ohci: USB Suspended
usb_ohci_stop pci-ohci: USB Suspended
usb_ohci_port_detach port #0
usb_ohci_port_attach port #0
ahci_reset ahci(0x51f000001940): HBA reset
ahci_reset_port ahci(0x51f000001940)[0]: reset port
ahci_reset_port ahci(0x51f000001940)[1]: reset port
ahci_reset_port ahci(0x51f000001940)[2]: reset port
ahci_reset_port ahci(0x51f000001940)[3]: reset port
ahci_reset_port ahci(0x51f000001940)[4]: reset port
ahci_reset_port ahci(0x51f000001940)[5]: reset port
[R +0.298415] outl 0xcf8 0x80000800
[S +0.298442] OK
OK
[R +0.298499] inw 0xcfc
[S +0.298519] OK 0x106b
OK 0x106b
[R +0.298581] outl 0xcf8 0x80000810
[S +0.298593] OK
OK
[R +0.298659] outl 0xcfc 0xffffffff
[S +0.298671] OK
OK
[R +0.298727] outl 0xcf8 0x80000810
[S +0.298735] OK
OK
[R +0.298786] inl 0xcfc
[S +0.298799] OK 0xffffff00
OK 0xffffff00
[R +0.298854] outl 0xcf8 0x80000810
[S +0.298861] OK
OK
[R +0.298916] outl 0xcfc 0xe0000000
[S +0.298924] OK
OK
[R +0.298980] outl 0xcf8 0x80000804
[S +0.298987] OK
OK
[R +0.299039] inw 0xcfc
[S +0.299051] OK 0x0000
OK 0x0000
[R +0.299106] outl 0xcf8 0x80000804
[S +0.299116] OK
OK
[R +0.299171] outw 0xcfc 0x7
[S +0.301267] OK
OK
[R +0.301341] outl 0xcf8 0x80000804
[S +0.301351] OK
OK
[R +0.301403] inw 0xcfc
[S +0.301419] OK 0x0007
OK 0x0007
[R +0.301474] outl 0xcf8 0x80000834
[S +0.301482] OK
OK
[R +0.301533] inb 0xcfc
[S +0.301545] OK 0x0000
OK 0x0000
[R +0.301616] outl 0xcf8 0x80000800
[S +0.301625] OK
OK
[R +0.301677] inb 0xcfc
[S +0.301692] OK 0x006b
OK 0x006b
[R +0.301747] outl 0xcf8 0x80000801
[S +0.301755] OK
OK
[R +0.301828] inb 0xcfc
[S +0.301841] OK 0x0010
OK 0x0010
[R +0.301902] outl 0xcf8 0x80000810
[S +0.301911] OK
OK
[R +0.301967] inb 0xcfc
[S +0.301980] OK 0x0000
OK 0x0000
[R +0.302041] outl 0xcf8 0x80000811
[S +0.302049] OK
OK
[R +0.302105] inb 0xcfc
[S +0.302118] OK 0x0000
OK 0x0000
[R +0.302192] writeq 0xe0000062 0x3def5ad7
usb_ohci_mem_write_unaligned at 0x62
usb_ohci_mem_write 4 <unknown> 0x64 25 <- 0x3def
usb_ohci_reset pci-ohci
usb_ohci_stop pci-ohci: USB Suspended
usb_ohci_stop pci-ohci: USB Suspended
usb_ohci_port_detach port #0
usb_ohci_port_attach port #0
usb_ohci_mem_write 2 <unknown> 0x68 26 <- 0x0
[S +0.302291] OK
OK
[R +0.302354] writew 0xe000001f 0xc59b
usb_ohci_mem_write_unaligned at 0x1f
usb_ohci_mem_write 1 HcControlHeadED 0x20 8 <- 0xc5
[S +0.302379] OK
OK
[R +0.302441] writeb 0xe0000054 0x36
usb_ohci_mem_port_write 1 HcRhPortStatus[1] 0x54 21 <- 0x36
usb_ohci_port_suspend port #0
usb_ohci_port_reset port #0
[S +0.302474] OK
OK
[R +0.302531] readl 0xe0000013
usb_ohci_mem_read_unaligned at 0x13
usb_ohci_mem_read 2 HcInterruptDisable 0x14 5 -> 0x80000000
usb_ohci_mem_read_unaligned at 0x16
[S +0.302572] OK 0x00000000ff0000ff
OK 0x00000000ff0000ff
[R +0.302648] readl 0xe00000ee
usb_ohci_mem_read_unaligned at 0xee
usb_ohci_mem_read_bad_offset 0xf0
usb_ohci_mem_read 2 <unknown> 0xf0 60 -> 0xffffffff
[S +0.302687] OK 0x00000000ffffffff
OK 0x00000000ffffffff
[R +0.302744] readl 0xe0000013
usb_ohci_mem_read_unaligned at 0x13
usb_ohci_mem_read 2 HcInterruptDisable 0x14 5 -> 0x80000000
usb_ohci_mem_read_unaligned at 0x16
[S +0.302781] OK 0x00000000ff0000ff
OK 0x00000000ff0000ff
[R +0.302838] readl 0xe0000019
usb_ohci_mem_read_unaligned at 0x19
usb_ohci_mem_read_unaligned at 0x1a
usb_ohci_mem_read 1 HcPeriodCurrentED 0x1c 7 -> 0x0
[S +0.302877] OK 0x0000000000ffffff
OK 0x0000000000ffffff
[R +0.302948] write 0x0 0x1 0xa0
[S +0.303231] OK
OK
[R +0.303304] writeq 0xa0 0x8f00
[S +0.303314] OK
OK
[R +0.303379] writeq 0xa4 0xd0
[S +0.303387] OK
OK
[R +0.303458] writeq 0xa8 0xc0
[S +0.303465] OK
OK
[R +0.303520] writeq 0xac 0x0
[S +0.303528] OK
OK
[R +0.303606] writeq 0xc4 0x1
[S +0.303621] OK
OK
[R +0.303681] writeq 0xcc 0x1
[S +0.303689] OK
OK
[R +0.303744] writel 0xd0 0xe000
[S +0.303753] OK
OK
[R +0.303807] writeq 0xe0000002 0x60951da7
usb_ohci_mem_write_unaligned at 0x2
usb_ohci_mem_write 4 HcControl 0x4 1 <- 0x6095
usb_ohci_set_ctl pci-ohci: new state 0x80
usb_ohci_start pci-ohci: USB Operational
usb_ohci_mem_write 2 HcCommandStatus 0x8 2 <- 0x0
[S +0.303859] OK
OK
[R +0.303902] clock_step
usb_ohci_ed_pkt ED @ 0x000000a0 h=0 c=0 head=0x000000c0 tailp=0x000000d0 next=0x00000000
usb_ohci_ed_pkt_flags fa=0 en=14 d=1 s=0 k=0 f=1 mps=0
usb_ohci_iso_td_head ISO_TD ED head 0x000000c0 tailp 0x000000d0, flags 0x00000000 bp 0x00000001 next 0x00000000 be 0x00000001, frame_number 0x00000000 starting_frame 0x00000000, frame_count 0x00000000 relative 0
usb_ohci_iso_td_head_offset 0x0000e000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
usb_packet_state_change bus 0, port 1, ep 14, packet 0x50d000026750, state undef -> setup
usb_packet_state_change bus 0, port 1, ep 14, packet 0x50d000026750, state setup -> complete
usb_ohci_iso_td_so 0x0000e000 eo 0x00000000 sa 0x00000000 ea 0x00000001 dir out len 2 ret -3
usb_ohci_iso_td_nak got NAK/STALL -3
[S +0.304036] OK 1000000
OK 1000000
[I +0.304132] CLOSED
=================================================================
==129128==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 16 byte(s) in 1 object(s) allocated from:
#0 0x60d451ac1973 in malloc (/media/sdb2/won26/dpfuzz-test.code/qemu-upstream/install/bin/qemu-system-i386+0x2cef973) (BuildId: e78039a123ad00f7bb6b1dfa71d401d31ac77053)
#1 0x7ac28b949ac9 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x62ac9) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#2 0x60d453ffc3c7 in qemu_iovec_init /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/iov.c:284:17
#3 0x60d452777230 in ohci_service_iso_td /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../hw/usb/hcd-ohci.c:743:5
#4 0x60d452777230 in ohci_service_ed_list /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../hw/usb/hcd-ohci.c:1160:21
#5 0x60d45276e55c in ohci_frame_boundary /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../hw/usb/hcd-ohci.c:1226:9
#6 0x60d453fd5d7e in timerlist_run_timers /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/qemu-timer.c:593:9
#7 0x60d453fd7554 in qemu_clock_run_timers /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/qemu-timer.c:607:12
#8 0x60d453fd7554 in qemu_clock_advance_virtual_time /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/qemu-timer.c:713:9
#9 0x60d452b5a14b in qtest_process_command /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../system/qtest.c:726:18
#10 0x60d452b54ffb in qtest_process_inbuf /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../system/qtest.c:777:9
#11 0x60d453c2fd48 in fd_chr_read /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../chardev/char-fd.c:72:9
#12 0x7ac28b94445d (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5d45d) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#13 0x7ac28b9446cf in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5d6cf) (BuildId: 116e142b9b52c8a4dfd403e759e71ab8f95d8bb3)
#14 0x60d453fbab58 in glib_pollfds_poll /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/main-loop.c:290:9
#15 0x60d453fbab58 in os_host_main_loop_wait /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/main-loop.c:313:5
#16 0x60d453fbab58 in main_loop_wait /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../util/main-loop.c:592:11
#17 0x60d452b66e94 in qemu_main_loop /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../system/runstate.c:943:9
#18 0x60d453cdd84b in qemu_default_main /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../system/main.c:50:14
#19 0x60d453cdda08 in main /media/sdb2/won26/dpfuzz-test.code/qemu-upstream/build/../system/main.c:93:9
#20 0x7ac28ae2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#21 0x7ac28ae2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#22 0x60d451a26b24 in _start (/media/sdb2/won26/dpfuzz-test.code/qemu-upstream/install/bin/qemu-system-i386+0x2c54b24) (BuildId: e78039a123ad00f7bb6b1dfa71d401d31ac77053)
SUMMARY: AddressSanitizer: 16 byte(s) leaked in 1 allocation(s).
```
## Additional information
OSS-fuzz report has been verified while the bug remains: https://issues.oss-fuzz.com/issues/42521338
Let me know if additional information is needed or if there are any other issues I missed. Thank you.
issue