From 116fe398216f1eec525285894f77fc645f194b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 3 Oct 2025 14:22:12 +0100 Subject: [PATCH 1/4] io: release active GSource in TLS channel finalizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While code is supposed to call qio_channel_close() before releasing the last reference on an QIOChannel, this is not guaranteed. QIOChannelFile and QIOChannelSocket both cleanup resources in their finalizer if the close operation was missed. This ensures the TLS channel will do the same failsafe cleanup. Reviewed-by: Eric Blake Signed-off-by: Daniel P. Berrangé --- io/channel-tls.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/io/channel-tls.c b/io/channel-tls.c index 34476e6b7b..f205ac00e2 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -252,6 +252,16 @@ static void qio_channel_tls_finalize(Object *obj) { QIOChannelTLS *ioc = QIO_CHANNEL_TLS(obj); + if (ioc->hs_ioc_tag) { + trace_qio_channel_tls_handshake_cancel(ioc); + g_clear_handle_id(&ioc->hs_ioc_tag, g_source_remove); + } + + if (ioc->bye_ioc_tag) { + trace_qio_channel_tls_bye_cancel(ioc); + g_clear_handle_id(&ioc->bye_ioc_tag, g_source_remove); + } + object_unref(OBJECT(ioc->master)); qcrypto_tls_session_free(ioc->session); } -- Gitee From 4520d9fa8b5963d7dedb566b8e4fa8d166ea27fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Tue, 30 Sep 2025 11:58:35 +0100 Subject: [PATCH 2/4] io: move websock resource release to close method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The QIOChannelWebsock object releases all its resources in the finalize callback. This is later than desired, as callers expect to be able to call qio_channel_close() to fully close a channel and release resources related to I/O. The logic in the finalize method is at most a failsafe to handle cases where a consumer forgets to call qio_channel_close. This adds equivalent logic to the close method to release the resources, using g_clear_handle_id/g_clear_pointer to be robust against repeated invocations. The finalize method is tweaked so that the GSource is removed before releasing the underlying channel. Reviewed-by: Eric Blake Signed-off-by: Daniel P. Berrangé --- io/channel-websock.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/io/channel-websock.c b/io/channel-websock.c index d9fc27a003..d4f1e0f209 100644 --- a/io/channel-websock.c +++ b/io/channel-websock.c @@ -921,13 +921,13 @@ static void qio_channel_websock_finalize(Object *obj) buffer_free(&ioc->encinput); buffer_free(&ioc->encoutput); buffer_free(&ioc->rawinput); - object_unref(OBJECT(ioc->master)); if (ioc->io_tag) { g_source_remove(ioc->io_tag); } if (ioc->io_err) { error_free(ioc->io_err); } + object_unref(OBJECT(ioc->master)); } @@ -1216,6 +1216,15 @@ static int qio_channel_websock_close(QIOChannel *ioc, QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); trace_qio_channel_websock_close(ioc); + buffer_free(&wioc->encinput); + buffer_free(&wioc->encoutput); + buffer_free(&wioc->rawinput); + if (wioc->io_tag) { + g_clear_handle_id(&wioc->io_tag, g_source_remove); + } + if (wioc->io_err) { + g_clear_pointer(&wioc->io_err, error_free); + } return qio_channel_close(wioc->master, errp); } -- Gitee From b1dec18da3fbee675dc7d22a80fd0138f192febd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Tue, 30 Sep 2025 12:03:15 +0100 Subject: [PATCH 3/4] io: fix use after free in websocket handshake code(CVE-2025-11234) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the QIOChannelWebsock object is freed while it is waiting to complete a handshake, a GSource is leaked. This can lead to the callback firing later on and triggering a use-after-free in the use of the channel. This was observed in the VNC server with the following trace from valgrind: ==2523108== Invalid read of size 4 ==2523108== at 0x4054A24: vnc_disconnect_start (vnc.c:1296) ==2523108== by 0x4054A24: vnc_client_error (vnc.c:1392) ==2523108== by 0x4068A09: vncws_handshake_done (vnc-ws.c:105) ==2523108== by 0x44863B4: qio_task_complete (task.c:197) ==2523108== by 0x448343D: qio_channel_websock_handshake_io (channel-websock.c:588) ==2523108== by 0x6EDB862: UnknownInlinedFun (gmain.c:3398) ==2523108== by 0x6EDB862: g_main_context_dispatch_unlocked.lto_priv.0 (gmain.c:4249) ==2523108== by 0x6EDBAE4: g_main_context_dispatch (gmain.c:4237) ==2523108== by 0x45EC79F: glib_pollfds_poll (main-loop.c:287) ==2523108== by 0x45EC79F: os_host_main_loop_wait (main-loop.c:310) ==2523108== by 0x45EC79F: main_loop_wait (main-loop.c:589) ==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835) ==2523108== by 0x454F300: qemu_default_main (main.c:37) ==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58) ==2523108== Address 0x57a6e0dc is 28 bytes inside a block of size 103,608 free'd ==2523108== at 0x5F2FE43: free (vg_replace_malloc.c:989) ==2523108== by 0x6EDC444: g_free (gmem.c:208) ==2523108== by 0x4053F23: vnc_update_client (vnc.c:1153) ==2523108== by 0x4053F23: vnc_refresh (vnc.c:3225) ==2523108== by 0x4042881: dpy_refresh (console.c:880) ==2523108== by 0x4042881: gui_update (console.c:90) ==2523108== by 0x45EFA1B: timerlist_run_timers.part.0 (qemu-timer.c:562) ==2523108== by 0x45EFC8F: timerlist_run_timers (qemu-timer.c:495) ==2523108== by 0x45EFC8F: qemu_clock_run_timers (qemu-timer.c:576) ==2523108== by 0x45EFC8F: qemu_clock_run_all_timers (qemu-timer.c:663) ==2523108== by 0x45EC765: main_loop_wait (main-loop.c:600) ==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835) ==2523108== by 0x454F300: qemu_default_main (main.c:37) ==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58) ==2523108== Block was alloc'd at ==2523108== at 0x5F343F3: calloc (vg_replace_malloc.c:1675) ==2523108== by 0x6EE2F81: g_malloc0 (gmem.c:133) ==2523108== by 0x4057DA3: vnc_connect (vnc.c:3245) ==2523108== by 0x448591B: qio_net_listener_channel_func (net-listener.c:54) ==2523108== by 0x6EDB862: UnknownInlinedFun (gmain.c:3398) ==2523108== by 0x6EDB862: g_main_context_dispatch_unlocked.lto_priv.0 (gmain.c:4249) ==2523108== by 0x6EDBAE4: g_main_context_dispatch (gmain.c:4237) ==2523108== by 0x45EC79F: glib_pollfds_poll (main-loop.c:287) ==2523108== by 0x45EC79F: os_host_main_loop_wait (main-loop.c:310) ==2523108== by 0x45EC79F: main_loop_wait (main-loop.c:589) ==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835) ==2523108== by 0x454F300: qemu_default_main (main.c:37) ==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58) ==2523108== The above can be reproduced by launching QEMU with $ qemu-system-x86_64 -vnc localhost:0,websocket=5700 and then repeatedly running: for i in {1..100}; do (echo -n "GET / HTTP/1.1" && sleep 0.05) | nc -w 1 localhost 5700 & done CVE-2025-11234 Reported-by: Grant Millar | Cylo Reviewed-by: Eric Blake Signed-off-by: Daniel P. Berrangé --- include/io/channel-websock.h | 3 ++- io/channel-websock.c | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h index e180827c57..6700cf8946 100644 --- a/include/io/channel-websock.h +++ b/include/io/channel-websock.h @@ -61,7 +61,8 @@ struct QIOChannelWebsock { size_t payload_remain; size_t pong_remain; QIOChannelWebsockMask mask; - guint io_tag; + guint hs_io_tag; /* tracking handshake task */ + guint io_tag; /* tracking watch task */ Error *io_err; gboolean io_eof; uint8_t opcode; diff --git a/io/channel-websock.c b/io/channel-websock.c index d4f1e0f209..1841a473ed 100644 --- a/io/channel-websock.c +++ b/io/channel-websock.c @@ -545,6 +545,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); qio_task_complete(task); + wioc->hs_io_tag = 0; return FALSE; } @@ -560,6 +561,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, trace_qio_channel_websock_handshake_complete(ioc); qio_task_complete(task); } + wioc->hs_io_tag = 0; return FALSE; } trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT); @@ -586,6 +588,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); qio_task_complete(task); + wioc->hs_io_tag = 0; return FALSE; } if (ret == 0) { @@ -597,7 +600,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, error_propagate(&wioc->io_err, err); trace_qio_channel_websock_handshake_reply(ioc); - qio_channel_add_watch( + wioc->hs_io_tag = qio_channel_add_watch( wioc->master, G_IO_OUT, qio_channel_websock_handshake_send, @@ -906,11 +909,12 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, trace_qio_channel_websock_handshake_start(ioc); trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); - qio_channel_add_watch(ioc->master, - G_IO_IN, - qio_channel_websock_handshake_io, - task, - NULL); + ioc->hs_io_tag = qio_channel_add_watch( + ioc->master, + G_IO_IN, + qio_channel_websock_handshake_io, + task, + NULL); } @@ -921,6 +925,9 @@ static void qio_channel_websock_finalize(Object *obj) buffer_free(&ioc->encinput); buffer_free(&ioc->encoutput); buffer_free(&ioc->rawinput); + if (ioc->hs_io_tag) { + g_source_remove(ioc->hs_io_tag); + } if (ioc->io_tag) { g_source_remove(ioc->io_tag); } @@ -1219,6 +1226,9 @@ static int qio_channel_websock_close(QIOChannel *ioc, buffer_free(&wioc->encinput); buffer_free(&wioc->encoutput); buffer_free(&wioc->rawinput); + if (wioc->hs_io_tag) { + g_clear_handle_id(&wioc->hs_io_tag, g_source_remove); + } if (wioc->io_tag) { g_clear_handle_id(&wioc->io_tag, g_source_remove); } -- Gitee From 909e1c4817573ba225a92ac3abb5f5ebee4771f9 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Tue, 28 Oct 2025 16:00:42 +0000 Subject: [PATCH 4/4] net: pad packets to minimum length in qemu_receive_packet()(CVE-2025-12464) In commits like 969e50b61a28 ("net: Pad short frames to minimum size before sending from SLiRP/TAP") we switched away from requiring network devices to handle short frames to instead having the net core code do the padding of short frames out to the ETH_ZLEN minimum size. We then dropped the code for handling short frames from the network devices in a series of commits like 140eae9c8f7 ("hw/net: e1000: Remove the logic of padding short frames in the receive path"). This missed one route where the device's receive code can still see a short frame: if the device is in loopback mode and it transmits a short frame via the qemu_receive_packet() function, this will be fed back into its own receive code without being padded. Add the padding logic to qemu_receive_packet(). This fixes a buffer overrun which can be triggered in the e1000_receive_iov() logic via the loopback code path. Other devices that use qemu_receive_packet() to implement loopback are cadence_gem, dp8393x, lan9118, msf2-emac, pcnet, rtl8139 and sungem. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3043 Reviewed-by: Akihiko Odaki Signed-off-by: Peter Maydell Signed-off-by: Jason Wang --- net/net.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/net/net.c b/net/net.c index abdb9dfdc5..53cb591f4e 100644 --- a/net/net.c +++ b/net/net.c @@ -708,10 +708,20 @@ ssize_t qemu_send_packet(NetClientState *nc, const uint8_t *buf, int size) ssize_t qemu_receive_packet(NetClientState *nc, const uint8_t *buf, int size) { + uint8_t min_pkt[ETH_ZLEN]; + size_t min_pktsz = sizeof(min_pkt); + if (!qemu_can_receive_packet(nc)) { return 0; } + if (net_peer_needs_padding(nc)) { + if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) { + buf = min_pkt; + size = min_pktsz; + } + } + return qemu_net_queue_receive(nc->incoming_queue, buf, size); } -- Gitee