!203 fix-CVE-2021-3997

Merge pull request !203 from Mingtai/openEuler-20.03-LTS-SP3
This commit is contained in:
openeuler-ci-bot 2022-01-20 02:57:02 +00:00 committed by Gitee
commit 445bf3d6e1
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
10 changed files with 1319 additions and 1 deletions

View File

@ -0,0 +1,206 @@
From 4d16bbb4de8cbcea2476bd8433be42c9d080e1f6 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
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 <unistd.h>
+
+#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

View File

@ -0,0 +1,113 @@
From 318ea885ca5a8466842b4808ac631473229bd970 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
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

View File

@ -0,0 +1,135 @@
From 3e4d0a7c37021bc5f75fa429991f597428d1b63a Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
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

View File

@ -0,0 +1,326 @@
From 6e1da1342eed3bcd1cd7dfb7f78d26a29d2a8b12 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
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

View File

@ -0,0 +1,45 @@
From 1a1d36c2e421f496b52f5e607c1d5bcfa61df2e5 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
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

View File

@ -0,0 +1,33 @@
From c8de3762ec6de66884be0c5313d3872f869c4ee0 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: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

View File

@ -0,0 +1,73 @@
From c5e7d1c9cf03542896d4fd994bee1d7b05ad6f2c 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 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

View File

@ -0,0 +1,105 @@
From 1ee928ffda0d202ded617d8e25ba92e62306c1cd 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 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

View File

@ -0,0 +1,270 @@
From fd956e3df0c64035a2de244bb39ff9345d7c9423 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 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

View File

@ -16,7 +16,7 @@
Name: systemd
Url: https://www.freedesktop.org/wiki/Software/systemd
Version: 243
Release: 50
Release: 51
License: MIT and LGPLv2+ and GPLv2+
Summary: System and Service Manager
@ -141,6 +141,15 @@ Patch0093: backport-network-add-missing-link-network-checks.patch
Patch0094: backport-core-fix-free-undefined-pointer-when-strdup-failed-i.patch
Patch0095: backport-test-adapt-to-the-new-capsh-format.patch
Patch0096: backport-sd-event-re-check-new-epoll-events-when-a-child-even.patch
Patch0097: backport-0001-CVE-2021-3997-rm-rf-add-new-flag-REMOVE_CHMOD.patch
Patch0098: backport-0002-CVE-2021-3997-btrfs-util-add-helper-that-abstracts-might-be-btrfs-.patch
Patch0099: backport-0003-CVE-2021-3997-rm-rf-fstatat-might-fail-if-containing-dir-has-limit.patch
Patch0100: backport-0004-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch
Patch0101: backport-0005-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch
Patch0102: backport-0006-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch
Patch0103: backport-0007-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch
Patch0104: backport-0008-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch
Patch0105: 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
@ -1529,6 +1538,9 @@ fi
%exclude /usr/share/man/man3/*
%changelog
* Tue Jan 18 2021 yangmingtai <yangmingtai@huawei.com> - 243-51
- CVE:fix CVE-2021-3997
* Fri Dec 3 2021 yangmingtai <yangmingtai@huawei.com> - 243-50
- re-check new epoll events when a child event is queued