summaryrefslogtreecommitdiffstats
path: root/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch')
-rw-r--r--meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch266
1 files changed, 266 insertions, 0 deletions
diff --git a/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch b/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
new file mode 100644
index 0000000000..c96b8d9a6e
--- /dev/null
+++ b/meta/recipes-core/systemd/systemd/CVE-2021-3997-3.patch
@@ -0,0 +1,266 @@
1Backport of the following upstream commit:
2From bef8e8e577368697b2e6f85183b1dbc99e0e520f Mon Sep 17 00:00:00 2001
3From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
4Date: Tue, 30 Nov 2021 22:29:05 +0100
5Subject: [PATCH 3/3] shared/rm-rf: loop over nested directories instead of
6 instead of recursing
7
8To remove directory structures, we need to remove the innermost items first,
9and then recursively remove higher-level directories. We would recursively
10descend into directories and invoke rm_rf_children and rm_rm_children_inner.
11This is problematic when too many directories are nested.
12
13Instead, let's create a "TODO" queue. In the the queue, for each level we
14hold the DIR* object we were working on, and the name of the directory. This
15allows us to leave a partially-processed directory, and restart the removal
16loop one level down. When done with the inner directory, we use the name to
17unlinkat() it from the parent, and proceed with the removal of other items.
18
19Because the nesting is increased by one level, it is best to view this patch
20with -b/--ignore-space-change.
21
22This fixes CVE-2021-3997, https://bugzilla.redhat.com/show_bug.cgi?id=2024639.
23The issue was reported and patches reviewed by Qualys Team.
24Mauro Matteo Cascella and Riccardo Schirone from Red Hat handled the disclosure.
25
26CVE: CVE-2021-3997
27Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz]
28Signed-off-by: Purushottam Choudhary <Purushottam.Choudhary@kpit.com>
29---
30 src/basic/rm-rf.c | 161 +++++++++++++++++++++++++++++++--------------
31 1 file changed, 113 insertions(+), 48 deletions(-)
32
33--- a/src/basic/rm-rf.c
34+++ b/src/basic/rm-rf.c
35@@ -26,12 +26,13 @@
36 return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
37 }
38
39-static int rm_rf_children_inner(
40+static int rm_rf_inner_child(
41 int fd,
42 const char *fname,
43 int is_dir,
44 RemoveFlags flags,
45- const struct stat *root_dev) {
46+ const struct stat *root_dev,
47+ bool allow_recursion) {
48
49 struct stat st;
50 int r, q = 0;
51@@ -49,9 +50,7 @@
52 }
53
54 if (is_dir) {
55- _cleanup_close_ int subdir_fd = -1;
56-
57- /* if root_dev is set, remove subdirectories only if device is same */
58+ /* If root_dev is set, remove subdirectories only if device is same */
59 if (root_dev && st.st_dev != root_dev->st_dev)
60 return 0;
61
62@@ -63,7 +62,6 @@
63 return 0;
64
65 if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) {
66-
67 /* This could be a subvolume, try to remove it */
68
69 r = btrfs_subvol_remove_fd(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
70@@ -77,13 +75,16 @@
71 return 1;
72 }
73
74- subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
75+ if (!allow_recursion)
76+ return -EISDIR;
77+
78+ int subdir_fd = openat(fd, fname, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
79 if (subdir_fd < 0)
80 return -errno;
81
82 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type
83 * again for each directory */
84- q = rm_rf_children(TAKE_FD(subdir_fd), flags | REMOVE_PHYSICAL, root_dev);
85+ q = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev);
86
87 } else if (flags & REMOVE_ONLY_DIRECTORIES)
88 return 0;
89@@ -96,64 +97,128 @@
90 return 1;
91 }
92
93+typedef struct TodoEntry {
94+ DIR *dir; /* A directory that we were operating on. */
95+ char *dirname; /* The filename of that directory itself. */
96+} TodoEntry;
97+
98+static void free_todo_entries(TodoEntry **todos) {
99+ for (TodoEntry *x = *todos; x && x->dir; x++) {
100+ closedir(x->dir);
101+ free(x->dirname);
102+ }
103+
104+ freep(todos);
105+}
106+
107 int rm_rf_children(
108 int fd,
109 RemoveFlags flags,
110 const struct stat *root_dev) {
111
112- _cleanup_closedir_ DIR *d = NULL;
113- struct dirent *de;
114+ _cleanup_(free_todo_entries) TodoEntry *todos = NULL;
115+ size_t n_todo = 0, allocated = 0;
116+ _cleanup_free_ char *dirname = NULL; /* Set when we are recursing and want to delete ourselves */
117 int ret = 0, r;
118
119- assert(fd >= 0);
120+ /* Return the first error we run into, but nevertheless try to go on.
121+ * The passed fd is closed in all cases, including on failure. */
122
123- /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
124- * fd, in all cases, including on failure. */
125+ for (;;) { /* This loop corresponds to the directory nesting level. */
126+ _cleanup_closedir_ DIR *d = NULL;
127+ struct dirent *de;
128+
129+ if (n_todo > 0) {
130+ /* We know that we are in recursion here, because n_todo is set.
131+ * We need to remove the inner directory we were operating on. */
132+ assert(dirname);
133+ r = unlinkat(dirfd(todos[n_todo-1].dir), dirname, AT_REMOVEDIR);
134+ if (r < 0 && r != -ENOENT && ret == 0)
135+ ret = r;
136+ dirname = mfree(dirname);
137+
138+ /* And now let's back out one level up */
139+ n_todo --;
140+ d = TAKE_PTR(todos[n_todo].dir);
141+ dirname = TAKE_PTR(todos[n_todo].dirname);
142+
143+ assert(d);
144+ fd = dirfd(d); /* Retrieve the file descriptor from the DIR object */
145+ assert(fd >= 0);
146+ } else {
147+ next_fd:
148+ assert(fd >= 0);
149+ d = fdopendir(fd);
150+ if (!d) {
151+ safe_close(fd);
152+ return -errno;
153+ }
154+ fd = dirfd(d); /* We donated the fd to fdopendir(). Let's make sure we sure we have
155+ * the right descriptor even if it were to internally invalidate the
156+ * one we passed. */
157+
158+ if (!(flags & REMOVE_PHYSICAL)) {
159+ struct statfs sfs;
160+
161+ if (fstatfs(fd, &sfs) < 0)
162+ return -errno;
163+
164+ if (is_physical_fs(&sfs)) {
165+ /* We refuse to clean physical file systems with this call, unless
166+ * explicitly requested. This is extra paranoia just to be sure we
167+ * never ever remove non-state data. */
168+
169+ _cleanup_free_ char *path = NULL;
170+
171+ (void) fd_get_path(fd, &path);
172+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
173+ "Attempted to remove disk file system under \"%s\", and we can't allow that.",
174+ strna(path));
175+ }
176+ }
177+ }
178
179- d = fdopendir(fd);
180- if (!d) {
181- safe_close(fd);
182- return -errno;
183- }
184+ FOREACH_DIRENT_ALL(de, d, return -errno) {
185+ int is_dir;
186
187- if (!(flags & REMOVE_PHYSICAL)) {
188- struct statfs sfs;
189+ if (dot_or_dot_dot(de->d_name))
190+ continue;
191
192- if (fstatfs(dirfd(d), &sfs) < 0)
193- return -errno;
194- }
195+ is_dir = de->d_type == DT_UNKNOWN ? -1 : de->d_type == DT_DIR;
196
197- if (is_physical_fs(&sfs)) {
198- /* We refuse to clean physical file systems with this call, unless explicitly
199- * requested. This is extra paranoia just to be sure we never ever remove non-state
200- * data. */
201-
202- _cleanup_free_ char *path = NULL;
203-
204- (void) fd_get_path(fd, &path);
205- return log_error_errno(SYNTHETIC_ERRNO(EPERM),
206- "Attempted to remove disk file system under \"%s\", and we can't allow that.",
207- strna(path));
208- }
209- }
210+ r = rm_rf_inner_child(fd, de->d_name, is_dir, flags, root_dev, false);
211+ if (r == -EISDIR) {
212+ /* Push the current working state onto the todo list */
213
214- FOREACH_DIRENT_ALL(de, d, return -errno) {
215- int is_dir;
216+ if (!GREEDY_REALLOC0(todos, allocated, n_todo + 2))
217+ return log_oom();
218
219- if (dot_or_dot_dot(de->d_name))
220- continue;
221+ _cleanup_free_ char *newdirname = strdup(de->d_name);
222+ if (!newdirname)
223+ return log_oom();
224
225- is_dir =
226- de->d_type == DT_UNKNOWN ? -1 :
227- de->d_type == DT_DIR;
228-
229- r = rm_rf_children_inner(dirfd(d), de->d_name, is_dir, flags, root_dev);
230- if (r < 0 && r != -ENOENT && ret == 0)
231- ret = r;
232- }
233+ int newfd = openat(fd, de->d_name,
234+ O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
235+ if (newfd >= 0) {
236+ todos[n_todo++] = (TodoEntry) { TAKE_PTR(d), TAKE_PTR(dirname) };
237+ fd = newfd;
238+ dirname = TAKE_PTR(newdirname);
239+
240+ goto next_fd;
241
242- if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0)
243- ret = -errno;
244+ } else if (errno != -ENOENT && ret == 0)
245+ ret = -errno;
246+
247+ } else if (r < 0 && r != -ENOENT && ret == 0)
248+ ret = r;
249+ }
250+
251+ if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(fd) < 0 && ret >= 0)
252+ ret = -errno;
253+
254+ if (n_todo == 0)
255+ break;
256+ }
257
258 return ret;
259 }
260@@ -250,5 +315,5 @@
261 if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
262 return -EINVAL;
263
264- return rm_rf_children_inner(fd, name, -1, flags, NULL);
265+ return rm_rf_inner_child(fd, name, -1, flags, NULL, true);
266 }