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 <rid@cylo.io>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
109 lines
3.4 KiB
C
109 lines
3.4 KiB
C
/*
|
|
* QEMU I/O channels driver websockets
|
|
*
|
|
* Copyright (c) 2015 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#ifndef QIO_CHANNEL_WEBSOCK_H
|
|
#define QIO_CHANNEL_WEBSOCK_H
|
|
|
|
#include "io/channel.h"
|
|
#include "qemu/buffer.h"
|
|
#include "io/task.h"
|
|
#include "qom/object.h"
|
|
|
|
#define TYPE_QIO_CHANNEL_WEBSOCK "qio-channel-websock"
|
|
OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelWebsock, QIO_CHANNEL_WEBSOCK)
|
|
|
|
typedef union QIOChannelWebsockMask QIOChannelWebsockMask;
|
|
|
|
union QIOChannelWebsockMask {
|
|
char c[4];
|
|
uint32_t u;
|
|
};
|
|
|
|
/**
|
|
* QIOChannelWebsock
|
|
*
|
|
* The QIOChannelWebsock class provides a channel wrapper which
|
|
* can transparently run the HTTP websockets protocol. This is
|
|
* usually used over a TCP socket, but there is actually no
|
|
* technical restriction on which type of master channel is
|
|
* used as the transport.
|
|
*
|
|
* This channel object is currently only capable of running as
|
|
* a websocket server and is a pretty crude implementation
|
|
* of it, not supporting the full websockets protocol feature
|
|
* set. It is sufficient to use with a simple websockets
|
|
* client for encapsulating VNC for noVNC in-browser client.
|
|
*/
|
|
|
|
struct QIOChannelWebsock {
|
|
QIOChannel parent;
|
|
QIOChannel *master;
|
|
Buffer encinput;
|
|
Buffer encoutput;
|
|
Buffer rawinput;
|
|
size_t payload_remain;
|
|
size_t pong_remain;
|
|
QIOChannelWebsockMask mask;
|
|
guint hs_io_tag; /* tracking handshake task */
|
|
guint io_tag; /* tracking watch task */
|
|
Error *io_err;
|
|
gboolean io_eof;
|
|
uint8_t opcode;
|
|
};
|
|
|
|
/**
|
|
* qio_channel_websock_new_server:
|
|
* @master: the underlying channel object
|
|
*
|
|
* Create a new websockets channel that runs the server
|
|
* side of the protocol.
|
|
*
|
|
* After creating the channel, it is mandatory to call
|
|
* the qio_channel_websock_handshake() method before attempting
|
|
* todo any I/O on the channel.
|
|
*
|
|
* Once the handshake has completed, all I/O should be done
|
|
* via the new websocket channel object and not the original
|
|
* master channel
|
|
*
|
|
* Returns: the new websockets channel object
|
|
*/
|
|
QIOChannelWebsock *
|
|
qio_channel_websock_new_server(QIOChannel *master);
|
|
|
|
/**
|
|
* qio_channel_websock_handshake:
|
|
* @ioc: the websocket channel object
|
|
* @func: the callback to invoke when completed
|
|
* @opaque: opaque data to pass to @func
|
|
* @destroy: optional callback to free @opaque
|
|
*
|
|
* Perform the websocket handshake. This method
|
|
* will return immediately and the handshake will
|
|
* continue in the background, provided the main
|
|
* loop is running. When the handshake is complete,
|
|
* or fails, the @func callback will be invoked.
|
|
*/
|
|
void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
|
|
QIOTaskFunc func,
|
|
gpointer opaque,
|
|
GDestroyNotify destroy);
|
|
|
|
#endif /* QIO_CHANNEL_WEBSOCK_H */
|