diff --git a/backport-0001-CVE-2021-3997-rm-rf-add-new-flag-REMOVE_CHMOD.patch b/backport-0001-CVE-2021-3997-rm-rf-add-new-flag-REMOVE_CHMOD.patch new file mode 100644 index 0000000000000000000000000000000000000000..2dabe2e04290748842f63067f7c2287a827c06d6 --- /dev/null +++ b/backport-0001-CVE-2021-3997-rm-rf-add-new-flag-REMOVE_CHMOD.patch @@ -0,0 +1,206 @@ +From 4d16bbb4de8cbcea2476bd8433be42c9d080e1f6 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 23 Jul 2020 15:24:54 +0200 +Subject: [PATCH 1/9] rm-rf: add new flag REMOVE_CHMOD + +Conflict:NA +Reference:https://github.com/systemd/systemd/commit/2899fb024f066f1cb14989fb470e188de7d6dc88 + +When removing a directory tree as unprivileged user we might encounter +files owned by us but not deletable since the containing directory might +have the "r" bit missing in its access mode. Let's try to deal with +this: optionally if we get EACCES try to set the bit and see if it works +then. +--- + src/basic/rm-rf.c | 54 ++++++++++++++++++++++++++----- + src/basic/rm-rf.h | 1 + + src/test/meson.build | 4 +++ + src/test/test-rm-rf.c | 74 +++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 125 insertions(+), 8 deletions(-) + create mode 100644 src/test/test-rm-rf.c + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 796eb93..03b41f3 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -25,6 +25,46 @@ static bool is_physical_fs(const struct statfs *sfs) { + return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs); + } + ++static int unlinkat_harder( ++ int dfd, ++ const char *filename, ++ int unlink_flags, ++ RemoveFlags remove_flags) { ++ ++ struct stat st; ++ int r; ++ ++ /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the ++ * directory. This is useful if we run unprivileged and have some files where the w bit is ++ * missing. */ ++ ++ if (unlinkat(dfd, filename, unlink_flags) >= 0) ++ return 0; ++ if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) ++ return -errno; ++ ++ if (fstat(dfd, &st) < 0) ++ return -errno; ++ if (!S_ISDIR(st.st_mode)) ++ return -ENOTDIR; ++ if ((st.st_mode & 0700) == 0700) /* Already set? */ ++ return -EACCES; /* original error */ ++ if (st.st_uid != geteuid()) /* this only works if the UID matches ours */ ++ return -EACCES; ++ ++ if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0) ++ return -errno; ++ ++ if (unlinkat(dfd, filename, unlink_flags) < 0) { ++ r = -errno; ++ /* Try to restore the original access mode if this didn't work */ ++ (void) fchmod(dfd, st.st_mode & 07777); ++ return r; ++ } ++ ++ return 0; ++} ++ + int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; +@@ -134,17 +174,15 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *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; +- } ++ r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags); ++ if (r < 0 && r != -ENOENT && ret == 0) ++ ret = r; + + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + +- if (unlinkat(fd, de->d_name, 0) < 0) { +- if (ret == 0 && errno != ENOENT) +- ret = -errno; +- } ++ r = unlinkat_harder(fd, de->d_name, 0, flags); ++ if (r < 0 && r != -ENOENT && ret == 0) ++ ret = r; + } + } + return ret; +diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h +index 40cbff2..0edf01e 100644 +--- a/src/basic/rm-rf.h ++++ b/src/basic/rm-rf.h +@@ -11,6 +11,7 @@ typedef enum RemoveFlags { + 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_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete something */ + } RemoveFlags; + + int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +diff --git a/src/test/meson.build b/src/test/meson.build +index 3fcfa9f..255b00b 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -631,6 +631,10 @@ tests += [ + [], + []], + ++ [['src/test/test-rm-rf.c'], ++ [], ++ []], ++ + [['src/test/test-chase-symlinks.c'], + [], + [], +diff --git a/src/test/test-rm-rf.c b/src/test/test-rm-rf.c +new file mode 100644 +index 0000000..d6e426c +--- /dev/null ++++ b/src/test/test-rm-rf.c +@@ -0,0 +1,74 @@ ++/* SPDX-License-Identifier: LGPL-2.1+ */ ++ ++#include ++ ++#include "alloc-util.h" ++#include "process-util.h" ++#include "rm-rf.h" ++#include "string-util.h" ++#include "tests.h" ++#include "tmpfile-util.h" ++ ++static void test_rm_rf_chmod_inner(void) { ++ _cleanup_free_ char *d = NULL; ++ const char *x, *y; ++ ++ assert_se(getuid() != 0); ++ ++ assert_se(mkdtemp_malloc(NULL, &d) >= 0); ++ ++ x = strjoina(d, "/d"); ++ assert_se(mkdir(x, 0700) >= 0); ++ y = strjoina(x, "/f"); ++ assert_se(mknod(y, S_IFREG | 0600, 0) >= 0); ++ ++ assert_se(chmod(y, 0400) >= 0); ++ assert_se(chmod(x, 0500) >= 0); ++ assert_se(chmod(d, 0500) >= 0); ++ ++ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT) == -EACCES); ++ ++ assert_se(access(d, F_OK) >= 0); ++ assert_se(access(x, F_OK) >= 0); ++ assert_se(access(y, F_OK) >= 0); ++ ++ assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT|REMOVE_CHMOD) >= 0); ++ ++ errno = 0; ++ assert_se(access(d, F_OK) < 0 && errno == ENOENT); ++} ++ ++static void test_rm_rf_chmod(void) { ++ int r; ++ ++ log_info("/* %s */", __func__); ++ ++ if (getuid() == 0) { ++ /* This test only works unpriv (as only then the access mask for the owning user matters), ++ * hence drop privs here */ ++ ++ r = safe_fork("(setresuid)", FORK_DEATHSIG|FORK_WAIT, NULL); ++ assert_se(r >= 0); ++ ++ if (r == 0) { ++ /* child */ ++ ++ assert_se(setresuid(1, 1, 1) >= 0); ++ ++ test_rm_rf_chmod_inner(); ++ _exit(EXIT_SUCCESS); ++ } ++ ++ return; ++ } ++ ++ test_rm_rf_chmod_inner(); ++} ++ ++int main(int argc, char **argv) { ++ test_setup_logging(LOG_DEBUG); ++ ++ test_rm_rf_chmod(); ++ ++ return 0; ++} +-- +2.23.0 + diff --git a/backport-0002-CVE-2021-3997-btrfs-util-add-helper-that-abstracts-might-be-btrfs-.patch b/backport-0002-CVE-2021-3997-btrfs-util-add-helper-that-abstracts-might-be-btrfs-.patch new file mode 100644 index 0000000000000000000000000000000000000000..d8c87acbc24b068a0b273e39efbc2c4a4d1daa13 --- /dev/null +++ b/backport-0002-CVE-2021-3997-btrfs-util-add-helper-that-abstracts-might-be-btrfs-.patch @@ -0,0 +1,113 @@ +From 318ea885ca5a8466842b4808ac631473229bd970 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 26 Feb 2021 17:39:55 +0100 +Subject: [PATCH 2/9] btrfs-util: add helper that abstracts "might be btrfs + subvol?" check + +Conflict:adapt context +Reference:https://github.com/systemd/systemd/commit/674b04ff1b6deab17f5d36c036c0275ba94e1ebc + +Let#s not hardcode inode nr 256 everywhere, but abstract this check +slightly. + +(cherry picked from commit 674b04ff1b6deab17f5d36c036c0275ba94e1ebc) +--- + src/basic/btrfs-util.c | 6 +++--- + src/basic/btrfs-util.h | 10 ++++++++++ + src/basic/rm-rf.c | 2 +- + src/import/export-tar.c | 2 +- + src/shared/machine-image.c | 3 +-- + 5 files changed, 16 insertions(+), 7 deletions(-) + +diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c +index 540a199..e55378e 100644 +--- a/src/basic/btrfs-util.c ++++ b/src/basic/btrfs-util.c +@@ -94,7 +94,7 @@ int btrfs_is_subvol_fd(int fd) { + if (fstat(fd, &st) < 0) + return -errno; + +- if (!S_ISDIR(st.st_mode) || st.st_ino != 256) ++ if (!btrfs_might_be_subvol(&st)) + return 0; + + return btrfs_is_filesystem(fd); +@@ -172,7 +172,7 @@ int btrfs_subvol_set_read_only_fd(int fd, bool b) { + if (fstat(fd, &st) < 0) + return -errno; + +- if (!S_ISDIR(st.st_mode) || st.st_ino != 256) ++ if (!btrfs_might_be_subvol(&st)) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) +@@ -211,7 +211,7 @@ int btrfs_subvol_get_read_only_fd(int fd) { + if (fstat(fd, &st) < 0) + return -errno; + +- if (!S_ISDIR(st.st_mode) || st.st_ino != 256) ++ if (!btrfs_might_be_subvol(&st)) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) +diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h +index b15667b..0acd125 100644 +--- a/src/basic/btrfs-util.h ++++ b/src/basic/btrfs-util.h +@@ -119,3 +119,13 @@ int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); + + int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); + int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); ++ ++static inline bool btrfs_might_be_subvol(const struct stat *st) { ++ if (!st) ++ return false; ++ ++ /* Returns true if this 'struct stat' looks like it could refer to a btrfs subvolume. To make a final ++ * decision, needs to be combined with an fstatfs() check to see if this is actually btrfs. */ ++ ++ return S_ISDIR(st->st_mode) && st->st_ino == 256; ++} +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 03b41f3..26b943e 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -149,7 +149,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + if (r > 0) + continue; + +- if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { ++ if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { + + /* This could be a subvolume, try to remove it */ + +diff --git a/src/import/export-tar.c b/src/import/export-tar.c +index ed54676..aa5c717 100644 +--- a/src/import/export-tar.c ++++ b/src/import/export-tar.c +@@ -284,7 +284,7 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType + + e->quota_referenced = (uint64_t) -1; + +- if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */ ++ if (btrfs_might_be_subvol(&e->st)) { + BtrfsQuotaInfo q; + + r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q); +diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c +index 7007374..07bb80c 100644 +--- a/src/shared/machine-image.c ++++ b/src/shared/machine-image.c +@@ -249,8 +249,7 @@ static int image_make( + if (fd < 0) + return -errno; + +- /* btrfs subvolumes have inode 256 */ +- if (st->st_ino == 256) { ++ if (btrfs_might_be_subvol(st)) { + + r = btrfs_is_filesystem(fd); + if (r < 0) +-- +2.23.0 + diff --git a/backport-0003-CVE-2021-3997-rm-rf-fstatat-might-fail-if-containing-dir-has-limit.patch b/backport-0003-CVE-2021-3997-rm-rf-fstatat-might-fail-if-containing-dir-has-limit.patch new file mode 100644 index 0000000000000000000000000000000000000000..def378241c7ec1ffff879c07f95297401958eeef --- /dev/null +++ b/backport-0003-CVE-2021-3997-rm-rf-fstatat-might-fail-if-containing-dir-has-limit.patch @@ -0,0 +1,135 @@ +From 3e4d0a7c37021bc5f75fa429991f597428d1b63a Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 26 Jan 2021 16:47:07 +0100 +Subject: [PATCH 3/9] rm-rf: fstatat() might fail if containing dir has limited + access mode, patch that too + +Conflict:according 1d6cc5d0e5658848ac8cce5da22626d17f15b1ec, use FLAGS_SET +instead of "==" +Reference:https://github.com/systemd/systemd/commit/1b55621dabf741dd963f59ac706ea62cd6e3e95c + +(cherry picked from commit 1b55621dabf741dd963f59ac706ea62cd6e3e95c) +--- + src/basic/rm-rf.c | 82 ++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 66 insertions(+), 16 deletions(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 26b943e..602266d 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -25,13 +25,38 @@ static bool is_physical_fs(const struct statfs *sfs) { + return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs); + } + ++static int patch_dirfd_mode( ++ int dfd, ++ mode_t *ret_old_mode) { ++ ++ struct stat st; ++ ++ assert(dfd >= 0); ++ assert(ret_old_mode); ++ ++ if (fstat(dfd, &st) < 0) ++ return -errno; ++ if (!S_ISDIR(st.st_mode)) ++ return -ENOTDIR; ++ if (FLAGS_SET(st.st_mode, 0700)) /* Already set? */ ++ return -EACCES; /* original error */ ++ if (st.st_uid != geteuid()) /* this only works if the UID matches ours */ ++ return -EACCES; ++ ++ if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0) ++ return -errno; ++ ++ *ret_old_mode = st.st_mode; ++ return 0; ++} ++ + static int unlinkat_harder( + int dfd, + const char *filename, + int unlink_flags, + RemoveFlags remove_flags) { + +- struct stat st; ++ mode_t old_mode; + int r; + + /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the +@@ -43,22 +68,46 @@ static int unlinkat_harder( + if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) + return -errno; + +- if (fstat(dfd, &st) < 0) +- return -errno; +- if (!S_ISDIR(st.st_mode)) +- return -ENOTDIR; +- if ((st.st_mode & 0700) == 0700) /* Already set? */ +- return -EACCES; /* original error */ +- if (st.st_uid != geteuid()) /* this only works if the UID matches ours */ +- return -EACCES; +- +- if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0) +- return -errno; ++ r = patch_dirfd_mode(dfd, &old_mode); ++ if (r < 0) ++ return r; + + if (unlinkat(dfd, filename, unlink_flags) < 0) { + r = -errno; + /* Try to restore the original access mode if this didn't work */ +- (void) fchmod(dfd, st.st_mode & 07777); ++ (void) fchmod(dfd, old_mode); ++ return r; ++ } ++ ++ /* If this worked, we won't reset the old mode, since we'll need it for other entries too, and we ++ * should destroy the whole thing */ ++ return 0; ++} ++ ++static int fstatat_harder( ++ int dfd, ++ const char *filename, ++ struct stat *ret, ++ int fstatat_flags, ++ RemoveFlags remove_flags) { ++ ++ mode_t old_mode; ++ int r; ++ ++ /* Like unlink_harder() but does the same for fstatat() */ ++ ++ if (fstatat(dfd, filename, ret, fstatat_flags) >= 0) ++ return 0; ++ if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD)) ++ return -errno; ++ ++ r = patch_dirfd_mode(dfd, &old_mode); ++ if (r < 0) ++ return r; ++ ++ if (fstatat(dfd, filename, ret, fstatat_flags) < 0) { ++ r = -errno; ++ (void) fchmod(dfd, old_mode); + return r; + } + +@@ -114,9 +163,10 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + + 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; ++ r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags); ++ if (r < 0) { ++ if (ret == 0 && r != -ENOENT) ++ ret = r; + continue; + } + +-- +2.23.0 + diff --git a/backport-0004-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch b/backport-0004-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch new file mode 100644 index 0000000000000000000000000000000000000000..1ba94f4f417c629083aeadcac2b06429b2dcd2a0 --- /dev/null +++ b/backport-0004-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch @@ -0,0 +1,326 @@ +From 6e1da1342eed3bcd1cd7dfb7f78d26a29d2a8b12 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 26 Jan 2021 16:30:06 +0100 +Subject: [PATCH 4/9] rm-rf: refactor rm_rf_children(), split out body of + directory iteration loop + +Conflict:modify files in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/1f0fb7d544711248cba34615e43c5a76bc902d74 + +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. + +(cherry picked from commit 1f0fb7d544711248cba34615e43c5a76bc902d74) +(cherry picked from commit ca4a0e7d41f0b2a1fe2f99dbc3763187c16cf7ab) +(cherry picked from commit 85ccac3393e78d4bf2776ffb8c3a1d8a2a909a2a) +--- + src/basic/rm-rf.c | 223 +++++++++++++++++++++++++++------------------- + src/basic/rm-rf.h | 3 +- + 2 files changed, 131 insertions(+), 95 deletions(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 602266d..8e3eb59 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -21,6 +21,9 @@ + #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); + } +@@ -114,133 +117,145 @@ static int fstatat_harder( + return 0; + } + +-int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { +- _cleanup_closedir_ DIR *d = NULL; +- struct dirent *de; +- int ret = 0, r; +- struct statfs sfs; ++static int rm_rf_children_inner( ++ int fd, ++ const char *fname, ++ int is_dir, ++ RemoveFlags flags, ++ const struct stat *root_dev) { + +- assert(fd >= 0); ++ struct stat st; ++ int r; + +- /* 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.. */ ++ assert(fd >= 0); ++ assert(fname); + +- if (!(flags & REMOVE_PHYSICAL)) { ++ if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) { + +- r = fstatfs(fd, &sfs); +- if (r < 0) { +- safe_close(fd); +- return -errno; +- } ++ r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags); ++ if (r < 0) ++ return r; + +- 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; ++ is_dir = S_ISDIR(st.st_mode); ++ } + +- (void) fd_get_path(fd, &path); +- log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.", +- strna(path)); ++ if (is_dir) { ++ _cleanup_close_ int subdir_fd = -1; ++ int q; + +- safe_close(fd); +- return -EPERM; +- } +- } ++ /* if root_dev is set, remove subdirectories only if device is same */ ++ if (root_dev && st.st_dev != root_dev->st_dev) ++ return 0; + +- d = fdopendir(fd); +- if (!d) { +- safe_close(fd); +- return errno == ENOENT ? 0 : -errno; +- } ++ /* Stop at mount points */ ++ r = fd_is_mount_point(fd, fname, 0); ++ if (r < 0) ++ return r; ++ if (r > 0) ++ return 0; + +- FOREACH_DIRENT_ALL(de, d, return -errno) { +- bool is_dir; +- struct stat st; ++ if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { + +- if (dot_or_dot_dot(de->d_name)) +- continue; ++ /* This could be a subvolume, try to remove it */ + +- if (de->d_type == DT_UNKNOWN || +- (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { +- r = fstatat_harder(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW, flags); ++ r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r < 0) { +- if (ret == 0 && r != -ENOENT) +- ret = r; +- continue; +- } ++ if (!IN_SET(r, -ENOTTY, -EINVAL)) ++ return r; + +- is_dir = S_ISDIR(st.st_mode); +- } else +- is_dir = de->d_type == DT_DIR; ++ /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */ ++ } else ++ /* It was a subvolume, done. */ ++ return 1; ++ } + +- if (is_dir) { +- _cleanup_close_ int subdir_fd = -1; ++ subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); ++ if (subdir_fd < 0) ++ return -errno; + +- /* if root_dev is set, remove subdirectories only if device is same */ +- if (root_dev && st.st_dev != root_dev->st_dev) +- continue; ++ /* 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); + +- 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; +- } ++ r = unlinkat_harder(fd, fname, AT_REMOVEDIR, flags); ++ if (r < 0) ++ return r; ++ if (q < 0) ++ return q; + +- /* Stop at mount points */ +- r = fd_is_mount_point(fd, de->d_name, 0); +- if (r < 0) { +- if (ret == 0 && r != -ENOENT) +- ret = r; ++ return 1; + +- continue; +- } +- if (r > 0) +- continue; ++ } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { ++ r = unlinkat_harder(fd, fname, 0, flags); ++ if (r < 0) ++ return r; + +- if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) { ++ return 1; ++ } + +- /* This could be a subvolume, try to remove it */ ++ return 0; ++} + +- 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; ++int rm_rf_children( ++ int fd, ++ RemoveFlags flags, ++ const struct stat *root_dev) { + +- continue; +- } ++ _cleanup_closedir_ DIR *d = NULL; ++ struct dirent *de; ++ int ret = 0, r; + +- /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */ +- } else +- /* It was a subvolume, continue. */ +- continue; +- } ++ 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. */ ++ ++ d = fdopendir(fd); ++ if (!d) { ++ safe_close(fd); ++ return -errno; ++ } + +- /* 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 (!(flags & REMOVE_PHYSICAL)) { ++ struct statfs sfs; + +- r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags); +- if (r < 0 && r != -ENOENT && ret == 0) +- ret = r; ++ 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. */ + +- } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { ++ _cleanup_free_ char *path = NULL; + +- r = unlinkat_harder(fd, de->d_name, 0, flags); +- if (r < 0 && r != -ENOENT && ret == 0) +- ret = r; ++ (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; ++ ++ 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; ++ } ++ + return ret; + } + + int rm_rf(const char *path, RemoveFlags flags) { + int fd, r; +- struct statfs s; + + assert(path); + +@@ -285,9 +300,10 @@ int rm_rf(const char *path, RemoveFlags flags) { + 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.", +@@ -315,3 +331,22 @@ int rm_rf(const char *path, RemoveFlags flags) { + + 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); ++} +diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h +index 0edf01e..15f7a87 100644 +--- a/src/basic/rm-rf.h ++++ b/src/basic/rm-rf.h +@@ -14,7 +14,8 @@ typedef enum RemoveFlags { + REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete something */ + } 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 */ +-- +2.23.0 + diff --git a/backport-0005-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch b/backport-0005-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch new file mode 100644 index 0000000000000000000000000000000000000000..a9646a911c92cfb5f4626b13a86e46b614bd4787 --- /dev/null +++ b/backport-0005-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch @@ -0,0 +1,45 @@ +From 1a1d36c2e421f496b52f5e607c1d5bcfa61df2e5 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 5 Oct 2021 10:32:56 +0200 +Subject: [PATCH 5/9] rm-rf: optionally fsync() after removing directory tree + +Conflict:modify files in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/bdfe7ada0d4d66e6d6e65f2822acbb1ec230f9c2 + +(cherry picked from commit bdfe7ada0d4d66e6d6e65f2822acbb1ec230f9c2) +(cherry picked from commit 2426beacca09d84091759be45b25c88116302184) +(cherry picked from commit 0e180f8e9c25c707b0465ad1b9447a4360f785f1) +--- + src/basic/rm-rf.c | 3 +++ + src/basic/rm-rf.h | 1 + + 2 files changed, 4 insertions(+) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 8e3eb59..4e46654 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -251,6 +251,9 @@ int rm_rf_children( + ret = r; + } + ++ if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0) ++ ret = -errno; ++ + return ret; + } + +diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h +index 15f7a87..1ecd552 100644 +--- a/src/basic/rm-rf.h ++++ b/src/basic/rm-rf.h +@@ -12,6 +12,7 @@ typedef enum RemoveFlags { + 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_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete something */ ++ REMOVE_SYNCFS = 1 << 6, /* 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); +-- +2.23.0 + diff --git a/backport-0006-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch b/backport-0006-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch new file mode 100644 index 0000000000000000000000000000000000000000..dba23ad332bb53808e7a9dc6bd02b59b64044406 --- /dev/null +++ b/backport-0006-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch @@ -0,0 +1,33 @@ +From c8de3762ec6de66884be0c5313d3872f869c4ee0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 23 Nov 2021 15:05:58 +0100 +Subject: [PATCH 6/9] tmpfiles: 'st' may have been used uninitialized + +Conflict:modify file in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/160dadc0350c77d612aa9d5569f57d9bc84c3dca + +(cherry picked from commit 160dadc0350c77d612aa9d5569f57d9bc84c3dca) +(cherry picked from commit 7563de501246dccf5a9ea229933481aa1e7bd5c9) +(cherry picked from commit f54b97b1d05052bfee824ecc03ae9f07f6c37be8) +--- + src/basic/rm-rf.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 4e46654..ad1f286 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -130,7 +130,9 @@ static int rm_rf_children_inner( + assert(fd >= 0); + assert(fname); + +- if (is_dir < 0 || (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) { ++ if (is_dir < 0 || ++ root_dev || ++ (is_dir > 0 && (root_dev || (flags & REMOVE_SUBVOLUME)))) { + + r = fstatat_harder(fd, fname, &st, AT_SYMLINK_NOFOLLOW, flags); + if (r < 0) +-- +2.23.0 + diff --git a/backport-0007-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch b/backport-0007-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch new file mode 100644 index 0000000000000000000000000000000000000000..dfdc7e010dbd69dbdb91691171ff74cae342c2c6 --- /dev/null +++ b/backport-0007-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch @@ -0,0 +1,73 @@ +From c5e7d1c9cf03542896d4fd994bee1d7b05ad6f2c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 23 Nov 2021 15:55:45 +0100 +Subject: [PATCH 7/9] shared/rm_rf: refactor rm_rf_children_inner() to shorten + code a bit + +Conflict:modify file in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/3bac86abfa1b1720180840ffb9d06b3d54841c11 + +(cherry picked from commit 3bac86abfa1b1720180840ffb9d06b3d54841c11) +(cherry picked from commit 47741ff9eae6311a03e4d3d837128191826a4a3a) +(cherry picked from commit 89395b63f04f1acc0db533c32637ea20379f97c0) +(cherry picked from commit 3976f244990aa1210ebe018647f32ab060e1c3d3) +--- + src/basic/rm-rf.c | 27 +++++++++------------------ + 1 file changed, 9 insertions(+), 18 deletions(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index ad1f286..c1c7bd1 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -125,7 +125,7 @@ static int rm_rf_children_inner( + const struct stat *root_dev) { + + struct stat st; +- int r; ++ int r, q = 0; + + assert(fd >= 0); + assert(fname); +@@ -143,7 +143,6 @@ 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 && st.st_dev != root_dev->st_dev) +@@ -179,23 +178,15 @@ static int rm_rf_children_inner( + * 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; +- +- return 1; +- +- } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { +- r = unlinkat_harder(fd, fname, 0, flags); +- if (r < 0) +- return r; +- +- return 1; +- } ++ } else if (flags & REMOVE_ONLY_DIRECTORIES) ++ return 0; + +- return 0; ++ r = unlinkat_harder(fd, fname, is_dir ? AT_REMOVEDIR : 0, flags); ++ if (r < 0) ++ return r; ++ if (q < 0) ++ return q; ++ return 1; + } + + int rm_rf_children( +-- +2.23.0 + diff --git a/backport-0008-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch b/backport-0008-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch new file mode 100644 index 0000000000000000000000000000000000000000..12c3c1097f7e2a45ed5ca16a607771ace2837c6b --- /dev/null +++ b/backport-0008-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch @@ -0,0 +1,105 @@ +From 1ee928ffda0d202ded617d8e25ba92e62306c1cd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 23 Nov 2021 16:56:42 +0100 +Subject: [PATCH 8/9] shared/rm_rf: refactor rm_rf() to shorten code a bit + +Conflict:modify file in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/84ced330020c0bae57bd4628f1f44eec91304e69 + +(cherry picked from commit 84ced330020c0bae57bd4628f1f44eec91304e69) +(cherry picked from commit 664529efa9431edc043126013ea54e6c399ae2d3) +(cherry picked from commit 811b137d6137cc3e8932599e6ef9254ba43ff5eb) +(cherry picked from commit 39a53d4f1445a8981efd0adcc1734dfad46647c5) +--- + src/basic/rm-rf.c | 54 +++++++++++++++++++++-------------------------- + 1 file changed, 24 insertions(+), 30 deletions(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index c1c7bd1..3b1a920 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -251,7 +251,7 @@ int rm_rf_children( + } + + int rm_rf(const char *path, RemoveFlags flags) { +- int fd, r; ++ int fd, r, q = 0; + + assert(path); + +@@ -283,49 +283,43 @@ int rm_rf(const char *path, RemoveFlags flags) { + } + + 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) && rmdir(path) < 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; ++ if (unlink(path) < 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) { +-- +2.23.0 + diff --git a/backport-0009-CVE-2021-3997-shared-rm-rf-loop-over-nested-directories-instead-of.patch b/backport-0009-CVE-2021-3997-shared-rm-rf-loop-over-nested-directories-instead-of.patch new file mode 100644 index 0000000000000000000000000000000000000000..f42af3933b7eea1dbb18f06bacd215f2a0f05ec3 --- /dev/null +++ b/backport-0009-CVE-2021-3997-shared-rm-rf-loop-over-nested-directories-instead-of.patch @@ -0,0 +1,270 @@ +From fd956e3df0c64035a2de244bb39ff9345d7c9423 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 30 Nov 2021 22:29:05 +0100 +Subject: [PATCH 9/9] shared/rm-rf: loop over nested directories instead of + instead of recursing + +Conflict:modify file in basic instead of shared +Reference:https://github.com/systemd/systemd/commit/5b1cf7a9be37e20133c0208005274ce4a5b5c6a1 + +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. + +(cherry picked from commit 5b1cf7a9be37e20133c0208005274ce4a5b5c6a1) +(cherry picked from commit 911516e1614e435755814ada5fc6064fa107a105) +(cherry picked from commit 6a28f8b55904c818b25e4db2e1511faac79fd471) +(cherry picked from commit c752f27b7647c99b4a17477c99d84fd8c950ddf0) +--- + src/basic/rm-rf.c | 160 ++++++++++++++++++++++++++++++++-------------- + 1 file changed, 113 insertions(+), 47 deletions(-) + +diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c +index 3b1a920..b7ecb27 100644 +--- a/src/basic/rm-rf.c ++++ b/src/basic/rm-rf.c +@@ -117,12 +117,13 @@ static int fstatat_harder( + 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, q = 0; +@@ -142,9 +143,7 @@ static int rm_rf_children_inner( + } + + 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; + +@@ -156,7 +155,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,13 +168,16 @@ 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); ++ q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); + + } else if (flags & REMOVE_ONLY_DIRECTORIES) + return 0; +@@ -189,63 +190,128 @@ static int rm_rf_children_inner( + 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, n_todo_alloc = 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. */ ++ ++ for (;;) { /* This loop corresponds to the directory nesting level. */ ++ _cleanup_closedir_ DIR *d = NULL; ++ ++ 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 (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)); ++ } ++ } ++ } + +- /* 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. */ ++ struct dirent *de; ++ FOREACH_DIRENT_ALL(de, d, return -errno) { ++ int is_dir; + +- d = fdopendir(fd); +- if (!d) { +- safe_close(fd); +- return -errno; +- } ++ if (dot_or_dot_dot(de->d_name)) ++ continue; + +- if (!(flags & REMOVE_PHYSICAL)) { +- struct statfs sfs; ++ is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR; + +- if (fstatfs(dirfd(d), &sfs) < 0) +- return -errno; ++ 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 (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 (!GREEDY_REALLOC0(todos, n_todo_alloc, n_todo + 2)) ++ return log_oom(); + +- _cleanup_free_ char *path = NULL; ++ _cleanup_free_ char *newdirname = strdup(de->d_name); ++ if (!newdirname) ++ return log_oom(); + +- (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)); +- } +- } ++ 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); + +- FOREACH_DIRENT_ALL(de, d, return -errno) { +- int is_dir; ++ goto next_fd; + +- if (dot_or_dot_dot(de->d_name)) +- continue; ++ } else if (errno != -ENOENT && ret == 0) ++ ret = -errno; + +- is_dir = +- de->d_type == DT_UNKNOWN ? -1 : +- de->d_type == DT_DIR; ++ } else if (r < 0 && r != -ENOENT && ret == 0) ++ ret = r; ++ } + +- r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev); +- if (r < 0 && r != -ENOENT && ret == 0) +- ret = r; +- } ++ if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0) ++ ret = -errno; + +- if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0) +- ret = -errno; ++ if (n_todo == 0) ++ break; ++ } + + return ret; + } +@@ -338,5 +404,5 @@ int rm_rf_child(int fd, const char *name, RemoveFlags flags) { + 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); + } +-- +2.23.0 + diff --git a/systemd.spec b/systemd.spec index 8abb12150742363043766d80445ab17fdca48c56..b24b20b3ace911d77763e39a34b1990f40446bd0 100644 --- a/systemd.spec +++ b/systemd.spec @@ -16,7 +16,7 @@ Name: systemd Url: https://www.freedesktop.org/wiki/Software/systemd Version: 243 -Release: 48 +Release: 49 License: MIT and LGPLv2+ and GPLv2+ Summary: System and Service Manager @@ -130,6 +130,15 @@ Patch0080: backport-network-add-missing-link-network-checks.patch Patch0081: backport-sd-event-re-check-new-epoll-events-when-a-child-even.patch Patch0082: backport-core-fix-free-undefined-pointer-when-strdup-failed-i.patch Patch0083: backport-test-adapt-to-the-new-capsh-format.patch +Patch0084: backport-0001-CVE-2021-3997-rm-rf-add-new-flag-REMOVE_CHMOD.patch +Patch0085: backport-0002-CVE-2021-3997-btrfs-util-add-helper-that-abstracts-might-be-btrfs-.patch +Patch0086: backport-0003-CVE-2021-3997-rm-rf-fstatat-might-fail-if-containing-dir-has-limit.patch +Patch0087: backport-0004-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch +Patch0088: backport-0005-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch +Patch0089: backport-0006-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch +Patch0090: backport-0007-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch +Patch0091: backport-0008-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch +Patch0092: backport-0009-CVE-2021-3997-shared-rm-rf-loop-over-nested-directories-instead-of.patch #openEuler Patch9002: 1509-fix-journal-file-descriptors-leak-problems.patch @@ -1517,6 +1526,9 @@ fi %exclude /usr/share/man/man3/* %changelog +* Tue Jan 18 2021 yangmingtai - 243-49 +- CVE:fix CVE-2021-3997 + * Wed Oct 27 2021 ExtinctFire - 243-48 - Type:bugfix - ID:NA