Mitigate UAF crashes due to wl_client_destroy reentrancy

There are situations in which a call into wl_client_destroy() can
result in a reentrant call into wl_client_destroy() - which
results in UAF / double free crashes.

For example, this can occur in the following scenario.

1. Server receives a message notifying it that a client has
   disconnected (WL_EVENT_HANGUP [1])

2. This beings client destruction with a call to wl_client_destroy()

3. wl_client_destroy() kicks off callbacks as client-associated
   resources are cleaned up and their destructors and destruction
   signals are invoked.

4. These callbacks eventually lead to an explicit call to
   wl_display_flush_clients() as the server attempts to flush
   events to other connected clients.

5. Since the client has already begun destruction, when it is
   reached in the iteration the flush fails wl_client_destroy()
   is called again [2].

This patch guards against this reentrant condition by removing
the client from the display's client list when wl_client_destroy()
is first called. This prevents access / iteration over the client
after wl_client_destroy() is called.

In the example above, wl_display_flush_clients() will pass over
the client currently undergoing destruction and the reentrant
call is avoided.

[1] 8f499bf404/src/wayland-server.c (L342)

[2] 8f499bf404/src/wayland-server.c (L1512)

Signed-off-by: Thomas Lukaszewicz [thomaslukaszewicz@gmail.com](mailto:thomaslukaszewicz@gmail.com)
This commit is contained in:
Thomas Lukaszewicz 2024-01-05 00:50:49 +00:00
parent d80bce5f1a
commit 47de87263c
2 changed files with 59 additions and 1 deletions

View file

@ -933,6 +933,18 @@ wl_client_get_destroy_late_listener(struct wl_client *client,
WL_EXPORT void
wl_client_destroy(struct wl_client *client)
{
/* wl_client_destroy() should not be called twice for the same client. */
if (wl_list_empty(&client->link)) {
client->error = 1;
wl_log("wl_client_destroy: encountered re-entrant client destruction.\n");
return;
}
wl_list_remove(&client->link);
/* Keep the client link safe to inspect. */
wl_list_init(&client->link);
wl_priv_signal_final_emit(&client->destroy_signal, client);
wl_client_flush(client);
@ -943,7 +955,6 @@ wl_client_destroy(struct wl_client *client)
wl_priv_signal_final_emit(&client->destroy_late_signal, client);
wl_list_remove(&client->link);
wl_list_remove(&client->resource_created_signal.listener_list);
if (client->data_dtor)