From 4157dd67f6b2bc22695cff53b11f7753b083cc00 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Sat, 31 Jan 2026 14:08:46 -0600 Subject: [PATCH] Fix use-after-free by moving config-reload into the manage cycle --- src/Context.zig | 23 +++++++++++++++++++++++ src/WindowManager.zig | 9 ++++++--- src/XkbBindings.zig | 15 ++++----------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Context.zig b/src/Context.zig index 8fbdc5d..552ab66 100644 --- a/src/Context.zig +++ b/src/Context.zig @@ -21,6 +21,13 @@ xkb_bindings: *XkbBindings, // WM Configuration config: *Config, +/// State consumed in manage() phase, reset at end of manage(). +pending_manage: PendingManage = .{}, + +pub const PendingManage = struct { + config: ?*Config = null, +}; + pub fn create( wl_display: *wl.Display, wl_registry: *wl.Registry, @@ -52,6 +59,22 @@ pub fn destroy(context: *Context) void { utils.allocator.destroy(context); } +pub fn manage(context: *Context) void { + defer context.pending_manage = .{}; + + if (context.pending_manage.config) |new_config| { + // Destroy all existing bindings + var it = context.xkb_bindings.bindings.safeIterator(.forward); + while (it.next()) |binding| { + binding.link.remove(); + binding.destroy(); + } + context.config.destroy(); + context.config = new_config; + context.initialized = false; + } +} + const std = @import("std"); const wayland = @import("wayland"); diff --git a/src/WindowManager.zig b/src/WindowManager.zig index d125c0a..e6db884 100644 --- a/src/WindowManager.zig +++ b/src/WindowManager.zig @@ -186,9 +186,12 @@ fn manage_start(wm: *WindowManager) void { const river_window_manager_v1 = wm.river_window_manager_v1; const context = wm.context; + + // This gets used shortly, so it goes at the beginning + context.manage(); + if (!context.initialized) { - // This code (should) only be run once while initializing the WM, so let's - // mark it as cold. + // This code runs during initial startup and after config reloads. @branchHint(.cold); context.initialized = true; @@ -220,7 +223,7 @@ fn manage_start(wm: *WindowManager) void { } } - // Manage the WM itself first + // Manage the WM itself before outputs/windows/seats if (wm.pending_manage.primary_ratio) |primary_ratio| { // Ratios outside of this range can cause crashes anyways (when doing the layout calulcation) std.debug.assert(primary_ratio >= 0.10 and primary_ratio <= 0.90); diff --git a/src/XkbBindings.zig b/src/XkbBindings.zig index 34bedfd..1429b0b 100644 --- a/src/XkbBindings.zig +++ b/src/XkbBindings.zig @@ -45,7 +45,7 @@ const XkbBinding = struct { return xkb_binding; } - fn destroy(xkb_binding: *XkbBinding) void { + pub fn destroy(xkb_binding: *XkbBinding) void { xkb_binding.xkb_binding_v1.destroy(); utils.allocator.destroy(xkb_binding); } @@ -137,16 +137,9 @@ const XkbBinding = struct { log.err("Failed to reload Config. Not deleting old one", .{}); return; }; - // Clean up old one - var it = context.xkb_bindings.bindings.safeIterator(.forward); - while (it.next()) |binding| { - binding.link.remove(); - binding.destroy(); - } - context.config.destroy(); - // Replace the old one - context.config = new_config; - context.initialized = false; + // Send the config to the WM to handle during next manage + context.pending_manage.config = new_config; + context.wm.river_window_manager_v1.manageDirty(); }, .toggle_fullscreen => { const seat = context.wm.seats.first() orelse return;