heap-use-after-free in ivshmem-doorbell process_msg_connect
## Host environment - Operating system: Linux - OS/kernel version: 5.15.0-94-generic #104-Ubuntu SMP Tue Jan 9 15:25:40 UTC 2024 - Architecture: x86_64 - QEMU flavor: qemu-system-x86_64 - QEMU version: v11.0.0-1699-g345d23d774 (git master, affected since v10.0.0) - QEMU command line: qemu-system-x86_64 -M pc -m 256M -accel tcg -display none -serial none -monitor none -nographic -chardev socket,id=char0,path=/tmp/ivshmem.sock -device ivshmem-doorbell,id=ivshmem0,chardev=char0,vectors=2 -qmp unix:/tmp/qmp.sock,server,nowait -no-shutdown ## Description of problem I used AI/LLM tools for initial discovery and confirmed the bug manually with ASAN and source analysis. hw/misc/ivshmem-pci.c: ivshmem_exit() frees s->peers (line 968) and s->msi_vectors (line 975) without clearing the chr_fe handlers set in ivshmem_common_realize (line 907). The handlers are only removed later in object_finalize -> release_chr -> qemu_chr_fe_deinit. s->peers and s->nb_peers are not zeroed after free, so process_msg still enters the resize_peers path on freed memory. If a message arrives between exit and finalize, ivshmem_read() fires and process_msg_connect() dereferences freed s->peers[posn]. This is deterministic, not a race — ivshmem_exit() returns to the main loop and pending chardev data is processed in the same iteration. 100% reproduction rate. Five UAF paths after s->peers is freed: 1. process_msg -> resize_peers -> g_renew(Peer, s->peers, n): realloc on freed pointer (ASAN catches as double-free) 2. process_msg_connect -> peer->eventfds[vector]: event_notifier_init_fd writes attacker-controlled fd to peer->eventfds[vector] — if eventfds pointer is heap residual, this is an arbitrary address write of a controlled int value 3. setup_interrupt -> watch_vector_notifier: registers fd handler with eventfd read from freed memory, opaque points to freed s->msi_vectors[vector] 4. msi_vectors double UAF: s->msi_vectors not zeroed after free, assert(!s->msi_vectors[vector].pdev) reads freed memory, s->msi_vectors[vector].pdev = PCI_DEVICE(s) writes freed memory 5. ivshmem_add_eventfd: registers &s->peers[posn].eventfds[i] in MMIO eventfd list — dangling pointer in ivshmem_mmio Non-ASAN crash confirms arbitrary address write: event_notifier_init_fd(e=0x555555dbba03 <memory_region_get_priority>, fd=17) at event_notifier-posix.c:30 peer->eventfds contained a heap residual pointing to QEMU code segment. Writing fd=17 to read-only code causes SIGSEGV; the same write would succeed against writable .data/.bss function pointers. ASAN output (qemu-system-x86_64 built with -fsanitize=address): ``` ==2779831==ERROR: AddressSanitizer: heap-use-after-free on address 0x51100008cbd0 READ of size 4 at 0x51100008cbd0 thread T0 #0 process_msg_connect ../hw/misc/ivshmem-pci.c:538 #1 process_msg ../hw/misc/ivshmem-pci.c:587 #2 ivshmem_read ../hw/misc/ivshmem-pci.c:619 #3 tcp_chr_read ../chardev/char-socket.c:511 #4 g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55c43) #5 glib_pollfds_poll ../util/main-loop.c:290 #6 os_host_main_loop_wait ../util/main-loop.c:313 #7 main_loop_wait ../util/main-loop.c:592 #8 qemu_main_loop ../system/runstate.c:950 #9 qemu_default_main ../system/main.c:50 #10 main ../system/main.c:93 #11 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #12 __libc_start_main_impl ../csu/libc-start.c:392 #13 _start (qemu-system-x86_64+0xb59844) 0x51100008cbd0 is located 80 bytes inside of 256-byte region [0x51100008cb80,0x51100008cc80) freed by thread T0 here: #0 __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127 #1 ivshmem_exit ../hw/misc/ivshmem-pci.c:968 #2 pci_qdev_unrealize ../hw/pci/pci.c:1484 #3 device_set_realized ../hw/core/qdev.c:603 previously allocated by thread T0 here: #0 __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:164 #1 g_realloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5ed7f) #2 resize_peers ../hw/misc/ivshmem-pci.c:414 #3 ivshmem_common_realize ../hw/misc/ivshmem-pci.c:888 SUMMARY: AddressSanitizer: heap-use-after-free ../hw/misc/ivshmem-pci.c:538 in process_msg_connect ``` Note: the ASAN evidence above was captured using `qom-set realized=false` for reproducibility; in a real attack the guest triggers ACPI hot-unplug via port 0xae08, which follows the same free path through pci_qdev_unrealize. Repro: 1. ivshmem-server -F -S /tmp/ivshmem.sock -l 1M -n 2 2. qemu-system-x86_64 -M pc -m 256M -accel tcg \ -chardev socket,id=char0,path=/tmp/ivshmem.sock \ -device ivshmem-doorbell,id=ivshmem0,chardev=char0,vectors=2 \ -qmp unix:/tmp/qmp.sock,server,nowait -no-shutdown 3. Connect second peer (another QEMU or ivshmem-client) 4. QMP: device_del ivshmem0 5. Server sends peer message -> ivshmem_read on freed s->peers -> crash Impact: confirmed denial of service (UAF crash on unmodified QEMU). Theoretical: arbitrary code execution if freed heap contents can be controlled via heap spray to redirect the event_notifier_init_fd write target to a writable function pointer. The non-ASAN crash demonstrates the write primitive reaches a code-segment address (heap residual); with heap control, a .data/.bss target is reachable. Guest can trigger via ACPI _EJ0 hot-unplug without QMP access. [ivshmem_uaf_asan.py](/uploads/01db5e12d4bb35c7208beab62db85cb0/ivshmem_uaf_asan.py)
issue