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,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+
96292static 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