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.
This commit is contained in:
Isaac Freund 2024-03-15 14:19:36 +01:00
parent e143cdeca9
commit 1b63c463a7
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
4 changed files with 145 additions and 40 deletions

2
deps/zig-wlroots vendored

@ -1 +1 @@
Subproject commit 3789f0e0c946fe2c9ac09dc09593cada93fd3797 Subproject commit c1e19f0b9fbdaf4f54a4a2129cf2cd7e68ec09ea

View File

@ -32,6 +32,8 @@ const Output = @import("Output.zig");
const log = std.log.scoped(.session_lock); const log = std.log.scoped(.session_lock);
wlr_manager: *wlr.SessionLockManagerV1,
state: enum { state: enum {
/// No lock request has been made and the session is unlocked. /// No lock request has been made and the session is unlocked.
unlocked, unlocked,
@ -66,11 +68,11 @@ pub fn init(manager: *LockManager) !void {
errdefer timer.remove(); errdefer timer.remove();
manager.* = .{ manager.* = .{
.wlr_manager = try wlr.SessionLockManagerV1.create(server.wl_server),
.lock_surfaces_timer = timer, .lock_surfaces_timer = timer,
}; };
const wlr_manager = try wlr.SessionLockManagerV1.create(server.wl_server); manager.wlr_manager.events.new_lock.add(&manager.new_lock);
wlr_manager.events.new_lock.add(&manager.new_lock);
} }
pub fn deinit(manager: *LockManager) void { pub fn deinit(manager: *LockManager) void {

View File

@ -83,6 +83,9 @@ new_output: wl.Listener(*wlr.Output) = wl.Listener(*wlr.Output).init(handleNewOu
output_layout: *wlr.OutputLayout, output_layout: *wlr.OutputLayout,
layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange), layout_change: wl.Listener(*wlr.OutputLayout) = wl.Listener(*wlr.OutputLayout).init(handleLayoutChange),
presentation: *wlr.Presentation,
xdg_output_manager: *wlr.XdgOutputManagerV1,
output_manager: *wlr.OutputManagerV1, output_manager: *wlr.OutputManagerV1,
manager_apply: wl.Listener(*wlr.OutputConfigurationV1) = manager_apply: wl.Listener(*wlr.OutputConfigurationV1) =
wl.Listener(*wlr.OutputConfigurationV1).init(handleManagerApply), wl.Listener(*wlr.OutputConfigurationV1).init(handleManagerApply),
@ -128,8 +131,6 @@ pub fn init(root: *Root) !void {
const outputs = try interactive_content.createSceneTree(); const outputs = try interactive_content.createSceneTree();
const override_redirect = if (build_options.xwayland) 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); const presentation = try wlr.Presentation.create(server.wl_server, server.backend);
scene.setPresentation(presentation); scene.setPresentation(presentation);
@ -164,6 +165,9 @@ pub fn init(root: *Root) !void {
.output_layout = output_layout, .output_layout = output_layout,
.all_outputs = undefined, .all_outputs = undefined,
.active_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), .output_manager = try wlr.OutputManagerV1.create(server.wl_server),
.power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server), .power_manager = try wlr.OutputPowerManagerV1.create(server.wl_server),
.gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server), .gamma_control_manager = try wlr.GammaControlManagerV1.create(server.wl_server),

View File

@ -18,6 +18,7 @@ const Server = @This();
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const wl = @import("wayland").server.wl; const wl = @import("wayland").server.wl;
@ -54,34 +55,33 @@ session: ?*wlr.Session,
renderer: *wlr.Renderer, renderer: *wlr.Renderer,
allocator: *wlr.Allocator, 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, compositor: *wlr.Compositor,
subcompositor: *wlr.Subcompositor,
cursor_shape_manager: *wlr.CursorShapeManagerV1,
xdg_shell: *wlr.XdgShell, xdg_shell: *wlr.XdgShell,
new_xdg_surface: wl.Listener(*wlr.XdgSurface) =
wl.Listener(*wlr.XdgSurface).init(handleNewXdgSurface),
xdg_decoration_manager: *wlr.XdgDecorationManagerV1, xdg_decoration_manager: *wlr.XdgDecorationManagerV1,
new_toplevel_decoration: wl.Listener(*wlr.XdgToplevelDecorationV1) =
wl.Listener(*wlr.XdgToplevelDecorationV1).init(handleNewToplevelDecoration),
layer_shell: *wlr.LayerShellV1, layer_shell: *wlr.LayerShellV1,
new_layer_surface: wl.Listener(*wlr.LayerSurfaceV1) = xdg_activation: *wlr.XdgActivationV1,
wl.Listener(*wlr.LayerSurfaceV1).init(handleNewLayerSurface),
xwayland: if (build_options.xwayland) ?*wlr.Xwayland else void = if (build_options.xwayland) null, data_device_manager: *wlr.DataDeviceManager,
new_xwayland_surface: if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface) else void = primary_selection_manager: *wlr.PrimarySelectionDeviceManagerV1,
if (build_options.xwayland) wl.Listener(*wlr.XwaylandSurface).init(handleNewXwaylandSurface), data_control_manager: *wlr.DataControlManagerV1,
export_dmabuf_manager: *wlr.ExportDmabufManagerV1,
screencopy_manager: *wlr.ScreencopyManagerV1,
foreign_toplevel_manager: *wlr.ForeignToplevelManagerV1, 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, input_manager: InputManager,
root: Root, root: Root,
config: Config, config: Config,
@ -91,6 +91,21 @@ layout_manager: LayoutManager,
idle_inhibit_manager: IdleInhibitManager, idle_inhibit_manager: IdleInhibitManager,
lock_manager: LockManager, 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 { pub fn init(server: *Server, runtime_xwayland: bool) !void {
// We intentionally don't try to prevent memory leaks on error in this function // 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. // 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, .renderer = renderer,
.allocator = try wlr.Allocator.autocreate(backend, 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, .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_shell = try wlr.XdgShell.create(wl_server, 5),
.xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(wl_server), .xdg_decoration_manager = try wlr.XdgDecorationManagerV1.create(wl_server),
.layer_shell = try wlr.LayerShellV1.create(wl_server, 4), .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), .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(), .config = try Config.init(),
@ -134,27 +166,16 @@ pub fn init(server: *Server, runtime_xwayland: bool) !void {
.lock_manager = undefined, .lock_manager = undefined,
}; };
try renderer.initWlShm(wl_server);
if (renderer.getDmabufFormats() != null and renderer.getDrmFd() >= 0) { if (renderer.getDmabufFormats() != null and renderer.getDrmFd() >= 0) {
// wl_drm is a legacy interface and all clients should switch to linux_dmabuf. // 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 // However, enough widely used clients still rely on wl_drm that the pragmatic option
// is to keep it around for the near future. // is to keep it around for the near future.
// TODO remove wl_drm support // 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) { if (build_options.xwayland and runtime_xwayland) {
server.xwayland = try wlr.Xwayland.create(wl_server, compositor, false); server.xwayland = try wlr.Xwayland.create(wl_server, compositor, false);
server.xwayland.?.events.new_surface.add(&server.new_xwayland_surface); server.xwayland.?.events.new_surface.add(&server.new_xwayland_surface);
@ -244,8 +265,86 @@ fn globalFilter(client: *const wl.Client, global: *const wl.Global, server: *Ser
} }
} }
// 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; 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 /// Handle SIGINT and SIGTERM by gracefully stopping the server
fn terminate(_: c_int, wl_server: *wl.Server) c_int { fn terminate(_: c_int, wl_server: *wl.Server) c_int {