From ba8d3f8028d15aedd6aadc52351469dfefcfa10c Mon Sep 17 00:00:00 2001 From: Tobias Bengfort Date: Fri, 3 Apr 2026 20:38:21 +0200 Subject: [PATCH] rcxml: allow to restrict privileged interfaces --- docs/labwc-config.5.scd | 46 +++++++++++++++++++++++++++++++++++++++ include/config/rcxml.h | 3 +++ src/config/rcxml.c | 48 +++++++++++++++++++++++++++++++++++++++++ src/server.c | 42 ++++++------------------------------ 4 files changed, 104 insertions(+), 35 deletions(-) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 09f7b3eb..061e57ad 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -1445,6 +1445,52 @@ situation. Whether to apply a bilinear filter to the magnified image, or just to use nearest-neighbour. Default is true - bilinear filtered. +## PRIVILEGED INTERFACES + +Labwc supports a small set of privileged wayland interfaces. All of these +interfaces are enabled by default for applications unless they are running +via a sandbox environment supporting the security-context-v1 protocol. + +Security conscious users are suggested to use a sandbox framework to run +potentially untrusted applications as it additionally limits access to the +filesystem (including labwc configuration) and other services like dbus. + +In addition to that, privileged protocols can be restricted for non-sandboxed +clients by defining a `` block: + +``` + + zwlr_layer_shell_v1 + zwlr_virtual_pointer_manager_v1 + +``` + +** + Name of the interface that should be allowed. + +This is the full list of interfaces that can be controlled with this mechanism: + +- `wp_drm_lease_device_v1` +- `zwlr_gamma_control_manager_v1` +- `zwlr_output_manager_v1` +- `zwlr_output_power_manager_v1` +- `zwp_input_method_manager_v2` +- `zwlr_virtual_pointer_manager_v1` +- `zwp_virtual_keyboard_manager_v1` +- `zwlr_export_dmabuf_manager_v1` +- `zwlr_screencopy_manager_v1` +- `ext_data_control_manager_v1` +- `zwlr_data_control_manager_v1` +- `wp_security_context_manager_v1` +- `ext_idle_notifier_v1` +- `zwlr_foreign_toplevel_manager_v1` +- `ext_foreign_toplevel_list_v1` +- `ext_session_lock_manager_v1` +- `zwlr_layer_shell_v1` +- `ext_workspace_manager_v1` +- `ext_image_copy_capture_manager_v1` +- `ext_output_image_capture_source_manager_v1` + ## ENVIRONMENT VARIABLES *XCURSOR_PATH* diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 517cd907..8a2c606c 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -76,6 +76,7 @@ struct rcxml { enum tearing_mode allow_tearing; bool auto_enable_outputs; bool reuse_output_mode; + uint32_t allowed_interfaces; bool xwayland_persistence; bool primary_selection; char *prompt_command; @@ -225,4 +226,6 @@ void rcxml_finish(void); */ void append_parsed_actions(xmlNode *node, struct wl_list *list); +uint32_t parse_privileged_interface(const char *name); + #endif /* LABWC_RCXML_H */ diff --git a/src/config/rcxml.c b/src/config/rcxml.c index a3eeed63..9884d15e 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -94,6 +94,43 @@ parse_window_type(const char *type) } } +uint32_t +parse_privileged_interface(const char *name) +{ + static const char * const ifaces[] = { + "wp_drm_lease_device_v1", + "zwlr_gamma_control_manager_v1", + "zwlr_output_manager_v1", + "zwlr_output_power_manager_v1", + "zwp_input_method_manager_v2", + "zwlr_virtual_pointer_manager_v1", + "zwp_virtual_keyboard_manager_v1", + "zwlr_export_dmabuf_manager_v1", + "zwlr_screencopy_manager_v1", + "ext_data_control_manager_v1", + "zwlr_data_control_manager_v1", + "wp_security_context_manager_v1", + "ext_idle_notifier_v1", + "zwlr_foreign_toplevel_manager_v1", + "ext_foreign_toplevel_list_v1", + "ext_session_lock_manager_v1", + "zwlr_layer_shell_v1", + "ext_workspace_manager_v1", + "ext_image_copy_capture_manager_v1", + "ext_output_image_capture_source_manager_v1", + }; + + static_assert(ARRAY_SIZE(ifaces) <= 32, + "return type too small for amount of privileged protocols"); + + for (size_t i = 0; i < ARRAY_SIZE(ifaces); i++) { + if (!strcmp(name, ifaces[i])) { + return 1 << i; + } + } + return 0; +} + /* * Openbox/labwc comparison * @@ -1377,6 +1414,16 @@ entry(xmlNode *node, char *nodename, char *content) rc.mag_increment = MAX(0, rc.mag_increment); } else if (!strcasecmp(nodename, "useFilter.magnifier")) { set_bool(content, &rc.mag_filter); + } else if (!strcasecmp(nodename, "privilegedInterfaces")) { + rc.allowed_interfaces = 0; + } else if (!strcasecmp(nodename, "allow.privilegedInterfaces")) { + uint32_t iface_id = parse_privileged_interface(content); + if (iface_id) { + rc.allowed_interfaces |= iface_id; + } else { + wlr_log(WLR_ERROR, "invalid value for " + ""); + } } return false; @@ -1459,6 +1506,7 @@ rcxml_init(void) rc.allow_tearing = LAB_TEARING_DISABLED; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; + rc.allowed_interfaces = UINT32_MAX; rc.xwayland_persistence = false; rc.primary_selection = true; diff --git a/src/server.c b/src/server.c index fd48efed..5c200c1f 100644 --- a/src/server.c +++ b/src/server.c @@ -210,39 +210,6 @@ handle_drm_lease_request(struct wl_listener *listener, void *data) } #endif -static bool -protocol_is_privileged(const struct wl_interface *iface) -{ - static const char * const rejected[] = { - "wp_drm_lease_device_v1", - "zwlr_gamma_control_manager_v1", - "zwlr_output_manager_v1", - "zwlr_output_power_manager_v1", - "zwp_input_method_manager_v2", - "zwlr_virtual_pointer_manager_v1", - "zwp_virtual_keyboard_manager_v1", - "zwlr_export_dmabuf_manager_v1", - "zwlr_screencopy_manager_v1", - "ext_data_control_manager_v1", - "zwlr_data_control_manager_v1", - "wp_security_context_manager_v1", - "ext_idle_notifier_v1", - "zwlr_foreign_toplevel_manager_v1", - "ext_foreign_toplevel_list_v1", - "ext_session_lock_manager_v1", - "zwlr_layer_shell_v1", - "ext_workspace_manager_v1", - "ext_image_copy_capture_manager_v1", - "ext_output_image_capture_source_manager_v1", - }; - for (size_t i = 0; i < ARRAY_SIZE(rejected); i++) { - if (!strcmp(iface->name, rejected[i])) { - return true; - } - } - return false; -} - static bool allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, const struct wl_interface *iface) @@ -323,6 +290,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo } #endif + uint32_t iface_id = parse_privileged_interface(iface->name); + if (iface_id && !(iface_id & rc.allowed_interfaces)) { + return false; + } + /* Do not allow security_context_manager_v1 to clients with a security context attached */ const struct wlr_security_context_v1_state *security_context = wlr_security_context_manager_v1_lookup_client( @@ -338,11 +310,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo /* * TODO: The following call is basically useless right now * and should be replaced with - * assert(allow || protocol_is_privileged(iface)); + * assert(allow || iface_id); * This ensures that our lists are in sync with what * protocols labwc supports. */ - if (!allow && !protocol_is_privileged(iface)) { + if (!allow && !iface_id) { wlr_log(WLR_ERROR, "Blocking unknown protocol %s", iface->name); } else if (!allow) { wlr_log(WLR_DEBUG, "Blocking %s for security context %s->%s->%s",