diff --git a/5000-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch b/5000-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch new file mode 100644 index 00000000..bb00830b --- /dev/null +++ b/5000-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch @@ -0,0 +1,66 @@ +From 5bc4f2e271c4907af1d3208c5bb33ce795326abc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 23 Nov 2021 15:55:45 +0100 +Subject: [PATCH 5000/5002] shared/rm_rf: refactor rm_rf_children_inner() to + shorten code a bit + +--- + 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.31.1 + diff --git a/5001-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch b/5001-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch new file mode 100644 index 00000000..347b5e55 --- /dev/null +++ b/5001-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch @@ -0,0 +1,98 @@ +From 8f608df0305355c9b2ddd7c75926a6bd6247e635 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 23 Nov 2021 16:56:42 +0100 +Subject: [PATCH 5001/5002] shared/rm_rf: refactor rm_rf() to shorten code a + bit + +--- + src/shared/rm-rf.c | 53 ++++++++++++++++++++-------------------------- + 1 file changed, 23 insertions(+), 30 deletions(-) + +diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c +index 7362954116..c7d3b8b7ad 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,42 @@ 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)) ++ q = RET_NERRNO(rmdir(path)); ++ } else { + if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT) + return 0; + + if (!IN_SET(errno, ENOTDIR, ELOOP)) + return -errno; + +- if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES)) ++ if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES) || !FLAGS_SET(flags, REMOVE_ROOT)) + return 0; + +- if (FLAGS_SET(flags, REMOVE_ROOT)) { +- +- if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { +- struct statfs s; +- +- if (statfs(path, &s) < 0) +- return -errno; +- if (is_physical_fs(&s)) +- return log_error_errno(SYNTHETIC_ERRNO(EPERM), +- "Attempted to remove files from a disk file system under \"%s\", refusing.", +- path); +- } +- +- if (unlink(path) < 0) { +- if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT) +- return 0; ++ if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) { ++ struct statfs s; + ++ if (statfs(path, &s) < 0) + return -errno; +- } ++ if (is_physical_fs(&s)) ++ return log_error_errno(SYNTHETIC_ERRNO(EPERM), ++ "Attempted to remove files from a disk file system under \"%s\", refusing.", ++ path); + } + +- return 0; ++ r = 0; ++ q = RET_NERRNO(unlink(path)); + } + +- 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.31.1 + diff --git a/5002-shared-rm-rf-loop-over-nested-directories-instead-of.patch b/5002-shared-rm-rf-loop-over-nested-directories-instead-of.patch new file mode 100644 index 00000000..9c7eac68 --- /dev/null +++ b/5002-shared-rm-rf-loop-over-nested-directories-instead-of.patch @@ -0,0 +1,273 @@ +From c561e2eab3b9b759b7592ea1b8168d4f36ede031 Mon Sep 17 00:00:00 2001 +From: Franck Bui +Date: Wed, 5 Jan 2022 15:08:07 +0100 +Subject: [PATCH 5002/5002] 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. + +[fbui: adjust context] +[fbui: fixes CVE-2021-3997] +[fbui: fixes bsc#1194178] +--- + src/shared/rm-rf.c | 159 +++++++++++++++++++++++++++++++-------------- + 1 file changed, 112 insertions(+), 47 deletions(-) + +diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c +index c7d3b8b7ad..58da213e9f 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; ++ _cleanup_(free_todo_entries) TodoEntry *todos = NULL; + struct dirent *de; ++ 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. */ ++ 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; + } +@@ -336,5 +401,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.31.1 + diff --git a/systemd-v249.7+suse.57.g523f32df57.tar.xz b/systemd-v249.7+suse.57.g523f32df57.tar.xz deleted file mode 100644 index b19fc651..00000000 --- a/systemd-v249.7+suse.57.g523f32df57.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:688c3f4409788f5ecea74d1dd6c5d1b2fbeb789a6c24c79bd35ed5f62c4b119c -size 7275376 diff --git a/systemd-v249.7+suse.66.ga54f80116c.tar.xz b/systemd-v249.7+suse.66.ga54f80116c.tar.xz new file mode 100644 index 00000000..e1babceb --- /dev/null +++ b/systemd-v249.7+suse.66.ga54f80116c.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91b2f8c8492b03612c3148615a39d7553632798941456e7702306837f0118dd8 +size 7276848 diff --git a/systemd.changes b/systemd.changes index 1b31cf54..b3e7e524 100644 --- a/systemd.changes +++ b/systemd.changes @@ -1,3 +1,29 @@ +------------------------------------------------------------------- +Tue Jan 11 08:06:11 UTC 2022 - Franck Bui + +- Added patches to fix CVE-2021-3997 (bsc#1194178) + + 5000-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch + 5001-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch + 5002-shared-rm-rf-loop-over-nested-directories-instead-of.patch + + These patches will be dropped and cherry-picked from upstream once upstream + will commit them in their main branch. + +------------------------------------------------------------------- +Thu Jan 6 14:31:21 UTC 2022 - Franck Bui + +- Import commit a54f80116ccf105dff11aef5d18dd110ebd3e8ee + + 30cbebc56f tmpfiles: 'st' may have been used uninitialized + 5443654ec0 macro: add new helper RET_NERRNO() + 8d90ecc435 rm-rf: optionally fsync() after removing directory tree + 591344010d rm-rf: refactor rm_rf_children(), split out body of directory iteration loop + 8c7762c4f1 Bump the max number of inodes for /dev to a million (bsc#1192858) + dc9476c881 journal: don't remove the flushed flag when journald is stopped + 29efc29efd TEST-10: don't attempt to write a byte to the socket + 773fb785b6 Bump the max number of inodes for /dev to 128k (bsc#1192858) + ------------------------------------------------------------------- Tue Jan 4 08:23:19 UTC 2022 - Franck Bui diff --git a/systemd.spec b/systemd.spec index 616eb386..2219cded 100644 --- a/systemd.spec +++ b/systemd.spec @@ -32,7 +32,7 @@ %endif %define min_kernel_version 4.5 -%define suse_version +suse.57.g523f32df57 +%define suse_version +suse.66.ga54f80116c %define _testsuitedir /usr/lib/systemd/tests %if 0%{?bootstrap} @@ -210,6 +210,12 @@ Patch12: 0012-resolved-create-etc-resolv.conf-symlink-at-runtime.patch # temporary and should be removed as soon as a fix is merged by # upstream. +# The following patches address CVE-2021-3997. They will be moved to the git +# repo once the issue will become public and upstream will release them. +Patch5000: 5000-shared-rm_rf-refactor-rm_rf_children_inner-to-shorte.patch +Patch5001: 5001-shared-rm_rf-refactor-rm_rf-to-shorten-code-a-bit.patch +Patch5002: 5002-shared-rm-rf-loop-over-nested-directories-instead-of.patch + %description Systemd is a system and service manager, compatible with SysV and LSB init scripts for Linux. systemd provides aggressive parallelization