1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <malloc.h>
6 #include <stddef.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <sys/time.h>
10 #include <sys/types.h>
11 #include <sys/un.h>
12 #include <syslog.h>
13 
14 #if HAVE_SELINUX
15 #include <selinux/avc.h>
16 #include <selinux/context.h>
17 #include <selinux/label.h>
18 #include <selinux/selinux.h>
19 #endif
20 
21 #include "alloc-util.h"
22 #include "errno-util.h"
23 #include "fd-util.h"
24 #include "log.h"
25 #include "macro.h"
26 #include "path-util.h"
27 #include "selinux-util.h"
28 #include "stdio-util.h"
29 #include "time-util.h"
30 
31 #if HAVE_SELINUX
32 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(context_t, context_free, NULL);
33 #define _cleanup_context_free_ _cleanup_(context_freep)
34 
35 static int mac_selinux_reload(int seqno);
36 
37 static int cached_use = -1;
38 static bool initialized = false;
39 static int last_policyload = 0;
40 static struct selabel_handle *label_hnd = NULL;
41 static bool have_status_page = false;
42 
43 #define log_enforcing(...)                                              \
44         log_full(mac_selinux_enforcing() ? LOG_ERR : LOG_WARNING, __VA_ARGS__)
45 
46 #define log_enforcing_errno(error, ...)                                 \
47         ({                                                              \
48                 bool _enforcing = mac_selinux_enforcing();              \
49                 int _level = _enforcing ? LOG_ERR : LOG_WARNING;        \
50                 int _e = (error);                                       \
51                                                                         \
52                 int _r = (log_get_max_level() >= LOG_PRI(_level))       \
53                         ? log_internal(_level, _e, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
54                         : -ERRNO_VALUE(_e);                             \
55                 _enforcing ? _r : 0;                                    \
56         })
57 #endif
58 
mac_selinux_use(void)59 bool mac_selinux_use(void) {
60 #if HAVE_SELINUX
61         if (_unlikely_(cached_use < 0)) {
62                 cached_use = is_selinux_enabled() > 0;
63                 log_debug("SELinux enabled state cached to: %s", cached_use ? "enabled" : "disabled");
64         }
65 
66         return cached_use;
67 #else
68         return false;
69 #endif
70 }
71 
mac_selinux_enforcing(void)72 bool mac_selinux_enforcing(void) {
73         int r = 0;
74 #if HAVE_SELINUX
75 
76         /* If the SELinux status page has been successfully opened, retrieve the enforcing
77          * status over it to avoid system calls in security_getenforce(). */
78 
79         if (have_status_page)
80                 r = selinux_status_getenforce();
81         else
82                 r = security_getenforce();
83 
84 #endif
85         return r != 0;
86 }
87 
mac_selinux_retest(void)88 void mac_selinux_retest(void) {
89 #if HAVE_SELINUX
90         cached_use = -1;
91 #endif
92 }
93 
94 #if HAVE_SELINUX
95 #  if HAVE_MALLINFO2
96 #    define HAVE_GENERIC_MALLINFO 1
97 typedef struct mallinfo2 generic_mallinfo;
generic_mallinfo_get(void)98 static generic_mallinfo generic_mallinfo_get(void) {
99         return mallinfo2();
100 }
101 #  elif HAVE_MALLINFO
102 #    define HAVE_GENERIC_MALLINFO 1
103 typedef struct mallinfo generic_mallinfo;
generic_mallinfo_get(void)104 static generic_mallinfo generic_mallinfo_get(void) {
105         /* glibc has deprecated mallinfo(), let's suppress the deprecation warning if mallinfo2() doesn't
106          * exist yet. */
107 DISABLE_WARNING_DEPRECATED_DECLARATIONS
108         return mallinfo();
109 REENABLE_WARNING
110 }
111 #  else
112 #    define HAVE_GENERIC_MALLINFO 0
113 #  endif
114 
open_label_db(void)115 static int open_label_db(void) {
116         struct selabel_handle *hnd;
117         usec_t before_timestamp, after_timestamp;
118 
119 #  if HAVE_GENERIC_MALLINFO
120         generic_mallinfo before_mallinfo = generic_mallinfo_get();
121 #  endif
122         before_timestamp = now(CLOCK_MONOTONIC);
123 
124         hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
125         if (!hnd)
126                 return log_enforcing_errno(errno, "Failed to initialize SELinux labeling handle: %m");
127 
128         after_timestamp = now(CLOCK_MONOTONIC);
129 #  if HAVE_GENERIC_MALLINFO
130         generic_mallinfo after_mallinfo = generic_mallinfo_get();
131         size_t l = LESS_BY((size_t) after_mallinfo.uordblks, (size_t) before_mallinfo.uordblks);
132         log_debug("Successfully loaded SELinux database in %s, size on heap is %zuK.",
133                   FORMAT_TIMESPAN(after_timestamp - before_timestamp, 0),
134                   DIV_ROUND_UP(l, 1024));
135 #  else
136         log_debug("Successfully loaded SELinux database in %s.",
137                   FORMAT_TIMESPAN(after_timestamp - before_timestamp, 0));
138 #  endif
139 
140         /* release memory after measurement */
141         if (label_hnd)
142                 selabel_close(label_hnd);
143         label_hnd = TAKE_PTR(hnd);
144 
145         return 0;
146 }
147 #endif
148 
mac_selinux_init(void)149 int mac_selinux_init(void) {
150 #if HAVE_SELINUX
151         int r;
152 
153         if (initialized)
154                 return 0;
155 
156         if (!mac_selinux_use())
157                 return 0;
158 
159         r = selinux_status_open(/* netlink fallback */ 1);
160         if (r < 0) {
161                 if (!ERRNO_IS_PRIVILEGE(errno))
162                         return log_enforcing_errno(errno, "Failed to open SELinux status page: %m");
163                 log_warning_errno(errno, "selinux_status_open() with netlink fallback failed, not checking for policy reloads: %m");
164         } else if (r == 1)
165                 log_warning("selinux_status_open() failed to open the status page, using the netlink fallback.");
166         else
167                 have_status_page = true;
168 
169         r = open_label_db();
170         if (r < 0) {
171                 selinux_status_close();
172                 return r;
173         }
174 
175         /* Save the current policyload sequence number, so mac_selinux_maybe_reload() does not trigger on
176          * first call without any actual change. */
177         last_policyload = selinux_status_policyload();
178 
179         initialized = true;
180 #endif
181         return 0;
182 }
183 
mac_selinux_maybe_reload(void)184 void mac_selinux_maybe_reload(void) {
185 #if HAVE_SELINUX
186         int policyload;
187 
188         if (!initialized)
189                 return;
190 
191         /* Do not use selinux_status_updated(3), cause since libselinux 3.2 selinux_check_access(3),
192          * called in core and user instances, does also use it under the hood.
193          * That can cause changes to be consumed by selinux_check_access(3) and not being visible here.
194          * Also do not use selinux callbacks, selinux_set_callback(3), cause they are only automatically
195          * invoked since libselinux 3.2 by selinux_status_updated(3).
196          * Relevant libselinux commit: https://github.com/SELinuxProject/selinux/commit/05bdc03130d741e53e1fb45a958d0a2c184be503
197          * Debian Bullseye is going to ship libselinux 3.1, so stay compatible for backports. */
198         policyload = selinux_status_policyload();
199         if (policyload < 0) {
200                 log_debug_errno(errno, "Failed to get SELinux policyload from status page: %m");
201                 return;
202         }
203 
204         if (policyload != last_policyload) {
205                 mac_selinux_reload(policyload);
206                 last_policyload = policyload;
207         }
208 #endif
209 }
210 
mac_selinux_finish(void)211 void mac_selinux_finish(void) {
212 
213 #if HAVE_SELINUX
214         if (label_hnd) {
215                 selabel_close(label_hnd);
216                 label_hnd = NULL;
217         }
218 
219         selinux_status_close();
220         have_status_page = false;
221 
222         initialized = false;
223 #endif
224 }
225 
226 #if HAVE_SELINUX
mac_selinux_reload(int seqno)227 static int mac_selinux_reload(int seqno) {
228         log_debug("SELinux reload %d", seqno);
229 
230         (void) open_label_db();
231 
232         return 0;
233 }
234 #endif
235 
mac_selinux_fix_container(const char * path,const char * inside_path,LabelFixFlags flags)236 int mac_selinux_fix_container(const char *path, const char *inside_path, LabelFixFlags flags) {
237 
238         assert(path);
239         assert(inside_path);
240 
241 #if HAVE_SELINUX
242         _cleanup_close_ int fd = -1;
243 
244         /* if mac_selinux_init() wasn't called before we are a NOOP */
245         if (!label_hnd)
246                 return 0;
247 
248         /* Open the file as O_PATH, to pin it while we determine and adjust the label */
249         fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
250         if (fd < 0) {
251                 if ((flags & LABEL_IGNORE_ENOENT) && errno == ENOENT)
252                         return 0;
253 
254                 return -errno;
255         }
256 
257         return mac_selinux_fix_container_fd(fd, path, inside_path, flags);
258 #endif
259 
260         return 0;
261 }
262 
mac_selinux_fix_container_fd(int fd,const char * path,const char * inside_path,LabelFixFlags flags)263 int mac_selinux_fix_container_fd(int fd, const char *path, const char *inside_path, LabelFixFlags flags) {
264 
265         assert(fd >= 0);
266         assert(inside_path);
267 
268 #if HAVE_SELINUX
269         _cleanup_freecon_ char* fcon = NULL;
270         struct stat st;
271         int r;
272 
273         /* if mac_selinux_init() wasn't called before we are a NOOP */
274         if (!label_hnd)
275                 return 0;
276 
277         if (fstat(fd, &st) < 0)
278                 return -errno;
279 
280         /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
281         mac_selinux_maybe_reload();
282         if (!label_hnd)
283                 return 0;
284 
285         if (selabel_lookup_raw(label_hnd, &fcon, inside_path, st.st_mode) < 0) {
286                 /* If there's no label to set, then exit without warning */
287                 if (errno == ENOENT)
288                         return 0;
289 
290                 r = -errno;
291                 goto fail;
292         }
293 
294         if (setfilecon_raw(FORMAT_PROC_FD_PATH(fd), fcon) < 0) {
295                 _cleanup_freecon_ char *oldcon = NULL;
296 
297                 /* If the FS doesn't support labels, then exit without warning */
298                 if (ERRNO_IS_NOT_SUPPORTED(errno))
299                         return 0;
300 
301                 /* It the FS is read-only and we were told to ignore failures caused by that, suppress error */
302                 if (errno == EROFS && (flags & LABEL_IGNORE_EROFS))
303                         return 0;
304 
305                 r = -errno;
306 
307                 /* If the old label is identical to the new one, suppress any kind of error */
308                 if (getfilecon_raw(FORMAT_PROC_FD_PATH(fd), &oldcon) >= 0 && streq(fcon, oldcon))
309                         return 0;
310 
311                 goto fail;
312         }
313 
314         return 0;
315 
316 fail:
317         return log_enforcing_errno(r, "Unable to fix SELinux security context of %s (%s): %m", strna(path), strna(inside_path));
318 #endif
319 
320         return 0;
321 }
322 
mac_selinux_apply(const char * path,const char * label)323 int mac_selinux_apply(const char *path, const char *label) {
324 
325         assert(path);
326 
327 #if HAVE_SELINUX
328         if (!mac_selinux_use())
329                 return 0;
330 
331         assert(label);
332 
333         if (setfilecon(path, label) < 0)
334                 return log_enforcing_errno(errno, "Failed to set SELinux security context %s on path %s: %m", label, path);
335 #endif
336         return 0;
337 }
338 
mac_selinux_apply_fd(int fd,const char * path,const char * label)339 int mac_selinux_apply_fd(int fd, const char *path, const char *label) {
340 
341         assert(fd >= 0);
342 
343 #if HAVE_SELINUX
344         if (!mac_selinux_use())
345                 return 0;
346 
347         assert(label);
348 
349         if (setfilecon(FORMAT_PROC_FD_PATH(fd), label) < 0)
350                 return log_enforcing_errno(errno, "Failed to set SELinux security context %s on path %s: %m", label, strna(path));
351 #endif
352         return 0;
353 }
354 
mac_selinux_get_create_label_from_exe(const char * exe,char ** label)355 int mac_selinux_get_create_label_from_exe(const char *exe, char **label) {
356 #if HAVE_SELINUX
357         _cleanup_freecon_ char *mycon = NULL, *fcon = NULL;
358         security_class_t sclass;
359         int r;
360 
361         assert(exe);
362         assert(label);
363 
364         if (!mac_selinux_use())
365                 return -EOPNOTSUPP;
366 
367         r = getcon_raw(&mycon);
368         if (r < 0)
369                 return -errno;
370 
371         r = getfilecon_raw(exe, &fcon);
372         if (r < 0)
373                 return -errno;
374 
375         sclass = string_to_security_class("process");
376         if (sclass == 0)
377                 return -ENOSYS;
378 
379         return RET_NERRNO(security_compute_create_raw(mycon, fcon, sclass, label));
380 #else
381         return -EOPNOTSUPP;
382 #endif
383 }
384 
mac_selinux_get_our_label(char ** label)385 int mac_selinux_get_our_label(char **label) {
386 #if HAVE_SELINUX
387         assert(label);
388 
389         if (!mac_selinux_use())
390                 return -EOPNOTSUPP;
391 
392         return RET_NERRNO(getcon_raw(label));
393 #else
394         return -EOPNOTSUPP;
395 #endif
396 }
397 
mac_selinux_get_child_mls_label(int socket_fd,const char * exe,const char * exec_label,char ** label)398 int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) {
399 #if HAVE_SELINUX
400         _cleanup_freecon_ char *mycon = NULL, *peercon = NULL, *fcon = NULL;
401         _cleanup_context_free_ context_t pcon = NULL, bcon = NULL;
402         security_class_t sclass;
403         const char *range = NULL;
404         int r;
405 
406         assert(socket_fd >= 0);
407         assert(exe);
408         assert(label);
409 
410         if (!mac_selinux_use())
411                 return -EOPNOTSUPP;
412 
413         r = getcon_raw(&mycon);
414         if (r < 0)
415                 return -errno;
416 
417         r = getpeercon_raw(socket_fd, &peercon);
418         if (r < 0)
419                 return -errno;
420 
421         if (!exec_label) {
422                 /* If there is no context set for next exec let's use context
423                    of target executable */
424                 r = getfilecon_raw(exe, &fcon);
425                 if (r < 0)
426                         return -errno;
427         }
428 
429         bcon = context_new(mycon);
430         if (!bcon)
431                 return -ENOMEM;
432 
433         pcon = context_new(peercon);
434         if (!pcon)
435                 return -ENOMEM;
436 
437         range = context_range_get(pcon);
438         if (!range)
439                 return -errno;
440 
441         r = context_range_set(bcon, range);
442         if (r)
443                 return -errno;
444 
445         freecon(mycon);
446         mycon = strdup(context_str(bcon));
447         if (!mycon)
448                 return -ENOMEM;
449 
450         sclass = string_to_security_class("process");
451         if (sclass == 0)
452                 return -ENOSYS;
453 
454         return RET_NERRNO(security_compute_create_raw(mycon, fcon, sclass, label));
455 #else
456         return -EOPNOTSUPP;
457 #endif
458 }
459 
mac_selinux_free(char * label)460 char* mac_selinux_free(char *label) {
461 
462 #if HAVE_SELINUX
463         freecon(label);
464 #else
465         assert(!label);
466 #endif
467 
468         return NULL;
469 }
470 
471 #if HAVE_SELINUX
selinux_create_file_prepare_abspath(const char * abspath,mode_t mode)472 static int selinux_create_file_prepare_abspath(const char *abspath, mode_t mode) {
473         _cleanup_freecon_ char *filecon = NULL;
474         int r;
475 
476         assert(abspath);
477         assert(path_is_absolute(abspath));
478 
479         /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
480         mac_selinux_maybe_reload();
481         if (!label_hnd)
482                 return 0;
483 
484         r = selabel_lookup_raw(label_hnd, &filecon, abspath, mode);
485         if (r < 0) {
486                 /* No context specified by the policy? Proceed without setting it. */
487                 if (errno == ENOENT)
488                         return 0;
489 
490                 return log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", abspath);
491         }
492 
493         if (setfscreatecon_raw(filecon) < 0)
494                 return log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", filecon, abspath);
495 
496         return 0;
497 }
498 #endif
499 
mac_selinux_create_file_prepare_at(int dir_fd,const char * path,mode_t mode)500 int mac_selinux_create_file_prepare_at(
501                 int dir_fd,
502                 const char *path,
503                 mode_t mode) {
504 
505 #if HAVE_SELINUX
506         _cleanup_free_ char *abspath = NULL;
507         int r;
508 
509         if (dir_fd < 0 && dir_fd != AT_FDCWD)
510                 return -EBADF;
511 
512         if (!label_hnd)
513                 return 0;
514 
515         if (isempty(path) || !path_is_absolute(path)) {
516                 if (dir_fd == AT_FDCWD)
517                         r = safe_getcwd(&abspath);
518                 else
519                         r = fd_get_path(dir_fd, &abspath);
520                 if (r < 0)
521                         return r;
522 
523                 if (!isempty(path) && !path_extend(&abspath, path))
524                         return -ENOMEM;
525 
526                 path = abspath;
527         }
528 
529         return selinux_create_file_prepare_abspath(path, mode);
530 #else
531         return 0;
532 #endif
533 }
534 
mac_selinux_create_file_prepare_label(const char * path,const char * label)535 int mac_selinux_create_file_prepare_label(const char *path, const char *label) {
536 #if HAVE_SELINUX
537 
538         if (!label)
539                 return 0;
540 
541         if (!mac_selinux_use())
542                 return 0;
543 
544         if (setfscreatecon_raw(label) < 0)
545                 return log_enforcing_errno(errno, "Failed to set specified SELinux security context '%s' for '%s': %m", label, strna(path));
546 #endif
547         return 0;
548 }
549 
mac_selinux_create_file_clear(void)550 void mac_selinux_create_file_clear(void) {
551 
552 #if HAVE_SELINUX
553         PROTECT_ERRNO;
554 
555         if (!mac_selinux_use())
556                 return;
557 
558         setfscreatecon_raw(NULL);
559 #endif
560 }
561 
mac_selinux_create_socket_prepare(const char * label)562 int mac_selinux_create_socket_prepare(const char *label) {
563 
564 #if HAVE_SELINUX
565         assert(label);
566 
567         if (!mac_selinux_use())
568                 return 0;
569 
570         if (setsockcreatecon(label) < 0)
571                 return log_enforcing_errno(errno, "Failed to set SELinux security context %s for sockets: %m", label);
572 #endif
573 
574         return 0;
575 }
576 
mac_selinux_create_socket_clear(void)577 void mac_selinux_create_socket_clear(void) {
578 
579 #if HAVE_SELINUX
580         PROTECT_ERRNO;
581 
582         if (!mac_selinux_use())
583                 return;
584 
585         setsockcreatecon_raw(NULL);
586 #endif
587 }
588 
mac_selinux_bind(int fd,const struct sockaddr * addr,socklen_t addrlen)589 int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
590 
591         /* Binds a socket and label its file system object according to the SELinux policy */
592 
593 #if HAVE_SELINUX
594         _cleanup_freecon_ char *fcon = NULL;
595         const struct sockaddr_un *un;
596         bool context_changed = false;
597         char *path;
598         int r;
599 
600         assert(fd >= 0);
601         assert(addr);
602         assert(addrlen >= sizeof(sa_family_t));
603 
604         if (!label_hnd)
605                 goto skipped;
606 
607         /* Filter out non-local sockets */
608         if (addr->sa_family != AF_UNIX)
609                 goto skipped;
610 
611         /* Filter out anonymous sockets */
612         if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1)
613                 goto skipped;
614 
615         /* Filter out abstract namespace sockets */
616         un = (const struct sockaddr_un*) addr;
617         if (un->sun_path[0] == 0)
618                 goto skipped;
619 
620         path = strndupa_safe(un->sun_path,
621                              addrlen - offsetof(struct sockaddr_un, sun_path));
622 
623         /* Check for policy reload so 'label_hnd' is kept up-to-date by callbacks */
624         mac_selinux_maybe_reload();
625         if (!label_hnd)
626                 goto skipped;
627 
628         if (path_is_absolute(path))
629                 r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK);
630         else {
631                 _cleanup_free_ char *newpath = NULL;
632 
633                 r = path_make_absolute_cwd(path, &newpath);
634                 if (r < 0)
635                         return r;
636 
637                 r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK);
638         }
639 
640         if (r < 0) {
641                 /* No context specified by the policy? Proceed without setting it */
642                 if (errno == ENOENT)
643                         goto skipped;
644 
645                 r = log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", path);
646                 if (r < 0)
647                         return r;
648         } else {
649                 if (setfscreatecon_raw(fcon) < 0) {
650                         r = log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", fcon, path);
651                         if (r < 0)
652                                 return r;
653                 } else
654                         context_changed = true;
655         }
656 
657         r = RET_NERRNO(bind(fd, addr, addrlen));
658 
659         if (context_changed)
660                 (void) setfscreatecon_raw(NULL);
661 
662         return r;
663 
664 skipped:
665 #endif
666         return RET_NERRNO(bind(fd, addr, addrlen));
667 }
668