Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 47 additions & 0 deletions sway/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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'},
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions sway/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ sway_deps = [
jsonc,
libevdev,
libinput,
libselinux,
libudev,
math,
pango,
Expand Down
170 changes: 170 additions & 0 deletions sway/server.c
Original file line number Diff line number Diff line change
@@ -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>
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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 =
Expand Down