From 924b0be88d5488d1e7b918e005e72cfaaaef87b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Tue, 17 Mar 2026 19:02:27 +0400 Subject: [PATCH 1/6] audio/mixeng: fix sw/hw mixup in audio_pcm_sw_init_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 42061a14358 ("audio/mixeng: replace redundant pcm_info fields with AudioFormat") accidentally changed the conv/clip function selection in audio_pcm_sw_init_ to use hw->info.af (the hardware voice format) instead of sw->info.af (the software voice format). This causes audio distortion when the software and hardware voices use different formats, as the wrong conversion functions are applied to the audio data. Fix by using sw->info.af, restoring the original behavior. Fixes: 42061a14358c ("audio/mixeng: replace redundant pcm_info fields with AudioFormat") Reported-by: Dmitry Osipenko Reviewed-by: Christian Schoenebeck Signed-off-by: Marc-André Lureau --- audio/audio_template.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/audio/audio_template.h b/audio/audio_template.h index fe769cde66..398a727373 100644 --- a/audio/audio_template.h +++ b/audio/audio_template.h @@ -172,7 +172,7 @@ static int glue (audio_pcm_sw_init_, TYPE) ( sw->empty = true; #endif - if (audio_format_is_float(hw->info.af)) { + if (audio_format_is_float(sw->info.af)) { #ifdef DAC sw->conv = mixeng_conv_float[sw->info.nchannels == 2] [sw->info.swap_endianness]; @@ -187,9 +187,9 @@ static int glue (audio_pcm_sw_init_, TYPE) ( sw->clip = mixeng_clip #endif [sw->info.nchannels == 2] - [audio_format_is_signed(hw->info.af)] + [audio_format_is_signed(sw->info.af)] [sw->info.swap_endianness] - [audio_format_to_index(hw->info.af)]; + [audio_format_to_index(sw->info.af)]; } sw->name = g_strdup (name); From 2151a67eb6552a824b37f3b90dd86ce55edea92f Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Thu, 26 Mar 2026 14:51:11 +0800 Subject: [PATCH 2/6] ui/dbus: associate add_client completion with its request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 99997823bbbd ("ui/dbus: add p2p=on/off option") introduced an asynchronous D-Bus client setup path, with the completion handler reaching back into the global dbus_display state. This makes the callback effectively operate on whatever request is current when it runs, rather than the one that created it. A completion from an older request can therefore clear a newer add_client_cancellable or install its connection after a replacement request has already been issued. It also relies on the DBusDisplay instance remaining alive until completion. Fix this by passing the DBusDisplay and GCancellable as callback data, taking references while the async setup is in flight, and only acting on completion if it still matches the current request. Also drop the previous cancellable before creating a new request. Fixes: 99997823bbbd ("ui/dbus: add p2p=on/off option") Signed-off-by: GuoHan Zhao Reviewed-by: Marc-André Lureau Message-ID: <20260326065111.626236-1-zhaoguohan@kylinos.cn> --- ui/dbus.c | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/ui/dbus.c b/ui/dbus.c index 4f24215555..7c54b6a502 100644 --- a/ui/dbus.c +++ b/ui/dbus.c @@ -263,22 +263,52 @@ dbus_display_complete(UserCreatable *uc, Error **errp) } } +typedef struct DBusDisplayAddClientData { + DBusDisplay *display; + GCancellable *cancellable; +} DBusDisplayAddClientData; + +static void dbus_display_add_client_data_free(DBusDisplayAddClientData *data) +{ + if (data->display) { + object_unref(OBJECT(data->display)); + data->display = NULL; + } + g_clear_object(&data->cancellable); + g_free(data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(DBusDisplayAddClientData, + dbus_display_add_client_data_free) + static void dbus_display_add_client_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) { + g_autoptr(DBusDisplayAddClientData) data = user_data; + DBusDisplay *display = data->display; + bool current = display->add_client_cancellable == data->cancellable; g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) conn = NULL; - g_clear_object(&dbus_display->add_client_cancellable); + if (current) { + g_clear_object(&display->add_client_cancellable); + } conn = g_dbus_connection_new_finish(res, &err); if (!conn) { - error_printf("Failed to accept D-Bus client: %s", err->message); + if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + error_printf("Failed to accept D-Bus client: %s", err->message); + } + return; } - g_dbus_object_manager_server_set_connection(dbus_display->server, conn); + if (!current) { + return; + } + + g_dbus_object_manager_server_set_connection(display->server, conn); g_dbus_connection_start_message_processing(conn); } @@ -290,6 +320,7 @@ dbus_display_add_client(int csock, Error **errp) g_autoptr(GSocket) socket = NULL; g_autoptr(GSocketConnection) conn = NULL; g_autofree char *guid = g_dbus_generate_guid(); + DBusDisplayAddClientData *data; if (!dbus_display) { error_setg(errp, "p2p connections not accepted in bus mode"); @@ -298,6 +329,7 @@ dbus_display_add_client(int csock, Error **errp) if (dbus_display->add_client_cancellable) { g_cancellable_cancel(dbus_display->add_client_cancellable); + g_clear_object(&dbus_display->add_client_cancellable); } #ifdef WIN32 @@ -318,6 +350,10 @@ dbus_display_add_client(int csock, Error **errp) conn = g_socket_connection_factory_create_connection(socket); dbus_display->add_client_cancellable = g_cancellable_new(); + data = g_new0(DBusDisplayAddClientData, 1); + data->display = DBUS_DISPLAY(object_ref(OBJECT(dbus_display))); + data->cancellable = g_object_ref(dbus_display->add_client_cancellable); + GDBusConnectionFlags flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING; @@ -332,7 +368,7 @@ dbus_display_add_client(int csock, Error **errp) NULL, dbus_display->add_client_cancellable, dbus_display_add_client_ready, - NULL); + data); return true; } From e5ef268596c33db43bf7ddedf23974af81d7315b Mon Sep 17 00:00:00 2001 From: GuoHan Zhao Date: Mon, 30 Mar 2026 17:13:10 +0800 Subject: [PATCH 3/6] ui/dbus: tear down clipboard callbacks on display finalize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clipboard D-Bus teardown path currently runs when the peer disappears, but not when DBusDisplay itself is finalized. That leaves pending clipboard requests and signal handlers associated with the clipboard proxy active past display teardown. Add an explicit clipboard fini hook and invoke it from dbus_display_finalize() so the clipboard teardown also runs during display destruction. bixes: ff1a5810f61f ("ui/dbus: add clipboard interface") Signed-off-by: GuoHan Zhao Message-ID: <20260330091310.42868-1-zhaoguohan@kylinos.cn> [ Marc-André - Move clipobard finalization to the function] Reviewed-by: Marc-André Lureau --- ui/dbus-clipboard.c | 16 ++++++++++++++++ ui/dbus.c | 3 +-- ui/dbus.h | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ui/dbus-clipboard.c b/ui/dbus-clipboard.c index 6787a77668..935b6b1a2a 100644 --- a/ui/dbus-clipboard.c +++ b/ui/dbus-clipboard.c @@ -191,6 +191,7 @@ static void dbus_clipboard_unregister_proxy(DBusDisplay *dpy) { const char *name = NULL; + GDBusConnection *connection = NULL; int i; for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) { @@ -201,6 +202,13 @@ dbus_clipboard_unregister_proxy(DBusDisplay *dpy) return; } + connection = g_dbus_proxy_get_connection( + G_DBUS_PROXY(dpy->clipboard_proxy)); + if (connection) { + g_signal_handlers_disconnect_by_data(connection, dpy); + } + g_signal_handlers_disconnect_by_data(dpy->clipboard_proxy, dpy); + name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); trace_dbus_clipboard_unregister(name); g_clear_object(&dpy->clipboard_proxy); @@ -425,6 +433,14 @@ dbus_clipboard_request( return DBUS_METHOD_INVOCATION_HANDLED; } +void +dbus_clipboard_fini(DBusDisplay *dpy) +{ + dbus_clipboard_unregister_proxy(dpy); + qemu_clipboard_peer_unregister(&dpy->clipboard_peer); + g_clear_object(&dpy->clipboard); +} + void dbus_clipboard_init(DBusDisplay *dpy) { diff --git a/ui/dbus.c b/ui/dbus.c index 7c54b6a502..794b65c4ad 100644 --- a/ui/dbus.c +++ b/ui/dbus.c @@ -145,8 +145,7 @@ dbus_display_finalize(Object *o) dbus_display_notifier_remove(&dd->notifier); } - qemu_clipboard_peer_unregister(&dd->clipboard_peer); - g_clear_object(&dd->clipboard); + dbus_clipboard_fini(dd); g_clear_object(&dd->server); g_clear_pointer(&dd->consoles, g_ptr_array_unref); diff --git a/ui/dbus.h b/ui/dbus.h index 1e8c24a48e..986d777460 100644 --- a/ui/dbus.h +++ b/ui/dbus.h @@ -150,5 +150,6 @@ void dbus_display_notify(DBusDisplayEvent *event); void dbus_chardev_init(DBusDisplay *dpy); void dbus_clipboard_init(DBusDisplay *dpy); +void dbus_clipboard_fini(DBusDisplay *dpy); #endif /* UI_DBUS_H */ From 3cae0b46be5416b26039df5259ffc8fcf2989516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Fri, 13 Mar 2026 20:54:47 +0400 Subject: [PATCH 4/6] ui/vnc-jobs: fix VncRectEntry leak on job cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a VncJob is freed, its associated VncRectEntry list must also be freed. Previously, vnc_job_push() and the disconnected path in vnc_worker_thread_loop() called g_free(job) directly, leaking all VncRectEntry allocations. Introduce vnc_job_free() which iterates and frees the rectangle entries before freeing the job itself, and use it in both paths. Also add QLIST_REMOVE() in the worker loop before g_free(entry), so that entries processed during normal operation are properly unlinked. Without this, vnc_job_free() would iterate dangling pointers to already-freed entries, causing use-after-free. Fixes: bd023f953e5e ("vnc: threaded VNC server") Reviewed-by: Daniel P. Berrangé Signed-off-by: Marc-André Lureau --- ui/vnc-jobs.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index b296d19e08..ca625da6d0 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -107,11 +107,25 @@ int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h) return 1; } +static void vnc_job_free(VncJob *job) +{ + VncRectEntry *entry, *tmp; + + if (!job) { + return; + } + QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) { + /* no need for QLIST_REMOVE(entry, next) */ + g_free(entry); + } + g_free(job); +} + void vnc_job_push(VncJob *job) { vnc_lock_queue(queue); if (queue->exit || QLIST_EMPTY(&job->rectangles)) { - g_free(job); + vnc_job_free(job); } else { QTAILQ_INSERT_TAIL(&queue->jobs, job, next); qemu_cond_broadcast(&queue->cond); @@ -296,6 +310,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) n_rectangles += n; } } + QLIST_REMOVE(entry, next); g_free(entry); } trace_vnc_job_nrects(&vs, job, n_rectangles); @@ -324,7 +339,7 @@ disconnected: QTAILQ_REMOVE(&queue->jobs, job, next); vnc_unlock_queue(queue); qemu_cond_broadcast(&queue->cond); - g_free(job); + vnc_job_free(job); vs.magic = 0; return 0; } From eea54988fd161a8bb80ae7b91382e5929c90de26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Fri, 13 Mar 2026 22:29:12 +0400 Subject: [PATCH 5/6] ui/vnc-jobs: clear source tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoid potentially removing a dangling source & simplify code. Reviewed-by: Daniel P. Berrangé Signed-off-by: Marc-André Lureau --- ui/vnc-jobs.c | 4 +--- ui/vnc.c | 31 +++++++------------------------ 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index ca625da6d0..ec90ae6d5f 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -162,9 +162,7 @@ void vnc_jobs_consume_buffer(VncState *vs) vnc_lock_output(vs); if (vs->jobs_buffer.offset) { if (vs->ioc != NULL && buffer_empty(&vs->output)) { - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); if (vs->disconnecting == FALSE) { vs->ioc_tag = qio_channel_add_watch( vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_OUT, diff --git a/ui/vnc.c b/ui/vnc.c index 952976e964..ccc73bd7aa 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -1301,10 +1301,7 @@ static void vnc_disconnect_start(VncState *vs) } trace_vnc_client_disconnect_start(vs, vs->ioc); vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - vs->ioc_tag = 0; - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); qio_channel_close(vs->ioc, NULL); vs->disconnecting = TRUE; } @@ -1462,9 +1459,7 @@ static size_t vnc_client_write_plain(VncState *vs) } if (vs->output.offset == 0) { - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); vs->ioc_tag = qio_channel_add_watch( vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR, vnc_client_io, vs, NULL); @@ -1500,9 +1495,7 @@ static void vnc_client_write(VncState *vs) if (vs->output.offset) { vnc_client_write_locked(vs); } else if (vs->ioc != NULL) { - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); vs->ioc_tag = qio_channel_add_watch( vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR, vnc_client_io, vs, NULL); @@ -1638,10 +1631,7 @@ gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED, } if (vs->disconnecting) { - if (vs->ioc_tag != 0) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = 0; + g_clear_handle_id(&vs->ioc_tag, g_source_remove); } return TRUE; } @@ -1684,9 +1674,7 @@ void vnc_write(VncState *vs, const void *data, size_t len) buffer_reserve(&vs->output, len); if (vs->ioc != NULL && buffer_empty(&vs->output)) { - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); vs->ioc_tag = qio_channel_add_watch( vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_OUT, vnc_client_io, vs, NULL); @@ -1734,10 +1722,7 @@ void vnc_flush(VncState *vs) vnc_client_write_locked(vs); } if (vs->disconnecting) { - if (vs->ioc_tag != 0) { - g_source_remove(vs->ioc_tag); - } - vs->ioc_tag = 0; + g_clear_handle_id(&vs->ioc_tag, g_source_remove); } vnc_unlock_output(vs); } @@ -3342,9 +3327,7 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, VNC_DEBUG("New client on socket %p\n", vs->sioc); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); qio_channel_set_blocking(vs->ioc, false, &error_abort); - if (vs->ioc_tag) { - g_source_remove(vs->ioc_tag); - } + g_clear_handle_id(&vs->ioc_tag, g_source_remove); if (websocket) { vs->websocket = 1; if (vd->tlscreds) { From 425f084fd2dc9838c82eebbfdb88eab5a545ec97 Mon Sep 17 00:00:00 2001 From: Anton Kuchin Date: Fri, 27 Mar 2026 16:45:51 +0000 Subject: [PATCH 6/6] tests: don't build audio tests when no audio drivers are enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When there are no audio drivers configure fails with "ERROR: Command cannot have '@INPUT@', since no input files were specified". Fixes: 3220b38a8d ("tests: start manual audio backend test") Signed-off-by: Anton Kuchin Message-ID: [ Marc-André - use empty modinfo stub ] Reviewed-by: Marc-André Lureau --- tests/audio/meson.build | 14 ++++++++------ tests/audio/modinfo-stub.c | 5 +++++ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 tests/audio/modinfo-stub.c diff --git a/tests/audio/meson.build b/tests/audio/meson.build index 84754bde22..be96313a63 100644 --- a/tests/audio/meson.build +++ b/tests/audio/meson.build @@ -6,12 +6,14 @@ endif modinfo_dep = not_found if enable_modules - modinfo_src = custom_target('modinfo.c', - output: 'modinfo.c', - input: audio_modinfo_files, - command: [modinfo_generate, '--skip-missing-deps', '@INPUT@'], - capture: true) - + modinfo_src = 'modinfo-stub.c' + if audio_modinfo_files.length() != 0 + modinfo_src = custom_target('modinfo.c', + output: 'modinfo.c', + input: audio_modinfo_files, + command: [modinfo_generate, '--skip-missing-deps', '@INPUT@'], + capture: true) + endif modinfo_lib = static_library('modinfo.c', modinfo_src) modinfo_dep = declare_dependency(link_with: modinfo_lib) endif diff --git a/tests/audio/modinfo-stub.c b/tests/audio/modinfo-stub.c new file mode 100644 index 0000000000..1cae8c6905 --- /dev/null +++ b/tests/audio/modinfo-stub.c @@ -0,0 +1,5 @@ +#include "qemu/osdep.h" +#include "qemu/module.h" +const QemuModinfo qemu_modinfo[] = { + { /* end of list */ } +};