From: "Daniel P. Berrangé" <berrange@redhat.com>
To: "Marc-André Lureau" <marcandre.lureau@gmail.com>
Cc: qemu-devel@nongnu.org, Grant Millar | Cylo <rid@cylo.io>
Subject: Re: [PATCH 2/2] io: fix use after free in websocket handshake code
Date: Tue, 30 Sep 2025 12:30:37 +0100 [thread overview]
Message-ID: <aNu_XWvcKI5TwCxW@redhat.com> (raw)
In-Reply-To: <CAJ+F1CJsoevDyvzpfSpcTop06aUT_HQ9CFzdHAy0wKyMFuhbrQ@mail.gmail.com>
On Tue, Sep 30, 2025 at 03:26:30PM +0400, Marc-André Lureau wrote:
> Hi
>
> On Tue, Sep 30, 2025 at 3:08 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > 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
> >
> > Reported-by: Grant Millar | Cylo <rid@cylo.io>
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> > include/io/channel-websock.h | 1 +
> > io/channel-websock.c | 16 ++++++++++------
> > 2 files changed, 11 insertions(+), 6 deletions(-)
> >
> > diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h
> > index e180827c57..d1e760e449 100644
> > --- a/include/io/channel-websock.h
> > +++ b/include/io/channel-websock.h
> > @@ -61,6 +61,7 @@ struct QIOChannelWebsock {
> > size_t payload_remain;
> > size_t pong_remain;
> > QIOChannelWebsockMask mask;
> > + guint hs_io_tag;
> > guint io_tag;
>
> I think it's worth a comment on why it needs two tags, and how they
> relate to each other.
>
> > Error *io_err;
> > gboolean io_eof;
> > diff --git a/io/channel-websock.c b/io/channel-websock.c
> > index 56d53355d5..588c313dfb 100644
> > --- a/io/channel-websock.c
> > +++ b/io/channel-websock.c
> > @@ -597,7 +597,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,
> > @@ -907,11 +907,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);
>
> There is still an untracked qio_channel_add_watch(). I suppose you
> checked that, it could use a comment explaining why it's left.
I don't see any untracked add_watch remaining ? (If you're just doing a
grep, you'll miss the prior line context where it captures the return
value)
>
> > }
> >
> >
> > @@ -1212,6 +1213,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);
> > }
> > --
> > 2.50.1
> >
>
>
> --
> Marc-André Lureau
>
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
prev parent reply other threads:[~2025-09-30 11:32 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-30 11:08 [PATCH 0/2] io: fix crash in VNC websock server when client quits early Daniel P. Berrangé
2025-09-30 11:08 ` [PATCH 1/2] io: move websock resource release to close method Daniel P. Berrangé
2025-09-30 11:21 ` Marc-André Lureau
2025-09-30 11:28 ` Daniel P. Berrangé
2025-09-30 11:08 ` [PATCH 2/2] io: fix use after free in websocket handshake code Daniel P. Berrangé
2025-09-30 11:26 ` Marc-André Lureau
2025-09-30 11:30 ` Daniel P. Berrangé [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aNu_XWvcKI5TwCxW@redhat.com \
--to=berrange@redhat.com \
--cc=marcandre.lureau@gmail.com \
--cc=qemu-devel@nongnu.org \
--cc=rid@cylo.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.