1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/mount.h>
4
5 #include "alloc-util.h"
6 #include "cgroup-setup.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "format-util.h"
10 #include "fs-util.h"
11 #include "mkdir.h"
12 #include "mount-util.h"
13 #include "mountpoint-util.h"
14 #include "nspawn-cgroup.h"
15 #include "nspawn-mount.h"
16 #include "path-util.h"
17 #include "rm-rf.h"
18 #include "string-util.h"
19 #include "strv.h"
20 #include "user-util.h"
21 #include "util.h"
22
chown_cgroup_path(const char * path,uid_t uid_shift)23 static int chown_cgroup_path(const char *path, uid_t uid_shift) {
24 _cleanup_close_ int fd = -1;
25
26 fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
27 if (fd < 0)
28 return -errno;
29
30 FOREACH_STRING(fn,
31 ".",
32 "cgroup.clone_children",
33 "cgroup.controllers",
34 "cgroup.events",
35 "cgroup.procs",
36 "cgroup.stat",
37 "cgroup.subtree_control",
38 "cgroup.threads",
39 "notify_on_release",
40 "tasks")
41 if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0)
42 log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
43 "Failed to chown \"%s/%s\", ignoring: %m", path, fn);
44
45 return 0;
46 }
47
chown_cgroup(pid_t pid,CGroupUnified unified_requested,uid_t uid_shift)48 int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) {
49 _cleanup_free_ char *path = NULL, *fs = NULL;
50 int r;
51
52 r = cg_pid_get_path(NULL, pid, &path);
53 if (r < 0)
54 return log_error_errno(r, "Failed to get container cgroup path: %m");
55
56 r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs);
57 if (r < 0)
58 return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
59
60 r = chown_cgroup_path(fs, uid_shift);
61 if (r < 0)
62 return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs);
63
64 if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) {
65 _cleanup_free_ char *lfs = NULL;
66 /* Always propagate access rights from unified to legacy controller */
67
68 r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, NULL, &lfs);
69 if (r < 0)
70 return log_error_errno(r, "Failed to get file system path for container cgroup: %m");
71
72 r = chown_cgroup_path(lfs, uid_shift);
73 if (r < 0)
74 return log_error_errno(r, "Failed to chown() cgroup %s: %m", lfs);
75 }
76
77 return 0;
78 }
79
sync_cgroup(pid_t pid,CGroupUnified unified_requested,uid_t uid_shift)80 int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) {
81 _cleanup_free_ char *cgroup = NULL;
82 char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1];
83 bool undo_mount = false;
84 const char *fn;
85 int r, unified_controller;
86
87 unified_controller = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER);
88 if (unified_controller < 0)
89 return log_error_errno(unified_controller, "Failed to determine whether the systemd hierarchy is unified: %m");
90 if ((unified_controller > 0) == (unified_requested >= CGROUP_UNIFIED_SYSTEMD))
91 return 0;
92
93 /* When the host uses the legacy cgroup setup, but the
94 * container shall use the unified hierarchy, let's make sure
95 * we copy the path from the name=systemd hierarchy into the
96 * unified hierarchy. Similar for the reverse situation. */
97
98 r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
99 if (r < 0)
100 return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid);
101
102 /* In order to access the unified hierarchy we need to mount it */
103 if (!mkdtemp(tree))
104 return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m");
105
106 if (unified_controller > 0)
107 r = mount_nofollow_verbose(LOG_ERR, "cgroup", tree, "cgroup",
108 MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr");
109 else
110 r = mount_nofollow_verbose(LOG_ERR, "cgroup", tree, "cgroup2",
111 MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
112 if (r < 0)
113 goto finish;
114
115 undo_mount = true;
116
117 /* If nspawn dies abruptly the cgroup hierarchy created below
118 * its unit isn't cleaned up. So, let's remove it
119 * https://github.com/systemd/systemd/pull/4223#issuecomment-252519810 */
120 fn = strjoina(tree, cgroup);
121 (void) rm_rf(fn, REMOVE_ROOT|REMOVE_ONLY_DIRECTORIES);
122
123 fn = strjoina(tree, cgroup, "/cgroup.procs");
124
125 sprintf(pid_string, PID_FMT, pid);
126 r = write_string_file(fn, pid_string, WRITE_STRING_FILE_DISABLE_BUFFER|WRITE_STRING_FILE_MKDIR_0755);
127 if (r < 0) {
128 log_error_errno(r, "Failed to move process: %m");
129 goto finish;
130 }
131
132 fn = strjoina(tree, cgroup);
133 r = chown_cgroup_path(fn, uid_shift);
134 if (r < 0)
135 log_error_errno(r, "Failed to chown() cgroup %s: %m", fn);
136 finish:
137 if (undo_mount)
138 (void) umount_verbose(LOG_ERR, tree, UMOUNT_NOFOLLOW);
139
140 (void) rmdir(tree);
141 return r;
142 }
143
create_subcgroup(pid_t pid,bool keep_unit,CGroupUnified unified_requested)144 int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested) {
145 _cleanup_free_ char *cgroup = NULL;
146 CGroupMask supported;
147 const char *payload;
148 int r;
149
150 assert(pid > 1);
151
152 /* In the unified hierarchy inner nodes may only contain subgroups, but not processes. Hence, if we running in
153 * the unified hierarchy and the container does the same, and we did not create a scope unit for the container
154 * move us and the container into two separate subcgroups.
155 *
156 * Moreover, container payloads such as systemd try to manage the cgroup they run in full (i.e. including
157 * its attributes), while the host systemd will only delegate cgroups for children of the cgroup created for a
158 * delegation unit, instead of the cgroup itself. This means, if we'd pass on the cgroup allocated from the
159 * host systemd directly to the payload, the host and payload systemd might fight for the cgroup
160 * attributes. Hence, let's insert an intermediary cgroup to cover that case too.
161 *
162 * Note that we only bother with the main hierarchy here, not with any secondary ones. On the unified setup
163 * that's fine because there's only one hierarchy anyway and controllers are enabled directly on it. On the
164 * legacy setup, this is fine too, since delegation of controllers is generally not safe there, hence we won't
165 * do it. */
166
167 r = cg_mask_supported(&supported);
168 if (r < 0)
169 return log_error_errno(r, "Failed to determine supported controllers: %m");
170
171 if (keep_unit)
172 r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup);
173 else
174 r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup);
175 if (r < 0)
176 return log_error_errno(r, "Failed to get our control group: %m");
177
178 payload = strjoina(cgroup, "/payload");
179 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid);
180 if (r < 0)
181 return log_error_errno(r, "Failed to create %s subcgroup: %m", payload);
182
183 if (keep_unit) {
184 const char *supervisor;
185
186 supervisor = strjoina(cgroup, "/supervisor");
187 r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, supervisor, 0);
188 if (r < 0)
189 return log_error_errno(r, "Failed to create %s subcgroup: %m", supervisor);
190 }
191
192 /* Try to enable as many controllers as possible for the new payload. */
193 (void) cg_enable_everywhere(supported, supported, cgroup, NULL);
194 return 0;
195 }
196
197 /* Retrieve existing subsystems. This function is called in a new cgroup
198 * namespace.
199 */
get_process_controllers(Set ** ret)200 static int get_process_controllers(Set **ret) {
201 _cleanup_set_free_ Set *controllers = NULL;
202 _cleanup_fclose_ FILE *f = NULL;
203 int r;
204
205 assert(ret);
206
207 f = fopen("/proc/self/cgroup", "re");
208 if (!f)
209 return errno == ENOENT ? -ESRCH : -errno;
210
211 for (;;) {
212 _cleanup_free_ char *line = NULL;
213 char *e, *l;
214
215 r = read_line(f, LONG_LINE_MAX, &line);
216 if (r < 0)
217 return r;
218 if (r == 0)
219 break;
220
221 l = strchr(line, ':');
222 if (!l)
223 continue;
224
225 l++;
226 e = strchr(l, ':');
227 if (!e)
228 continue;
229
230 *e = 0;
231
232 if (STR_IN_SET(l, "", "name=systemd", "name=unified"))
233 continue;
234
235 r = set_put_strdup(&controllers, l);
236 if (r < 0)
237 return r;
238 }
239
240 *ret = TAKE_PTR(controllers);
241
242 return 0;
243 }
244
mount_legacy_cgroup_hierarchy(const char * dest,const char * controller,const char * hierarchy,bool read_only)245 static int mount_legacy_cgroup_hierarchy(
246 const char *dest,
247 const char *controller,
248 const char *hierarchy,
249 bool read_only) {
250
251 const char *to, *fstype, *opts;
252 int r;
253
254 to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy);
255
256 r = path_is_mount_point(to, dest, 0);
257 if (r < 0 && r != -ENOENT)
258 return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to);
259 if (r > 0)
260 return 0;
261
262 (void) mkdir_p(to, 0755);
263
264 /* The superblock mount options of the mount point need to be
265 * identical to the hosts', and hence writable... */
266 if (streq(controller, SYSTEMD_CGROUP_CONTROLLER_HYBRID)) {
267 fstype = "cgroup2";
268 opts = NULL;
269 } else if (streq(controller, SYSTEMD_CGROUP_CONTROLLER_LEGACY)) {
270 fstype = "cgroup";
271 opts = "none,name=systemd,xattr";
272 } else {
273 fstype = "cgroup";
274 opts = controller;
275 }
276
277 r = mount_nofollow_verbose(LOG_ERR, "cgroup", to, fstype, MS_NOSUID|MS_NOEXEC|MS_NODEV, opts);
278 if (r < 0)
279 return r;
280
281 /* ... hence let's only make the bind mount read-only, not the superblock. */
282 if (read_only) {
283 r = mount_nofollow_verbose(LOG_ERR, NULL, to, NULL,
284 MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL);
285 if (r < 0)
286 return r;
287 }
288
289 return 1;
290 }
291
292 /* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */
mount_legacy_cgns_supported(const char * dest,CGroupUnified unified_requested,bool userns,uid_t uid_shift,uid_t uid_range,const char * selinux_apifs_context)293 static int mount_legacy_cgns_supported(
294 const char *dest,
295 CGroupUnified unified_requested,
296 bool userns,
297 uid_t uid_shift,
298 uid_t uid_range,
299 const char *selinux_apifs_context) {
300
301 _cleanup_set_free_ Set *controllers = NULL;
302 const char *cgroup_root = "/sys/fs/cgroup", *c;
303 int r;
304
305 (void) mkdir_p(cgroup_root, 0755);
306
307 /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
308 r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW);
309 if (r < 0)
310 return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
311 if (r == 0) {
312 _cleanup_free_ char *options = NULL;
313
314 /* When cgroup namespaces are enabled and user namespaces are
315 * used then the mount of the cgroupfs is done *inside* the new
316 * user namespace. We're root in the new user namespace and the
317 * kernel will happily translate our uid/gid to the correct
318 * uid/gid as seen from e.g. /proc/1/mountinfo. So we simply
319 * pass uid 0 and not uid_shift to tmpfs_patch_options().
320 */
321 r = tmpfs_patch_options("mode=755" TMPFS_LIMITS_SYS_FS_CGROUP, 0, selinux_apifs_context, &options);
322 if (r < 0)
323 return log_oom();
324
325 r = mount_nofollow_verbose(LOG_ERR, "tmpfs", cgroup_root, "tmpfs",
326 MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options);
327 if (r < 0)
328 return r;
329 }
330
331 r = cg_all_unified();
332 if (r < 0)
333 return r;
334 if (r > 0)
335 goto skip_controllers;
336
337 r = get_process_controllers(&controllers);
338 if (r < 0)
339 return log_error_errno(r, "Failed to determine cgroup controllers: %m");
340
341 for (;;) {
342 _cleanup_free_ const char *controller = NULL;
343
344 controller = set_steal_first(controllers);
345 if (!controller)
346 break;
347
348 r = mount_legacy_cgroup_hierarchy("", controller, controller, !userns);
349 if (r < 0)
350 return r;
351
352 /* When multiple hierarchies are co-mounted, make their
353 * constituting individual hierarchies a symlink to the
354 * co-mount.
355 */
356 c = controller;
357 for (;;) {
358 _cleanup_free_ char *target = NULL, *tok = NULL;
359
360 r = extract_first_word(&c, &tok, ",", 0);
361 if (r < 0)
362 return log_error_errno(r, "Failed to extract co-mounted cgroup controller: %m");
363 if (r == 0)
364 break;
365
366 if (streq(controller, tok))
367 break;
368
369 target = path_join("/sys/fs/cgroup/", tok);
370 if (!target)
371 return log_oom();
372
373 r = symlink_idempotent(controller, target, false);
374 if (r == -EINVAL)
375 return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m");
376 if (r < 0)
377 return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m");
378 }
379 }
380
381 skip_controllers:
382 if (unified_requested >= CGROUP_UNIFIED_SYSTEMD) {
383 r = mount_legacy_cgroup_hierarchy("", SYSTEMD_CGROUP_CONTROLLER_HYBRID, "unified", false);
384 if (r < 0)
385 return r;
386 }
387
388 r = mount_legacy_cgroup_hierarchy("", SYSTEMD_CGROUP_CONTROLLER_LEGACY, "systemd", false);
389 if (r < 0)
390 return r;
391
392 if (!userns)
393 return mount_nofollow_verbose(LOG_ERR, NULL, cgroup_root, NULL,
394 MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755");
395
396 return 0;
397 }
398
399 /* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */
mount_legacy_cgns_unsupported(const char * dest,CGroupUnified unified_requested,bool userns,uid_t uid_shift,uid_t uid_range,const char * selinux_apifs_context)400 static int mount_legacy_cgns_unsupported(
401 const char *dest,
402 CGroupUnified unified_requested,
403 bool userns,
404 uid_t uid_shift,
405 uid_t uid_range,
406 const char *selinux_apifs_context) {
407
408 _cleanup_set_free_ Set *controllers = NULL;
409 const char *cgroup_root;
410 int r;
411
412 cgroup_root = prefix_roota(dest, "/sys/fs/cgroup");
413
414 (void) mkdir_p(cgroup_root, 0755);
415
416 /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
417 r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW);
418 if (r < 0)
419 return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
420 if (r == 0) {
421 _cleanup_free_ char *options = NULL;
422
423 r = tmpfs_patch_options("mode=755" TMPFS_LIMITS_SYS_FS_CGROUP, uid_shift == 0 ? UID_INVALID : uid_shift, selinux_apifs_context, &options);
424 if (r < 0)
425 return log_oom();
426
427 r = mount_nofollow_verbose(LOG_ERR, "tmpfs", cgroup_root, "tmpfs",
428 MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options);
429 if (r < 0)
430 return r;
431 }
432
433 r = cg_all_unified();
434 if (r < 0)
435 return r;
436 if (r > 0)
437 goto skip_controllers;
438
439 r = cg_kernel_controllers(&controllers);
440 if (r < 0)
441 return log_error_errno(r, "Failed to determine cgroup controllers: %m");
442
443 for (;;) {
444 _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL;
445
446 controller = set_steal_first(controllers);
447 if (!controller)
448 break;
449
450 origin = path_join("/sys/fs/cgroup/", controller);
451 if (!origin)
452 return log_oom();
453
454 r = readlink_malloc(origin, &combined);
455 if (r == -EINVAL) {
456 /* Not a symbolic link, but directly a single cgroup hierarchy */
457
458 r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true);
459 if (r < 0)
460 return r;
461
462 } else if (r < 0)
463 return log_error_errno(r, "Failed to read link %s: %m", origin);
464 else {
465 _cleanup_free_ char *target = NULL;
466
467 target = path_join(dest, origin);
468 if (!target)
469 return log_oom();
470
471 /* A symbolic link, a combination of controllers in one hierarchy */
472
473 if (!filename_is_valid(combined)) {
474 log_warning("Ignoring invalid combined hierarchy %s.", combined);
475 continue;
476 }
477
478 r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true);
479 if (r < 0)
480 return r;
481
482 r = symlink_idempotent(combined, target, false);
483 if (r == -EINVAL)
484 return log_error_errno(r, "Invalid existing symlink for combined hierarchy: %m");
485 if (r < 0)
486 return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m");
487 }
488 }
489
490 skip_controllers:
491 if (unified_requested >= CGROUP_UNIFIED_SYSTEMD) {
492 r = mount_legacy_cgroup_hierarchy(dest, SYSTEMD_CGROUP_CONTROLLER_HYBRID, "unified", false);
493 if (r < 0)
494 return r;
495 }
496
497 r = mount_legacy_cgroup_hierarchy(dest, SYSTEMD_CGROUP_CONTROLLER_LEGACY, "systemd", false);
498 if (r < 0)
499 return r;
500
501 return mount_nofollow_verbose(LOG_ERR, NULL, cgroup_root, NULL,
502 MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755");
503 }
504
mount_unified_cgroups(const char * dest)505 static int mount_unified_cgroups(const char *dest) {
506 const char *p;
507 int r;
508
509 assert(dest);
510
511 p = prefix_roota(dest, "/sys/fs/cgroup");
512
513 (void) mkdir_p(p, 0755);
514
515 r = path_is_mount_point(p, dest, AT_SYMLINK_FOLLOW);
516 if (r < 0)
517 return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p);
518 if (r > 0) {
519 p = prefix_roota(dest, "/sys/fs/cgroup/cgroup.procs");
520 if (access(p, F_OK) >= 0)
521 return 0;
522 if (errno != ENOENT)
523 return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p);
524
525 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
526 "%s is already mounted but not a unified cgroup hierarchy. Refusing.", p);
527 }
528
529 return mount_nofollow_verbose(LOG_ERR, "cgroup", p, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
530 }
531
mount_cgroups(const char * dest,CGroupUnified unified_requested,bool userns,uid_t uid_shift,uid_t uid_range,const char * selinux_apifs_context,bool use_cgns)532 int mount_cgroups(
533 const char *dest,
534 CGroupUnified unified_requested,
535 bool userns,
536 uid_t uid_shift,
537 uid_t uid_range,
538 const char *selinux_apifs_context,
539 bool use_cgns) {
540
541 if (unified_requested >= CGROUP_UNIFIED_ALL)
542 return mount_unified_cgroups(dest);
543 if (use_cgns)
544 return mount_legacy_cgns_supported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context);
545
546 return mount_legacy_cgns_unsupported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context);
547 }
548
mount_systemd_cgroup_writable_one(const char * root,const char * own)549 static int mount_systemd_cgroup_writable_one(const char *root, const char *own) {
550 int r;
551
552 assert(root);
553 assert(own);
554
555 /* Make our own cgroup a (writable) bind mount */
556 r = mount_nofollow_verbose(LOG_ERR, own, own, NULL, MS_BIND, NULL);
557 if (r < 0)
558 return r;
559
560 /* And then remount the systemd cgroup root read-only */
561 return mount_nofollow_verbose(LOG_ERR, NULL, root, NULL,
562 MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL);
563 }
564
mount_systemd_cgroup_writable(const char * dest,CGroupUnified unified_requested)565 int mount_systemd_cgroup_writable(
566 const char *dest,
567 CGroupUnified unified_requested) {
568
569 _cleanup_free_ char *own_cgroup_path = NULL;
570 const char *root, *own;
571 int r;
572
573 assert(dest);
574
575 r = cg_pid_get_path(NULL, 0, &own_cgroup_path);
576 if (r < 0)
577 return log_error_errno(r, "Failed to determine our own cgroup path: %m");
578
579 /* If we are living in the top-level, then there's nothing to do... */
580 if (path_equal(own_cgroup_path, "/"))
581 return 0;
582
583 if (unified_requested >= CGROUP_UNIFIED_ALL) {
584
585 root = prefix_roota(dest, "/sys/fs/cgroup");
586 own = strjoina(root, own_cgroup_path);
587
588 } else {
589
590 if (unified_requested >= CGROUP_UNIFIED_SYSTEMD) {
591 root = prefix_roota(dest, "/sys/fs/cgroup/unified");
592 own = strjoina(root, own_cgroup_path);
593
594 r = mount_systemd_cgroup_writable_one(root, own);
595 if (r < 0)
596 return r;
597 }
598
599 root = prefix_roota(dest, "/sys/fs/cgroup/systemd");
600 own = strjoina(root, own_cgroup_path);
601 }
602
603 return mount_systemd_cgroup_writable_one(root, own);
604 }
605