]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
cad45ba1 | 2 | |
66b6d9d5 | 3 | #include <errno.h> |
ca78ad1d | 4 | #include <fcntl.h> |
66b6d9d5 | 5 | #include <malloc.h> |
11c3a366 TA |
6 | #include <stddef.h> |
7 | #include <string.h> | |
8 | #include <sys/stat.h> | |
9 | #include <sys/time.h> | |
ca78ad1d | 10 | #include <sys/types.h> |
66b6d9d5 | 11 | #include <sys/un.h> |
11c3a366 | 12 | #include <syslog.h> |
a07e9cfb | 13 | |
349cc4a5 | 14 | #if HAVE_SELINUX |
66b6d9d5 | 15 | #include <selinux/context.h> |
cf0fbc49 TA |
16 | #include <selinux/label.h> |
17 | #include <selinux/selinux.h> | |
66b6d9d5 WC |
18 | #endif |
19 | ||
b5efdb8a | 20 | #include "alloc-util.h" |
2b2fec7d | 21 | #include "errno-util.h" |
08c84981 | 22 | #include "fd-util.h" |
11c3a366 TA |
23 | #include "log.h" |
24 | #include "macro.h" | |
93cc7779 TA |
25 | #include "path-util.h" |
26 | #include "selinux-util.h" | |
08c84981 | 27 | #include "stdio-util.h" |
11c3a366 | 28 | #include "time-util.h" |
cad45ba1 | 29 | |
349cc4a5 | 30 | #if HAVE_SELINUX |
2ed96880 | 31 | DEFINE_TRIVIAL_CLEANUP_FUNC(char*, freecon); |
66b6d9d5 | 32 | DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free); |
0b6018f3 | 33 | |
2ed96880 | 34 | #define _cleanup_freecon_ _cleanup_(freeconp) |
66b6d9d5 | 35 | #define _cleanup_context_free_ _cleanup_(context_freep) |
0b6018f3 | 36 | |
6baa7db0 | 37 | static int cached_use = -1; |
66b6d9d5 | 38 | static struct selabel_handle *label_hnd = NULL; |
66cedb30 | 39 | |
08c84981 LP |
40 | #define log_enforcing(...) log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, __VA_ARGS__) |
41 | #define log_enforcing_errno(r, ...) log_full_errno(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, r, __VA_ARGS__) | |
66b6d9d5 | 42 | #endif |
cad45ba1 | 43 | |
6d395665 | 44 | bool mac_selinux_use(void) { |
349cc4a5 | 45 | #if HAVE_SELINUX |
6baa7db0 LP |
46 | if (cached_use < 0) |
47 | cached_use = is_selinux_enabled() > 0; | |
cad45ba1 | 48 | |
6baa7db0 | 49 | return cached_use; |
66b6d9d5 WC |
50 | #else |
51 | return false; | |
52 | #endif | |
cad45ba1 LP |
53 | } |
54 | ||
6baa7db0 | 55 | void mac_selinux_retest(void) { |
349cc4a5 | 56 | #if HAVE_SELINUX |
6baa7db0 | 57 | cached_use = -1; |
66b6d9d5 | 58 | #endif |
cad45ba1 | 59 | } |
0b6018f3 | 60 | |
c3dacc8b | 61 | int mac_selinux_init(void) { |
66b6d9d5 | 62 | int r = 0; |
d682b3a7 | 63 | |
349cc4a5 | 64 | #if HAVE_SELINUX |
66b6d9d5 WC |
65 | usec_t before_timestamp, after_timestamp; |
66 | struct mallinfo before_mallinfo, after_mallinfo; | |
67 | ||
c3dacc8b | 68 | if (label_hnd) |
66b6d9d5 WC |
69 | return 0; |
70 | ||
c3dacc8b | 71 | if (!mac_selinux_use()) |
66b6d9d5 WC |
72 | return 0; |
73 | ||
74 | before_mallinfo = mallinfo(); | |
75 | before_timestamp = now(CLOCK_MONOTONIC); | |
76 | ||
c3dacc8b | 77 | label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); |
66b6d9d5 | 78 | if (!label_hnd) { |
b8b846d7 | 79 | log_enforcing_errno(errno, "Failed to initialize SELinux context: %m"); |
66b6d9d5 WC |
80 | r = security_getenforce() == 1 ? -errno : 0; |
81 | } else { | |
82 | char timespan[FORMAT_TIMESPAN_MAX]; | |
83 | int l; | |
84 | ||
85 | after_timestamp = now(CLOCK_MONOTONIC); | |
86 | after_mallinfo = mallinfo(); | |
87 | ||
88 | l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; | |
89 | ||
90 | log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", | |
91 | format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), | |
92 | (l+1023)/1024); | |
93 | } | |
94 | #endif | |
95 | ||
96 | return r; | |
d682b3a7 LP |
97 | } |
98 | ||
ecabcf8b LP |
99 | void mac_selinux_finish(void) { |
100 | ||
349cc4a5 | 101 | #if HAVE_SELINUX |
ecabcf8b LP |
102 | if (!label_hnd) |
103 | return; | |
104 | ||
105 | selabel_close(label_hnd); | |
f5ce2b49 | 106 | label_hnd = NULL; |
ecabcf8b LP |
107 | #endif |
108 | } | |
109 | ||
08c84981 | 110 | int mac_selinux_fix(const char *path, LabelFixFlags flags) { |
66b6d9d5 | 111 | |
349cc4a5 | 112 | #if HAVE_SELINUX |
08c84981 LP |
113 | char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; |
114 | _cleanup_freecon_ char* fcon = NULL; | |
115 | _cleanup_close_ int fd = -1; | |
66b6d9d5 | 116 | struct stat st; |
ecabcf8b | 117 | int r; |
66b6d9d5 | 118 | |
5dfc5461 LP |
119 | assert(path); |
120 | ||
121 | /* if mac_selinux_init() wasn't called before we are a NOOP */ | |
66b6d9d5 WC |
122 | if (!label_hnd) |
123 | return 0; | |
124 | ||
08c84981 LP |
125 | /* Open the file as O_PATH, to pin it while we determine and adjust the label */ |
126 | fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH); | |
127 | if (fd < 0) { | |
128 | if ((flags & LABEL_IGNORE_ENOENT) && errno == ENOENT) | |
129 | return 0; | |
130 | ||
131 | return -errno; | |
132 | } | |
133 | ||
134 | if (fstat(fd, &st) < 0) | |
135 | return -errno; | |
5dfc5461 | 136 | |
08c84981 LP |
137 | if (selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode) < 0) { |
138 | r = -errno; | |
66b6d9d5 WC |
139 | |
140 | /* If there's no label to set, then exit without warning */ | |
08c84981 | 141 | if (r == -ENOENT) |
66b6d9d5 WC |
142 | return 0; |
143 | ||
08c84981 | 144 | goto fail; |
66b6d9d5 WC |
145 | } |
146 | ||
08c84981 LP |
147 | xsprintf(procfs_path, "/proc/self/fd/%i", fd); |
148 | if (setfilecon_raw(procfs_path, fcon) < 0) { | |
149 | _cleanup_freecon_ char *oldcon = NULL; | |
150 | ||
151 | r = -errno; | |
152 | ||
153 | /* If the FS doesn't support labels, then exit without warning */ | |
154 | if (r == -EOPNOTSUPP) | |
66b6d9d5 WC |
155 | return 0; |
156 | ||
08c84981 LP |
157 | /* It the FS is read-only and we were told to ignore failures caused by that, suppress error */ |
158 | if (r == -EROFS && (flags & LABEL_IGNORE_EROFS)) | |
66b6d9d5 WC |
159 | return 0; |
160 | ||
08c84981 LP |
161 | /* If the old label is identical to the new one, suppress any kind of error */ |
162 | if (getfilecon_raw(procfs_path, &oldcon) >= 0 && streq(fcon, oldcon)) | |
163 | return 0; | |
164 | ||
165 | goto fail; | |
66b6d9d5 | 166 | } |
08c84981 LP |
167 | |
168 | return 0; | |
169 | ||
170 | fail: | |
171 | log_enforcing_errno(r, "Unable to fix SELinux security context of %s: %m", path); | |
172 | if (security_getenforce() == 1) | |
173 | return r; | |
66b6d9d5 WC |
174 | #endif |
175 | ||
ecabcf8b | 176 | return 0; |
66b6d9d5 WC |
177 | } |
178 | ||
ecabcf8b | 179 | int mac_selinux_apply(const char *path, const char *label) { |
66b6d9d5 | 180 | |
349cc4a5 | 181 | #if HAVE_SELINUX |
ecabcf8b LP |
182 | if (!mac_selinux_use()) |
183 | return 0; | |
184 | ||
0f474365 LP |
185 | assert(path); |
186 | assert(label); | |
187 | ||
2ed96880 | 188 | if (setfilecon(path, label) < 0) { |
b8b846d7 | 189 | log_enforcing_errno(errno, "Failed to set SELinux security context %s on path %s: %m", label, path); |
0f474365 | 190 | if (security_getenforce() > 0) |
ecabcf8b LP |
191 | return -errno; |
192 | } | |
66b6d9d5 | 193 | #endif |
ecabcf8b | 194 | return 0; |
d682b3a7 LP |
195 | } |
196 | ||
cc56fafe | 197 | int mac_selinux_get_create_label_from_exe(const char *exe, char **label) { |
7f416dae | 198 | int r = -EOPNOTSUPP; |
66b6d9d5 | 199 | |
349cc4a5 | 200 | #if HAVE_SELINUX |
2ed96880 | 201 | _cleanup_freecon_ char *mycon = NULL, *fcon = NULL; |
66b6d9d5 WC |
202 | security_class_t sclass; |
203 | ||
7f416dae LP |
204 | assert(exe); |
205 | assert(label); | |
206 | ||
6d395665 | 207 | if (!mac_selinux_use()) |
7f416dae | 208 | return -EOPNOTSUPP; |
66b6d9d5 | 209 | |
24154879 | 210 | r = getcon_raw(&mycon); |
66b6d9d5 | 211 | if (r < 0) |
7f416dae | 212 | return -errno; |
66b6d9d5 | 213 | |
24154879 | 214 | r = getfilecon_raw(exe, &fcon); |
66b6d9d5 | 215 | if (r < 0) |
7f416dae | 216 | return -errno; |
66b6d9d5 WC |
217 | |
218 | sclass = string_to_security_class("process"); | |
2ed96880 | 219 | r = security_compute_create_raw(mycon, fcon, sclass, label); |
7f416dae LP |
220 | if (r < 0) |
221 | return -errno; | |
66b6d9d5 WC |
222 | #endif |
223 | ||
224 | return r; | |
225 | } | |
226 | ||
cc56fafe | 227 | int mac_selinux_get_our_label(char **label) { |
66b6d9d5 WC |
228 | int r = -EOPNOTSUPP; |
229 | ||
7f416dae LP |
230 | assert(label); |
231 | ||
349cc4a5 | 232 | #if HAVE_SELINUX |
6d395665 | 233 | if (!mac_selinux_use()) |
7f416dae | 234 | return -EOPNOTSUPP; |
66b6d9d5 | 235 | |
24154879 | 236 | r = getcon_raw(label); |
66b6d9d5 | 237 | if (r < 0) |
7f416dae | 238 | return -errno; |
0b6018f3 | 239 | #endif |
66b6d9d5 WC |
240 | |
241 | return r; | |
242 | } | |
243 | ||
9008e1ac | 244 | int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { |
66b6d9d5 WC |
245 | int r = -EOPNOTSUPP; |
246 | ||
349cc4a5 | 247 | #if HAVE_SELINUX |
2ed96880 | 248 | _cleanup_freecon_ char *mycon = NULL, *peercon = NULL, *fcon = NULL; |
66b6d9d5 WC |
249 | _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; |
250 | security_class_t sclass; | |
66b6d9d5 WC |
251 | const char *range = NULL; |
252 | ||
253 | assert(socket_fd >= 0); | |
254 | assert(exe); | |
255 | assert(label); | |
256 | ||
6d395665 | 257 | if (!mac_selinux_use()) |
7f416dae LP |
258 | return -EOPNOTSUPP; |
259 | ||
24154879 | 260 | r = getcon_raw(&mycon); |
7f416dae LP |
261 | if (r < 0) |
262 | return -errno; | |
66b6d9d5 | 263 | |
a1d2de07 | 264 | r = getpeercon_raw(socket_fd, &peercon); |
7f416dae LP |
265 | if (r < 0) |
266 | return -errno; | |
66b6d9d5 | 267 | |
9008e1ac | 268 | if (!exec_label) { |
66b6d9d5 WC |
269 | /* If there is no context set for next exec let's use context |
270 | of target executable */ | |
24154879 | 271 | r = getfilecon_raw(exe, &fcon); |
7f416dae LP |
272 | if (r < 0) |
273 | return -errno; | |
66b6d9d5 WC |
274 | } |
275 | ||
276 | bcon = context_new(mycon); | |
7f416dae LP |
277 | if (!bcon) |
278 | return -ENOMEM; | |
66b6d9d5 WC |
279 | |
280 | pcon = context_new(peercon); | |
7f416dae LP |
281 | if (!pcon) |
282 | return -ENOMEM; | |
66b6d9d5 WC |
283 | |
284 | range = context_range_get(pcon); | |
7f416dae LP |
285 | if (!range) |
286 | return -errno; | |
66b6d9d5 WC |
287 | |
288 | r = context_range_set(bcon, range); | |
7f416dae LP |
289 | if (r) |
290 | return -errno; | |
66b6d9d5 WC |
291 | |
292 | freecon(mycon); | |
293 | mycon = strdup(context_str(bcon)); | |
7f416dae LP |
294 | if (!mycon) |
295 | return -ENOMEM; | |
66b6d9d5 WC |
296 | |
297 | sclass = string_to_security_class("process"); | |
2ed96880 | 298 | r = security_compute_create_raw(mycon, fcon, sclass, label); |
7f416dae LP |
299 | if (r < 0) |
300 | return -errno; | |
66b6d9d5 | 301 | #endif |
7f416dae | 302 | |
66b6d9d5 WC |
303 | return r; |
304 | } | |
305 | ||
710a6b50 | 306 | char* mac_selinux_free(char *label) { |
ecabcf8b | 307 | |
349cc4a5 | 308 | #if HAVE_SELINUX |
710a6b50 LP |
309 | if (!label) |
310 | return NULL; | |
311 | ||
6d395665 | 312 | if (!mac_selinux_use()) |
710a6b50 LP |
313 | return NULL; |
314 | ||
2ed96880 | 315 | freecon(label); |
ecabcf8b | 316 | #endif |
710a6b50 LP |
317 | |
318 | return NULL; | |
ecabcf8b LP |
319 | } |
320 | ||
349cc4a5 | 321 | #if HAVE_SELINUX |
7e531a52 | 322 | static int selinux_create_file_prepare_abspath(const char *abspath, mode_t mode) { |
2ed96880 | 323 | _cleanup_freecon_ char *filecon = NULL; |
0f474365 | 324 | int r; |
66b6d9d5 | 325 | |
7e531a52 FB |
326 | assert(abspath); |
327 | assert(path_is_absolute(abspath)); | |
c34255bd | 328 | |
7e531a52 | 329 | r = selabel_lookup_raw(label_hnd, &filecon, abspath, mode); |
0f474365 LP |
330 | if (r < 0) { |
331 | /* No context specified by the policy? Proceed without setting it. */ | |
332 | if (errno == ENOENT) | |
333 | return 0; | |
2d58aa46 | 334 | |
7e531a52 | 335 | log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", abspath); |
0f474365 | 336 | } else { |
5c5433ad | 337 | if (setfscreatecon_raw(filecon) >= 0) |
0f474365 LP |
338 | return 0; /* Success! */ |
339 | ||
7e531a52 | 340 | log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", filecon, abspath); |
66b6d9d5 WC |
341 | } |
342 | ||
0f474365 LP |
343 | if (security_getenforce() > 0) |
344 | return -errno; | |
66b6d9d5 | 345 | |
0f474365 | 346 | return 0; |
66b6d9d5 | 347 | } |
7e531a52 FB |
348 | #endif |
349 | ||
350 | int mac_selinux_create_file_prepare_at(int dirfd, const char *path, mode_t mode) { | |
351 | int r = 0; | |
352 | ||
353 | #if HAVE_SELINUX | |
354 | _cleanup_free_ char *abspath = NULL; | |
7e531a52 FB |
355 | |
356 | assert(path); | |
357 | ||
358 | if (!label_hnd) | |
359 | return 0; | |
360 | ||
361 | if (!path_is_absolute(path)) { | |
362 | _cleanup_free_ char *p = NULL; | |
363 | ||
364 | if (dirfd == AT_FDCWD) | |
365 | r = safe_getcwd(&p); | |
366 | else | |
367 | r = fd_get_path(dirfd, &p); | |
368 | if (r < 0) | |
369 | return r; | |
370 | ||
62a85ee0 | 371 | path = abspath = path_join(p, path); |
30016f21 | 372 | if (!path) |
7e531a52 | 373 | return -ENOMEM; |
7e531a52 FB |
374 | } |
375 | ||
376 | r = selinux_create_file_prepare_abspath(path, mode); | |
377 | #endif | |
378 | return r; | |
379 | } | |
380 | ||
381 | int mac_selinux_create_file_prepare(const char *path, mode_t mode) { | |
382 | int r = 0; | |
383 | ||
384 | #if HAVE_SELINUX | |
385 | _cleanup_free_ char *abspath = NULL; | |
386 | ||
387 | assert(path); | |
388 | ||
389 | if (!label_hnd) | |
390 | return 0; | |
391 | ||
392 | r = path_make_absolute_cwd(path, &abspath); | |
393 | if (r < 0) | |
394 | return r; | |
395 | ||
396 | r = selinux_create_file_prepare_abspath(abspath, mode); | |
397 | #endif | |
398 | return r; | |
399 | } | |
66b6d9d5 | 400 | |
ecabcf8b | 401 | void mac_selinux_create_file_clear(void) { |
66b6d9d5 | 402 | |
349cc4a5 | 403 | #if HAVE_SELINUX |
66b6d9d5 WC |
404 | PROTECT_ERRNO; |
405 | ||
6baa7db0 | 406 | if (!mac_selinux_use()) |
66b6d9d5 WC |
407 | return; |
408 | ||
a1d2de07 | 409 | setfscreatecon_raw(NULL); |
66b6d9d5 WC |
410 | #endif |
411 | } | |
412 | ||
ecabcf8b | 413 | int mac_selinux_create_socket_prepare(const char *label) { |
66b6d9d5 | 414 | |
349cc4a5 | 415 | #if HAVE_SELINUX |
6baa7db0 | 416 | if (!mac_selinux_use()) |
ecabcf8b | 417 | return 0; |
66b6d9d5 | 418 | |
ecabcf8b LP |
419 | assert(label); |
420 | ||
2ed96880 | 421 | if (setsockcreatecon(label) < 0) { |
b8b846d7 | 422 | log_enforcing_errno(errno, "Failed to set SELinux security context %s for sockets: %m", label); |
ecabcf8b LP |
423 | |
424 | if (security_getenforce() == 1) | |
425 | return -errno; | |
426 | } | |
66b6d9d5 | 427 | #endif |
ecabcf8b LP |
428 | |
429 | return 0; | |
66b6d9d5 WC |
430 | } |
431 | ||
ecabcf8b | 432 | void mac_selinux_create_socket_clear(void) { |
66b6d9d5 | 433 | |
349cc4a5 | 434 | #if HAVE_SELINUX |
ecabcf8b LP |
435 | PROTECT_ERRNO; |
436 | ||
6baa7db0 | 437 | if (!mac_selinux_use()) |
66b6d9d5 WC |
438 | return; |
439 | ||
a1d2de07 | 440 | setsockcreatecon_raw(NULL); |
66b6d9d5 WC |
441 | #endif |
442 | } | |
443 | ||
cc56fafe | 444 | int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { |
66b6d9d5 WC |
445 | |
446 | /* Binds a socket and label its file system object according to the SELinux policy */ | |
447 | ||
349cc4a5 | 448 | #if HAVE_SELINUX |
2ed96880 | 449 | _cleanup_freecon_ char *fcon = NULL; |
66b6d9d5 | 450 | const struct sockaddr_un *un; |
0f474365 | 451 | bool context_changed = false; |
66b6d9d5 WC |
452 | char *path; |
453 | int r; | |
454 | ||
455 | assert(fd >= 0); | |
456 | assert(addr); | |
457 | assert(addrlen >= sizeof(sa_family_t)); | |
458 | ||
ecabcf8b | 459 | if (!label_hnd) |
66b6d9d5 WC |
460 | goto skipped; |
461 | ||
462 | /* Filter out non-local sockets */ | |
463 | if (addr->sa_family != AF_UNIX) | |
464 | goto skipped; | |
465 | ||
466 | /* Filter out anonymous sockets */ | |
0f474365 | 467 | if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1) |
66b6d9d5 WC |
468 | goto skipped; |
469 | ||
470 | /* Filter out abstract namespace sockets */ | |
471 | un = (const struct sockaddr_un*) addr; | |
472 | if (un->sun_path[0] == 0) | |
473 | goto skipped; | |
474 | ||
475 | path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); | |
476 | ||
477 | if (path_is_absolute(path)) | |
478 | r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); | |
479 | else { | |
0f474365 | 480 | _cleanup_free_ char *newpath = NULL; |
66b6d9d5 | 481 | |
0f474365 LP |
482 | r = path_make_absolute_cwd(path, &newpath); |
483 | if (r < 0) | |
484 | return r; | |
66b6d9d5 WC |
485 | |
486 | r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); | |
487 | } | |
488 | ||
0f474365 LP |
489 | if (r < 0) { |
490 | /* No context specified by the policy? Proceed without setting it */ | |
491 | if (errno == ENOENT) | |
492 | goto skipped; | |
66b6d9d5 | 493 | |
b8b846d7 | 494 | log_enforcing_errno(errno, "Failed to determine SELinux security context for %s: %m", path); |
0f474365 LP |
495 | if (security_getenforce() > 0) |
496 | return -errno; | |
66b6d9d5 | 497 | |
0f474365 | 498 | } else { |
a1d2de07 | 499 | if (setfscreatecon_raw(fcon) < 0) { |
b8b846d7 | 500 | log_enforcing_errno(errno, "Failed to set SELinux security context %s for %s: %m", fcon, path); |
0f474365 LP |
501 | if (security_getenforce() > 0) |
502 | return -errno; | |
503 | } else | |
504 | context_changed = true; | |
66b6d9d5 WC |
505 | } |
506 | ||
0f474365 LP |
507 | r = bind(fd, addr, addrlen) < 0 ? -errno : 0; |
508 | ||
509 | if (context_changed) | |
a1d2de07 | 510 | setfscreatecon_raw(NULL); |
66b6d9d5 | 511 | |
66b6d9d5 WC |
512 | return r; |
513 | ||
514 | skipped: | |
515 | #endif | |
0f474365 LP |
516 | if (bind(fd, addr, addrlen) < 0) |
517 | return -errno; | |
518 | ||
519 | return 0; | |
66b6d9d5 | 520 | } |