New paste Repaste Download
From dee5990eb9700a71604a298e0003be6a68092c34 Mon Sep 17 00:00:00 2001
From: Rahul Sandhu <nvraxn@gmail.com>
Date: Mon, 24 Feb 2025 18:38:08 +0000
Subject: [PATCH] sway: implement selinux based filtering for globals
This patch exposes privileged globals to SELinux so access to priv
globals can be mediated by SELinux.
Signed-off-by: Rahul Sandhu <nvraxn@gmail.com>
---
meson.build       |   2 +
meson_options.txt |   1 +
sway/meson.build  |   1 +
sway/server.c     | 198 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 202 insertions(+)
diff --git a/meson.build b/meson.build
index 9ce5723e..45d94c23 100644
--- a/meson.build
+++ b/meson.build
@@ -73,6 +73,7 @@ gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
pixman = dependency('pixman-1')
libevdev = dependency('libevdev')
libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.26.0') : null_dep
+libselinux = dependency('libselinux', required: get_option('selinux'))
xcb = wlroots_features['xwayland'] ? dependency('xcb') : null_dep
drm = dependency('libdrm')
libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep
@@ -109,6 +110,7 @@ conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd
conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind')
conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu')
conf_data.set10('HAVE_TRAY', have_tray)
+conf_data.set10('HAVE_SELINUX', libselinux.found())
foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY']
conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: libinput))
endforeach
diff --git a/meson_options.txt b/meson_options.txt
index 506ecc9a..ca217a4e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -8,3 +8,4 @@ option('tray', type: 'feature', value: 'auto', description: 'Enable support for
option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybar tray')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
+option('selinux', type: 'boolean', value: false, description: 'Enable support for SELinux access control')
diff --git a/sway/meson.build b/sway/meson.build
index 8042c89b..0d4f12af 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -223,6 +223,7 @@ sway_deps = [
jsonc,
libevdev,
libinput,
+ libselinux,
libudev,
math,
pango,
diff --git a/sway/server.c b/sway/server.c
index c7fc2a6d..1c15f886 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -1,7 +1,12 @@
+#ifdef __linux__
+#define _DEFAULT_SOURCE
+#endif
+
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/socket.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/headless.h>
@@ -69,6 +74,10 @@
#include <wlr/types/wlr_drm_lease_v1.h>
#endif
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
#define SWAY_XDG_SHELL_VERSION 5
#define SWAY_LAYER_SHELL_VERSION 4
#define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1
@@ -90,6 +99,191 @@ static void handle_drm_lease_request(struct wl_listener *listener, void *data) {
}
#endif
+#if HAVE_SELINUX
+static const char *get_selinux_protocol(const struct wl_global *global) {
+#if WLR_HAS_DRM_BACKEND
+ if (server.drm_lease_manager != NULL) {
+ struct wlr_drm_lease_device_v1 *drm_lease_dev;
+ wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) {
+ if (drm_lease_dev->global == global) {
+ return "drm_lease";
+ }
+ }
+ }
+#endif
+
+ if (global == server.output_manager_v1->global) {
+ return "output_manager";
+ }
+ if (global == server.output_power_manager_v1->global) {
+ return "output_power_manager";
+ }
+ if (global == server.input_method->global) {
+ return "input_method";
+ }
+ if (global == server.foreign_toplevel_list->global) {
+ return "foreign_toplevel_list";
+ }
+ if (global == server.foreign_toplevel_manager->global) {
+ return "foreign_toplevel_manager";
+ }
+ if (global == server.data_control_manager_v1->global) {
+ return "data_control_manager";
+ }
+ if (global == server.screencopy_manager_v1->global) {
+ return "screencopy_manager";
+ }
+ if (global == server.ext_image_copy_capture_manager_v1->global) {
+ return "ext_image_copy_capture_manager";
+ }
+ if (global == server.export_dmabuf_manager_v1->global) {
+ return "export_dmabuf_manager";
+ }
+ if (global == server.security_context_manager_v1->global) {
+ return "security_context_manager";
+ }
+ if (global == server.gamma_control_manager_v1->global) {
+ return "gamma_control_manager";
+ }
+ if (global == server.layer_shell->global) {
+ return "layer_shell";
+ }
+ if (global == server.session_lock.manager->global) {
+ return "session_lock_manager";
+ }
+ if (global == server.input->keyboard_shortcuts_inhibit->global) {
+ return "keyboard_shortcuts_inhibit";
+ }
+ if (global == server.input->virtual_keyboard->global) {
+ return "virtual_keyboard";
+ }
+ if (global == server.input->virtual_pointer->global) {
+ return "virtual_pointer";
+ }
+ if (global == server.input->transient_seat_manager->global) {
+ return "transient_seat_manager";
+ }
+ if (global == server.xdg_output_manager_v1->global) {
+ return "xdg_output_manager";
+ }
+
+ return NULL;
+}
+
+static sway_log_importance_t convert_selinux_log_importance(int type) {
+ switch (type) {
+ case SELINUX_ERROR:
+ return SWAY_ERROR;
+ case SELINUX_WARNING:
+ case SELINUX_AVC:
+ case SELINUX_INFO:
+ return SWAY_INFO;
+ default:
+ return SWAY_DEBUG;
+ }
+}
+
+static int log_callback_selinux(int type, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ int space_needed = snprintf(NULL, 0, "[selinux] %s", fmt);
+ if (space_needed < 0) {
+ return -1;
+ }
+ char *buffer = calloc(space_needed + 1, sizeof(*buffer));
+ if (buffer == NULL) {
+ return -1;
+ }
+ sprintf(buffer, "[selinux %s]", fmt);
+
+ _sway_vlog(convert_selinux_log_importance(type), buffer, args);
+
+ free(buffer);
+ va_end(args);
+ return 0;
+}
+#endif
+
+static bool check_access_selinux(const struct wl_global *global,
+ const struct wl_client *client) {
+#if HAVE_SELINUX
+ if (is_selinux_enabled() == 0) {
+ // SELinux not running
+ return true;
+ }
+
+ const char *protocol = get_selinux_protocol(global);
+ if (protocol == NULL) {
+ return true; // Not a privileged protocol, access granted
+ }
+
+ char *client_context = NULL;
+ socklen_t len = NAME_MAX;
+ int r;
+
+ int sockfd = wl_client_get_fd((struct wl_client *)client);
+
+ do {
+ char *new_context = realloc(client_context, len);
+ if (new_context == NULL) {
+ free(client_context);
+ return false;
+ }
+ client_context = new_context;
+
+ r = getsockopt(sockfd, SOL_SOCKET, SO_PEERSEC, client_context, &len);
+ if (r < 0 && errno != ERANGE) {
+ free(client_context);
+ return false;
+ }
+ } while (r < 0 && errno == ERANGE);
+
+ if (client_context == NULL) {
+ return true; // Getting NULL back for SO_PEERSEC means that an LSM
+              // that provides security contexts is not running.
+ }
+
+ selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = log_callback_selinux });
+
+ r = security_getenforce();
+ // If we can't determine if SELinux is enforcing or not,
+ // proceed as if enforcing
+ bool enforcing = !r;
+
+ char *compositor_context = NULL;
+ if (getcon_raw(&compositor_context) < 0) {
+ _sway_log(SWAY_ERROR, "[selinux] getcon_raw() failed: %s", strerror(errno));
+ // We can't get our own context. Only allow
+ // the access if not in enforcing mode.
+ free(client_context);
+ return !enforcing;
+ }
+ if (compositor_context == NULL) {
+ _sway_log(SWAY_ERROR, "[selinux] getcon_raw() returned NULL");
+ // We can't get our own context. Only allow
+ // the access if not in enforcing mode.
+ free(client_context);
+ return !enforcing;
+ }
+
+ const char *tclass = "wayland_protocol";
+ errno = 0;
+ r = selinux_check_access(client_context, compositor_context, tclass, protocol, NULL);
+ if (r < 0) {
+ _sway_log(SWAY_INFO, "[selinux] access check denied: %s", strerror(errno));
+ }
+ _sway_log(SWAY_DEBUG, "[selinux] access check scon=%s tcon=%s tclass=%s perm=%s",
+          client_context, compositor_context, tclass, protocol);
+ free(client_context);
+ free(compositor_context);
+
+ return enforcing ? (r == 0) : true;
+#else
+ return true;
+#endif
+}
+
static bool is_privileged(const struct wl_global *global) {
#if WLR_HAS_DRM_BACKEND
if (server.drm_lease_manager != NULL) {
@@ -132,6 +326,10 @@ static bool filter_global(const struct wl_client *client,
}
#endif
+ if (!check_access_selinux(global, client)) {
+ return false;
+ }
+
// Restrict usage of privileged protocols to unsandboxed clients
// TODO: add a way for users to configure an allow-list
const struct wlr_security_context_v1_state *security_context =
--
2.49.0
Filename: 0001-sway-implement-selinux-based-filtering-for-globals.patch. Size: 9kb. View raw, , hex, or download this file.

This paste expires on 2025-05-16 06:16:59.118812. Pasted through v1-api.