!203 fix-CVE-2021-3997
Merge pull request !203 from Mingtai/openEuler-20.03-LTS-SP3
This commit is contained in:
commit
445bf3d6e1
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
14
systemd.spec
14
systemd.spec
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user