fix CVE-2021-3997 and CVE-2021-33910

This commit is contained in:
yangmingtaip 2022-02-15 20:23:34 +08:00
parent d80fce2655
commit b4c5569ed4
8 changed files with 907 additions and 3 deletions

View File

@ -0,0 +1,40 @@
From 2426beacca09d84091759be45b25c88116302184 Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 5 Oct 2021 10:32:56 +0200
Subject: [PATCH 2/6] rm-rf: optionally fsync() after removing directory tree
(cherry picked from commit bdfe7ada0d4d66e6d6e65f2822acbb1ec230f9c2)
---
src/shared/rm-rf.c | 3 +++
src/shared/rm-rf.h | 1 +
2 files changed, 4 insertions(+)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index dffb9cf6ee..5ef7c662dd 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -250,6 +250,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/shared/rm-rf.h b/src/shared/rm-rf.h
index 577a2795e0..24fd9a2aa2 100644
--- a/src/shared/rm-rf.h
+++ b/src/shared/rm-rf.h
@@ -14,6 +14,7 @@ typedef enum RemoveFlags {
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 or access something */
REMOVE_CHMOD_RESTORE = 1 << 6, /* Restore the old mode before returning */
+ REMOVE_SYNCFS = 1 << 7, /* syncfs() the root of the specified directory after removing everything in it */
} RemoveFlags;
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags);
--
2.27.0

View File

@ -0,0 +1,321 @@
From ca4a0e7d41f0b2a1fe2f99dbc3763187c16cf7ab Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 26 Jan 2021 16:30:06 +0100
Subject: [PATCH 1/6] rm-rf: refactor rm_rf_children(), split out body of
directory iteration loop
This splits out rm_rf_children_inner() as body of the loop. We can use
that to implement rm_rf_child() for deleting one specific entry in a
directory.
(cherry picked from commit 1f0fb7d544711248cba34615e43c5a76bc902d74)
---
src/shared/rm-rf.c | 223 ++++++++++++++++++++++++++-------------------
src/shared/rm-rf.h | 3 +-
2 files changed, 131 insertions(+), 95 deletions(-)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 900a7fb5ff..dffb9cf6ee 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -19,6 +19,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);
}
@@ -113,133 +116,145 @@ int fstatat_harder(int dfd,
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);
@@ -284,9 +299,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.",
@@ -314,3 +330,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/shared/rm-rf.h b/src/shared/rm-rf.h
index 40f0894c96..577a2795e0 100644
--- a/src/shared/rm-rf.h
+++ b/src/shared/rm-rf.h
@@ -23,7 +23,8 @@ int fstatat_harder(int dfd,
int fstatat_flags,
RemoveFlags remove_flags);
-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.27.0

View File

@ -0,0 +1,273 @@
From 6a28f8b55904c818b25e4db2e1511faac79fd471 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 6/6] shared/rm-rf: loop over nested directories instead of
instead of recursing
To remove directory structures, we need to remove the innermost items first,
and then recursively remove higher-level directories. We would recursively
descend into directories and invoke rm_rf_children and rm_rm_children_inner.
This is problematic when too many directories are nested.
Instead, let's create a "TODO" queue. In the the queue, for each level we
hold the DIR* object we were working on, and the name of the directory. This
allows us to leave a partially-processed directory, and restart the removal
loop one level down. When done with the inner directory, we use the name to
unlinkat() it from the parent, and proceed with the removal of other items.
Because the nesting is increased by one level, it is best to view this patch
with -b/--ignore-space-change.
This fixes CVE-2021-3997, https://bugzilla.redhat.com/show_bug.cgi?id=2024639.
The issue was reported and patches reviewed by Qualys Team.
Mauro Matteo Cascella and Riccardo Schirone from Red Hat handled the disclosure.
(cherry picked from commit 5b1cf7a9be37e20133c0208005274ce4a5b5c6a1)
(cherry picked from commit 911516e1614e435755814ada5fc6064fa107a105)
---
src/shared/rm-rf.c | 161 +++++++++++++++++++++++++++++++--------------
1 file changed, 113 insertions(+), 48 deletions(-)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 1bd2431d8a..954686ffc9 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -52,7 +52,6 @@ static int patch_dirfd_mode(
}
int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags) {
-
mode_t old_mode;
int r;
@@ -116,12 +115,13 @@ int fstatat_harder(int dfd,
return 0;
}
-static int rm_rf_children_inner(
+static int rm_rf_inner_child(
int fd,
const char *fname,
int is_dir,
RemoveFlags flags,
- const struct stat *root_dev) {
+ const struct stat *root_dev,
+ bool allow_recursion) {
struct stat st;
int r, q = 0;
@@ -141,9 +141,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;
@@ -155,7 +153,6 @@ static int rm_rf_children_inner(
return 0;
if ((flags & REMOVE_SUBVOLUME) && btrfs_might_be_subvol(&st)) {
-
/* This could be a subvolume, try to remove it */
r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
@@ -169,13 +166,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;
@@ -188,63 +188,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;
+ _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 + 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;
}
@@ -337,5 +402,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.27.0

View File

@ -0,0 +1,100 @@
From 811b137d6137cc3e8932599e6ef9254ba43ff5eb 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 5/6] shared/rm_rf: refactor rm_rf() to shorten code a bit
(cherry picked from commit 84ced330020c0bae57bd4628f1f44eec91304e69)
(cherry picked from commit 664529efa9431edc043126013ea54e6c399ae2d3)
---
src/shared/rm-rf.c | 54 +++++++++++++++++++++-------------------------
1 file changed, 24 insertions(+), 30 deletions(-)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 7362954116..1bd2431d8a 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -250,7 +250,7 @@ int rm_rf_children(
}
int rm_rf(const char *path, RemoveFlags flags) {
- int fd, r;
+ int fd, r, q = 0;
assert(path);
@@ -282,49 +282,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.27.0

View File

@ -0,0 +1,68 @@
From 89395b63f04f1acc0db533c32637ea20379f97c0 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 4/6] shared/rm_rf: refactor rm_rf_children_inner() to shorten
code a bit
(cherry picked from commit 3bac86abfa1b1720180840ffb9d06b3d54841c11)
(cherry picked from commit 47741ff9eae6311a03e4d3d837128191826a4a3a)
---
src/shared/rm-rf.c | 27 +++++++++------------------
1 file changed, 9 insertions(+), 18 deletions(-)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 19f37e0f19..7362954116 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -124,7 +124,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);
@@ -142,7 +142,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)
@@ -178,23 +177,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.27.0

View File

@ -0,0 +1,28 @@
From 7563de501246dccf5a9ea229933481aa1e7bd5c9 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 3/6] tmpfiles: 'st' may have been used uninitialized
(cherry picked from commit 160dadc0350c77d612aa9d5569f57d9bc84c3dca)
---
src/shared/rm-rf.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c
index 5ef7c662dd..19f37e0f19 100644
--- a/src/shared/rm-rf.c
+++ b/src/shared/rm-rf.c
@@ -129,7 +129,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.27.0

