aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorThomas Lukaszewicz <tluk@chromium.org>2024-01-05 00:50:49 +0000
committerThomas Lukaszewicz <tluk@chromium.org>2024-02-23 00:40:32 +0000
commit47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef (patch)
treebfb0da6215911644d283308c30ad3afb9faf5147 /tests
parentbuild: fix build and provide compat for OpenBSD (diff)
downloadwayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar.gz
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar.bz2
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar.lz
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar.xz
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.tar.zst
wayland-47de87263c88d3e23c7d7eb6d56ff75ddc8a02ef.zip
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] https://gitlab.freedesktop.org/wayland/wayland/-/blob/8f499bf4045f88f3a4b4b0a445befca467bebe20/src/wayland-server.c#L342 [2] https://gitlab.freedesktop.org/wayland/wayland/-/blob/8f499bf4045f88f3a4b4b0a445befca467bebe20/src/wayland-server.c#L1512 Signed-off-by: Thomas Lukaszewicz [thomaslukaszewicz@gmail.com](mailto:thomaslukaszewicz@gmail.com)
Diffstat (limited to 'tests')
-rw-r--r--tests/client-test.c47
1 files changed, 47 insertions, 0 deletions
diff --git a/tests/client-test.c b/tests/client-test.c
index 659bac1..5585c0c 100644
--- a/tests/client-test.c
+++ b/tests/client-test.c
@@ -158,3 +158,50 @@ TEST(client_destroy_listener)
wl_display_destroy(display);
}
+static void
+client_destroy_remove_link_notify(struct wl_listener *l, void *data)
+{
+ struct wl_client *client = data;
+ struct client_destroy_listener *listener =
+ wl_container_of(l, listener, listener);
+
+ /* The client destruction signal should not be emitted more than once. */
+ assert(!listener->done);
+ listener->done = true;
+
+ /* The client should have been removed from the display's list. */
+ assert(wl_list_empty(wl_client_get_link(client)));
+}
+
+/*
+ * Tests that wl_client_destroy() will remove the client from the display's
+ * client list to prevent client access during destruction.
+ */
+TEST(client_destroy_removes_link)
+{
+ struct wl_display *display;
+ struct wl_client *client;
+ struct client_destroy_listener destroy_listener;
+ int s[2];
+
+ assert(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, s) == 0);
+ display = wl_display_create();
+ assert(display);
+ client = wl_client_create(display, s[0]);
+ assert(client);
+
+ destroy_listener.listener.notify = client_destroy_remove_link_notify;
+ destroy_listener.done = false;
+ wl_client_add_destroy_listener(client, &destroy_listener.listener);
+
+ assert(wl_client_get_destroy_listener(client,
+ client_destroy_remove_link_notify) == &destroy_listener.listener);
+
+ wl_client_destroy(client);
+ assert(destroy_listener.done);
+
+ close(s[0]);
+ close(s[1]);
+
+ wl_display_destroy(display);
+}