mirror of
https://github.com/swaywm/sway.git
synced 2025-10-08 13:16:05 +00:00
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>
This commit is contained in:
parent
d9e615c507
commit
ca7cd98367
5 changed files with 221 additions and 0 deletions
|
@ -74,6 +74,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
|
||||
|
@ -110,6 +111,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
|
||||
|
|
|
@ -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')
|
||||
|
|
47
sway/main.c
47
sway/main.c
|
@ -26,6 +26,10 @@
|
|||
#include "stringop.h"
|
||||
#include "util.h"
|
||||
|
||||
#if HAVE_SELINUX
|
||||
#include <selinux/selinux.h>
|
||||
#endif
|
||||
|
||||
static bool terminate_request = false;
|
||||
static int exit_value = 0;
|
||||
static struct rlimit original_nofile_rlimit = {0};
|
||||
|
@ -199,6 +203,44 @@ static void handle_wlr_log(enum wlr_log_importance importance,
|
|||
_sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args);
|
||||
}
|
||||
|
||||
#if HAVE_SELINUX
|
||||
static sway_log_importance_t convert_selinux_log_importance(int type) {
|
||||
switch (type) {
|
||||
case SELINUX_ERROR:
|
||||
return SWAY_ERROR;
|
||||
case SELINUX_WARNING:
|
||||
__attribute__ ((fallthrough));
|
||||
case SELINUX_AVC:
|
||||
__attribute__ ((fallthrough));
|
||||
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);
|
||||
|
||||
const 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 const struct option long_options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"config", required_argument, NULL, 'c'},
|
||||
|
@ -299,6 +341,11 @@ int main(int argc, char **argv) {
|
|||
wlr_log_init(WLR_ERROR, handle_wlr_log);
|
||||
}
|
||||
|
||||
#if HAVE_SELINUX
|
||||
// Initalize libselinux logging.
|
||||
selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = log_callback_selinux });
|
||||
#endif
|
||||
|
||||
sway_log(SWAY_INFO, "Sway version " SWAY_VERSION);
|
||||
sway_log(SWAY_INFO, "wlroots version " WLR_VERSION_STR);
|
||||
log_kernel();
|
||||
|
|
|
@ -224,6 +224,7 @@ sway_deps = [
|
|||
jsonc,
|
||||
libevdev,
|
||||
libinput,
|
||||
libselinux,
|
||||
libudev,
|
||||
math,
|
||||
pango,
|
||||
|
|
170
sway/server.c
170
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>
|
||||
|
@ -72,6 +77,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
|
||||
|
@ -93,6 +102,163 @@ 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.wlr_data_control_manager_v1->global) {
|
||||
return "data_control_manager";
|
||||
}
|
||||
if (global == server.ext_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 "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;
|
||||
}
|
||||
#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;
|
||||
|
||||
const 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.
|
||||
}
|
||||
|
||||
r = security_getenforce();
|
||||
// If we can't determine if SELinux is enforcing or not, proceed as if enforcing.
|
||||
const bool enforcing = !r;
|
||||
|
||||
char *compositor_context = NULL;
|
||||
if (getcon_raw(&compositor_context) < 0) {
|
||||
_sway_log(SWAY_ERROR, "[selinux] getcon_raw() failed: %s", strerror(errno));
|
||||
free(client_context);
|
||||
// We can't get our own context. Only allow the access if not in enforcing mode.
|
||||
return !enforcing;
|
||||
}
|
||||
if (compositor_context == NULL) {
|
||||
_sway_log(SWAY_ERROR, "[selinux] getcon_raw() returned NULL");
|
||||
free(client_context);
|
||||
// We can't get our own context. Only allow the access if not in enforcing mode.
|
||||
return !enforcing;
|
||||
}
|
||||
|
||||
static const char *const tclass = "wayland";
|
||||
errno = 0;
|
||||
r = selinux_check_access(client_context, compositor_context, tclass, protocol, NULL);
|
||||
_sway_log(SWAY_DEBUG, "[selinux] access check scon=%s tcon=%s tclass=%s perm=%s",
|
||||
client_context, compositor_context, tclass, protocol);
|
||||
if (r < 0) {
|
||||
// EINVAL for contexts unknown to policy.
|
||||
if (errno != EACCES || errno != EINVAL) {
|
||||
_sway_log(SWAY_INFO, "[selinux] access check failed: %s", strerror(errno));
|
||||
free(client_context);
|
||||
free(compositor_context);
|
||||
// selinux_check_access failed. Only allow the access if not in enforcing mode.
|
||||
return !enforcing;
|
||||
}
|
||||
_sway_log(SWAY_INFO, "[selinux] access check denied: %s", strerror(errno));
|
||||
}
|
||||
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) {
|
||||
|
@ -136,6 +302,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 =
|
||||
|
|
Loading…
Add table
Reference in a new issue