View File

@ -0,0 +1,66 @@
From 441e0115646d54f080e5c3bb0ba477c892861ab9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
Date: Wed, 23 Jun 2021 11:46:41 +0200
Subject: [PATCH] basic/unit-name: do not use strdupa() on a path
The path may have unbounded length, for example through a fuse mount.
CVE-2021-33910: attacked controlled alloca() leads to crash in systemd and
ultimately a kernel panic. Systemd parses the content of /proc/self/mountinfo
and each mountpoint is passed to mount_setup_unit(), which calls
unit_name_path_escape() underneath. A local attacker who is able to mount a
filesystem with a very long path can crash systemd and the whole system.
https://bugzilla.redhat.com/show_bug.cgi?id=1970887
The resulting string length is bounded by UNIT_NAME_MAX, which is 256. But we
can't easily check the length after simplification before doing the
simplification, which in turns uses a copy of the string we can write to.
So we can't reject paths that are too long before doing the duplication.
Hence the most obvious solution is to switch back to strdup(), as before
7410616cd9dbbec97cf98d75324da5cda2b2f7a2.
https://github.com/systemd/systemd/pull/20256/commits/441e0115646d54f080e5c3bb0ba477c892861ab9
---
src/basic/unit-name.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c
index 532f8fa..024b8a5 100644
--- a/src/basic/unit-name.c
+++ b/src/basic/unit-name.c
@@ -378,12 +378,13 @@ int unit_name_unescape(const char *f, char **ret) {
}
int unit_name_path_escape(const char *f, char **ret) {
- char *p, *s;
+ _cleanup_free_ char *p = NULL;
+ char *s;
assert(f);
assert(ret);
- p = strdupa(f);
+ p = strdup(f);
if (!p)
return -ENOMEM;
@@ -395,13 +396,9 @@ int unit_name_path_escape(const char *f, char **ret) {
if (!path_is_normalized(p))
return -EINVAL;
- /* Truncate trailing slashes */
+ /* Truncate trailing slashes and skip leading slashes */
delete_trailing_chars(p, "/");
-
- /* Truncate leading slashes */
- p = skip_leading_chars(p, "/");
-
- s = unit_name_escape(p);
+ s = unit_name_escape(skip_leading_chars(p, "/"));
}
if (!s)
return -ENOMEM;
--
2.23.0

View File

@ -20,7 +20,7 @@
Name: systemd
Url: https://www.freedesktop.org/wiki/Software/systemd
Version: 249
Release: 4
Release: 5
License: MIT and LGPLv2+ and GPLv2+
Summary: System and Service Manager
@ -69,8 +69,13 @@ Patch6000: backport-core-fix-free-undefined-pointer-when-strdup-failed-i.pa
Patch6001: backport-fix-ConditionDirectoryNotEmpty-when-it-comes-to-a-No.patch
Patch6002: backport-fix-ConditionPathIsReadWrite-when-path-does-not-exis.patch
Patch6003: backport-fix-DirectoryNotEmpty-when-it-comes-to-a-Non-directo.patch
#openEuler
Patch6004: backport-CVE-2021-3997-rm-rf-refactor-rm_rf_children-split-out-body-of-dire.patch
Patch6005: backport-CVE-2021-3997-rm-rf-optionally-fsync-after-removing-directory-tree.patch
Patch6006: backport-CVE-2021-3997-tmpfiles-st-may-have-been-used-uninitialized.patch
Patch6007: backport-CVE-2021-3997-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch
Patch6008: backport-CVE-2021-3997-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch
Patch6009: backport-CVE-2021-3997-shared-rm-rf-loop-over-nested-directories-instead-of.patch
patch6010: backport-fix-CVE-2021-33910.patch
BuildRequires: gcc, gcc-c++
BuildRequires: libcap-devel, libmount-devel, pam-devel, libselinux-devel
@ -1705,6 +1710,9 @@ fi
%{_unitdir}/systemd-userdbd.socket
%changelog
* Tue Feb 15 2021 yangmingtai <yangmingtai@huawei.com> - 249-5
- fix CVE-2021-3997 and CVE-2021-33910
* Tue Feb 8 2021 yangmingtai <yangmingtai@huawei.com> - 249-4
- fix ConditionDirectoryNotEmpty,ConditionPathIsReadWrite and DirectoryNotEmpty