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