Skip to content

Commit e66c312

Browse files
committed
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 <[email protected]>
1 parent d9e615c commit e66c312

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
7474
pixman = dependency('pixman-1')
7575
libevdev = dependency('libevdev')
7676
libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.26.0') : null_dep
77+
libselinux = dependency('libselinux', required: get_option('selinux'))
7778
xcb = wlroots_features['xwayland'] ? dependency('xcb') : null_dep
7879
drm = dependency('libdrm')
7980
libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep
@@ -110,6 +111,7 @@ conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd
110111
conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind')
111112
conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu')
112113
conf_data.set10('HAVE_TRAY', have_tray)
114+
conf_data.set10('HAVE_SELINUX', libselinux.found())
113115
foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY']
114116
conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: libinput))
115117
endforeach

meson_options.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ option('tray', type: 'feature', value: 'auto', description: 'Enable support for
88
option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybar tray')
99
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
1010
option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
11+
option('selinux', type: 'boolean', value: false, description: 'Enable support for SELinux access control')

sway/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ sway_deps = [
224224
jsonc,
225225
libevdev,
226226
libinput,
227+
libselinux,
227228
libudev,
228229
math,
229230
pango,

sway/server.c

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
#ifdef __linux__
2+
#define _DEFAULT_SOURCE
3+
#endif
4+
15
#include <assert.h>
26
#include <stdbool.h>
37
#include <stdlib.h>
48
#include <string.h>
9+
#include <sys/socket.h>
510
#include <wayland-server-core.h>
611
#include <wlr/backend.h>
712
#include <wlr/backend/headless.h>
@@ -72,6 +77,10 @@
7277
#include <wlr/types/wlr_drm_lease_v1.h>
7378
#endif
7479

80+
#if HAVE_SELINUX
81+
#include <selinux/selinux.h>
82+
#endif
83+
7584
#define SWAY_XDG_SHELL_VERSION 5
7685
#define SWAY_LAYER_SHELL_VERSION 4
7786
#define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1
@@ -93,6 +102,193 @@ static void handle_drm_lease_request(struct wl_listener *listener, void *data) {
93102
}
94103
#endif
95104

105+
#if HAVE_SELINUX
106+
static const char *get_selinux_protocol(const struct wl_global *global) {
107+
#if WLR_HAS_DRM_BACKEND
108+
if (server.drm_lease_manager != NULL) {
109+
struct wlr_drm_lease_device_v1 *drm_lease_dev;
110+
wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) {
111+
if (drm_lease_dev->global == global) {
112+
return "drm_lease";
113+
}
114+
}
115+
}
116+
#endif
117+
118+
if (global == server.output_manager_v1->global) {
119+
return "output_manager";
120+
}
121+
if (global == server.output_power_manager_v1->global) {
122+
return "output_power_manager";
123+
}
124+
if (global == server.input_method->global) {
125+
return "input_method";
126+
}
127+
if (global == server.foreign_toplevel_list->global) {
128+
return "foreign_toplevel_list";
129+
}
130+
if (global == server.foreign_toplevel_manager->global) {
131+
return "foreign_toplevel_manager";
132+
}
133+
if (global == server.wlr_data_control_manager_v1->global) {
134+
return "data_control_manager";
135+
}
136+
if (global == server.ext_data_control_manager_v1->global) {
137+
return "data_control_manager";
138+
}
139+
if (global == server.screencopy_manager_v1->global) {
140+
return "screencopy_manager";
141+
}
142+
if (global == server.ext_image_copy_capture_manager_v1->global) {
143+
return "image_copy_capture_manager";
144+
}
145+
if (global == server.export_dmabuf_manager_v1->global) {
146+
return "export_dmabuf_manager";
147+
}
148+
if (global == server.security_context_manager_v1->global) {
149+
return "security_context_manager";
150+
}
151+
if (global == server.gamma_control_manager_v1->global) {
152+
return "gamma_control_manager";
153+
}
154+
if (global == server.layer_shell->global) {
155+
return "layer_shell";
156+
}
157+
if (global == server.session_lock.manager->global) {
158+
return "session_lock_manager";
159+
}
160+
if (global == server.input->keyboard_shortcuts_inhibit->global) {
161+
return "keyboard_shortcuts_inhibit";
162+
}
163+
if (global == server.input->virtual_keyboard->global) {
164+
return "virtual_keyboard";
165+
}
166+
if (global == server.input->virtual_pointer->global) {
167+
return "virtual_pointer";
168+
}
169+
if (global == server.input->transient_seat_manager->global) {
170+
return "transient_seat_manager";
171+
}
172+
if (global == server.xdg_output_manager_v1->global) {
173+
return "xdg_output_manager";
174+
}
175+
176+
return NULL;
177+
}
178+
179+
static sway_log_importance_t convert_selinux_log_importance(int type) {
180+
switch (type) {
181+
case SELINUX_ERROR:
182+
return SWAY_ERROR;
183+
case SELINUX_WARNING:
184+
__attribute__ ((fallthrough));
185+
case SELINUX_AVC:
186+
__attribute__ ((fallthrough));
187+
case SELINUX_INFO:
188+
return SWAY_INFO;
189+
default:
190+
return SWAY_DEBUG;
191+
}
192+
}
193+
194+
static int log_callback_selinux(int type, const char *fmt, ...) {
195+
va_list args;
196+
va_start(args, fmt);
197+
198+
const int space_needed = snprintf(NULL, 0, "[selinux] %s", fmt);
199+
if (space_needed < 0) {
200+
return -1;
201+
}
202+
char *buffer = calloc(space_needed + 1, sizeof(*buffer));
203+
if (buffer == NULL) {
204+
return -1;
205+
}
206+
sprintf(buffer, "[selinux] %s", fmt);
207+
208+
_sway_vlog(convert_selinux_log_importance(type), buffer, args);
209+
210+
free(buffer);
211+
va_end(args);
212+
return 0;
213+
}
214+
#endif
215+
216+
static bool check_access_selinux(const struct wl_global *global,
217+
const struct wl_client *client) {
218+
#if HAVE_SELINUX
219+
if (is_selinux_enabled() == 0) {
220+
// SELinux not running
221+
return true;
222+
}
223+
224+
const char *protocol = get_selinux_protocol(global);
225+
if (protocol == NULL) {
226+
return true; // Not a privileged protocol, access granted
227+
}
228+
229+
char *client_context = NULL;
230+
socklen_t len = NAME_MAX;
231+
int r;
232+
233+
const int sockfd = wl_client_get_fd((struct wl_client *)client);
234+
235+
do {
236+
char *new_context = realloc(client_context, len);
237+
if (new_context == NULL) {
238+
free(client_context);
239+
return false;
240+
}
241+
client_context = new_context;
242+
243+
r = getsockopt(sockfd, SOL_SOCKET, SO_PEERSEC, client_context, &len);
244+
if (r < 0 && errno != ERANGE) {
245+
free(client_context);
246+
return false;
247+
}
248+
} while (r < 0 && errno == ERANGE);
249+
250+
if (client_context == NULL) {
251+
return true; // Getting NULL back for SO_PEERSEC means that an LSM
252+
// that provides security contexts is not running.
253+
}
254+
255+
selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = log_callback_selinux });
256+
257+
r = security_getenforce();
258+
// If we can't determine if SELinux is enforcing or not, proceed as if enforcing.
259+
const bool enforcing = !r;
260+
261+
char *compositor_context = NULL;
262+
if (getcon_raw(&compositor_context) < 0) {
263+
_sway_log(SWAY_ERROR, "[selinux] getcon_raw() failed: %s", strerror(errno));
264+
free(client_context);
265+
// We can't get our own context. Only allow the access if not in enforcing mode.
266+
return !enforcing;
267+
}
268+
if (compositor_context == NULL) {
269+
_sway_log(SWAY_ERROR, "[selinux] getcon_raw() returned NULL");
270+
free(client_context);
271+
// We can't get our own context. Only allow the access if not in enforcing mode.
272+
return !enforcing;
273+
}
274+
275+
static const char *const tclass = "wayland";
276+
errno = 0;
277+
r = selinux_check_access(client_context, compositor_context, tclass, protocol, NULL);
278+
if (r < 0) {
279+
_sway_log(SWAY_INFO, "[selinux] access check denied: %s", strerror(errno));
280+
}
281+
_sway_log(SWAY_DEBUG, "[selinux] access check scon=%s tcon=%s tclass=%s perm=%s",
282+
client_context, compositor_context, tclass, protocol);
283+
free(client_context);
284+
free(compositor_context);
285+
286+
return enforcing ? (r == 0) : true;
287+
#else
288+
return true;
289+
#endif
290+
}
291+
96292
static bool is_privileged(const struct wl_global *global) {
97293
#if WLR_HAS_DRM_BACKEND
98294
if (server.drm_lease_manager != NULL) {
@@ -136,6 +332,10 @@ static bool filter_global(const struct wl_client *client,
136332
}
137333
#endif
138334

335+
if (!check_access_selinux(global, client)) {
336+
return false;
337+
}
338+
139339
// Restrict usage of privileged protocols to unsandboxed clients
140340
// TODO: add a way for users to configure an allow-list
141341
const struct wlr_security_context_v1_state *security_context =

0 commit comments

Comments
 (0)