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>
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,191 @@ 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 "ext_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+ case SELINUX_AVC :
185+ case SELINUX_INFO :
186+ return SWAY_INFO ;
187+ default :
188+ return SWAY_DEBUG ;
189+ }
190+ }
191+
192+ static int log_callback_selinux (int type , const char * fmt , ...) {
193+ va_list args ;
194+ va_start (args , fmt );
195+
196+ int space_needed = snprintf (NULL , 0 , "[selinux] %s" , fmt );
197+ if (space_needed < 0 ) {
198+ return -1 ;
199+ }
200+ char * buffer = calloc (space_needed + 1 , sizeof (* buffer ));
201+ if (buffer == NULL ) {
202+ return -1 ;
203+ }
204+ sprintf (buffer , "[selinux] %s" , fmt );
205+
206+ _sway_vlog (convert_selinux_log_importance (type ), buffer , args );
207+
208+ free (buffer );
209+ va_end (args );
210+ return 0 ;
211+ }
212+ #endif
213+
214+ static bool check_access_selinux (const struct wl_global * global ,
215+ const struct wl_client * client ) {
216+ #if HAVE_SELINUX
217+ if (is_selinux_enabled () == 0 ) {
218+ // SELinux not running
219+ return true;
220+ }
221+
222+ const char * protocol = get_selinux_protocol (global );
223+ if (protocol == NULL ) {
224+ return true; // Not a privileged protocol, access granted
225+ }
226+
227+ char * client_context = NULL ;
228+ socklen_t len = NAME_MAX ;
229+ int r ;
230+
231+ int sockfd = wl_client_get_fd ((struct wl_client * )client );
232+
233+ do {
234+ char * new_context = realloc (client_context , len );
235+ if (new_context == NULL ) {
236+ free (client_context );
237+ return false;
238+ }
239+ client_context = new_context ;
240+
241+ r = getsockopt (sockfd , SOL_SOCKET , SO_PEERSEC , client_context , & len );
242+ if (r < 0 && errno != ERANGE ) {
243+ free (client_context );
244+ return false;
245+ }
246+ } while (r < 0 && errno == ERANGE );
247+
248+ if (client_context == NULL ) {
249+ return true; // Getting NULL back for SO_PEERSEC means that an LSM
250+ // that provides security contexts is not running.
251+ }
252+
253+ selinux_set_callback (SELINUX_CB_LOG , (union selinux_callback ) { .func_log = log_callback_selinux });
254+
255+ r = security_getenforce ();
256+ // If we can't determine if SELinux is enforcing or not, proceed as if enforcing.
257+ bool enforcing = !r ;
258+
259+ char * compositor_context = NULL ;
260+ if (getcon_raw (& compositor_context ) < 0 ) {
261+ _sway_log (SWAY_ERROR , "[selinux] getcon_raw() failed: %s" , strerror (errno ));
262+ free (client_context );
263+ // We can't get our own context. Only allow the access if not in enforcing mode.
264+ return !enforcing ;
265+ }
266+ if (compositor_context == NULL ) {
267+ _sway_log (SWAY_ERROR , "[selinux] getcon_raw() returned NULL" );
268+ free (client_context );
269+ // We can't get our own context. Only allow the access if not in enforcing mode.
270+ return !enforcing ;
271+ }
272+
273+ const char * tclass = "wayland_protocol" ;
274+ errno = 0 ;
275+ r = selinux_check_access (client_context , compositor_context , tclass , protocol , NULL );
276+ if (r < 0 ) {
277+ _sway_log (SWAY_INFO , "[selinux] access check denied: %s" , strerror (errno ));
278+ }
279+ _sway_log (SWAY_DEBUG , "[selinux] access check scon=%s tcon=%s tclass=%s perm=%s" ,
280+ client_context , compositor_context , tclass , protocol );
281+ free (client_context );
282+ free (compositor_context );
283+
284+ return enforcing ? (r == 0 ) : true;
285+ #else
286+ return true;
287+ #endif
288+ }
289+
96290static bool is_privileged (const struct wl_global * global ) {
97291#if WLR_HAS_DRM_BACKEND
98292 if (server .drm_lease_manager != NULL ) {
@@ -136,6 +330,10 @@ static bool filter_global(const struct wl_client *client,
136330 }
137331#endif
138332
333+ if (!check_access_selinux (global , client )) {
334+ return false;
335+ }
336+
139337 // Restrict usage of privileged protocols to unsandboxed clients
140338 // TODO: add a way for users to configure an allow-list
141339 const struct wlr_security_context_v1_state * security_context =
0 commit comments