Submitted By:            Douglas R. Reno <renodr at linuxfromscratch dot org>
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 <sys/utsname.h>
+
 #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);
