Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:qemu User: [email protected] Usertags: pu
[ Reason ] There's a new upstream stable/bugfix release, with a number of security and correctness fixes. Security fixes include: CVE-2026-17588, CVE-2026-93834 (#1149064), CVE-2026-12080, CVE-2026-66899, CVE-2026-66900, CVE-2026-66020, CVE-2026-84788, CVE-2026-81627 (#1148473), CVE-2026-77913, CVE-2026-16271. [ Tests ] As usual, this release passes upstream testsuite (the bits which are relevant still, since more and more environments used in the testing becomes unavailable), and a bunch of my regular testing VMs, including windows, linux and freebsd. No regressions are observed. [ Risks ] There's relatively small amount of changes this time, and most of them are easily understandable and verifiable. I don't expect much risks from this update. [ 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 ] The debian/changelog is below, in the debdiff output. There are two obvious changes in debian/ - the changelog and a version bump in debian/rules. The rest are upstream changes, which might better be viewed as git commits at https://salsa.debian.org/qemu-team/qemu/-/commits/upstream-10.0 between v10.0.13 (already in debian) and v10.0.14 tags. Thanks, /mjt diff -Nru qemu-10.0.13+ds/debian/changelog qemu-10.0.14+ds/debian/changelog --- qemu-10.0.13+ds/debian/changelog 2026-08-29 20:55:40.000000000 +0300 +++ qemu-10.0.14+ds/debian/changelog 2026-10-03 20:01:57.000000000 +0300 @@ -1,3 +1,94 @@ +qemu (1:10.0.14+ds-0+deb13u1) trixie; urgency=medium + + * new upstream stable/bugfix release, including multiple security fixes: + - Update version for 10.0.14 release + - target/sh4: Replace TB_FLAG_GUSA_EXCLUSIVE with CF_STEP_ATOMIC + - accel/tcg: Set CF_NOIRQ during cpu_exec_step_atomic + - target/i386: Mark MOVNTI as not valid with prefixes 0x66, 0xF2, 0xF3 + - target/i386: Update FPU tag word for FXCH + - target/i386: Update FPU tag word for FSTP + - target/i386: Update FPU tag word for FXTRACT's old ST(0) + - target/i386: Fix FXCH to unconditionally clear C1 + - target/ppc: Stop vCPU thread before calling parent_unrealize + - tests/qtest/usb-hcd-xhci: test isoch endpoint type mismatch + - tests/qtest/usb-hcd-xhci: test isoch pacing with MFINDEX above 2^32 + - hw/usb/hcd-xhci: don't assert on NAK when retrying an isoch transfer + - hw/usb/hcd-xhci: fix interval alignment after MFINDEX passes 2^32 + - hw/usb/hcd-xhci: Set reentrancy guard in timer functions + Closes: CVE-2026-17588 + - tcg/riscv64: Set vtype before whole-register vector loads + - accel/tcg: Fix TLB_MMIO check in tlb_plugin_lookup() + - accel/tcg: Use TLB_FORCE_SLOW not TLB_MMIO for system plugins + - hw/9pfs: mutate FID path from main thread only + Closes: #1149064, CVE-2026-93834 + - s390x/pci: fix DMA slot leak on I/O TLB entry replacement + - linux-user/loongarch64: Detect vector stores in host_signal_write() + - linux-user: implement mlock2(2) syscall + - linux-user/riscv: honor zicntr=false for base counterCSRs + - hw/uefi: add missing uefi_str_is_valid check to uefi_vars_mm_lock_variable + - hw/riscv/virt.c: fix aclint soc/mtimer nodename + - hw/intc/bcm2835_ic: reject out-of-range FIQ source values + - qga: Change effective user/group ID in guest-ssh-* commands + Closes: CVE-2026-12080 + - vhost-user-gpu: validate command buffer size in submit_3d + - ui/cursor: make the cursor refcount atomic + - hw/display/qxl: hold ssd.lock while replacing ssd.cursor + - hw/cxl: fix the CDAT DOE overlapping the Flex Bus DVSEC when sn= is set + - virtio-scsi: set dataplane_started to false upon failure + - virtio-balloon: fix free-page BH teardown on unrealize + Closes: CVE-2026-66899 + - hw/virtio: reject inverted virtio-iommu IOVA ranges + - hw/net/virtio-net: strip trailing padding when caching RSC segment + Closes: CVE-2026-66900 + - hw/net/virtio-net: check packet size before VLAN tag access + in receive_filter() + - qapi/misc: Fix missed query-iothreads items + - hw/cxl: Fix guest-triggerable QEMU exit on reserved interleave ways + - virtio-gpu: clear res->blob on mapping cleanup + - virtio-gpu: disable blob scanouts on mapping cleanup + Closes: CVE-2026-66020 + - tests/unit: cover blocked IO during the websock handshake + - io/channel-websock: do not lose QIO_CHANNEL_ERR_BLOCK while reading + - tests/unit: add websock handshake test + - io/channel-websock: handle a blocked write during the handshake + - io/channel-websock: send an HTTP 400 when the greeting has no space + - io/channel-socket: do not treat a zero length write as an error + (The above 5 changes) Closes: CVE-2026-84788 + - hw/sd: sdhci: Accept version 4 enable without UHS-I + - target/arm: Make Thumb T1 hint space UNDEF before v6T2 + - target/arm: Make IT insn undef when not present + - target/arm: Make CBZ/CBNZ UNDEF before v6T2 + - target/arm: Correct reset value of SCTLR for arm926, arm1026 + - target/arm: fix TTA instruction S bit for IDAU-exempt addresses + - i386/vapic: unref MemoryRegion if vapic_map_rom_writable fails + - vapic: confine the VAPIC region to 0xc0000..0xe0000 + Closes: #1148473, CVE-2026-81627 + - scsi: hide MODE SELECT block size change behind a quirk + - scsi-disk: fix out-of-bound read in WRITE SAME + - hw/ide: report the default CHS translation in IDENTIFY DEVICE + - tests/qtest/ide-test: cover a CHS translation with zero sectors + - hw/ide: reject an unsupported CHS translation + - s390x/sclp: pv: only copy the original SCCB buffer + - tests/qtest: Add seed CSR zero extension test + - target/riscv: Fix seed CSR sign extension + - target/riscv: tt-ascalon: Enable Zkr extension + - dirty-bitmap: fix integer overflow in serialization coverage + - block/monitor: allow dropping a bitmap never stored on disk + - migration/block-dirty-bitmap: reject bitmap load onto ro node + - block/monitor: reject persistent bitmap add on a read-only node + - tests/unit/test-blockjob: cover keeping a job paused + while a pause is pending + - job: keep job paused across overlapping pause requests + - hw/input/ps2: answer unknown mouse commands with a resend + - hw/display/vga: fix text-mode OOB write after a graphics surface switch + Closes: CVE-2026-77913 + - crypto: fix build against nettle >= 4 + - virtio-gpu: use g_try_malloc to avoid guest-triggered abort + - hw/display/qxl: validate primary surface stride against width + Closes: CVE-2026-16271 + + -- Michael Tokarev <[email protected]> Sat, 03 Oct 2026 20:01:57 +0300 + qemu (1:10.0.13+ds-0+deb13u1) trixie; urgency=medium * new upstream stable/bugfix release, including multiple security fixes: diff -Nru qemu-10.0.13+ds/debian/control.mk qemu-10.0.14+ds/debian/control.mk --- qemu-10.0.13+ds/debian/control.mk 2026-08-29 20:55:40.000000000 +0300 +++ qemu-10.0.14+ds/debian/control.mk 2026-10-02 22:51:51.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.13+ds +checked-version := 10.0.14+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.13+ds/VERSION qemu-10.0.14+ds/VERSION --- qemu-10.0.13+ds/VERSION 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/VERSION 2026-09-29 03:48:20.000000000 +0300 @@ -1 +1 @@ -10.0.13 +10.0.14 diff -Nru qemu-10.0.13+ds/accel/tcg/cpu-exec.c qemu-10.0.14+ds/accel/tcg/cpu-exec.c --- qemu-10.0.13+ds/accel/tcg/cpu-exec.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/accel/tcg/cpu-exec.c 2026-09-29 03:48:20.000000000 +0300 @@ -580,7 +580,8 @@ /* Execute in a serial context. */ cflags &= ~CF_PARALLEL; /* After 1 insn, return and release the exclusive lock. */ - cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | 1; + cflags |= CF_NO_GOTO_TB | CF_NO_GOTO_PTR | + CF_NOIRQ | CF_STEP_ATOMIC | 1; /* * No need to check_for_breakpoints here. * We only arrive in cpu_exec_step_atomic after beginning execution diff -Nru qemu-10.0.13+ds/accel/tcg/cputlb.c qemu-10.0.14+ds/accel/tcg/cputlb.c --- qemu-10.0.13+ds/accel/tcg/cputlb.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/accel/tcg/cputlb.c 2026-09-29 03:48:20.000000000 +0300 @@ -1363,7 +1363,6 @@ uint64_t tlb_addr = tlb_read_idx(entry, access_type); vaddr page_addr = addr & TARGET_PAGE_MASK; int flags = TLB_FLAGS_MASK & ~TLB_FORCE_SLOW; - bool force_mmio = check_mem_cbs && cpu_plugin_mem_cbs_enabled(cpu); CPUTLBEntryFull *full; if (!tlb_hit_page(tlb_addr, page_addr)) { @@ -1393,16 +1392,13 @@ *pfull = full = &cpu->neg.tlb.d[mmu_idx].fulltlb[index]; flags |= full->slow_flags[access_type]; - - /* Fold all "mmio-like" bits into TLB_MMIO. This is not RAM. */ - if (unlikely(flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY | TLB_CHECK_ALIGNED)) - || (access_type != MMU_INST_FETCH && force_mmio)) { - *phost = NULL; - return TLB_MMIO; + if (check_mem_cbs && cpu_plugin_mem_cbs_enabled(cpu)) { + flags |= TLB_FORCE_SLOW; } - /* Everything else is RAM. */ - *phost = (void *)((uintptr_t)addr + entry->addend); + *phost = (flags & ~(TLB_WATCHPOINT | TLB_NOTDIRTY | TLB_CHECK_ALIGNED) + ? NULL + : (void *)((uintptr_t)addr + entry->addend)); return flags; } @@ -1587,7 +1583,7 @@ data->phys_addr = full->phys_addr | (addr & ~TARGET_PAGE_MASK); /* We must have an iotlb entry for MMIO */ - if (tlb_addr & TLB_MMIO) { + if (full->slow_flags[access_type] & TLB_MMIO) { MemoryRegionSection *section = iotlb_to_section(cpu, full->xlat_section & ~TARGET_PAGE_MASK, full->attrs); diff -Nru qemu-10.0.13+ds/block/dirty-bitmap.c qemu-10.0.14+ds/block/dirty-bitmap.c --- qemu-10.0.13+ds/block/dirty-bitmap.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/block/dirty-bitmap.c 2026-09-29 03:48:20.000000000 +0300 @@ -612,7 +612,7 @@ const BdrvDirtyBitmap *bitmap) { uint64_t granularity = bdrv_dirty_bitmap_granularity(bitmap); - uint64_t limit = granularity * (serialized_chunk_size << 3); + uint64_t limit = granularity * ((uint64_t)serialized_chunk_size << 3); assert(QEMU_IS_ALIGNED(limit, bdrv_dirty_bitmap_serialization_align(bitmap))); diff -Nru qemu-10.0.13+ds/block/monitor/bitmap-qmp-cmds.c qemu-10.0.14+ds/block/monitor/bitmap-qmp-cmds.c --- qemu-10.0.13+ds/block/monitor/bitmap-qmp-cmds.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/block/monitor/bitmap-qmp-cmds.c 2026-09-29 03:48:20.000000000 +0300 @@ -125,10 +125,17 @@ disabled = false; } - if (persistent && - !bdrv_can_store_new_dirty_bitmap(bs, name, granularity, errp)) - { - return; + if (persistent) { + if (!bdrv_is_writable(bs)) { + error_setg(errp, "Cannot add a persistent bitmap to " + "read-only or inactive node '%s'", + bdrv_get_node_name(bs)); + return; + } + + if (!bdrv_can_store_new_dirty_bitmap(bs, name, granularity, errp)) { + return; + } } bitmap = bdrv_create_dirty_bitmap(bs, granularity, name, errp); @@ -158,11 +165,11 @@ return NULL; } - if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_BUSY | BDRV_BITMAP_RO, - errp)) { + if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_BUSY, errp)) { return NULL; } + /* Dropping a bitmap needs no write access unless it is actually stored. */ if (bdrv_dirty_bitmap_get_persistence(bitmap) && bdrv_remove_persistent_dirty_bitmap(bs, name, errp) < 0) { diff -Nru qemu-10.0.13+ds/block/qcow2-bitmap.c qemu-10.0.14+ds/block/qcow2-bitmap.c --- qemu-10.0.13+ds/block/qcow2-bitmap.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/block/qcow2-bitmap.c 2026-09-29 03:48:20.000000000 +0300 @@ -1487,6 +1487,15 @@ goto out; } + if (!can_write(bs)) { + error_setg(errp, "Cannot remove persistent bitmap '%s': " + "no write access to node '%s'", name, + bdrv_get_node_name(bs)); + ret = -EACCES; + bm = NULL; + goto out; + } + QSIMPLEQ_REMOVE(bm_list, bm, Qcow2Bitmap, entry); ret = update_ext_header_and_dir(bs, bm_list); diff -Nru qemu-10.0.13+ds/contrib/vhost-user-gpu/vhost-user-gpu.c qemu-10.0.14+ds/contrib/vhost-user-gpu/vhost-user-gpu.c --- qemu-10.0.13+ds/contrib/vhost-user-gpu/vhost-user-gpu.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/contrib/vhost-user-gpu/vhost-user-gpu.c 2026-09-29 03:48:20.000000000 +0300 @@ -487,7 +487,7 @@ struct virtio_gpu_ctrl_command *cmd, struct iovec **iov) { - struct virtio_gpu_mem_entry *ents; + g_autofree struct virtio_gpu_mem_entry *ents = NULL; size_t esize, s; int i; @@ -498,17 +498,22 @@ } esize = sizeof(*ents) * ab->nr_entries; - ents = g_malloc(esize); + ents = g_try_malloc(esize); + if (!ents && esize) { + return -1; + } s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(*ab), ents, esize); if (s != esize) { g_critical("%s: command data size incorrect %zu vs %zu", __func__, s, esize); - g_free(ents); return -1; } - *iov = g_new0(struct iovec, ab->nr_entries); + *iov = g_try_new0(struct iovec, ab->nr_entries); + if (!*iov && ab->nr_entries) { + return -1; + } for (i = 0; i < ab->nr_entries; i++) { uint64_t len = ents[i].length; (*iov)[i].iov_len = ents[i].length; @@ -517,12 +522,10 @@ g_critical("%s: resource %d element %d", __func__, ab->resource_id, i); g_free(*iov); - g_free(ents); *iov = NULL; return -1; } } - g_free(ents); return 0; } @@ -828,8 +831,14 @@ PIXMAN_FORMAT_BPP(pixman_image_get_format(res->image)) / 8; size_t size = width * height * bpp; - void *p = g_malloc(VHOST_USER_GPU_HDR_SIZE + - sizeof(VhostUserGpuUpdate) + size); + void *p = g_try_malloc(VHOST_USER_GPU_HDR_SIZE + + sizeof(VhostUserGpuUpdate) + size); + if (!p) { + pixman_region_fini(®ion); + pixman_region_fini(&finalregion); + cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; + break; + } VhostUserGpuMsg *msg = p; msg->request = VHOST_USER_GPU_UPDATE; msg->size = sizeof(VhostUserGpuUpdate) + size; diff -Nru qemu-10.0.13+ds/contrib/vhost-user-gpu/virgl.c qemu-10.0.14+ds/contrib/vhost-user-gpu/virgl.c --- qemu-10.0.13+ds/contrib/vhost-user-gpu/virgl.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/contrib/vhost-user-gpu/virgl.c 2026-09-29 03:48:20.000000000 +0300 @@ -197,19 +197,27 @@ struct virtio_gpu_ctrl_command *cmd) { struct virtio_gpu_cmd_submit cs; + size_t iov_len; void *buf; size_t s; VUGPU_FILL_CMD(cs); - if (cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) { - g_critical("%s: command buffer too large (%u)", - __func__, cs.size); + iov_len = iov_size(cmd->elem.out_sg, cmd->elem.out_num); + if (cs.size == 0 || iov_len < sizeof(cs) || + cs.size > iov_len - sizeof(cs) || + cs.size > VIRTIO_GPU_MAX_CMD_SUBMIT_SIZE) { + g_critical("%s: size out of range (%u/%zu)", + __func__, cs.size, iov_len); cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; return; } - buf = g_malloc(cs.size); + buf = g_try_malloc(cs.size); + if (!buf) { + cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; + return; + } s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); if (s != cs.size) { diff -Nru qemu-10.0.13+ds/contrib/vhost-user-gpu/vugbm.c qemu-10.0.14+ds/contrib/vhost-user-gpu/vugbm.c --- qemu-10.0.13+ds/contrib/vhost-user-gpu/vugbm.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/contrib/vhost-user-gpu/vugbm.c 2026-09-29 03:48:20.000000000 +0300 @@ -13,7 +13,10 @@ static bool mem_alloc_bo(struct vugbm_buffer *buf) { - buf->mmap = g_malloc((uint64_t)buf->width * buf->height * 4); + buf->mmap = g_try_malloc((uint64_t)buf->width * buf->height * 4); + if (!buf->mmap && buf->width && buf->height) { + return false; + } buf->stride = buf->width * 4; return true; } diff -Nru qemu-10.0.13+ds/crypto/hash-nettle.c qemu-10.0.14+ds/crypto/hash-nettle.c --- qemu-10.0.13+ds/crypto/hash-nettle.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/crypto/hash-nettle.c 2026-09-29 03:48:20.000000000 +0300 @@ -24,7 +24,8 @@ #include "crypto/hash.h" #include "hashpriv.h" #include <nettle/md5.h> -#include <nettle/sha.h> +#include <nettle/sha1.h> +#include <nettle/sha2.h> #include <nettle/ripemd160.h> #ifdef CONFIG_CRYPTO_SM3 #include <nettle/sm3.h> diff -Nru qemu-10.0.13+ds/hw/9pfs/cofile.c qemu-10.0.14+ds/hw/9pfs/cofile.c --- qemu-10.0.13+ds/hw/9pfs/cofile.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/9pfs/cofile.c 2026-09-29 03:48:20.000000000 +0300 @@ -143,10 +143,11 @@ cred.fc_mode = mode & 07777; cred.fc_uid = fidp->uid; cred.fc_gid = gid; + v9fs_path_init(&path); /* * Hold the directory fid lock so that directory path name - * don't change. Take the write lock to be sure this fid - * cannot be used by another operation. + * don't change. Take the write lock since the fid path is + * mutated below on success. */ v9fs_path_write_lock(s); v9fs_co_run_in_worker( @@ -156,23 +157,30 @@ if (err < 0) { err = -errno; } else { - v9fs_path_init(&path); err = v9fs_name_to_path(s, &fidp->path, name->data, &path); if (!err) { err = s->ops->lstat(&s->ctx, &path, stbuf); if (err < 0) { err = -errno; s->ops->close(&s->ctx, &fidp->fs); - } else { - v9fs_path_copy(&fidp->path, &path); } } else { s->ops->close(&s->ctx, &fidp->fs); } - v9fs_path_free(&path); } }); + /* + * The fid path must not be mutated from the worker thread: other + * requests may access the same fid on the main thread, and the main + * thread never takes the path lock for reads. Mutate the new path + * here, on the main thread and still under the held write lock, like + * every other mutation of a fid path. + */ + if (!err) { + v9fs_path_copy(&fidp->path, &path); + } v9fs_path_unlock(s); + v9fs_path_free(&path); if (!err) { total_open_fd++; if (total_open_fd > open_fd_hw) { diff -Nru qemu-10.0.13+ds/hw/display/qxl-render.c qemu-10.0.14+ds/hw/display/qxl-render.c --- qemu-10.0.13+ds/hw/display/qxl-render.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/qxl-render.c 2026-09-29 03:48:20.000000000 +0300 @@ -27,6 +27,7 @@ static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect) { DisplaySurface *surface = qemu_console_surface(qxl->vga.con); + int dst_stride = surface_stride(surface); uint8_t *dst = surface_data(surface); uint8_t *src; int len, i; @@ -45,14 +46,14 @@ } else { src += rect->top * qxl->guest_primary.abs_stride; } - dst += rect->top * qxl->guest_primary.abs_stride; + dst += rect->top * dst_stride; src += rect->left * qxl->guest_primary.bytes_pp; dst += rect->left * qxl->guest_primary.bytes_pp; len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp; for (i = rect->top; i < rect->bottom; i++) { memcpy(dst, src, len); - dst += qxl->guest_primary.abs_stride; + dst += dst_stride; src += qxl->guest_primary.qxl_stride; } } @@ -61,30 +62,13 @@ { QXLSurfaceCreate *sc = &qxl->guest_primary.surface; - qxl->guest_primary.qxl_stride = sc->stride; - qxl->guest_primary.abs_stride = abs(sc->stride); + qxl->guest_primary.qxl_stride = le32_to_cpu(sc->stride); + qxl->guest_primary.abs_stride = abs(qxl->guest_primary.qxl_stride); qxl->guest_primary.resized++; - switch (sc->format) { - case SPICE_SURFACE_FMT_16_555: - qxl->guest_primary.bytes_pp = 2; - qxl->guest_primary.bits_pp = 15; - break; - case SPICE_SURFACE_FMT_16_565: - qxl->guest_primary.bytes_pp = 2; - qxl->guest_primary.bits_pp = 16; - break; - case SPICE_SURFACE_FMT_32_xRGB: - case SPICE_SURFACE_FMT_32_ARGB: - qxl->guest_primary.bytes_pp = 4; - qxl->guest_primary.bits_pp = 32; - break; - default: - fprintf(stderr, "%s: unhandled format: %x\n", __func__, - qxl->guest_primary.surface.format); - qxl->guest_primary.bytes_pp = 4; - qxl->guest_primary.bits_pp = 32; - break; - } + /* fallback to default bpp if format is unknown */ + qxl_format_bpp(qxl, le32_to_cpu(sc->format), + &qxl->guest_primary.bytes_pp, + &qxl->guest_primary.bits_pp); } static void qxl_set_rect_to_surface(PCIQXLDevice *qxl, QXLRect *area) @@ -101,15 +85,45 @@ DisplaySurface *surface; int width = qxl->guest_head0_width ?: qxl->guest_primary.surface.width; int height = qxl->guest_head0_height ?: qxl->guest_primary.surface.height; + uint64_t map_height; int i; + if (width <= 0 || height <= 0) { + goto end; + } + + if (qxl->guest_primary.bytes_pp > 0) { + int max_width = qxl->guest_primary.abs_stride + / qxl->guest_primary.bytes_pp; + width = MIN(width, max_width); + } + + if (qxl->guest_primary.qxl_stride < 0) { + /* qxl_blit() uses the primary height to find the first scanline. */ + height = MIN(height, (int)qxl->guest_primary.surface.height); + } + + if (qxl->guest_primary.abs_stride > 0) { + int max_height = qxl->vgamem_size / qxl->guest_primary.abs_stride; + height = MIN(height, max_height); + } + + /* + * height limits the visible update, while map_height is the guest memory + * span validated by qxl_phys2virt(). With a negative stride qxl_blit() + * addresses scanlines from the declared primary height, so a shorter + * monitor still requires validating the full primary surface. + */ + map_height = qxl->guest_primary.qxl_stride < 0 ? + qxl->guest_primary.surface.height : height; + if (qxl->guest_primary.resized) { qxl->guest_primary.resized = 0; qxl->guest_primary.data = qxl_phys2virt(qxl, qxl->guest_primary.surface.mem, MEMSLOT_GROUP_GUEST, qxl->guest_primary.abs_stride - * height); + * map_height); if (!qxl->guest_primary.data) { goto end; } diff -Nru qemu-10.0.13+ds/hw/display/qxl.c qemu-10.0.14+ds/hw/display/qxl.c --- qemu-10.0.13+ds/hw/display/qxl.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/qxl.c 2026-09-29 03:48:20.000000000 +0300 @@ -299,10 +299,12 @@ qemu_mutex_lock(&qxl->track_lock); qxl->guest_cursor = 0; qemu_mutex_unlock(&qxl->track_lock); + qemu_mutex_lock(&qxl->ssd.lock); if (qxl->ssd.cursor) { cursor_unref(qxl->ssd.cursor); } qxl->ssd.cursor = cursor_builtin_hidden(); + qemu_mutex_unlock(&qxl->ssd.lock); } static uint32_t qxl_crc32(const uint8_t *p, unsigned len) @@ -1507,6 +1509,47 @@ qxl_render_resize(qxl); } +/* + * Convert a SpiceSurfaceFormat to bytes per pixel and bits per pixel. + * + * Only valid for surface suitable for rendering. + */ +bool qxl_format_bpp(PCIQXLDevice *qxl, SpiceSurfaceFmt format, + uint32_t *bytes_pp, uint32_t *bits_pp) +{ + uint32_t bypp = 4; + uint32_t bipp = 32; + bool ret = true; + + switch (format) { + case SPICE_SURFACE_FMT_16_555: + bypp = 2; + bipp = 15; + break; + case SPICE_SURFACE_FMT_16_565: + bypp = 2; + bipp = 16; + break; + case SPICE_SURFACE_FMT_32_xRGB: + case SPICE_SURFACE_FMT_32_ARGB: + bypp = 4; + bipp = 32; + break; + default: + ret = false; + qxl_set_guest_bug(qxl, "%s: unhandled format: %x", __func__, format); + } + + if (bytes_pp != NULL) { + *bytes_pp = bypp; + } + if (bits_pp != NULL) { + *bits_pp = bipp; + } + + return ret; +} + static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm, qxl_async_io async) { @@ -1514,6 +1557,7 @@ QXLSurfaceCreate *sc = &qxl->guest_primary.surface; uint32_t requested_height = le32_to_cpu(sc->height); int requested_stride = le32_to_cpu(sc->stride); + uint32_t bytes_pp; if (requested_stride == INT32_MIN || abs(requested_stride) * (uint64_t)requested_height @@ -1550,6 +1594,23 @@ return; } + if (!qxl_format_bpp(qxl, surface.format, &bytes_pp, NULL)) { + return; + } + + if (surface.width == 0 || surface.height == 0) { + qxl_set_guest_bug(qxl, "%s: zero dimension %ux%u", + __func__, surface.width, surface.height); + return; + } + + if ((uint64_t)surface.width * bytes_pp > abs(surface.stride)) { + qxl_set_guest_bug(qxl, "%s: stride too small for width:" + " stride %d width %u bpp %u", + __func__, surface.stride, surface.width, bytes_pp); + return; + } + surface.mouse_mode = true; surface.group_id = MEMSLOT_GROUP_GUEST; if (loadvm) { diff -Nru qemu-10.0.13+ds/hw/display/qxl.h qemu-10.0.14+ds/hw/display/qxl.h --- qemu-10.0.13+ds/hw/display/qxl.h 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/qxl.h 2026-09-29 03:48:20.000000000 +0300 @@ -181,6 +181,8 @@ void qxl_spice_reset_memslots(PCIQXLDevice *qxl); void qxl_spice_reset_image_cache(PCIQXLDevice *qxl); void qxl_spice_reset_cursor(PCIQXLDevice *qxl); +bool qxl_format_bpp(PCIQXLDevice *qxl, SpiceSurfaceFmt format, + uint32_t *bytes_pp, uint32_t *bits_pp); /* qxl-logger.c */ int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id); diff -Nru qemu-10.0.13+ds/hw/display/vga.c qemu-10.0.14+ds/hw/display/vga.c --- qemu-10.0.13+ds/hw/display/vga.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/vga.c 2026-09-29 03:48:20.000000000 +0300 @@ -1239,7 +1239,10 @@ return; } - if (width != s->last_width || height != s->last_height || + if (surface == NULL || + surface_width(surface) != width * cw || + surface_height(surface) != height * cheight || + width != s->last_text_width || height != s->last_text_height || cw != s->last_cw || cheight != s->last_ch || s->last_depth) { s->last_scr_width = width * cw; s->last_scr_height = height * cheight; @@ -1247,8 +1250,8 @@ surface = qemu_console_surface(s->con); dpy_text_resize(s->con, width, height); s->last_depth = 0; - s->last_width = width; - s->last_height = height; + s->last_text_width = width; + s->last_text_height = height; s->last_ch = cheight; s->last_cw = cw; full_update = 1; @@ -1827,6 +1830,8 @@ s->last_width = -1; s->last_height = -1; + s->last_text_width = -1; + s->last_text_height = -1; } void vga_common_reset(VGACommonState *s) @@ -1869,6 +1874,8 @@ s->last_ch = 0; s->last_width = 0; s->last_height = 0; + s->last_text_width = 0; + s->last_text_height = 0; s->last_scr_width = 0; s->last_scr_height = 0; s->cursor_start = 0; @@ -1920,8 +1927,8 @@ s->graphic_mode = graphic_mode; full_update = 1; } - if (s->last_width == -1) { - s->last_width = 0; + if (s->last_text_width == -1) { + s->last_text_width = 0; full_update = 1; } @@ -1960,15 +1967,15 @@ break; } - if (width != s->last_width || height != s->last_height || + if (width != s->last_text_width || height != s->last_text_height || cw != s->last_cw || cheight != s->last_ch) { s->last_scr_width = width * cw; s->last_scr_height = height * cheight; qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height); dpy_text_resize(s->con, width, height); s->last_depth = 0; - s->last_width = width; - s->last_height = height; + s->last_text_width = width; + s->last_text_height = height; s->last_ch = cheight; s->last_cw = cw; full_update = 1; @@ -2053,22 +2060,22 @@ } /* Display a message */ - s->last_width = 60; - s->last_height = height = 3; + s->last_text_width = 60; + s->last_text_height = height = 3; dpy_text_cursor(s->con, -1, -1); - dpy_text_resize(s->con, s->last_width, height); + dpy_text_resize(s->con, s->last_text_width, height); - for (dst = chardata, i = 0; i < s->last_width * height; i ++) + for (dst = chardata, i = 0; i < s->last_text_width * height; i ++) console_write_ch(dst ++, ' '); size = strlen(msg_buffer); - width = (s->last_width - size) / 2; - dst = chardata + s->last_width + width; + width = (s->last_text_width - size) / 2; + dst = chardata + s->last_text_width + width; for (i = 0; i < size; i ++) console_write_ch(dst ++, ATTR2CHTYPE(msg_buffer[i], QEMU_COLOR_BLUE, QEMU_COLOR_BLACK, 1)); - dpy_text_update(s->con, 0, 0, s->last_width, height); + dpy_text_update(s->con, 0, 0, s->last_text_width, height); } static uint64_t vga_mem_read(void *opaque, hwaddr addr, diff -Nru qemu-10.0.13+ds/hw/display/vga_int.h qemu-10.0.14+ds/hw/display/vga_int.h --- qemu-10.0.13+ds/hw/display/vga_int.h 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/vga_int.h 2026-09-29 03:48:20.000000000 +0300 @@ -122,7 +122,8 @@ uint32_t plane_updated; uint32_t last_line_offset; uint8_t last_cw, last_ch; - uint32_t last_width, last_height; /* in chars or pixels */ + uint32_t last_width, last_height; /* in pixels (graphics renderer) */ + uint32_t last_text_width, last_text_height; /* in chars (text renderer) */ uint32_t last_scr_width, last_scr_height; /* in pixels */ uint32_t last_depth; /* in bits */ bool last_byteswap; diff -Nru qemu-10.0.13+ds/hw/display/virtio-gpu-rutabaga.c qemu-10.0.14+ds/hw/display/virtio-gpu-rutabaga.c --- qemu-10.0.13+ds/hw/display/virtio-gpu-rutabaga.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/virtio-gpu-rutabaga.c 2026-09-29 03:48:20.000000000 +0300 @@ -366,10 +366,20 @@ return; } - buf = g_new0(uint8_t, cs.size); + buf = g_try_new0(uint8_t, cs.size); + if (!buf && cs.size) { + cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; + return; + } s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); - CHECK(s == cs.size, cmd); + if (s != cs.size) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: size mismatch (%zu/%u)\n", + __func__, s, cs.size); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } rutabaga_cmd.ctx_id = cs.hdr.ctx_id; rutabaga_cmd.cmd = buf; diff -Nru qemu-10.0.13+ds/hw/display/virtio-gpu-udmabuf.c qemu-10.0.14+ds/hw/display/virtio-gpu-udmabuf.c --- qemu-10.0.13+ds/hw/display/virtio-gpu-udmabuf.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/virtio-gpu-udmabuf.c 2026-09-29 03:48:20.000000000 +0300 @@ -38,8 +38,11 @@ return; } - list = g_malloc0(sizeof(struct udmabuf_create_list) + - sizeof(struct udmabuf_create_item) * res->iov_cnt); + list = g_try_malloc0(sizeof(struct udmabuf_create_list) + + sizeof(struct udmabuf_create_item) * res->iov_cnt); + if (!list) { + return; + } for (i = 0; i < res->iov_cnt; i++) { rcu_read_lock(); @@ -88,6 +91,7 @@ close(res->dmabuf_fd); res->dmabuf_fd = -1; } + res->blob = NULL; } static int find_memory_backend_type(Object *obj, void *opaque) diff -Nru qemu-10.0.13+ds/hw/display/virtio-gpu-virgl.c qemu-10.0.14+ds/hw/display/virtio-gpu-virgl.c --- qemu-10.0.13+ds/hw/display/virtio-gpu-virgl.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/virtio-gpu-virgl.c 2026-09-29 03:48:20.000000000 +0300 @@ -499,7 +499,11 @@ return; } - buf = g_malloc(cs.size); + buf = g_try_malloc(cs.size); + if (!buf && cs.size) { + cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; + return; + } s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, sizeof(cs), buf, cs.size); if (s != cs.size) { diff -Nru qemu-10.0.13+ds/hw/display/virtio-gpu.c qemu-10.0.14+ds/hw/display/virtio-gpu.c --- qemu-10.0.13+ds/hw/display/virtio-gpu.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/display/virtio-gpu.c 2026-09-29 03:48:20.000000000 +0300 @@ -63,8 +63,16 @@ } data = pixman_image_get_data(res->image); } else { + if (!res->blob) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: resource %d has no blob\n", + __func__, resource_id); + return; + } if (res->blob_size < (s->current_cursor->width * s->current_cursor->height * 4)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: blob size too small for resource %d\n", + __func__, resource_id); return; } data = res->blob; @@ -888,7 +896,10 @@ } esize = sizeof(*ents) * nr_entries; - ents = g_malloc(esize); + ents = g_try_malloc(esize); + if (!ents && esize) { + return -1; + } s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num, offset, ents, esize); if (s != esize) { @@ -909,6 +920,7 @@ hwaddr len; void *map; + /* TODO: a common DMA map SG helper */ do { len = l; map = dma_memory_map(VIRTIO_DEVICE(g)->dma_as, a, &len, @@ -917,20 +929,27 @@ if (!map) { qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map MMIO memory for" " element %d\n", __func__, e); - virtio_gpu_cleanup_mapping_iov(g, *iov, v); - g_free(ents); - *iov = NULL; - if (addr) { - g_free(*addr); - *addr = NULL; - } - return -1; + goto err; } if (!(v % 16)) { - *iov = g_renew(struct iovec, *iov, v + 16); + struct iovec *new_iov; + new_iov = g_try_renew(struct iovec, *iov, v + 16); + if (!new_iov) { + dma_memory_unmap(VIRTIO_DEVICE(g)->dma_as, map, len, + DMA_DIRECTION_TO_DEVICE, len); + goto err; + } + *iov = new_iov; if (addr) { - *addr = g_renew(uint64_t, *addr, v + 16); + uint64_t *new_addr; + new_addr = g_try_renew(uint64_t, *addr, v + 16); + if (!new_addr) { + dma_memory_unmap(VIRTIO_DEVICE(g)->dma_as, map, len, + DMA_DIRECTION_TO_DEVICE, len); + goto err; + } + *addr = new_addr; } } (*iov)[v].iov_base = map; @@ -948,6 +967,15 @@ g_free(ents); return 0; + +err: + virtio_gpu_cleanup_mapping_iov(g, *iov, v); + *iov = NULL; + if (addr) { + g_clear_pointer(addr, g_free); + } + g_free(ents); + return -1; } void virtio_gpu_cleanup_mapping_iov(VirtIOGPU *g, @@ -967,6 +995,16 @@ void virtio_gpu_cleanup_mapping(VirtIOGPU *g, struct virtio_gpu_simple_resource *res) { + if (res->blob) { + int i, max_outputs = g->parent_obj.conf.max_outputs; + + for (i = 0; i < max_outputs; i++) { + if (g->parent_obj.scanout[i].resource_id == res->resource_id) { + virtio_gpu_disable_scanout(g, i); + } + } + } + virtio_gpu_cleanup_mapping_iov(g, res->iov, res->iov_cnt); res->iov = NULL; res->iov_cnt = 0; diff -Nru qemu-10.0.13+ds/hw/i386/vapic.c qemu-10.0.14+ds/hw/i386/vapic.c --- qemu-10.0.13+ds/hw/i386/vapic.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/i386/vapic.c 2026-09-29 03:48:20.000000000 +0300 @@ -30,6 +30,10 @@ #define ROM_BLOCK_SIZE 512 #define ROM_BLOCK_MASK (~(ROM_BLOCK_SIZE - 1)) +/* Option ROM window on PC/Q35 machines; the vapic ROM must live in here. */ +#define OPTION_ROM_START 0xc0000 +#define OPTION_ROM_END 0xe0000 + typedef enum VAPICMode { VAPIC_INACTIVE = 0, VAPIC_ACTIVE = 1, @@ -586,6 +590,14 @@ size_t rom_size; uint8_t *ram; + /* + * The VAPIC region should be mapped in place, refuse mapping it + * outside of the option ROM window. + */ + if (rom_paddr < OPTION_ROM_START || rom_paddr >= OPTION_ROM_END) { + return -1; + } + if (s->rom_mapped_writable) { memory_region_del_subregion(mr, &s->rom); object_unparent(OBJECT(&s->rom)); @@ -596,13 +608,16 @@ /* read ROM size from RAM region */ if (rom_paddr + 2 >= memory_region_size(section.mr)) { + memory_region_unref(section.mr); return -1; } ram = memory_region_get_ram_ptr(section.mr); rom_size = ram[rom_paddr + 2] * ROM_BLOCK_SIZE; - if (rom_size == 0) { + if (rom_size == 0 || rom_size > OPTION_ROM_END - rom_paddr) { + memory_region_unref(section.mr); return -1; } + s->rom_size = rom_size; /* We need to round to avoid creating subpages @@ -610,6 +625,7 @@ rom_size += rom_paddr & ~TARGET_PAGE_MASK; rom_paddr &= TARGET_PAGE_MASK; rom_size = TARGET_PAGE_ALIGN(rom_size); + assert(rom_paddr >= OPTION_ROM_START && rom_paddr + rom_size <= OPTION_ROM_END); memory_region_init_alias(&s->rom, OBJECT(s), "kvmvapic-rom", section.mr, rom_paddr, rom_size); diff -Nru qemu-10.0.13+ds/hw/ide/core.c qemu-10.0.14+ds/hw/ide/core.c --- qemu-10.0.13+ds/hw/ide/core.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/ide/core.c 2026-09-29 03:48:20.000000000 +0300 @@ -138,11 +138,12 @@ memset(p, 0, sizeof(s->identify_data)); put_le16(p + 0, 0x0040); + /* Words 1, 3 and 6 describe the default translation (ATA-5 8.16.8) */ put_le16(p + 1, s->cylinders); - put_le16(p + 3, s->heads); - put_le16(p + 4, 512 * s->sectors); /* XXX: retired, remove ? */ + put_le16(p + 3, s->drive_heads); + put_le16(p + 4, 512 * s->drive_sectors); /* XXX: retired, remove ? */ put_le16(p + 5, 512); /* XXX: retired, remove ? */ - put_le16(p + 6, s->sectors); + put_le16(p + 6, s->drive_sectors); padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */ put_le16(p + 20, 3); /* XXX: retired, remove ? */ put_le16(p + 21, 512); /* cache size in sectors */ @@ -332,8 +333,8 @@ put_le16(p + 0, 0x848a); /* CF Storage Card signature */ put_le16(p + 1, s->cylinders); /* Default cylinders */ - put_le16(p + 3, s->heads); /* Default heads */ - put_le16(p + 6, s->sectors); /* Default sectors per track */ + put_le16(p + 3, s->drive_heads); /* Default heads */ + put_le16(p + 6, s->drive_sectors); /* Default sectors per track */ /* *(p + 7) := nb_sectors >> 16 -- see ide_cfata_identify_size */ /* *(p + 8) := nb_sectors -- see ide_cfata_identify_size */ padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */ @@ -1655,14 +1656,21 @@ /* INITIALIZE DEVICE PARAMETERS */ static bool cmd_specify(IDEState *s, uint8_t cmd) { - if (s->blk && s->drive_kind != IDE_CD) { - s->heads = (s->select & (ATA_DEV_HS)) + 1; - s->sectors = s->nsector; - ide_bus_set_irq(s->bus); - } else { + if (!s->blk || s->drive_kind == IDE_CD) { + ide_abort_command(s); + return true; + } + + /* ATA-2 D.2.8 limits IDENTIFY DEVICE word 56, and the count, to 1..255 */ + if (s->nsector == 0 || s->nsector > 255) { ide_abort_command(s); + return true; } + s->heads = (s->select & (ATA_DEV_HS)) + 1; + s->sectors = s->nsector; + ide_bus_set_irq(s->bus); + return true; } diff -Nru qemu-10.0.13+ds/hw/input/ps2.c qemu-10.0.14+ds/hw/input/ps2.c --- qemu-10.0.13+ds/hw/input/ps2.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/input/ps2.c 2026-09-29 03:48:20.000000000 +0300 @@ -72,6 +72,7 @@ #define AUX_SET_DEFAULT 0xF6 #define AUX_RESET 0xFF /* Reset aux device */ #define AUX_ACK 0xFA /* Command byte ACK. */ +#define AUX_RESEND 0xFE /* Command NACK, send the cmd again */ #define MOUSE_STATUS_REMOTE 0x40 #define MOUSE_STATUS_ENABLED 0x20 @@ -961,6 +962,11 @@ s->mouse_type); break; default: + /* + * A PS/2 device answers every command it is given; an unknown + * one draws a resend. + */ + ps2_queue(ps2, AUX_RESEND); break; } break; diff -Nru qemu-10.0.13+ds/hw/intc/bcm2835_ic.c qemu-10.0.14+ds/hw/intc/bcm2835_ic.c --- qemu-10.0.13+ds/hw/intc/bcm2835_ic.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/intc/bcm2835_ic.c 2026-09-29 03:48:20.000000000 +0300 @@ -139,10 +139,19 @@ BCM2835ICState *s = opaque; switch (offset) { - case FIQ_CONTROL: - s->fiq_select = extract32(val, 0, 7); + case FIQ_CONTROL: { + unsigned fiq_select = extract32(val, 0, 7); + + if (fiq_select >= GPU_IRQS + ARM_IRQS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: FIQ select %u out of range\n", + __func__, fiq_select); + return; + } + s->fiq_select = fiq_select; s->fiq_enable = extract32(val, 7, 1); break; + } case IRQ_ENABLE_1: s->gpu_irq_enable |= val; break; diff -Nru qemu-10.0.13+ds/hw/m68k/q800.c qemu-10.0.14+ds/hw/m68k/q800.c --- qemu-10.0.13+ds/hw/m68k/q800.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/m68k/q800.c 2026-09-29 03:48:20.000000000 +0300 @@ -715,12 +715,14 @@ static GlobalProperty hw_compat_q800[] = { { "scsi-hd", "quirk_mode_page_vendor_specific_apple", "on" }, + { "scsi-hd", "quirk_mode_page_set_block_size", "on" }, { "scsi-hd", "vendor", " SEAGATE" }, { "scsi-hd", "product", " ST225N" }, { "scsi-hd", "ver", "1.0 " }, { "scsi-cd", "quirk_mode_page_apple_vendor", "on" }, { "scsi-cd", "quirk_mode_sense_rom_use_dbd", "on" }, { "scsi-cd", "quirk_mode_page_vendor_specific_apple", "on" }, + { "scsi-cd", "quirk_mode_page_set_block_size", "on" }, { "scsi-cd", "quirk_mode_page_truncated", "on" }, { "scsi-cd", "vendor", "MATSHITA" }, { "scsi-cd", "product", "CD-ROM CR-8005" }, diff -Nru qemu-10.0.13+ds/hw/mem/cxl_type3.c qemu-10.0.14+ds/hw/mem/cxl_type3.c --- qemu-10.0.13+ds/hw/mem/cxl_type3.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/mem/cxl_type3.c 2026-09-29 03:48:20.000000000 +0300 @@ -903,8 +903,8 @@ } /* DOE Initialization */ - pcie_doe_init(pci_dev, &ct3d->doe_cdat, 0x190, doe_cdat_prot, true, - CXL_T3_MSIX_PCIE_DOE_TABLE_ACCESS); + pcie_doe_init(pci_dev, &ct3d->doe_cdat, cxl_cstate->dvsec_offset, + doe_cdat_prot, true, CXL_T3_MSIX_PCIE_DOE_TABLE_ACCESS); cxl_cstate->cdat.build_cdat_table = ct3_build_cdat_table; cxl_cstate->cdat.free_cdat_table = ct3_free_cdat_table; @@ -1093,7 +1093,7 @@ } if (((uint64_t)host_addr < decoder_base) || (hpa_offset >= decoder_size)) { - int decoded_iw = cxl_interleave_ways_dec(iw, &error_fatal); + int decoded_iw = cxl_interleave_ways_dec(iw, NULL); if (decoded_iw == 0) { return false; diff -Nru qemu-10.0.13+ds/hw/net/virtio-net.c qemu-10.0.14+ds/hw/net/virtio-net.c --- qemu-10.0.13+ds/hw/net/virtio-net.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/net/virtio-net.c 2026-09-29 03:48:20.000000000 +0300 @@ -1756,12 +1756,16 @@ } ptr += n->host_hdr_len; + size -= n->host_hdr_len; + + if (size < sizeof(struct eth_header)) { + return 0; + } if (!memcmp(&ptr[12], vlan, sizeof(vlan))) { int vid; - /* Truncated vlan packet */ - if (size < n->host_hdr_len + 16) { + if (size < 16) { return 0; } vid = lduw_be_p(ptr + 14) & 0xfff; @@ -2198,13 +2202,31 @@ { uint16_t hdr_len; VirtioNetRscSeg *seg; + size_t ip_size; hdr_len = chain->n->guest_hdr_len; + + /* + * Strip any trailing padding beyond the IP payload so that seg->size + * stays in sync with the IP length field used by the bounds check in + * virtio_net_rsc_coalesce_data(). virtio_net_rsc_sanity_check4/6() + * guarantees that ip_size <= size. + */ + ip_size = hdr_len + sizeof(struct eth_header); + if (chain->proto == ETH_P_IP) { + struct ip_header *ip = (struct ip_header *)(buf + ip_size); + ip_size += htons(ip->ip_len); + } else { + struct ip6_header *ip6 = (struct ip6_header *)(buf + ip_size); + ip_size += sizeof(struct ip6_header) + + htons(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen); + } + seg = g_new(VirtioNetRscSeg, 1); seg->buf = g_malloc(hdr_len + sizeof(struct eth_header) + sizeof(struct ip6_header) + VIRTIO_NET_MAX_TCP_PAYLOAD); - memcpy(seg->buf, buf, size); - seg->size = size; + memcpy(seg->buf, buf, ip_size); + seg->size = ip_size; seg->packets = 1; seg->dup_ack = 0; seg->is_coalesced = 0; diff -Nru qemu-10.0.13+ds/hw/riscv/virt.c qemu-10.0.14+ds/hw/riscv/virt.c --- qemu-10.0.13+ds/hw/riscv/virt.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/riscv/virt.c 2026-09-29 03:48:20.000000000 +0300 @@ -405,7 +405,8 @@ (memmap[VIRT_CLINT].size * socket); size = memmap[VIRT_CLINT].size - RISCV_ACLINT_SWI_SIZE; } - name = g_strdup_printf("/soc/mtimer@%lx", addr); + name = g_strdup_printf("/soc/mtimer@%lx", + addr + RISCV_ACLINT_DEFAULT_MTIME); qemu_fdt_add_subnode(ms->fdt, name); qemu_fdt_setprop_string(ms->fdt, name, "compatible", "riscv,aclint-mtimer"); diff -Nru qemu-10.0.13+ds/hw/s390x/s390-pci-inst.c qemu-10.0.14+ds/hw/s390x/s390-pci-inst.c --- qemu-10.0.13+ds/hw/s390x/s390-pci-inst.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/s390x/s390-pci-inst.c 2026-09-29 03:48:20.000000000 +0300 @@ -653,6 +653,7 @@ goto out; } else { if (cache) { + /* valid->valid transitions reuse the DMA slot */ if (cache->perm == entry->perm && cache->translated_addr == entry->translated_addr) { goto out; @@ -663,6 +664,9 @@ memory_region_notify_iommu(&iommu->iommu_mr, 0, event); event.type = IOMMU_NOTIFIER_MAP; event.entry.perm = entry->perm; + } else { + /* invalid->valid transitions consume a new DMA slot */ + dec_dma_avail(iommu); } cache = g_new(S390IOTLBEntry, 1); @@ -671,7 +675,6 @@ cache->len = TARGET_PAGE_SIZE; cache->perm = entry->perm; g_hash_table_replace(iommu->iotlb, &cache->iova, cache); - dec_dma_avail(iommu); } /* diff -Nru qemu-10.0.13+ds/hw/s390x/sclp.c qemu-10.0.14+ds/hw/s390x/sclp.c --- qemu-10.0.13+ds/hw/s390x/sclp.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/s390x/sclp.c 2026-09-29 03:48:20.000000000 +0300 @@ -291,7 +291,7 @@ sclp_c->execute(sclp, work_sccb, code); out_write: s390_cpu_pv_mem_write(env_archcpu(env), 0, work_sccb, - be16_to_cpu(work_sccb->h.length)); + be16_to_cpu(header.length)); sclp_c->service_interrupt(sclp, SCLP_PV_DUMMY_ADDR); return 0; } diff -Nru qemu-10.0.13+ds/hw/scsi/scsi-disk.c qemu-10.0.14+ds/hw/scsi/scsi-disk.c --- qemu-10.0.13+ds/hw/scsi/scsi-disk.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/scsi/scsi-disk.c 2026-09-29 03:48:20.000000000 +0300 @@ -1695,8 +1695,12 @@ goto invalid_param; } - /* Allow changing the block size */ - if (bd_len) { + /* + * Allow changing the block size only if the quirk is enabled for it. + * Writing s->qdev.blocksize is not thread safe! + */ + if (bd_len && (s->quirks & + (1 << SCSI_DISK_QUIRK_MODE_PAGE_SET_BLOCK_SIZE))) { bs = p[5] << 16 | p[6] << 8 | p[7]; /* @@ -1933,6 +1937,7 @@ SCSIRequest *req = &r->req; SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); uint32_t nb_sectors = scsi_data_cdb_xfer(r->req.cmd.buf); + uint32_t buflen = MIN(s->qdev.blocksize, r->buflen); WriteSameCBData *data; uint8_t *buf; int i, l; @@ -1952,7 +1957,7 @@ return; } - if ((req->cmd.buf[1] & 0x1) || buffer_is_zero(inbuf, s->qdev.blocksize)) { + if ((req->cmd.buf[1] & 0x1) || buffer_is_zero(inbuf, buflen)) { int flags = (req->cmd.buf[1] & 0x8) ? BDRV_REQ_MAY_UNMAP : 0; /* The request is used as the AIO opaque value, so add a ref. */ @@ -1978,7 +1983,7 @@ qemu_iovec_init_external(&data->qiov, &data->iov, 1); for (i = 0; i < data->iov.iov_len; i += l) { - l = MIN(s->qdev.blocksize, data->iov.iov_len - i); + l = MIN(buflen, data->iov.iov_len - i); memcpy(&buf[i], inbuf, l); } @@ -3234,6 +3239,8 @@ DEFINE_PROP_BIT("quirk_mode_page_vendor_specific_apple", SCSIDiskState, quirks, SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE, 0), + DEFINE_PROP_BIT("quirk_mode_page_set_block_size", SCSIDiskState, + quirks, SCSI_DISK_QUIRK_MODE_PAGE_SET_BLOCK_SIZE, 0), DEFINE_BLOCK_CHS_PROPERTIES(SCSIDiskState, qdev.conf), }; @@ -3292,6 +3299,8 @@ 0), DEFINE_PROP_BIT("quirk_mode_page_truncated", SCSIDiskState, quirks, SCSI_DISK_QUIRK_MODE_PAGE_TRUNCATED, 0), + DEFINE_PROP_BIT("quirk_mode_page_set_block_size", SCSIDiskState, + quirks, SCSI_DISK_QUIRK_MODE_PAGE_SET_BLOCK_SIZE, 0), }; static void scsi_cd_class_initfn(ObjectClass *klass, void *data) diff -Nru qemu-10.0.13+ds/hw/scsi/virtio-scsi-dataplane.c qemu-10.0.14+ds/hw/scsi/virtio-scsi-dataplane.c --- qemu-10.0.13+ds/hw/scsi/virtio-scsi-dataplane.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/scsi/virtio-scsi-dataplane.c 2026-09-29 03:48:20.000000000 +0300 @@ -230,7 +230,7 @@ fail_guest_notifiers: s->dataplane_fenced = true; s->dataplane_starting = false; - s->dataplane_started = true; + s->dataplane_started = false; return -ENOSYS; } diff -Nru qemu-10.0.13+ds/hw/sd/sdhci.c qemu-10.0.14+ds/hw/sd/sdhci.c --- qemu-10.0.13+ds/hw/sd/sdhci.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/sd/sdhci.c 2026-09-29 03:48:20.000000000 +0300 @@ -1344,11 +1344,24 @@ } sdhci_update_irq(s); break; - case SDHC_ACMD12ERRSTS: + case SDHC_ACMD12ERRSTS: { + uint16_t hostctl2_mask = mask >> 16; + uint16_t hostctl2_value = value >> 16; + MASKED_WRITE(s->acmd12errsts, mask, value & UINT16_MAX); - if (s->uhs_mode >= UHS_I) { - MASKED_WRITE(s->hostctl2, mask >> 16, value >> 16); + if (s->uhs_mode < UHS_I) { + /* + * VERSION4 is writable even without UHS-I. Preserve all other + * Host Control 2 bits when UHS-I is not supported. + */ + uint16_t independent = R_SDHC_HOSTCTL2_VERSION4_MASK; + hostctl2_mask |= ~independent; + hostctl2_value &= independent; + } + MASKED_WRITE(s->hostctl2, hostctl2_mask, hostctl2_value); + + if (s->uhs_mode >= UHS_I) { if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, V18_ENA)) { sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_1_8V); } else { @@ -1356,6 +1369,7 @@ } } break; + } case SDHC_CAPAB: case SDHC_CAPAB + 4: diff -Nru qemu-10.0.13+ds/hw/uefi/var-service-vars.c qemu-10.0.14+ds/hw/uefi/var-service-vars.c --- qemu-10.0.13+ds/hw/uefi/var-service-vars.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/uefi/var-service-vars.c 2026-09-29 03:48:20.000000000 +0300 @@ -652,6 +652,10 @@ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE); } + if (!uefi_str_is_valid(name, lv->name_size, true)) { + return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER); + } + uefi_trace_variable(__func__, lv->guid, name, lv->name_size); pe = g_malloc0(sizeof(*pe) + lv->name_size); diff -Nru qemu-10.0.13+ds/hw/usb/hcd-xhci.c qemu-10.0.14+ds/hw/usb/hcd-xhci.c --- qemu-10.0.13+ds/hw/usb/hcd-xhci.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/usb/hcd-xhci.c 2026-09-29 03:48:20.000000000 +0300 @@ -458,9 +458,15 @@ { XHCIState *xhci = opaque; XHCIEvent wrap = { ER_MFINDEX_WRAP, CC_SUCCESS }; + MemReentrancyGuard *guard = &xhci->parent.mem_reentrancy_guard; + + assert(!guard->engaged_in_io); + guard->engaged_in_io = true; xhci_event(xhci, &wrap, 0); xhci_mfwrap_update(xhci); + + guard->engaged_in_io = false; } static void xhci_die(XHCIState *xhci) @@ -1087,7 +1093,14 @@ static void xhci_ep_kick_timer(void *opaque) { XHCIEPContext *epctx = opaque; + MemReentrancyGuard *guard = &epctx->xhci->parent.mem_reentrancy_guard; + + assert(!guard->engaged_in_io); + guard->engaged_in_io = true; + xhci_kick_epctx(epctx, 0); + + guard->engaged_in_io = false; } static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci, @@ -1744,8 +1757,7 @@ static void xhci_calc_intr_kick(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx, uint64_t mfindex) { - uint64_t asap = ((mfindex + epctx->interval - 1) & - ~(epctx->interval-1)); + uint64_t asap = ROUND_UP(mfindex, epctx->interval); uint64_t kick = epctx->mfindex_last + epctx->interval; assert(epctx->interval != 0); @@ -1756,8 +1768,7 @@ XHCIEPContext *epctx, uint64_t mfindex) { if (xfer->trbs[0].control & TRB_TR_SIA) { - uint64_t asap = ((mfindex + epctx->interval - 1) & - ~(epctx->interval-1)); + uint64_t asap = ROUND_UP(mfindex, epctx->interval); if (asap >= epctx->mfindex_last && asap <= epctx->mfindex_last + epctx->interval * 4) { xfer->mfindex_kick = epctx->mfindex_last + epctx->interval; @@ -1916,26 +1927,15 @@ xfer->timed_xfer = 0; xfer->running_retry = 1; } - if (xfer->iso_xfer) { - /* retry iso transfer */ - if (xhci_setup_packet(xfer) < 0) { - return; - } - usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); - assert(xfer->packet.status != USB_RET_NAK); - xhci_try_complete_packet(xfer); - } else { - /* retry nak'ed transfer */ - if (xhci_setup_packet(xfer) < 0) { - return; - } - usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); - if (xfer->packet.status == USB_RET_NAK) { - xhci_xfer_unmap(xfer); - return; - } - xhci_try_complete_packet(xfer); + if (xhci_setup_packet(xfer) < 0) { + return; + } + usb_handle_packet(xfer->packet.ep->dev, &xfer->packet); + if (xfer->packet.status == USB_RET_NAK) { + xhci_xfer_unmap(xfer); + return; } + xhci_try_complete_packet(xfer); assert(!xfer->running_retry); if (xfer->complete) { /* update ring dequeue ptr */ diff -Nru qemu-10.0.13+ds/hw/virtio/virtio-balloon.c qemu-10.0.14+ds/hw/virtio/virtio-balloon.c --- qemu-10.0.13+ds/hw/virtio/virtio-balloon.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/virtio/virtio-balloon.c 2026-09-29 03:48:20.000000000 +0300 @@ -34,6 +34,7 @@ #include "system/reset.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-access.h" +#include "block/aio-wait.h" #define BALLOON_PAGE_SIZE (1 << VIRTIO_BALLOON_PFN_SHIFT) @@ -518,6 +519,9 @@ int i; while (dev->block_iothread) { + if (dev->free_page_hint_status == FREE_PAGE_HINT_S_UNREALIZE) { + return false; + } qemu_cond_wait(&dev->free_page_cond, &dev->free_page_lock); } @@ -914,6 +918,11 @@ qemu_register_resettable(OBJECT(dev)); } +static void dummy_bh(void *opaque) +{ + /* Do nothing */ +} + static void virtio_balloon_device_unrealize(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); @@ -921,9 +930,17 @@ qemu_unregister_resettable(OBJECT(dev)); if (s->free_page_bh) { + AioContext *ctx = iothread_get_aio_context(s->iothread); + qemu_bh_delete(s->free_page_bh); + + qemu_mutex_lock(&s->free_page_lock); + s->free_page_hint_status = FREE_PAGE_HINT_S_UNREALIZE; + qemu_cond_signal(&s->free_page_cond); + qemu_mutex_unlock(&s->free_page_lock); + aio_wait_bh_oneshot(ctx, dummy_bh, NULL); + object_unref(OBJECT(s->iothread)); - virtio_balloon_free_page_stop(s); precopy_remove_notifier(&s->free_page_hint_notify); } balloon_stats_destroy_timer(s); diff -Nru qemu-10.0.13+ds/hw/virtio/virtio-iommu.c qemu-10.0.14+ds/hw/virtio/virtio-iommu.c --- qemu-10.0.13+ds/hw/virtio/virtio-iommu.c 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/hw/virtio/virtio-iommu.c 2026-09-29 03:48:20.000000000 +0300 @@ -213,6 +213,10 @@ { uint64_t delta = virt_end - virt_start; + if (virt_end < virt_start) { + return; + } + event->entry.iova = virt_start; event->entry.addr_mask = delta; @@ -808,6 +812,10 @@ return VIRTIO_IOMMU_S_INVAL; } + if (virt_end < virt_start) { + return VIRTIO_IOMMU_S_INVAL; + } + domain = g_tree_lookup(s->domains, GUINT_TO_POINTER(domain_id)); if (!domain) { return VIRTIO_IOMMU_S_NOENT; @@ -858,6 +866,10 @@ trace_virtio_iommu_unmap(domain_id, virt_start, virt_end); + if (virt_end < virt_start) { + return VIRTIO_IOMMU_S_INVAL; + } + domain = g_tree_lookup(s->domains, GUINT_TO_POINTER(domain_id)); if (!domain) { return VIRTIO_IOMMU_S_NOENT; @@ -880,7 +892,10 @@ virtio_iommu_notify_unmap(ep->iommu_mr, current_low, current_high); } - g_tree_remove(domain->mappings, iter_key); + if (!g_tree_remove(domain->mappings, iter_key)) { + ret = VIRTIO_IOMMU_S_DEVERR; + break; + } trace_virtio_iommu_unmap_done(domain_id, current_low, current_high); } else { ret = VIRTIO_IOMMU_S_RANGE; diff -Nru qemu-10.0.13+ds/include/exec/translation-block.h qemu-10.0.14+ds/include/exec/translation-block.h --- qemu-10.0.13+ds/include/exec/translation-block.h 2026-08-26 22:31:25.000000000 +0300 +++ qemu-10.0.14+ds/include/exec/translation-block.h 2026-09-29 03:48:20.000000000 +0300 @@ -80,6 +80,7 @@ #define CF_NOIRQ 0x00010000 /* Generate an uninterruptible TB */ #define CF_PCREL 0x00020000 /* Opcodes in TB are PC-relative */ #define CF_BP_PAGE 0x00040000 /* Breakpoint present in code page */ +#define CF_STEP_ATOMIC 0x00080000 /* Running in cpu_exec_step_atomic */ #define CF_CLUSTER_MASK 0xff000000 /* Top 8 bits are cluster ID */ #define CF_CLUSTER_SHIFT 24 diff -Nru qemu-10.0.13+ds/include/hw/scsi/scsi.h qemu-10.0.14+ds/include/hw/scsi/scsi.h --- qemu-10.0.13+ds/include/hw/scsi/scsi.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/include/hw/scsi/scsi.h 2026-09-29 03:48:20.000000000 +0300 @@ -250,5 +250,6 @@ #define SCSI_DISK_QUIRK_MODE_SENSE_ROM_USE_DBD 1 #define SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE 2 #define SCSI_DISK_QUIRK_MODE_PAGE_TRUNCATED 3 +#define SCSI_DISK_QUIRK_MODE_PAGE_SET_BLOCK_SIZE 4 #endif diff -Nru qemu-10.0.13+ds/include/hw/virtio/virtio-balloon.h qemu-10.0.14+ds/include/hw/virtio/virtio-balloon.h --- qemu-10.0.13+ds/include/hw/virtio/virtio-balloon.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/include/hw/virtio/virtio-balloon.h 2026-09-29 03:48:20.000000000 +0300 @@ -39,6 +39,7 @@ FREE_PAGE_HINT_S_REQUESTED = 1, FREE_PAGE_HINT_S_START = 2, FREE_PAGE_HINT_S_DONE = 3, + FREE_PAGE_HINT_S_UNREALIZE = 4, }; struct VirtIOBalloon { diff -Nru qemu-10.0.13+ds/include/ui/console.h qemu-10.0.14+ds/include/ui/console.h --- qemu-10.0.13+ds/include/ui/console.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/include/ui/console.h 2026-09-29 03:48:20.000000000 +0300 @@ -164,6 +164,15 @@ } QEMUCursor; QEMUCursor *cursor_alloc(uint16_t width, uint16_t height); + +/* + * A cursor may be shared between the main loop, a vCPU thread and a + * display backend's own thread, so the refcount is atomic and these two + * may be called from any of them. The object itself is not otherwise + * thread-safe: take a reference before publishing the pointer anywhere + * another thread can reach it, and never dereference a cursor you do + * not hold a reference to. + */ QEMUCursor *cursor_ref(QEMUCursor *c); void cursor_unref(QEMUCursor *c); QEMUCursor *cursor_builtin_hidden(void); diff -Nru qemu-10.0.13+ds/io/channel-socket.c qemu-10.0.14+ds/io/channel-socket.c --- qemu-10.0.13+ds/io/channel-socket.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/io/channel-socket.c 2026-09-29 03:48:20.000000000 +0300 @@ -604,7 +604,7 @@ retry: ret = sendmsg(sioc->fd, &msg, sflags); - if (ret <= 0) { + if (ret < 0) { switch (errno) { case EAGAIN: return QIO_CHANNEL_ERR_BLOCK; diff -Nru qemu-10.0.13+ds/io/channel-websock.c qemu-10.0.14+ds/io/channel-websock.c --- qemu-10.0.13+ds/io/channel-websock.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/io/channel-websock.c 2026-09-29 03:48:20.000000000 +0300 @@ -230,7 +230,7 @@ tmp = strchr(buffer, ' '); if (!tmp) { error_setg(errp, "Missing HTTP path delimiter"); - return 0; + goto bad_request; } *tmp = '\0'; @@ -492,6 +492,9 @@ buffer_reserve(&ioc->encinput, want); ret = qio_channel_read(ioc->master, (char *)buffer_end(&ioc->encinput), want, errp); + if (ret == QIO_CHANNEL_ERR_BLOCK) { + return 0; + } if (ret < 0) { return -1; } @@ -562,6 +565,11 @@ wioc->encoutput.offset, &err); + if (ret == QIO_CHANNEL_ERR_BLOCK) { + /* Socket buffer is full, the G_IO_OUT watch stays armed */ + return TRUE; + } + if (ret < 0) { trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); diff -Nru qemu-10.0.13+ds/job.c qemu-10.0.14+ds/job.c --- qemu-10.0.13+ds/job.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/job.c 2026-09-29 03:48:20.000000000 +0300 @@ -629,7 +629,14 @@ ? JOB_STATUS_STANDBY : JOB_STATUS_PAUSED); job->paused = true; - job_do_yield_locked(job, -1); + /* + * Stay paused across back-to-back pause requests: a transient + * paused == false while pause_count > 0 would be observed as + * "not paused" by job_set_aio_context() and other drain consumers. + */ + do { + job_do_yield_locked(job, -1); + } while (job_should_pause_locked(job) && !job_is_cancelled_locked(job)); job->paused = false; job_state_transition_locked(job, status); } diff -Nru qemu-10.0.13+ds/linux-user/include/host/loongarch64/host-signal.h qemu-10.0.14+ds/linux-user/include/host/loongarch64/host-signal.h --- qemu-10.0.13+ds/linux-user/include/host/loongarch64/host-signal.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/linux-user/include/host/loongarch64/host-signal.h 2026-09-29 03:48:20.000000000 +0300 @@ -61,6 +61,10 @@ return true; } break; + case 0b001011: /* v{ld,st}, xv{ld,st} */ + return (insn >> 22) & 1; + case 0b001100: /* v{ldrepl,stelm}, xv{ldrepl,stelm} */ + return (insn >> 24) & 1; case 0b001110: /* indexed, atomic, bounds-checking memory operations */ switch ((insn >> 15) & 0b11111111111) { case 0b00000100000: /* stx.b */ @@ -69,6 +73,8 @@ case 0b00000111000: /* stx.d */ case 0b00001110000: /* fstx.s */ case 0b00001111000: /* fstx.d */ + case 0b00010001000: /* vstx */ + case 0b00010011000: /* xvstx */ case 0b00011101100: /* fstgt.s */ case 0b00011101101: /* fstgt.d */ case 0b00011101110: /* fstle.s */ diff -Nru qemu-10.0.13+ds/linux-user/syscall.c qemu-10.0.14+ds/linux-user/syscall.c --- qemu-10.0.13+ds/linux-user/syscall.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/linux-user/syscall.c 2026-09-29 03:48:20.000000000 +0300 @@ -10941,6 +10941,15 @@ case TARGET_NR_mlock: return get_errno(mlock(g2h(cpu, arg1), arg2)); #endif +#ifdef TARGET_NR_mlock2 + case TARGET_NR_mlock2: + if (arg3 & ~TARGET_MLOCK_ONFAULT) { + return -TARGET_EINVAL; + } + return get_errno(mlock2(g2h(cpu, arg1), arg2, + (arg3 & TARGET_MLOCK_ONFAULT) ? + MLOCK_ONFAULT : 0)); +#endif #ifdef TARGET_NR_munlock case TARGET_NR_munlock: return get_errno(munlock(g2h(cpu, arg1), arg2)); diff -Nru qemu-10.0.13+ds/linux-user/syscall_defs.h qemu-10.0.14+ds/linux-user/syscall_defs.h --- qemu-10.0.13+ds/linux-user/syscall_defs.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/linux-user/syscall_defs.h 2026-09-29 03:48:20.000000000 +0300 @@ -2792,6 +2792,10 @@ #ifndef RESOLVE_IN_ROOT #define RESOLVE_IN_ROOT 0x10 #endif + +/* flags for mlock2() */ +#define TARGET_MLOCK_ONFAULT 0x01 + #if (defined(TARGET_I386) && defined(TARGET_ABI32)) || \ (defined(TARGET_ARM) && defined(TARGET_ABI32)) || \ defined(TARGET_M68K) || defined(TARGET_MICROBLAZE) || \ diff -Nru qemu-10.0.13+ds/migration/block-dirty-bitmap.c qemu-10.0.14+ds/migration/block-dirty-bitmap.c --- qemu-10.0.13+ds/migration/block-dirty-bitmap.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/migration/block-dirty-bitmap.c 2026-09-29 03:48:20.000000000 +0300 @@ -808,13 +808,6 @@ error_report("Bitmap with the same name ('%s') already exists on " "destination", bdrv_dirty_bitmap_name(s->bitmap)); return -EINVAL; - } else { - s->bitmap = bdrv_create_dirty_bitmap(s->bs, granularity, - s->bitmap_name, &local_err); - if (!s->bitmap) { - error_report_err(local_err); - return -EINVAL; - } } if (flags & DIRTY_BITMAP_MIG_START_FLAG_RESERVED_MASK) { @@ -831,6 +824,21 @@ persistent = flags & DIRTY_BITMAP_MIG_START_FLAG_PERSISTENT; } + /* Not bdrv_is_writable(): nodes stay inactive until migration ends. */ + if (persistent && bdrv_is_read_only(s->bs)) { + error_report("Cannot make migrated bitmap '%s' persistent " + "on read-only node '%s'", s->bitmap_name, + bdrv_get_node_name(s->bs)); + return -EINVAL; + } + + s->bitmap = bdrv_create_dirty_bitmap(s->bs, granularity, + s->bitmap_name, &local_err); + if (!s->bitmap) { + error_report_err(local_err); + return -EINVAL; + } + if (persistent) { bdrv_dirty_bitmap_set_persistence(s->bitmap, true); } diff -Nru qemu-10.0.13+ds/qapi/block-core.json qemu-10.0.14+ds/qapi/block-core.json --- qemu-10.0.13+ds/qapi/block-core.json 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/qapi/block-core.json 2026-09-29 03:48:20.000000000 +0300 @@ -2263,7 +2263,9 @@ # @persistent: the bitmap is persistent, i.e. it will be saved to the # corresponding block device image file on its close. For now # only Qcow2 disks support persistent bitmaps. Default is false -# for block-dirty-bitmap-add. (Since: 2.10) +# for block-dirty-bitmap-add. This fails if the node is +# read-only or inactive, since such a bitmap could never be +# stored. (Since: 2.10) # # @disabled: the bitmap is created in the disabled state, which means # that it will not track drive changes. The bitmap may be enabled diff -Nru qemu-10.0.13+ds/qapi/misc.json qemu-10.0.14+ds/qapi/misc.json --- qemu-10.0.13+ds/qapi/misc.json 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/qapi/misc.json 2026-09-29 03:48:20.000000000 +0300 @@ -117,11 +117,19 @@ # <- { "return": [ # { # "id":"iothread0", -# "thread-id":3134 +# "thread-id":3134, +# "poll-max-ns":32768, +# "poll-grow":0, +# "poll-shrink":0, +# "aio-max-batch":0 # }, # { # "id":"iothread1", -# "thread-id":3135 +# "thread-id":3135, +# "poll-max-ns":32768, +# "poll-grow":0, +# "poll-shrink":0, +# "aio-max-batch":0 # } # ] # } diff -Nru qemu-10.0.13+ds/qga/commands-posix-ssh.c qemu-10.0.14+ds/qga/commands-posix-ssh.c --- qemu-10.0.13+ds/qga/commands-posix-ssh.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/qga/commands-posix-ssh.c 2026-09-29 03:48:20.000000000 +0300 @@ -8,11 +8,34 @@ #include <glib/gstdio.h> #include <locale.h> #include <pwd.h> +#include <grp.h> #include "commands-common-ssh.h" #include "qapi/error.h" #include "qga-qapi-commands.h" +typedef struct EffectiveUserInfo { + uid_t uid; + gid_t gid; +} EffectiveUserInfo; + +typedef EffectiveUserInfo *PEffectiveUserInfo; + +static void rollback_effective_info(PEffectiveUserInfo info) +{ + if (info) { + /* There is nothing to do in case when rollback to original user/group IDs + * fails. In that case, the process will be terminated by the kernel + * and systemd should restart the daemon again. + */ + assert(seteuid(info->uid) == 0); + assert(setegid(info->gid) == 0); + g_free(info); + } +} + +G_DEFINE_AUTO_CLEANUP_FREE_FUNC(PEffectiveUserInfo, rollback_effective_info, NULL); + #ifdef QGA_BUILD_UNIT_TEST static struct passwd * test_get_passwd_entry(const gchar *user_name, GError **error) @@ -109,6 +132,36 @@ return true; } +static PEffectiveUserInfo set_privileges_to_user(const struct passwd *p, Error **errp) +{ + g_auto(PEffectiveUserInfo) info = g_new0(EffectiveUserInfo, 1); + + info->uid = geteuid(); + info->gid = getegid(); + +#ifndef QGA_BUILD_UNIT_TEST + /* The initgroups requires CAP_SETGID. During build time unit tests, we can't do this. */ + if (initgroups(p->pw_name, p->pw_gid) == -1) { + error_setg_errno(errp, errno, "failed to set group for user '%s'", + p->pw_name); + return NULL; + } +#endif + + if (setegid(p->pw_gid) == -1) { + error_setg_errno(errp, errno, "failed to set effective group ID for user '%s'", + p->pw_name); + return NULL; + } + if (seteuid(p->pw_uid) == -1) { + error_setg_errno(errp, errno, "failed to set effective user ID for user '%s'", + p->pw_name); + return NULL; + } + + return g_steal_pointer(&info); +} + void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys, bool has_reset, bool reset, @@ -120,6 +173,7 @@ g_auto(GStrv) authkeys = NULL; strList *k; size_t nkeys, nauthkeys; + g_auto(PEffectiveUserInfo) effective_user_info = NULL; reset = has_reset && reset; @@ -132,6 +186,11 @@ return; } + effective_user_info = set_privileges_to_user(p, errp); + if (effective_user_info == NULL) { + return; + } + ssh_path = g_build_filename(p->pw_dir, ".ssh", NULL); authkeys_path = g_build_filename(ssh_path, "authorized_keys", NULL); @@ -169,6 +228,7 @@ g_auto(GStrv) authkeys = NULL; GStrv a; size_t nkeys = 0; + g_auto(PEffectiveUserInfo) effective_user_info = NULL; if (!check_openssh_pub_keys(keys, NULL, errp)) { return; @@ -179,6 +239,11 @@ return; } + effective_user_info = set_privileges_to_user(p, errp); + if (effective_user_info == NULL) { + return; + } + authkeys_path = g_build_filename(p->pw_dir, ".ssh", "authorized_keys", NULL); if (!g_file_test(authkeys_path, G_FILE_TEST_EXISTS)) { @@ -216,12 +281,18 @@ g_auto(GStrv) authkeys = NULL; g_autoptr(GuestAuthorizedKeys) ret = NULL; int i; + g_auto(PEffectiveUserInfo) effective_user_info = NULL; p = get_passwd_entry(username, errp); if (p == NULL) { return NULL; } + effective_user_info = set_privileges_to_user(p, errp); + if (effective_user_info == NULL) { + return NULL; + } + authkeys_path = g_build_filename(p->pw_dir, ".ssh", "authorized_keys", NULL); authkeys = read_authkeys(authkeys_path, errp); diff -Nru qemu-10.0.13+ds/target/arm/tcg/cpu32.c qemu-10.0.14+ds/target/arm/tcg/cpu32.c --- qemu-10.0.13+ds/target/arm/tcg/cpu32.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/arm/tcg/cpu32.c 2026-09-29 03:48:20.000000000 +0300 @@ -134,7 +134,7 @@ cpu->midr = 0x41069265; cpu->reset_fpsid = 0x41011090; cpu->ctr = 0x1dd20d2; - cpu->reset_sctlr = 0x00090078; + cpu->reset_sctlr = 0x00050078; /* * ARMv5 does not have the ID_ISAR registers, but we can still @@ -175,7 +175,7 @@ cpu->midr = 0x4106a262; cpu->reset_fpsid = 0x410110a0; cpu->ctr = 0x1dd20d2; - cpu->reset_sctlr = 0x00090078; + cpu->reset_sctlr = 0x00050078; cpu->reset_auxcr = 1; /* diff -Nru qemu-10.0.13+ds/target/arm/tcg/m_helper.c qemu-10.0.14+ds/target/arm/tcg/m_helper.c --- qemu-10.0.13+ds/target/arm/tcg/m_helper.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/arm/tcg/m_helper.c 2026-09-29 03:48:20.000000000 +0300 @@ -2839,8 +2839,9 @@ } if (env->v7m.secure) { + /* Note that security check is done as Secure even if alt is true */ v8m_security_lookup(env, addr, MMU_DATA_LOAD, mmu_idx, - targetsec, &sattrs); + env->v7m.secure, &sattrs); nsr = sattrs.ns && r; nsrw = sattrs.ns && rw; } else { diff -Nru qemu-10.0.13+ds/target/arm/tcg/t16.decode qemu-10.0.14+ds/target/arm/tcg/t16.decode --- qemu-10.0.13+ds/target/arm/tcg/t16.decode 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/arm/tcg/t16.decode 2026-09-29 03:48:20.000000000 +0300 @@ -224,6 +224,9 @@ { { + # Before v6T2 this was not NOP space and must UNDEF + MAYBE_UNDEF_T1_HINT 1011 1111 ---- 0000 + YIELD 1011 1111 0001 0000 WFE 1011 1111 0010 0000 WFI 1011 1111 0011 0000 diff -Nru qemu-10.0.13+ds/target/arm/tcg/translate.c qemu-10.0.14+ds/target/arm/tcg/translate.c --- qemu-10.0.13+ds/target/arm/tcg/translate.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/arm/tcg/translate.c 2026-09-29 03:48:20.000000000 +0300 @@ -1,3 +1,4 @@ + /* * ARM translation * @@ -4546,6 +4547,24 @@ return true; } +static bool trans_MAYBE_UNDEF_T1_HINT(DisasContext *s, + arg_MAYBE_UNDEF_T1_HINT *a) +{ + /* + * The Thumb T1 encoding hint space was only defined starting + * in v6T2 for A-profile. For M-profile it always exists, even + * in v6M. + */ + if (arm_dc_feature(s, ARM_FEATURE_M) || + arm_dc_feature(s, ARM_FEATURE_THUMB2)) { + /* Allow decode to fall through to the hint insns and NOP space */ + return false; + } + /* On the earlier cores, we must UNDEF */ + unallocated_encoding(s); + return true; +} + static bool trans_MSR_imm(DisasContext *s, arg_MSR_imm *a) { uint32_t val = ror32(a->imm, a->rot * 2); @@ -6991,7 +7010,14 @@ static bool trans_CBZ(DisasContext *s, arg_CBZ *a) { - TCGv_i32 tmp = load_reg(s, a->rn); + TCGv_i32 tmp; + + /* CBZ was introduced in v6T2 and v7M */ + if (!arm_dc_feature(s, ARM_FEATURE_THUMB2)) { + return false; + } + + tmp = load_reg(s, a->rn); arm_gen_condlabel(s); tcg_gen_brcondi_i32(a->nz ? TCG_COND_EQ : TCG_COND_NE, @@ -7240,6 +7266,16 @@ int cond_mask = a->cond_mask; /* + * IT insn introduced in v6T2 for A-profile; it is only present + * on M-profile if the Main Extension is implemented. + */ + if (!(arm_dc_feature(s, ARM_FEATURE_M) + ? arm_dc_feature(s, ARM_FEATURE_M_MAIN) + : arm_dc_feature(s, ARM_FEATURE_THUMB2))) { + return false; + } + + /* * No actual code generated for this insn, just setup state. * * Combinations of firstcond and mask which set up an 0b1111 diff -Nru qemu-10.0.13+ds/target/i386/tcg/decode-new.c.inc qemu-10.0.14+ds/target/i386/tcg/decode-new.c.inc --- qemu-10.0.13+ds/target/i386/tcg/decode-new.c.inc 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/i386/tcg/decode-new.c.inc 2026-09-29 03:48:20.000000000 +0300 @@ -1265,7 +1265,7 @@ [0xc0] = X86_OP_ENTRY2(XADD, E,b, G,b, lock), [0xc1] = X86_OP_ENTRY2(XADD, E,v, G,v, lock), [0xc2] = X86_OP_ENTRY4(VCMP, V,x, H,x, W,x, vex2_rep3 p_00_66_f3_f2), - [0xc3] = X86_OP_ENTRY3(MOV, EM,y,G,y, None,None, cpuid(SSE2)), /* MOVNTI */ + [0xc3] = X86_OP_ENTRY3(MOV, EM,y,G,y, None,None, cpuid(SSE2) p_00), /* MOVNTI */ [0xc4] = X86_OP_ENTRY4(PINSRW, V,dq,H,dq,E,w, vex5 mmx p_00_66), [0xc5] = X86_OP_ENTRY3(PEXTRW, G,d, U,dq,I,b, vex5 mmx p_00_66), [0xc6] = X86_OP_ENTRY4(VSHUF, V,x, H,x, W,x, vex4 p_00_66), diff -Nru qemu-10.0.13+ds/target/i386/tcg/fpu_helper.c qemu-10.0.14+ds/target/i386/tcg/fpu_helper.c --- qemu-10.0.13+ds/target/i386/tcg/fpu_helper.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/i386/tcg/fpu_helper.c 2026-09-29 03:48:20.000000000 +0300 @@ -510,6 +510,7 @@ void helper_fmov_STN_ST0(CPUX86State *env, int st_index) { ST(st_index) = ST0; + env->fptags[(env->fpstt + st_index) & 7] = 0; } void helper_fxchg_ST0_STN(CPUX86State *env, int st_index) @@ -519,6 +520,12 @@ tmp = ST(st_index); ST(st_index) = ST0; ST0 = tmp; + + env->fptags[env->fpstt] = 0; + env->fptags[(env->fpstt + st_index) & 7] = 0; + + /* C1 is unconditionally cleared to 0 */ + env->fpus &= ~0x0200; } /* FPU operations */ @@ -1804,6 +1811,13 @@ merge_exception_flags(env, old_flags); } +/* fpush() only validates the new top. FXTRACT also needs ST(1) validated. */ +static inline void fpush_fxtract(CPUX86State *env) +{ + fpush(env); + env->fptags[(env->fpstt + 1) & 7] = 0; +} + void helper_fxtract(CPUX86State *env) { int old_flags = save_exception_flags(env); @@ -1815,22 +1829,22 @@ /* Easy way to generate -inf and raising division by 0 exception */ ST0 = floatx80_div(floatx80_chs(floatx80_one), floatx80_zero, &env->fp_status); - fpush(env); + fpush_fxtract(env); ST0 = temp.d; } else if (floatx80_invalid_encoding(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_default_nan(&env->fp_status); - fpush(env); + fpush_fxtract(env); ST0 = ST1; } else if (floatx80_is_any_nan(ST0)) { if (floatx80_is_signaling_nan(ST0, &env->fp_status)) { float_raise(float_flag_invalid, &env->fp_status); ST0 = floatx80_silence_nan(ST0, &env->fp_status); } - fpush(env); + fpush_fxtract(env); ST0 = ST1; } else if (floatx80_is_infinity(ST0, &env->fp_status)) { - fpush(env); + fpush_fxtract(env); ST0 = ST1; ST1 = floatx80_default_inf(0, &env->fp_status); } else { @@ -1846,7 +1860,7 @@ } /* DP exponent bias */ ST0 = int32_to_floatx80(expdif, &env->fp_status); - fpush(env); + fpush_fxtract(env); BIASEXPONENT(temp); ST0 = temp.d; } diff -Nru qemu-10.0.13+ds/target/ppc/cpu_init.c qemu-10.0.14+ds/target/ppc/cpu_init.c --- qemu-10.0.13+ds/target/ppc/cpu_init.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/ppc/cpu_init.c 2026-09-29 03:48:20.000000000 +0300 @@ -6969,10 +6969,10 @@ PowerPCCPU *cpu = POWERPC_CPU(dev); PowerPCCPUClass *pcc = POWERPC_CPU_GET_CLASS(cpu); - pcc->parent_unrealize(dev); - cpu_remove_sync(CPU(cpu)); + pcc->parent_unrealize(dev); + destroy_ppc_opcodes(cpu); } diff -Nru qemu-10.0.13+ds/target/riscv/cpu.c qemu-10.0.14+ds/target/riscv/cpu.c --- qemu-10.0.13+ds/target/riscv/cpu.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/riscv/cpu.c 2026-09-29 03:48:20.000000000 +0300 @@ -646,6 +646,7 @@ cpu->cfg.ext_zba = true; cpu->cfg.ext_zbb = true; cpu->cfg.ext_zbs = true; + cpu->cfg.ext_zkr = true; cpu->cfg.ext_zkt = true; cpu->cfg.ext_zvbb = true; cpu->cfg.ext_zvbc = true; diff -Nru qemu-10.0.13+ds/target/riscv/cpu_bits.h qemu-10.0.14+ds/target/riscv/cpu_bits.h --- qemu-10.0.13+ds/target/riscv/cpu_bits.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/riscv/cpu_bits.h 2026-09-29 03:48:20.000000000 +0300 @@ -1023,11 +1023,11 @@ (HVICTL_VTI | HVICTL_IID | HVICTL_IPRIOM | HVICTL_IPRIO) /* seed CSR bits */ -#define SEED_OPST (0b11 << 30) -#define SEED_OPST_BIST (0b00 << 30) -#define SEED_OPST_WAIT (0b01 << 30) -#define SEED_OPST_ES16 (0b10 << 30) -#define SEED_OPST_DEAD (0b11 << 30) +#define SEED_OPST (0b11U << 30) +#define SEED_OPST_BIST (0b00U << 30) +#define SEED_OPST_WAIT (0b01U << 30) +#define SEED_OPST_ES16 (0b10U << 30) +#define SEED_OPST_DEAD (0b11U << 30) /* PMU related bits */ #define MIE_LCOFIE (1 << IRQ_PMU_OVF) diff -Nru qemu-10.0.13+ds/target/riscv/csr.c qemu-10.0.14+ds/target/riscv/csr.c --- qemu-10.0.13+ds/target/riscv/csr.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/riscv/csr.c 2026-09-29 03:48:20.000000000 +0300 @@ -107,6 +107,13 @@ static RISCVException ctr(CPURISCVState *env, int csrno) { + if ((csrno >= CSR_CYCLE && csrno <= CSR_INSTRET) || + (csrno >= CSR_CYCLEH && csrno <= CSR_INSTRETH)) { + if (!riscv_cpu_cfg(env)->ext_zicntr) { + return RISCV_EXCP_ILLEGAL_INST; + } + } + #if !defined(CONFIG_USER_ONLY) RISCVCPU *cpu = env_archcpu(env); int ctr_index; @@ -123,10 +130,6 @@ if ((csrno >= CSR_CYCLE && csrno <= CSR_INSTRET) || (csrno >= CSR_CYCLEH && csrno <= CSR_INSTRETH)) { - if (!riscv_cpu_cfg(env)->ext_zicntr) { - return RISCV_EXCP_ILLEGAL_INST; - } - goto skip_ext_pmu_check; } diff -Nru qemu-10.0.13+ds/target/sh4/cpu.h qemu-10.0.14+ds/target/sh4/cpu.h --- qemu-10.0.13+ds/target/sh4/cpu.h 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/sh4/cpu.h 2026-09-29 03:48:20.000000000 +0300 @@ -83,8 +83,7 @@ #define TB_FLAG_DELAY_SLOT_RTE (1 << 2) #define TB_FLAG_PENDING_MOVCA (1 << 3) #define TB_FLAG_GUSA_SHIFT 4 /* [11:4] */ -#define TB_FLAG_GUSA_EXCLUSIVE (1 << 12) -#define TB_FLAG_UNALIGN (1 << 13) +#define TB_FLAG_UNALIGN (1 << 12) #define TB_FLAG_SR_FD (1 << SR_FD) /* 15 */ #define TB_FLAG_FPSCR_PR FPSCR_PR /* 19 */ #define TB_FLAG_FPSCR_SZ FPSCR_SZ /* 20 */ @@ -95,8 +94,7 @@ #define TB_FLAG_DELAY_SLOT_MASK (TB_FLAG_DELAY_SLOT | \ TB_FLAG_DELAY_SLOT_COND | \ TB_FLAG_DELAY_SLOT_RTE) -#define TB_FLAG_GUSA_MASK ((0xff << TB_FLAG_GUSA_SHIFT) | \ - TB_FLAG_GUSA_EXCLUSIVE) +#define TB_FLAG_GUSA_MASK (0xff << TB_FLAG_GUSA_SHIFT) #define TB_FLAG_FPSCR_MASK (TB_FLAG_FPSCR_PR | \ TB_FLAG_FPSCR_SZ | \ TB_FLAG_FPSCR_FR) diff -Nru qemu-10.0.13+ds/target/sh4/translate.c qemu-10.0.14+ds/target/sh4/translate.c --- qemu-10.0.13+ds/target/sh4/translate.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/target/sh4/translate.c 2026-09-29 03:48:20.000000000 +0300 @@ -47,6 +47,9 @@ uint16_t opcode; bool has_movcal; +#ifdef CONFIG_USER_ONLY + bool in_gusa_exclusive; +#endif } DisasContext; #if defined(CONFIG_USER_ONLY) @@ -220,7 +223,11 @@ static inline bool use_exit_tb(DisasContext *ctx) { - return (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) != 0; +#ifdef CONFIG_USER_ONLY + return ctx->in_gusa_exclusive; +#else + return false; +#endif } static bool use_goto_tb(DisasContext *ctx, target_ulong dest) @@ -273,7 +280,8 @@ TCGLabel *l1 = gen_new_label(); TCGCond cond_not_taken = jump_if_true ? TCG_COND_EQ : TCG_COND_NE; - if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) { +#ifdef CONFIG_USER_ONLY + if (ctx->in_gusa_exclusive) { /* When in an exclusive region, we must continue to the end. Therefore, exit the region on a taken branch, but otherwise fall through to the next instruction. */ @@ -286,6 +294,7 @@ ctx->base.is_jmp = DISAS_NEXT; return; } +#endif gen_save_cpu_state(ctx, false); tcg_gen_brcondi_i32(cond_not_taken, cpu_sr_t, 0, l1); @@ -304,7 +313,8 @@ tcg_gen_mov_i32(ds, cpu_delayed_cond); tcg_gen_discard_i32(cpu_delayed_cond); - if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) { +#ifdef CONFIG_USER_ONLY + if (ctx->in_gusa_exclusive) { /* When in an exclusive region, we must continue to the end. Therefore, exit the region on a taken branch, but otherwise fall through to the next instruction. */ @@ -318,6 +328,7 @@ ctx->base.is_jmp = DISAS_NEXT; return; } +#endif tcg_gen_brcondi_i32(TCG_COND_NE, ds, 0, l1); gen_goto_tb(ctx, 1, ctx->base.pc_next + 2); @@ -1800,16 +1811,18 @@ /* go out of the delay slot */ ctx->envflags &= ~TB_FLAG_DELAY_SLOT_MASK; +#ifdef CONFIG_USER_ONLY /* When in an exclusive region, we must continue to the end for conditional branches. */ - if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE - && old_flags & TB_FLAG_DELAY_SLOT_COND) { + if (ctx->in_gusa_exclusive && old_flags & TB_FLAG_DELAY_SLOT_COND) { gen_delayed_conditional_jump(ctx); return; } + /* Otherwise this is probably an invalid gUSA region. Drop the GUSA bits so the next TB doesn't see them. */ ctx->envflags &= ~TB_FLAG_GUSA_MASK; +#endif tcg_gen_movi_i32(cpu_flags, ctx->envflags); if (old_flags & TB_FLAG_DELAY_SLOT_COND) { @@ -1827,7 +1840,6 @@ */ static void gen_restart_exclusive(DisasContext *ctx) { - ctx->envflags |= TB_FLAG_GUSA_EXCLUSIVE; gen_save_cpu_state(ctx, false); gen_helper_exclusive(tcg_env); ctx->base.is_jmp = DISAS_NORETURN; @@ -2215,11 +2227,13 @@ int backup = sextract32(ctx->tbflags, TB_FLAG_GUSA_SHIFT, 8); int max_insns = (pc_end - pc) / 2; + ctx->in_gusa_exclusive = ctx->base.tb->cflags & CF_STEP_ATOMIC; + if (pc != pc_end + backup || max_insns < 2) { /* This is a malformed gUSA region. Don't do anything special, since the interpreter is likely to get confused. */ ctx->envflags &= ~TB_FLAG_GUSA_MASK; - } else if (tbflags & TB_FLAG_GUSA_EXCLUSIVE) { + } else if (ctx->in_gusa_exclusive) { /* Regardless of single-stepping or the end of the page, we must complete execution of the gUSA region while holding the exclusive lock. */ @@ -2253,7 +2267,7 @@ #ifdef CONFIG_USER_ONLY if (unlikely(ctx->envflags & TB_FLAG_GUSA_MASK) - && !(ctx->envflags & TB_FLAG_GUSA_EXCLUSIVE)) { + && !ctx->in_gusa_exclusive) { /* * We're in an gUSA region, and we have not already fallen * back on using an exclusive region. Attempt to parse the @@ -2283,10 +2297,12 @@ { DisasContext *ctx = container_of(dcbase, DisasContext, base); - if (ctx->tbflags & TB_FLAG_GUSA_EXCLUSIVE) { +#ifdef CONFIG_USER_ONLY + if (ctx->in_gusa_exclusive) { /* Ending the region of exclusivity. Clear the bits. */ ctx->envflags &= ~TB_FLAG_GUSA_MASK; } +#endif switch (ctx->base.is_jmp) { case DISAS_STOP: diff -Nru qemu-10.0.13+ds/tcg/riscv/tcg-target.c.inc qemu-10.0.14+ds/tcg/riscv/tcg-target.c.inc --- qemu-10.0.13+ds/tcg/riscv/tcg-target.c.inc 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tcg/riscv/tcg-target.c.inc 2026-09-29 03:48:20.000000000 +0300 @@ -784,6 +784,7 @@ case TCG_TYPE_V64: case TCG_TYPE_V128: case TCG_TYPE_V256: + tcg_debug_assert(s->riscv_cur_type != TCG_TYPE_COUNT); { int lmul = type - riscv_lg2_vlenb; int nf = 1 << MAX(lmul, 0); @@ -1023,6 +1024,10 @@ unsigned idx = type - riscv_lg2_vlenb; tcg_debug_assert(idx < ARRAY_SIZE(whole_reg_ld)); + /* We must initialize vtype to something to avoid VILL. */ + if (s->riscv_cur_type == TCG_TYPE_COUNT) { + set_vtype(s, type, MO_8); + } insn = whole_reg_ld[idx]; } else { static const RISCVInsn unit_stride_ld[] = { @@ -1055,6 +1060,7 @@ case TCG_TYPE_V64: case TCG_TYPE_V128: case TCG_TYPE_V256: + tcg_debug_assert(s->riscv_cur_type != TCG_TYPE_COUNT); if (type >= riscv_lg2_vlenb) { static const RISCVInsn whole_reg_st[] = { OPC_VS1R_V, OPC_VS2R_V, OPC_VS4R_V, OPC_VS8R_V diff -Nru qemu-10.0.13+ds/tests/qemu-iotests/tests/migrate-bitmaps-test qemu-10.0.14+ds/tests/qemu-iotests/tests/migrate-bitmaps-test --- qemu-10.0.13+ds/tests/qemu-iotests/tests/migrate-bitmaps-test 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qemu-iotests/tests/migrate-bitmaps-test 2026-09-29 03:48:20.000000000 +0300 @@ -206,6 +206,39 @@ self.vm_b.launch() self.check_bitmap(self.vm_b, sha256 if persistent else False) + def test_migration_to_readonly_destination(self): + granularity = 512 + mig_caps = [{'capability': 'events', 'state': True}, + {'capability': 'dirty-bitmaps', 'state': True}] + + self.vm_b.add_incoming("defer") + self.vm_b.add_drive(disk_b, 'read-only=on') + + self.add_bitmap(self.vm_a, granularity, True) + self.vm_a.hmp_qemu_io('drive0', 'write 0 4096') + + self.vm_a.cmd('migrate-set-capabilities', capabilities=mig_caps) + self.vm_a.cmd('migrate', uri=mig_cmd) + while True: + event = self.vm_a.event_wait('MIGRATION') + if event['data']['status'] == 'completed': + break + self.vm_a.shutdown() + + self.vm_b.launch() + self.vm_b.cmd('migrate-set-capabilities', capabilities=mig_caps) + self.vm_b.cmd('migrate-incoming', uri=incoming_cmd) + while True: + event = self.vm_b.event_wait('MIGRATION') + if event['data']['status'] in ('completed', 'failed'): + break + + self.assert_qmp(event, 'data/status', 'failed') + + # A failed incoming load makes the destination process exit on + # its own; reap it so tearDown()'s shutdown() is a clean no-op. + self.vm_b.wait() + def inject_test_case(klass, suffix, method, *args, **kwargs): mc = operator.methodcaller(method, *args, **kwargs) diff -Nru qemu-10.0.13+ds/tests/qemu-iotests/tests/migrate-bitmaps-test.out qemu-10.0.14+ds/tests/qemu-iotests/tests/migrate-bitmaps-test.out --- qemu-10.0.13+ds/tests/qemu-iotests/tests/migrate-bitmaps-test.out 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qemu-iotests/tests/migrate-bitmaps-test.out 2026-09-29 03:48:20.000000000 +0300 @@ -1,5 +1,5 @@ -..................................... +...................................... ---------------------------------------------------------------------- -Ran 37 tests +Ran 38 tests OK diff -Nru qemu-10.0.13+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing qemu-10.0.14+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing --- qemu-10.0.13+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing 2026-09-29 03:48:20.000000000 +0300 @@ -35,7 +35,7 @@ # Just assert that our method of checking bitmaps in the image works. assert 'bitmaps' in qemu_img_info(base)['format-specific']['data'] -vm = iotests.VM().add_drive(top, 'backing.node-name=base') +vm = iotests.VM().add_drive(top, 'node-name=top,backing.node-name=base') vm.launch() log('Trying to remove persistent bitmap from r-o base node, should fail:') @@ -66,6 +66,33 @@ if result != {'return': {}}: log('Failed to reopen: ' + str(result)) +log('Adding a persistent bitmap to the r-o base node, should fail:') +vm.qmp_log('block-dirty-bitmap-add', node='base', name='bitmap1', + persistent=True) + +log('Same add inside a transaction, preceded by an otherwise valid') +log('action: the whole transaction must fail and roll back the') +log('already-succeeded first action too:') +vm.qmp_log('transaction', actions=[ + {'type': 'block-dirty-bitmap-add', + 'data': {'node': 'top', 'name': 'bitmap2', 'persistent': True}}, + {'type': 'block-dirty-bitmap-add', + 'data': {'node': 'base', 'name': 'bitmap1', 'persistent': True}}, +]) + +log('bitmap2 on the rw top node must not have survived the rollback:') +vm.qmp_log('block-dirty-bitmap-remove', node='top', name='bitmap2') + +log('Marking the rw top node inactive:') +vm.qmp_log('blockdev-set-active', **{'node-name': 'top', 'active': False}) + +log('Adding a persistent bitmap to a rw but inactive node, should fail:') +vm.qmp_log('block-dirty-bitmap-add', node='top', name='bitmap3', + persistent=True) + +log('Reactivating the top node:') +vm.qmp_log('blockdev-set-active', **{'node-name': 'top', 'active': True}) + vm.shutdown() if 'bitmaps' in qemu_img_info(base)['format-specific']['data']: diff -Nru qemu-10.0.13+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing.out qemu-10.0.14+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing.out --- qemu-10.0.13+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing.out 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qemu-iotests/tests/remove-bitmap-from-backing.out 2026-09-29 03:48:20.000000000 +0300 @@ -1,6 +1,26 @@ Trying to remove persistent bitmap from r-o base node, should fail: {"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "base"}} -{"error": {"class": "GenericError", "desc": "Bitmap 'bitmap0' is readonly and cannot be modified"}} +{"error": {"class": "GenericError", "desc": "Cannot remove persistent bitmap 'bitmap0': no write access to node 'base'"}} Remove persistent bitmap from base node reopened to RW: {"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "base"}} {"return": {}} +Adding a persistent bitmap to the r-o base node, should fail: +{"execute": "block-dirty-bitmap-add", "arguments": {"name": "bitmap1", "node": "base", "persistent": true}} +{"error": {"class": "GenericError", "desc": "Cannot add a persistent bitmap to read-only or inactive node 'base'"}} +Same add inside a transaction, preceded by an otherwise valid +action: the whole transaction must fail and roll back the +already-succeeded first action too: +{"execute": "transaction", "arguments": {"actions": [{"data": {"name": "bitmap2", "node": "top", "persistent": true}, "type": "block-dirty-bitmap-add"}, {"data": {"name": "bitmap1", "node": "base", "persistent": true}, "type": "block-dirty-bitmap-add"}]}} +{"error": {"class": "GenericError", "desc": "Cannot add a persistent bitmap to read-only or inactive node 'base'"}} +bitmap2 on the rw top node must not have survived the rollback: +{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap2", "node": "top"}} +{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap2' not found"}} +Marking the rw top node inactive: +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "top"}} +{"return": {}} +Adding a persistent bitmap to a rw but inactive node, should fail: +{"execute": "block-dirty-bitmap-add", "arguments": {"name": "bitmap3", "node": "top", "persistent": true}} +{"error": {"class": "GenericError", "desc": "Cannot add a persistent bitmap to read-only or inactive node 'top'"}} +Reactivating the top node: +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "top"}} +{"return": {}} diff -Nru qemu-10.0.13+ds/tests/qtest/bcm2835-ic-test.c qemu-10.0.14+ds/tests/qtest/bcm2835-ic-test.c --- qemu-10.0.13+ds/tests/qtest/bcm2835-ic-test.c 1970-01-01 03:00:00.000000000 +0300 +++ qemu-10.0.14+ds/tests/qtest/bcm2835-ic-test.c 2026-09-29 03:48:20.000000000 +0300 @@ -0,0 +1,66 @@ +/* + * QTest testcase for the BCM2835 Interrupt Controller + * + * Copyright (c) 2026 Bin Guo <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" + +#define IC_BASE 0x3f00b200 +#define FIQ_CONTROL (IC_BASE + 0x0c) + +static void test_fiq_select_out_of_range(void) +{ + uint32_t val; + + /* + * Only FIQ sources 0..71 exist. Source 96 used to trigger an assertion + * in bcm2835_ic_update() because extract32(arm_irq_level, 32, 1) was + * called with start >= 32. Make sure the write is rejected and QEMU + * keeps running. + */ + writel(FIQ_CONTROL, 0xe0); /* fiq_select = 96, fiq_enable = 1 */ + val = readl(FIQ_CONTROL); + g_assert_cmpint(val, ==, 0); + + /* The first source past the ARM IRQ range should also be rejected. */ + writel(FIQ_CONTROL, 0xc8); /* fiq_select = 72, fiq_enable = 1 */ + val = readl(FIQ_CONTROL); + g_assert_cmpint(val, ==, 0); +} + +static void test_fiq_select_valid(void) +{ + uint32_t val; + + /* Select the highest valid ARM IRQ source (64 + 7 = 71). */ + writel(FIQ_CONTROL, 0xc7); /* fiq_select = 71, fiq_enable = 1 */ + val = readl(FIQ_CONTROL); + g_assert_cmpint(val, ==, 0xc7); + + /* Select the highest valid GPU IRQ source. */ + writel(FIQ_CONTROL, 0x3f); /* fiq_select = 63, fiq_enable = 0 */ + val = readl(FIQ_CONTROL); + g_assert_cmpint(val, ==, 0x3f); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/bcm2835/bcm2835-ic/fiq-select-out-of-range", + test_fiq_select_out_of_range); + qtest_add_func("/bcm2835/bcm2835-ic/fiq-select-valid", + test_fiq_select_valid); + + qtest_start("-machine raspi3b"); + ret = g_test_run(); + qtest_end(); + + return ret; +} diff -Nru qemu-10.0.13+ds/tests/qtest/ide-test.c qemu-10.0.14+ds/tests/qtest/ide-test.c --- qemu-10.0.13+ds/tests/qtest/ide-test.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qtest/ide-test.c 2026-09-29 03:48:20.000000000 +0300 @@ -95,6 +95,7 @@ enum { CMD_DSM = 0x06, + CMD_READ = 0x20, /* READ SECTOR(S) */ CMD_DIAGNOSE = 0x90, CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */ CMD_READ_DMA = 0xc8, @@ -1194,6 +1195,66 @@ free_pci_device(dev); } +/* Zero sectors per track has to abort (ATA-5 8.16.6), not divide by zero */ +static void test_specify_zero_sectors(void) +{ + QTestState *qts; + QPCIDevice *dev; + QPCIBar bmdma_bar, ide_bar; + uint16_t buf[256]; + uint8_t data; + int i; + + qts = ide_test_start( + "-blockdev driver=file,node-name=hda,filename=%s " + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", + tmp_path[0]); + + dev = get_pci_device(qts, &bmdma_bar, &ide_bar); + + qpci_io_writeb(dev, ide_bar, reg_nsectors, 0); + qpci_io_writeb(dev, ide_bar, reg_device, 0); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP); + + assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), ERR); + assert_bit_set(qpci_io_readb(dev, ide_bar, reg_error), ABRT); + + /* The refused request has to leave the default translation in effect */ + qpci_io_writeb(dev, ide_bar, reg_device, 0); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_IDENTIFY); + for (i = 0; i < 256; i++) { + buf[i] = qpci_io_readw(dev, ide_bar, reg_data); + } + g_assert_cmpint(buf[55], ==, 16); + g_assert_cmpint(buf[56], ==, 63); + + /* READ SECTOR(S) of CHS 0/0/1, which used to crash QEMU */ + qpci_io_writeb(dev, ide_bar, reg_nsectors, 1); + qpci_io_writeb(dev, ide_bar, reg_lba_low, 1); + qpci_io_writeb(dev, ide_bar, reg_lba_middle, 0); + qpci_io_writeb(dev, ide_bar, reg_lba_high, 0); + qpci_io_writeb(dev, ide_bar, reg_device, 0); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ); + + data = ide_wait_clear(qts, BSY); + assert_bit_set(data, DRQ); + assert_bit_clear(data, ERR | DF); + for (i = 0; i < 256; i++) { + buf[i] = qpci_io_readw(dev, ide_bar, reg_data); + } + assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR | DF | DRQ); + + /* A supported translation is still accepted */ + qpci_io_writeb(dev, ide_bar, reg_nsectors, 32); + qpci_io_writeb(dev, ide_bar, reg_device, 7); + qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP); + + assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR); + + ide_test_quit(qts); + free_pci_device(dev); +} + static void test_cdrom_pio(void) { cdrom_read_impl(1, CDROM_PIO); @@ -1265,6 +1326,7 @@ g_test_init(&argc, &argv, NULL); qtest_add_func("/ide/read_native", test_specify); + qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors); qtest_add_func("/ide/identify", test_identify); diff -Nru qemu-10.0.13+ds/tests/qtest/meson.build qemu-10.0.14+ds/tests/qtest/meson.build --- qemu-10.0.13+ds/tests/qtest/meson.build 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qtest/meson.build 2026-09-29 03:48:20.000000000 +0300 @@ -254,7 +254,7 @@ ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_VERSAL') ? ['xlnx-canfd-test', 'xlnx-versal-trng-test'] : []) + \ - (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 'bcm2835-i2c-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 'bcm2835-i2c-test', 'bcm2835-ic-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') and \ config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \ (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \ diff -Nru qemu-10.0.13+ds/tests/qtest/riscv-csr-test.c qemu-10.0.14+ds/tests/qtest/riscv-csr-test.c --- qemu-10.0.13+ds/tests/qtest/riscv-csr-test.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qtest/riscv-csr-test.c 2026-09-29 03:48:20.000000000 +0300 @@ -20,6 +20,12 @@ #define CSR_MVENDORID 0xf11 #define CSR_MISELECT 0x350 +#define CSR_SEED 0x015 + +#define SEED_OPST_MASK (UINT64_C(0x3) << 30) +#define SEED_OPST_ES16 (UINT64_C(0x2) << 30) +#define SEED_OPST_DEAD (UINT64_C(0x3) << 30) + static void run_test_csr(void) { uint64_t res; @@ -46,11 +52,31 @@ qtest_quit(qts); } +static void run_test_seed_csr(void) +{ + uint64_t val = 0; + uint64_t opst; + QTestState *qts; + + qts = qtest_init("-machine virt -cpu tt-ascalon"); + + qtest_csr_call(qts, "get_csr", 0, CSR_SEED, &val); + + opst = val & SEED_OPST_MASK; + g_assert_true(opst == SEED_OPST_ES16 || + opst == SEED_OPST_DEAD); + + g_assert_cmphex(val >> 32, ==, 0); + + qtest_quit(qts); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); qtest_add_func("/cpu/csr", run_test_csr); + qtest_add_func("/cpu/csr/seed", run_test_seed_csr); return g_test_run(); } diff -Nru qemu-10.0.13+ds/tests/qtest/usb-hcd-xhci-test.c qemu-10.0.14+ds/tests/qtest/usb-hcd-xhci-test.c --- qemu-10.0.13+ds/tests/qtest/usb-hcd-xhci-test.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/qtest/usb-hcd-xhci-test.c 2026-09-29 03:48:20.000000000 +0300 @@ -10,8 +10,70 @@ #include "qemu/osdep.h" #include "libqtest-single.h" #include "libqos/usb.h" +#include "libqos/malloc-pc.h" #include "qobject/qdict.h" +/* capability registers */ +#define XHCI_CAPLENGTH 0x00 +#define XHCI_HCSPARAMS1 0x04 +#define XHCI_DBOFF 0x14 +#define XHCI_RTSOFF 0x18 +/* operational registers */ +#define XHCI_USBCMD 0x00 +#define XHCI_USBSTS 0x04 +#define XHCI_CRCR 0x18 +#define XHCI_DCBAAP 0x30 +#define XHCI_CONFIG 0x38 +#define XHCI_PORTSC(n) (0x400 + 0x10 * (n)) +/* interrupter 0, relative to the runtime registers */ +#define XHCI_ERSTSZ 0x28 +#define XHCI_ERSTBA 0x30 +#define XHCI_ERDP 0x38 + +#define USBCMD_RS (1 << 0) +#define USBCMD_HCRST (1 << 1) +#define USBSTS_HCH (1 << 0) +#define USBSTS_HCE (1 << 12) +#define PORTSC_CCS (1 << 0) +#define PORTSC_PR (1 << 4) +#define PORTSC_PP (1 << 9) +#define CRCR_RCS (1 << 0) +#define ERDP_EHB (1 << 3) + +#define TRB_C (1 << 0) +#define TRB_TR_IOC (1 << 5) +#define TRB_TR_SIA (1U << 31) +#define TRB_TYPE(t) ((t) << 10) +#define TRB_GET_TYPE(control) (((control) >> 10) & 0x3f) +#define TRB_GET_CCODE(status) ((status) >> 24) +#define TRB_GET_SLOT(control) ((control) >> 24) + +#define TR_ISOCH 5 +#define CR_ENABLE_SLOT 9 +#define CR_ADDRESS_DEVICE 11 +#define CR_CONFIGURE_ENDPOINT 12 +#define ER_TRANSFER 32 +#define ER_COMMAND_COMPLETE 33 +#define CC_SUCCESS 1 + +#define EP_TYPE_ISOCH_OUT 1 +#define EP_TYPE_CONTROL 4 +#define EP_TYPE_ISOCH_IN 5 + +#define XHCI_RING_TRBS 64 +#define XHCI_MICROFRAME_NS 125000 + +typedef struct XHCITest { + QTestState *qts; + QGuestAllocator alloc; + QPCIBus *bus; + struct qhc hc; + uint32_t oper, runtime, doorbell; + uint64_t cmd_ring, event_ring, input_ctx; + unsigned int cmd_idx, event_idx; + unsigned int port, slot; +} XHCITest; + static void wait_device_deleted_event(QTestState *qtest, const char *id) { QDict *resp, *data; @@ -109,6 +171,258 @@ qtest_qmp_device_del(qts, "ccid"); } +static uint32_t xhci_readl(XHCITest *x, uint32_t off) +{ + return qpci_io_readl(x->hc.dev, x->hc.bar, off); +} + +static void xhci_writel(XHCITest *x, uint32_t off, uint32_t val) +{ + qpci_io_writel(x->hc.dev, x->hc.bar, off, val); +} + +static void xhci_writeq(XHCITest *x, uint32_t off, uint64_t val) +{ + xhci_writel(x, off, val); + xhci_writel(x, off + 4, val >> 32); +} + +static uint64_t xhci_alloc_page(XHCITest *x) +{ + uint64_t addr = guest_alloc(&x->alloc, 0x1000); + + qtest_memset(x->qts, addr, 0, 0x1000); + return addr; +} + +static void xhci_write_trb(XHCITest *x, uint64_t addr, uint64_t parameter, + uint32_t status, uint32_t control) +{ + qtest_writeq(x->qts, addr, parameter); + qtest_writel(x->qts, addr + 8, status); + qtest_writel(x->qts, addr + 12, control); +} + +/* Fetch the next event if there is one. Does not advance the clock. */ +static bool xhci_next_event(XHCITest *x, uint32_t *status, uint32_t *control) +{ + uint64_t addr = x->event_ring + 16 * x->event_idx; + uint32_t c = qtest_readl(x->qts, addr + 12); + + if (!(c & TRB_C)) { + return false; + } + if (status) { + *status = qtest_readl(x->qts, addr + 8); + } + if (control) { + *control = c; + } + x->event_idx++; + g_assert_cmpuint(x->event_idx, <, XHCI_RING_TRBS); + xhci_writeq(x, x->runtime + XHCI_ERDP, (addr + 16) | ERDP_EHB); + return true; +} + +static unsigned int xhci_command(XHCITest *x, uint64_t parameter, + uint32_t control) +{ + uint32_t status; + + g_assert_cmpuint(x->cmd_idx, <, XHCI_RING_TRBS); + xhci_write_trb(x, x->cmd_ring + 16 * x->cmd_idx++, parameter, 0, + control | TRB_C); + xhci_writel(x, x->doorbell, 0); + + g_assert_true(xhci_next_event(x, &status, &control)); + g_assert_cmpuint(TRB_GET_TYPE(control), ==, ER_COMMAND_COMPLETE); + g_assert_cmpuint(TRB_GET_CCODE(status), ==, CC_SUCCESS); + return TRB_GET_SLOT(control); +} + +/* + * Start qemu-xhci with one USB device, run the controller and bring the + * device to the Addressed state. + */ +static void xhci_test_start(XHCITest *x, const char *usb_device) +{ + uint64_t dcbaa, erst, ep0_ring; + unsigned int maxports; + + memset(x, 0, sizeof(*x)); + /* pit=off: a long clock step would run the i8254 timer all the way */ + x->qts = qtest_initf("-machine pc,pit=off -nodefaults " + "-device qemu-xhci,id=xhci,addr=04.0 %s", usb_device); + pc_alloc_init(&x->alloc, x->qts, ALLOC_NO_FLAGS); + x->bus = qpci_new_pc(x->qts, NULL); + qusb_pci_init_one(x->bus, &x->hc, QPCI_DEVFN(4, 0), 0); + + x->oper = qpci_io_readb(x->hc.dev, x->hc.bar, XHCI_CAPLENGTH); + x->runtime = xhci_readl(x, XHCI_RTSOFF) & ~0x1f; + x->doorbell = xhci_readl(x, XHCI_DBOFF) & ~0x3; + maxports = xhci_readl(x, XHCI_HCSPARAMS1) >> 24; + + xhci_writel(x, x->oper + XHCI_USBCMD, USBCMD_HCRST); + g_assert_false(xhci_readl(x, x->oper + XHCI_USBCMD) & USBCMD_HCRST); + + dcbaa = xhci_alloc_page(x); + erst = xhci_alloc_page(x); + x->cmd_ring = xhci_alloc_page(x); + x->event_ring = xhci_alloc_page(x); + x->input_ctx = xhci_alloc_page(x); + + xhci_writel(x, x->oper + XHCI_CONFIG, 1); + xhci_writeq(x, x->oper + XHCI_DCBAAP, dcbaa); + qtest_writeq(x->qts, erst, x->event_ring); + qtest_writel(x->qts, erst + 8, XHCI_RING_TRBS); + xhci_writel(x, x->runtime + XHCI_ERSTSZ, 1); + xhci_writeq(x, x->runtime + XHCI_ERSTBA, erst); + xhci_writeq(x, x->runtime + XHCI_ERDP, x->event_ring | ERDP_EHB); + xhci_writeq(x, x->oper + XHCI_CRCR, x->cmd_ring | CRCR_RCS); + xhci_writel(x, x->oper + XHCI_USBCMD, USBCMD_RS); + g_assert_false(xhci_readl(x, x->oper + XHCI_USBSTS) & USBSTS_HCH); + + for (x->port = 0; x->port < maxports; x->port++) { + if (xhci_readl(x, x->oper + XHCI_PORTSC(x->port)) & PORTSC_CCS) { + break; + } + } + g_assert_cmpuint(x->port, <, maxports); + xhci_writel(x, x->oper + XHCI_PORTSC(x->port), PORTSC_PP | PORTSC_PR); + while (xhci_next_event(x, NULL, NULL)) { + /* drop the port status change events */ + } + + x->slot = xhci_command(x, 0, TRB_TYPE(CR_ENABLE_SLOT)); + qtest_writeq(x->qts, dcbaa + 8 * x->slot, xhci_alloc_page(x)); + + /* input control context: add slot and ep0 */ + qtest_writel(x->qts, x->input_ctx + 0x04, 0x3); + /* slot context: one context entry, root hub port */ + qtest_writel(x->qts, x->input_ctx + 0x20, 1 << 27); + qtest_writel(x->qts, x->input_ctx + 0x24, (x->port + 1) << 16); + /* ep0 context */ + ep0_ring = xhci_alloc_page(x); + qtest_writel(x->qts, x->input_ctx + 0x44, + (64 << 16) | (EP_TYPE_CONTROL << 3)); + qtest_writeq(x->qts, x->input_ctx + 0x48, ep0_ring | 1); + xhci_command(x, x->input_ctx, + TRB_TYPE(CR_ADDRESS_DEVICE) | (x->slot << 24)); +} + +/* Returns the address of the transfer ring. */ +static uint64_t xhci_configure_ep(XHCITest *x, unsigned int epid, + unsigned int type, unsigned int interval, + unsigned int max_packet) +{ + uint64_t ring = xhci_alloc_page(x); + uint64_t epctx = x->input_ctx + 0x20 * (epid + 1); + + qtest_memset(x->qts, x->input_ctx, 0, 0x1000); + qtest_writel(x->qts, x->input_ctx + 0x04, (1 << epid) | 1); + qtest_writel(x->qts, x->input_ctx + 0x20, epid << 27); + qtest_writel(x->qts, x->input_ctx + 0x24, (x->port + 1) << 16); + qtest_writel(x->qts, epctx + 0x00, interval << 16); + qtest_writel(x->qts, epctx + 0x04, (max_packet << 16) | (type << 3)); + qtest_writeq(x->qts, epctx + 0x08, ring | 1); + xhci_command(x, x->input_ctx, + TRB_TYPE(CR_CONFIGURE_ENDPOINT) | (x->slot << 24)); + return ring; +} + +static void xhci_test_end(XHCITest *x) +{ + g_free(x->hc.dev); + qpci_free_pc(x->bus); + alloc_destroy(&x->alloc); + qtest_quit(x->qts); +} + +static bool xhci_test_supported(const char *usb_device) +{ + const char *arch = qtest_get_arch(); + + if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) { + g_test_skip("Test only runs on x86 (pc machine)"); + return false; + } + if (!qtest_has_device("qemu-xhci") || !qtest_has_device(usb_device)) { + g_test_skip("Devices not available"); + return false; + } + return true; +} + +/* + * An isoch TD with SIA set is run at the next interval boundary. That has to + * hold once the microframe index no longer fits in 32 bits as well. + */ +static void test_xhci_isoch_mfindex_32bit(void) +{ + const unsigned int interval = 6; + uint32_t control; + uint64_t ring; + XHCITest x; + + if (!xhci_test_supported("usb-audio")) { + return; + } + + xhci_test_start(&x, "-audiodev none,id=snd0 " + "-device usb-audio,audiodev=snd0"); + ring = xhci_configure_ep(&x, 2, EP_TYPE_ISOCH_OUT, interval, 64); + + /* Go past 2^32 microframes and stop off an interval boundary. */ + qtest_clock_step(x.qts, (1ULL << 32) * XHCI_MICROFRAME_NS); + qtest_clock_step(x.qts, 5 * XHCI_MICROFRAME_NS); + + xhci_write_trb(&x, ring, xhci_alloc_page(&x), 64, + TRB_TYPE(TR_ISOCH) | TRB_TR_SIA | TRB_TR_IOC | TRB_C); + xhci_writel(&x, x.doorbell + 4 * x.slot, 2); + g_assert_false(xhci_next_event(&x, NULL, NULL)); + + /* + * The streaming interface has not been enabled, so usb-audio stalls the + * TD. What matters is when that happens. + */ + qtest_clock_step(x.qts, XHCI_MICROFRAME_NS << interval); + g_assert_true(xhci_next_event(&x, NULL, &control)); + g_assert_cmpuint(TRB_GET_TYPE(control), ==, ER_TRANSFER); + + xhci_test_end(&x); +} + +/* + * The endpoint type in the endpoint context is whatever the guest says. Tell + * the controller that the interrupt endpoint of usb-kbd is isoch. The idle + * keyboard NAKs, and the TD has to stay pending when first the kick timer and + * then a doorbell retry it. + */ +static void test_xhci_isoch_ep_type_mismatch(void) +{ + uint64_t ring; + XHCITest x; + + if (!xhci_test_supported("usb-kbd")) { + return; + } + + xhci_test_start(&x, "-device usb-kbd"); + ring = xhci_configure_ep(&x, 3, EP_TYPE_ISOCH_IN, 0, 8); + + xhci_write_trb(&x, ring, xhci_alloc_page(&x), 8, + TRB_TYPE(TR_ISOCH) | TRB_TR_SIA | TRB_TR_IOC | TRB_C); + xhci_writel(&x, x.doorbell + 4 * x.slot, 3); + qtest_clock_step(x.qts, 2 * XHCI_MICROFRAME_NS); + xhci_writel(&x, x.doorbell + 4 * x.slot, 3); + + g_assert_false(xhci_next_event(&x, NULL, NULL)); + g_assert_cmphex(xhci_readl(&x, x.oper + XHCI_USBSTS) & + (USBSTS_HCH | USBSTS_HCE), ==, 0); + + xhci_test_end(&x); +} + int main(int argc, char **argv) { int ret; @@ -123,6 +437,10 @@ if (qtest_has_device("usb-ccid")) { qtest_add_func("/xhci/pci/hotplug/usb-ccid", test_usb_ccid_hotplug); } + qtest_add_func("/xhci/pci/isoch/mfindex-32bit", + test_xhci_isoch_mfindex_32bit); + qtest_add_func("/xhci/pci/isoch/ep-type-mismatch", + test_xhci_isoch_ep_type_mismatch); qtest_start("-device nec-usb-xhci,id=xhci" " -drive id=drive0,if=none,file=null-co://," diff -Nru qemu-10.0.13+ds/tests/unit/meson.build qemu-10.0.14+ds/tests/unit/meson.build --- qemu-10.0.13+ds/tests/unit/meson.build 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/unit/meson.build 2026-09-29 03:48:21.000000000 +0300 @@ -117,6 +117,7 @@ endif if host_os != 'windows' tests += { + 'test-io-channel-websock': [io], 'test-image-locking': [testblock], 'test-nested-aio-poll': [], } diff -Nru qemu-10.0.13+ds/tests/unit/test-blockjob.c qemu-10.0.14+ds/tests/unit/test-blockjob.c --- qemu-10.0.13+ds/tests/unit/test-blockjob.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/tests/unit/test-blockjob.c 2026-09-29 03:48:21.000000000 +0300 @@ -388,6 +388,105 @@ cancel_common(s); } +typedef struct PauseCountJob { + BlockJob common; + int n; + bool should_complete; +} PauseCountJob; + +static void pause_count_job_complete(Job *job, Error **errp) +{ + PauseCountJob *s = container_of(job, PauseCountJob, common.job); + s->should_complete = true; +} + +static int coroutine_fn pause_count_job_run(Job *job, Error **errp) +{ + PauseCountJob *s = container_of(job, PauseCountJob, common.job); + + while (!s->should_complete) { + if (job_is_cancelled(&s->common.job)) { + return 0; + } + s->n++; + /* + * Yields; while a pause is pending the yield is skipped and the job + * parks in job_pause_point() instead. + */ + job_sleep_ns(&s->common.job, 10 * 1000 * 1000); + } + + return 0; +} + +static const BlockJobDriver pause_count_job_driver = { + .job_driver = { + .instance_size = sizeof(PauseCountJob), + .free = block_job_free, + .user_resume = block_job_user_resume, + .run = pause_count_job_run, + .complete = pause_count_job_complete, + }, +}; + +/* + * A job that has reached its pause point must stay paused while a pause is + * still pending (pause_count > 0). An overlapping drain re-enters the job (one + * drain's job_resume() wakes it while the next drain's job_pause() is already + * counted); the job must not run or clear job->paused, otherwise + * job_set_aio_context() can observe paused == false and abort. + */ +static void test_pause_keeps_paused(void) +{ + BlockBackend *blk; + BlockJob *bjob; + PauseCountJob *s; + Job *job; + int n0; + + blk = create_blk(NULL); + bjob = mk_job(blk, "job0", &pause_count_job_driver, true, + JOB_MANUAL_FINALIZE | JOB_MANUAL_DISMISS); + s = container_of(bjob, PauseCountJob, common); + job = &bjob->job; + WITH_JOB_LOCK_GUARD() { + job_ref_locked(job); + } + + job_start(job); + + /* Pause the running job; it parks in job_pause_point() with paused set. */ + WITH_JOB_LOCK_GUARD() { + job_pause_locked(job); + g_assert_true(job->paused); + g_assert_cmpint(job->status, ==, JOB_STATUS_PAUSED); + } + n0 = s->n; + + /* + * Spurious wake while the pause is still pending. The job must stay parked: + * the bug clears job->paused, runs an iteration (s->n advances) and + * re-pauses, exposing a paused == false window. + */ + job_enter(job); + WITH_JOB_LOCK_GUARD() { + g_assert_true(job->paused); + } + g_assert_cmpint(s->n, ==, n0); + + /* Resume and tear down. */ + WITH_JOB_LOCK_GUARD() { + job_resume_locked(job); + } + job_cancel_sync(job, true); + WITH_JOB_LOCK_GUARD() { + Job *dummy = job; + job_dismiss_locked(&dummy, &error_abort); + job_unref_locked(job); + } + destroy_blk(blk); +} + int main(int argc, char **argv) { qemu_init_main_loop(&error_abort); @@ -402,5 +501,6 @@ g_test_add_func("/blockjob/cancel/standby", test_cancel_standby); g_test_add_func("/blockjob/cancel/pending", test_cancel_pending); g_test_add_func("/blockjob/cancel/concluded", test_cancel_concluded); + g_test_add_func("/blockjob/pause/keep_paused", test_pause_keeps_paused); return g_test_run(); } diff -Nru qemu-10.0.13+ds/tests/unit/test-io-channel-websock.c qemu-10.0.14+ds/tests/unit/test-io-channel-websock.c --- qemu-10.0.13+ds/tests/unit/test-io-channel-websock.c 1970-01-01 03:00:00.000000000 +0300 +++ qemu-10.0.14+ds/tests/unit/test-io-channel-websock.c 2026-09-29 03:48:21.000000000 +0300 @@ -0,0 +1,249 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * QEMU I/O channel websock test + * + * Copyright (c) 2026 Virtuozzo International GmbH + */ + +#include "qemu/osdep.h" +#include "io/channel-websock.h" +#include "io/channel-socket.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qemu/sockets.h" +#include "qom/object.h" + +#define TYPE_QIO_CHANNEL_STALL "qio-channel-stall" +OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelStall, QIO_CHANNEL_STALL) + +/* + * Reports QIO_CHANNEL_ERR_BLOCK for the first @rstalls reads and @wstalls + * writes, the way a TLS channel does when a record arrives split across TCP + * segments or the socket cannot take the whole reply at once. + */ +struct QIOChannelStall { + QIOChannel parent; + QIOChannel *master; + unsigned rstalls; + unsigned wstalls; +}; + +static ssize_t qio_channel_stall_readv(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int **fds, + size_t *nfds, + int flags, + Error **errp) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc); + + if (sioc->rstalls) { + sioc->rstalls--; + return QIO_CHANNEL_ERR_BLOCK; + } + return qio_channel_readv_full(sioc->master, iov, niov, fds, nfds, + flags, errp); +} + +static ssize_t qio_channel_stall_writev(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, + int *fds, + size_t nfds, + int flags, + Error **errp) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc); + + if (sioc->wstalls) { + sioc->wstalls--; + return QIO_CHANNEL_ERR_BLOCK; + } + return qio_channel_writev_full(sioc->master, iov, niov, fds, nfds, + flags, errp); +} + +static int qio_channel_stall_set_blocking(QIOChannel *ioc, bool enabled, + Error **errp) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc); + + return qio_channel_set_blocking(sioc->master, enabled, errp) ? 0 : -1; +} + +static int qio_channel_stall_close(QIOChannel *ioc, Error **errp) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc); + + return qio_channel_close(sioc->master, errp); +} + +static GSource *qio_channel_stall_create_watch(QIOChannel *ioc, + GIOCondition condition) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc); + + return qio_channel_create_watch(sioc->master, condition); +} + +static void qio_channel_stall_finalize(Object *obj) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL(obj); + + object_unref(OBJECT(sioc->master)); +} + +static void qio_channel_stall_class_init(ObjectClass *klass, + void *class_data G_GNUC_UNUSED) +{ + QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass); + + ioc_klass->io_writev = qio_channel_stall_writev; + ioc_klass->io_readv = qio_channel_stall_readv; + ioc_klass->io_set_blocking = qio_channel_stall_set_blocking; + ioc_klass->io_close = qio_channel_stall_close; + ioc_klass->io_create_watch = qio_channel_stall_create_watch; +} + +static const TypeInfo qio_channel_stall_info = { + .parent = TYPE_QIO_CHANNEL, + .name = TYPE_QIO_CHANNEL_STALL, + .instance_size = sizeof(QIOChannelStall), + .instance_finalize = qio_channel_stall_finalize, + .class_init = qio_channel_stall_class_init, +}; + +static QIOChannelStall *qio_channel_stall_new(QIOChannel *master, + unsigned rstalls, + unsigned wstalls) +{ + QIOChannelStall *sioc = QIO_CHANNEL_STALL( + object_new(TYPE_QIO_CHANNEL_STALL)); + + object_ref(OBJECT(master)); + sioc->master = master; + sioc->rstalls = rstalls; + sioc->wstalls = wstalls; + + return sioc; +} + +typedef struct { + bool finished; + bool failed; +} QIOChannelWebsockHandshake; + +static void test_websock_handshake_done(QIOTask *task, gpointer opaque) +{ + QIOChannelWebsockHandshake *res = opaque; + + res->finished = true; + res->failed = qio_task_propagate_error(task, NULL); +} + +/* + * Drives a server-side handshake against @request and returns whatever + * the server wrote back, NUL terminated. The handshake is expected to + * fail; the point of the test is the HTTP response that goes with it. + */ +static char *test_websock_handshake_reply(const char *request, + unsigned rstalls, unsigned wstalls) +{ + QIOChannelWebsockHandshake res = { false, false }; + QIOChannelSocket *cli, *srv; + QIOChannelStall *stall; + QIOChannelWebsock *wioc; + GMainContext *mainloop; + int channel[2]; + char *reply; + ssize_t got; + + g_assert(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0); + + cli = qio_channel_socket_new_fd(channel[0], &error_abort); + srv = qio_channel_socket_new_fd(channel[1], &error_abort); + qio_channel_set_blocking(QIO_CHANNEL(srv), false, &error_abort); + qio_channel_set_blocking(QIO_CHANNEL(cli), false, &error_abort); + + stall = qio_channel_stall_new(QIO_CHANNEL(srv), rstalls, wstalls); + wioc = qio_channel_websock_new_server(QIO_CHANNEL(stall)); + qio_channel_websock_handshake(wioc, test_websock_handshake_done, + &res, NULL); + + qio_channel_write_all(QIO_CHANNEL(cli), request, strlen(request), + &error_abort); + + mainloop = g_main_context_default(); + while (!res.finished) { + g_main_context_iteration(mainloop, TRUE); + } + g_assert(res.failed); + + reply = g_malloc0(1024); + got = qio_channel_read(QIO_CHANNEL(cli), reply, 1023, &error_abort); + if (got > 0) { + reply[got] = '\0'; + } + + object_unref(OBJECT(wioc)); + object_unref(OBJECT(stall)); + object_unref(OBJECT(srv)); + object_unref(OBJECT(cli)); + + return reply; +} + +static void test_websock_bad_request(const void *opaque) +{ + const char *request = opaque; + g_autofree char *reply = test_websock_handshake_reply(request, 0, 0); + + g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n")); +} + +static void test_websock_stalled_read(const void *opaque) +{ + const char *request = opaque; + g_autofree char *reply = test_websock_handshake_reply(request, 1, 0); + + g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n")); +} + +static void test_websock_stalled_write(const void *opaque) +{ + const char *request = opaque; + g_autofree char *reply = test_websock_handshake_reply(request, 0, 1); + + g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n")); +} + +int main(int argc, char **argv) +{ + module_call_init(MODULE_INIT_QOM); + type_register_static(&qio_channel_stall_info); + g_test_init(&argc, &argv, NULL); + +#define TEST_BAD_REQUEST(name, request) \ + g_test_add_data_func("/io/channel/websock/bad-request/" name, \ + request, test_websock_bad_request) + + /* + * A greeting with no space at all used to leave the response buffer + * empty, which drove the handshake into a zero length write. + */ + TEST_BAD_REQUEST("no-space", "stats\r\nx\r\n\r\n"); + TEST_BAD_REQUEST("method-only", "GET\r\nx\r\n\r\n"); + TEST_BAD_REQUEST("no-version", "GET /\r\nx\r\n\r\n"); + TEST_BAD_REQUEST("bad-method", "POST / HTTP/1.1\r\nx: y\r\n\r\n"); + TEST_BAD_REQUEST("bad-version", "GET / HTTP/1.0\r\nx: y\r\n\r\n"); + + /* A read which blocks before any header arrives is not a fatal error. */ + g_test_add_data_func("/io/channel/websock/stalled-read", + "stats\r\nx\r\n\r\n", test_websock_stalled_read); + g_test_add_data_func("/io/channel/websock/stalled-write", + "stats\r\nx\r\n\r\n", test_websock_stalled_write); + + return g_test_run(); +} diff -Nru qemu-10.0.13+ds/ui/cursor.c qemu-10.0.14+ds/ui/cursor.c --- qemu-10.0.13+ds/ui/cursor.c 2026-08-26 22:31:26.000000000 +0300 +++ qemu-10.0.14+ds/ui/cursor.c 2026-09-29 03:48:21.000000000 +0300 @@ -1,4 +1,5 @@ #include "qemu/osdep.h" +#include "qemu/atomic.h" #include "ui/console.h" #include "cursor_hidden.xpm" @@ -103,24 +104,28 @@ c = g_malloc0(sizeof(QEMUCursor) + datasize); c->width = width; c->height = height; - c->refcount = 1; + qatomic_set(&c->refcount, 1); return c; } QEMUCursor *cursor_ref(QEMUCursor *c) { - c->refcount++; + qatomic_inc(&c->refcount); return c; } void cursor_unref(QEMUCursor *c) { + int refcount; + if (c == NULL) return; - c->refcount--; - if (c->refcount) - return; - g_free(c); + + refcount = qatomic_fetch_dec(&c->refcount); + assert(refcount > 0); + if (refcount == 1) { + g_free(c); + } } int cursor_get_mono_bpl(QEMUCursor *c)

