Import Debian changes 4.92-8+deb10u6
[hcoop/debian/exim4.git] / debian / patches / 84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch
1 From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001
2 From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
3 Date: Thu, 25 Mar 2021 22:48:09 +0100
4 Subject: [PATCH 26/29] CVE-2020-28014, CVE-2021-27216: Arbitrary PID file
5 creation, clobbering, and deletion
6
7 Arbitrary PID file creation, clobbering, and deletion.
8 Patch provided by Qualys.
9
10 (cherry picked from commit 974f32939a922512b27d9f0a8a1cb5dec60e7d37)
11 (cherry picked from commit 43c6f0b83200b7082353c50187ef75de3704580a)
12 ---
13 doc/ChangeLog | 5 +
14 src/daemon.c | 212 ++++++++++++++++++++++++++++++++++++++----
15 src/exim.c | 12 ++-
16 test/stderr/0433 | 24 +++++
17 4 files changed, 232 insertions(+), 21 deletions(-)
18
19 --- a/doc/ChangeLog
20 +++ b/doc/ChangeLog
21 @@ -10,13 +10,18 @@ QS/02 PID file creation/deletion: only p
22 runtime user.
23
24 QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of
25 the Exim runtime user are allowed to create files.
26
27 +QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of
28 + the Exim runtime user are allowed to create files.
29
30 HS/01 Handle trailing backslash gracefully. (CVE-2019-15846)
31
32 +QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim
33 + runtime user.
34 +
35
36 Since version 4.92
37 ------------------
38
39 JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible
40 --- a/src/daemon.c
41 +++ b/src/daemon.c
42 @@ -886,10 +886,198 @@ while ((pid = waitpid(-1, &status, WNOHA
43 }
44 }
45 }
46
47
48 +static void
49 +set_pid_file_path(void)
50 +{
51 +if (override_pid_file_path)
52 + pid_file_path = override_pid_file_path;
53 +
54 +if (!*pid_file_path)
55 + pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
56 +
57 +if (pid_file_path[0] != '/')
58 + log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path);
59 +}
60 +
61 +
62 +enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE };
63 +
64 +/* Do various pid file operations as safe as possible. Ideally we'd just
65 +drop the privileges for creation of the pid file and not care at all about removal of
66 +the file. FIXME.
67 +Returns: true on success, false + errno==EACCES otherwise
68 +*/
69 +static BOOL
70 +operate_on_pid_file(const enum pid_op operation, const pid_t pid)
71 +{
72 +char pid_line[sizeof(int) * 3 + 2];
73 +const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid);
74 +BOOL lines_match = FALSE;
75 +
76 +char * path = NULL;
77 +char * base = NULL;
78 +char * dir = NULL;
79 +
80 +const int dir_flags = O_RDONLY | O_NONBLOCK;
81 +const int base_flags = O_NOFOLLOW | O_NONBLOCK;
82 +const mode_t base_mode = 0644;
83 +struct stat sb;
84 +
85 +int cwd_fd = -1;
86 +int dir_fd = -1;
87 +int base_fd = -1;
88 +
89 +BOOL success = FALSE;
90 +errno = EACCES;
91 +
92 +set_pid_file_path();
93 +if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup;
94 +if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup;
95 +
96 +path = CS string_copy(pid_file_path);
97 +if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */
98 + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path);
99 +
100 +dir = (base != path) ? path : "/";
101 +*base++ = '\0';
102 +
103 +if (!dir || !*dir || *dir != '/') goto cleanup;
104 +if (!base || !*base || strchr(base, '/') != NULL) goto cleanup;
105 +
106 +cwd_fd = open(".", dir_flags);
107 +if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
108 +dir_fd = open(dir, dir_flags);
109 +if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup;
110 +
111 +/* emulate openat */
112 +if (fchdir(dir_fd) != 0) goto cleanup;
113 +base_fd = open(base, O_RDONLY | base_flags);
114 +if (fchdir(cwd_fd) != 0)
115 + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
116 +
117 +if (base_fd >= 0)
118 + {
119 + char line[sizeof(pid_line)];
120 + ssize_t len = -1;
121 +
122 + if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup;
123 + if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup;
124 + if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup;
125 +
126 + len = read(base_fd, line, sizeof(line));
127 + if (len != (ssize_t)sb.st_size) goto cleanup;
128 + line[len] = '\0';
129 +
130 + if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup;
131 + if (line[len-1] != '\n') goto cleanup;
132 + lines_match = (len == pid_len && strcmp(line, pid_line) == 0);
133 + }
134 +
135 +if (operation == PID_WRITE)
136 + {
137 + if (!lines_match)
138 + {
139 + if (base_fd >= 0)
140 + {
141 + int error = -1;
142 + /* emulate unlinkat */
143 + if (fchdir(dir_fd) != 0) goto cleanup;
144 + error = unlink(base);
145 + if (fchdir(cwd_fd) != 0)
146 + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
147 + if (error) goto cleanup;
148 + (void)close(base_fd);
149 + base_fd = -1;
150 + }
151 + /* emulate openat */
152 + if (fchdir(dir_fd) != 0) goto cleanup;
153 + base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode);
154 + if (fchdir(cwd_fd) != 0)
155 + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
156 + if (base_fd < 0) goto cleanup;
157 + if (fchmod(base_fd, base_mode) != 0) goto cleanup;
158 + if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup;
159 + DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
160 + }
161 + }
162 +else
163 + {
164 + if (!lines_match) goto cleanup;
165 + if (operation == PID_DELETE)
166 + {
167 + int error = -1;
168 + /* emulate unlinkat */
169 + if (fchdir(dir_fd) != 0) goto cleanup;
170 + error = unlink(base);
171 + if (fchdir(cwd_fd) != 0)
172 + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno));
173 + if (error) goto cleanup;
174 + }
175 + }
176 +
177 +success = TRUE;
178 +errno = 0;
179 +
180 +cleanup:
181 +if (cwd_fd >= 0) (void)close(cwd_fd);
182 +if (dir_fd >= 0) (void)close(dir_fd);
183 +if (base_fd >= 0) (void)close(base_fd);
184 +return success;
185 +}
186 +
187 +
188 +/* Remove the daemon's pidfile. Note: runs with root privilege,
189 +as a direct child of the daemon. Does not return. */
190 +
191 +void
192 +delete_pid_file(void)
193 +{
194 +const BOOL success = operate_on_pid_file(PID_DELETE, getppid());
195 +
196 +DEBUG(D_any)
197 + debug_printf("delete pid file %s %s: %s\n", pid_file_path,
198 + success ? "success" : "failure", strerror(errno));
199 +
200 +exim_exit(EXIT_SUCCESS, US"");
201 +}
202 +
203 +
204 +/* Called by the daemon; exec a child to get the pid file deleted
205 +since we may require privs for the containing directory */
206 +
207 +static void
208 +daemon_die(void)
209 +{
210 +int pid;
211 +
212 +DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n");
213 +#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT))
214 +tls_watch_invalidate();
215 +#endif
216 +
217 +if (f.running_in_test_harness || write_pid)
218 + {
219 + if ((pid = fork()) == 0)
220 + {
221 + if (override_pid_file_path)
222 + (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3,
223 + "-oP", override_pid_file_path, "-oPX");
224 + else
225 + (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX");
226 +
227 + /* Control never returns here. */
228 + }
229 + if (pid > 0)
230 + child_close(pid, 1);
231 + }
232 +exim_exit(EXIT_SUCCESS, US"");
233 +}
234 +
235 +
236
237 /*************************************************
238 * Exim Daemon Mainline *
239 *************************************************/
240
241 @@ -1538,32 +1726,18 @@ automatically. Consequently, Exim 4 writ
242
243 The variable daemon_write_pid is used to control this. */
244
245 if (f.running_in_test_harness || write_pid)
246 {
247 - FILE *f;
248 -
249 - if (override_pid_file_path)
250 - pid_file_path = override_pid_file_path;
251 -
252 - if (pid_file_path[0] == 0)
253 - pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory);
254 -
255 - if ((f = modefopen(pid_file_path, "wb", 0644)))
256 - {
257 - (void)fprintf(f, "%d\n", (int)getpid());
258 - (void)fclose(f);
259 - DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path);
260 - }
261 - else
262 - DEBUG(D_any)
263 - debug_printf("%s\n", string_open_failed(errno, "pid file %s",
264 - pid_file_path));
265 + const enum pid_op operation = (f.running_in_test_harness
266 + || real_uid == root_uid
267 + || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK;
268 + if (!operate_on_pid_file(operation, getpid()))
269 + DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno));
270 }
271
272 /* Set up the handler for SIGHUP, which causes a restart of the daemon. */
273 -
274 sighup_seen = FALSE;
275 signal(SIGHUP, sighup_handler);
276
277 /* Give up root privilege at this point (assuming that exim_uid and exim_gid
278 are not root). The third argument controls the running of initgroups().
279 --- a/src/exim.c
280 +++ b/src/exim.c
281 @@ -3042,12 +3042,20 @@ for (i = 1; i < argc; i++)
282
283 else if (Ustrcmp(argrest, "o") == 0) {}
284
285 /* -oP <name>: set pid file path for daemon */
286
287 - else if (Ustrcmp(argrest, "P") == 0)
288 - override_pid_file_path = argv[++i];
289 + else if (*argrest == 'P')
290 + {
291 + if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid)
292 + exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
293 + "(uid=%d euid=%d | %d)\n",
294 + root_uid, exim_uid, getuid(), geteuid(), real_uid);
295 + if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i];
296 + else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file();
297 + else badarg = TRUE;
298 + }
299
300 /* -or <n>: set timeout for non-SMTP acceptance
301 -os <n>: set timeout for SMTP acceptance */
302
303 else if (*argrest == 'r' || *argrest == 's')