diff options
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.patch | 266 |
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 @@ | |||
1 | Backport of the following upstream commit: | ||
2 | From bef8e8e577368697b2e6f85183b1dbc99e0e520f Mon Sep 17 00:00:00 2001 | ||
3 | From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl> | ||
4 | Date: Tue, 30 Nov 2021 22:29:05 +0100 | ||
5 | Subject: [PATCH 3/3] shared/rm-rf: loop over nested directories instead of | ||
6 | instead of recursing | ||
7 | |||
8 | To remove directory structures, we need to remove the innermost items first, | ||
9 | and then recursively remove higher-level directories. We would recursively | ||
10 | descend into directories and invoke rm_rf_children and rm_rm_children_inner. | ||
11 | This is problematic when too many directories are nested. | ||
12 | |||
13 | Instead, let's create a "TODO" queue. In the the queue, for each level we | ||
14 | hold the DIR* object we were working on, and the name of the directory. This | ||
15 | allows us to leave a partially-processed directory, and restart the removal | ||
16 | loop one level down. When done with the inner directory, we use the name to | ||
17 | unlinkat() it from the parent, and proceed with the removal of other items. | ||
18 | |||
19 | Because the nesting is increased by one level, it is best to view this patch | ||
20 | with -b/--ignore-space-change. | ||
21 | |||
22 | This fixes CVE-2021-3997, https://bugzilla.redhat.com/show_bug.cgi?id=2024639. | ||
23 | The issue was reported and patches reviewed by Qualys Team. | ||
24 | Mauro Matteo Cascella and Riccardo Schirone from Red Hat handled the disclosure. | ||
25 | |||
26 | CVE: CVE-2021-3997 | ||
27 | Upstream-Status: Backport [http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd_245.4-4ubuntu3.15.debian.tar.xz] | ||
28 | Signed-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 | } | ||