Bug#1142759: trixie-pu: package qemu/1:10.0.12+ds-0+deb13u1
Michael Tokarev <[email protected]> Sat, 25 Jul 2026 10:06:29 +0300
| Newsgroups | gmane.linux.debian.devel.release |
|---|---|
| Message-ID | <178496318937.1432768.2089776159492067898.reportbug__21707.8200353839$1784963396$gmane$org@localhost> |
Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:qemu User: [email protected] Usertags: pu [ Reason ] New upstream stable/bugfix release. With more than 120 fixes all over the places, - fixing bugs, correctness. Including the following CVE fixes: CVE-2026-8348 CVE-2026-9238 CVE-2026-15578 CVE-2026-15705 CVE-2026-16043 CVE-2026-48002 CVE-2026-61475 CVE-2026-63319 [ Tests ] This is an upstream qemu release, which passed all relevant upstream CI tests. Additionally, my usual set of various guest images are being tested now, - I'll update this report if anything pops out, but I don't expect anything. All changes were taken from the qemu master branch, where things are being tested all the time too. [ Risks ] This is a big update, with large number of changes. Most of them are small and easy to verify, but some are more complex. However I don't expect significant risks from this update, as has been usual for the stable qemu updates. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable [ Changes ] d/changelog differences is on top of the debdiff. [ Other info ] The diff between two source tarballs is rather large, due to the amount of individual changes included in this release. It might be better to see individual commits on salsa, from https://salsa.debian.org/qemu-team/qemu/-/commits/v10.0.12 up to v10.0.11 which is already in debian. The same commits and tags can be found on the upstream qemu git repository, https://gitlab.com/qemu-project/qemu Thanks, /mjt diff -Nru qemu-10.0.11+ds/debian/changelog qemu-10.0.12+ds/debian/changelog --- qemu-10.0.11+ds/debian/changelog 2026-06-28 08:33:17.000000000 +0300 +++ qemu-10.0.12+ds/debian/changelog 2026-07-25 09:35:02.000000000 +0300 @@ -1,8 +1,198 @@ +qemu (1:10.0.12+ds-0+deb13u1) trixie; urgency=medium + + * new upstream stable/bugfix release: + - Update version for 10.0.12 release + - hw/audio/intel-hda: restrict all DMA engine paths to memories + (update to CVE-2021-3611) + - hw/net/cadence: Return current Cadence GEM queue pointers + - hw/misc/applesmc: Fix a typo setting MSSD key + - replay: fix use of uninitialized pointer on error + - hw/display/qxl: validate monitors_config heads[] in phys2virt + https://gitlab.com/qemu-project/qemu/-/work_items/4027 + - net: Correct padding check in qemu_receive_packet() + - hw/net/xilinx_axienet: Fix PHY register 17 link status reporting + - hw/usb/hcd-xhci-sysbus: Fix OOB heap access in xhci_sysbus_intr_raise() + https://gitlab.com/qemu-project/qemu/-/work_items/4001 + (Closes: CVE-2026-16043) + - hw/usb/hcd-xhci: Fix guest-triggerable assert() in xhci_find_stream() + https://gitlab.com/qemu-project/qemu/-/work_items/273, 2020-06-16 + - usbredir: fix infinite loop and SIGFPE with zero max_packet_size + https://gitlab.com/qemu-project/qemu/-/work_items/3995 + (Closes: CVE-2026-63319) + - usbredir: fix use-after-free on buffered bulk packet overflow + https://gitlab.com/qemu-project/qemu/-/work_items/3808 + (Closes: CVE-2026-15705) + - tests/qtest: add xhci-pci unplug finalize regression test + - hw/usb/hcd-xhci-pci: break host link cycle so device_finalize() + runs on unplug + - hw/usb/xhci: clamp interval exponent to avoid UB shift + in xhci_init_epctx() + https://gitlab.com/qemu-project/qemu/-/work_items/3703 + - accel/tcg: move jit thread manipulation into do_tb_phys_invalidate + https://gitlab.com/qemu-project/qemu/-/work_items/3444 + - hw/display/virtio-gpu: Check pixman_image_create_bits() results + - hw/display/virtio-gpu: handle migration iov allocation failure + https://gitlab.com/qemu-project/qemu/-/work_items/3753 + - hw/display/virtio-gpu: cap submit_3d command buffer allocation + https://gitlab.com/qemu-project/qemu/-/work_items/3776 + - ui/vnc: validate SetPixelFormat field ranges + https://gitlab.com/qemu-project/qemu/-/issues/3976 + (Closes: CVE-2026-15578) + - ui/vnc: fix out-of-bounds write in lossy refresh dirty marking + https://gitlab.com/qemu-project/qemu/-/work_items/3935 + (Closes: CVE-2026-61475) + - ui/gtk: Narrow DMA-BUF critical section + - ui/input-barrier: fix off-by-one in keycode bounds check + https://gitlab.com/qemu-project/qemu/-/issues/3951 + - ui/vnc: validate color shifts in SetPixelFormat + https://gitlab.com/qemu-project/qemu/-/work_items/3948 + - ui/vnc: fix OOB write in vnc_refresh_lossy_rect + https://gitlab.com/qemu-project/qemu/-/work_items/3950 + (Closes: CVE-2026-48002) + - hw/usb/hcd-xhci: Turn guest-triggerable abort() into qemu_log_mask() + https://gitlab.com/qemu-project/qemu/-/work_items/3784 + - hw/usb/hcd-ohci: Make sure that ohci_service_ed_list() cannot loop forever + https://gitlab.com/qemu-project/qemu/-/work_items/3781 + - hw/display/virtio-gpu: fix dmabuf_fd leak on remap failure + - hw/scsi/vmw_pvscsi: add a comment to explain the endianness + - hw/scsi/vmw_pvscsi: translate data endianness + - hw/sparc64/niagara: use int64_t for vdisk size to avoid truncation + - hw/display/qxl: fix TOCTOU in cursor chunk data_size handling + https://gitlab.com/qemu-project/qemu/-/work_items/3757 + - hw/misc/ivshmem: clear chardev handlers before freeing peers + https://gitlab.com/qemu-project/qemu/-/work_items/3594 + - linux-user/alpha: populate AT_HWCAP from env->amask + - linux-user/alpha: add coredump support + - s390x/css: firm up handling of chained TIC CCWs + - s390x/sclp: prevent re-reading the sclp header + - hw/s390x/sclp: Do not ignore address_space_read/write() errors + - hw/s390x/sclp: Replace [cpu_physical_memory -> address_space]_r/w() + - hw/misc/stm32_rcc: Correct offset-to-irq calculation + - hw/display/sm501: Don't allow guest to set ram size larger than it is + https://gitlab.com/qemu-project/qemu/-/work_items/3811 + - hw/display/sm501: Avoid overflow problems in bounds check calculations + https://gitlab.com/qemu-project/qemu/-/work_items/3584 + - hw/display/sm501: Catch bad coordinates for RTL operations + https://gitlab.com/qemu-project/qemu/-/work_items/3920 + - hw/usb/dev-wacom: Don't write off end of buffer + https://gitlab.com/qemu-project/qemu/-/work_items/3672 + - hw/net/vmxnet3: Correct bounds check on tx queue index + https://gitlab.com/qemu-project/qemu/-/work_items/3780 + - target/arm: Be more defensive for invalid tlbi_aa64_get_range + - linux-user: Validate guest-passed dm_ioctl data_size + https://gitlab.com/qemu-project/qemu/-/work_items/3736 + - tests: update SPCR loongarch64 and riscv64 test data + - hw/acpi: correct field sequence in SPCR table + https://bugs.launchpad.net/ubuntu/+source/qemu/+bug/2146419 + - tests: allow differences in SPCR + - hw/pci/pcie_doe: Check mailbox length for overflows + https://gitlab.com/qemu-project/qemu/-/work_items/3679 + - hw/riscv/riscv-iommu: Fix MemoryRegion owner + - hw/riscv/riscv-iommu.c: set ftype and iova in riscv_iommu_ctx() + https://gitlab.com/qemu-project/qemu/-/work_items/3564 + - hw/riscv: riscv-iommu: Don't look up DDT cache in Off and Bare modes + - hw/riscv/riscv-iommu: Avoid caching PCI device IDs + - hw/riscv/riscv-iommu: forbid GATE/SADE if caps.AMO_HWADD is zero + https://gitlab.com/qemu-project/qemu/-/work_items/3549 + - hw/riscv/riscv-iommu.c: update ioval2 when faulting in spa_fetch() + https://gitlab.com/qemu-project/qemu/-/work_items/3559 + - hw/riscv/riscv-iommu.c: check for misaligned IOHGATP_PPN + https://gitlab.com/qemu-project/qemu/-/work_items/3550 + - hw/riscv/riscv-iommu-sys.c: record fault on IOMMU-generated MSI write + https://gitlab.com/qemu-project/qemu/-/work_items/3572 + - hw/riscv/riscv-iommu.c: check reserved MSI PTE basic bits + https://gitlab.com/qemu-project/qemu/-/work_items/3563 + - hw/riscv/riscv-iommu.c: fault for non-user PTE in G_STAGE + https://gitlab.com/qemu-project/qemu/-/work_items/3555 + - hw/riscv/riscv-iommu.c: fault when !PTE_U and no priv access + https://gitlab.com/qemu-project/qemu/-/work_items/3553 + - hw/riscv/riscv-iommu.c: check for reserved PTE bits + https://gitlab.com/qemu-project/qemu/-/work_items/3554 + - hw/riscv/riscv-iommu.c: fix fault type for spa_fetch() faults + https://gitlab.com/qemu-project/qemu/-/work_items/3557 + https://gitlab.com/qemu-project/qemu/-/work_items/3577 + - hw/ufs: avoid double unref of wrapped scsi-hd + - target/sh4: fixup tcg for sh4 fipr/ftrv instructions + - tcg/loongarch64: Fix cmp_vec with TCG_COND_NE + https://gitlab.com/qemu-project/qemu/-/work_items/3589 + - tcg/loongarch64: Improve constraints for TCG_CT_CONST_VCMP + - tcg/loongarch64: Fix vec_val computation in tcg_target_const_match + - accel/tcg: Make PageFlagsNodes' start and last immutable + - accel/tcg: Use TLB_FORCE_SLOW not TLB_MMIO for user-only plugins + - hw/net: fix e1000e/igb ip_len inflation by Ethernet minimum-frame padding + - hw/core/qdev-clock: Fix potential null pointer dereference + https://gitlab.com/qemu-project/qemu/-/work_items/2342 + - vdpa: fix use-after-free of vqs in vhost_vdpa_device_unrealize + - hw/display/qxl: Fix mono cursor validation that can read + past a cursor chunk + https://gitlab.com/qemu-project/qemu/-/work_items/3646 + - hw/scsi/mptsas: Reset doorbell state on reset + https://gitlab.com/qemu-project/qemu/-/work_items/304, 2020-12-17 + - hw/dma/i8257: Return zeroes for read_memory in verify mode + https://gitlab.com/qemu-project/qemu/-/work_items/3487 + - tests/qtest/ahci: test ATAPI read completing after engine restart + - hw/ide/ahci: cancel in-flight buffered reads on command engine restart + - hw/misc/edu: restrict dma access to dma buffer + https://gitlab.com/qemu-project/qemu/-/work_items/3852 + - s390x/css: limit number of CHPIDs in description + - s390x/ioinst: Require strict length and format for SEI CHSC handler + - s390x/pci: Shrink RPCIT ranges to registered window + - s390x/pci: Tighten region detection for BAR read/write + - s390x/sclp: reject invalid write event data headers + - hw/nvme: ensure sgl forward progress + - hw/nvme: fix FDP set FDP events + https://gitlab.com/qemu-project/qemu/-/work_items/3631 + - hw/arm: use cortex-a9 mpcore base for CBAR on npcm7xx machines + - hw/net/fsl_etsec: validate FCB offsets in process_tx_fcb() + https://gitlab.com/qemu-project/qemu/-/work_items/3517 + - virtio-net: validate RSS indirections_len in post_load + - hw/char/virtio-serial-bus: fix guest-triggerable OOM in control_out() + https://gitlab.com/qemu-project/qemu/-/issues/3585 + - hw/riscv/riscv-iommu.c: always fault with SADE=0 and A=0 + https://gitlab.com/qemu-project/qemu/-/work_items/3551 + - target/riscv: Apply UXL WARL handling to vsstatus + - target/riscv: avoid abort when reading vtype before env->xl is set + https://gitlab.com/qemu-project/qemu/-/issues/3545 + - hw/riscv/virt-acpi-build: Fix RINTC PLIC context ID for KVM + - target/riscv: Check PMP before updating PTE + - hw/9pfs/local: harden local_fid_fd() on FID types + - hw/9pfs: fix invalid union access by v9fs_co_fstat() + - hw/9pfs: fix invalid union access by v9fs_co_fsync() + - tests/9p: add 3 xattr FID limit test cases (local fs driver) + - tests/9p: add 3 xattr FID limit test cases (synth fs driver) + - tests/9p: add virtio_9p_add_synth_driver_args() test client function + - tests/9p: increase P9_MAX_SIZE for test client + - hw/9pfs: add xattr count query interface to fs synth driver + - hw/9pfs: enable xattr (mockup) support for synth fs driver + - tests/9p: add Txattrcreate / Rxattrcreate test client functions + - tests/9p: add Tclunk / Rclunk test client functions + - tests/9p: add Tread / Rread test client functions + - qemu-options: document 9pfs max_xattr option + - hw/9pfs: add max_xattr option + - hw/9pfs: add xattr FID limit to prevent memory exhaustion + (Closes: CVE-2026-8348) + - hw/9pfs: cap Treaddir allocation + (Closes: CVE-2026-9238) + - 9pfs/xen: implement response_buffer_size callback + - 9pfs/virtio: implement response_buffer_size callback + - hw/9pfs: add response_buffer_size transport callback + - hw/9pfs: cap negotiated msize to transport limit + - 9pfs/xen: implement msize_limit callback + - 9pfs/virtio: implement msize_limit callback + - hw/9pfs: add msize_limit transport callback + - 9pfs: local : Introduce local_fid_fd() helper + - 9pfs: Don't use file descriptors in core code + - s390x/kvm: clamp stsi 3.2.2 size + - ui/gtk: fix bad widget realize on non-GFX VC + + -- Michael Tokarev <[email protected]> Sat, 25 Jul 2026 09:35:02 +0300 + qemu (1:10.0.11+ds-0+deb13u1) trixie; urgency=medium * new upstream stable/bugfix release: - Update version for 10.0.11 release - - linux-user: Fix AT_PHDR when program headers are relocated into their own segment + - linux-user: Fix AT_PHDR when program headers are relocated + into their own segment - hw/pci: Replace assert with bounds check and return - ppc/pnv_phb3: Error out on invalid config access - linux-user/xtensa: fix unlock of uninitialized frame pointer on sigreturn @@ -45,7 +235,8 @@ - qemu-io: Add 'aio_discard' command - virtio-blk: add missing VIRTIO_BLK_T_SCSI_CMD size check (Closes: #1139923, CVE-2026-48914) - - block/io: fallback to bounce buffer if BLKZEROOUT is not supported because of alignment + - block/io: fallback to bounce buffer if BLKZEROOUT is not supported + because of alignment - s390x/pci: Fix interrupt forwarding disable for interpreted devices - target/s390x: Make container ids in SysIB_15x 1-based - tests/unit: add test-envlist covering setenv/unsetenv name matching @@ -54,9 +245,11 @@ (Closes: CVE-2026-48004) - tests/9pfs: add deep absolute path test - tests/qtest/libqos: add qvirtqueue_reset_pool() for descriptor pool reset - - hw/9pfs: let callers of v9fs_path_sprintf() and v9fs_fix_path() handle errors + - hw/9pfs: let callers of v9fs_path_sprintf() and v9fs_fix_path() + handle errors - hw/9pfs: add error handling to v9fs_fix_path() - - hw/9pfs: change V9fsPath.size to size_t and v9fs_path_sprintf() return type + - hw/9pfs: change V9fsPath.size to size_t and v9fs_path_sprintf() + return type - hw/9pfs: add NULL check in v9fs_path_is_ancestor() - hw/9pfs: move G_GNUC_PRINTF to header - linux-user/s390x: restore fpu_status rounding mode from FPC on sigreturn diff -Nru qemu-10.0.11+ds/debian/control.mk qemu-10.0.12+ds/debian/control.mk --- qemu-10.0.11+ds/debian/control.mk 2026-06-28 08:22:14.000000000 +0300 +++ qemu-10.0.12+ds/debian/control.mk 2026-07-25 09:35:02.000000000 +0300 @@ -9,7 +9,7 @@ # since some files and/or lists differ from version to version, # ensure we have the expected qemu version, or else scream loudly -checked-version := 10.0.11+ds +checked-version := 10.0.12+ds # version of last vdso change for d/control Depends field: vdso-version := 1:9.2.0~rc3+ds-1~ diff -Nru qemu-10.0.11+ds/VERSION qemu-10.0.12+ds/VERSION --- qemu-10.0.11+ds/VERSION 2026-06-26 00:39:13.000000000 +0300 +++ qemu-10.0.12+ds/VERSION 2026-07-25 01:10:12.000000000 +0300 @@ -1 +1 @@ -10.0.11 +10.0.12 diff -Nru qemu-10.0.11+ds/accel/tcg/tb-maint.c qemu-10.0.12+ds/accel/tcg/tb-maint.c --- qemu-10.0.11+ds/accel/tcg/tb-maint.c 2026-06-26 00:39:13.000000000 +0300 +++ qemu-10.0.12+ds/accel/tcg/tb-maint.c 2026-07-25 01:10:12.000000000 +0300 @@ -921,6 +921,7 @@ uint32_t orig_cflags = tb_cflags(tb); assert_memory_lock(); + qemu_thread_jit_write(); /* make sure no further incoming jumps will be chained to this TB */ qemu_spin_lock(&tb->jmp_lock); @@ -931,33 +932,27 @@ phys_pc = tb_page_addr0(tb); h = tb_hash_func(phys_pc, (orig_cflags & CF_PCREL ? 0 : tb->pc), tb->flags, tb->cs_base, orig_cflags); - if (!qht_remove(&tb_ctx.htable, tb, h)) { - return; - } + if (qht_remove(&tb_ctx.htable, tb, h)) { - /* remove the TB from the page list */ - if (rm_from_page_list) { - tb_remove(tb); - } + /* remove the TB from the page list */ + if (rm_from_page_list) { + tb_remove(tb); + } - /* remove the TB from the hash list */ - tb_jmp_cache_inval_tb(tb); + /* remove the TB from the hash list */ + tb_jmp_cache_inval_tb(tb); - /* suppress this TB from the two jump lists */ - tb_remove_from_jmp_list(tb, 0); - tb_remove_from_jmp_list(tb, 1); + /* suppress this TB from the two jump lists */ + tb_remove_from_jmp_list(tb, 0); + tb_remove_from_jmp_list(tb, 1); - /* suppress any remaining jumps to this TB */ - tb_jmp_unlink(tb); + /* suppress any remaining jumps to this TB */ + tb_jmp_unlink(tb); - qatomic_set(&tb_ctx.tb_phys_invalidate_count, - tb_ctx.tb_phys_invalidate_count + 1); -} + qatomic_set(&tb_ctx.tb_phys_invalidate_count, + tb_ctx.tb_phys_invalidate_count + 1); + } -static void tb_phys_invalidate__locked(TranslationBlock *tb) -{ - qemu_thread_jit_write(); - do_tb_phys_invalidate(tb, true); qemu_thread_jit_execute(); } @@ -1025,7 +1020,7 @@ assert_memory_lock(); PAGE_FOR_EACH_TB(start, last, unused, tb, n) { - tb_phys_invalidate__locked(tb); + do_tb_phys_invalidate(tb, true); } } @@ -1090,7 +1085,7 @@ current_tb_modified = true; cpu_restore_state_from_tb(current_cpu, current_tb, pc); } - tb_phys_invalidate__locked(tb); + do_tb_phys_invalidate(tb, true); } if (current_tb_modified) { @@ -1153,7 +1148,7 @@ cpu_restore_state_from_tb(current_cpu, current_tb, retaddr); } #endif /* TARGET_HAS_PRECISE_SMC */ - tb_phys_invalidate__locked(tb); + do_tb_phys_invalidate(tb, true); } } diff -Nru qemu-10.0.11+ds/accel/tcg/user-exec.c qemu-10.0.12+ds/accel/tcg/user-exec.c --- qemu-10.0.11+ds/accel/tcg/user-exec.c 2026-06-26 00:39:13.000000000 +0300 +++ qemu-10.0.12+ds/accel/tcg/user-exec.c 2026-07-25 01:10:12.000000000 +0300 @@ -222,13 +222,16 @@ int page_get_flags(target_ulong address) { - PageFlagsNode *p = pageflags_find(address, address); + PageFlagsNode *p; + + RCU_READ_LOCK_GUARD(); /* * See util/interval-tree.c re lockless lookups: no false positives but * there are false negatives. If we find nothing, retry with the mmap * lock acquired. */ + p = pageflags_find(address, address); if (p) { return p->flags; } @@ -327,15 +330,15 @@ if (prev) { if (next) { - prev->itree.last = next->itree.last; + pageflags_create(prev->itree.start, next->itree.last, flags); g_free_rcu(next, rcu); } else { - prev->itree.last = last; + pageflags_create(prev->itree.start, last, flags); } - interval_tree_insert(&prev->itree, &pageflags_root); + g_free_rcu(prev, rcu); } else if (next) { - next->itree.start = start; - interval_tree_insert(&next->itree, &pageflags_root); + pageflags_create(start, next->itree.last, flags); + g_free_rcu(next, rcu); } else { pageflags_create(start, last, flags); } @@ -405,8 +408,8 @@ if (set_flags != merge_flags) { if (p_start < start) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.last = start - 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(p_start, start - 1, p_flags); + g_free_rcu(p, rcu); if (last < p_last) { if (merge_flags) { @@ -428,11 +431,11 @@ } if (last < p_last) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.start = last + 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(last + 1, p_last, p_flags); if (merge_flags) { pageflags_create(start, last, merge_flags); } + g_free_rcu(p, rcu); } else { if (merge_flags) { p->flags = merge_flags; @@ -453,8 +456,8 @@ if (set_flags == p_flags) { if (start < p_start) { interval_tree_remove(&p->itree, &pageflags_root); - p->itree.start = start; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(start, p_last, p_flags); + g_free_rcu(p, rcu); } if (p_last < last) { start = p_last + 1; @@ -466,8 +469,8 @@ /* Maybe split out head and/or tail ranges with the original flags. */ interval_tree_remove(&p->itree, &pageflags_root); if (p_start < start) { - p->itree.last = start - 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(p_start, start - 1, p_flags); + g_free_rcu(p, rcu); if (p_last < last) { goto restart; @@ -476,8 +479,8 @@ pageflags_create(last + 1, p_last, p_flags); } } else if (last < p_last) { - p->itree.start = last + 1; - interval_tree_insert(&p->itree, &pageflags_root); + pageflags_create(last + 1, p_last, p_flags); + g_free_rcu(p, rcu); } else { g_free_rcu(p, rcu); goto restart; @@ -545,6 +548,8 @@ return false; /* wrap around */ } + RCU_READ_LOCK_GUARD(); + locked = have_mmap_lock(); while (true) { PageFlagsNode *p = pageflags_find(start, last); @@ -807,7 +812,7 @@ if (page_flags & acc_flag) { if (access_type != MMU_INST_FETCH && cpu_plugin_mem_cbs_enabled(env_cpu(env))) { - return TLB_MMIO; + return TLB_FORCE_SLOW; } return 0; /* success */ } @@ -842,7 +847,7 @@ g_assert(-(addr | TARGET_PAGE_MASK) >= size); flags = probe_access_internal(env, addr, size, access_type, false, ra); - g_assert((flags & ~TLB_MMIO) == 0); + g_assert((flags & ~TLB_FORCE_SLOW) == 0); return size ? g2h(env_cpu(env), addr) : NULL; } diff -Nru qemu-10.0.11+ds/contrib/vhost-user-gpu/virgl.c qemu-10.0.12+ds/contrib/vhost-user-gpu/virgl.c --- qemu-10.0.11+ds/contrib/vhost-user-gpu/virgl.c 2026-06-26 00:39:13.000000000 +0300 +++ qemu-10.0.12+ds/contrib/vhost-user-gpu/virgl.c 2026-07-25 01:10:13.000000000 +0300 @@ -202,6 +202,13 @@ VUGPU_FILL_CMD(cs); + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) { + g_critical("%s: command buffer too large (%u)", + __func__, cs.size); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + buf = g_malloc(cs.size); s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); diff -Nru qemu-10.0.11+ds/contrib/vhost-user-gpu/vugpu.h qemu-10.0.12+ds/contrib/vhost-user-gpu/vugpu.h --- qemu-10.0.11+ds/contrib/vhost-user-gpu/vugpu.h 2026-06-26 00:39:13.000000000 +0300 +++ qemu-10.0.12+ds/contrib/vhost-user-gpu/vugpu.h 2026-07-25 01:10:13.000000000 +0300 @@ -22,6 +22,7 @@ #include "qemu/queue.h" #include "qemu/iov.h" #include "qemu/bswap.h" +#include "qemu/units.h" #include "vugbm.h" typedef enum VhostUserGpuRequest { @@ -163,6 +164,14 @@ QTAILQ_ENTRY(virtio_gpu_ctrl_command) next; }; +/* + * With 4 KiB pages and QEMU's VIRTQUEUE_MAX_SIZE (1024) mapped-iov + * limit, the largest inline command is ~4 MiB. Cap submit_3d + * allocations to this value to prevent a malicious guest from + * triggering an OOM abort via an inflated cs.size field. + */ +#define VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE (4 * MiB) + #define VUGPU_FILL_CMD(out) do { \ size_t vugpufillcmd_s_ = \ iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0, \ diff -Nru qemu-10.0.11+ds/fsdev/file-op-9p.h qemu-10.0.12+ds/fsdev/file-op-9p.h --- qemu-10.0.11+ds/fsdev/file-op-9p.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/fsdev/file-op-9p.h 2026-07-25 01:10:13.000000000 +0300 @@ -79,6 +79,11 @@ #define V9FS_SEC_MASK 0x0000003C +/* + * Limits the maximum amount of simultaneously open xattr FIDs to prevent + * host memory exhaustion (as each xattr FID contains a xattr value buffer). + */ +#define V9FS_MAX_XATTR_DEFAULT 1024 typedef struct FileOperations FileOperations; typedef struct XattrOperations XattrOperations; @@ -94,6 +99,8 @@ FsThrottle fst; mode_t fmode; mode_t dmode; + /* temporary storage for parse_opts only */ + uint32_t max_xattr; } FsDriverEntry; struct FsContext { @@ -107,6 +114,10 @@ void *private; mode_t fmode; mode_t dmode; + /* max. amount of simultaneously open xattr FIDs */ + uint32_t xattr_fid_limit; + /* current amount of open xattr FIDs */ + uint32_t xattr_fid_count; }; struct V9fsPath { @@ -164,6 +175,7 @@ int (*renameat)(FsContext *ctx, V9fsPath *olddir, const char *old_name, V9fsPath *newdir, const char *new_name); int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags); + bool (*has_valid_file_handle)(int fid_type, V9fsFidOpenState *fs); }; #endif diff -Nru qemu-10.0.11+ds/fsdev/qemu-fsdev-opts.c qemu-10.0.12+ds/fsdev/qemu-fsdev-opts.c --- qemu-10.0.11+ds/fsdev/qemu-fsdev-opts.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/fsdev/qemu-fsdev-opts.c 2026-07-25 01:10:13.000000000 +0300 @@ -46,6 +46,9 @@ }, { .name = "dmode", .type = QEMU_OPT_NUMBER, + }, { + .name = "max_xattr", + .type = QEMU_OPT_NUMBER, }, THROTTLE_OPTS, @@ -92,6 +95,9 @@ }, { .name = "dmode", .type = QEMU_OPT_NUMBER, + }, { + .name = "max_xattr", + .type = QEMU_OPT_NUMBER, }, { /*End of list */ } diff -Nru qemu-10.0.11+ds/fsdev/qemu-fsdev.c qemu-10.0.12+ds/fsdev/qemu-fsdev.c --- qemu-10.0.11+ds/fsdev/qemu-fsdev.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/fsdev/qemu-fsdev.c 2026-07-25 01:10:13.000000000 +0300 @@ -45,7 +45,7 @@ static QTAILQ_HEAD(, FsDriverListEntry) fsdriver_entries = QTAILQ_HEAD_INITIALIZER(fsdriver_entries); -#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly" +#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly", "max_xattr" static FsDriverTable FsDrivers[] = { { diff -Nru qemu-10.0.11+ds/hw/9pfs/9p-local.c qemu-10.0.12+ds/hw/9pfs/9p-local.c --- qemu-10.0.11+ds/hw/9pfs/9p-local.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/9p-local.c 2026-07-25 01:10:13.000000000 +0300 @@ -766,16 +766,22 @@ return err; } -static int local_fstat(FsContext *fs_ctx, int fid_type, - V9fsFidOpenState *fs, struct stat *stbuf) +static int local_fid_fd(int fid_type, V9fsFidOpenState *fs) { - int err, fd; - if (fid_type == P9_FID_DIR) { - fd = dirfd(fs->dir.stream); + return dirfd(fs->dir.stream); + } else if (fid_type == P9_FID_FILE) { + return fs->fd; } else { - fd = fs->fd; + errno = EBADF; + return -1; } +} + +static int local_fstat(FsContext *fs_ctx, int fid_type, + V9fsFidOpenState *fs, struct stat *stbuf) +{ + int err, fd = local_fid_fd(fid_type, fs); err = fstat(fd, stbuf); if (err) { @@ -1167,13 +1173,7 @@ static int local_fsync(FsContext *ctx, int fid_type, V9fsFidOpenState *fs, int datasync) { - int fd; - - if (fid_type == P9_FID_DIR) { - fd = dirfd(fs->dir.stream); - } else { - fd = fs->fd; - } + int fd = local_fid_fd(fid_type, fs); if (datasync) { return qemu_fdatasync(fd); @@ -1509,6 +1509,15 @@ const char *path = qemu_opt_get(opts, "path"); const char *multidevs = qemu_opt_get(opts, "multidevs"); + uint64_t val = qemu_opt_get_number(opts, "max_xattr", + V9FS_MAX_XATTR_DEFAULT); + if (val > UINT32_MAX) { + error_setg(errp, "max_xattr value '%s' too large", + qemu_opt_get(opts, "max_xattr")); + return -1; + } + fse->max_xattr = val; + if (!sec_model) { error_setg(errp, "security_model property not set"); error_append_security_model_hint(errp); @@ -1584,6 +1593,13 @@ return 0; } +static bool local_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs) +{ + return + (fid_type == P9_FID_FILE && fs->fd != -1) || + (fid_type == P9_FID_DIR && fs->dir.stream != NULL); +} + FileOperations local_ops = { .parse_opts = local_parse_opts, .init = local_init, @@ -1621,4 +1637,5 @@ .name_to_path = local_name_to_path, .renameat = local_renameat, .unlinkat = local_unlinkat, + .has_valid_file_handle = local_has_valid_file_handle, }; diff -Nru qemu-10.0.11+ds/hw/9pfs/9p-synth.c qemu-10.0.12+ds/hw/9pfs/9p-synth.c --- qemu-10.0.11+ds/hw/9pfs/9p-synth.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/9p-synth.c 2026-07-25 01:10:13.000000000 +0300 @@ -25,6 +25,8 @@ #include "qemu/rcu_queue.h" #include "qemu/cutils.h" #include "system/qtest.h" +#include "qapi/error.h" +#include "qemu/option.h" /* Root node for synth file system */ static V9fsSynthNode synth_root = { @@ -461,15 +463,15 @@ const char *name, void *value, size_t size, int flags) { - errno = ENOTSUP; - return -1; + /* pretend it worked */ + return 0; } static int synth_lremovexattr(FsContext *ctx, V9fsPath *path, const char *name) { - errno = ENOTSUP; - return -1; + /* pretend it worked */ + return 0; } static int synth_name_to_path(FsContext *ctx, V9fsPath *dir_path, @@ -549,6 +551,19 @@ return 1; } +/* transmits internal xattr counter to client */ +static ssize_t v9fs_synth_read_xattr_count(void *buf, int len, off_t offset, + void *arg) +{ + FsContext *ctx = arg; + size_t local_count = ctx->xattr_fid_count; + if (len < (int)sizeof(size_t)) { + return -ENOSPC; + } + memcpy(buf, &local_count, sizeof(size_t)); + return sizeof(size_t); +} + static int synth_init(FsContext *ctx, Error **errp) { QLIST_INIT(&synth_root.child); @@ -610,12 +625,45 @@ g_free(name); } } + + /* Directory for internal statistic queries */ + { + V9fsSynthNode *stat_dir = NULL; + ret = qemu_v9fs_synth_mkdir(NULL, 0755, "stat", &stat_dir); + assert(!ret); + + /* File for internal xattr count query */ + ret = qemu_v9fs_synth_add_file(stat_dir, 0444, "xattr_count", + v9fs_synth_read_xattr_count, + NULL, ctx); + assert(!ret); + } + } + + return 0; +} + +static int synth_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp) +{ + uint64_t val = qemu_opt_get_number(opts, "max_xattr", + V9FS_MAX_XATTR_DEFAULT); + if (val > UINT32_MAX) { + error_setg(errp, "max_xattr value '%s' too large", + qemu_opt_get(opts, "max_xattr")); + return -1; } + fse->max_xattr = val; return 0; } +static bool synth_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs) +{ + return false; +} + FileOperations synth_ops = { + .parse_opts = synth_parse_opts, .init = synth_init, .lstat = synth_lstat, .readlink = synth_readlink, @@ -650,4 +698,5 @@ .name_to_path = synth_name_to_path, .renameat = synth_renameat, .unlinkat = synth_unlinkat, + .has_valid_file_handle = synth_has_valid_file_handle, }; diff -Nru qemu-10.0.11+ds/hw/9pfs/9p.c qemu-10.0.12+ds/hw/9pfs/9p.c --- qemu-10.0.11+ds/hw/9pfs/9p.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/9p.c 2026-07-25 01:10:13.000000000 +0300 @@ -263,6 +263,31 @@ return str->size; } +static int xattr_fid_count_inc(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + + if (s->ctx.xattr_fid_limit > 0 && + s->ctx.xattr_fid_count >= s->ctx.xattr_fid_limit) { + error_report_once("9pfs: xattr_fid_count limit exceeded " + "(configurable by option 'max_xattr')."); + return -ENOSPC; + } + s->ctx.xattr_fid_count++; + return 0; +} + +static void xattr_fid_count_decr(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + + if (s->ctx.xattr_fid_count > 0) { + s->ctx.xattr_fid_count--; + } else { + error_report_once("9pfs: xattr_fid_count underflow detected"); + } +} + /* * returns 0 if fid got re-opened, 1 if not, < 0 on error */ @@ -395,6 +420,7 @@ } } else if (fidp->fid_type == P9_FID_XATTR) { retval = v9fs_xattr_fid_clunk(pdu, fidp); + xattr_fid_count_decr(pdu); } v9fs_path_free(&fidp->path); g_free(fidp); @@ -624,6 +650,14 @@ fidp->clunked = true; put_fid(pdu, fidp); } + + /* + * Explicitly reset the xattr FID counter. + * + * free_fid() already decrements the counter for each P9_FID_XATTR, so the + * counter should already be zero, hence this is just a defensive measure. + */ + s->ctx.xattr_fid_count = 0; } #define P9_QID_TYPE_DIR 0x80 @@ -1459,6 +1493,16 @@ goto out; } + /* cap msize to transport's theoretical limit */ + if (s->transport->msize_limit) { + size_t limit = s->transport->msize_limit(s); + if (s->msize > limit) { + s->msize = limit; + warn_report_once("9p: client msize capped to %zu (transport limit)", + limit); + } + } + /* 8192 is the default msize of Linux clients */ if (s->msize <= 8192 && !(s->ctx.export_flags & V9FS_NO_PERF_WARN)) { warn_report_once( @@ -1607,6 +1651,11 @@ pdu_complete(pdu, err); } +static bool fid_has_valid_file_handle(V9fsState *s, V9fsFidState *fidp) +{ + return s->ops->has_valid_file_handle(fidp->fid_type, &fidp->fs); +} + static void coroutine_fn v9fs_getattr(void *opaque) { int32_t fid; @@ -1629,9 +1678,7 @@ retval = -ENOENT; goto out_nofid; } - if ((fidp->fid_type == P9_FID_FILE && fidp->fs.fd != -1) || - (fidp->fid_type == P9_FID_DIR && fidp->fs.dir.stream)) - { + if (fid_has_valid_file_handle(pdu->s, fidp)) { retval = v9fs_co_fstat(pdu, fidp, &stbuf); } else { retval = v9fs_co_lstat(pdu, &fidp->path, &stbuf); @@ -2226,10 +2273,15 @@ err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fsync(pdu, fidp, datasync); if (!err) { err = offset; } +out: put_fid(pdu, fidp); out_nofid: pdu_complete(pdu, err); @@ -2634,6 +2686,7 @@ uint32_t max_count; V9fsPDU *pdu = opaque; V9fsState *s = pdu->s; + size_t max_resp_sz; retval = pdu_unmarshal(pdu, offset, "dqd", &fid, &initial_offset, &max_count); @@ -2642,9 +2695,28 @@ } trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count); + max_resp_sz = s->msize; + + /* + * Constrain max_count to transport's current, actual response buffer size. + * A bad client might provide a response buffer < msize. + */ + if (s->transport->response_buffer_size) { + size_t buf_size = s->transport->response_buffer_size(pdu); + if (max_resp_sz > buf_size) { + max_resp_sz = buf_size; + } + } + /* Enough space for a R_readdir header: size[4] Rreaddir tag[2] count[4] */ - if (max_count > s->msize - 11) { - max_count = s->msize - 11; + if (max_resp_sz > 11) { + max_resp_sz -= 11; + } else { + max_resp_sz = 0; + } + + if (max_count > max_resp_sz) { + max_count = max_resp_sz; warn_report_once( "9p: bad client: T_readdir with count > msize - 11" ); @@ -3563,6 +3635,10 @@ } /* do we need to sync the file? */ if (donttouch_stat(&v9stat)) { + if (!fid_has_valid_file_handle(s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fsync(pdu, fidp, 0); goto out; } @@ -3831,6 +3907,10 @@ err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fstat(pdu, fidp, &stbuf); if (err < 0) { goto out; @@ -3876,6 +3956,10 @@ err = -ENOENT; goto out_nofid; } + if (!fid_has_valid_file_handle(pdu->s, fidp)) { + err = -EBADF; + goto out; + } err = v9fs_co_fstat(pdu, fidp, &stbuf); if (err < 0) { goto out; @@ -3994,6 +4078,14 @@ clunk_fid(s, xattr_fidp->fid); goto out; } + + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + clunk_fid(s, xattr_fidp->fid); + goto out; + } + /* * Read the xattr value */ @@ -4001,6 +4093,7 @@ xattr_fidp->fid_type = P9_FID_XATTR; xattr_fidp->fs.xattr.xattrwalk_fid = true; xattr_fidp->fs.xattr.value = g_malloc0(size); + if (size) { err = v9fs_co_llistxattr(pdu, &xattr_fidp->path, xattr_fidp->fs.xattr.value, @@ -4027,6 +4120,14 @@ clunk_fid(s, xattr_fidp->fid); goto out; } + + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + clunk_fid(s, xattr_fidp->fid); + goto out; + } + /* * Read the xattr value */ @@ -4034,6 +4135,7 @@ xattr_fidp->fid_type = P9_FID_XATTR; xattr_fidp->fs.xattr.xattrwalk_fid = true; xattr_fidp->fs.xattr.value = g_malloc0(size); + if (size) { err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path, &name, xattr_fidp->fs.xattr.value, @@ -4125,6 +4227,12 @@ goto out_put_fid; } + /* Check xattr FID limit */ + err = xattr_fid_count_inc(pdu); + if (err < 0) { + goto out_put_fid; + } + /* Make the file fid point to xattr */ xattr_fidp = file_fidp; xattr_fidp->fid_type = P9_FID_XATTR; @@ -4387,6 +4495,10 @@ s->reclaiming = false; + /* init xattr FID limit from fsdev config */ + s->ctx.xattr_fid_limit = fse->max_xattr; + s->ctx.xattr_fid_count = 0; + rc = 0; out: if (rc) { diff -Nru qemu-10.0.11+ds/hw/9pfs/9p.h qemu-10.0.12+ds/hw/9pfs/9p.h --- qemu-10.0.11+ds/hw/9pfs/9p.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/9p.h 2026-07-25 01:10:13.000000000 +0300 @@ -481,6 +481,8 @@ void (*init_out_iov_from_pdu)(V9fsPDU *pdu, struct iovec **piov, unsigned int *pniov, size_t size); void (*push_and_notify)(V9fsPDU *pdu); + size_t (*msize_limit)(V9fsState *s); + size_t (*response_buffer_size)(V9fsPDU *pdu); }; #endif diff -Nru qemu-10.0.11+ds/hw/9pfs/virtio-9p-device.c qemu-10.0.12+ds/hw/9pfs/virtio-9p-device.c --- qemu-10.0.11+ds/hw/9pfs/virtio-9p-device.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/virtio-9p-device.c 2026-07-25 01:10:13.000000000 +0300 @@ -192,12 +192,29 @@ *pniov = elem->out_num; } +static size_t virtio_9p_msize_limit(V9fsState *s) +{ + const size_t guestPageSize = 4096; + return (VIRTQUEUE_MAX_SIZE - 2) * guestPageSize; +} + +static size_t virtio_9p_response_buffer_size(V9fsPDU *pdu) +{ + V9fsState *s = pdu->s; + V9fsVirtioState *v = container_of(s, V9fsVirtioState, state); + VirtQueueElement *elem = v->elems[pdu->idx]; + + return iov_size(elem->in_sg, elem->in_num); +} + static const V9fsTransport virtio_9p_transport = { .pdu_vmarshal = virtio_pdu_vmarshal, .pdu_vunmarshal = virtio_pdu_vunmarshal, .init_in_iov_from_pdu = virtio_init_in_iov_from_pdu, .init_out_iov_from_pdu = virtio_init_out_iov_from_pdu, .push_and_notify = virtio_9p_push_and_notify, + .msize_limit = virtio_9p_msize_limit, + .response_buffer_size = virtio_9p_response_buffer_size, }; static void virtio_9p_device_realize(DeviceState *dev, Error **errp) diff -Nru qemu-10.0.11+ds/hw/9pfs/xen-9p-backend.c qemu-10.0.12+ds/hw/9pfs/xen-9p-backend.c --- qemu-10.0.11+ds/hw/9pfs/xen-9p-backend.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/9pfs/xen-9p-backend.c 2026-07-25 01:10:13.000000000 +0300 @@ -250,12 +250,43 @@ qemu_bh_schedule(ring->bh); } +static size_t xen_9p_msize_limit(V9fsState *s) +{ + Xen9pfsDev *xen_9pfs = container_of(s, Xen9pfsDev, state); + size_t limit; + int i; + + if (!xen_9pfs->num_rings) { + return 0; + } + + limit = XEN_FLEX_RING_SIZE(xen_9pfs->rings[0].ring_order); + for (i = 1; i < xen_9pfs->num_rings; i++) { + limit = MIN(limit, XEN_FLEX_RING_SIZE(xen_9pfs->rings[i].ring_order)); + } + + return limit; +} + +static size_t xen_9pfs_response_buffer_size(V9fsPDU *pdu) +{ + Xen9pfsDev *priv = container_of(pdu->s, Xen9pfsDev, state); + Xen9pfsRing *ring = &priv->rings[pdu->tag % priv->num_rings]; + struct iovec in_sg[2]; + int num; + + xen_9pfs_in_sg(ring, in_sg, &num, pdu->idx, 0); + return iov_size(in_sg, num); +} + static const V9fsTransport xen_9p_transport = { .pdu_vmarshal = xen_9pfs_pdu_vmarshal, .pdu_vunmarshal = xen_9pfs_pdu_vunmarshal, .init_in_iov_from_pdu = xen_9pfs_init_in_iov_from_pdu, .init_out_iov_from_pdu = xen_9pfs_init_out_iov_from_pdu, .push_and_notify = xen_9pfs_push_and_notify, + .msize_limit = xen_9p_msize_limit, + .response_buffer_size = xen_9pfs_response_buffer_size, }; static int xen_9pfs_init(struct XenLegacyDevice *xendev) diff -Nru qemu-10.0.11+ds/hw/acpi/aml-build.c qemu-10.0.12+ds/hw/acpi/aml-build.c --- qemu-10.0.11+ds/hw/acpi/aml-build.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/acpi/aml-build.c 2026-07-25 01:10:13.000000000 +0300 @@ -2106,10 +2106,10 @@ build_append_int_noprefix(table_data, f->stop_bits, 1); /* Flow Control */ build_append_int_noprefix(table_data, f->flow_control, 1); - /* Language */ - build_append_int_noprefix(table_data, f->language, 1); /* Terminal Type */ build_append_int_noprefix(table_data, f->terminal_type, 1); + /* Language */ + build_append_int_noprefix(table_data, f->language, 1); /* PCI Device ID */ build_append_int_noprefix(table_data, f->pci_device_id, 2); /* PCI Vendor ID */ diff -Nru qemu-10.0.11+ds/hw/arm/npcm7xx.c qemu-10.0.12+ds/hw/arm/npcm7xx.c --- qemu-10.0.11+ds/hw/arm/npcm7xx.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/arm/npcm7xx.c 2026-07-25 01:10:13.000000000 +0300 @@ -492,7 +492,7 @@ /* CPUs */ for (i = 0; i < nc->num_cpus; i++) { object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar", - NPCM7XX_GIC_CPU_IF_ADDR, &error_abort); + NPCM7XX_CPUP_BA, &error_abort); object_property_set_bool(OBJECT(&s->cpu[i]), "reset-hivecs", true, &error_abort); diff -Nru qemu-10.0.11+ds/hw/audio/intel-hda.c qemu-10.0.12+ds/hw/audio/intel-hda.c --- qemu-10.0.11+ds/hw/audio/intel-hda.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/audio/intel-hda.c 2026-07-25 01:10:13.000000000 +0300 @@ -305,6 +305,7 @@ static void intel_hda_corb_run(IntelHDAState *d) { + const MemTxAttrs attrs = { .memory = true }; hwaddr addr; uint32_t rp, verb; @@ -330,7 +331,7 @@ rp = (d->corb_rp + 1) & 0xff; addr = intel_hda_addr(d->corb_lbase, d->corb_ubase); - ldl_le_pci_dma(&d->pci, addr + 4 * rp, &verb, MEMTXATTRS_UNSPECIFIED); + ldl_le_pci_dma(&d->pci, addr + 4 * rp, &verb, attrs); d->corb_rp = rp; dprint(d, 2, "%s: [rp 0x%x] verb 0x%08x\n", __func__, rp, verb); @@ -395,7 +396,7 @@ static bool intel_hda_xfer(HDACodecDevice *dev, uint32_t stnr, bool output, uint8_t *buf, uint32_t len) { - const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED; + const MemTxAttrs attrs = { .memory = true }; HDACodecBus *bus = HDA_BUS(dev->qdev.parent_bus); IntelHDAState *d = container_of(bus, IntelHDAState, codecs); hwaddr addr; @@ -466,6 +467,7 @@ static void intel_hda_parse_bdl(IntelHDAState *d, IntelHDAStream *st) { + const MemTxAttrs attrs = { .memory = true }; hwaddr addr; uint8_t buf[16]; uint32_t i; @@ -475,7 +477,8 @@ g_free(st->bpl); st->bpl = g_new(bpl, st->bentries); for (i = 0; i < st->bentries; i++, addr += 16) { - pci_dma_read(&d->pci, addr, buf, 16); + pci_dma_rw(&d->pci, addr, buf, 16, + DMA_DIRECTION_TO_DEVICE, attrs); st->bpl[i].addr = le64_to_cpu(*(uint64_t *)buf); st->bpl[i].len = le32_to_cpu(*(uint32_t *)(buf + 8)); st->bpl[i].flags = le32_to_cpu(*(uint32_t *)(buf + 12)); diff -Nru qemu-10.0.11+ds/hw/char/virtio-serial-bus.c qemu-10.0.12+ds/hw/char/virtio-serial-bus.c --- qemu-10.0.11+ds/hw/char/virtio-serial-bus.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/char/virtio-serial-bus.c 2026-07-25 01:10:13.000000000 +0300 @@ -344,22 +344,16 @@ } /* Guest wants to notify us of some event */ -static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) +static void handle_control_message(VirtIOSerial *vser, + struct virtio_console_control *gcpkt) { VirtIODevice *vdev = VIRTIO_DEVICE(vser); struct VirtIOSerialPort *port; VirtIOSerialPortClass *vsc; - struct virtio_console_control cpkt, *gcpkt; + struct virtio_console_control cpkt; uint8_t *buffer; size_t buffer_len; - gcpkt = buf; - - if (len < sizeof(cpkt)) { - /* The guest sent an invalid control packet */ - return; - } - cpkt.event = virtio_lduw_p(vdev, &gcpkt->event); cpkt.value = virtio_lduw_p(vdev, &gcpkt->value); @@ -457,41 +451,27 @@ static void control_out(VirtIODevice *vdev, VirtQueue *vq) { + struct virtio_console_control cpkt; VirtQueueElement *elem; VirtIOSerial *vser; - uint8_t *buf; size_t len; vser = VIRTIO_SERIAL(vdev); - len = 0; - buf = NULL; for (;;) { - size_t cur_len; - elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); if (!elem) { break; } - cur_len = iov_size(elem->out_sg, elem->out_num); - /* - * Allocate a new buf only if we didn't have one previously or - * if the size of the buf differs - */ - if (cur_len > len) { - g_free(buf); - - buf = g_malloc(cur_len); - len = cur_len; + len = iov_to_buf(elem->out_sg, elem->out_num, 0, &cpkt, sizeof(cpkt)); + if (len == sizeof(cpkt)) { + handle_control_message(vser, &cpkt); } - iov_to_buf(elem->out_sg, elem->out_num, 0, buf, cur_len); - handle_control_message(vser, buf, cur_len); virtqueue_push(vq, elem, 0); g_free(elem); } - g_free(buf); virtio_notify(vdev, vq); } diff -Nru qemu-10.0.11+ds/hw/core/qdev-clock.c qemu-10.0.12+ds/hw/core/qdev-clock.c --- qemu-10.0.11+ds/hw/core/qdev-clock.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/core/qdev-clock.c 2026-07-25 01:10:13.000000000 +0300 @@ -157,7 +157,14 @@ DeviceState *alias_dev, const char *alias_name) { NamedClockList *ncl = qdev_get_clocklist(dev, name); - Clock *clk = ncl->clock; + Clock *clk; + + if (!ncl) { + error_report("Can not find clock '%s' for device type '%s'", + name, object_get_typename(OBJECT(dev))); + abort(); + } + clk = ncl->clock; ncl = qdev_init_clocklist(alias_dev, alias_name, true, ncl->output, clk); diff -Nru qemu-10.0.11+ds/hw/display/qxl-render.c qemu-10.0.12+ds/hw/display/qxl-render.c --- qemu-10.0.11+ds/hw/display/qxl-render.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/qxl-render.c 2026-07-25 01:10:13.000000000 +0300 @@ -217,7 +217,8 @@ } static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl, - QXLDataChunk *chunk, uint32_t group_id) + QXLDataChunk *chunk, uint32_t group_id, + uint32_t chunk_data_size) { uint32_t max_chunks = 32; size_t offset = 0; @@ -225,22 +226,21 @@ QXLPHYSICAL next_chunk_phys = 0; for (;;) { - bytes = MIN(size - offset, chunk->data_size); + bytes = MIN(size - offset, chunk_data_size); memcpy(dest + offset, chunk->data, bytes); offset += bytes; if (offset == size) { return; } next_chunk_phys = chunk->next_chunk; - /* fist time, only get the next chunk's data size */ chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id, sizeof(QXLDataChunk)); if (!chunk) { return; } - /* second time, check data size and get data */ + chunk_data_size = chunk->data_size; chunk = qxl_phys2virt(qxl, next_chunk_phys, group_id, - sizeof(QXLDataChunk) + chunk->data_size); + sizeof(QXLDataChunk) + chunk_data_size); if (!chunk) { return; } @@ -252,7 +252,7 @@ } static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor, - uint32_t group_id) + uint32_t group_id, uint32_t chunk_data_size) { QEMUCursor *c; uint8_t *and_mask, *xor_mask; @@ -272,9 +272,11 @@ case SPICE_CURSOR_TYPE_MONO: /* Assume that the full cursor is available in a single chunk. */ size = 2 * cursor_get_mono_bpl(c) * c->height; - if (size != cursor->data_size) { - fprintf(stderr, "%s: bad monochrome cursor %ux%u with size %u\n", - __func__, c->width, c->height, cursor->data_size); + if (size != cursor->data_size || chunk_data_size < size) { + qxl_set_guest_bug(qxl, "%s: bad monochrome cursor %ux%u" + " data_size %u chunk_size %u", + __func__, c->width, c->height, + cursor->data_size, chunk_data_size); goto fail; } and_mask = cursor->chunk.data; @@ -286,7 +288,8 @@ break; case SPICE_CURSOR_TYPE_ALPHA: size = sizeof(uint32_t) * c->width * c->height; - qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id); + qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id, + chunk_data_size); if (qxl->debug > 2) { cursor_print_ascii_art(c, "qxl/alpha"); } @@ -323,19 +326,23 @@ } switch (cmd->type) { case QXL_CURSOR_SET: + { + uint32_t chunk_data_size; + /* First read the QXLCursor to get QXLDataChunk::data_size ... */ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id, sizeof(QXLCursor)); if (!cursor) { return 1; } + chunk_data_size = cursor->chunk.data_size; /* Then read including the chunked data following QXLCursor. */ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id, - sizeof(QXLCursor) + cursor->chunk.data_size); + sizeof(QXLCursor) + chunk_data_size); if (!cursor) { return 1; } - c = qxl_cursor(qxl, cursor, ext->group_id); + c = qxl_cursor(qxl, cursor, ext->group_id, chunk_data_size); if (c == NULL) { c = cursor_builtin_left_ptr(); } @@ -349,6 +356,7 @@ qemu_mutex_unlock(&qxl->ssd.lock); qemu_bh_schedule(qxl->ssd.cursor_bh); break; + } case QXL_CURSOR_MOVE: qemu_mutex_lock(&qxl->ssd.lock); qxl->ssd.mouse_x = cmd->u.position.x; diff -Nru qemu-10.0.11+ds/hw/display/qxl.c qemu-10.0.12+ds/hw/display/qxl.c --- qemu-10.0.11+ds/hw/display/qxl.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/qxl.c 2026-07-25 01:10:13.000000000 +0300 @@ -275,7 +275,7 @@ } cfg = qxl_phys2virt(qxl, qxl->guest_monitors_config, MEMSLOT_GROUP_GUEST, - sizeof(QXLMonitorsConfig)); + sizeof(QXLMonitorsConfig) + sizeof(QXLHead)); if (cfg != NULL && cfg->count == 1) { qxl->guest_primary.resized = 1; qxl->guest_head0_width = cfg->heads[0].width; diff -Nru qemu-10.0.11+ds/hw/display/sm501.c qemu-10.0.12+ds/hw/display/sm501.c --- qemu-10.0.11+ds/hw/display/sm501.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/sm501.c 2026-07-25 01:10:13.000000000 +0300 @@ -570,6 +570,25 @@ return index; } +static void set_new_local_mem_size_index(SM501State *s, uint32_t idx) +{ + /* + * Update local_mem_size_index on guest write. We don't allow this + * to be set to larger than the actual RAM size. (The guest will + * still read back the SYSTEM_CONTROL.Size bits that it wrote.) + */ + if (idx < ARRAY_SIZE(sm501_mem_local_size) && + sm501_mem_local_size[idx] <= memory_region_size(&s->local_mem_region)) { + s->local_mem_size_index = idx; + return; + } + qemu_log_mask(LOG_GUEST_ERROR, + "sm501: Guest set DRAM_CONTROL.Size to 0x%x but " + "local memory is not that large\n", + idx); + /* Don't change the effective size, leave it as whatever it was */ +} + static ram_addr_t get_fb_addr(SM501State *s, int crt) { return (crt ? s->dc_crt_fb_addr : s->dc_panel_fb_addr) & 0x3FFFFF0; @@ -681,6 +700,28 @@ get_fb_addr(s, crt) + start, end - start); } +static bool sm501_rect_outside_vram(SM501State *s, uint32_t base, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height, + uint32_t pitch, uint32_t bypp) +{ + /* + * Return true if the 2D area specified by the arguments is + * partially or completely outside the VRAM (a guest error) + * + * Limits on the input sizes mean we can't overflow as long as + * we do all the arithmetic at 64 bits. + */ + uint64_t rect_size, last_addr; + + assert(x <= UINT16_MAX && y <= UINT16_MAX && height <= UINT16_MAX && + pitch <= UINT16_MAX && bypp <= 8); + rect_size = (((uint64_t)y + height) * pitch + x + width) * bypp; + last_addr = base + rect_size; + + return last_addr >= get_local_mem_size(s); +} + static void sm501_2d_operation(SM501State *s) { int cmd = (s->twoD_control >> 16) & 0x1F; @@ -722,13 +763,16 @@ } if (rtl) { + if (dst_x < (width - 1) || dst_y < (height - 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "sm501: RTL op out of bounds\n"); + return; + } dst_x -= width - 1; dst_y -= height - 1; } - if (dst_base >= get_local_mem_size(s) || - dst_base + (dst_x + width + (dst_y + height) * dst_pitch) * bypp >= - get_local_mem_size(s)) { + if (sm501_rect_outside_vram(s, dst_base, dst_x, dst_y, width, height, + dst_pitch, bypp)) { qemu_log_mask(LOG_GUEST_ERROR, "sm501: 2D op dest is outside vram.\n"); return; } @@ -747,13 +791,16 @@ } if (rtl) { + if (src_x < (width - 1) || src_y < (height - 1)) { + qemu_log_mask(LOG_GUEST_ERROR, "sm501: RTL op out of bounds\n"); + return; + } src_x -= width - 1; src_y -= height - 1; } - if (src_base >= get_local_mem_size(s) || - src_base + (src_x + width + (src_y + height) * src_pitch) * bypp >= - get_local_mem_size(s)) { + if (sm501_rect_outside_vram(s, src_base, src_x, src_y, width, height, + src_pitch, bypp)) { qemu_log_mask(LOG_GUEST_ERROR, "sm501: 2D op src is outside vram.\n"); return; @@ -961,7 +1008,7 @@ ret = 0x050100A0; break; case SM501_DRAM_CONTROL: - ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13; + ret = (s->dram_control & 0x07F1E7C0); break; case SM501_ARBTRTN_CONTROL: ret = s->arbitration_control; @@ -1020,8 +1067,7 @@ s->gpio_63_32_control = value & 0xFF80FFFF; break; case SM501_DRAM_CONTROL: - s->local_mem_size_index = (value >> 13) & 0x7; - /* TODO : check validity of size change */ + set_new_local_mem_size_index(s, (value >> 13) & 0x7); s->dram_control &= 0x80000000; s->dram_control |= value & 0x7FFFFFC3; break; diff -Nru qemu-10.0.11+ds/hw/display/virtio-gpu-rutabaga.c qemu-10.0.12+ds/hw/display/virtio-gpu-rutabaga.c --- qemu-10.0.11+ds/hw/display/virtio-gpu-rutabaga.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/virtio-gpu-rutabaga.c 2026-07-25 01:10:13.000000000 +0300 @@ -351,6 +351,14 @@ VIRTIO_GPU_FILL_CMD(cs); trace_virtio_gpu_cmd_ctx_submit(cs.hdr.ctx_id, cs.size); + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: command buffer too large (%u)\n", + __func__, cs.size); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + buf = g_new0(uint8_t, cs.size); s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); diff -Nru qemu-10.0.11+ds/hw/display/virtio-gpu-udmabuf.c qemu-10.0.12+ds/hw/display/virtio-gpu-udmabuf.c --- qemu-10.0.11+ds/hw/display/virtio-gpu-udmabuf.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/virtio-gpu-udmabuf.c 2026-07-25 01:10:13.000000000 +0300 @@ -142,6 +142,7 @@ } virtio_gpu_remap_udmabuf(res); if (!res->remapped) { + virtio_gpu_destroy_udmabuf(res); return; } pdata = res->remapped; diff -Nru qemu-10.0.11+ds/hw/display/virtio-gpu-virgl.c qemu-10.0.12+ds/hw/display/virtio-gpu-virgl.c --- qemu-10.0.11+ds/hw/display/virtio-gpu-virgl.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/virtio-gpu-virgl.c 2026-07-25 01:10:13.000000000 +0300 @@ -486,6 +486,14 @@ VIRTIO_GPU_FILL_CMD(cs); trace_virtio_gpu_cmd_ctx_submit(cs.hdr.ctx_id, cs.size); + if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: command buffer too large (%u)\n", + __func__, cs.size); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } + buf = g_malloc(cs.size); s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); diff -Nru qemu-10.0.11+ds/hw/display/virtio-gpu.c qemu-10.0.12+ds/hw/display/virtio-gpu.c --- qemu-10.0.11+ds/hw/display/virtio-gpu.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/display/virtio-gpu.c 2026-07-25 01:10:13.000000000 +0300 @@ -670,6 +670,10 @@ void *ptr = data + fb->offset; rect = pixman_image_create_bits(fb->format, r->width, r->height, ptr, fb->stride); + if (!rect) { + *error = VIRTIO_GPU_RESP_ERR_UNSPEC; + return false; + } if (res->image) { pixman_image_ref(res->image); @@ -1336,8 +1340,15 @@ return -EINVAL; } - res->addrs = g_new(uint64_t, res->iov_cnt); - res->iov = g_new(struct iovec, res->iov_cnt); + res->addrs = g_try_new(uint64_t, res->iov_cnt); + res->iov = g_try_new(struct iovec, res->iov_cnt); + if (res->iov_cnt && (!res->addrs || !res->iov)) { + pixman_image_unref(res->image); + g_free(res->addrs); + g_free(res->iov); + g_free(res); + return -EINVAL; + } /* read data */ for (i = 0; i < res->iov_cnt; i++) { @@ -1409,8 +1420,15 @@ res->resource_id = resource_id; res->blob_size = qemu_get_be32(f); res->iov_cnt = qemu_get_be32(f); - res->addrs = g_new(uint64_t, res->iov_cnt); - res->iov = g_new(struct iovec, res->iov_cnt); + + res->addrs = g_try_new(uint64_t, res->iov_cnt); + res->iov = g_try_new(struct iovec, res->iov_cnt); + if (res->iov_cnt && (!res->addrs || !res->iov)) { + g_free(res->addrs); + g_free(res->iov); + g_free(res); + return -EINVAL; + } /* read data */ for (i = 0; i < res->iov_cnt; i++) { diff -Nru qemu-10.0.11+ds/hw/dma/i8257.c qemu-10.0.12+ds/hw/dma/i8257.c --- qemu-10.0.11+ds/hw/dma/i8257.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/dma/i8257.c 2026-07-25 01:10:13.000000000 +0300 @@ -406,6 +406,19 @@ hwaddr addr = ((r->pageh & 0x7f) << 24) | (r->page << 16) | r->now[ADDR]; if (i8257_is_verify_transfer(r)) { + /* + * If the device is expecting this verify operation then + * it won't care about the nonexistent data. But if it + * is expecting a real read (i.e. the guest has misprogrammed + * the DMA controller and the device) it's going to try to do + * something with the buffer contents. Give it zeroes. + * (It's not clear whether this is exactly what happens if + * you do this on real hardware. In practice no device QEMU + * emulates has a use for verify on a memory-read transfer, + * so we don't care beyond avoiding the guest being able to + * trigger the caller reading uninitialized data.) + */ + memset(buf, 0, len); return len; } diff -Nru qemu-10.0.11+ds/hw/ide/ahci.c qemu-10.0.12+ds/hw/ide/ahci.c --- qemu-10.0.11+ds/hw/ide/ahci.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/ide/ahci.c 2026-07-25 01:10:13.000000000 +0300 @@ -740,6 +740,9 @@ static void ahci_unmap_clb_address(AHCIDevice *ad) { + /* Cancel in-flight reads that would complete against a cleared cur_cmd. */ + ide_cancel_dma_sync(ide_bus_active_if(&ad->port)); + if (ad->lst == NULL) { trace_ahci_unmap_clb_address_null(ad->hba, ad->port_no); return; diff -Nru qemu-10.0.11+ds/hw/misc/applesmc.c qemu-10.0.12+ds/hw/misc/applesmc.c --- qemu-10.0.11+ds/hw/misc/applesmc.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/misc/applesmc.c 2026-07-25 01:10:13.000000000 +0300 @@ -333,9 +333,9 @@ applesmc_add_key(s, "REV ", 6, "\x01\x13\x0f\x00\x00\x03"); applesmc_add_key(s, "OSK0", 32, s->osk); applesmc_add_key(s, "OSK1", 32, s->osk + 32); - applesmc_add_key(s, "NATJ", 1, "\0"); - applesmc_add_key(s, "MSSP", 1, "\0"); - applesmc_add_key(s, "MSSD", 1, "\0x3"); + applesmc_add_key(s, "NATJ", 1, "\x00"); + applesmc_add_key(s, "MSSP", 1, "\x00"); + applesmc_add_key(s, "MSSD", 1, "\x03"); } static void applesmc_unrealize(DeviceState *dev) diff -Nru qemu-10.0.11+ds/hw/misc/edu.c qemu-10.0.12+ds/hw/misc/edu.c --- qemu-10.0.11+ds/hw/misc/edu.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/misc/edu.c 2026-07-25 01:10:13.000000000 +0300 @@ -103,7 +103,7 @@ } } -static void edu_check_range(uint64_t xfer_start, uint64_t xfer_size, +static bool edu_check_range(uint64_t xfer_start, uint64_t xfer_size, uint64_t dma_start, uint64_t dma_size) { uint64_t xfer_end = xfer_start + xfer_size; @@ -115,13 +115,15 @@ */ if (dma_end >= dma_start && xfer_end >= xfer_start && xfer_start >= dma_start && xfer_end <= dma_end) { - return; + return true; } qemu_log_mask(LOG_GUEST_ERROR, "EDU: DMA range 0x%016"PRIx64"-0x%016"PRIx64 " out of bounds (0x%016"PRIx64"-0x%016"PRIx64")!", xfer_start, xfer_end - 1, dma_start, dma_end - 1); + + return false; } static dma_addr_t edu_clamp_addr(const EduState *edu, dma_addr_t addr) @@ -148,16 +150,18 @@ if (EDU_DMA_DIR(edu->dma.cmd) == EDU_DMA_FROM_PCI) { uint64_t dst = edu->dma.dst; - edu_check_range(dst, edu->dma.cnt, DMA_START, DMA_SIZE); - dst -= DMA_START; - pci_dma_read(&edu->pdev, edu_clamp_addr(edu, edu->dma.src), - edu->dma_buf + dst, edu->dma.cnt); + if (edu_check_range(dst, edu->dma.cnt, DMA_START, DMA_SIZE)) { + dst -= DMA_START; + pci_dma_read(&edu->pdev, edu_clamp_addr(edu, edu->dma.src), + edu->dma_buf + dst, edu->dma.cnt); + } } else { uint64_t src = edu->dma.src; - edu_check_range(src, edu->dma.cnt, DMA_START, DMA_SIZE); - src -= DMA_START; - pci_dma_write(&edu->pdev, edu_clamp_addr(edu, edu->dma.dst), - edu->dma_buf + src, edu->dma.cnt); + if (edu_check_range(src, edu->dma.cnt, DMA_START, DMA_SIZE)) { + src -= DMA_START; + pci_dma_write(&edu->pdev, edu_clamp_addr(edu, edu->dma.dst), + edu->dma_buf + src, edu->dma.cnt); + } } edu->dma.cmd &= ~EDU_DMA_RUN; diff -Nru qemu-10.0.11+ds/hw/misc/ivshmem-pci.c qemu-10.0.12+ds/hw/misc/ivshmem-pci.c --- qemu-10.0.11+ds/hw/misc/ivshmem-pci.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/misc/ivshmem-pci.c 2026-07-25 01:10:13.000000000 +0300 @@ -920,6 +920,9 @@ IVShmemState *s = IVSHMEM_COMMON(dev); int i; + qemu_chr_fe_set_handlers(&s->server_chr, + NULL, NULL, NULL, NULL, NULL, NULL, true); + migrate_del_blocker(&s->migration_blocker); if (memory_region_is_mapped(s->ivshmem_bar2)) { @@ -948,6 +951,8 @@ close_peer_eventfds(s, i); } g_free(s->peers); + s->peers = NULL; + s->nb_peers = 0; } if (ivshmem_has_feature(s, IVSHMEM_MSI)) { @@ -955,6 +960,7 @@ } g_free(s->msi_vectors); + s->msi_vectors = NULL; } static int ivshmem_pre_load(void *opaque) diff -Nru qemu-10.0.11+ds/hw/misc/stm32_rcc.c qemu-10.0.12+ds/hw/misc/stm32_rcc.c --- qemu-10.0.11+ds/hw/misc/stm32_rcc.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/misc/stm32_rcc.c 2026-07-25 01:10:13.000000000 +0300 @@ -53,6 +53,27 @@ return value; } +static int reg_offset_to_irq_offset(hwaddr addr) +{ + /* + * The reset and enable registers aren't all consecutive. In getting the + * irq index from the register offset, we need to account for the gap + * between the AHB regs and the APB regs. + */ + switch (addr) { + case STM32_RCC_AHB1_RSTR ... STM32_RCC_AHB3_RSTR: + return ((addr - STM32_RCC_AHB1_RSTR) / 4) * 32; + case STM32_RCC_APB1_RSTR ... STM32_RCC_APB2_RSTR: + return ((addr - STM32_RCC_APB1_RSTR) / 4) * 32 + STM32_RCC_N_AHB_IRQS; + case STM32_RCC_AHB1_ENR ... STM32_RCC_AHB3_ENR: + return ((addr - STM32_RCC_AHB1_ENR) / 4) * 32; + case STM32_RCC_APB1_ENR ... STM32_RCC_APB2_ENR: + return ((addr - STM32_RCC_APB1_ENR) / 4) * 32 + STM32_RCC_N_AHB_IRQS; + default: + g_assert_not_reached(); + } +} + static void stm32_rcc_write(void *opaque, hwaddr addr, uint64_t val64, unsigned int size) { @@ -69,11 +90,12 @@ } switch (addr) { - case STM32_RCC_AHB1_RSTR...STM32_RCC_APB2_RSTR: + case STM32_RCC_AHB1_RSTR ... STM32_RCC_AHB3_RSTR: + case STM32_RCC_APB1_RSTR ... STM32_RCC_APB2_RSTR: prev_value = s->regs[addr / 4]; s->regs[addr / 4] = value; - irq_offset = ((addr - STM32_RCC_AHB1_RSTR) / 4) * 32; + irq_offset = reg_offset_to_irq_offset(addr); for (int i = 0; i < 32; i++) { new_value = extract32(value, i, 1); if (extract32(prev_value, i, 1) && !new_value) { @@ -82,11 +104,12 @@ } } return; - case STM32_RCC_AHB1_ENR...STM32_RCC_APB2_ENR: + case STM32_RCC_AHB1_ENR ... STM32_RCC_AHB3_ENR: + case STM32_RCC_APB1_ENR ... STM32_RCC_APB2_ENR: prev_value = s->regs[addr / 4]; s->regs[addr / 4] = value; - irq_offset = ((addr - STM32_RCC_AHB1_ENR) / 4) * 32; + irq_offset = reg_offset_to_irq_offset(addr); for (int i = 0; i < 32; i++) { new_value = extract32(value, i, 1); if (!extract32(prev_value, i, 1) && new_value) { diff -Nru qemu-10.0.11+ds/hw/net/cadence_gem.c qemu-10.0.12+ds/hw/net/cadence_gem.c --- qemu-10.0.11+ds/hw/net/cadence_gem.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/cadence_gem.c 2026-07-25 01:10:13.000000000 +0300 @@ -1469,6 +1469,8 @@ /* Set post reset register values */ memset(&s->regs[0], 0, sizeof(s->regs)); + memset(&s->rx_desc_addr[0], 0, sizeof(s->rx_desc_addr)); + memset(&s->tx_desc_addr[0], 0, sizeof(s->tx_desc_addr)); s->regs[R_NWCFG] = 0x00080000; s->regs[R_NWSTATUS] = 0x00000006; s->regs[R_DMACFG] = 0x00020784; @@ -1582,9 +1584,22 @@ offset >>= 2; retval = s->regs[offset]; - DB_PRINT("offset: 0x%04x read: 0x%08x\n", (unsigned)offset*4, retval); + DB_PRINT("offset: 0x%04x read: 0x%08x\n", (unsigned)offset * 4, + retval); switch (offset) { + case R_RXQBASE: + retval = s->rx_desc_addr[0]; + break; + case R_TXQBASE: + retval = s->tx_desc_addr[0]; + break; + case R_TRANSMIT_Q1_PTR ... R_TRANSMIT_Q7_PTR: + retval = s->tx_desc_addr[offset - R_TRANSMIT_Q1_PTR + 1]; + break; + case R_RECEIVE_Q1_PTR ... R_RECEIVE_Q7_PTR: + retval = s->rx_desc_addr[offset - R_RECEIVE_Q1_PTR + 1]; + break; case R_ISR: DB_PRINT("lowering irqs on ISR read\n"); /* The interrupts get updated at the end of the function. */ diff -Nru qemu-10.0.11+ds/hw/net/fsl_etsec/rings.c qemu-10.0.12+ds/hw/net/fsl_etsec/rings.c --- qemu-10.0.11+ds/hw/net/fsl_etsec/rings.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/fsl_etsec/rings.c 2026-07-25 01:10:13.000000000 +0300 @@ -175,15 +175,30 @@ static void process_tx_fcb(eTSEC *etsec) { uint8_t flags = (uint8_t)(*etsec->tx_buffer); - /* L3 header offset from start of frame */ + /* L3 header offset from start of frame (FCB byte 3) */ uint8_t l3_header_offset = (uint8_t)*(etsec->tx_buffer + 3); - /* L4 header offset from start of L3 header */ + /* L4 header offset from start of L3 header (FCB byte 2) */ uint8_t l4_header_offset = (uint8_t)*(etsec->tx_buffer + 2); + uint8_t *l3_header; + uint8_t *l4_header; + int csum = 0; + + /* + * Validate FCB header offsets before pointer arithmetic. The highest + * byte accessed is l4_header[7], at offset + * 8 (FCB size) + l3_header_offset + l4_header_offset + 7 + * from tx_buffer. Drop the frame if this exceeds the buffer length. + */ + if (etsec->tx_buffer_len < 8u + l3_header_offset + l4_header_offset + 8u) { + qemu_log_mask(LOG_GUEST_ERROR, + "eTSEC: FCB offsets exceed frame length, dropping\n"); + return; + } + /* L3 header */ - uint8_t *l3_header = etsec->tx_buffer + 8 + l3_header_offset; + l3_header = etsec->tx_buffer + 8 + l3_header_offset; /* L4 header */ - uint8_t *l4_header = l3_header + l4_header_offset; - int csum = 0; + l4_header = l3_header + l4_header_offset; /* if packet is IP4 and IP checksum is requested */ if (flags & FCB_TX_IP && flags & FCB_TX_CIP) { diff -Nru qemu-10.0.11+ds/hw/net/net_tx_pkt.c qemu-10.0.12+ds/hw/net/net_tx_pkt.c --- qemu-10.0.11+ds/hw/net/net_tx_pkt.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/net_tx_pkt.c 2026-07-25 01:10:13.000000000 +0300 @@ -93,9 +93,6 @@ uint16_t csum; assert(pkt); - pkt->l3_hdr.ip.ip_len = cpu_to_be16(pkt->payload_len + - pkt->vec[NET_TX_PKT_L3HDR_FRAG].iov_len); - pkt->l3_hdr.ip.ip_sum = 0; csum = net_raw_checksum(pkt->l3_hdr.octets, pkt->vec[NET_TX_PKT_L3HDR_FRAG].iov_len); @@ -117,7 +114,9 @@ if (gso_type == VIRTIO_NET_HDR_GSO_TCPV4 || gso_type == VIRTIO_NET_HDR_GSO_UDP) { - /* Calculate IP header checksum */ + /* Set ip_len and calculate IP header checksum */ + pkt->l3_hdr.ip.ip_len = cpu_to_be16(pkt->payload_len + + pkt->vec[NET_TX_PKT_L3HDR_FRAG].iov_len); net_tx_pkt_update_ip_hdr_checksum(pkt); /* Calculate IP pseudo header checksum */ diff -Nru qemu-10.0.11+ds/hw/net/virtio-net.c qemu-10.0.12+ds/hw/net/virtio-net.c --- qemu-10.0.11+ds/hw/net/virtio-net.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/virtio-net.c 2026-07-25 01:10:13.000000000 +0300 @@ -1380,6 +1380,11 @@ ebpf_rss_unload(&n->ebpf_rss); } +static bool virtio_net_rss_indirections_len_valid(uint16_t len) +{ + return is_power_of_2(len) && len <= VIRTIO_NET_RSS_MAX_TABLE_LEN; +} + static uint16_t virtio_net_handle_rss(VirtIONet *n, struct iovec *iov, unsigned int iov_cnt, @@ -1417,14 +1422,9 @@ if (!do_rss) { n->rss_data.indirections_len = 0; } - if (n->rss_data.indirections_len >= VIRTIO_NET_RSS_MAX_TABLE_LEN) { - err_msg = "Too large indirection table"; - err_value = n->rss_data.indirections_len; - goto error; - } n->rss_data.indirections_len++; - if (!is_power_of_2(n->rss_data.indirections_len)) { - err_msg = "Invalid size of indirection table"; + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) { + err_msg = "Invalid indirection table length"; err_value = n->rss_data.indirections_len; goto error; } @@ -3311,6 +3311,20 @@ }, }; +static int virtio_net_rss_post_load(void *opaque, int version_id) +{ + VirtIONet *n = VIRTIO_NET(opaque); + + if (!virtio_net_rss_indirections_len_valid(n->rss_data.indirections_len)) { + error_report("virtio-net: saved image has invalid RSS " + "indirections_len: %u", + n->rss_data.indirections_len); + return -EINVAL; + } + + return 0; +} + static bool virtio_net_rss_needed(void *opaque) { return VIRTIO_NET(opaque)->rss_data.enabled; @@ -3320,6 +3334,7 @@ .name = "virtio-net-device/rss", .version_id = 1, .minimum_version_id = 1, + .post_load = virtio_net_rss_post_load, .needed = virtio_net_rss_needed, .fields = (const VMStateField[]) { VMSTATE_BOOL(rss_data.enabled, VirtIONet), diff -Nru qemu-10.0.11+ds/hw/net/vmxnet3.c qemu-10.0.12+ds/hw/net/vmxnet3.c --- qemu-10.0.11+ds/hw/net/vmxnet3.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/vmxnet3.c 2026-07-25 01:10:13.000000000 +0300 @@ -1092,7 +1092,7 @@ int tx_queue_idx = VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_TXPROD, VMXNET3_REG_ALIGN); - if (tx_queue_idx <= s->txq_num) { + if (tx_queue_idx < s->txq_num) { vmxnet3_process_tx_queue(s, tx_queue_idx); } else { qemu_log_mask(LOG_GUEST_ERROR, "vmxnet3: Illegal TX queue %d/%d\n", diff -Nru qemu-10.0.11+ds/hw/net/xilinx_axienet.c qemu-10.0.12+ds/hw/net/xilinx_axienet.c --- qemu-10.0.11+ds/hw/net/xilinx_axienet.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/net/xilinx_axienet.c 2026-07-25 01:10:13.000000000 +0300 @@ -103,6 +103,9 @@ case 17: /* Marvell PHY on many xilinx boards. */ r = 0x8000; /* 1000Mb */ + if (phy->link) { + r |= 0x0400; /* Link is up */ + } break; case 18: { diff -Nru qemu-10.0.11+ds/hw/nvme/ctrl.c qemu-10.0.12+ds/hw/nvme/ctrl.c --- qemu-10.0.11+ds/hw/nvme/ctrl.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/nvme/ctrl.c 2026-07-25 01:10:13.000000000 +0300 @@ -1085,6 +1085,8 @@ } for (;;) { + size_t prev_len = len; + switch (NVME_SGL_TYPE(sgld->type)) { case NVME_SGL_DESCR_TYPE_SEGMENT: case NVME_SGL_DESCR_TYPE_LAST_SEGMENT: @@ -1165,6 +1167,17 @@ if (status) { goto unmap; } + + /* + * Reject if this segment made no forward progress. The host should + * have skipped linking an empty segment. While not strictly spec + * compliant, allowing this makes it easy for a pathological host to + * create an infinite loop. + */ + if (len == prev_len) { + status = NVME_INVALID_SGL_SEG_DESCR | NVME_DNR; + goto unmap; + } } out: @@ -6240,10 +6253,6 @@ for (uint8_t event_type = 0; event_type < FDP_EVT_MAX; event_type++) { uint8_t shift = nvme_fdp_evf_shifts[event_type]; if (!shift && event_type) { - /* - * only first entry (event_type == 0) has a shift value of 0 - * other entries are simply unpopulated. - */ continue; } @@ -6488,9 +6497,9 @@ uint8_t noet = (cdw11 >> 16) & 0xff; uint16_t ret, ruhid; uint8_t enable = le32_to_cpu(cmd->cdw12) & 0x1; - uint8_t event_mask = 0; + uint64_t event_mask = 0; unsigned int i; - g_autofree uint8_t *events = g_malloc0(noet); + g_autofree uint8_t *events = NULL; NvmeRuHandle *ruh = NULL; assert(ns); @@ -6503,15 +6512,29 @@ return NVME_INVALID_FIELD | NVME_DNR; } + if (unlikely(noet == 0)) { + return NVME_SUCCESS; + } + ruhid = ns->fdp.phs[ph]; ruh = &n->subsys->endgrp.fdp.ruhs[ruhid]; + events = g_malloc0(noet); + ret = nvme_h2c(n, events, noet, req); if (ret) { return ret; } for (i = 0; i < noet; i++) { + /* + * We ignore requests to enable tracking of unsupported FDP event types + */ + uint8_t event_type = events[i]; + uint8_t shift = nvme_fdp_evf_shifts[event_type]; + if (!shift && event_type) { + continue; + } event_mask |= (1 << nvme_fdp_evf_shifts[events[i]]); } diff -Nru qemu-10.0.11+ds/hw/nvme/nvme.h qemu-10.0.12+ds/hw/nvme/nvme.h --- qemu-10.0.11+ds/hw/nvme/nvme.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/nvme/nvme.h 2026-07-25 01:10:13.000000000 +0300 @@ -160,7 +160,14 @@ #define NVME_FDP_MAX_NS_RUHS 32u #define FDPVSS 0 -static const uint8_t nvme_fdp_evf_shifts[FDP_EVT_MAX] = { +/* + * NOTE: Apart from event type 0, any event type with a shift value of 0 is + * considered unsupported and thus skipped in get/set features calls. + * + * NOTE: NvmeRuHandle uses a 64bit event mask - refactor to support event types + * of 63 or greater. + */ +static const uint8_t nvme_fdp_evf_shifts[FDP_EVT_MAX + 1] = { /* Host events */ [FDP_EVT_RU_NOT_FULLY_WRITTEN] = 0, [FDP_EVT_RU_ATL_EXCEEDED] = 1, diff -Nru qemu-10.0.11+ds/hw/pci/pcie_doe.c qemu-10.0.12+ds/hw/pci/pcie_doe.c --- qemu-10.0.11+ds/hw/pci/pcie_doe.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/pci/pcie_doe.c 2026-07-25 01:10:13.000000000 +0300 @@ -78,14 +78,21 @@ return true; } +static void pcie_doe_reset_write_mbox(DOECap *st) +{ + st->write_mbox_len = 0; + + memset(st->write_mbox, 0, PCI_DOE_DW_SIZE_MAX * DWORD_BYTE); +} + static void pcie_doe_reset_mbox(DOECap *st) { st->read_mbox_idx = 0; st->read_mbox_len = 0; - st->write_mbox_len = 0; memset(st->read_mbox, 0, PCI_DOE_DW_SIZE_MAX * DWORD_BYTE); - memset(st->write_mbox, 0, PCI_DOE_DW_SIZE_MAX * DWORD_BYTE); + + pcie_doe_reset_write_mbox(st); } void pcie_doe_init(PCIDevice *dev, DOECap *doe_cap, uint16_t offset, @@ -356,8 +363,20 @@ if (size != DWORD_BYTE) { return; } - doe_cap->write_mbox[doe_cap->write_mbox_len] = val; - doe_cap->write_mbox_len++; + if (doe_cap->write_mbox_len < PCI_DOE_DW_SIZE_MAX) { + doe_cap->write_mbox[doe_cap->write_mbox_len] = val; + doe_cap->write_mbox_len++; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "Mailbox write length (%d) overflow\n", + doe_cap->write_mbox_len); + /* + * Too much data has been written, it can't + * "match the Length indicated in DOE Data Object Header 2" + * so we drop the entire object. + */ + pcie_doe_reset_write_mbox(doe_cap); + } break; case PCI_EXP_DOE_CAP: /* fallthrough */ diff -Nru qemu-10.0.11+ds/hw/riscv/riscv-iommu-bits.h qemu-10.0.12+ds/hw/riscv/riscv-iommu-bits.h --- qemu-10.0.11+ds/hw/riscv/riscv-iommu-bits.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/riscv/riscv-iommu-bits.h 2026-07-25 01:10:13.000000000 +0300 @@ -85,6 +85,7 @@ #define RISCV_IOMMU_CAP_SV57X4 BIT_ULL(19) #define RISCV_IOMMU_CAP_MSI_FLAT BIT_ULL(22) #define RISCV_IOMMU_CAP_MSI_MRIF BIT_ULL(23) +#define RISCV_IOMMU_CAP_AMO_HWAD BIT_ULL(24) #define RISCV_IOMMU_CAP_ATS BIT_ULL(25) #define RISCV_IOMMU_CAP_T2GPA BIT_ULL(26) #define RISCV_IOMMU_CAP_IGS GENMASK_ULL(29, 28) diff -Nru qemu-10.0.11+ds/hw/riscv/riscv-iommu-sys.c qemu-10.0.12+ds/hw/riscv/riscv-iommu-sys.c --- qemu-10.0.11+ds/hw/riscv/riscv-iommu-sys.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/riscv/riscv-iommu-sys.c 2026-07-25 01:10:13.000000000 +0300 @@ -27,6 +27,7 @@ #include "qemu/module.h" #include "qom/object.h" #include "exec/exec-all.h" +#include "target/riscv/cpu_bits.h" #include "trace.h" #include "riscv-iommu.h" @@ -150,7 +151,20 @@ address_space_stl_le(&address_space_memory, msi_addr, msi_data, MEMTXATTRS_UNSPECIFIED, &result); - trace_riscv_iommu_sys_msi_sent(vector, msi_addr, msi_data, result); + + if (result == MEMTX_OK) { + trace_riscv_iommu_sys_msi_sent(vector, msi_addr, msi_data, result); + } else { + /* Record an access fault error in the fault queue */ + struct riscv_iommu_fq_record ev = { 0 }; + RISCVIOMMUState *iommu = &s->iommu; + + ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_CAUSE, + RISCV_IOMMU_FQ_CAUSE_MSI_WR_FAULT); + ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, + RISCV_IOMMU_FQ_TTYPE_UADDR_WR); + riscv_iommu_fault(iommu, &ev); + } } static void riscv_iommu_sysdev_notify(RISCVIOMMUState *iommu, diff -Nru qemu-10.0.11+ds/hw/riscv/riscv-iommu.c qemu-10.0.12+ds/hw/riscv/riscv-iommu.c --- qemu-10.0.11+ds/hw/riscv/riscv-iommu.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/riscv/riscv-iommu.c 2026-07-25 01:10:13.000000000 +0300 @@ -46,7 +46,8 @@ IOMMUMemoryRegion iova_mr; /* IOVA memory region for attached device */ AddressSpace iova_as; /* IOVA address space for attached device */ RISCVIOMMUState *iommu; /* Managing IOMMU device state */ - uint32_t devid; /* Requester identifier, AKA device_id */ + PCIBus *bus; /* PCI bus of the requester */ + uint8_t devfn; /* Requester identifier, AKA device_id */ bool notifier; /* IOMMU unmap notifier enabled */ QLIST_ENTRY(RISCVIOMMUSpace) list; }; @@ -71,6 +72,15 @@ /* IOMMU index for transactions without process_id specified. */ #define RISCV_IOMMU_NOPROCID 0 +static uint32_t riscv_iommu_space_devid(RISCVIOMMUSpace *as) +{ + uint32_t devid = PCI_BUILD_BDF(pci_bus_num(as->bus), as->devfn); + + /* FIXME: PCIe bus remapping for attached endpoints. */ + devid |= as->iommu->bus << 8; + return devid; +} + static uint8_t riscv_iommu_get_icvec_vector(uint32_t icvec, uint32_t vec_type) { switch (vec_type) { @@ -105,8 +115,7 @@ } } -static void riscv_iommu_fault(RISCVIOMMUState *s, - struct riscv_iommu_fq_record *ev) +void riscv_iommu_fault(RISCVIOMMUState *s, struct riscv_iommu_fq_record *ev) { uint32_t ctrl = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_FQCSR); uint32_t head = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_FQH) & s->fq_mask; @@ -269,6 +278,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, IOMMUTLBEntry *iotlb) { + IOMMUAccessFlags pte_perm; dma_addr_t addr, base; uint64_t satp, gatp, pte; bool en_s, en_g; @@ -284,6 +294,7 @@ G_STAGE = 1, } pass; MemTxResult ret; + bool pv = !!ctx->process_id; satp = get_field(ctx->satp, RISCV_IOMMU_ATP_MODE_FIELD); gatp = get_field(ctx->gatp, RISCV_IOMMU_ATP_MODE_FIELD); @@ -403,6 +414,11 @@ const bool ade = ctx->tc & (pass ? RISCV_IOMMU_DC_TC_GADE : RISCV_IOMMU_DC_TC_SADE); + if (ade && !(s->cap & RISCV_IOMMU_CAP_AMO_HWAD)) { + /* GADE/SADE are reserved bits if AMO_HWAD is cleared. */ + return RISCV_IOMMU_FQ_CAUSE_DDT_MISCONFIGURED; + } + /* Address range check before first level lookup */ if (!sc[pass].step) { const uint64_t va_len = va_skip + va_bits; @@ -455,6 +471,15 @@ if (!(pte & PTE_V)) { break; /* Invalid PTE */ + } else if (pte & PTE_RESERVED) { + break; /* Reserved PTE bits set */ + } else if (!(pte & PTE_U) && !pv) { + /* + * All accesses are assumed to be User mode unless + * process_id is valid (pv). In case we have a + * non-user mode PTE and !pv we need to fault. + */ + break; } else if (!(pte & (PTE_R | PTE_W | PTE_X))) { base = PPN_PHYS(ppn); /* Inner PTE, continue walking */ } else if ((pte & (PTE_R | PTE_W | PTE_X)) == PTE_W) { @@ -467,10 +492,20 @@ break; /* Read access check failed */ } else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) { break; /* Write access check failed */ - } else if ((iotlb->perm & IOMMU_RO) && !ade && !(pte & PTE_A)) { + } else if (!ade && !(pte & PTE_A)) { break; /* Access bit not set */ } else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte & PTE_D)) { break; /* Dirty bit not set */ + } else if (pass == G_STAGE && !(pte & PTE_U)) { + /* + * riscv-iommu spec 1.0: "When checking the U bit in a + * second-stage PTE, the transaction is treated as + * not requesting supervisor privilege." + * + * I.e. we need to fault if this is a non-user PTE since + * we are always in user mode at this point. + */ + break; } else { /* Leaf PTE, translation completed. */ sc[pass].step = sc[pass].levels; @@ -496,8 +531,16 @@ } /* Translation phase completed (GPA or SPA) */ iotlb->translated_addr = base; - iotlb->perm = (pte & PTE_W) ? ((pte & PTE_R) ? IOMMU_RW : IOMMU_WO) - : IOMMU_RO; + + /* + * Do a bit_and between the PTE bits and the original + * request flags to determine the exact permission we + * need, i.e. if the original request is RO and the + * PTE has RW flags the actual perm is RO. + */ + pte_perm = (pte & PTE_W) ? ((pte & PTE_R) ? IOMMU_RW : IOMMU_WO) + : IOMMU_RO; + iotlb->perm &= pte_perm; /* Check MSI GPA address match */ if (pass == S_STAGE && (iotlb->perm & IOMMU_WO) && @@ -533,6 +576,14 @@ } } while (1); + /* + * riscv_iommu_translate() will receive a fault and then call + * riscv_iommu_report_fault() using iotlb->translated_addr + * as iotval2. Update translated_addr it with the latest + * translated addr we have. + */ + iotlb->translated_addr = addr; + return (iotlb->perm & IOMMU_WO) ? (pass ? RISCV_IOMMU_FQ_CAUSE_WR_FAULT_VS : RISCV_IOMMU_FQ_CAUSE_WR_FAULT_S) : @@ -635,6 +686,27 @@ switch (get_field(pte[0], RISCV_IOMMU_MSI_PTE_M)) { case RISCV_IOMMU_MSI_PTE_M_BASIC: + /* + * riscv-iommu spec MSI PTE basic translate mode: + * "When an MSI PTE has fields V = 1, C = 0, and M = 3 + * (basic translate mode), the PTE's complete format is: + * First doubleword: bit 63 C, = 0 + * bits 53:10 PPN + * bits 2:1 M, = 3 + * bit 0 V, = 1 + * All other bits of the first doubleword are reserved + * and must be set to zeros by software. The second + * doubleword is ignored by an IOMMU so is free for + * software to use." + * + * In other words, bits 62:54 and 9:3 of pte[0] are reserved. + */ + if (pte[0] & (GENMASK_ULL(62, 54) | GENMASK_ULL(9, 3))) { + res = MEMTX_DECODE_ERROR; + cause = RISCV_IOMMU_FQ_CAUSE_MSI_MISCONFIGURED; + goto err; + } + /* MSI Pass-through mode */ addr = PPN_PHYS(get_field(pte[0], RISCV_IOMMU_MSI_PTE_PPN)); @@ -777,6 +849,21 @@ return false; } + if (gatp != RISCV_IOMMU_DC_IOHGATP_MODE_BARE) { + uint64_t iohgatp_ppn = get_field(ctx->gatp, + RISCV_IOMMU_DC_IOHGATP_PPN); + + /* + * One of the conditions for a misconfigured DDT entry + * according to the riscv-spec: "DC.iohgatp.MODE is not + * Bare and the root page table (address) determined by + * DC.iohgatp.PPN is not aligned to a 16-KiB boundary." + */ + if (PPN_PHYS(iohgatp_ppn) & ((1ULL << 14) - 1)) { + return false; + } + } + fsc_mode = get_field(ctx->satp, RISCV_IOMMU_DC_FSC_MODE); if (ctx->tc & RISCV_IOMMU_DC_TC_PDTV) { @@ -1317,6 +1404,7 @@ /* Find or allocate translation context for a given {device_id, process_id} */ static RISCVIOMMUContext *riscv_iommu_ctx(RISCVIOMMUState *s, unsigned devid, unsigned process_id, + IOMMUAccessFlags perm, uint64_t iova, void **ref) { GHashTable *ctx_cache; @@ -1325,13 +1413,19 @@ .devid = devid, .process_id = process_id, }; + unsigned mode = get_field(s->ddtp, RISCV_IOMMU_DDTP_MODE); + uint32_t fault_type; ctx_cache = g_hash_table_ref(s->ctx_cache); - ctx = g_hash_table_lookup(ctx_cache, &key); - if (ctx && (ctx->tc & RISCV_IOMMU_DC_TC_V)) { - *ref = ctx_cache; - return ctx; + if (mode != RISCV_IOMMU_DDTP_MODE_OFF && + mode != RISCV_IOMMU_DDTP_MODE_BARE) { + ctx = g_hash_table_lookup(ctx_cache, &key); + + if (ctx && (ctx->tc & RISCV_IOMMU_DC_TC_V)) { + *ref = ctx_cache; + return ctx; + } } ctx = g_new0(RISCVIOMMUContext, 1); @@ -1356,8 +1450,21 @@ g_hash_table_unref(ctx_cache); *ref = NULL; - riscv_iommu_report_fault(s, ctx, RISCV_IOMMU_FQ_TTYPE_UADDR_RD, - fault, !!process_id, 0, 0); + /* + * TODO: (1) do we need to distinguish other fault types + * for ctx fetching and (2) evaluate putting the 'fault_type' + * logic inside riscv_iommu_report_fault() - there's at + * least one other place (end of riscv_iommu_translate()) + * that does something similar. + */ + if (perm & IOMMU_RO) { + fault_type = RISCV_IOMMU_FQ_TTYPE_UADDR_RD; + } else { + fault_type = RISCV_IOMMU_FQ_TTYPE_UADDR_WR; + } + + riscv_iommu_report_fault(s, ctx, fault_type, fault, + !!process_id, iova, 0); g_free(ctx); return NULL; @@ -1371,15 +1478,13 @@ } /* Find or allocate address space for a given device */ -static AddressSpace *riscv_iommu_space(RISCVIOMMUState *s, uint32_t devid) +static AddressSpace *riscv_iommu_space(RISCVIOMMUState *s, PCIBus *bus, + int devfn) { RISCVIOMMUSpace *as; - /* FIXME: PCIe bus remapping for attached endpoints. */ - devid |= s->bus << 8; - QLIST_FOREACH(as, &s->spaces, list) { - if (as->devid == devid) { + if (as->bus == bus && as->devfn == devfn) { break; } } @@ -1389,21 +1494,22 @@ as = g_new0(RISCVIOMMUSpace, 1); as->iommu = s; - as->devid = devid; + as->bus = bus; + as->devfn = devfn; snprintf(name, sizeof(name), "riscv-iommu-%04x:%02x.%d-iova", - PCI_BUS_NUM(as->devid), PCI_SLOT(as->devid), PCI_FUNC(as->devid)); + pci_bus_num(bus), PCI_SLOT(devfn), PCI_FUNC(devfn)); /* IOVA address space, untranslated addresses */ memory_region_init_iommu(&as->iova_mr, sizeof(as->iova_mr), TYPE_RISCV_IOMMU_MEMORY_REGION, - OBJECT(as), "riscv_iommu", UINT64_MAX); + OBJECT(s), "riscv_iommu", UINT64_MAX); address_space_init(&as->iova_as, MEMORY_REGION(&as->iova_mr), name); QLIST_INSERT_HEAD(&s->spaces, as, list); - trace_riscv_iommu_new(s->parent_obj.id, PCI_BUS_NUM(as->devid), - PCI_SLOT(as->devid), PCI_FUNC(as->devid)); + trace_riscv_iommu_new(s->parent_obj.id, pci_bus_num(bus), + PCI_SLOT(devfn), PCI_FUNC(devfn)); } return &as->iova_as; } @@ -1674,7 +1780,8 @@ if (fault) { unsigned ttype = RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ; - if (iotlb->perm & IOMMU_RW) { + if ((iotlb->perm & IOMMU_RW) == IOMMU_RW + || iotlb->perm & IOMMU_WO) { ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_WR; } else if (iotlb->perm & IOMMU_RO) { ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_RD; @@ -1727,7 +1834,7 @@ pid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_PID); QLIST_FOREACH(as, &s->spaces, list) { - if (as->devid == devid) { + if (riscv_iommu_space_devid(as) == devid) { break; } } @@ -2088,6 +2195,8 @@ uint64_t ctrl = riscv_iommu_reg_get64(s, RISCV_IOMMU_REG_TR_REQ_CTL); unsigned devid = get_field(ctrl, RISCV_IOMMU_TR_REQ_CTL_DID); unsigned pid = get_field(ctrl, RISCV_IOMMU_TR_REQ_CTL_PID); + IOMMUAccessFlags perm = ctrl & RISCV_IOMMU_TR_REQ_CTL_NW + ? IOMMU_RO : IOMMU_RW; RISCVIOMMUContext *ctx; void *ref; @@ -2095,7 +2204,7 @@ return; } - ctx = riscv_iommu_ctx(s, devid, pid, &ref); + ctx = riscv_iommu_ctx(s, devid, pid, perm, iova, &ref); if (ctx == NULL) { riscv_iommu_reg_set64(s, RISCV_IOMMU_REG_TR_RESPONSE, RISCV_IOMMU_TR_RESPONSE_FAULT | @@ -2103,7 +2212,7 @@ } else { IOMMUTLBEntry iotlb = { .iova = iova, - .perm = ctrl & RISCV_IOMMU_TR_REQ_CTL_NW ? IOMMU_RO : IOMMU_RW, + .perm = perm, .addr_mask = ~0, .target_as = NULL, }; @@ -2442,7 +2551,7 @@ /* FIXME: PCIe bus remapping for attached endpoints. */ devid |= s->bus << 8; - ctx = riscv_iommu_ctx(s, devid, 0, &ref); + ctx = riscv_iommu_ctx(s, devid, 0, IOMMU_RW, addr, &ref); if (ctx == NULL) { res = MEMTX_ACCESS_ERROR; } else { @@ -2730,8 +2839,9 @@ .addr_mask = ~0ULL, .perm = flag, }; + uint32_t devid = riscv_iommu_space_devid(as); - ctx = riscv_iommu_ctx(as->iommu, as->devid, iommu_idx, &ref); + ctx = riscv_iommu_ctx(as->iommu, devid, iommu_idx, flag, addr, &ref); if (ctx == NULL) { /* Translation disabled or invalid. */ iotlb.addr_mask = 0; @@ -2743,8 +2853,8 @@ } /* Trace all dma translations with original access flags. */ - trace_riscv_iommu_dma(as->iommu->parent_obj.id, PCI_BUS_NUM(as->devid), - PCI_SLOT(as->devid), PCI_FUNC(as->devid), iommu_idx, + trace_riscv_iommu_dma(as->iommu->parent_obj.id, PCI_BUS_NUM(devid), + PCI_SLOT(devid), PCI_FUNC(devid), iommu_idx, IOMMU_FLAG_STR[flag & IOMMU_RW], iotlb.iova, iotlb.translated_addr); @@ -2792,7 +2902,7 @@ /* Find first matching IOMMU */ while (s != NULL && as == NULL) { - as = riscv_iommu_space(s, PCI_BUILD_BDF(pci_bus_num(bus), devfn)); + as = riscv_iommu_space(s, bus, devfn); s = s->iommus.le_next; } diff -Nru qemu-10.0.11+ds/hw/riscv/riscv-iommu.h qemu-10.0.12+ds/hw/riscv/riscv-iommu.h --- qemu-10.0.11+ds/hw/riscv/riscv-iommu.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/riscv/riscv-iommu.h 2026-07-25 01:10:13.000000000 +0300 @@ -98,6 +98,7 @@ void riscv_iommu_set_cap_igs(RISCVIOMMUState *s, riscv_iommu_igs_mode mode); void riscv_iommu_reset(RISCVIOMMUState *s); void riscv_iommu_notify(RISCVIOMMUState *s, int vec_type); +void riscv_iommu_fault(RISCVIOMMUState *s, struct riscv_iommu_fq_record *ev); typedef struct RISCVIOMMUContext RISCVIOMMUContext; /* Device translation context state. */ diff -Nru qemu-10.0.11+ds/hw/riscv/virt-acpi-build.c qemu-10.0.12+ds/hw/riscv/virt-acpi-build.c --- qemu-10.0.11+ds/hw/riscv/virt-acpi-build.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/riscv/virt-acpi-build.c 2026-07-25 01:10:13.000000000 +0300 @@ -100,6 +100,8 @@ build_append_int_noprefix(entry, ACPI_BUILD_INTC_ID( arch_ids->cpus[uid].props.node_id, + kvm_enabled() ? + local_cpu_id : 2 * local_cpu_id + 1), 4); } else { diff -Nru qemu-10.0.11+ds/hw/s390x/css.c qemu-10.0.12+ds/hw/s390x/css.c --- qemu-10.0.11+ds/hw/s390x/css.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/s390x/css.c 2026-07-25 01:10:13.000000000 +0300 @@ -1104,6 +1104,12 @@ ret = -EINVAL; break; } + /* Limit the number of TICs in a given channel program */ + if (sch->ccw_tic_cnt == 255) { + ret = -EINVAL; + break; + } + sch->ccw_tic_cnt++; sch->channel_prog = ccw.cda; ret = -EAGAIN; break; @@ -1155,6 +1161,7 @@ sch->ccw_fmt_1 = !!(orb->ctrl0 & ORB_CTRL0_MASK_FMT); schib->scsw.flags |= (sch->ccw_fmt_1) ? SCSW_FLAGS_MASK_FMT : 0; sch->ccw_no_data_cnt = 0; + sch->ccw_tic_cnt = 0; suspend_allowed = !!(orb->ctrl0 & ORB_CTRL0_MASK_SPND); } else { /* Start Function resumed via rsch */ @@ -1900,6 +1907,7 @@ int i, desc_size; uint32_t words[8]; uint32_t chpid_type_word; + uint32_t max_chpids, chpid_count = 0; CssImage *css; if (!m && !cssid) { @@ -1910,9 +1918,25 @@ if (!css) { return 0; } + + if (rfmt == 0) { + max_chpids = 256; + } else if (rfmt == 1) { + max_chpids = 127; + } else { + /* Should be rejected by caller */ + return 0; + } + desc_size = 0; for (i = f_chpid; i <= l_chpid; i++) { if (css->chpids[i].in_use) { + /* Limit number of CHPIDs sent back */ + if (chpid_count == max_chpids) { + break; + } + + chpid_count++; chpid_type_word = 0x80000000 | (css->chpids[i].type << 8) | i; if (rfmt == 0) { words[0] = cpu_to_be32(chpid_type_word); diff -Nru qemu-10.0.11+ds/hw/s390x/event-facility.c qemu-10.0.12+ds/hw/s390x/event-facility.c --- qemu-10.0.11+ds/hw/s390x/event-facility.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/s390x/event-facility.c 2026-07-25 01:10:13.000000000 +0300 @@ -291,6 +291,7 @@ static void write_event_mask(SCLPEventFacility *ef, SCCB *sccb) { WriteEventMask *we_mask = (WriteEventMask *) sccb; + uint16_t sccb_length = be16_to_cpu(sccb->h.length); uint16_t mask_length = be16_to_cpu(we_mask->mask_length); sccb_mask_t tmp_mask; @@ -300,6 +301,11 @@ return; } + if (sccb_length < sizeof(WriteEventMask) + 4 * mask_length) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INSUFFICIENT_SCCB_LENGTH); + return; + } + /* * Note: We currently only support masks up to 8 byte length; * the remainder is filled up with zeroes. Older Linux diff -Nru qemu-10.0.11+ds/hw/s390x/s390-pci-inst.c qemu-10.0.12+ds/hw/s390x/s390-pci-inst.c --- qemu-10.0.11+ds/hw/s390x/s390-pci-inst.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/s390x/s390-pci-inst.c 2026-07-25 01:10:13.000000000 +0300 @@ -390,13 +390,22 @@ static MemoryRegion *s390_get_subregion(MemoryRegion *mr, uint64_t offset, uint8_t len) { + uint64_t last = offset + len; MemoryRegion *subregion; uint64_t subregion_size; + /* + * Ensure the region is valid, the calculated address cannot wrap and that + * it falls within this region. + */ + if (!mr || offset > last || last > memory_region_size(mr)) { + return NULL; + } + QTAILQ_FOREACH(subregion, &mr->subregions, subregions_link) { subregion_size = int128_get64(subregion->size); if ((offset >= subregion->addr) && - (offset + len) <= (subregion->addr + subregion_size)) { + (last) <= (subregion->addr + subregion_size)) { mr = subregion; break; } @@ -411,6 +420,10 @@ mr = pbdev->pdev->io_regions[pcias].memory; mr = s390_get_subregion(mr, offset, len); + if (!mr) { + return MEMTX_ERROR; + } + offset -= mr->addr; return memory_region_dispatch_read(mr, offset, data, size_memop(len) | MO_BE, @@ -511,6 +524,10 @@ mr = pbdev->pdev->io_regions[pcias].memory; mr = s390_get_subregion(mr, offset, len); + if (!mr) { + return MEMTX_ERROR; + } + offset -= mr->addr; return memory_region_dispatch_write(mr, offset, data, size_memop(len) | MO_BE, @@ -751,10 +768,16 @@ goto err; } - if (end < iommu->pba || start > iommu->pal) { + if (end < start || end < iommu->pba || start > iommu->pal) { error = ERR_EVENT_OORANGE; goto err; } + /* + * If the specified range at least partially overlaps the registered + * aperture, clamp the request to the aperture and ignore the rest. + */ + sstart = MAX(start, iommu->pba); + end = MIN(end, iommu->pal + 1); retry: start = sstart; @@ -898,6 +921,11 @@ mr = pbdev->pdev->io_regions[pcias].memory; mr = s390_get_subregion(mr, offset, len); + if (!mr) { + s390_program_interrupt(env, PGM_OPERAND, ra); + return 0; + } + offset -= mr->addr; for (i = 0; i < len; i += 8) { diff -Nru qemu-10.0.11+ds/hw/s390x/sclp.c qemu-10.0.12+ds/hw/s390x/sclp.c --- qemu-10.0.11+ds/hw/s390x/sclp.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/s390x/sclp.c 2026-07-25 01:10:13.000000000 +0300 @@ -303,6 +303,9 @@ SCLPDeviceClass *sclp_c = SCLP_GET_CLASS(sclp); SCCBHeader header; g_autofree SCCB *work_sccb = NULL; + AddressSpace *as = CPU(cpu)->as; + const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED; + MemTxResult ret; /* first some basic checks on program checks */ if (env->psw.mask & PSW_MASK_PSTATE) { @@ -317,7 +320,10 @@ } /* the header contains the actual length of the sccb */ - cpu_physical_memory_read(sccb, &header, sizeof(SCCBHeader)); + ret = address_space_read(as, sccb, attrs, &header, sizeof(SCCBHeader)); + if (ret != MEMTX_OK) { + return -PGM_ADDRESSING; + } /* Valid sccb sizes */ if (be16_to_cpu(header.length) < sizeof(SCCBHeader)) { @@ -327,10 +333,16 @@ /* * we want to work on a private copy of the sccb, to prevent guests * from playing dirty tricks by modifying the memory content after - * the host has checked the values + * the host has checked the values. + * Reuse the previously fetched header */ work_sccb = g_malloc0(be16_to_cpu(header.length)); - cpu_physical_memory_read(sccb, work_sccb, be16_to_cpu(header.length)); + ret = address_space_read(as, sccb, attrs, + work_sccb, be16_to_cpu(header.length)); + if (ret != MEMTX_OK) { + return -PGM_ADDRESSING; + } + work_sccb->h = header; if (!sclp_command_code_valid(code)) { work_sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND); @@ -344,8 +356,11 @@ sclp_c->execute(sclp, work_sccb, code); out_write: - cpu_physical_memory_write(sccb, work_sccb, - be16_to_cpu(work_sccb->h.length)); + ret = address_space_write(as, sccb, attrs, + work_sccb, be16_to_cpu(header.length)); + if (ret != MEMTX_OK) { + return -PGM_PROTECTION; + } sclp_c->service_interrupt(sclp, sccb); diff -Nru qemu-10.0.11+ds/hw/scsi/mptsas.c qemu-10.0.12+ds/hw/scsi/mptsas.c --- qemu-10.0.11+ds/hw/scsi/mptsas.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/scsi/mptsas.c 2026-07-25 01:10:13.000000000 +0300 @@ -811,6 +811,10 @@ s->intr_status = 0; s->intr_mask = save_mask; + s->doorbell_state = DOORBELL_NONE; + s->doorbell_reply_idx = 0; + s->doorbell_reply_size = 0; + s->reply_free_tail = 0; s->reply_free_head = 0; s->reply_post_tail = 0; diff -Nru qemu-10.0.11+ds/hw/scsi/vmw_pvscsi.c qemu-10.0.12+ds/hw/scsi/vmw_pvscsi.c --- qemu-10.0.11+ds/hw/scsi/vmw_pvscsi.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/scsi/vmw_pvscsi.c 2026-07-25 01:10:13.000000000 +0300 @@ -404,9 +404,18 @@ pvscsi_cmp_ring_put(PVSCSIState *s, struct PVSCSIRingCmpDesc *cmp_desc) { hwaddr cmp_descr_pa; + PVSCSIRingCmpDesc cmp_desc_conv; cmp_descr_pa = pvscsi_ring_pop_cmp_descr(&s->rings); trace_pvscsi_cmp_ring_put(cmp_descr_pa); + cmp_desc_conv = (struct PVSCSIRingCmpDesc) { + .context = cpu_to_le64(cmp_desc->context), + .dataLen = cpu_to_le64(cmp_desc->dataLen), + .senseLen = cpu_to_le32(cmp_desc->senseLen), + .hostStatus = cpu_to_le16(cmp_desc->hostStatus), + .scsiStatus = cpu_to_le16(cmp_desc->scsiStatus), + }; + cmp_desc = &cmp_desc_conv; cpu_physical_memory_write(cmp_descr_pa, cmp_desc, sizeof(*cmp_desc)); } @@ -414,9 +423,18 @@ pvscsi_msg_ring_put(PVSCSIState *s, struct PVSCSIRingMsgDesc *msg_desc) { hwaddr msg_descr_pa; + PVSCSIRingMsgDesc msg_desc_conv; + int i; msg_descr_pa = pvscsi_ring_pop_msg_descr(&s->rings); trace_pvscsi_msg_ring_put(msg_descr_pa); + msg_desc_conv = (PVSCSIRingMsgDesc) { + .type = cpu_to_le32(msg_desc->type), + }; + for (i = 0; i < ARRAY_SIZE(msg_desc->args); i++) { + msg_desc_conv.args[i] = cpu_to_le32(msg_desc->args[i]); + } + msg_desc = &msg_desc_conv; cpu_physical_memory_write(msg_descr_pa, msg_desc, sizeof(*msg_desc)); } @@ -493,6 +511,9 @@ struct PVSCSISGElement elem; cpu_physical_memory_read(sg->elemAddr, &elem, sizeof(elem)); + elem.addr = le64_to_cpu(elem.addr); + elem.length = le32_to_cpu(elem.length); + elem.flags = le32_to_cpu(elem.flags); if ((elem.flags & ~PVSCSI_KNOWN_FLAGS) != 0) { /* * There is PVSCSI_SGE_FLAG_CHAIN_ELEMENT flag described in @@ -771,6 +792,12 @@ trace_pvscsi_process_io(next_descr_pa); cpu_physical_memory_read(next_descr_pa, &descr, sizeof(descr)); + descr.context = le64_to_cpu(descr.context); + descr.dataAddr = le64_to_cpu(descr.dataAddr); + descr.dataLen = le64_to_cpu(descr.dataLen); + descr.senseAddr = le64_to_cpu(descr.senseAddr); + descr.senseLen = le32_to_cpu(descr.senseLen); + descr.flags = le32_to_cpu(descr.flags); pvscsi_process_request_descriptor(s, &descr); } @@ -820,6 +847,17 @@ { PVSCSICmdDescSetupRings *rc = (PVSCSICmdDescSetupRings *) s->curr_cmd_data; + PVSCSICmdDescSetupRings translated; + int i; + + translated.reqRingNumPages = le32_to_cpu(rc->reqRingNumPages); + translated.cmpRingNumPages = le32_to_cpu(rc->cmpRingNumPages); + translated.ringsStatePPN = le64_to_cpu(rc->ringsStatePPN); + for (i = 0; i < PVSCSI_SETUP_RINGS_MAX_NUM_PAGES; i++) { + translated.reqRingPPNs[i] = le64_to_cpu(rc->reqRingPPNs[i]); + translated.cmpRingPPNs[i] = le64_to_cpu(rc->cmpRingPPNs[i]); + } + rc = &translated; trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_RINGS"); @@ -843,6 +881,11 @@ PVSCSICmdDescAbortCmd *cmd = (PVSCSICmdDescAbortCmd *) s->curr_cmd_data; PVSCSIRequest *r, *next; + PVSCSICmdDescAbortCmd translated = *cmd; + translated.context = le32_to_cpu(cmd->context); + translated.target = le32_to_cpu(cmd->target); + cmd = &translated; + trace_pvscsi_on_cmd_abort(cmd->context, cmd->target); QTAILQ_FOREACH_SAFE(r, &s->pending_queue, next, next) { @@ -874,6 +917,10 @@ (struct PVSCSICmdDescResetDevice *) s->curr_cmd_data; SCSIDevice *sdev; + PVSCSICmdDescResetDevice translated = *cmd; + translated.target = le32_to_cpu(cmd->target); + cmd = &translated; + sdev = pvscsi_device_find(s, 0, cmd->target, cmd->lun, &target_lun); trace_pvscsi_on_cmd_reset_dev(cmd->target, (int) target_lun, sdev); @@ -904,6 +951,14 @@ { PVSCSICmdDescSetupMsgRing *rc = (PVSCSICmdDescSetupMsgRing *) s->curr_cmd_data; + PVSCSICmdDescSetupMsgRing translated = *rc; + int i; + + translated.numPages = le32_to_cpu(rc->numPages); + for (i = 0; i < PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES; i++) { + translated.ringPPNs[i] = le64_to_cpu(rc->ringPPNs[i]); + } + rc = &translated; trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_MSG_RING"); @@ -1006,7 +1061,7 @@ size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t); assert(bytes_arrived < sizeof(s->curr_cmd_data)); - s->curr_cmd_data[s->curr_cmd_data_cntr++] = value; + s->curr_cmd_data[s->curr_cmd_data_cntr++] = cpu_to_le32(value); pvscsi_do_command_processing(s); } diff -Nru qemu-10.0.11+ds/hw/scsi/vmw_pvscsi.h qemu-10.0.12+ds/hw/scsi/vmw_pvscsi.h --- qemu-10.0.11+ds/hw/scsi/vmw_pvscsi.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/scsi/vmw_pvscsi.h 2026-07-25 01:10:13.000000000 +0300 @@ -109,6 +109,20 @@ #define PVSCSI_COMMAND_NOT_ENOUGH_DATA (-2) /* + * About endianess for the below structs: + * + * These structs are used to describe the data that is exchanged between the + * guest and the PVSCSI device. The endianess of the fields in these structs + * is not defined by any standard. The current implemented drivers are designed + * to only work on x86 architecture, so there is no endianess awareness in the + * drivers and thus we have no idea whether the fields should be in little- + * endian or target native endian format. + * + * Considering the above, we assume that PVSCSI is implicitly little-endian and + * expect the fields in these structs to be in little-endian format. + */ + +/* * Command descriptor for PVSCSI_CMD_RESET_DEVICE -- */ diff -Nru qemu-10.0.11+ds/hw/sparc64/niagara.c qemu-10.0.12+ds/hw/sparc64/niagara.c --- qemu-10.0.11+ds/hw/sparc64/niagara.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/sparc64/niagara.c 2026-07-25 01:10:13.000000000 +0300 @@ -137,7 +137,7 @@ outside of the partition RAM */ if (dinfo) { BlockBackend *blk = blk_by_legacy_dinfo(dinfo); - int size = blk_getlength(blk); + int64_t size = blk_getlength(blk); if (size > 0) { memory_region_init_ram(&s->vdisk_ram, NULL, "sun4v_vdisk.ram", size, &error_fatal); diff -Nru qemu-10.0.11+ds/hw/ufs/lu.c qemu-10.0.12+ds/hw/ufs/lu.c --- qemu-10.0.11+ds/hw/ufs/lu.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/ufs/lu.c 2026-07-25 01:10:13.000000000 +0300 @@ -412,10 +412,7 @@ { UfsLu *lu = DO_UPCAST(UfsLu, qdev, dev); - if (lu->scsi_dev) { - object_unref(OBJECT(lu->scsi_dev)); - lu->scsi_dev = NULL; - } + lu->scsi_dev = NULL; } static void ufs_lu_class_init(ObjectClass *oc, void *data) diff -Nru qemu-10.0.11+ds/hw/usb/dev-wacom.c qemu-10.0.12+ds/hw/usb/dev-wacom.c --- qemu-10.0.11+ds/hw/usb/dev-wacom.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/dev-wacom.c 2026-07-25 01:10:13.000000000 +0300 @@ -252,6 +252,10 @@ if (s->buttons_state & MOUSE_EVENT_MBUTTON) b |= 0x04; + if (len < 3) { + return 0; + } + buf[0] = b; buf[1] = dx; buf[2] = dy; diff -Nru qemu-10.0.11+ds/hw/usb/hcd-ohci.c qemu-10.0.12+ds/hw/usb/hcd-ohci.c --- qemu-10.0.11+ds/hw/usb/hcd-ohci.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/hcd-ohci.c 2026-07-25 01:10:13.000000000 +0300 @@ -28,6 +28,7 @@ #include "qemu/osdep.h" #include "hw/irq.h" #include "qapi/error.h" +#include "qemu/log.h" #include "qemu/module.h" #include "qemu/timer.h" #include "hw/usb.h" @@ -1130,6 +1131,8 @@ return 0; } for (cur = head; cur && link_cnt++ < ED_LINK_LIMIT; cur = next_ed) { + unsigned int ed_cnt = 0; + if (ohci_read_ed(ohci, cur, &ed)) { trace_usb_ohci_ed_read_error(cur); ohci_die(ohci); @@ -1173,6 +1176,13 @@ break; } } + + if (ed_cnt++ > ED_LINK_LIMIT) { + qemu_log_mask(LOG_GUEST_ERROR, + "ohci: Too many endpoint descriptors in loop\n"); + ohci_die(ohci); + return 0; + } } if (ohci_put_ed(ohci, cur, &ed)) { diff -Nru qemu-10.0.11+ds/hw/usb/hcd-xhci-pci.c qemu-10.0.12+ds/hw/usb/hcd-xhci-pci.c --- qemu-10.0.11+ds/hw/usb/hcd-xhci-pci.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/hcd-xhci-pci.c 2026-07-25 01:10:13.000000000 +0300 @@ -190,6 +190,18 @@ && dev->msix_entry_used) { msix_uninit(dev, &s->xhci.mem, &s->xhci.mem); } + /* + * The embedded xhci-core child holds a strong "host" link back to this + * PCI device (set in usb_xhci_pci_realize()), forming a refcount cycle: + * the PCI device owns the child, and the child's strong link pins the PCI + * device. On unplug, object_unparent() only drops the parent/bus refs, so + * the link ref keeps this device at refcount 1 forever and + * device_finalize() never runs. Unrealize the child first (so the + * realized-check in set_link passes), then clear the link to break the + * cycle. + */ + qdev_unrealize(DEVICE(&s->xhci)); + object_property_set_link(OBJECT(&s->xhci), "host", NULL, &error_abort); } static const VMStateDescription vmstate_xhci_pci = { diff -Nru qemu-10.0.11+ds/hw/usb/hcd-xhci-sysbus.c qemu-10.0.12+ds/hw/usb/hcd-xhci-sysbus.c --- qemu-10.0.11+ds/hw/usb/hcd-xhci-sysbus.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/hcd-xhci-sysbus.c 2026-07-25 01:10:13.000000000 +0300 @@ -20,6 +20,7 @@ { XHCISysbusState *s = container_of(xhci, XHCISysbusState, xhci); + assert(n < xhci->numintrs); qemu_set_irq(s->irq[n], level); return false; diff -Nru qemu-10.0.11+ds/hw/usb/hcd-xhci.c qemu-10.0.12+ds/hw/usb/hcd-xhci.c --- qemu-10.0.11+ds/hw/usb/hcd-xhci.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/hcd-xhci.c 2026-07-25 01:10:13.000000000 +0300 @@ -965,11 +965,13 @@ * together and make an usb_device_alloc_streams call per group. */ if (epctxs[i]->nr_pstreams != req_nr_streams) { - FIXME("guest streams config not identical for all eps"); + qemu_log_mask(LOG_UNIMP, + "guest streams config not identical for all eps\n"); return CC_RESOURCE_ERROR; } if (eps[i]->max_streams != dev_max_streams) { - FIXME("device streams config not identical for all eps"); + qemu_log_mask(LOG_UNIMP, + "device streams config not identical for all eps\n"); return CC_RESOURCE_ERROR; } } @@ -1009,7 +1011,12 @@ dma_addr_t base; uint32_t ctx[2], sct; - assert(streamid != 0); + if (!streamid) { + qemu_log_mask(LOG_GUEST_ERROR, "xhci: stream ID is zero\n"); + *cc_error = CC_INVALID_STREAM_ID_ERROR; + return NULL; + } + if (epctx->lsa) { if (streamid >= epctx->nr_pstreams) { *cc_error = CC_INVALID_STREAM_ID_ERROR; @@ -1120,7 +1127,7 @@ epctx->ring.ccs = ctx[2] & 1; } - epctx->interval = 1 << ((ctx[0] >> 16) & 0xff); + epctx->interval = 1u << MIN((ctx[0] >> 16) & 0xffu, 18u); } static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, @@ -3039,6 +3046,12 @@ } } else { int v = (reg - 0x20) / 0x20; + + if (v >= xhci->numintrs) { + qemu_log_mask(LOG_GUEST_ERROR, + "xhci: read from nonexistent interrupter %i\n", v); + goto out_trace; + } XHCIInterrupter *intr = &xhci->intr[v]; switch (reg & 0x1f) { case 0x00: /* IMAN */ @@ -3065,6 +3078,7 @@ } } +out_trace: trace_usb_xhci_runtime_read(reg, ret); return ret; } @@ -3082,7 +3096,13 @@ trace_usb_xhci_unimplemented("runtime write", reg); return; } + v = (reg - 0x20) / 0x20; + if (v >= xhci->numintrs) { + qemu_log_mask(LOG_GUEST_ERROR, + "xhci: write to nonexistent interrupter %i\n", v); + return; + } intr = &xhci->intr[v]; switch (reg & 0x1f) { diff -Nru qemu-10.0.11+ds/hw/usb/redirect.c qemu-10.0.12+ds/hw/usb/redirect.c --- qemu-10.0.11+ds/hw/usb/redirect.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/usb/redirect.c 2026-07-25 01:10:13.000000000 +0300 @@ -690,6 +690,7 @@ struct buf_packet *bulkp; int count; + assert(maxp != 0); while ((bulkp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq)) && p->actual_length < p->iov.size && p->status == USB_RET_SUCCESS) { if (bulkp->len < 2) { @@ -739,6 +740,7 @@ .stream_id = 0, .no_transfers = 5, }; + assert(dev->endpoint[EP2I(ep)].max_packet_size != 0); /* Round bytes_per_transfer up to a multiple of max_packet_size */ bpt = 512 + dev->endpoint[EP2I(ep)].max_packet_size - 1; bpt /= dev->endpoint[EP2I(ep)].max_packet_size; @@ -793,6 +795,7 @@ } if (dev->endpoint[EP2I(ep)].bulk_receiving_enabled) { + assert(maxp != 0); if (size != 0 && (size % maxp) == 0) { usbredir_handle_buffered_bulk_in_data(dev, p, ep); return; @@ -1801,6 +1804,17 @@ if (usbredirparser_peer_has_cap(dev->parser, usb_redir_cap_ep_info_max_packet_size)) { dev->endpoint[i].max_packet_size = ep_info->max_packet_size[i]; + if (ep_info->max_packet_size[i] == 0 && + dev->endpoint[i].bulk_receiving_enabled) { + USBPacket *p = dev->endpoint[i].pending_async_packet; + usbredir_stop_bulk_receiving(dev, I2EP(i)); + dev->endpoint[i].bulk_receiving_enabled = 0; + if (p != NULL) { + dev->endpoint[i].pending_async_packet = NULL; + p->status = USB_RET_IOERROR; + usb_packet_complete(&dev->dev, p); + } + } } #if USBREDIR_VERSION >= 0x000700 if (usbredirparser_peer_has_cap(dev->parser, @@ -2143,7 +2157,7 @@ USBRedirDevice *dev = priv; uint8_t status, ep = buffered_bulk_packet->endpoint; void *free_on_destroy; - int i, len; + int i, len, queued = 0; DPRINTF("buffered-bulk-in status %d ep %02X len %d id %"PRIu64"\n", buffered_bulk_packet->status, ep, data_len, id); @@ -2161,6 +2175,7 @@ } /* Data must be in maxp chunks for buffered_bulk_add_*_data_to_packet */ + assert(dev->endpoint[EP2I(ep)].max_packet_size != 0); len = dev->endpoint[EP2I(ep)].max_packet_size; status = usb_redir_success; free_on_destroy = NULL; @@ -2174,8 +2189,24 @@ /* bufp_alloc also adds the packet to the ep queue */ r = bufp_alloc(dev, data + i, len, status, ep, free_on_destroy); if (r) { + /* + * Earlier fragments from this packet are in the queue + * with interior pointers into data. If the dropped + * fragment was the final one, bufp_alloc already freed + * data so those pointers are dangling. Remove them. + */ + while (queued > 0) { + struct buf_packet *bufp; + bufp = QTAILQ_LAST(&dev->endpoint[EP2I(ep)].bufpq); + bufp_free(dev, bufp, ep); + queued--; + } + if (!free_on_destroy) { + free(data); + } break; } + queued++; } if (dev->endpoint[EP2I(ep)].pending_async_packet) { @@ -2228,6 +2259,15 @@ usbredir_setup_usb_eps(dev); usbredir_check_bulk_receiving(dev); + for (int i = 0; i < MAX_ENDPOINTS; i++) { + if (dev->endpoint[i].bulk_receiving_started && + dev->endpoint[i].max_packet_size == 0) { + error_report("usbredir: endpoint %d has bulk receiving started " + "with zero max_packet_size", i); + return -EINVAL; + } + } + return 0; } diff -Nru qemu-10.0.11+ds/hw/virtio/vdpa-dev.c qemu-10.0.12+ds/hw/virtio/vdpa-dev.c --- qemu-10.0.11+ds/hw/virtio/vdpa-dev.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/hw/virtio/vdpa-dev.c 2026-07-25 01:10:13.000000000 +0300 @@ -173,6 +173,7 @@ { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); + struct vhost_virtqueue *vqs = s->dev.vqs; int i; virtio_set_status(vdev, 0); @@ -184,8 +185,8 @@ virtio_cleanup(vdev); g_free(s->config); - g_free(s->dev.vqs); vhost_dev_cleanup(&s->dev); + g_free(vqs); g_free(s->vdpa.shared); qemu_close(s->vhostfd); s->vhostfd = -1; diff -Nru qemu-10.0.11+ds/include/exec/cpu-all.h qemu-10.0.12+ds/include/exec/cpu-all.h --- qemu-10.0.11+ds/include/exec/cpu-all.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/include/exec/cpu-all.h 2026-07-25 01:10:13.000000000 +0300 @@ -105,12 +105,14 @@ static inline int cpu_mmu_index(CPUState *cs, bool ifetch); /* - * Allow some level of source compatibility with softmmu. We do not - * support any of the more exotic features, so only invalid pages may - * be signaled by probe_access_flags(). + * Allow some level of source compatibility with softmmu. + * Invalid is set when the page does not have requested permissions. + * MMIO is set when we want the target helper to use the functional + * interface for load/store so that plugins see the access. */ #define TLB_INVALID_MASK (1 << (TARGET_PAGE_BITS_MIN - 1)) -#define TLB_MMIO (1 << (TARGET_PAGE_BITS_MIN - 2)) +#define TLB_FORCE_SLOW (1 << (TARGET_PAGE_BITS_MIN - 2)) +#define TLB_MMIO 0 #define TLB_WATCHPOINT 0 static inline int cpu_mmu_index(CPUState *cs, bool ifetch) diff -Nru qemu-10.0.11+ds/include/hw/misc/stm32_rcc.h qemu-10.0.12+ds/include/hw/misc/stm32_rcc.h --- qemu-10.0.11+ds/include/hw/misc/stm32_rcc.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/include/hw/misc/stm32_rcc.h 2026-07-25 01:10:13.000000000 +0300 @@ -65,7 +65,11 @@ #define STM32_RCC_NREGS ((STM32_RCC_DCKCFGR2 >> 2) + 1) #define STM32_RCC_PERIPHERAL_SIZE 0x400 -#define STM32_RCC_NIRQS (32 * 5) /* 32 bits per reg, 5 en/rst regs */ + +/* 32 bits per reg, 3 AHB regs and 2 APB regs */ +#define STM32_RCC_N_AHB_IRQS (32 * 3) +#define STM32_RCC_N_APB_IRQS (32 * 2) +#define STM32_RCC_NIRQS (STM32_RCC_N_AHB_IRQS + STM32_RCC_N_APB_IRQS) #define STM32_RCC_GPIO_IRQ_OFFSET 0 diff -Nru qemu-10.0.11+ds/include/hw/s390x/css.h qemu-10.0.12+ds/include/hw/s390x/css.h --- qemu-10.0.11+ds/include/hw/s390x/css.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/include/hw/s390x/css.h 2026-07-25 01:10:13.000000000 +0300 @@ -132,6 +132,7 @@ bool ccw_fmt_1; bool thinint_active; uint8_t ccw_no_data_cnt; + uint8_t ccw_tic_cnt; uint16_t migrated_schid; /* used for mismatch detection */ CcwDataStream cds; /* transport-provided data: */ diff -Nru qemu-10.0.11+ds/include/hw/virtio/virtio-gpu.h qemu-10.0.12+ds/include/hw/virtio/virtio-gpu.h --- qemu-10.0.11+ds/include/hw/virtio/virtio-gpu.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/include/hw/virtio/virtio-gpu.h 2026-07-25 01:10:13.000000000 +0300 @@ -15,6 +15,7 @@ #define HW_VIRTIO_GPU_H #include "qemu/queue.h" +#include "qemu/units.h" #include "ui/qemu-pixman.h" #include "ui/console.h" #include "hw/virtio/virtio.h" @@ -280,6 +281,14 @@ struct rutabaga *rutabaga; }; +/* + * With 4 KiB pages and QEMU's VIRTQUEUE_MAX_SIZE (1024) mapped-iov + * limit, the largest inline command is ~4 MiB. Cap submit_3d + * allocations to this value to prevent a malicious guest from + * triggering an OOM abort via an inflated cs.size field. + */ +#define VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE (4 * MiB) + #define VIRTIO_GPU_FILL_CMD(out) do { \ size_t virtiogpufillcmd_s_ = \ iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, 0, \ diff -Nru qemu-10.0.11+ds/include/net/net.h qemu-10.0.12+ds/include/net/net.h --- qemu-10.0.11+ds/include/net/net.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/include/net/net.h 2026-07-25 01:10:13.000000000 +0300 @@ -329,9 +329,32 @@ .offset = vmstate_offset_macaddr(_state, _field), \ } +/** + * net_peer_needs_padding: Should we pad as we send out packets? + * @nc: NetClientState + * + * Return true if the peer of this NetClientState (i.e. the + * destination that qemu_send_packet() etc send to) requires us to pad + * out packets that are shorter than the minimum ethernet frame + * length. + */ static inline bool net_peer_needs_padding(NetClientState *nc) { return nc->peer && !nc->peer->do_not_pad; } +/** + * net_client_needs_padding: Should we pad as we queue packets to ourselves? + * @nc: NetClientState + * + * Return true if this NetClientState requires us to pad out packets + * that are shorter than the minimum ethernet frame length. This is + * the check to make in qemu_receive_packet() when we are queuing a + * packet back into ourselves (i.e. loopback). + */ +static inline bool net_client_needs_padding(NetClientState *nc) +{ + return !nc->do_not_pad; +} + #endif diff -Nru qemu-10.0.11+ds/linux-user/elfload.c qemu-10.0.12+ds/linux-user/elfload.c --- qemu-10.0.11+ds/linux-user/elfload.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/linux-user/elfload.c 2026-07-25 01:10:13.000000000 +0300 @@ -1720,6 +1720,20 @@ #define ELF_CLASS ELFCLASS64 #define ELF_ARCH EM_ALPHA +#define ELF_HWCAP get_elf_hwcap() + +static uint32_t get_elf_hwcap(void) +{ + CPUState *cs = thread_cpu; + /* + * The Linux kernel computes ELF_HWCAP as ~amask(-1), which clears a bit + * for each supported ISA extension. env->amask stores exactly those bits + * set for the extensions supported by the emulated CPU model, matching + * the kernel's convention: bit set in AT_HWCAP ↔ extension present. + */ + return cpu_env(cs)->amask; +} + static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) { @@ -1728,6 +1742,29 @@ regs->usp = infop->start_stack; } +/* + * Matches the kernel's elf_gregset_t (ELF_NGREG = 33): + * r0-r30 at indices 0-30, pc at 31, ps at 32. + * r31 (hardwired zero) is not stored; pc occupies index 31. + */ +typedef struct target_elf_gregset_t { + abi_ulong regs[31]; /* integer registers r0-r30 [0..30] */ + abi_ulong pc; /* program counter [31] */ + abi_ulong unique; /* thread's UNIQUE field [32] */ +} target_elf_gregset_t; + +static void elf_core_copy_regs(target_elf_gregset_t *r, const CPUAlphaState *env) +{ + int i; + + for (i = 0; i < 31; i++) { + r->regs[i] = tswap64(env->ir[i]); + } + r->pc = tswap64(env->pc); + r->unique = tswap64(env->unique); +} + +#define USE_ELF_CORE_DUMP #define ELF_EXEC_PAGESIZE 8192 #endif /* TARGET_ALPHA */ diff -Nru qemu-10.0.11+ds/linux-user/syscall.c qemu-10.0.12+ds/linux-user/syscall.c --- qemu-10.0.11+ds/linux-user/syscall.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/linux-user/syscall.c 2026-07-25 01:10:13.000000000 +0300 @@ -5092,6 +5092,9 @@ } #endif /* CONFIG_USBFS */ +#define DM_MAX_TARGETS 1048576 +#define DM_MAX_TARGET_PARAMS 1024 + static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd, int cmd, abi_long arg) { @@ -5104,6 +5107,7 @@ abi_long ret; void *big_buf = NULL; char *host_data; + const size_t minimum_data_size = offsetof(struct dm_ioctl, data); arg_type++; target_size = thunk_type_size(arg_type, 0); @@ -5115,9 +5119,26 @@ thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); unlock_user(argptr, arg, 0); - /* buf_temp is too small, so fetch things into a bigger buffer */ - big_buf = g_malloc0(((struct dm_ioctl*)buf_temp)->data_size * 2); - memcpy(big_buf, buf_temp, target_size); + /* At this point this includes the size of the fixed dm_ioctl parts */ + guest_data_size = ((struct dm_ioctl *)buf_temp)->data_size; + + if (guest_data_size < minimum_data_size || + guest_data_size > DM_MAX_TARGETS * DM_MAX_TARGET_PARAMS) { + ret = -TARGET_EINVAL; + goto out; + } + + /* + * buf_temp is too small, so fetch things into a bigger buffer. Here + * we copy all of the fixed parts of struct dm_ioctl but not the + * data at the end (which in the struct is "char data[7]" but in + * reality is command-specific and might be nothing or might be + * much larger, as defined by data_size). We know struct dm_ioctl's + * size is not target specific so we don't need to distinguish between + * its minimum size for the host vs the target. + */ + big_buf = g_malloc0(guest_data_size * 2); + memcpy(big_buf, buf_temp, minimum_data_size); buf_temp = big_buf; host_dm = big_buf; @@ -5126,7 +5147,8 @@ ret = -TARGET_EINVAL; goto out; } - guest_data_size = host_dm->data_size - host_dm->data_start; + /* Adjust down to only the size of the payload */ + guest_data_size -= host_dm->data_start; host_data = (char*)host_dm + host_dm->data_start; argptr = lock_user(VERIFY_READ, guest_data, guest_data_size, 1); diff -Nru qemu-10.0.11+ds/net/net.c qemu-10.0.12+ds/net/net.c --- qemu-10.0.11+ds/net/net.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/net/net.c 2026-07-25 01:10:13.000000000 +0300 @@ -764,7 +764,7 @@ return 0; } - if (net_peer_needs_padding(nc)) { + if (net_client_needs_padding(nc)) { if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) { buf = min_pkt; size = min_pktsz; diff -Nru qemu-10.0.11+ds/qemu-options.hx qemu-10.0.12+ds/qemu-options.hx --- qemu-10.0.11+ds/qemu-options.hx 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/qemu-options.hx 2026-07-25 01:10:13.000000000 +0300 @@ -1801,19 +1801,19 @@ DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev, "-fsdev local,id=id,path=path,security_model=mapped-xattr|mapped-file|passthrough|none\n" - " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode]\n" + " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max]\n" " [[,throttling.bps-total=b]|[[,throttling.bps-read=r][,throttling.bps-write=w]]]\n" " [[,throttling.iops-total=i]|[[,throttling.iops-read=r][,throttling.iops-write=w]]]\n" " [[,throttling.bps-total-max=bm]|[[,throttling.bps-read-max=rm][,throttling.bps-write-max=wm]]]\n" " [[,throttling.iops-total-max=im]|[[,throttling.iops-read-max=irm][,throttling.iops-write-max=iwm]]]\n" " [[,throttling.iops-size=is]]\n" - "-fsdev synth,id=id\n", + "-fsdev synth,id=id[,max_xattr=max]\n", QEMU_ARCH_ALL) SRST -``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode] [,throttling.option=value[,throttling.option=value[,...]]]`` +``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max] [,throttling.option=value[,throttling.option=value[,...]]]`` \ -``-fsdev synth,id=id[,readonly=on]`` +``-fsdev synth,id=id[,readonly=on][,max_xattr=max]`` Define a new file system device. Valid options are: ``local`` @@ -1887,6 +1887,12 @@ Let every is bytes of a request count as a new request for iops throttling purposes. + ``max_xattr=max`` + Specifies the maximum number of concurrent xattr FIDs allowed for + this export. The default is 1024. Set to 0 for allowing an infinite + number of xattr FIDs. This limit prevents host memory exhaustion + attacks by capping the number of simultaneous xattr FIDs. + -fsdev option is used along with -device driver "virtio-9p-...". ``-device virtio-9p-type,fsdev=id,mount_tag=mount_tag`` @@ -1906,14 +1912,14 @@ DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs, "-virtfs local,path=path,mount_tag=tag,security_model=mapped-xattr|mapped-file|passthrough|none\n" - " [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn]\n" - "-virtfs synth,mount_tag=tag[,id=id][,readonly=on]\n", + " [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn][,max_xattr=max]\n" + "-virtfs synth,mount_tag=tag[,id=id][,readonly=on][,max_xattr=max]\n", QEMU_ARCH_ALL) SRST -``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs]`` +``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs][,max_xattr=max]`` \ -``-virtfs synth,mount_tag=mount_tag`` +``-virtfs synth,mount_tag=mount_tag[,max_xattr=max]`` Define a new virtual filesystem device and expose it to the guest using a virtio-9p-device (a.k.a. 9pfs), which essentially means that a certain directory on host is made directly accessible by guest as a pass-through @@ -1979,6 +1985,12 @@ Specifies the tag name to be used by the guest to mount this export point. + ``max_xattr=max`` + Specifies the maximum number of concurrent xattr FIDs allowed for + this export. The default is 1024. Set to 0 for allowing an infinite + number of xattr FIDs. This limit prevents host memory exhaustion + attacks by capping the number of simultaneous xattr FIDs. + ``multidevs=remap|forbid|warn`` Specifies how to deal with multiple devices being shared with the same 9p export in order to avoid file ID collisions on guest. diff -Nru qemu-10.0.11+ds/replay/replay-debugging.c qemu-10.0.12+ds/replay/replay-debugging.c --- qemu-10.0.11+ds/replay/replay-debugging.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/replay/replay-debugging.c 2026-07-25 01:10:13.000000000 +0300 @@ -139,9 +139,8 @@ int64_t *snapshot_icount) { BlockDriverState *bs; - QEMUSnapshotInfo *sn_tab; + g_autofree QEMUSnapshotInfo *sn_tab = NULL; QEMUSnapshotInfo *nearest = NULL; - char *ret = NULL; int rv; int nb_sns, i; @@ -149,15 +148,19 @@ bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, NULL); if (!bs) { - goto fail; + return NULL; } nb_sns = bdrv_snapshot_list(bs, &sn_tab); + if (nb_sns < 0) { + return NULL; + } for (i = 0; i < nb_sns; i++) { rv = bdrv_all_has_snapshot(sn_tab[i].name, false, NULL, NULL); - if (rv < 0) - goto fail; + if (rv < 0) { + return NULL; + } if (rv == 1) { if (sn_tab[i].icount != -1ULL && sn_tab[i].icount <= icount @@ -166,14 +169,12 @@ } } } - if (nearest) { - ret = g_strdup(nearest->name); - *snapshot_icount = nearest->icount; + if (!nearest) { + return NULL; } - g_free(sn_tab); -fail: - return ret; + *snapshot_icount = nearest->icount; + return g_strdup(nearest->name); } static void replay_seek(int64_t icount, QEMUTimerCB callback, Error **errp) diff -Nru qemu-10.0.11+ds/system/vl.c qemu-10.0.12+ds/system/vl.c --- qemu-10.0.11+ds/system/vl.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/system/vl.c 2026-07-25 01:10:13.000000000 +0300 @@ -3256,7 +3256,7 @@ QemuOpts *fsdev; QemuOpts *device; const char *writeout, *sock_fd, *socket, *path, *security_model, - *multidevs; + *multidevs, *max_xattr_str; olist = qemu_find_opts("virtfs"); if (!olist) { @@ -3320,6 +3320,11 @@ if (multidevs) { qemu_opt_set(fsdev, "multidevs", multidevs, &error_abort); } + max_xattr_str = qemu_opt_get(opts, "max_xattr"); + if (max_xattr_str) { + qemu_opt_set(fsdev, "max_xattr", max_xattr_str, + &error_abort); + } device = qemu_opts_create(qemu_find_opts("device"), NULL, 0, &error_abort); qemu_opt_set(device, "driver", "virtio-9p-pci", &error_abort); diff -Nru qemu-10.0.11+ds/target/arm/tcg/tlb-insns.c qemu-10.0.12+ds/target/arm/tcg/tlb-insns.c --- qemu-10.0.11+ds/target/arm/tcg/tlb-insns.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/arm/tcg/tlb-insns.c 2026-07-25 01:10:13.000000000 +0300 @@ -839,7 +839,7 @@ gran = tlbi_range_tg_to_gran_size(page_size_granule); /* The granule encoded in value must match the granule in use. */ - if (gran != param.gran) { + if (gran != param.gran || gran == GranInvalid) { qemu_log_mask(LOG_GUEST_ERROR, "Invalid tlbi page size granule %d\n", page_size_granule); return ret; diff -Nru qemu-10.0.11+ds/target/riscv/cpu_helper.c qemu-10.0.12+ds/target/riscv/cpu_helper.c --- qemu-10.0.11+ds/target/riscv/cpu_helper.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/riscv/cpu_helper.c 2026-07-25 01:10:13.000000000 +0300 @@ -1695,10 +1695,18 @@ /* Page table updates need to be atomic with MTTCG enabled */ if (updated_pte != pte && !is_debug) { + int pmp_prot, pmp_ret; + if (!adue) { return TRANSLATE_FAIL; } + pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, + sxlen_bytes, MMU_DATA_STORE, PRV_S); + if (pmp_ret != TRANSLATE_SUCCESS) { + return TRANSLATE_PMP_FAIL; + } + /* * - if accessed or dirty bits need updating, and the PTE is * in RAM, then we do so atomically with a compare and swap. diff -Nru qemu-10.0.11+ds/target/riscv/csr.c qemu-10.0.12+ds/target/riscv/csr.c --- qemu-10.0.11+ds/target/riscv/csr.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/riscv/csr.c 2026-07-25 01:10:13.000000000 +0300 @@ -914,7 +914,16 @@ target_ulong *val) { uint64_t vill; - switch (env->xl) { + int xl = env->xl; + /* + * TCG plugins can read registers before env->xl is initialized. + * Fall back to the CPU's maximum XLEN in that early-init case. + */ + if (xl == 0) { + xl = riscv_cpu_mxl(env); + } + + switch (xl) { case MXL_RV32: vill = (uint32_t)env->vill << 31; break; @@ -1969,6 +1978,20 @@ return val; } +static uint64_t riscv_write_uxl(CPURISCVState *env, uint64_t val, + uint64_t field) +{ + RISCVMXL xl = riscv_cpu_mxl(env); + uint64_t uxl = get_field(val, field); + + if (uxl == MXL_RV128) { + uxl = xl == MXL_RV128 ? MXL_RV64 : xl; + val = set_field(val, field, uxl); + } + + return val; +} + static RISCVException write_mstatus(CPURISCVState *env, int csrno, target_ulong val) { @@ -2015,17 +2038,8 @@ if (xl != MXL_RV32 || env->debugger) { if ((val & MSTATUS64_UXL) != 0) { - uint64_t uxl = val & MSTATUS64_UXL >> 32; mask |= MSTATUS64_UXL; - - /* - * uxl = 3 is reserved so write the current xl instead. - * In case xl = MXL_RV128 (3) write MXL_RV64. - */ - if (uxl == 3) { - uxl = xl == MXL_RV128 ? MXL_RV64 : xl; - val = deposit64(val, 32, 2, uxl); - } + val = riscv_write_uxl(env, val, MSTATUS64_UXL); } } @@ -5100,6 +5114,8 @@ uint64_t mask = (target_ulong)-1; if ((val & VSSTATUS64_UXL) == 0) { mask &= ~VSSTATUS64_UXL; + } else { + val = riscv_write_uxl(env, val, VSSTATUS64_UXL); } if ((env->henvcfg & HENVCFG_DTE)) { if ((val & SSTATUS_SDT) != 0) { diff -Nru qemu-10.0.11+ds/target/s390x/ioinst.c qemu-10.0.12+ds/target/s390x/ioinst.c --- qemu-10.0.11+ds/target/s390x/ioinst.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/s390x/ioinst.c 2026-07-25 01:10:13.000000000 +0300 @@ -601,13 +601,27 @@ #define CHSC_SEI_NT0 (1ULL << 63) #define CHSC_SEI_NT2 (1ULL << 61) +#define CHSC_SEI_0_FMT 0x0f000000 static void ioinst_handle_chsc_sei(ChscReq *req, ChscResp *res) { uint64_t selection_mask = ldq_be_p(&req->param1); + uint32_t param0 = be32_to_cpu(req->param0); uint8_t *res_flags = (uint8_t *)res->data; + uint16_t len = be16_to_cpu(req->len); + uint16_t resp_code; int have_event = 0; int have_more = 0; + if (len != 0x0010) { + resp_code = 0x0003; + goto out_err; + } + + if (param0 & CHSC_SEI_0_FMT) { + resp_code = 0x0007; + goto out_err; + } + /* regarding architecture nt0 can not be masked */ have_event = !chsc_sei_nt0_get_event(res); have_more = chsc_sei_nt0_have_event(); @@ -634,6 +648,12 @@ res->code = cpu_to_be16(0x0005); res->len = cpu_to_be16(CHSC_MIN_RESP_LEN); } + return; + + out_err: + res->code = cpu_to_be16(resp_code); + res->len = cpu_to_be16(CHSC_MIN_RESP_LEN); + res->param = 0; } static void ioinst_handle_chsc_unimplemented(ChscResp *res) diff -Nru qemu-10.0.11+ds/target/s390x/kvm/kvm.c qemu-10.0.12+ds/target/s390x/kvm/kvm.c --- qemu-10.0.11+ds/target/s390x/kvm/kvm.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/s390x/kvm/kvm.c 2026-07-25 01:10:13.000000000 +0300 @@ -1792,6 +1792,15 @@ } else if (s390_cpu_virt_mem_read(cpu, addr, ar, &sysib, sizeof(sysib))) { return; } + + /* + * The memory was filled by the kernel but mapped into the guest. + * If something is fishy, do not touch the buffer. + */ + if (sysib.count == 0 || sysib.count > ARRAY_SIZE(sysib.ext_names)) { + return; + } + /* Shift the stack of Extended Names to prepare for our own data */ memmove(&sysib.ext_names[1], &sysib.ext_names[0], sizeof(sysib.ext_names[0]) * (sysib.count - 1)); diff -Nru qemu-10.0.11+ds/target/sh4/op_helper.c qemu-10.0.12+ds/target/sh4/op_helper.c --- qemu-10.0.11+ds/target/sh4/op_helper.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/sh4/op_helper.c 2026-07-25 01:10:13.000000000 +0300 @@ -485,7 +485,7 @@ float32 p; bank_matrix = (env->sr & FPSCR_FR) ? 0 : 16; - bank_vector = (env->sr & FPSCR_FR) ? 16 : 0; + bank_vector = (env->sr & FPSCR_FR) ? 16 + n : n; set_float_exception_flags(0, &env->fp_status); for (i = 0 ; i < 4 ; i++) { r[i] = float32_zero; diff -Nru qemu-10.0.11+ds/target/sh4/translate.c qemu-10.0.12+ds/target/sh4/translate.c --- qemu-10.0.11+ds/target/sh4/translate.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/target/sh4/translate.c 2026-07-25 01:10:13.000000000 +0300 @@ -377,11 +377,6 @@ goto do_illegal; \ } -#define CHECK_FPSCR_PR_1 \ - if (!(ctx->tbflags & FPSCR_PR)) { \ - goto do_illegal; \ - } - #define CHECK_SH4A \ if (!(ctx->features & SH_FEATURE_SH4A)) { \ goto do_illegal; \ @@ -1746,22 +1741,22 @@ return; case 0xf0ed: /* fipr FVm,FVn */ CHECK_FPU_ENABLED - CHECK_FPSCR_PR_1 + CHECK_FPSCR_PR_0 { - TCGv m = tcg_constant_i32((ctx->opcode >> 8) & 3); - TCGv n = tcg_constant_i32((ctx->opcode >> 10) & 3); + TCGv m = tcg_constant_i32(((ctx->opcode >> 8) & 3) << 2); + TCGv n = tcg_constant_i32(((ctx->opcode >> 10) & 3) << 2); gen_helper_fipr(tcg_env, m, n); return; } break; case 0xf0fd: /* ftrv XMTRX,FVn */ CHECK_FPU_ENABLED - CHECK_FPSCR_PR_1 + CHECK_FPSCR_PR_0 { if ((ctx->opcode & 0x0300) != 0x0100) { goto do_illegal; } - TCGv n = tcg_constant_i32((ctx->opcode >> 10) & 3); + TCGv n = tcg_constant_i32(((ctx->opcode >> 10) & 3) << 2); gen_helper_ftrv(tcg_env, n); return; } diff -Nru qemu-10.0.11+ds/tcg/loongarch64/tcg-target.c.inc qemu-10.0.12+ds/tcg/loongarch64/tcg-target.c.inc --- qemu-10.0.11+ds/tcg/loongarch64/tcg-target.c.inc 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tcg/loongarch64/tcg-target.c.inc 2026-07-25 01:10:13.000000000 +0300 @@ -211,12 +211,24 @@ if ((ct & TCG_CT_CONST_WSZ) && val == (type == TCG_TYPE_I32 ? 32 : 64)) { return true; } - int64_t vec_val = sextract64(val, 0, 8 << vece); - if ((ct & TCG_CT_CONST_VCMP) && -0x10 <= vec_val && vec_val <= 0x1f) { - return true; - } - if ((ct & TCG_CT_CONST_VADD) && -0x1f <= vec_val && vec_val <= 0x1f) { - return true; + if (ct & (TCG_CT_CONST_VCMP | TCG_CT_CONST_VADD)) { + int64_t vec_val = sextract64(val, 0, 8 << vece); + if (ct & TCG_CT_CONST_VCMP) { + switch (cond) { + case TCG_COND_EQ: + case TCG_COND_LE: + case TCG_COND_LT: + return -0x10 <= vec_val && vec_val <= 0x0f; + case TCG_COND_LEU: + case TCG_COND_LTU: + return 0x00 <= vec_val && vec_val <= 0x1f; + default: + return false; + } + } + if ((ct & TCG_CT_CONST_VADD) && -0x1f <= vec_val && vec_val <= 0x1f) { + return true; + } } return false; } @@ -2027,40 +2039,51 @@ * Try vseqi/vslei/vslti */ int64_t value = sextract64(a2, 0, 8 << vece); - if ((cond == TCG_COND_EQ || - cond == TCG_COND_LE || - cond == TCG_COND_LT) && - (-0x10 <= value && value <= 0x0f)) { + switch (cond) { + case TCG_COND_EQ: + case TCG_COND_LE: + case TCG_COND_LT: insn = cmp_vec_imm_insn[cond][lasx][vece]; tcg_out32(s, encode_vdvjsk5_insn(insn, a0, a1, value)); break; - } else if ((cond == TCG_COND_LEU || - cond == TCG_COND_LTU) && - (0x00 <= value && value <= 0x1f)) { + case TCG_COND_LEU: + case TCG_COND_LTU: insn = cmp_vec_imm_insn[cond][lasx][vece]; tcg_out32(s, encode_vdvjuk5_insn(insn, a0, a1, value)); break; + default: + g_assert_not_reached(); + } + } else { + switch (cond) { + case TCG_COND_EQ: + case TCG_COND_LE: + case TCG_COND_LEU: + case TCG_COND_LT: + case TCG_COND_LTU: + insn = cmp_vec_insn[cond][lasx][vece]; + tcg_out32(s, encode_vdvjvk_insn(insn, a0, a1, a2)); + break; + case TCG_COND_GE: + case TCG_COND_GEU: + case TCG_COND_GT: + case TCG_COND_GTU: + insn = cmp_vec_insn[tcg_swap_cond(cond)][lasx][vece]; + tcg_out32(s, encode_vdvjvk_insn(insn, a0, a2, a1)); + break; + case TCG_COND_NE: + /* ne -> not(eq) */ + insn = cmp_vec_insn[TCG_COND_EQ][lasx][vece]; + tcg_out32(s, encode_vdvjvk_insn(insn, a0, a1, a2)); + insn = lasx ? OPC_XVNOR_V : OPC_VNOR_V; + tcg_out32(s, encode_vdvjvk_insn(insn, a0, a0, a0)); + break; + default: + g_assert_not_reached(); } - - /* - * Fallback to: - * dupi_vec temp, a2 - * cmp_vec a0, a1, temp, cond - */ - tcg_out_dupi_vec(s, type, vece, TCG_VEC_TMP0, a2); - a2 = TCG_VEC_TMP0; - } - - insn = cmp_vec_insn[cond][lasx][vece]; - if (insn == 0) { - TCGArg t; - t = a1, a1 = a2, a2 = t; - cond = tcg_swap_cond(cond); - insn = cmp_vec_insn[cond][lasx][vece]; - tcg_debug_assert(insn != 0); } } - goto vdvjvk; + break; case INDEX_op_add_vec: tcg_out_addsub_vec(s, lasx, vece, a0, a1, a2, const_args[2], true); break; Binary files /tmp/mSmP83bXnq/qemu-10.0.11+ds/tests/data/acpi/riscv64/virt/SPCR and /tmp/Y2FXfgHGQX/qemu-10.0.12+ds/tests/data/acpi/riscv64/virt/SPCR differ diff -Nru qemu-10.0.11+ds/tests/qtest/ahci-test.c qemu-10.0.12+ds/tests/qtest/ahci-test.c --- qemu-10.0.11+ds/tests/qtest/ahci-test.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/ahci-test.c 2026-07-25 01:10:13.000000000 +0300 @@ -1625,6 +1625,69 @@ ahci_test_cdrom_read10(3, false); } +/* + * Regression test: a buffered ATAPI read completing after a command + * engine restart must not dereference the cleared cur_cmd. Cover both + * PIO and DMA; the DMA variant is the reliable guard. + */ +static void test_atapi_engine_restart_in_flight(bool dma) +{ + AHCIQState *ahci; + AHCICommand *cmd; + unsigned char *tx; + char *iso; + int fd; + uint8_t port; + uint64_t buffer; + uint64_t iso_size = (uint64_t)ATAPI_SECTOR_SIZE * 2; + + fd = prepare_iso(iso_size, &tx, &iso); + + ahci = ahci_boot_and_enable("-drive if=none,id=drive0," + "file=blkdebug::%s,format=raw,readonly=on " + "-M q35 " + "-device ide-cd,drive=drive0 ", iso); + port = ahci_port_select(ahci); + + buffer = ahci_alloc(ahci, ATAPI_SECTOR_SIZE); + qtest_memset(ahci->parent->qts, buffer, 0x00, ATAPI_SECTOR_SIZE); + + /* Suspend the next backend read so the ATAPI read stays in flight. */ + g_free(qtest_hmp(ahci->parent->qts, + "qemu-io drive0 \"break read_aio rd\"")); + + cmd = ahci_atapi_command_create(CMD_ATAPI_READ_10, ATAPI_SECTOR_SIZE, + dma); + ahci_command_adjust(cmd, 0, buffer, ATAPI_SECTOR_SIZE, 0); + ahci_command_commit(ahci, cmd, port); + ahci_command_issue_async(ahci, cmd); + + /* Stop and restart the command engine to re-map the command list. */ + ahci_px_clr(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST); + ahci_px_set(ahci, port, AHCI_PX_CMD, AHCI_PX_CMD_ST); + + g_free(qtest_hmp(ahci->parent->qts, "qemu-io drive0 \"resume rd\"")); + + /* Round-trip through the device to confirm qemu is still alive. */ + ahci_px_rreg(ahci, port, AHCI_PX_TFD); + + ahci_command_free(cmd); + ahci_free(ahci, buffer); + g_free(tx); + ahci_shutdown(ahci); + remove_iso(fd, iso); +} + +static void test_atapi_engine_restart_pio(void) +{ + test_atapi_engine_restart_in_flight(false); +} + +static void test_atapi_engine_restart_dma(void) +{ + test_atapi_engine_restart_in_flight(true); +} + /* Regression test: Test that a READ_CD command with a BCL of 0 but a size of 0 * completes as a NOP instead of erroring out. */ static void test_atapi_bcl(void) @@ -2043,6 +2106,10 @@ qtest_add_func("/ahci/cdrom/pio/bcl", test_atapi_bcl); qtest_add_func("/ahci/cdrom/eject", test_atapi_tray); + qtest_add_func("/ahci/cdrom/engine_restart/pio", + test_atapi_engine_restart_pio); + qtest_add_func("/ahci/cdrom/engine_restart/dma", + test_atapi_engine_restart_dma); ret = g_test_run(); diff -Nru qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p-client.c qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p-client.c --- qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p-client.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p-client.c 2026-07-25 01:10:13.000000000 +0300 @@ -240,6 +240,9 @@ id == P9_RUNLINKAT ? "RUNLINKAT" : id == P9_RFLUSH ? "RFLUSH" : id == P9_RREADDIR ? "RREADDIR" : + id == P9_RREAD ? "RREAD" : + id == P9_RCLUNK ? "RCLUNK" : + id == P9_RXATTRCREATE ? "RXATTRCREATE" : "<unknown>"; } @@ -1053,3 +1056,124 @@ v9fs_req_recv(req, P9_RUNLINKAT); v9fs_req_free(req); } + +/* size[4] Tread tag[2] fid[4] offset[8] count[4] */ +TReadRes v9fs_tread(TReadOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + + uint32_t body_size = 4 + 8 + 4; + + req = v9fs_req_init(opt.client, body_size, P9_TREAD, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_uint64_write(req, opt.offset); + v9fs_uint32_write(req, opt.count); + v9fs_req_send(req); + + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rread(req, opt.rread.count, opt.rread.data); + } + req = NULL; /* request was freed */ + } + + return (TReadRes) { + .req = req, + .count = opt.rread.count ? *opt.rread.count : 0 + }; +} + +/* size[4] Rread tag[2] count[4] data[count] */ +void v9fs_rread(P9Req *req, uint32_t *count, void *data) +{ + v9fs_req_recv(req, P9_RREAD); + v9fs_uint32_read(req, count); + if (data && *count > 0) { + v9fs_memread(req, data, *count); + } + v9fs_req_free(req); +} + +/* size[4] Tclunk tag[2] fid[4] */ +TClunkRes v9fs_tclunk(TClunkOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + + req = v9fs_req_init(opt.client, 4, P9_TCLUNK, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_req_send(req); + + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rclunk(req); + } + req = NULL; /* request was freed */ + } + + return (TClunkRes) { .req = req }; +} + +/* size[4] Rclunk tag[2] */ +void v9fs_rclunk(P9Req *req) +{ + v9fs_req_recv(req, P9_RCLUNK); + v9fs_req_free(req); +} + +/* size[4] Txattrcreate tag[2] fid[4] name[s] attr_size[8] flags[4] */ +TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt) +{ + P9Req *req; + uint32_t err; + + g_assert(opt.client); + g_assert(opt.name); + + uint32_t body_size = 4 + 8 + 4; + uint16_t string_size = v9fs_string_size(opt.name); + + g_assert_cmpint(body_size, <=, UINT32_MAX - string_size); + body_size += string_size; + + req = v9fs_req_init(opt.client, body_size, P9_TXATTRCREATE, opt.tag); + v9fs_uint32_write(req, opt.fid); + v9fs_string_write(req, opt.name); + v9fs_uint64_write(req, opt.size); + v9fs_uint32_write(req, opt.flags); + v9fs_req_send(req); + + err = 0; + if (!opt.requestOnly) { + v9fs_req_wait_for_reply(req, NULL); + if (opt.expectErr) { + v9fs_rlerror(req, &err); + g_assert_cmpint(err, ==, opt.expectErr); + } else { + v9fs_rxattrcreate(req); + } + req = NULL; /* request was freed */ + } + + return (TXattrCreateRes) { .req = req, .err = err }; +} + +/* size[4] Rxattrcreate tag[2] */ +void v9fs_rxattrcreate(P9Req *req) +{ + v9fs_req_recv(req, P9_RXATTRCREATE); + v9fs_req_free(req); +} diff -Nru qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p-client.h qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p-client.h --- qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p-client.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p-client.h 2026-07-25 01:10:13.000000000 +0300 @@ -21,7 +21,8 @@ #include "qgraph.h" #include "tests/qtest/libqtest-single.h" -#define P9_MAX_SIZE 4096 /* Max size of a T-message or R-message */ +/* Max size of a T-message or R-message */ +#define P9_MAX_SIZE (32 * 1024) typedef struct { QTestState *qts; @@ -441,6 +442,85 @@ P9Req *req; } TunlinkatRes; +/* options for 'Tread' 9p request */ +typedef struct TReadOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID of file to read from (required) */ + uint32_t fid; + /* start position of read from beginning of file (optional) */ + uint64_t offset; + /* how many bytes to read (required) */ + uint32_t count; + /* data being received from 9p server as 'Rread' response (optional) */ + struct { + uint32_t *count; + void *data; + } rread; + /* only send Tread request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TReadOpt; + +/* result of 'Tread' 9p request */ +typedef struct TReadRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; + /* amount of bytes read */ + uint32_t count; +} TReadRes; + +/* options for 'Tclunk' 9p request */ +typedef struct TClunkOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID to clunk (required) */ + uint32_t fid; + /* only send Tclunk request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TClunkOpt; + +/* result of 'Tclunk' 9p request */ +typedef struct TClunkRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; +} TClunkRes; + +/* options for 'Txattrcreate' 9p request */ +typedef struct TXattrCreateOpt { + /* 9P client being used (mandatory) */ + QVirtio9P *client; + /* user supplied tag number being returned with response (optional) */ + uint16_t tag; + /* file ID to convert to xattr fid (required) */ + uint32_t fid; + /* name of the xattr (required) */ + const char *name; + /* size of the xattr value (required) */ + uint64_t size; + /* flags: P9_XATTR_CREATE or P9_XATTR_REPLACE (optional) */ + uint32_t flags; + /* only send Txattrcreate request but not wait for a reply? (optional) */ + bool requestOnly; + /* do we expect an Rlerror response, if yes which error code? (optional) */ + uint32_t expectErr; +} TXattrCreateOpt; + +/* result of 'Txattrcreate' 9p request */ +typedef struct TXattrCreateRes { + /* if requestOnly was set: request object for further processing */ + P9Req *req; + /* error code if Rlerror received */ + uint32_t err; +} TXattrCreateRes; + void v9fs_set_allocator(QGuestAllocator *t_alloc); void v9fs_memwrite(P9Req *req, const void *addr, size_t len); void v9fs_memskip(P9Req *req, size_t len); @@ -490,5 +570,11 @@ void v9fs_rlink(P9Req *req); TunlinkatRes v9fs_tunlinkat(TunlinkatOpt); void v9fs_runlinkat(P9Req *req); +TReadRes v9fs_tread(TReadOpt opt); +void v9fs_rread(P9Req *req, uint32_t *count, void *data); +TClunkRes v9fs_tclunk(TClunkOpt opt); +void v9fs_rclunk(P9Req *req); +TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt); +void v9fs_rxattrcreate(P9Req *req); #endif diff -Nru qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p.c qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p.c --- qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p.c 2026-07-25 01:10:13.000000000 +0300 @@ -228,6 +228,12 @@ g_string_assign(haystack, s); } +void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args) +{ + /* append passed args to '-fsdev ...' group */ + regex_replace(cmd_line, "(-fsdev \\w[^ ]*)", "\\1,%s", args); +} + void virtio_9p_assign_local_driver(GString *cmd_line, const char *args) { g_assert_nonnull(local_test_path); diff -Nru qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p.h qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p.h --- qemu-10.0.11+ds/tests/qtest/libqos/virtio-9p.h 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/libqos/virtio-9p.h 2026-07-25 01:10:13.000000000 +0300 @@ -45,6 +45,12 @@ }; /** + * Add required test specific args to the QEMU command line for the 9pfs + * 'synth' fs driver. + */ +void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args); + +/** * Creates the directory for the 9pfs 'local' filesystem driver to access. */ void virtio_9p_create_local_test_dir(void); diff -Nru qemu-10.0.11+ds/tests/qtest/ufs-test.c qemu-10.0.12+ds/tests/qtest/ufs-test.c --- qemu-10.0.11+ds/tests/qtest/ufs-test.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/ufs-test.c 2026-07-25 01:10:13.000000000 +0300 @@ -33,6 +33,8 @@ #define TEST_QID 0 #define QUEUE_SIZE 32 #define UFS_MCQ_MAX_QNUM 32 +#define ACPI_PCIHP_ADDR 0xae00 +#define PCI_EJ_BASE 0x0008 typedef struct QUfs QUfs; @@ -634,6 +636,17 @@ qpci_iounmap(&ufs->dev, ufs->bar); } +static void ufstest_acpi_eject(void *obj, void *data, QGuestAllocator *alloc) +{ + QUfs *ufs = obj; + QTestState *qts = ufs->dev.bus->qts; + + qtest_outl(qts, ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << 4); + qtest_qmp_assert_success(qts, "{ 'execute': 'query-status' }"); + g_usleep(3 * G_USEC_PER_SEC); + qtest_qmp_assert_success(qts, "{ 'execute': 'query-status' }"); +} + static void ufstest_init(void *obj, void *data, QGuestAllocator *alloc) { QUfs *ufs = obj; @@ -1233,6 +1246,8 @@ .edge.extra_device_opts = "mcq=true,mcq-maxq=1" }; + QOSGraphTestOptions acpi_eject_test_opts = { .subprocess = true }; + add_qpci_address(&edge_opts, &(QPCIAddress){ .devfn = QPCI_DEVFN(4, 0) }); qos_node_create_driver("ufs", ufs_create); @@ -1250,6 +1265,10 @@ g_test_message("Skipping ufs io tests for ppc64"); return; } + if (!strcmp(arch, "i386") || !strcmp(arch, "x86_64")) { + qos_add_test("acpi-eject", "ufs", ufstest_acpi_eject, + &acpi_eject_test_opts); + } qos_add_test("init", "ufs", ufstest_init, NULL); qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts); qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts); diff -Nru qemu-10.0.11+ds/tests/qtest/usb-hcd-xhci-test.c qemu-10.0.12+ds/tests/qtest/usb-hcd-xhci-test.c --- qemu-10.0.11+ds/tests/qtest/usb-hcd-xhci-test.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/usb-hcd-xhci-test.c 2026-07-25 01:10:13.000000000 +0300 @@ -10,6 +10,72 @@ #include "qemu/osdep.h" #include "libqtest-single.h" #include "libqos/usb.h" +#include "qobject/qdict.h" + +static void wait_device_deleted_event(QTestState *qtest, const char *id) +{ + QDict *resp, *data; + const char *device; + + /* + * Other devices might get removed along with the removed device. Skip + * these. The device of interest will be the last one. + */ + for (;;) { + resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED"); + data = qdict_get_qdict(resp, "data"); + device = data ? qdict_get_try_str(data, "device") : NULL; + if (device && !strcmp(device, id)) { + qobject_unref(resp); + break; + } + qobject_unref(resp); + } +} + +/* + * Regression test for the xHCI-PCI "host" strong-link reference cycle. + * + * The xHCI PCI wrapper embeds an xhci-core child whose strong "host" link + * points back at the PCI device, forming a refcount cycle. If + * usb_xhci_pci_exit() does not break that cycle, the device's refcount never + * reaches 0 on unplug, device_finalize() never runs, and therefore the + * DEVICE_DELETED event (emitted from device_finalize()) is never sent. + * + * This test hot-plugs an xHCI controller into an ACPI-hotpluggable bus, + * requests its removal and waits for DEVICE_DELETED. Without the fix the event + * is never delivered (device_finalize() is blocked), so the test would + * hang/time out. + */ +static void test_xhci_unplug_finalize(void) +{ + QTestState *qtest; + const char *arch = qtest_get_arch(); + + if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) { + g_test_skip("Test only runs on x86 (ACPI PCI hotplug)"); + return; + } + if (!qtest_has_device("nec-usb-xhci")) { + g_test_skip("Device nec-usb-xhci not available"); + return; + } + + qtest = qtest_initf("-machine pc"); + + qtest_qmp_device_add(qtest, "nec-usb-xhci", "xhci-finalize", "{}"); + + /* + * Request device removal. As the guest is not running, the unplug request + * won't be processed until the next system reset, which performs the + * removal and triggers device_finalize() (and thus DEVICE_DELETED). + */ + qtest_qmp_device_del_send(qtest, "xhci-finalize"); + qtest_system_reset_nowait(qtest); + wait_device_deleted_event(qtest, "xhci-finalize"); + + qtest_quit(qtest); +} static void test_xhci_hotplug(void) { @@ -50,6 +116,7 @@ g_test_init(&argc, &argv, NULL); qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug); + qtest_add_func("/xhci/pci/unplug/finalize", test_xhci_unplug_finalize); if (qtest_has_device("usb-uas")) { qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug); } diff -Nru qemu-10.0.11+ds/tests/qtest/virtio-9p-test.c qemu-10.0.12+ds/tests/qtest/virtio-9p-test.c --- qemu-10.0.11+ds/tests/qtest/virtio-9p-test.c 2026-06-26 00:39:14.000000000 +0300 +++ qemu-10.0.12+ds/tests/qtest/virtio-9p-test.c 2026-07-25 01:10:13.000000000 +0300 @@ -30,6 +30,18 @@ #define tsymlink(...) v9fs_tsymlink((TsymlinkOpt) __VA_ARGS__) #define tlink(...) v9fs_tlink((TlinkOpt) __VA_ARGS__) #define tunlinkat(...) v9fs_tunlinkat((TunlinkatOpt) __VA_ARGS__) +#define tread(...) v9fs_tread((TReadOpt) __VA_ARGS__) +#define tclunk(...) v9fs_tclunk((TClunkOpt) __VA_ARGS__) +#define txattrcreate(...) v9fs_txattrcreate((TXattrCreateOpt) __VA_ARGS__) + +/* + * xattr size to be used for xattr tests + * + * 64k is the max. xattr size supported by the Linux kernel, However btrfs + * for instance supports only 16219 bytes. So let's be conservative and + * just use 8k for the xattr tests. + */ +#define TEST_XATTR_SIZE (8 * 1024) static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc) { @@ -103,6 +115,42 @@ return false; } +/* + * Returns the current internal xattr FID count (works with synth driver only). + */ +static size_t get_xattr_count(QVirtio9P *v9p) +{ + uint16_t nwqid; + v9fs_qid *wqid; + const char *xattr_count_path[] = { "stat", "xattr_count" }; + size_t xattr_count; + uint32_t bytes_read; + + /* walk to /stat/xattr_count file */ + uint32_t fid = twalk({ + .client = v9p, .fid = 0, + .nwname = 2, .wnames = (char **)xattr_count_path, + .rwalk = { .nwqid = &nwqid, .wqid = &wqid } + }).newfid; + + /* open for read */ + tlopen({ + .client = v9p, .fid = fid, .flags = O_RDONLY, + .rlopen = { .qid = NULL, .iounit = NULL } + }); + + /* read the internal xattr FID count */ + tread({ + .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count), + .rread = { .count = &bytes_read, .data = &xattr_count } + }); + + /* cleanup */ + tclunk({ .client = v9p, .fid = fid }); + + return xattr_count; +} + /* basic readdir test where reply fits into a single response message */ static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc) { @@ -244,6 +292,121 @@ g_free(wnames[0]); } +/* + * Test 9p server's xattr FID count limit enforcement. + * + * Shared test code for both 'synth' and 'local' driver to verify correct + * behaviour of 9p server enforcing preconfigured xattr FID count limit + * correctly. + * + * @v9p: 9pfs client + * + * @max_xattr: max. allowed xattr FIDs, or -1 for infinite + * + * @check_counter: whether to verify 9p server internal xattr FID counter + * (only works with 'synth' fs driver) + */ +static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter) +{ + size_t count; + int i; + int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100; + g_autofree uint32_t *fids = g_new0(uint32_t, limit); + uint32_t err_fid = 0; + const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE }; + g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE); + + if (!g_test_slow()) { + g_test_skip("This is a slow test, run with -m slow"); + return; + } + + /* prepare xattr data with 'X' characters */ + memset(xattr_data, 'X', TEST_XATTR_SIZE); + + tattach({ .client = v9p }); + + /* create max. amount of permitted xattrs */ + for (i = 0; i < limit; i++) { + /* walk to create a new fid */ + fids[i] = twalk({ + .client = v9p, .fid = 0, + .nwname = 1, .wnames = (char **) file_path + }).newfid; + + /* create new xattr fid */ + txattrcreate({ + .client = v9p, .fid = fids[i], .name = "user.test", + .size = TEST_XATTR_SIZE, .flags = 0 + }); + + /* transfer the xattr data */ + twrite({ + .client = v9p, .fid = fids[i], .offset = 0, + .count = TEST_XATTR_SIZE, .data = xattr_data + }); + + /* verify server internal xattr counter */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, (i + 1)); + } + + /* avoid virtio descriptor exhaustion */ + qvirtqueue_reset_pool(v9p->vq); + } + + /* if xattrs are limited, the next xattr should fail */ + if (max_xattr != -1) { + /* walk to create another fid */ + err_fid = twalk({ + .client = v9p, .fid = 0, + .nwname = 1, .wnames = (char **) file_path + }).newfid; + + /* try to create one more xattr fid - should fail */ + txattrcreate({ + .client = v9p, .fid = err_fid, .name = "user.test_exceed", + .size = TEST_XATTR_SIZE, .flags = 0, + .expectErr = ENOSPC + }); + + /* verify internal xattr counter hasn't changed */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, limit); + } + } + + /* clunk all fids (should decrement xattr counter) */ + for (i = 0; i < limit; i++) { + tclunk({ .client = v9p, .fid = fids[i] }); + qvirtqueue_reset_pool(v9p->vq); + } + if (err_fid) { + tclunk({ .client = v9p, .fid = err_fid }); + } + + /* verify internal xattr counter is zero */ + if (check_counter) { + count = get_xattr_count(v9p); + g_assert_cmpuint(count, ==, 0); + } +} + +static void do_local_xattr_limit(QVirtio9P *v9p, int max_xattr) +{ + g_autofree char *test_file = virtio_9p_test_path("WRITE"); + + /* + * this file must be created for the test to work with the 'local' fs driver + */ + g_file_set_contents(test_file, "", 0, NULL); + + /* the actual test code shared with the 'synth' fs driver tests */ + do_xattr_limit(v9p, max_xattr, false); +} + static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc) { QVirtio9P *v9p = obj; @@ -504,6 +667,27 @@ do_readdir_split(obj, 512); } +static void fs_synth_xattr_limit_default(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true); +} + +static void fs_synth_xattr_limit_custom(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, 100, true); +} + +static void fs_synth_xattr_limit_unlimited(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_xattr_limit(obj, -1, true); +} + /* tests using the 9pfs 'local' fs driver */ @@ -804,26 +988,81 @@ g_string_free(path, TRUE); } +static void fs_local_xattr_limit_default(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT); +} + +static void fs_local_xattr_limit_custom(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, 100); +} + +static void fs_local_xattr_limit_unlimited(void *obj, void *data, + QGuestAllocator *t_alloc) +{ + v9fs_set_allocator(t_alloc); + do_local_xattr_limit(obj, -1); +} + +static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg) +{ + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100"); + return arg; +} + +static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg) +{ + virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0"); + return arg; +} + static void cleanup_9p_local_driver(void *data) { /* remove previously created test dir when test is completed */ virtio_9p_remove_local_test_dir(); } -static void *assign_9p_local_driver(GString *cmd_line, void *arg) +static void assign_9p_local_driver_with_args(GString *cmd_line, + const char *extra_opts) { /* make sure test dir for the 'local' tests exists */ virtio_9p_create_local_test_dir(); - virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr"); + g_autofree char *opts = + (extra_opts) ? + g_strdup_printf("security_model=mapped-xattr,%s", extra_opts) : + g_strdup("security_model=mapped-xattr"); + + virtio_9p_assign_local_driver(cmd_line, opts); g_test_queue_destroy(cleanup_9p_local_driver, NULL); +} + +static void *assign_9p_local_driver(GString *cmd_line, void *arg) +{ + assign_9p_local_driver_with_args(cmd_line, NULL); return arg; } -static void register_virtio_9p_test(void) +static void *local_max_xattr_custom_opt(GString *cmd_line, void *arg) { + assign_9p_local_driver_with_args(cmd_line, "max_xattr=100"); + return arg; +} +static void *local_max_xattr_unlimited_opt(GString *cmd_line, void *arg) +{ + assign_9p_local_driver_with_args(cmd_line, "max_xattr=0"); + return arg; +} + +static void register_virtio_9p_test(void) +{ QOSGraphTestOptions opts = { }; @@ -854,7 +1093,14 @@ fs_readdir_split_256, &opts); qos_add_test("synth/readdir/split_128", "virtio-9p", fs_readdir_split_128, &opts); - + qos_add_test("synth/xattr_limit/default", "virtio-9p", + fs_synth_xattr_limit_default, &opts); + opts.before = synth_max_xattr_custom_opt; + qos_add_test("synth/xattr_limit/custom", "virtio-9p", + fs_synth_xattr_limit_custom, &opts); + opts.before = synth_max_xattr_unlimited_opt; + qos_add_test("synth/xattr_limit/unlimited", "virtio-9p", + fs_synth_xattr_limit_unlimited, &opts); /* 9pfs test cases using the 'local' filesystem driver */ opts.before = assign_9p_local_driver; @@ -873,6 +1119,14 @@ &opts); qos_add_test("local/deep_absolute_path", "virtio-9p", fs_deep_absolute_path, &opts); + qos_add_test("local/xattr_limit/default", "virtio-9p", + fs_local_xattr_limit_default, &opts); + opts.before = local_max_xattr_custom_opt; + qos_add_test("local/xattr_limit/custom", "virtio-9p", + fs_local_xattr_limit_custom, &opts); + opts.before = local_max_xattr_unlimited_opt; + qos_add_test("local/xattr_limit/unlimited", "virtio-9p", + fs_local_xattr_limit_unlimited, &opts); } libqos_init(register_virtio_9p_test); diff -Nru qemu-10.0.11+ds/ui/gtk-egl.c qemu-10.0.12+ds/ui/gtk-egl.c --- qemu-10.0.11+ds/ui/gtk-egl.c 2026-06-26 00:39:15.000000000 +0300 +++ qemu-10.0.12+ds/ui/gtk-egl.c 2026-07-25 01:10:13.000000000 +0300 @@ -91,6 +91,7 @@ } else { qemu_dmabuf_set_draw_submitted(dmabuf, false); } + graphic_hw_gl_block(vc->gfx.dcl.con, true); } #endif gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h); @@ -383,14 +384,11 @@ if (vc->gfx.guest_fb.dmabuf && !qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { - graphic_hw_gl_block(vc->gfx.dcl.con, true); qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true); gtk_egl_set_scanout_mode(vc, true); - gtk_widget_queue_draw_area(area, x, y, w, h); - return; } - gd_egl_scanout_flush(&vc->gfx.dcl, x, y, w, h); + gtk_widget_queue_draw_area(area, x, y, w, h); } void gtk_egl_init(DisplayGLMode mode) diff -Nru qemu-10.0.11+ds/ui/gtk-gl-area.c qemu-10.0.12+ds/ui/gtk-gl-area.c --- qemu-10.0.11+ds/ui/gtk-gl-area.c 2026-06-26 00:39:15.000000000 +0300 +++ qemu-10.0.12+ds/ui/gtk-gl-area.c 2026-07-25 01:10:13.000000000 +0300 @@ -73,6 +73,7 @@ } else { qemu_dmabuf_set_draw_submitted(dmabuf, false); } + graphic_hw_gl_block(vc->gfx.dcl.con, true); } #endif @@ -135,27 +136,6 @@ gd_update_monitor_refresh_rate(vc, vc->window ? vc->window : vc->gfx.drawing_area); - if (vc->gfx.guest_fb.dmabuf && - qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { - /* - * gd_egl_refresh() calls gd_egl_draw() if a DMA-BUF draw has already - * been submitted, but this function does not call gd_gl_area_draw() in - * such a case due to display corruption. - * - * Calling gd_gl_area_draw() is necessary to prevent a situation where - * there is a scheduled draw event but it won't happen bacause the window - * is currently in inactive state (minimized or tabified). If draw is not - * done for a long time, gl_block timeout and/or fence timeout (on the - * guest) will happen eventually. - * - * However, it is found that calling gd_gl_area_draw() here causes guest - * display corruption on a Wayland Compositor. The display corruption is - * more serious than the possible fence timeout so gd_gl_area_draw() is - * omitted for now. - */ - return; - } - if (!vc->gfx.gls) { if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { return; @@ -314,7 +294,6 @@ if (vc->gfx.guest_fb.dmabuf && !qemu_dmabuf_get_draw_submitted(vc->gfx.guest_fb.dmabuf)) { - graphic_hw_gl_block(vc->gfx.dcl.con, true); qemu_dmabuf_set_draw_submitted(vc->gfx.guest_fb.dmabuf, true); gtk_gl_area_set_scanout_mode(vc, true); } diff -Nru qemu-10.0.11+ds/ui/gtk.c qemu-10.0.12+ds/ui/gtk.c --- qemu-10.0.11+ds/ui/gtk.c 2026-06-26 00:39:15.000000000 +0300 +++ qemu-10.0.12+ds/ui/gtk.c 2026-07-25 01:10:13.000000000 +0300 @@ -2552,7 +2552,9 @@ if (!con) { break; } - gtk_widget_realize(s->vc[idx].gfx.drawing_area); + if (s->vc[idx].type == GD_VC_GFX) { + gtk_widget_realize(s->vc[idx].gfx.drawing_area); + } } if (opts->u.gtk.has_show_menubar && diff -Nru qemu-10.0.11+ds/ui/input-barrier.c qemu-10.0.12+ds/ui/input-barrier.c --- qemu-10.0.11+ds/ui/input-barrier.c 2026-06-26 00:39:15.000000000 +0300 +++ qemu-10.0.12+ds/ui/input-barrier.c 2026-07-25 01:10:13.000000000 +0300 @@ -87,7 +87,7 @@ static int input_barrier_to_qcode(uint16_t keyid, uint16_t keycode) { /* keycode is optional, if it is not provided use keyid */ - if (keycode && keycode <= qemu_input_map_xorgkbd_to_qcode_len) { + if (keycode && keycode < qemu_input_map_xorgkbd_to_qcode_len) { return qemu_input_map_xorgkbd_to_qcode[keycode]; } diff -Nru qemu-10.0.11+ds/ui/vnc.c qemu-10.0.12+ds/ui/vnc.c --- qemu-10.0.11+ds/ui/vnc.c 2026-06-26 00:39:15.000000000 +0300 +++ qemu-10.0.12+ds/ui/vnc.c 2026-07-25 01:10:13.000000000 +0300 @@ -2308,6 +2308,18 @@ return; } + if (red_max > UINT8_MAX || green_max > UINT8_MAX || blue_max > UINT8_MAX) { + vnc_client_error(vs); + return; + } + + if (red_shift >= bits_per_pixel || red_shift >= 32 || + green_shift >= bits_per_pixel || green_shift >= 32 || + blue_shift >= bits_per_pixel || blue_shift >= 32) { + vnc_client_error(vs); + return; + } + vs->client_pf.rmax = red_max ? red_max : 0xFF; vs->client_pf.rbits = ctpopl(red_max); vs->client_pf.rshift = red_shift; @@ -2982,15 +2994,23 @@ } } -static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) +static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y, + int height) { VncState *vs; int sty = y / VNC_STAT_RECT; int stx = x / VNC_STAT_RECT; int has_dirty = 0; + int rows; y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT); x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT); + rows = MIN(VNC_STAT_RECT, height - y); + + rows = MIN(VNC_STAT_RECT, height - y); + if (rows <= 0) { + return 0; + } QTAILQ_FOREACH(vs, &vd->clients, next) { int j; @@ -3005,7 +3025,7 @@ } vs->lossy_rect[sty][stx] = 0; - for (j = 0; j < VNC_STAT_RECT; ++j) { + for (j = 0; j < rows; ++j) { bitmap_set(vs->dirty[y + j], x / VNC_DIRTY_PIXELS_PER_BIT, VNC_STAT_RECT / VNC_DIRTY_PIXELS_PER_BIT); @@ -3056,7 +3076,7 @@ if (timercmp(&res, &VNC_REFRESH_LOSSY, >)) { rect->freq = 0; - has_dirty += vnc_refresh_lossy_rect(vd, x, y); + has_dirty += vnc_refresh_lossy_rect(vd, x, y, height); memset(rect->times, 0, sizeof (rect->times)); continue ; }