[dunfell] systemd: Fix CVE-2021-3997

Message ID 20220121130733.13839-1-purushottamchoudhary29@gmail.com
State Accepted, archived
Commit b7f79fbf23488b954987dfc4aa867e42bdce7fee
Headers show
Series [dunfell] systemd: Fix CVE-2021-3997 | expand

Commit Message

Purushottam Choudhary Jan. 21, 2022, 1:07 p.m. UTC
Add patches to fix CVE-2021-3997.

Add additional below mentioned patches which are
required to fix CVE:
1. rm-rf-optionally-fsync-after-removing-directory-tree.patch
2. rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch
Link: http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz

Signed-off-by: Purushottam Choudhary <purushottam.choudhary@kpit.com>
Signed-off-by: Purushottam Choudhary <purushottamchoudhary29@gmail.com>
---
 .../systemd/systemd/CVE-2021-3997-1.patch     |  65 ++++
 .../systemd/systemd/CVE-2021-3997-2.patch     | 101 ++++++
 .../systemd/systemd/CVE-2021-3997-3.patch     | 266 +++++++++++++++
 ...-fsync-after-removing-directory-tree.patch |  35 ++
 ...children-split-out-body-of-directory.patch | 318 ++++++++++++++++++
 meta/recipes-core/systemd/systemd_244.5.bb    |   5 +
 6 files changed, 790 insertions(+)
 create mode 100644 meta/recipes-core/systemd/systemd/CVE-2021-3997-1.patch
 create mode 100644 meta/recipes-core/systemd/systemd/CVE-2021-3997-2.patch
 create mode 100644 meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
 create mode 100644 meta/recipes-core/systemd/systemd/rm-rf-optionally-fsync-after-removing-directory-tree.patch
 create mode 100644 meta/recipes-core/systemd/systemd/rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch

Patch

