From 1b63c463a79dd40d0f13d9ada0d58f6be7e9d56f Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 15 Mar 2024 14:19:36 +0100 Subject: [PATCH] security-context: implement protocol Sensitive Wayland protocols such as wlr_screencopy and wlr_data_control (clipboard managment) are now blocked by default inside security contexts (e.g. flatpak 1.15.6 or later). User configuration of the allowlist/blocklist is TODO. --- deps/zig-wlroots | 2 +- river/LockManager.zig | 6 +- river/Root.zig | 8 +- river/Server.zig | 169 +++++++++++++++++++++++++++++++++--------- 4 files changed, 145 insertions(+), 40 deletions(-) diff --git a/deps/zig-wlroots b/deps/zig-wlroots index 3789f0e..c1e19f0 160000 --- a/deps/zig-wlroots +++ b/deps/zig-wlroots @@ -1 +1 @@ -Subproject commit 3789f0e0c946fe2c9ac09dc09593cada93fd3797 +Subproject commit c1e19f0b9fbdaf4f54a4a2129cf2cd7e68ec09ea diff --git a/river/LockManager.zig b/river/LockManager.zig index 670a379..6d60e67 100644 --- a/river/LockManager.zig +++ b/river/LockManager.zig @@ -32,6 +32,8 @@ const Output = @import("Output.zig"); const log = std.log.scoped(.session_lock); +wlr_manager: *wlr.SessionLockManagerV1, + state: enum { /// No lock request has been made and the session is unlocked. unlocked, @@ -66,11 +68,11 @@ pub fn init(manager: *LockManager) !void { errdefer timer.remove(); manager.* = .{ + .wlr_manager = try wlr.SessionLockManagerV1.create(server.wl_server), .lock_surfaces_timer = timer, }; - const wlr_manager = try wlr.SessionLockManagerV1.create(server.wl_server); - wlr_manager.events.new_lock.add(&manager.new_lock); + manager.wlr_manager.events.new_lock.add(&manager.new_lock); } pub fn deinit(manager: *LockManager) void { diff --git a/river/Root.zig b/river/Root.zig index 5829861..76b642f 100644 --- a/river/Root.zig +++ b/river/Root.zig @@ -83,6 +83,9 @@ new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOu output_layout: *wlr.OutputLayout, layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange), +presentation: *wlr.Presentation, +xdg_output_manager: *wlr.XdgOutputManagerV1, + output_manager: *wlr.OutputManagerV1, manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = wl.Listener(*wlr.OutputConfigurationV1).init(handleManagerApply), @@ -128,8 +131,6 @@ pub fn init(root: *Root) !void { const outputs = try interactive_content.createSceneTree(); const override_redirect = if (build_options.xwayland) try interactive_content.createSceneTree(); - _ = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout); - const presentation = try wlr.Presentation.create(server.wl_server, server.backend); scene.setPresentation(presentation); @@ -164,6 +165,9 @@ pub fn init(root: *Root) !void { .output_layout = output_layout, .all_outputs = undefined, .active_outputs = undefined, + + .presentation = presentation, + .xdg_output_manager = try wlr.XdgOutputManagerV1.create(server.wl_server, output_layout), .output_manager = try wlr.OutputManagerV1.create(server.wl_server), .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server), .gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server), diff --git a/river/Server.zig b/river/Server.zig index a3e8118..e1fda5f 100644 --- a/river/Server.zig +++ b/river/Server.zig @@ -18,6 +18,7 @@ const Server = @This(); const build_options = @import("build_options"); const std = @import("std"); +const assert = std.debug.assert; const wlr = @import("wlroots"); const wl = @import("wayland").server.wl; @@ -54,34 +55,33 @@ session: ?*wlr.Session, renderer: *wlr.Renderer, allocator: *wlr.Allocator, +security_context_manager: *wlr.SecurityContextManagerV1, + +shm: *wlr.Shm, +drm: ?*wlr.Drm = null, +linux_dmabuf: ?*wlr.LinuxDmabufV1 = null, +single_pixel_buffer_manager: *wlr.SinglePixelBufferManagerV1, + +viewporter: *wlr.Viewporter, +fractional_scale_manager: *wlr.FractionalScaleManagerV1, compositor: *wlr.Compositor, +subcompositor: *wlr.Subcompositor, +cursor_shape_manager: *wlr.CursorShapeManagerV1, xdg_shell: *wlr.XdgShell, -new_xdg_surface: wl.Listener(*wlr.XdgSurface) = - wl.Listener(*wlr.XdgSurface).init(handleNewXdgSurface), - xdg_decoration_manager: *wlr.XdgDecorationManagerV1, -new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) = - wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration), - layer_shell: *wlr.LayerShellV1, -new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1) = - wl.Listener(*wlr.LayerSurfaceV1).init(handleNewLayerSurface), +xdg_activation: *wlr.XdgActivationV1, -xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_options.xwayland) null, -new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void = - if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface).init(handleNewXwaylandSurface), +data_device_manager: *wlr.DataDeviceManager, +primary_selection_manager: *wlr.PrimarySelectionDeviceManagerV1, +data_control_manager: *wlr.DataControlManagerV1, + +export_dmabuf_manager: *wlr.ExportDmabufManagerV1, +screencopy_manager: *wlr.ScreencopyManagerV1, foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, -xdg_activation: *wlr.XdgActivationV1, -request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = - wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), - -cursor_shape_manager: *wlr.CursorShapeManagerV1, -request_set_cursor_shape: wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape) = - wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape).init(handleRequestSetCursorShape), - input_manager: InputManager, root: Root, config: Config, @@ -91,6 +91,21 @@ layout_manager: LayoutManager, idle_inhibit_manager: IdleInhibitManager, lock_manager: LockManager, +xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_options.xwayland) null, +new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void = + if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface).init(handleNewXwaylandSurface), + +new_xdg_surface: wl.Listener(*wlr.XdgSurface) = + wl.Listener(*wlr.XdgSurface).init(handleNewXdgSurface), +new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) = + wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration), +new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1) = + wl.Listener(*wlr.LayerSurfaceV1).init(handleNewLayerSurface), +request_activate: wl.Listener(*wlr.XdgActivationV1.event.RequestActivate) = + wl.Listener(*wlr.XdgActivationV1.event.RequestActivate).init(handleRequestActivate), +request_set_cursor_shape: wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape) = + wl.Listener(*wlr.CursorShapeManagerV1.event.RequestSetShape).init(handleRequestSetCursorShape), + pub fn init(server: *Server, runtime_xwayland: bool) !void { // We intentionally don't try to prevent memory leaks on error in this function // since river will exit during initialization anyway if there is an error. @@ -115,13 +130,30 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { .renderer = renderer, .allocator = try wlr.Allocator.autocreate(backend, renderer), + .security_context_manager = try wlr.SecurityContextManagerV1.create(wl_server), + + .shm = try wlr.Shm.createWithRenderer(wl_server, 1, renderer), + .single_pixel_buffer_manager = try wlr.SinglePixelBufferManagerV1.create(wl_server), + + .viewporter = try wlr.Viewporter.create(wl_server), + .fractional_scale_manager = try wlr.FractionalScaleManagerV1.create(wl_server, 1), .compositor = compositor, + .subcompositor = try wlr.Subcompositor.create(wl_server), + .cursor_shape_manager = try wlr.CursorShapeManagerV1.create(server.wl_server, 1), + .xdg_shell = try wlr.XdgShell.create(wl_server, 5), .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(wl_server), .layer_shell = try wlr.LayerShellV1.create(wl_server, 4), - .foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server), .xdg_activation = try wlr.XdgActivationV1.create(wl_server), - .cursor_shape_manager = try wlr.CursorShapeManagerV1.create(server.wl_server, 1), + + .data_device_manager = try wlr.DataDeviceManager.create(wl_server), + .primary_selection_manager = try wlr.PrimarySelectionDeviceManagerV1.create(wl_server), + .data_control_manager = try wlr.DataControlManagerV1.create(wl_server), + + .export_dmabuf_manager = try wlr.ExportDmabufManagerV1.create(wl_server), + .screencopy_manager = try wlr.ScreencopyManagerV1.create(wl_server), + + .foreign_toplevel_manager = try wlr.ForeignToplevelManagerV1.create(wl_server), .config = try Config.init(), @@ -134,27 +166,16 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void { .lock_manager = undefined, }; - try renderer.initWlShm(wl_server); if (renderer.getDmabufFormats() != null and renderer.getDrmFd() >= 0) { // wl_drm is a legacy interface and all clients should switch to linux_dmabuf. // However, enough widely used clients still rely on wl_drm that the pragmatic option // is to keep it around for the near future. // TODO remove wl_drm support - _ = try wlr.Drm.create(wl_server, renderer); + server.drm = try wlr.Drm.create(wl_server, renderer); - _ = try wlr.LinuxDmabufV1.createWithRenderer(wl_server, 4, renderer); + server.linux_dmabuf = try wlr.LinuxDmabufV1.createWithRenderer(wl_server, 4, renderer); } - _ = try wlr.Subcompositor.create(wl_server); - _ = try wlr.PrimarySelectionDeviceManagerV1.create(wl_server); - _ = try wlr.DataDeviceManager.create(wl_server); - _ = try wlr.DataControlManagerV1.create(wl_server); - _ = try wlr.ExportDmabufManagerV1.create(wl_server); - _ = try wlr.ScreencopyManagerV1.create(wl_server); - _ = try wlr.SinglePixelBufferManagerV1.create(wl_server); - _ = try wlr.Viewporter.create(wl_server); - _ = try wlr.FractionalScaleManagerV1.create(wl_server, 1); - if (build_options.xwayland and runtime_xwayland) { server.xwayland = try wlr.Xwayland.create(wl_server, compositor, false); server.xwayland.?.events.new_surface.add(&server.new_xwayland_surface); @@ -244,7 +265,85 @@ fn globalFilter(client: *const wl.Client, global: *const wl.Global, server: *Ser } } - return true; + // User-configurable allow/block lists are TODO + if (server.security_context_manager.lookupClient(client) != null) { + const allowed = server.allowlist(global); + const blocked = server.blocklist(global); + assert(allowed != blocked); + return allowed; + } else { + return true; + } +} + +fn hackGlobal(ptr: *anyopaque) *wl.Global { + // TODO(wlroots) MR that eliminates the need for this hack: + // https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4612 + if (wlr.version.major != 0 or wlr.version.minor != 17) @compileError("FIXME"); + + return @as(*extern struct { global: *wl.Global }, @alignCast(@ptrCast(ptr))).global; +} + +/// Returns true if the global is allowlisted for security contexts +fn allowlist(server: *Server, global: *const wl.Global) bool { + if (server.drm) |drm| if (global == drm.global) return true; + if (server.linux_dmabuf) |linux_dmabuf| if (global == linux_dmabuf.global) return true; + + { + var it = server.root.all_outputs.iterator(.forward); + while (it.next()) |output| { + if (global == output.wlr_output.global) return true; + } + } + + { + var it = server.input_manager.seats.first; + while (it) |node| : (it = node.next) { + if (global == node.data.wlr_seat.global) return true; + } + } + + return global == hackGlobal(server.shm) or + global == hackGlobal(server.single_pixel_buffer_manager) or + global == server.viewporter.global or + global == server.fractional_scale_manager.global or + global == server.compositor.global or + global == server.subcompositor.global or + global == server.cursor_shape_manager.global or + global == server.xdg_shell.global or + global == server.xdg_decoration_manager.global or + global == server.xdg_activation.global or + global == server.data_device_manager.global or + global == server.primary_selection_manager.global or + global == server.root.presentation.global or + global == server.root.xdg_output_manager.global or + global == server.input_manager.relative_pointer_manager.global or + global == server.input_manager.pointer_constraints.global or + global == server.input_manager.text_input_manager.global or + global == server.input_manager.tablet_manager.global or + global == server.input_manager.pointer_gestures.global or + global == server.idle_inhibit_manager.wlr_manager.global; +} + +/// Returns true if the global is blocked for security contexts +fn blocklist(server: *Server, global: *const wl.Global) bool { + return global == server.security_context_manager.global or + global == server.layer_shell.global or + global == server.foreign_toplevel_manager.global or + global == server.screencopy_manager.global or + global == server.export_dmabuf_manager.global or + global == server.data_control_manager.global or + global == server.layout_manager.global or + global == server.control.global or + global == server.status_manager.global or + global == server.root.output_manager.global or + global == server.root.power_manager.global or + global == server.root.gamma_control_manager.global or + global == hackGlobal(server.input_manager.idle_notifier) or + global == server.input_manager.virtual_pointer_manager.global or + global == server.input_manager.virtual_keyboard_manager.global or + global == server.input_manager.input_method_manager.global or + global == server.lock_manager.wlr_manager.global; } /// Handle SIGINT and SIGTERM by gracefully stopping the server