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