diff --git a/meta/recipes-core/systemd/systemd/CVE-2021-3997-1.patch b/meta/recipes-core/systemd/systemd/CVE-2021-3997-1.patch
new file mode 100644
index 0000000000..341976822b
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/CVE-2021-3997-1.patch
@@ -0,0 +1,65 @@ 
+Backport of the following upstream commit:
+From fbb77e1e55866633c9f064e2b3bcf2b6402d962d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 23 Nov 2021 15:55:45 +0100
+Subject: [PATCH 1/3] shared/rm_rf: refactor rm_rf_children_inner() to shorten
+ code a bit
+
+CVE: CVE-2021-3997
+Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
+Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
+---
+ src/basic/rm-rf.c | 27 +++++++++------------------
+ 1 file changed, 9 insertions(+), 18 deletions(-)
+
+--- a/src/basic/rm-rf.c
++++ b/src/basic/rm-rf.c
+@@ -34,7 +34,7 @@
+                 const struct stat *root_dev) {
+ 
+         struct stat st;
+-        int r;
++        int r, q = 0;
+ 
+         assert(fd >= 0);
+         assert(fname);
+@@ -50,7 +50,6 @@
+ 
+         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 && st.st_dev != root_dev->st_dev)
+@@ -86,23 +85,15 @@
+                  * again for each directory */
+                 q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
+ 
+-                r = unlinkat(fd, fname, AT_REMOVEDIR);
+-                if (r < 0)
+-                        return r;
+-                if (q < 0)
+-                        return q;
+-
+-                return 1;
+-
+-        } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+-                r = unlinkat(fd, fname, 0);
+-                if (r < 0)
+-                        return r;
+-
+-                return 1;
+-        }
++        } else if (flags & REMOVE_ONLY_DIRECTORIES)
++                return 0;
+ 
+-        return 0;
++        r = unlinkat(fd, fname, is_dir ? AT_REMOVEDIR : 0);
++        if (r < 0)
++                return r;
++        if (q < 0)
++                return q;
++        return 1;
+ }
+ 
+ int rm_rf_children(
diff --git a/meta/recipes-core/systemd/systemd/CVE-2021-3997-2.patch b/meta/recipes-core/systemd/systemd/CVE-2021-3997-2.patch
new file mode 100644
index 0000000000..066e10fbbc
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/CVE-2021-3997-2.patch
@@ -0,0 +1,101 @@ 
+Backport of the following upstream commit:
+From bd0127daaaae009ade053718f7d2f297aee4acaf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 23 Nov 2021 16:56:42 +0100
+Subject: [PATCH 2/3] shared/rm_rf: refactor rm_rf() to shorten code a bit
+
+CVE: CVE-2021-3997
+Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
+Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
+---
+ src/basic/rm-rf.c | 53 ++++++++++++++++++++--------------------------
+ 1 file changed, 23 insertions(+), 30 deletions(-)
+
+--- a/src/basic/rm-rf.c
++++ b/src/basic/rm-rf.c
+@@ -159,7 +159,7 @@
+ }
+ 
+ int rm_rf(const char *path, RemoveFlags flags) {
+-        int fd, r;
++        int fd, r, q = 0;
+ 
+         assert(path);
+ 
+@@ -191,49 +191,47 @@
+         }
+ 
+         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 = rmdir(path);
++                        if (q < 0)
++                                q = -errno;
++                }
++        } 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 = unlink(path);
++                if (q < 0)
++                        q = -errno;
+         }
+ 
+-        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) {
diff --git a/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch b/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
new file mode 100644
index 0000000000..c96b8d9a6e
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
@@ -0,0 +1,266 @@ 
+Backport of the following upstream commit:
+From bef8e8e577368697b2e6f85183b1dbc99e0e520f Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
+Date: Tue, 30 Nov 2021 22:29:05 +0100
+Subject: [PATCH 3/3] shared/rm-rf: loop over nested directories instead of
+ instead of recursing
+
+To remove directory structures, we need to remove the innermost items first,
+and then recursively remove higher-level directories. We would recursively
+descend into directories and invoke rm_rf_children and rm_rm_children_inner.
+This is problematic when too many directories are nested.
+
+Instead, let's create a "TODO" queue. In the the queue, for each level we
+hold the DIR* object we were working on, and the name of the directory. This
+allows us to leave a partially-processed directory, and restart the removal
+loop one level down. When done with the inner directory, we use the name to
+unlinkat() it from the parent, and proceed with the removal of other items.
+
+Because the nesting is increased by one level, it is best to view this patch
+with -b/--ignore-space-change.
+
+This fixes CVE-2021-3997, https://bugzilla.redhat.com/show_bug.cgi?id=2024639.
+The issue was reported and patches reviewed by Qualys Team.
+Mauro Matteo Cascella and Riccardo Schirone from Red Hat handled the disclosure.
+
+CVE: CVE-2021-3997
+Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
+Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
+---
+ src/basic/rm-rf.c | 161 +++++++++++++++++++++++++++++++--------------
+ 1 file changed, 113 insertions(+), 48 deletions(-)
+
+--- a/src/basic/rm-rf.c
++++ b/src/basic/rm-rf.c
+@@ -26,12 +26,13 @@
+         return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
+ }
+ 
+-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, q = 0;
+@@ -49,9 +50,7 @@
+         }
+ 
+         if (is_dir) {
+-                _cleanup_close_ int subdir_fd = -1;
+-
+-                /* 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;
+ 
+@@ -63,7 +62,6 @@
+                         return 0;
+ 
+                 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
+-
+                         /* This could be a subvolume, try to remove it */
+ 
+                         r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
+@@ -77,13 +75,16 @@
+                                 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);
++                q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
+ 
+         } else if (flags & REMOVE_ONLY_DIRECTORIES)
+                 return 0;
+@@ -96,64 +97,128 @@
+         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);
++        }
++
++        freep(todos);
++}
++
+ int rm_rf_children(
+                 int fd,
+                 RemoveFlags flags,
+                 const struct stat *root_dev) {
+ 
+-        _cleanup_closedir_ DIR *d = NULL;
+-        struct dirent *de;
++        _cleanup_(free_todo_entries) TodoEntry *todos = NULL;
++        size_t n_todo = 0, allocated = 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;
++                struct dirent *de;
++
++                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(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR);
++                        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 (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. */
++
++                                        _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));
++                                }
++                        }
++                }
+ 
+-        d = fdopendir(fd);
+-        if (!d) {
+-                safe_close(fd);
+-                return -errno;
+-        }
++                FOREACH_DIRENT_ALL(de, d, return -errno) {
++                        int is_dir;
+ 
+-        if (!(flags & REMOVE_PHYSICAL)) {
+-                struct statfs sfs;
++                        if (dot_or_dot_dot(de->d_name))
++                                continue;
+ 
+-                if (fstatfs(dirfd(d), &sfs) < 0)
+-                        return -errno;
+-                }
++                        is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR;
+ 
+-                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));
+-                }
+-        }
++                        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 */
+ 
+-        FOREACH_DIRENT_ALL(de, d, return -errno) {
+-                int is_dir;
++                                if (!GREEDY_REALLOC0(todos, allocated, n_todo + 2))
++                                        return log_oom();
+ 
+-                if (dot_or_dot_dot(de->d_name))
+-                        continue;
++                                 _cleanup_free_ char *newdirname = strdup(de->d_name);
++                                 if (!newdirname)
++                                         return log_oom();
+ 
+-                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;
+-        }
++                                 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;
+ }
+@@ -250,5 +315,5 @@
+         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 --git a/meta/recipes-core/systemd/systemd/rm-rf-optionally-fsync-after-removing-directory-tree.patch b/meta/recipes-core/systemd/systemd/rm-rf-optionally-fsync-after-removing-directory-tree.patch
new file mode 100644
index 0000000000..b860da008c
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/rm-rf-optionally-fsync-after-removing-directory-tree.patch
@@ -0,0 +1,35 @@ 
+Backport of the following upstream commit:
+From bdfe7ada0d4d66e6d6e65f2822acbb1ec230f9c2 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 5 Oct 2021 10:32:56 +0200
+Subject: [PATCH] rm-rf: optionally fsync() after removing directory tree
+
+Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
+Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
+---
+ src/basic/rm-rf.c | 3 +++
+ src/basic/rm-rf.h | 1 +
+ 2 files changed, 4 insertions(+)
+
+--- a/src/basic/rm-rf.c
++++ b/src/basic/rm-rf.c
+@@ -161,6 +161,9 @@
+                         ret = r;
+         }
+ 
++        if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0)
++                ret = -errno;
++
+         return ret;
+ }
+ 
+--- a/src/basic/rm-rf.h
++++ b/src/basic/rm-rf.h
+@@ -11,6 +11,7 @@
+         REMOVE_PHYSICAL         = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
+         REMOVE_SUBVOLUME        = 1 << 3, /* Drop btrfs subvolumes in the tree too */
+         REMOVE_MISSING_OK       = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
++        REMOVE_SYNCFS           = 1 << 7, /* syncfs() the root of the specified directory after removing everything in it */
+ } RemoveFlags;
+ 
+ int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev);
diff --git a/meta/recipes-core/systemd/systemd/rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch b/meta/recipes-core/systemd/systemd/rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch
new file mode 100644
index 0000000000..f80e6433c6
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch
@@ -0,0 +1,318 @@ 
+Backport of the following upstream commit:
+From 96906b22417c65d70933976e0ee920c70c9113a4 Mon Sep 17 00:00:00 2001
+From: Lennart Poettering <lennart@poettering.net>
+Date: Tue, 26 Jan 2021 16:30:06 +0100
+Subject: [PATCH] rm-rf: refactor rm_rf_children(), split out body of directory
+ iteration loop
+
+This splits out rm_rf_children_inner() as body of the loop. We can use
+that to implement rm_rf_child() for deleting one specific entry in a
+directory.
+
+Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
+Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
+---
+ src/basic/rm-rf.c | 223 ++++++++++++++++++++++++++-------------------
+ src/basic/rm-rf.h |   3 +-
+ 2 files changed, 131 insertions(+), 95 deletions(-)
+
+--- a/src/basic/rm-rf.c
++++ b/src/basic/rm-rf.c
+@@ -19,138 +19,153 @@
+ #include "stat-util.h"
+ #include "string-util.h"
+ 
++/* We treat tmpfs/ramfs + cgroupfs as non-physical file sytems. cgroupfs is similar to tmpfs in a way after
++ * all: we can create arbitrary directory hierarchies in it, and hence can also use rm_rf() on it to remove
++ * those again. */
+ static bool is_physical_fs(const struct statfs *sfs) {
+         return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
+ }
+ 
+-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
++static int rm_rf_children_inner(
++                int fd,
++                const char *fname,
++                int is_dir,
++                RemoveFlags flags,
++                const struct stat *root_dev) {
++
++        struct stat st;
++        int r;
++
++        assert(fd >= 0);
++        assert(fname);
++
++        if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
++
++                r = fstatat(fd, fname, &st, AT_SYMLINK_NOFOLLOW);
++                if (r < 0)
++                        return r;
++
++                is_dir = S_ISDIR(st.st_mode);
++        }
++
++        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 && st.st_dev != root_dev->st_dev)
++                        return 0;
++
++                /* Stop at mount points */
++                r = fd_is_mount_point(fd, fname, 0);
++                if (r < 0)
++                        return r;
++                if (r > 0)
++                        return 0;
++
++                if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
++
++                        /* This could be a subvolume, try to remove it */
++
++                        r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
++                        if (r < 0) {
++                                if (!IN_SET(r, -ENOTTY, -EINVAL))
++                                        return r;
++
++                                /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
++                        } else
++                                /* It was a subvolume, done. */
++                                return 1;
++                }
++
++                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(fd, fname, AT_REMOVEDIR);
++                if (r < 0)
++                        return r;
++                if (q < 0)
++                        return q;
++
++                return 1;
++
++        } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
++                r = unlinkat(fd, fname, 0);
++                if (r < 0)
++                        return r;
++
++                return 1;
++        }
++
++        return 0;
++}
++
++int rm_rf_children(
++                int fd,
++                RemoveFlags flags,
++                const struct stat *root_dev) {
++
+         _cleanup_closedir_ DIR *d = NULL;
+         struct dirent *de;
+         int ret = 0, r;
+-        struct statfs sfs;
+ 
+         assert(fd >= 0);
+ 
+         /* 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.. */
++         * fd, in all cases, including on failure. */
++
++        d = fdopendir(fd);
++        if (!d) {
++                safe_close(fd);
++                return -errno;
++        }
+ 
+         if (!(flags & REMOVE_PHYSICAL)) {
++                struct statfs sfs;
+ 
+-                r = fstatfs(fd, &sfs);
+-                if (r < 0) {
+-                        safe_close(fd);
++                if (fstatfs(dirfd(d), &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. */
++                        /* 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);
+-                        log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
+-                                  strna(path));
+-
+-                        safe_close(fd);
+-                        return -EPERM;
++                        return log_error_errno(SYNTHETIC_ERRNO(EPERM),
++                                               "Attempted to remove disk file system under \"%s\", and we can't allow that.",
++                                               strna(path));
+                 }
+         }
+ 
+-        d = fdopendir(fd);
+-        if (!d) {
+-                safe_close(fd);
+-                return errno == ENOENT ? 0 : -errno;
+-        }
+-
+         FOREACH_DIRENT_ALL(de, d, return -errno) {
+-                bool is_dir;
+-                struct stat st;
++                int is_dir;
+ 
+                 if (dot_or_dot_dot(de->d_name))
+                         continue;
+ 
+-                if (de->d_type == DT_UNKNOWN ||
+-                    (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) {
+-                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+-                                if (ret == 0 && errno != ENOENT)
+-                                        ret = -errno;
+-                                continue;
+-                        }
+-
+-                        is_dir = S_ISDIR(st.st_mode);
+-                } else
+-                        is_dir = de->d_type == DT_DIR;
+-
+-                if (is_dir) {
+-                        _cleanup_close_ int subdir_fd = -1;
+-
+-                        /* if root_dev is set, remove subdirectories only if device is same */
+-                        if (root_dev && st.st_dev != root_dev->st_dev)
+-                                continue;
+-
+-                        subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
+-                        if (subdir_fd < 0) {
+-                                if (ret == 0 && errno != ENOENT)
+-                                        ret = -errno;
+-                                continue;
+-                        }
+-
+-                        /* Stop at mount points */
+-                        r = fd_is_mount_point(fd, de->d_name, 0);
+-                        if (r < 0) {
+-                                if (ret == 0 && r != -ENOENT)
+-                                        ret = r;
+-
+-                                continue;
+-                        }
+-                        if (r > 0)
+-                                continue;
+-
+-                        if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
+-
+-                                /* This could be a subvolume, try to remove it */
+-
+-                                r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
+-                                if (r < 0) {
+-                                        if (!IN_SET(r, -ENOTTY, -EINVAL)) {
+-                                                if (ret == 0)
+-                                                        ret = r;
+-
+-                                                continue;
+-                                        }
+-
+-                                        /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
+-                                } else
+-                                        /* It was a subvolume, continue. */
+-                                        continue;
+-                        }
+-
+-                        /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
+-                         * system type again for each directory */
+-                        r = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
+-                        if (r < 0 && ret == 0)
+-                                ret = r;
+-
+-                        if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
+-                                if (ret == 0 && errno != ENOENT)
+-                                        ret = -errno;
+-                        }
+-
+-                } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
+-
+-                        if (unlinkat(fd, de->d_name, 0) < 0) {
+-                                if (ret == 0 && errno != ENOENT)
+-                                        ret = -errno;
+-                        }
+-                }
++                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;
+         }
++
+         return ret;
+ }
+ 
+ int rm_rf(const char *path, RemoveFlags flags) {
+         int fd, r;
+-        struct statfs s;
+ 
+         assert(path);
+ 
+@@ -195,9 +210,10 @@
+                 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.",
+@@ -225,3 +241,22 @@
+ 
+         return r;
+ }
++
++int rm_rf_child(int fd, const char *name, RemoveFlags flags) {
++
++        /* Removes one specific child of the specified directory */
++
++        if (fd < 0)
++                return -EBADF;
++
++        if (!filename_is_valid(name))
++                return -EINVAL;
++
++        if ((flags & (REMOVE_ROOT|REMOVE_MISSING_OK)) != 0) /* Doesn't really make sense here, we are not supposed to remove 'fd' anyway */
++                return -EINVAL;
++
++        if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
++                return -EINVAL;
++
++        return rm_rf_children_inner(fd, name, -1, flags, NULL);
++}
+--- a/src/basic/rm-rf.h
++++ b/src/basic/rm-rf.h
+@@ -13,7 +13,8 @@
+         REMOVE_MISSING_OK       = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
+ } RemoveFlags;
+ 
+-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
++int rm_rf_children(int fd, RemoveFlags flags, const struct stat *root_dev);
++int rm_rf_child(int fd, const char *name, RemoveFlags flags);
+ int rm_rf(const char *path, RemoveFlags flags);
+ 
+ /* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */
diff --git a/meta/recipes-core/systemd/systemd_244.5.bb b/meta/recipes-core/systemd/systemd_244.5.bb
index b6f5a47d63..66446e2a7c 100644
--- a/meta/recipes-core/systemd/systemd_244.5.bb
+++ b/meta/recipes-core/systemd/systemd_244.5.bb
@@ -28,6 +28,11 @@  SRC_URI += "file://touchscreen.rules \
            file://network-merge-link_drop-and-link_detach_from_manager.patch \
            file://network-also-drop-requests-when-link-enters-linger-state.patch \
            file://network-fix-Link-reference-counter-issue.patch \
+           file://rm-rf-refactor-rm-rf-children-split-out-body-of-directory.patch \
+           file://rm-rf-optionally-fsync-after-removing-directory-tree.patch \
+           file://CVE-2021-3997-1.patch \
+           file://CVE-2021-3997-2.patch \
+           file://CVE-2021-3997-3.patch \
            "
 
 # patches needed by musl