Submitted By: Douglas R. Reno Date: 2022-01-13 Initial Package Version: 250 Origin: Upstream (PRs 21941, 21927, and commits for rm-rf) Upstream Status: Applied Description: Fixes CVE-2021-3997, which is an infinite recursion vulnerability in systemd-tmpfiles which can be triggered during creation/deletion of temporary files and directories. This also fixes a problem with the default hostname, and with getty@.service having a large delay (by way of fixing the way Idle= units are interpreted). diff -Naurp systemd-250.orig/src/basic/hostname-util.c systemd-250/src/basic/hostname-util.c --- systemd-250.orig/src/basic/hostname-util.c 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/basic/hostname-util.c 2022-01-13 11:21:23.267423307 -0600 @@ -46,8 +46,7 @@ int gethostname_full(GetHostnameFlags fl assert_se(uname(&u) >= 0); s = u.nodename; - if (isempty(s) || - (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_NONE) && streq(s, "(none)")) || + if (isempty(s) || streq(s, "(none)") || (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) || (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) { if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT)) diff -Naurp systemd-250.orig/src/basic/hostname-util.h systemd-250/src/basic/hostname-util.h --- systemd-250.orig/src/basic/hostname-util.h 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/basic/hostname-util.h 2022-01-13 11:21:23.267423307 -0600 @@ -9,10 +9,9 @@ #include "strv.h" typedef enum GetHostnameFlags { - GET_HOSTNAME_ALLOW_NONE = 1 << 0, /* accepts "(none)". */ - GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 1, /* accepts "localhost" or friends. */ - GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 2, /* use default hostname if no hostname is set. */ - GET_HOSTNAME_SHORT = 1 << 3, /* kills the FQDN part if present. */ + GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 0, /* accepts "localhost" or friends. */ + GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 1, /* use default hostname if no hostname is set. */ + GET_HOSTNAME_SHORT = 1 << 2, /* kills the FQDN part if present. */ } GetHostnameFlags; int gethostname_full(GetHostnameFlags flags, char **ret); diff -Naurp systemd-250.orig/src/core/manager.c systemd-250/src/core/manager.c --- systemd-250.orig/src/core/manager.c 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/core/manager.c 2022-01-13 11:21:49.929289326 -0600 @@ -3556,14 +3556,14 @@ void manager_check_finished(Manager *m) manager_send_ready(m); + /* Notify Type=idle units that we are done now */ + manager_close_idle_pipe(m); + if (MANAGER_IS_FINISHED(m)) return; manager_flip_auto_status(m, false, "boot finished"); - /* Notify Type=idle units that we are done now */ - manager_close_idle_pipe(m); - /* Turn off confirm spawn now */ m->confirm_spawn = NULL; diff -Naurp systemd-250.orig/src/shared/hostname-setup.c systemd-250/src/shared/hostname-setup.c --- systemd-250.orig/src/shared/hostname-setup.c 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/shared/hostname-setup.c 2022-01-13 11:21:23.267423307 -0600 @@ -20,16 +20,13 @@ #include "util.h" static int sethostname_idempotent_full(const char *s, bool really) { - _cleanup_free_ char *buf = NULL; - int r; + struct utsname u; assert(s); - r = gethostname_full(GET_HOSTNAME_ALLOW_NONE | GET_HOSTNAME_ALLOW_LOCALHOST, &buf); - if (r < 0) - return r; + assert_se(uname(&u) >= 0); - if (streq(buf, s)) + if (streq_ptr(s, u.nodename)) return 0; if (really && diff -Naurp systemd-250.orig/src/shared/rm-rf.c systemd-250/src/shared/rm-rf.c --- systemd-250.orig/src/shared/rm-rf.c 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/shared/rm-rf.c 2022-01-13 11:20:37.417653608 -0600 @@ -52,7 +52,6 @@ static int patch_dirfd_mode( } int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) { - mode_t old_mode; int r; @@ -116,15 +115,16 @@ int fstatat_harder(int dfd, return 0; } -static int rm_rf_children_inner( +static int rm_rf_inner_child( int fd, const char *fname, int is_dir, RemoveFlags flags, - const struct stat *root_dev) { + const struct stat *root_dev, + bool allow_recursion) { struct stat st; - int r; + int r, q = 0; assert(fd >= 0); assert(fname); @@ -141,10 +141,7 @@ static int rm_rf_children_inner( } if (is_dir) { - _cleanup_close_ int subdir_fd = -1; - int q; - - /* if root_dev is set, remove subdirectories only if device is same */ + /* If root_dev is set, remove subdirectories only if device is same */ if (root_dev && st.st_dev != root_dev->st_dev) return 0; @@ -156,7 +153,6 @@ static int rm_rf_children_inner( return 0; if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { - /* This could be a subvolume, try to remove it */ r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); @@ -170,31 +166,40 @@ static int rm_rf_children_inner( return 1; } - subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (!allow_recursion) + return -EISDIR; + + int subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (subdir_fd < 0) return -errno; /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type * again for each directory */ - q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev); - - r = unlinkat_harder(fd, fname, AT_REMOVEDIR, flags); - if (r < 0) - return r; - if (q < 0) - return q; + q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); - return 1; + } else if (flags & REMOVE_ONLY_DIRECTORIES) + return 0; - } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { - r = unlinkat_harder(fd, fname, 0, flags); - if (r < 0) - return r; + r = unlinkat_harder(fd, fname, is_dir ? AT_REMOVEDIR : 0, flags); + if (r < 0) + return r; + if (q < 0) + return q; + return 1; +} - return 1; +typedef struct TodoEntry { + DIR *dir; /* A directory that we were operating on. */ + char *dirname; /* The filename of that directory itself. */ +} TodoEntry; + +static void free_todo_entries(TodoEntry **todos) { + for (TodoEntry *x = *todos; x && x->dir; x++) { + closedir(x->dir); + free(x->dirname); } - return 0; + freep(todos); } int rm_rf_children( @@ -202,63 +207,114 @@ int rm_rf_children( RemoveFlags flags, const struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; + _cleanup_(free_todo_entries) TodoEntry *todos = NULL; + size_t n_todo = 0; + _cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */ int ret = 0, r; - assert(fd >= 0); + /* Return the first error we run into, but nevertheless try to go on. + * The passed fd is closed in all cases, including on failure. */ - /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed - * fd, in all cases, including on failure. */ + for (;;) { /* This loop corresponds to the directory nesting level. */ + _cleanup_closedir_ DIR *d = NULL; - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return -errno; - } + if (n_todo > 0) { + /* We know that we are in recursion here, because n_todo is set. + * We need to remove the inner directory we were operating on. */ + assert(dirname); + r = unlinkat_harder(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR, flags); + if (r < 0 && r != -ENOENT && ret == 0) + ret = r; + dirname = mfree(dirname); + + /* And now let's back out one level up */ + n_todo --; + d = TAKE_PTR(todos[n_todo].dir); + dirname = TAKE_PTR(todos[n_todo].dirname); + + assert(d); + fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */ + assert(fd >= 0); + } else { + next_fd: + assert(fd >= 0); + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } + fd = dirfd(d); /* We donated the fd to fdopendir(). Let's make sure we sure we have + * the right descriptor even if it were to internally invalidate the + * one we passed. */ - if (!(flags & REMOVE_PHYSICAL)) { - struct statfs sfs; + if (!(flags & REMOVE_PHYSICAL)) { + struct statfs sfs; - if (fstatfs(dirfd(d), &sfs) < 0) - return -errno; + if (fstatfs(fd, &sfs) < 0) + return -errno; + + if (is_physical_fs(&sfs)) { + /* We refuse to clean physical file systems with this call, unless + * explicitly requested. This is extra paranoia just to be sure we + * never ever remove non-state data. */ - if (is_physical_fs(&sfs)) { - /* We refuse to clean physical file systems with this call, unless explicitly - * requested. This is extra paranoia just to be sure we never ever remove non-state - * data. */ - - _cleanup_free_ char *path = NULL; - - (void) fd_get_path(fd, &path); - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "Attempted to remove disk file system under \"%s\", and we can't allow that.", - strna(path)); + _cleanup_free_ char *path = NULL; + + (void) fd_get_path(fd, &path); + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Attempted to remove disk file system under \"%s\", and we can't allow that.", + strna(path)); + } + } } - } - FOREACH_DIRENT_ALL(de, d, return -errno) { - int is_dir; + FOREACH_DIRENT_ALL(de, d, return -errno) { + int is_dir; - if (dot_or_dot_dot(de->d_name)) - continue; + if (dot_or_dot_dot(de->d_name)) + continue; - is_dir = - de->d_type == DT_UNKNOWN ? -1 : - de->d_type == DT_DIR; - - r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev); - if (r < 0 && r != -ENOENT && ret == 0) - ret = r; - } + is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR; + + r = rm_rf_inner_child(fd, de->d_name, is_dir, flags, root_dev, false); + if (r == -EISDIR) { + /* Push the current working state onto the todo list */ + + if (!GREEDY_REALLOC0(todos, n_todo + 2)) + return log_oom(); + + _cleanup_free_ char *newdirname = strdup(de->d_name); + if (!newdirname) + return log_oom(); + + int newfd = openat(fd, de->d_name, + O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (newfd >= 0) { + todos[n_todo++] = (TodoEntry) { TAKE_PTR(d), TAKE_PTR(dirname) }; + fd = newfd; + dirname = TAKE_PTR(newdirname); + + goto next_fd; - if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0) - ret = -errno; + } else if (errno != -ENOENT && ret == 0) + ret = -errno; + + } else if (r < 0 && r != -ENOENT && ret == 0) + ret = r; + } + + if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0) + ret = -errno; + + if (n_todo == 0) + break; + } return ret; } int rm_rf(const char *path, RemoveFlags flags) { - int fd, r; + int fd, r, q = 0; assert(path); @@ -290,49 +346,42 @@ int rm_rf(const char *path, RemoveFlags } fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (fd < 0) { + if (fd >= 0) { + /* We have a dir */ + r = rm_rf_children(fd, flags, NULL); + + if (FLAGS_SET(flags, REMOVE_ROOT)) + q = RET_NERRNO(rmdir(path)); + } else { if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT) return 0; if (!IN_SET(errno, ENOTDIR, ELOOP)) return -errno; - if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES)) + if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT)) return 0; - if (FLAGS_SET(flags, REMOVE_ROOT)) { - - if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { - struct statfs s; - - if (statfs(path, &s) < 0) - return -errno; - if (is_physical_fs(&s)) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "Attempted to remove files from a disk file system under \"%s\", refusing.", - path); - } - - if (unlink(path) < 0) { - if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT) - return 0; + if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { + struct statfs s; + if (statfs(path, &s) < 0) return -errno; - } + if (is_physical_fs(&s)) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Attempted to remove files from a disk file system under \"%s\", refusing.", + path); } - return 0; + r = 0; + q = RET_NERRNO(unlink(path)); } - r = rm_rf_children(fd, flags, NULL); - - if (FLAGS_SET(flags, REMOVE_ROOT) && - rmdir(path) < 0 && - r >= 0 && - (!FLAGS_SET(flags, REMOVE_MISSING_OK) || errno != ENOENT)) - r = -errno; - - return r; + if (r < 0) + return r; + if (q < 0 && (q != -ENOENT || !FLAGS_SET(flags, REMOVE_MISSING_OK))) + return q; + return 0; } int rm_rf_child(int fd, const char *name, RemoveFlags flags) { @@ -351,5 +400,5 @@ int rm_rf_child(int fd, const char *name if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME)) return -EINVAL; - return rm_rf_children_inner(fd, name, -1, flags, NULL); + return rm_rf_inner_child(fd, name, -1, flags, NULL, true); } diff -Naurp systemd-250.orig/src/test/test-sysctl-util.c systemd-250/src/test/test-sysctl-util.c --- systemd-250.orig/src/test/test-sysctl-util.c 2021-12-23 14:09:35.000000000 -0600 +++ systemd-250/src/test/test-sysctl-util.c 2022-01-13 11:21:23.267423307 -0600 @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-id128.h" #include "errno-util.h" @@ -38,7 +40,8 @@ TEST(sysctl_normalize) { } TEST(sysctl_read) { - _cleanup_free_ char *s = NULL, *h = NULL; + _cleanup_free_ char *s = NULL; + struct utsname u; sd_id128_t a, b; int r; @@ -63,8 +66,8 @@ TEST(sysctl_read) { s = mfree(s); assert_se(sysctl_read("kernel/hostname", &s) >= 0); - assert_se(gethostname_full(GET_HOSTNAME_ALLOW_NONE|GET_HOSTNAME_ALLOW_LOCALHOST, &h) >= 0); - assert_se(streq(s, h)); + assert_se(uname(&u) >= 0); + assert_se(streq_ptr(s, u.nodename)); r = sysctl_write("kernel/hostname", s); assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || r == -EROFS);