From efd8a4a0f757d6dabce9c25d4b5b485c5e243fc4 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:51:49 +0100 Subject: [PATCH 1/6] server: remove event listeners on fini This fixes a crash in wlroots listener checks. See #8509. --- sway/server.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sway/server.c b/sway/server.c index 5de6648f..fc2ba819 100644 --- a/sway/server.c +++ b/sway/server.c @@ -460,8 +460,29 @@ bool server_init(struct sway_server *server) { } void server_fini(struct sway_server *server) { + // remove listeners + wl_list_remove(&server->renderer_lost.link); + wl_list_remove(&server->new_output.link); + wl_list_remove(&server->layer_shell_surface.link); + wl_list_remove(&server->xdg_shell_toplevel.link); + wl_list_remove(&server->server_decoration.link); + wl_list_remove(&server->xdg_decoration.link); + wl_list_remove(&server->pointer_constraint.link); + wl_list_remove(&server->output_manager_apply.link); + wl_list_remove(&server->output_manager_test.link); + wl_list_remove(&server->output_power_manager_set_mode.link); +#if WLR_HAS_DRM_BACKEND + wl_list_remove(&server->drm_lease_request.link); +#endif + wl_list_remove(&server->tearing_control_new_object.link); + wl_list_remove(&server->xdg_activation_v1_request_activate.link); + wl_list_remove(&server->xdg_activation_v1_new_token.link); + wl_list_remove(&server->request_set_cursor_shape.link); + // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND + wl_list_remove(&server->xwayland_surface.link); + wl_list_remove(&server->xwayland_ready.link); wlr_xwayland_destroy(server->xwayland.wlr_xwayland); #endif wl_display_destroy_clients(server->wl_display); From 1c100769cd3ed6d6d320e0423444a8b07b27809d Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:52:25 +0100 Subject: [PATCH 2/6] input/input-manager: remove event listeners on fini This fixes a crash in wlroots listener checks. See #8509. --- include/sway/input/input-manager.h | 2 ++ sway/input/input-manager.c | 8 ++++++++ sway/server.c | 1 + 3 files changed, 11 insertions(+) diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index b014e18f..ba53a84e 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -39,6 +39,8 @@ struct sway_input_manager { struct sway_input_manager *input_manager_create(struct sway_server *server); +void input_manager_finish(struct sway_server *server); + bool input_manager_has_focus(struct sway_node *node); void input_manager_set_focus(struct sway_node *node); diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 99af4c69..7db598c7 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -493,6 +493,14 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) { return input; } +void input_manager_finish(struct sway_server *server) { + wl_list_remove(&server->input->new_input.link); + wl_list_remove(&server->input->virtual_keyboard_new.link); + wl_list_remove(&server->input->virtual_pointer_new.link); + wl_list_remove(&server->input->keyboard_shortcuts_inhibit_new_inhibitor.link); + wl_list_remove(&server->input->transient_seat_create.link); +} + bool input_manager_has_focus(struct sway_node *node) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { diff --git a/sway/server.c b/sway/server.c index fc2ba819..8ca6993c 100644 --- a/sway/server.c +++ b/sway/server.c @@ -478,6 +478,7 @@ void server_fini(struct sway_server *server) { wl_list_remove(&server->xdg_activation_v1_request_activate.link); wl_list_remove(&server->xdg_activation_v1_new_token.link); wl_list_remove(&server->request_set_cursor_shape.link); + input_manager_finish(server); // TODO: free sway-specific resources #if WLR_HAS_XWAYLAND From 12eec851548cfcc87f1161123553d88f3e770d29 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 01:52:57 +0100 Subject: [PATCH 3/6] desktop/idle_inhibit: remove event listeners on destroy This fixes a crash in wlroots listener checks. See #8509. --- include/sway/desktop/idle_inhibit_v1.h | 1 + sway/desktop/idle_inhibit_v1.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h index 84cc666d..447ac870 100644 --- a/include/sway/desktop/idle_inhibit_v1.h +++ b/include/sway/desktop/idle_inhibit_v1.h @@ -13,6 +13,7 @@ enum sway_idle_inhibit_mode { struct sway_idle_inhibit_manager_v1 { struct wlr_idle_inhibit_manager_v1 *wlr_manager; struct wl_listener new_idle_inhibitor_v1; + struct wl_listener manager_destroy; struct wl_list inhibitors; }; diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index f3af7aa1..3a0e694a 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -44,6 +44,14 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) { sway_idle_inhibit_v1_check_active(); } +void handle_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_idle_inhibit_manager_v1 *manager = + wl_container_of(listener, manager, manager_destroy); + + wl_list_remove(&manager->manager_destroy.link); + wl_list_remove(&manager->new_idle_inhibitor_v1.link); +} + void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, enum sway_idle_inhibit_mode mode) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; @@ -153,6 +161,9 @@ bool sway_idle_inhibit_manager_v1_init(void) { wl_signal_add(&manager->wlr_manager->events.new_inhibitor, &manager->new_idle_inhibitor_v1); manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1; + wl_signal_add(&manager->wlr_manager->events.destroy, + &manager->manager_destroy); + manager->manager_destroy.notify = handle_manager_destroy; wl_list_init(&manager->inhibitors); return true; From 351f8c84a82cccaf85497a93dc31f12a4d207986 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 22:45:53 +0100 Subject: [PATCH 4/6] input/text_input: remove event listeners on destroy sway_input_method_relay can be destroyed from two sources, either the seat is destroyed or the manager protocol objects are destroyed due compositor exit. Therefore, finish must check whether it has already been called. This fixes a crash in wlroots listener checks. See #8509. --- include/sway/input/text_input.h | 2 ++ sway/input/text_input.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/sway/input/text_input.h b/include/sway/input/text_input.h index 1993f928..1818749a 100644 --- a/include/sway/input/text_input.h +++ b/include/sway/input/text_input.h @@ -25,8 +25,10 @@ struct sway_input_method_relay { struct wlr_input_method_v2 *input_method; // doesn't have to be present struct wl_listener text_input_new; + struct wl_listener text_input_manager_destroy; struct wl_listener input_method_new; + struct wl_listener input_method_manager_destroy; struct wl_listener input_method_commit; struct wl_listener input_method_new_popup_surface; struct wl_listener input_method_grab_keyboard; diff --git a/sway/input/text_input.c b/sway/input/text_input.c index a46f833c..97be955e 100644 --- a/sway/input/text_input.c +++ b/sway/input/text_input.c @@ -597,6 +597,20 @@ static void relay_handle_input_method(struct wl_listener *listener, } } +static void relay_handle_text_input_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + text_input_manager_destroy); + + sway_input_method_relay_finish(relay); +} + +static void relay_handle_input_method_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + input_method_manager_destroy); + + sway_input_method_relay_finish(relay); +} + void sway_input_method_relay_init(struct sway_seat *seat, struct sway_input_method_relay *relay) { relay->seat = seat; @@ -606,16 +620,28 @@ void sway_input_method_relay_init(struct sway_seat *seat, relay->text_input_new.notify = relay_handle_text_input; wl_signal_add(&server.text_input->events.text_input, &relay->text_input_new); + relay->text_input_manager_destroy.notify = relay_handle_text_input_manager_destroy; + wl_signal_add(&server.text_input->events.destroy, + &relay->text_input_manager_destroy); relay->input_method_new.notify = relay_handle_input_method; wl_signal_add( &server.input_method->events.input_method, &relay->input_method_new); + relay->input_method_manager_destroy.notify = relay_handle_input_method_manager_destroy; + wl_signal_add(&server.input_method->events.destroy, + &relay->input_method_manager_destroy); } void sway_input_method_relay_finish(struct sway_input_method_relay *relay) { + // return early if finish was already called + // can be called due to seat or manager protocol object being destroyed + if (!relay->input_method_new.link.prev) return; + wl_list_remove(&relay->input_method_new.link); + wl_list_remove(&relay->input_method_manager_destroy.link); wl_list_remove(&relay->text_input_new.link); + wl_list_remove(&relay->text_input_manager_destroy.link); } void sway_input_method_relay_set_focus(struct sway_input_method_relay *relay, From 59f5ca0fe2ab505fc21de80b81b916fb87b7b18a Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Tue, 18 Feb 2025 23:14:06 +0100 Subject: [PATCH 5/6] tree/container: remove event listeners on destroy Change begin_destroy to remove event listeners before the final destroy, since otherwise event listeners would be removed twice, which crashes. This fixes a crash in wlroots listener checks. See #8509. --- include/sway/tree/container.h | 1 + sway/tree/container.c | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 4608b8ac..4fb2d720 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -95,6 +95,7 @@ struct sway_container { struct wl_listener output_enter; struct wl_listener output_leave; + struct wl_listener output_handler_destroy; struct sway_container_state current; struct sway_container_state pending; diff --git a/sway/tree/container.c b/sway/tree/container.c index 6ff4036f..0385d7c1 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -50,6 +50,14 @@ static void handle_output_leave( } } +static void handle_destroy( + struct wl_listener *listener, void *data) { + struct sway_container *con = wl_container_of( + listener, con, output_handler_destroy); + + container_begin_destroy(con); +} + static bool handle_point_accepts_input( struct wlr_scene_buffer *buffer, double *x, double *y) { return false; @@ -135,6 +143,9 @@ struct sway_container *container_create(struct sway_view *view) { c->output_leave.notify = handle_output_leave; wl_signal_add(&c->output_handler->events.output_leave, &c->output_leave); + c->output_handler_destroy.notify = handle_destroy; + wl_signal_add(&c->output_handler->node.events.destroy, + &c->output_handler_destroy); c->output_handler->point_accepts_input = handle_point_accepts_input; } } @@ -508,8 +519,6 @@ void container_destroy(struct sway_container *con) { if (con->view && con->view->container == con) { con->view->container = NULL; - wl_list_remove(&con->output_enter.link); - wl_list_remove(&con->output_leave.link); wlr_scene_node_destroy(&con->output_handler->node); if (con->view->destroying) { view_destroy(con->view); @@ -552,6 +561,12 @@ void container_begin_destroy(struct sway_container *con) { if (con->pending.parent || con->pending.workspace) { container_detach(con); } + + if (con->view && con->view->container == con) { + wl_list_remove(&con->output_enter.link); + wl_list_remove(&con->output_leave.link); + wl_list_remove(&con->output_handler_destroy.link); + } } void container_reap_empty(struct sway_container *con) { From 95117e8209425191a22418a98028f66004f0c0c4 Mon Sep 17 00:00:00 2001 From: Ferdinand Bachmann Date: Fri, 21 Mar 2025 18:35:36 +0100 Subject: [PATCH 6/6] server: recreate renderer in idle callback to avoid UAF Destroying the wlr_renderer in a callback to its own renderer_lost event is unsafe due to wl_signal_emit*() still accessing it after it was destroyed. Delegate recreation of renderer to an idle callback and ensure that only one such idle callback is scheduled at a time by storing the returned event source. --- include/sway/server.h | 1 + sway/server.c | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/sway/server.h b/include/sway/server.h index feb516c5..b1d7523c 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -46,6 +46,7 @@ struct sway_server { struct wl_listener new_output; struct wl_listener renderer_lost; + struct wl_event_source *recreating_renderer; struct wlr_idle_notifier_v1 *idle_notifier_v1; struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1; diff --git a/sway/server.c b/sway/server.c index 8ca6993c..9d6bd39e 100644 --- a/sway/server.c +++ b/sway/server.c @@ -182,11 +182,11 @@ static void detect_proprietary(struct wlr_backend *backend, void *data) { drmFreeVersion(version); } -static void handle_renderer_lost(struct wl_listener *listener, void *data) { - struct sway_server *server = wl_container_of(listener, server, renderer_lost); +static void do_renderer_recreate(void *data) { + struct sway_server *server = (struct sway_server *)data; + server->recreating_renderer = NULL; sway_log(SWAY_INFO, "Re-creating renderer after GPU reset"); - struct wlr_renderer *renderer = wlr_renderer_autocreate(server->backend); if (renderer == NULL) { sway_log(SWAY_ERROR, "Unable to create renderer"); @@ -221,6 +221,18 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) { wlr_renderer_destroy(old_renderer); } +static void handle_renderer_lost(struct wl_listener *listener, void *data) { + struct sway_server *server = wl_container_of(listener, server, renderer_lost); + + if (server->recreating_renderer != NULL) { + sway_log(SWAY_DEBUG, "Re-creation of renderer already scheduled"); + return; + } + + sway_log(SWAY_INFO, "Scheduling re-creation of renderer after GPU reset"); + server->recreating_renderer = wl_event_loop_add_idle(server->wl_event_loop, do_renderer_recreate, server); +} + bool server_init(struct sway_server *server) { sway_log(SWAY_DEBUG, "Initializing Wayland server"); server->wl_display = wl_display_create();