From 0c0c20aac64e1ef4037c144efec9a064add0d055 Mon Sep 17 00:00:00 2001 From: Andreas Metzler Date: Sat, 1 May 2021 11:42:39 +0200 Subject: [PATCH] Import Debian changes 4.92-8+deb10u6 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit exim4 (4.92-8+deb10u6) buster-security; urgency=high . * Fix several security vulnerabilities reported by Qualys and add related robustness improvements. (Originally fixed in upstream release 4.94.3 and in upstream GIT branch exim-4.92.3+fixes. (Special thanks to Heiko) + CVE-2020-28025: Heap out-of-bounds read in pdkim_finish_bodyhash() + CVE-2020-28018: Use-after-free in tls-openssl.c + CVE-2020-28023: Out-of-bounds read in smtp_setup_msg() + CVE-2020-28010: Heap out-of-bounds write in main() + CVE-2020-28011: Heap buffer overflow in queue_run() + CVE-2020-28013: Heap buffer overflow in parse_fix_phrase() + CVE-2020-28017: Integer overflow in receive_add_recipient() + CVE-2020-28022: Heap out-of-bounds read and write in extract_option() + CVE-2020-28026: Line truncation and injection in spool_read_header() + CVE-2020-28015 and CVE-2020-28021: New-line injection into spool header file. + CVE-2020-28009: Integer overflow in get_stdinput() + CVE-2020-28024: Heap buffer underflow in smtp_ungetc() + CVE-2020-28012: Missing close-on-exec flag for privileged pipe + CVE-2020-28019: Failure to reset function pointer after BDAT error + CVE-2020-28007: Link attack in Exim's log directory + CVE-2020-28008: Assorted attacks in Exim's spool directory + CVE-2020-28014, CVE-2021-27216: Arbitrary PID file creation, clobbering, and deletion. . exim4 (4.92-8+deb10u5) buster; urgency=medium . * Fix use of concurrent TLS connections under GnuTLS: 80_01-GnuTLS-fix-hanging-callout-connections.patch 80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch 80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch (Thanks, Heiko Schlittermann for the backport) * Pull 82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch from upstream git (already included in 4.94), on TLS connections to a CNAME verify the certificate against the original CNAME instead of against the A record. Closes: #985243 * In README.Debian explicitly document the limitation/extent of server certificate checking (authenticity not enforced) in the default configuration (Thanks, Jö Fahlke). This Closes: #985244 (improved documentation and Closes: #985344 (Yes, without required cert checking MitM attacks are possible, but for a stable update documenting this is the best compromise.) --- debian/README.Debian.xml | 33 +- debian/changelog | 47 ++ ...uTLS-fix-hanging-callout-connections.patch | 83 +++ ...ite-wait-after-uncorking-the-session.patch | 73 +++ ...are-about-corked-data-when-uncorking.patch | 55 ++ ...-rules-for-certifucate-name-checks-w.patch | 188 ++++++ ...eap-out-of-bounds-read-in-pdkim_fini.patch | 44 ++ ...8018-Use-after-free-in-tls-openssl.c.patch | 33 ++ ...Out-of-bounds-read-in-smtp_setup_msg.patch | 58 ++ ...010-Heap-out-of-bounds-write-in-main.patch | 42 ++ ...11-Heap-buffer-overflow-in-queue_run.patch | 39 ++ ...eap-buffer-overflow-in-parse_fix_phr.patch | 34 ++ ...negative-and-large-store-allocations.patch | 74 +++ ...nteger-overflow-in-receive_add_recip.patch | 49 ++ ...eap-out-of-bounds-read-and-write-in-.patch | 61 ++ ...ine-truncation-and-injection-in-spoo.patch | 102 ++++ ...8021-New-line-injection-into-spool-h.patch | 69 +++ ...009-Integer-overflow-in-get_stdinput.patch | 61 ++ ...Heap-buffer-underflow-in-smtp_ungetc.patch | 41 ++ ...issing-close-on-exec-flag-for-privil.patch | 31 + ...rd-against-relative-names-for-msglog.patch | 41 ++ ...ity-Check-overrun-rcpt_count-integer.patch | 39 ++ ...lways-exit-when-LOG_PANIC_DIE-is-set.patch | 24 + ...-by-one-in-smtp-transport-read-respo.patch | 47 ++ ...ecrement-of-dkim_collect_input-if-al.patch | 59 ++ ...-clean-smtp_out-input-buffer-even-in.patch | 67 +++ ...odification-of-constant-data-in-dkim.patch | 89 +++ ...ailure-to-reset-function-pointer-aft.patch | 135 +++++ ...-Link-attack-in-Exim-s-log-directory.patch | 542 ++++++++++++++++++ ...ssorted-attacks-in-Exim-s-spool-dire.patch | 205 +++++++ ...VE-2021-27216-Arbitrary-PID-file-cre.patch | 303 ++++++++++ ...ments-for-CVE-2020-28014-CVE-2021-27.patch | 57 ++ ...or-body-w-o-trailing-CRLF-again-Bug-.patch | 26 + debian/patches/series | 31 + 34 files changed, 2876 insertions(+), 6 deletions(-) create mode 100644 debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch create mode 100644 debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch create mode 100644 debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch create mode 100644 debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch create mode 100644 debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch create mode 100644 debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch create mode 100644 debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch create mode 100644 debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch create mode 100644 debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch create mode 100644 debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch create mode 100644 debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch create mode 100644 debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch create mode 100644 debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch create mode 100644 debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch create mode 100644 debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch create mode 100644 debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch create mode 100644 debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch create mode 100644 debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch create mode 100644 debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch create mode 100644 debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch create mode 100644 debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch create mode 100644 debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch create mode 100644 debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch create mode 100644 debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch create mode 100644 debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch create mode 100644 debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch create mode 100644 debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch create mode 100644 debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch create mode 100644 debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch create mode 100644 debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch create mode 100644 debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch diff --git a/debian/README.Debian.xml b/debian/README.Debian.xml index 77b4a37..df8f4a3 100644 --- a/debian/README.Debian.xml +++ b/debian/README.Debian.xml @@ -1084,17 +1084,38 @@ This means that you will not need any special configuration if - you want to use TLS for outgoing mail. However, if your + you want to use TLS for outgoing mail. However, to enforce + TLS and successful certificate verification, a few things + need to be configured. + + + To enforce TLS and prevent fallback to unencrypted + connections, ensure that hosts_require_tls = * is in effect on + the respective transport. For the remote_smtp_smarthost + transport, this setting can be controlled via the + REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS macro. + + + The certificate presented by the remote host is checked + against the system CA certificate store + (/etc/ssl/certs/) and the verification + result is logged (CV=...). However successful certificate + verification is not enforced by default. + This can be changed by setting tls_verify_hosts = * on the + respective transport. + + + Another possibility would be to use DANE for certificate + verification. This requires support on the server side and + a resolver with DNSSEC support on the client side. + + + If your server setup mandates the use of client certificates, you need to amend your remote_smtp and/or remote_smtp_smarthost transports with a tls_certificate option. This is not commonly needed. - - The certificate - presented by the remote host is not checked unless you - specify a tls_verify_certificate option on the transport. - To make exim send a TLS certificate to the remote host set REMOTE_SMTP_TLS_CERTIFICATE/REMOTE_SMTP_PRIVATEKEY or for diff --git a/debian/changelog b/debian/changelog index 42f11ec..b9625bd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,50 @@ +exim4 (4.92-8+deb10u6) buster-security; urgency=high + + * Fix several security vulnerabilities reported by Qualys and add related + robustness improvements. (Originally fixed in upstream release 4.94.3 and + in upstream GIT branch exim-4.92.3+fixes. (Special thanks to Heiko) + + CVE-2020-28025: Heap out-of-bounds read in pdkim_finish_bodyhash() + + CVE-2020-28018: Use-after-free in tls-openssl.c + + CVE-2020-28023: Out-of-bounds read in smtp_setup_msg() + + CVE-2020-28010: Heap out-of-bounds write in main() + + CVE-2020-28011: Heap buffer overflow in queue_run() + + CVE-2020-28013: Heap buffer overflow in parse_fix_phrase() + + CVE-2020-28017: Integer overflow in receive_add_recipient() + + CVE-2020-28022: Heap out-of-bounds read and write in extract_option() + + CVE-2020-28026: Line truncation and injection in spool_read_header() + + CVE-2020-28015 and CVE-2020-28021: New-line injection into spool header + file. + + CVE-2020-28009: Integer overflow in get_stdinput() + + CVE-2020-28024: Heap buffer underflow in smtp_ungetc() + + CVE-2020-28012: Missing close-on-exec flag for privileged pipe + + CVE-2020-28019: Failure to reset function pointer after BDAT error + + CVE-2020-28007: Link attack in Exim's log directory + + CVE-2020-28008: Assorted attacks in Exim's spool directory + + CVE-2020-28014, CVE-2021-27216: Arbitrary PID file creation, clobbering, + and deletion. + + -- Andreas Metzler Sat, 01 May 2021 11:42:39 +0200 + +exim4 (4.92-8+deb10u5) buster; urgency=medium + + * Fix use of concurrent TLS connections under GnuTLS: + 80_01-GnuTLS-fix-hanging-callout-connections.patch + 80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch + 80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch + (Thanks, Heiko Schlittermann for the backport) + * Pull 82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch from + upstream git (already included in 4.94), on TLS connections to a CNAME + verify the certificate against the original CNAME instead of against + the A record. Closes: #985243 + * In README.Debian explicitly document the limitation/extent of server + certificate checking (authenticity not enforced) in the default + configuration (Thanks, Jö Fahlke). This Closes: #985244 (improved + documentation and Closes: #985344 (Yes, without required cert + checking MitM attacks are possible, but for a stable update documenting + this is the best compromise.) + + -- Andreas Metzler Thu, 18 Mar 2021 09:10:15 +0100 + exim4 (4.92-8+deb10u4) buster-security; urgency=high * Fix authentication bypass in SPA authenticator due to out-of-bound buffer diff --git a/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch new file mode 100644 index 0000000..0f36d73 --- /dev/null +++ b/debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch @@ -0,0 +1,83 @@ +From 97c5e07c220b55d1c506a1798c9ce3ae3105adea Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Thu, 13 Feb 2020 16:45:38 +0000 +Subject: [PATCH 4/6] GnuTLS: fix hanging callout connections + +Broken-by: 925ac8e4f1 +(cherry picked from commit bd95ffc2ba87fbd3c752df17bc8fd9c01586d45a) +--- + doc/ChangeLog | 81 ++++--------------------------------------- + src/tls-gnu.c | 24 +++++++------ + 2 files changed, 20 insertions(+), 85 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -67,6 +67,11 @@ JH/41 Bug 2571: Fix SPA authenticator. + being used. A malicious client could thus cause an out-of-bounds read and + possibly gain authentication. Fix by adding the check. + ++JH/25 Fix use of concurrent TLS connections under GnuTLS. When a callout was ++ done during a receiving connection, and both used TLS, global info was ++ used rather than per-connection info for tracking the state of data ++ queued for transmission. This could result in a connection hang. ++ + + Exim version 4.92 + ----------------- +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -124,10 +124,17 @@ typedef struct exim_gnutls_state { + enum peer_verify_requirement verify_requirement; + int fd_in; + int fd_out; +- BOOL peer_cert_verified; +- BOOL peer_dane_verified; +- BOOL trigger_sni_changes; +- BOOL have_set_peerdn; ++ ++ BOOL peer_cert_verified:1; ++ BOOL peer_dane_verified:1; ++ BOOL trigger_sni_changes:1; ++ BOOL have_set_peerdn:1; ++ BOOL xfer_eof:1; /*XXX never gets set! */ ++ BOOL xfer_error:1; ++#ifdef SUPPORT_CORK ++ BOOL corked:1; ++#endif ++ + const struct host_item *host; /* NULL if server */ + gnutls_x509_crt_t peercert; + uschar *peerdn; +@@ -160,8 +167,6 @@ typedef struct exim_gnutls_state { + uschar *xfer_buffer; + int xfer_buffer_lwm; + int xfer_buffer_hwm; +- BOOL xfer_eof; /*XXX never gets set! */ +- BOOL xfer_error; + } exim_gnutls_state_st; + + static const exim_gnutls_state_st exim_gnutls_state_init = { +@@ -2790,9 +2795,8 @@ ssize_t outbytes; + size_t left = len; + exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; + #ifdef SUPPORT_CORK +-static BOOL corked = FALSE; + +-if (more && !corked) gnutls_record_cork(state->session); ++if (more && !state->corked) gnutls_record_cork(state->session); + #endif + + DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, +@@ -2833,10 +2837,10 @@ if (len > INT_MAX) + } + + #ifdef SUPPORT_CORK +-if (more != corked) ++if (more != state->corked) + { + if (!more) (void) gnutls_record_uncork(state->session, 0); +- corked = more; ++ state->corked = more; + } + #endif + diff --git a/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch new file mode 100644 index 0000000..9998618 --- /dev/null +++ b/debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch @@ -0,0 +1,73 @@ +From 783cb0301d9ceef2748956c3f91762275b7b45e5 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Tue, 18 Feb 2020 18:59:49 +0100 +Subject: [PATCH 5/6] GnuTLS: tls_write(): wait after uncorking the session + +(cherry picked from commit 8f9adfd36222d4e9e730734e00dffe874073e5b4) +--- + src/tls-gnu.c | 34 ++++++++++++++++++++++++++++------ + 1 file changed, 28 insertions(+), 6 deletions(-) + +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index 822ad89c6..94a718673 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2835,9 +2835,14 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) + ssize_t outbytes; + size_t left = len; + exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; +-#ifdef SUPPORT_CORK + +-if (more && !state->corked) gnutls_record_cork(state->session); ++#ifdef SUPPORT_CORK ++if (more && !state->corked) ++ { ++ DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session); ++ gnutls_record_cork(state->session); ++ state->corked = TRUE; ++ } + #endif + + DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, +@@ -2853,6 +2858,7 @@ while (left > 0) + while (outbytes == GNUTLS_E_AGAIN); + + DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes); ++ + if (outbytes < 0) + { + DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__); +@@ -2878,10 +2884,26 @@ if (len > INT_MAX) + } + + #ifdef SUPPORT_CORK +-if (more != state->corked) +- { +- if (!more) (void) gnutls_record_uncork(state->session, 0); +- state->corked = more; ++if (!more && state->corked) ++ { ++ DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session); ++ do { ++ do ++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on ++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). ++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. ++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN ++ match the EINTR and EAGAIN errno values.) */ ++ outbytes = gnutls_record_uncork(state->session, 0); ++ while (outbytes == GNUTLS_E_AGAIN); ++ ++ if (outbytes < 0) ++ { ++ record_io_error(state, len, US"uncork", NULL); ++ return -1; ++ } ++ } while (gnutls_record_check_corked(state->session) > 0); ++ state->corked = FALSE; + } + #endif + +-- +2.28.0 + diff --git a/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch new file mode 100644 index 0000000..da8748f --- /dev/null +++ b/debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch @@ -0,0 +1,55 @@ +From 3afb07f2c63fb6dc3983b28e7cdaf11fceb741d1 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Mon, 2 Mar 2020 22:56:32 +0100 +Subject: [PATCH 6/6] GnuTLS: Do not care about corked data when uncorking + +(cherry picked from commit d8d7e3a4162b52382daf8319f221c085c76c5b8f) +--- + src/tls-gnu.c | 31 +++++++++++++++---------------- + 1 file changed, 15 insertions(+), 16 deletions(-) + +diff --git a/src/tls-gnu.c b/src/tls-gnu.c +index 94a718673..2091e44db 100644 +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2887,22 +2887,21 @@ if (len > INT_MAX) + if (!more && state->corked) + { + DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session); +- do { +- do +- /* We can't use GNUTLS_RECORD_WAIT here, as it retries on +- GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). +- The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. +- But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN +- match the EINTR and EAGAIN errno values.) */ +- outbytes = gnutls_record_uncork(state->session, 0); +- while (outbytes == GNUTLS_E_AGAIN); +- +- if (outbytes < 0) +- { +- record_io_error(state, len, US"uncork", NULL); +- return -1; +- } +- } while (gnutls_record_check_corked(state->session) > 0); ++ do ++ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on ++ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm(). ++ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway. ++ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN ++ match the EINTR and EAGAIN errno values.) */ ++ outbytes = gnutls_record_uncork(state->session, 0); ++ while (outbytes == GNUTLS_E_AGAIN); ++ ++ if (outbytes < 0) ++ { ++ record_io_error(state, len, US"uncork", NULL); ++ return -1; ++ } ++ + state->corked = FALSE; + } + #endif +-- +2.28.0 + diff --git a/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch new file mode 100644 index 0000000..10e54e3 --- /dev/null +++ b/debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch @@ -0,0 +1,188 @@ +Description: TLS: use RFC 6125 rules for certificate name checks when + CNAMES are present. Bug 2594 +Origin: upstream https://git.exim.org/exim.git/commit/0851a3bbf4667081d47f5d85b6b3a5cb33cbdba6 +Bug: https://bugs.exim.org/show_bug.cgi?id=2594 +Forwarded: not-needed +Last-Update: 2021-03-02 + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -41,10 +41,15 @@ JH/10 OpenSSL: Fix aggregation of messag + + JH/11 Harden plaintext authenticator against a badly misconfigured client-send + string. Previously it was possible to cause undefined behaviour in a + library routine (usually a crash). Found by "zerons". + ++JH/06 Bug 2594: Change the name used for certificate name checks in the smtp ++ transport. Previously it was the name on the DNS A-record; use instead ++ the head of the CNAME chain leading there (if there is one). This seems ++ to align better with RFC 6125. ++ + + JH/18 GnuTLS: fix $tls_out_ocsp under hosts_request_ocsp. Previously the + verification result was not updated unless hosts_require_ocsp applied. + + JH/20 Bug 2389: fix server advertising of usable certificates, under GnuTLS in +--- a/src/host.c ++++ b/src/host.c +@@ -1966,10 +1966,17 @@ host_item *last = NULL; + BOOL temp_error = FALSE; + #if HAVE_IPV6 + int af; + #endif + ++#ifndef DISABLE_TLS ++/* Copy the host name at this point to the value which is used for ++TLS certificate name checking, before anything modifies it. */ ++ ++host->certname = host->name; ++#endif ++ + /* Make sure DNS options are set as required. This appears to be necessary in + some circumstances when the get..byname() function actually calls the DNS. */ + + dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0, + (flags & HOST_FIND_SEARCH_PARENTS) != 0, +@@ -2132,10 +2139,13 @@ for (i = 1; i <= times; + + else + { + host_item *next = store_get(sizeof(host_item)); + next->name = host->name; ++#ifndef DISABLE_TLS ++ next->certname = host->certname; ++#endif + next->mx = host->mx; + next->address = text_address; + next->port = PORT_NONE; + next->status = hstatus_unknown; + next->why = hwhy_unknown; +@@ -2150,16 +2160,16 @@ for (i = 1; i <= times; + + /* If no hosts were found, the address field in the original host block will be + NULL. If temp_error is set, at least one of the lookups gave a temporary error, + so we pass that back. */ + +-if (host->address == NULL) ++if (!host->address) + { + uschar *msg = + #ifndef STAND_ALONE +- (message_id[0] == 0 && smtp_in != NULL)? +- string_sprintf("no IP address found for host %s (during %s)", host->name, ++ message_id[0] == 0 && smtp_in ++ ? string_sprintf("no IP address found for host %s (during %s)", host->name, + smtp_get_connection_info()) : + #endif + string_sprintf("no IP address found for host %s", host->name); + + HDEBUG(D_host_lookup) debug_printf("%s\n", msg); +@@ -2277,10 +2287,17 @@ dns_record *rr; + host_item *thishostlast = NULL; /* Indicates not yet filled in anything */ + BOOL v6_find_again = FALSE; + BOOL dnssec_fail = FALSE; + int i; + ++#ifndef DISABLE_TLS ++/* Copy the host name at this point to the value which is used for ++TLS certificate name checking, before any CNAME-following modifies it. */ ++ ++host->certname = host->name; ++#endif ++ + /* If allow_ip is set, a name which is an IP address returns that value + as its address. This is used for MX records when allow_mx_to_ip is set, for + those sites that feel they have to flaunt the RFC rules. */ + + if (allow_ip && string_is_ip_address(host->name, NULL) != 0) +--- a/src/structs.h ++++ b/src/structs.h +@@ -77,18 +77,21 @@ host addresses is done using this struct + + typedef enum {DS_UNK=-1, DS_NO, DS_YES} dnssec_status_t; + + typedef struct host_item { + struct host_item *next; +- const uschar *name; /* Host name */ +- const uschar *address; /* IP address in text form */ +- int port; /* port value in host order (if SRV lookup) */ +- int mx; /* MX value if found via MX records */ +- int sort_key; /* MX*1000 plus random "fraction" */ +- int status; /* Usable, unusable, or unknown */ +- int why; /* Why host is unusable */ +- int last_try; /* Time of last try if known */ ++ const uschar *name; /* Host name */ ++#ifndef DISABLE_TLS ++ const uschar *certname; /* Name used for certificate checks */ ++#endif ++ const uschar *address; /* IP address in text form */ ++ int port; /* port value in host order (if SRV lookup) */ ++ int mx; /* MX value if found via MX records */ ++ int sort_key; /* MX*1000 plus random "fraction" */ ++ int status; /* Usable, unusable, or unknown */ ++ int why; /* Why host is unusable */ ++ int last_try; /* Time of last try if known */ + dnssec_status_t dnssec; + } host_item; + + /* Chain of rewrite rules, read from the rewrite config, or parsed from the + rewrite_headers field of a transport. */ +--- a/src/tls-gnu.c ++++ b/src/tls-gnu.c +@@ -2191,13 +2191,13 @@ tls_client_setup_hostname_checks(host_it + { + if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) + { + state->exp_tls_verify_cert_hostnames = + #ifdef SUPPORT_I18N +- string_domain_utf8_to_alabel(host->name, NULL); ++ string_domain_utf8_to_alabel(host->certname, NULL); + #else +- host->name; ++ host->certname; + #endif + DEBUG(D_tls) + debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", + state->exp_tls_verify_cert_hostnames); + } +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -309,18 +309,18 @@ typedef struct tls_ext_ctx_cb { + X509_STORE *verify_store; /* non-null if status requested */ + BOOL verify_required; + } client; + } u_ocsp; + #endif +- uschar *dhparam; ++ uschar * dhparam; + /* these are cached from first expand */ +- uschar *server_cipher_list; ++ uschar * server_cipher_list; + /* only passed down to tls_error: */ +- host_item *host; ++ host_item * host; + const uschar * verify_cert_hostnames; + #ifndef DISABLE_EVENT +- uschar * event_action; ++ uschar * event_action; + #endif + } tls_ext_ctx_cb; + + /* should figure out a cleanup of API to handle state preserved per + implementation, for various reasons, which can be void * in the APIs. +@@ -2359,13 +2359,13 @@ if ((rc = setup_certs(ctx, ob->tls_verif + + if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) + { + cbinfo->verify_cert_hostnames = + #ifdef SUPPORT_I18N +- string_domain_utf8_to_alabel(host->name, NULL); ++ string_domain_utf8_to_alabel(host->certname, NULL); + #else +- host->name; ++ host->certname; + #endif + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + cbinfo->verify_cert_hostnames); + } + return OK; diff --git a/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch new file mode 100644 index 0000000..7c79753 --- /dev/null +++ b/debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch @@ -0,0 +1,44 @@ +From 9db12ffa00aa1dcbe60eec543307f405e35cfe15 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 18:54:16 -0800 +Subject: [PATCH 01/29] CVE-2020-28025: Heap out-of-bounds read in + pdkim_finish_bodyhash() + +--- + src/pdkim/pdkim.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index 594af03c5..e203311da 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -825,7 +825,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + /* VERIFICATION --------------------------------------------------------- */ + /* Be careful that the header sig included a bodyash */ + +- if ( sig->bodyhash.data ++ if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len + && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0) + { + DEBUG(D_acl) debug_printf("PDKIM [%s] Body hash compared OK\n", sig->domain); +@@ -1524,7 +1524,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + do this hash incrementally. + We don't need the hash we're calculating here for the GnuTLS and OpenSSL + cases of RSA signing, since those library routines can do hash-and-sign. +- ++ + Some time in the future we could easily avoid doing the hash here for those + cases (which will be common for a long while. We could also change from + the current copy-all-the-headers-into-one-block, then call the hash-and-sign +@@ -1779,7 +1779,7 @@ for (sig = ctx->sig; sig; sig = sig->next) + ); + goto NEXT_VERIFY; + } +- ++ + /* Make sure sig uses supported DKIM version (only v1) */ + if (sig->version != 1) + { +-- +2.30.2 + diff --git a/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch new file mode 100644 index 0000000..3a488b3 --- /dev/null +++ b/debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch @@ -0,0 +1,33 @@ +From 86cafc842feb6223476568921c2d3e06c706cc31 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:05:56 -0800 +Subject: [PATCH 02/29] CVE-2020-28018: Use-after-free in tls-openssl.c + +--- + src/tls-openssl.c | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/src/tls-openssl.c b/src/tls-openssl.c +index e751edd9a..2a8d4cabd 100644 +--- a/src/tls-openssl.c ++++ b/src/tls-openssl.c +@@ -2910,16 +2910,12 @@ a store reset there, so use POOL_PERM. */ + + if (!ct_ctx && (more || corked)) + { +-#ifdef EXPERIMENTAL_PIPE_CONNECT + int save_pool = store_pool; + store_pool = POOL_PERM; +-#endif + + corked = string_catn(corked, buff, len); + +-#ifdef EXPERIMENTAL_PIPE_CONNECT + store_pool = save_pool; +-#endif + + if (more) + { +-- +2.30.2 + diff --git a/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch new file mode 100644 index 0000000..28af9cc --- /dev/null +++ b/debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch @@ -0,0 +1,58 @@ +From 4cfadd994e5ab6e57cc43164d1e3198bb4faedbb Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:11:55 -0800 +Subject: [PATCH 03/29] CVE-2020-28023: Out-of-bounds read in smtp_setup_msg() + +Extracted from Jeremy Harris's commit afaf5a50. +--- + src/acl.c | 3 ++- + src/macros.h | 1 + + src/smtp_in.c | 4 ++-- + 3 files changed, 5 insertions(+), 3 deletions(-) + +diff --git a/src/acl.c b/src/acl.c +index f3b860e4a..49f6fe79c 100644 +--- a/src/acl.c ++++ b/src/acl.c +@@ -4464,7 +4464,8 @@ switch (where) + /* Drop cutthrough conns, and drop heldopen verify conns if + the previous was not DATA */ + { +- uschar prev = smtp_connection_had[smtp_ch_index-2]; ++ uschar prev = ++ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))]; + BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT); + + cancel_cutthrough_connection(dropverify, US"quit or conndrop"); +diff --git a/src/macros.h b/src/macros.h +index 0f93543ce..b3896b736 100644 +--- a/src/macros.h ++++ b/src/macros.h +@@ -154,6 +154,7 @@ enough to hold all the headers from a normal kind of message. */ + /* The size of the circular buffer that remembers recent SMTP commands */ + + #define SMTP_HBUFF_SIZE 20 ++#define SMTP_HBUFF_PREV(n) ((n) ? (n)-1 : SMTP_HBUFF_SIZE-1) + + /* The initial size of a big buffer for use in various places. It gets put + into big_buffer_size and in some circumstances increased. It should be at least +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 86f87eae1..4265d77b7 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -5322,10 +5322,10 @@ while (done <= 0) + } + if (f.smtp_in_pipelining_advertised && last_was_rcpt) + smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, +- smtp_names[smtp_connection_had[smtp_ch_index-1]]); ++ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]); + else + done = synprot_error(L_smtp_protocol_error, 503, NULL, +- smtp_connection_had[smtp_ch_index-1] == SCH_DATA ++ smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA + ? US"valid RCPT command must precede DATA" + : US"valid RCPT command must precede BDAT"); + +-- +2.30.2 + diff --git a/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch new file mode 100644 index 0000000..9e3d368 --- /dev/null +++ b/debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch @@ -0,0 +1,42 @@ +From 5987d0dfe88ee6081b72857bc8085c7d2afd53a3 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:17:32 -0800 +Subject: [PATCH 04/29] CVE-2020-28010: Heap out-of-bounds write in main() + +Based on Phil Pennock's commit 0f57feb4. +--- + src/exim.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/exim.c b/src/exim.c +index 83b5ef51f..a7dc48c4e 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -3664,6 +3664,9 @@ during readconf_main() some expansion takes place already. */ + /* Store the initial cwd before we change directories. Can be NULL if the + dir has already been unlinked. */ + initial_cwd = os_getcwd(NULL, 0); ++if (initial_cwd && strlen(CCS initial_cwd) >= BIG_BUFFER_SIZE) { ++ exim_fail("exim: initial cwd is far too long\n"); ++} + + /* checking: + -be[m] expansion test - +@@ -3950,11 +3953,9 @@ if ( (debug_selector & D_any || LOGGING(arguments)) + p += 13; + else + { +- Ustrncpy(p + 4, initial_cwd, big_buffer_size-5); +- p += 4 + Ustrlen(initial_cwd); +- /* in case p is near the end and we don't provide enough space for +- * string_format to be willing to write. */ +- *p = '\0'; ++ p += 4; ++ snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd); ++ p += strlen(CCS p); + } + + (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc); +-- +2.30.2 + diff --git a/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch new file mode 100644 index 0000000..086644b --- /dev/null +++ b/debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch @@ -0,0 +1,39 @@ +From 9970ba4d8b9477d98c722221b6b7b97f03104b9f Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:22:33 -0800 +Subject: [PATCH 05/29] CVE-2020-28011: Heap buffer overflow in queue_run() + +--- + src/queue.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +diff --git a/src/queue.c b/src/queue.c +index 92109ef92..41af5b85e 100644 +--- a/src/queue.c ++++ b/src/queue.c +@@ -416,12 +416,18 @@ if (!recurse) + p += sprintf(CS p, " -q%s", extras); + + if (deliver_selectstring) +- p += sprintf(CS p, " -R%s %s", f.deliver_selectstring_regex? "r" : "", +- deliver_selectstring); ++ { ++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -R%s %s", ++ f.deliver_selectstring_regex? "r" : "", deliver_selectstring); ++ p += strlen(CCS p); ++ } + + if (deliver_selectstring_sender) +- p += sprintf(CS p, " -S%s %s", f.deliver_selectstring_sender_regex? "r" : "", +- deliver_selectstring_sender); ++ { ++ snprintf(CS p, big_buffer_size - (p - big_buffer), " -S%s %s", ++ f.deliver_selectstring_sender_regex? "r" : "", deliver_selectstring_sender); ++ p += strlen(CCS p); ++ } + + log_detail = string_copy(big_buffer); + if (*queue_name) +-- +2.30.2 + diff --git a/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch new file mode 100644 index 0000000..6acdecc --- /dev/null +++ b/debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch @@ -0,0 +1,34 @@ +From 0f6c3d3f7efb5d66dabf69c36e06912d89ff96fc Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:28:28 -0800 +Subject: [PATCH 06/29] CVE-2020-28013: Heap buffer overflow in + parse_fix_phrase() + +Based on Phil Pennock's commit 8a50c88a. +--- + src/parse.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/src/parse.c b/src/parse.c +index 4b0efa0e1..e1e2e7358 100644 +--- a/src/parse.c ++++ b/src/parse.c +@@ -1149,9 +1149,12 @@ while (s < end) + { + if (ss >= end) ss--; + *t++ = '('; +- Ustrncpy(t, s, ss-s); +- t += ss-s; +- s = ss; ++ if (ss > s) ++ { ++ Ustrncpy(t, s, ss-s); ++ t += ss-s; ++ s = ss; ++ } + } + } + +-- +2.30.2 + diff --git a/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch new file mode 100644 index 0000000..53b4492 --- /dev/null +++ b/debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch @@ -0,0 +1,74 @@ +From 5fd6af5815401dc60e8fe4309258911aa41d3013 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:40:21 -0800 +Subject: [PATCH 07/29] Security: Refuse negative and large store allocations + +Based on Phil Pennock's commits b34d3046 and e6c1606a. +--- + src/store.c | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/store.c b/src/store.c +index b52799132..a2a80f631 100644 +--- a/src/store.c ++++ b/src/store.c +@@ -128,6 +128,12 @@ Returns: pointer to store (panic on malloc failure) + void * + store_get_3(int size, const char *filename, int linenumber) + { ++if (size < 0 || size > INT_MAX/2) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory allocation requested (%d bytes)", ++ size); ++ } + /* Round up the size to a multiple of the alignment. Although this looks a + messy statement, because "alignment" is a constant expression, the compiler can + do a reasonable job of optimizing, especially if the value of "alignment" is a +@@ -270,6 +276,13 @@ store_extend_3(void *ptr, int oldsize, int newsize, const char *filename, + int inc = newsize - oldsize; + int rounded_oldsize = oldsize; + ++if (oldsize < 0 || newsize < oldsize || newsize >= INT_MAX/2) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory extension requested (%d -> %d bytes)", ++ oldsize, newsize); ++ } ++ + if (rounded_oldsize % alignment != 0) + rounded_oldsize += alignment - (rounded_oldsize % alignment); + +@@ -508,7 +521,16 @@ store_newblock_3(void * block, int newsize, int len, + const char * filename, int linenumber) + { + BOOL release_ok = store_last_get[store_pool] == block; +-uschar * newtext = store_get(newsize); ++uschar * newtext; ++ ++if (len < 0 || len > newsize) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory extension requested (%d -> %d bytes)", ++ len, newsize); ++ } ++ ++newtext = store_get(newsize); + + memcpy(newtext, block, len); + if (release_ok) store_release_3(block, filename, linenumber); +@@ -539,6 +561,11 @@ store_malloc_3(int size, const char *filename, int linenumber) + { + void *yield; + ++if (size < 0 || size >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "bad memory allocation requested (%d bytes)", ++ size); ++ + if (size < 16) size = 16; + + if (!(yield = malloc((size_t)size))) +-- +2.30.2 + diff --git a/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch new file mode 100644 index 0000000..d621b70 --- /dev/null +++ b/debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch @@ -0,0 +1,49 @@ +From b5052a65ed1ba81269ac5a03b4505aa9d55ce084 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:46:55 -0800 +Subject: [PATCH 08/29] CVE-2020-28017: Integer overflow in + receive_add_recipient() + +Based on Phil Pennock's commit e3b441f7. +--- + src/receive.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/receive.c b/src/receive.c +index a0467e8c8..227ace084 100644 +--- a/src/receive.c ++++ b/src/receive.c +@@ -488,6 +488,12 @@ if (recipients_count >= recipients_list_max) + { + recipient_item *oldlist = recipients_list; + int oldmax = recipients_list_max; ++ ++ const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item); ++ if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max); ++ } + recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50; + recipients_list = store_get(recipients_list_max * sizeof(recipient_item)); + if (oldlist != NULL) +@@ -4070,7 +4076,7 @@ if (message_logs && !blackholed_by) + { + int fd; + uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US""); +- ++ + if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0 + && errno == ENOENT + ) +@@ -4229,7 +4235,7 @@ if(!smtp_reply) + if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by); + if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN, + "no immediate delivery: queued%s%s by %s", +- *queue_name ? " in " : "", *queue_name ? CS queue_name : "", ++ *queue_name ? " in " : "", *queue_name ? CS queue_name : "", + queued_by); + } + f.receive_call_bombout = FALSE; +-- +2.30.2 + diff --git a/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch new file mode 100644 index 0000000..1ace416 --- /dev/null +++ b/debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch @@ -0,0 +1,61 @@ +From f46455c848def70d686d7b164df75b27f8dae04d Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 19:53:43 -0800 +Subject: [PATCH 09/29] CVE-2020-28022: Heap out-of-bounds read and write in + extract_option() + +Based on Phil Pennock's commit c5017adf. +--- + src/smtp_in.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 4265d77b7..16c3a3e33 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -1984,29 +1984,35 @@ static BOOL + extract_option(uschar **name, uschar **value) + { + uschar *n; +-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; +-while (isspace(*v)) v--; ++uschar *v; ++if (Ustrlen(smtp_cmd_data) <= 0) return FALSE; ++v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; ++while (v > smtp_cmd_data && isspace(*v)) v--; + v[1] = 0; ++ + while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) + { + /* Take care to not stop at a space embedded in a quoted local-part */ +- +- if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1); ++ if (*v == '"') ++ { ++ do v--; while (v > smtp_cmd_data && *v != '"'); ++ if (v <= smtp_cmd_data) return FALSE; ++ } + v--; + } ++if (v <= smtp_cmd_data) return FALSE; + + n = v; + if (*v == '=') + { +- while(isalpha(n[-1])) n--; ++ while (n > smtp_cmd_data && isalpha(n[-1])) n--; + /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ +- if (!isspace(n[-1])) return FALSE; ++ if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE; + n[-1] = 0; + } + else + { + n++; +- if (v == smtp_cmd_data) return FALSE; + } + *v++ = 0; + *name = n; +-- +2.30.2 + diff --git a/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch new file mode 100644 index 0000000..3864de2 --- /dev/null +++ b/debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch @@ -0,0 +1,102 @@ +From 327f647a849c3974e7107b5386421b0058c15b29 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 21:17:31 -0800 +Subject: [PATCH 10/29] CVE-2020-28026: Line truncation and injection in + spool_read_header() + +This also fixes: + +2/ In src/spool_in.c: + + 462 while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1 + 463 && big_buffer[len-1] != '\n' + 464 ) + 465 { /* buffer not big enough for line; certs make this possible */ + 466 uschar * buf; + 467 if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR; + 468 buf = store_get_perm(big_buffer_size *= 2, FALSE); + 469 memcpy(buf, big_buffer, --len); + +The --len in memcpy() chops off a useful byte (we know for sure that +big_buffer[len-1] is not a '\n' because we entered the while loop). +--- + src/spool_in.c | 48 +++++++++++++++++++++++++++++++--------------- + 1 file changed, 33 insertions(+), 15 deletions(-) + +diff --git a/src/spool_in.c b/src/spool_in.c +index 2d349778c..dbbcf23ee 100644 +--- a/src/spool_in.c ++++ b/src/spool_in.c +@@ -307,6 +307,36 @@ dsn_ret = 0; + dsn_envid = NULL; + } + ++static void * ++fgets_big_buffer(FILE *fp) ++{ ++int len = 0; ++ ++big_buffer[0] = 0; ++if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) return NULL; ++ ++while ((len = Ustrlen(big_buffer)) == big_buffer_size-1 ++ && big_buffer[len-1] != '\n') ++ { ++ uschar *newbuffer; ++ int newsize; ++ ++ if (big_buffer_size >= BIG_BUFFER_SIZE * 4) return NULL; ++ newsize = big_buffer_size * 2; ++ newbuffer = store_get_perm(newsize); ++ memcpy(newbuffer, big_buffer, len); ++ ++ big_buffer = newbuffer; ++ big_buffer_size = newsize; ++ if (Ufgets(big_buffer + len, big_buffer_size - len, fp) == NULL) return NULL; ++ } ++ ++if (len <= 0 || big_buffer[len-1] != '\n') return NULL; ++return big_buffer; ++} ++ ++ ++ + + /************************************************* + * Read spool header file * +@@ -450,21 +480,9 @@ p = big_buffer + 2; + for (;;) + { + int len; +- if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR; ++ if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR; + if (big_buffer[0] != '-') break; +- while ( (len = Ustrlen(big_buffer)) == big_buffer_size-1 +- && big_buffer[len-1] != '\n' +- ) +- { /* buffer not big enough for line; certs make this possible */ +- uschar * buf; +- if (big_buffer_size >= BIG_BUFFER_SIZE*4) goto SPOOL_READ_ERROR; +- buf = store_get_perm(big_buffer_size *= 2); +- memcpy(buf, big_buffer, --len); +- big_buffer = buf; +- if (Ufgets(big_buffer+len, big_buffer_size-len, fp) == NULL) +- goto SPOOL_READ_ERROR; +- } +- big_buffer[len-1] = 0; ++ big_buffer[Ustrlen(big_buffer)-1] = 0; + + switch(big_buffer[1]) + { +@@ -724,7 +742,7 @@ DEBUG(D_deliver) + buffer. It contains the count of recipients which follow on separate lines. + Apply an arbitrary sanity check.*/ + +-if (Ufgets(big_buffer, big_buffer_size, fp) == NULL) goto SPOOL_READ_ERROR; ++if (fgets_big_buffer(fp) == NULL) goto SPOOL_READ_ERROR; + if (sscanf(CS big_buffer, "%d", &rcount) != 1 || rcount > 16384) + goto SPOOL_FORMAT_ERROR; + +-- +2.30.2 + diff --git a/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch new file mode 100644 index 0000000..1d2cc7e --- /dev/null +++ b/debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch @@ -0,0 +1,69 @@ +From ac8f49ef90e768a63ed3dca50e2b2c6e8d333bfd Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 21:26:53 -0800 +Subject: [PATCH 11/29] CVE-2020-28015+28021: New-line injection into spool + header file + +--- + src/spool_out.c | 21 +++++++++++++++++---- + 1 file changed, 17 insertions(+), 4 deletions(-) + +diff --git a/src/spool_out.c b/src/spool_out.c +index d55895202..9394393d5 100644 +--- a/src/spool_out.c ++++ b/src/spool_out.c +@@ -108,6 +108,18 @@ return fd; + * Write the header spool file * + *************************************************/ + ++static const uschar * ++zap_newlines(const uschar *s) ++{ ++uschar *z, *p; ++ ++if (Ustrchr(s, '\n') == NULL) return s; ++ ++p = z = string_copy(s); ++while ((p = Ustrchr(p, '\n')) != NULL) *p++ = ' '; ++return z; ++} ++ + /* Returns the size of the file for success; zero for failure. The file is + written under a temporary name, and then renamed. It's done this way so that it + works with re-writing the file on message deferral as well as for the initial +@@ -210,7 +222,7 @@ if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount); + if (authenticated_id) + fprintf(fp, "-auth_id %s\n", authenticated_id); + if (authenticated_sender) +- fprintf(fp, "-auth_sender %s\n", authenticated_sender); ++ fprintf(fp, "-auth_sender %s\n", zap_newlines(authenticated_sender)); + + if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n"); + if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n"); +@@ -283,19 +295,20 @@ fprintf(fp, "%d\n", recipients_count); + for (i = 0; i < recipients_count; i++) + { + recipient_item *r = recipients_list + i; ++ const uschar *address = zap_newlines(r->address); + + DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags); + + if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0) +- fprintf(fp, "%s\n", r->address); ++ fprintf(fp, "%s\n", address); + else + { +- uschar * errors_to = r->errors_to ? r->errors_to : US""; ++ const uschar * errors_to = r->errors_to ? zap_newlines(r->errors_to) : US""; + /* for DSN SUPPORT extend exim 4 spool in a compatible way by + adding new values upfront and add flag 0x02 */ + uschar * orcpt = r->orcpt ? r->orcpt : US""; + +- fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt), ++ fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", address, orcpt, Ustrlen(orcpt), + r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno); + } + +-- +2.30.2 + diff --git a/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch new file mode 100644 index 0000000..acde64a --- /dev/null +++ b/debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch @@ -0,0 +1,61 @@ +From 2cb94a53eb9186bd405120543301e1240b895d86 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 21:45:19 -0800 +Subject: [PATCH 12/29] CVE-2020-28009: Integer overflow in get_stdinput() + +--- + src/string.c | 23 ++++++++++++++++++++++- + 1 file changed, 22 insertions(+), 1 deletion(-) + +diff --git a/src/string.c b/src/string.c +index 3445f8a42..2cdbe7c75 100644 +--- a/src/string.c ++++ b/src/string.c +@@ -1147,6 +1147,18 @@ To try to keep things reasonable, we use increments whose size depends on the + existing length of the string. */ + + unsigned inc = oldsize < 4096 ? 127 : 1023; ++ ++if (g->ptr < 0 || g->ptr > g->size || g->size >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in gstring_grow (ptr %d size %d)", g->ptr, g->size); ++ ++if (count <= 0) return; ++ ++if (count >= INT_MAX/2 - g->ptr) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in gstring_grow (ptr %d count %d)", g->ptr, count); ++ ++ + g->size = ((p + count + inc) & ~inc) + 1; + + /* Try to extend an existing allocation. If the result of calling +@@ -1194,6 +1206,10 @@ string_catn(gstring * g, const uschar *s, int count) + { + int p; + ++if (count < 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in string_catn (count %d)", count); ++ + if (!g) + { + unsigned inc = count < 4096 ? 127 : 1023; +@@ -1201,8 +1217,13 @@ if (!g) + g = string_get(size); + } + ++if (g->ptr < 0 || g->ptr > g->size) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "internal error in string_catn (ptr %d size %d)", g->ptr, g->size); ++ + p = g->ptr; +-if (p + count >= g->size) ++ ++if (count >= g->size - p) + gstring_grow(g, p, count); + + /* Because we always specify the exact number of characters to copy, we can +-- +2.30.2 + diff --git a/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch new file mode 100644 index 0000000..4545ff3 --- /dev/null +++ b/debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch @@ -0,0 +1,41 @@ +From 7ea481a6471cdad3a674b767f808357b3c7fc721 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 21:49:30 -0800 +Subject: [PATCH 13/29] CVE-2020-28024: Heap buffer underflow in smtp_ungetc() + +--- + src/smtp_in.c | 3 +++ + src/tls.c | 3 +++ + 2 files changed, 6 insertions(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 16c3a3e33..bdcfde65f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -805,6 +805,9 @@ Returns: the character + int + smtp_ungetc(int ch) + { ++if (smtp_inptr <= smtp_inbuffer) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); ++ + *--smtp_inptr = ch; + return ch; + } +diff --git a/src/tls.c b/src/tls.c +index f79bc3193..2a316fe59 100644 +--- a/src/tls.c ++++ b/src/tls.c +@@ -151,6 +151,9 @@ Returns: the character + int + tls_ungetc(int ch) + { ++if (ssl_xfer_buffer_lwm <= 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in tls_ungetc"); ++ + ssl_xfer_buffer[--ssl_xfer_buffer_lwm] = ch; + return ch; + } +-- +2.30.2 + diff --git a/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch new file mode 100644 index 0000000..c9b2f65 --- /dev/null +++ b/debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch @@ -0,0 +1,31 @@ +From a1f36d86760def10138c1053eb3b1882b281fcd9 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 21:53:55 -0800 +Subject: [PATCH 14/29] CVE-2020-28012: Missing close-on-exec flag for + privileged pipe + +--- + src/rda.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/rda.c b/src/rda.c +index 13f570928..c27e073a3 100644 +--- a/src/rda.c ++++ b/src/rda.c +@@ -623,9 +623,13 @@ search_tidyup(); + if ((pid = fork()) == 0) + { + header_line *waslast = header_last; /* Save last header */ ++ int fd_flags = -1; + + fd = pfd[pipe_write]; + (void)close(pfd[pipe_read]); ++ ++ if ((fd_flags = fcntl(fd, F_GETFD)) == -1) goto bad; ++ if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1) goto bad; + exim_setugid(ugid->uid, ugid->gid, FALSE, rname); + + /* Addresses can get rewritten in filters; if we are not root or the exim +-- +2.30.2 + diff --git a/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch new file mode 100644 index 0000000..7b2607a --- /dev/null +++ b/debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch @@ -0,0 +1,41 @@ +From 0d5d8fc918c4b999a2d5b025d94e25e43680377d Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:00:31 -0800 +Subject: [PATCH 15/29] Security: Safeguard against relative names for msglog + files. + +Based on Heiko Schlittermann's commit 4f0ac4ad. This fixes: + +3/ In src/deliver.c: + + 333 static int + 334 open_msglog_file(uschar *filename, int mode, uschar **error) + 335 { + 336 if (Ustrstr(filename, US"/../")) + 337 log_write(0, LOG_MAIN|LOG_PANIC, + 338 "Attempt to open msglog file path with upward-traversal: '%s'\n", filename); + +Should this be LOG_PANIC_DIE instead of LOG_PANIC? Right now it will log +the /../ attempt but will open the file anyway. +--- + src/deliver.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/deliver.c b/src/deliver.c +index d4ed8af08..279672ce0 100644 +--- a/src/deliver.c ++++ b/src/deliver.c +@@ -331,6 +331,10 @@ open_msglog_file(uschar *filename, int mode, uschar **error) + { + int fd, i; + ++if (Ustrstr(filename, US"/../")) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, ++ "Attempt to open msglog file path with upward-traversal: '%s'", filename); ++ + for (i = 2; i > 0; i--) + { + fd = Uopen(filename, +-- +2.30.2 + diff --git a/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch new file mode 100644 index 0000000..f8bda54 --- /dev/null +++ b/debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch @@ -0,0 +1,39 @@ +From 56aadff97bc4e45e6a2ce25cfb9a98a4ae4bec79 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:05:37 -0800 +Subject: [PATCH 16/29] Security: Check overrun rcpt_count integer + +Based on Heiko Schlittermann's commit e5cb5e61. This fixes: + +4/ In src/smtp_in.c: + +4966 case RCPT_CMD: +4967 HAD(SCH_RCPT); +4968 rcpt_count++; +.... +5123 if (rcpt_count > recipients_max && recipients_max > 0) + +In theory this recipients_max check can be bypassed, because the int +rcpt_count can overflow (become negative). In practice this would either +consume too much memory or generate too much network traffic, but maybe +it should be fixed anyway. +--- + src/smtp_in.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index bdcfde65f..1a5fbfea3 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -4993,6 +4993,8 @@ while (done <= 0) + + case RCPT_CMD: + HAD(SCH_RCPT); ++ if (rcpt_count < 0 || rcpt_count >= INT_MAX/2) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count); + rcpt_count++; + was_rcpt = fl.rcpt_in_progress = TRUE; + +-- +2.30.2 + diff --git a/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch new file mode 100644 index 0000000..a9eee56 --- /dev/null +++ b/debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch @@ -0,0 +1,24 @@ +From 9b1ba71e66d18b1ae185e4d83788dc913f903a56 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:09:06 -0800 +Subject: [PATCH 17/29] Security: Always exit when LOG_PANIC_DIE is set + +--- + src/log.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/log.c b/src/log.c +index d08200044..c8313890e 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -894,6 +894,7 @@ if (!(flags & (LOG_MAIN|LOG_PANIC|LOG_REJECT))) + if (f.disable_logging) + { + DEBUG(D_any) debug_printf("log writing disabled\n"); ++ if ((flags & LOG_PANIC_DIE) == LOG_PANIC_DIE) exim_exit(EXIT_FAILURE, NULL); + return; + } + +-- +2.30.2 + diff --git a/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch new file mode 100644 index 0000000..47d67d2 --- /dev/null +++ b/debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch @@ -0,0 +1,47 @@ +From 28335a4704d8d615fd61e05ea6e435a4cd24e4df Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:13:18 -0800 +Subject: [PATCH 18/29] Security: Fix off-by-one in smtp transport (read + response) + +Based on Heiko Schlittermann's commit 1887a160. This fixes: + +1/ In src/transports/smtp.c: + +2281 int n = sizeof(sx->buffer); +2282 uschar * rsp = sx->buffer; +2283 +2284 if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) +2285 { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } + +This should probably be either: + +rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n - 1; + +or: + +rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; + +(not sure which) to avoid an off-by-one. +--- + src/transports/smtp.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/transports/smtp.c b/src/transports/smtp.c +index cc37e73f3..07b63a2aa 100644 +--- a/src/transports/smtp.c ++++ b/src/transports/smtp.c +@@ -2328,8 +2328,8 @@ goto SEND_QUIT; + int n = sizeof(sx->buffer); + uschar * rsp = sx->buffer; + +- if (sx->esmtp_sent && (n = Ustrlen(sx->buffer)) < sizeof(sx->buffer)/2) +- { rsp = sx->buffer + n + 1; n = sizeof(sx->buffer) - n; } ++ if (sx->esmtp_sent && (n = Ustrlen(sx->buffer) + 1) < sizeof(sx->buffer)/2) ++ { rsp = sx->buffer + n; n = sizeof(sx->buffer) - n; } + + if (smtp_write_command(sx, SCMD_FLUSH, "HELO %s\r\n", sx->helo_data) < 0) + goto SEND_FAILED; +-- +2.30.2 + diff --git a/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch new file mode 100644 index 0000000..a2b52fe --- /dev/null +++ b/debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch @@ -0,0 +1,59 @@ +From 031ae594f6e68511117f6d39ce238b0c5215d8d1 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:19:42 -0800 +Subject: [PATCH 19/29] Security: Avoid decrement of dkim_collect_input if + already at 0 + +Based on Heiko Schlittermann's commit bf2d6e58. This fixes: + +5/ receive_msg() calls dkim_exim_verify_finish(), which sets +dkim_collect_input to 0 and calls pdkim_feed_finish(), which calls +pdkim_header_complete(), which decreases dkim_collect_input to UINT_MAX, +which reactivates the DKIM code. + +As a result, pdkim_feed() is called again (through receive_getc at the +end of receive_msg()), but functions like pdkim_finish_bodyhash() and +exim_sha_finish() have already been called (in pdkim_feed_finish()). +This suggests a use-after-free. + +But it seems that a use-after-free would happen only with +EVP_DigestFinal() (in exim_sha_finish()), which does not seem to be +reachable via DKIM (no SHA3). But we checked OpenSSL only, not GnuTLS. + +Here is a proof of concept that triggers the bug (which came very close +to a security vulnerability): + +(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'BDAT 42 LAST'; date >&2; sleep 30; printf 'not a valid header line\r\n +DKIM-Signature:\r\nXXX'; sleep 30) | nc -n -v 192.168.56.102 25 + +(gdb) print &dkim_collect_input +$2 = (unsigned int *) 0x55e180386d90 +(gdb) watch *(unsigned int *) 0x55e180386d90 + +Hardware watchpoint 1: *(unsigned int *) 0x55e180386d90 +Old value = 0 +New value = 4294967295 +#0 0x000055e18031f805 in pdkim_header_complete (ctx=ctx@entry=0x55e181b9e8e0) at pdkim.c:1006 +#1 0x000055e18032106c in pdkim_feed_finish (ctx=0x55e181b9e8e0, return_signatures=0x55e180386d78 , err=err@entry=0x7ffe443e1d00) at pdkim.c:1490 +#2 0x000055e1802a3280 in dkim_exim_verify_finish () at dkim.c:328 +#3 0x000055e1802c9d1d in receive_msg (extract_recip=extract_recip@entry=0) at receive.c:3409 +--- + src/pdkim/pdkim.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index e203311da..e3233e9f0 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -1010,7 +1010,7 @@ else + last_sig->next = sig; + } + +- if (--dkim_collect_input == 0) ++ if (dkim_collect_input && --dkim_collect_input == 0) + { + ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s); + ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; +-- +2.30.2 + diff --git a/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch new file mode 100644 index 0000000..acf17d3 --- /dev/null +++ b/debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch @@ -0,0 +1,67 @@ +From 6b647c508aced6961f00e139f0337e2c8aba9eb7 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:24:13 -0800 +Subject: [PATCH 20/29] Security: Leave a clean smtp_out input buffer even in + case of read error + +Based on Heiko Schlittermann's commit 54895bc3. This fixes: + +7/ In src/smtp_out.c, read_response_line(), inblock->ptr is not updated +when -1 is returned. This does not seem to have bad consequences, but is +maybe not the intended behavior. +--- + src/smtp_out.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/src/smtp_out.c ++++ b/src/smtp_out.c +@@ -387,11 +387,11 @@ HDEBUG(D_transport|D_acl|D_v) + #ifdef SUPPORT_SOCKS + if (ob->socks_proxy) + { + int sock = socks_sock_connect(sc->host, sc->host_af, port, sc->interface, + sc->tblock, ob->connect_timeout); +- ++ + if (sock >= 0) + { + if (early_data && early_data->data && early_data->len) + if (send(sock, early_data->data, early_data->len, 0) < 0) + { +@@ -588,11 +588,11 @@ Arguments: + buffer where to put the line + size space available for the line + timelimit deadline for reading the lime, seconds past epoch + + Returns: length of a line that has been put in the buffer +- -1 otherwise, with errno set ++ -1 otherwise, with errno set, and inblock->ptr adjusted + */ + + static int + read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit) + { +@@ -629,10 +629,11 @@ for (;;) + *p++ = c; + if (--size < 4) + { + *p = 0; /* Leave malformed line for error message */ + errno = ERRNO_SMTPFORMAT; ++ inblock->ptr = ptr; + return -1; + } + } + + /* Need to read a new input packet. */ +@@ -654,10 +655,11 @@ for (;;) + } + + /* Get here if there has been some kind of recv() error; errno is set, but we + ensure that the result buffer is empty before returning. */ + ++inblock->ptr = inblock->ptrend = inblock->buffer; + *buffer = 0; + return -1; + } + + diff --git a/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch new file mode 100644 index 0000000..b723d0f --- /dev/null +++ b/debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch @@ -0,0 +1,89 @@ +From a4e1b7755ebbdee2689d40683ba69f09e38a8d7f Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:30:03 -0800 +Subject: [PATCH 21/29] Security: Avoid modification of constant data in dkim + handling + +Based on Heiko Schlittermann's commits f880c7f3 and c118c7f4. This +fixes: + +6/ In src/pdkim/pdkim.c, pdkim_update_ctx_bodyhash() is sometimes called +with a global orig_data and hence canon_data, and the following line can +therefore modify data that should be constant: + + 773 canon_data->len = b->bodylength - b->signed_body_bytes; + +For example, the following proof of concept sets lineending.len to 0 +(this should not be possible): + +(sleep 10; echo 'EHLO test'; sleep 3; echo 'MAIL FROM:<>'; sleep 3; echo 'RCPT TO:postmaster'; sleep 3; echo 'DATA'; date >&2; sleep 30; printf 'DKIM-Signature:a=rsa-sha1;c=simple/simple;l=0\r\n\r\n\r\nXXX\r\n.\r\n'; sleep 30) | nc -n -v 192.168.56.102 25 + +(gdb) print lineending +$1 = {data = 0x55e18035b2ad "\r\n", len = 2} +(gdb) print &lineending.len +$3 = (size_t *) 0x55e180385948 +(gdb) watch *(size_t *) 0x55e180385948 + +Hardware watchpoint 1: *(size_t *) 0x55e180385948 +Old value = 2 +New value = 0 +(gdb) print lineending +$5 = {data = 0x55e18035b2ad "\r\n", len = 0} +--- + src/pdkim/pdkim.c | 21 ++++++++++++--------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +diff --git a/src/pdkim/pdkim.c b/src/pdkim/pdkim.c +index e3233e9f0..512a3e352 100644 +--- a/src/pdkim/pdkim.c ++++ b/src/pdkim/pdkim.c +@@ -107,7 +107,7 @@ pdkim_combined_canon_entry pdkim_combined_canons[] = { + }; + + +-static blob lineending = {.data = US"\r\n", .len = 2}; ++static const blob lineending = {.data = US"\r\n", .len = 2}; + + /* -------------------------------------------------------------------------- */ + uschar * +@@ -719,9 +719,11 @@ return NULL; + If we have to relax the data for this sig, return our copy of it. */ + + static blob * +-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, blob * orig_data, blob * relaxed_data) ++pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data) + { +-blob * canon_data = orig_data; ++const blob * canon_data = orig_data; ++size_t left; ++ + /* Defaults to simple canon (no further treatment necessary) */ + + if (b->canon_method == PDKIM_CANON_RELAXED) +@@ -767,16 +769,17 @@ if (b->canon_method == PDKIM_CANON_RELAXED) + } + + /* Make sure we don't exceed the to-be-signed body length */ ++left = canon_data->len; + if ( b->bodylength >= 0 +- && b->signed_body_bytes + (unsigned long)canon_data->len > b->bodylength ++ && left > (unsigned long)b->bodylength - b->signed_body_bytes + ) +- canon_data->len = b->bodylength - b->signed_body_bytes; ++ left = (unsigned long)b->bodylength - b->signed_body_bytes; + +-if (canon_data->len > 0) ++if (left > 0) + { +- exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, canon_data->len); +- b->signed_body_bytes += canon_data->len; +- DEBUG(D_acl) pdkim_quoteprint(canon_data->data, canon_data->len); ++ exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left); ++ b->signed_body_bytes += left; ++ DEBUG(D_acl) pdkim_quoteprint(canon_data->data, left); + } + + return relaxed_data; +-- +2.30.2 + diff --git a/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch new file mode 100644 index 0000000..0d44293 --- /dev/null +++ b/debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch @@ -0,0 +1,135 @@ +From 1663ab541b37675dec5bcf235605568ff36ac65a Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Sun, 21 Feb 2021 22:36:10 -0800 +Subject: [PATCH 22/29] CVE-2020-28019: Failure to reset function pointer after + BDAT error + +Based on Phil Pennock's commits 4715403e and 151ffd72, and Jeremy +Harris's commits aa171254 and 9aceb5c2. +--- + src/globals.c | 1 + + src/globals.h | 1 + + src/smtp_in.c | 55 +++++++++++++++++++++++++++++++++++++++-------- + 3 files changed, 48 insertions(+), 9 deletions(-) + +diff --git a/src/globals.c b/src/globals.c +index b3362a34c..894b8487b 100644 +--- a/src/globals.c ++++ b/src/globals.c +@@ -247,6 +247,7 @@ struct global_flags f = + .authentication_local = FALSE, + + .background_daemon = TRUE, ++ .bdat_readers_wanted = FALSE, + + .chunking_offered = FALSE, + .config_changed = FALSE, +diff --git a/src/globals.h b/src/globals.h +index f71f104e2..58f7ae55f 100644 +--- a/src/globals.h ++++ b/src/globals.h +@@ -173,6 +173,7 @@ extern struct global_flags { + BOOL authentication_local :1; /* TRUE if non-smtp (implicit authentication) */ + + BOOL background_daemon :1; /* Set FALSE to keep in foreground */ ++ BOOL bdat_readers_wanted :1; /* BDAT-handling to be pushed on readfunc stack */ + + BOOL chunking_offered :1; + BOOL config_changed :1; /* True if -C used */ +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 1a5fbfea3..016c44c0f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -602,6 +602,10 @@ if (n > 0) + #endif + } + ++/* Forward declarations */ ++static inline void bdat_push_receive_functions(void); ++static inline void bdat_pop_receive_functions(void); ++ + + /* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the + previous BDAT chunk and getting new ones when we run out. Uses the +@@ -634,9 +638,7 @@ for(;;) + if (chunking_data_left > 0) + return lwr_receive_getc(chunking_data_left--); + +- receive_getc = lwr_receive_getc; +- receive_getbuf = lwr_receive_getbuf; +- receive_ungetc = lwr_receive_ungetc; ++ bdat_pop_receive_functions(); + #ifndef DISABLE_DKIM + dkim_save = dkim_collect_input; + dkim_collect_input = 0; +@@ -740,9 +742,7 @@ next_cmd: + goto repeat_until_rset; + } + +- receive_getc = bdat_getc; +- receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */ +- receive_ungetc = bdat_ungetc; ++ bdat_push_receive_functions(); + #ifndef DISABLE_DKIM + dkim_collect_input = dkim_save; + #endif +@@ -775,9 +775,7 @@ while (chunking_data_left) + if (!bdat_getbuf(&n)) break; + } + +-receive_getc = lwr_receive_getc; +-receive_getbuf = lwr_receive_getbuf; +-receive_ungetc = lwr_receive_ungetc; ++bdat_pop_receive_functions(); + + if (chunking_state != CHUNKING_LAST) + { +@@ -787,6 +785,45 @@ if (chunking_state != CHUNKING_LAST) + } + + ++static inline void ++bdat_push_receive_functions(void) ++{ ++/* push the current receive_* function on the "stack", and ++replace them by bdat_getc(), which in turn will use the lwr_receive_* ++functions to do the dirty work. */ ++if (lwr_receive_getc == NULL) ++ { ++ lwr_receive_getc = receive_getc; ++ lwr_receive_getbuf = receive_getbuf; ++ lwr_receive_ungetc = receive_ungetc; ++ } ++else ++ { ++ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n"); ++ } ++ ++receive_getc = bdat_getc; ++receive_getbuf = bdat_getbuf; ++receive_ungetc = bdat_ungetc; ++} ++ ++static inline void ++bdat_pop_receive_functions(void) ++{ ++if (lwr_receive_getc == NULL) ++ { ++ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n"); ++ return; ++ } ++ ++receive_getc = lwr_receive_getc; ++receive_getbuf = lwr_receive_getbuf; ++receive_ungetc = lwr_receive_ungetc; ++ ++lwr_receive_getc = NULL; ++lwr_receive_getbuf = NULL; ++lwr_receive_ungetc = NULL; ++} + + + /************************************************* +-- +2.30.2 + diff --git a/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch new file mode 100644 index 0000000..211abe3 --- /dev/null +++ b/debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch @@ -0,0 +1,542 @@ +From 99ae249e9857e80ff4d65b2388bc68c624dcb739 Mon Sep 17 00:00:00 2001 +From: Qualys Security Advisory +Date: Tue, 23 Feb 2021 08:33:03 -0800 +Subject: [PATCH 23/29] CVE-2020-28007: Link attack in Exim's log directory + +We patch this vulnerability by opening (instead of just creating) the +log file in an unprivileged (exim) child process, and by passing this +file descriptor back to the privileged (root) parent process. The two +functions log_send_fd() and log_recv_fd() are inspired by OpenSSH's +functions mm_send_fd() and mm_receive_fd(); thanks! + +This patch also fixes: + +- a NULL-pointer dereference in usr1_handler() (this signal handler is + installed before process_log_path is initialized); + +- a file-descriptor leak in dmarc_write_history_file() (two return paths + did not close history_file_fd). + +Note: the use of log_open_as_exim() in dmarc_write_history_file() should +be fine because the documentation explicitly states "Make sure the +directory of this file is writable by the user exim runs as." + +(cherry picked from commit 2502cc41d1d92c1413eca6a4ba035c21162662bd) +(cherry picked from commit 93e9a18fbf09deb59bd133986f4c89aeb2d2d86a) +--- + src/dmarc.c | 179 ++++++++++++++++++------------------ + src/exim.c | 14 +-- + src/functions.h | 3 +- + src/log.c | 214 ++++++++++++++++++++++++++++---------------- + test/stderr/0397 | 6 +- + 5 files changed, 234 insertions(+), 182 deletions(-) + +diff --git a/src/dmarc.c b/src/dmarc.c +index f29f7eba6..c5e01c7ee 100644 +--- a/src/dmarc.c ++++ b/src/dmarc.c +@@ -204,6 +204,97 @@ if ( dmarc_policy == DMARC_POLICY_REJECT && action == DMARC_RESULT_REJECT + } + } + ++ ++static int ++dmarc_write_history_file() ++{ ++int tmp_ans; ++u_char **rua; /* aggregate report addressees */ ++uschar *history_buffer = NULL; ++ ++if (!dmarc_history_file) ++ { ++ DEBUG(D_receive) debug_printf("DMARC history file not set\n"); ++ return DMARC_HIST_DISABLED; ++ } ++ ++/* Generate the contents of the history file */ ++history_buffer = string_sprintf( ++ "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n", ++ message_id, primary_hostname, time(NULL), sender_host_address, ++ header_from_sender, expand_string(US"$sender_address_domain")); ++ ++if (spf_response) ++ history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result); ++ /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */ ++ ++history_buffer = string_sprintf( ++ "%s%spdomain %s\npolicy %d\n", ++ history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy); ++ ++if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1))) ++ for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++) ++ history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]); ++else ++ history_buffer = string_sprintf("%srua -\n", history_buffer); ++ ++opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans); ++ ++opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); ++history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans); ++ ++history_buffer = string_sprintf( ++ "%salign_dkim %d\nalign_spf %d\naction %d\n", ++ history_buffer, da, sa, action); ++ ++/* Write the contents to the history file */ ++DEBUG(D_receive) ++ debug_printf("DMARC logging history data for opendmarc reporting%s\n", ++ (host_checking || f.running_in_test_harness) ? " (not really)" : ""); ++if (host_checking || f.running_in_test_harness) ++ { ++ DEBUG(D_receive) ++ debug_printf("DMARC history data for debugging:\n%s", history_buffer); ++ } ++else ++ { ++ ssize_t written_len; ++ const int history_file_fd = log_open_as_exim(dmarc_history_file); ++ ++ if (history_file_fd < 0) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", ++ dmarc_history_file); ++ return DMARC_HIST_FILE_ERR; ++ } ++ ++ written_len = write_to_fd_buf(history_file_fd, ++ history_buffer, ++ Ustrlen(history_buffer)); ++ ++ (void)close(history_file_fd); ++ ++ if (written_len <= 0) ++ { ++ log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s", ++ dmarc_history_file); ++ return DMARC_HIST_WRITE_ERR; ++ } ++ } ++return DMARC_HIST_OK; ++} ++ ++ + /* dmarc_process adds the envelope sender address to the existing + context (if any), retrieves the result, sets up expansion + strings and evaluates the condition outcome. */ +@@ -486,94 +577,6 @@ if (!f.dmarc_disable_verify) + return OK; + } + +-static int +-dmarc_write_history_file() +-{ +-int history_file_fd; +-ssize_t written_len; +-int tmp_ans; +-u_char **rua; /* aggregate report addressees */ +-uschar *history_buffer = NULL; +- +-if (!dmarc_history_file) +- { +- DEBUG(D_receive) debug_printf("DMARC history file not set\n"); +- return DMARC_HIST_DISABLED; +- } +-history_file_fd = log_create(dmarc_history_file); +- +-if (history_file_fd < 0) +- { +- log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s", +- dmarc_history_file); +- return DMARC_HIST_FILE_ERR; +- } +- +-/* Generate the contents of the history file */ +-history_buffer = string_sprintf( +- "job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n", +- message_id, primary_hostname, time(NULL), sender_host_address, +- header_from_sender, expand_string(US"$sender_address_domain")); +- +-if (spf_response) +- history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result); +- /* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */ +- +-history_buffer = string_sprintf( +- "%s%spdomain %s\npolicy %d\n", +- history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy); +- +-if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1))) +- for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++) +- history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]); +-else +- history_buffer = string_sprintf("%srua -\n", history_buffer); +- +-opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans); +- +-opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans); +-history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans); +- +-history_buffer = string_sprintf( +- "%salign_dkim %d\nalign_spf %d\naction %d\n", +- history_buffer, da, sa, action); +- +-/* Write the contents to the history file */ +-DEBUG(D_receive) +- debug_printf("DMARC logging history data for opendmarc reporting%s\n", +- (host_checking || f.running_in_test_harness) ? " (not really)" : ""); +-if (host_checking || f.running_in_test_harness) +- { +- DEBUG(D_receive) +- debug_printf("DMARC history data for debugging:\n%s", history_buffer); +- } +-else +- { +- written_len = write_to_fd_buf(history_file_fd, +- history_buffer, +- Ustrlen(history_buffer)); +- if (written_len == 0) +- { +- log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s", +- dmarc_history_file); +- return DMARC_HIST_WRITE_ERR; +- } +- (void)close(history_file_fd); +- } +-return DMARC_HIST_OK; +-} +- +- + uschar * + dmarc_exim_expand_query(int what) + { +diff --git a/src/exim.c b/src/exim.c +index a7dc48c4e..f0a168983 100644 +--- a/src/exim.c ++++ b/src/exim.c +@@ -227,18 +227,8 @@ int fd; + + os_restarting_signal(sig, usr1_handler); + +-if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0) +- { +- /* If we are already running as the Exim user, try to create it in the +- current process (assuming spool_directory exists). Otherwise, if we are +- root, do the creation in an exim:exim subprocess. */ +- +- int euid = geteuid(); +- if (euid == exim_uid) +- fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); +- else if (euid == root_uid) +- fd = log_create_as_exim(process_log_path); +- } ++if (!process_log_path) return; ++fd = log_open_as_exim(process_log_path); + + /* If we are neither exim nor root, or if we failed to create the log file, + give up. There is not much useful we can do with errors, since we don't want +diff --git a/src/functions.h b/src/functions.h +index cab7a7363..366cb2f26 100644 +--- a/src/functions.h ++++ b/src/functions.h +@@ -281,8 +281,7 @@ extern int ip_streamsocket(const uschar *, uschar **, int); + extern int ipv6_nmtoa(int *, uschar *); + + extern uschar *local_part_quote(uschar *); +-extern int log_create(uschar *); +-extern int log_create_as_exim(uschar *); ++extern int log_open_as_exim(uschar *); + extern void log_close_all(void); + + extern macro_item * macro_create(const uschar *, const uschar *, BOOL); +diff --git a/src/log.c b/src/log.c +index c8313890e..15c88c13e 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -264,14 +264,19 @@ overwrite it temporarily if it is necessary to create the directory. + Returns: a file descriptor, or < 0 on failure (errno set) + */ + +-int +-log_create(uschar *name) ++static int ++log_open_already_exim(uschar * const name) + { +-int fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); ++int fd = -1; ++const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; ++ ++if (geteuid() != exim_uid) ++ { ++ errno = EACCES; ++ return -1; ++ } ++ ++fd = Uopen(name, flags, LOG_MODE); + + /* If creation failed, attempt to build a log directory in case that is the + problem. */ +@@ -285,11 +290,7 @@ if (fd < 0 && errno == ENOENT) + DEBUG(D_any) debug_printf("%s log directory %s\n", + created ? "created" : "failed to create", name); + *lastslash = '/'; +- if (created) fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_CREAT|O_APPEND|O_WRONLY, LOG_MODE); ++ if (created) fd = Uopen(name, flags, LOG_MODE); + } + + return fd; +@@ -297,6 +298,81 @@ return fd; + + + ++/* Inspired by OpenSSH's mm_send_fd(). Thanks! */ ++ ++static int ++log_send_fd(const int sock, const int fd) ++{ ++struct msghdr msg; ++union { ++ struct cmsghdr hdr; ++ char buf[CMSG_SPACE(sizeof(int))]; ++} cmsgbuf; ++struct cmsghdr *cmsg; ++struct iovec vec; ++char ch = 'A'; ++ssize_t n; ++ ++memset(&msg, 0, sizeof(msg)); ++memset(&cmsgbuf, 0, sizeof(cmsgbuf)); ++msg.msg_control = &cmsgbuf.buf; ++msg.msg_controllen = sizeof(cmsgbuf.buf); ++ ++cmsg = CMSG_FIRSTHDR(&msg); ++cmsg->cmsg_len = CMSG_LEN(sizeof(int)); ++cmsg->cmsg_level = SOL_SOCKET; ++cmsg->cmsg_type = SCM_RIGHTS; ++*(int *)CMSG_DATA(cmsg) = fd; ++ ++vec.iov_base = &ch; ++vec.iov_len = 1; ++msg.msg_iov = &vec; ++msg.msg_iovlen = 1; ++ ++while ((n = sendmsg(sock, &msg, 0)) == -1 && errno == EINTR); ++if (n != 1) return -1; ++return 0; ++} ++ ++/* Inspired by OpenSSH's mm_receive_fd(). Thanks! */ ++ ++static int ++log_recv_fd(const int sock) ++{ ++struct msghdr msg; ++union { ++ struct cmsghdr hdr; ++ char buf[CMSG_SPACE(sizeof(int))]; ++} cmsgbuf; ++struct cmsghdr *cmsg; ++struct iovec vec; ++ssize_t n; ++char ch = '\0'; ++int fd = -1; ++ ++memset(&msg, 0, sizeof(msg)); ++vec.iov_base = &ch; ++vec.iov_len = 1; ++msg.msg_iov = &vec; ++msg.msg_iovlen = 1; ++ ++memset(&cmsgbuf, 0, sizeof(cmsgbuf)); ++msg.msg_control = &cmsgbuf.buf; ++msg.msg_controllen = sizeof(cmsgbuf.buf); ++ ++while ((n = recvmsg(sock, &msg, 0)) == -1 && errno == EINTR); ++if (n != 1 || ch != 'A') return -1; ++ ++cmsg = CMSG_FIRSTHDR(&msg); ++if (cmsg == NULL) return -1; ++if (cmsg->cmsg_type != SCM_RIGHTS) return -1; ++fd = *(const int *)CMSG_DATA(cmsg); ++if (fd < 0) return -1; ++return fd; ++} ++ ++ ++ + /************************************************* + * Create a log file as the exim user * + *************************************************/ +@@ -312,41 +388,60 @@ Returns: a file descriptor, or < 0 on failure (errno set) + */ + + int +-log_create_as_exim(uschar *name) ++log_open_as_exim(uschar * const name) + { +-pid_t pid = fork(); +-int status = 1; + int fd = -1; ++const uid_t euid = geteuid(); + +-/* In the subprocess, change uid/gid and do the creation. Return 0 from the +-subprocess on success. If we don't check for setuid failures, then the file +-can be created as root, so vulnerabilities which cause setuid to fail mean +-that the Exim user can use symlinks to cause a file to be opened/created as +-root. We always open for append, so can't nuke existing content but it would +-still be Rather Bad. */ +- +-if (pid == 0) ++if (euid == exim_uid) + { +- if (setgid(exim_gid) < 0) +- die(US"exim: setgid for log-file creation failed, aborting", +- US"Unexpected log failure, please try later"); +- if (setuid(exim_uid) < 0) +- die(US"exim: setuid for log-file creation failed, aborting", +- US"Unexpected log failure, please try later"); +- _exit((log_create(name) < 0)? 1 : 0); ++ fd = log_open_already_exim(name); + } ++else if (euid == root_uid) ++ { ++ int sock[2]; ++ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == 0) ++ { ++ const pid_t pid = fork(); ++ if (pid == 0) ++ { ++ (void)close(sock[0]); ++ if (setgroups(1, &exim_gid) != 0) _exit(EXIT_FAILURE); ++ if (setgid(exim_gid) != 0) _exit(EXIT_FAILURE); ++ if (setuid(exim_uid) != 0) _exit(EXIT_FAILURE); ++ ++ if (getuid() != exim_uid || geteuid() != exim_uid) _exit(EXIT_FAILURE); ++ if (getgid() != exim_gid || getegid() != exim_gid) _exit(EXIT_FAILURE); ++ ++ fd = log_open_already_exim(name); ++ if (fd < 0) _exit(EXIT_FAILURE); ++ if (log_send_fd(sock[1], fd) != 0) _exit(EXIT_FAILURE); ++ (void)close(sock[1]); ++ _exit(EXIT_SUCCESS); ++ } + +-/* If we created a subprocess, wait for it. If it succeeded, try the open. */ +- +-while (pid > 0 && waitpid(pid, &status, 0) != pid); +-if (status == 0) fd = Uopen(name, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_APPEND|O_WRONLY, LOG_MODE); ++ (void)close(sock[1]); ++ if (pid > 0) ++ { ++ fd = log_recv_fd(sock[0]); ++ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR); ++ } ++ (void)close(sock[0]); ++ } ++ } + +-/* If we failed to create a subprocess, we are in a bad way. We return +-with fd still < 0, and errno set, letting the caller handle the error. */ ++if (fd >= 0) ++ { ++ int flags; ++ flags = fcntl(fd, F_GETFD); ++ if (flags != -1) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC); ++ flags = fcntl(fd, F_GETFL); ++ if (flags != -1) (void)fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); ++ } ++else ++ { ++ errno = EACCES; ++ } + + return fd; + } +@@ -459,52 +554,17 @@ if (!ok) + die(US"exim: log file path too long: aborting", + US"Logging failure; please try later"); + +-/* We now have the file name. Try to open an existing file. After a successful +-open, arrange for automatic closure on exec(), and then return. */ ++/* We now have the file name. After a successful open, return. */ + +-*fd = Uopen(buffer, +-#ifdef O_CLOEXEC +- O_CLOEXEC | +-#endif +- O_APPEND|O_WRONLY, LOG_MODE); ++*fd = log_open_as_exim(buffer); + + if (*fd >= 0) + { +-#ifndef O_CLOEXEC +- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); +-#endif + return; + } + +-/* Open was not successful: try creating the file. If this is a root process, +-we must do the creating in a subprocess set to exim:exim in order to ensure +-that the file is created with the right ownership. Otherwise, there can be a +-race if another Exim process is trying to write to the log at the same time. +-The use of SIGUSR1 by the exiwhat utility can provoke a lot of simultaneous +-writing. */ +- + euid = geteuid(); + +-/* If we are already running as the Exim user (even if that user is root), +-we can go ahead and create in the current process. */ +- +-if (euid == exim_uid) *fd = log_create(buffer); +- +-/* Otherwise, if we are root, do the creation in an exim:exim subprocess. If we +-are neither exim nor root, creation is not attempted. */ +- +-else if (euid == root_uid) *fd = log_create_as_exim(buffer); +- +-/* If we now have an open file, set the close-on-exec flag and return. */ +- +-if (*fd >= 0) +- { +-#ifndef O_CLOEXEC +- (void)fcntl(*fd, F_SETFD, fcntl(*fd, F_GETFD) | FD_CLOEXEC); +-#endif +- return; +- } +- + /* Creation failed. There are some circumstances in which we get here when + the effective uid is not root or exim, which is the problem. (For example, a + non-setuid binary with log_arguments set, called in certain ways.) Rather than +-- +2.30.2 + diff --git a/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch new file mode 100644 index 0000000..2bda99c --- /dev/null +++ b/debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch @@ -0,0 +1,205 @@ +From 5fec3406547fd1e46838a76f000102beb6bfe468 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Sun, 14 Mar 2021 12:16:57 +0100 +Subject: [PATCH 24/29] CVE-2020-28008: Assorted attacks in Exim's spool + directory + +We patch dbfn_open() by introducing two functions priv_drop_temp() and +priv_restore() (inspired by OpenSSH's functions temporarily_use_uid() +and restore_uid()), which temporarily drop and restore root privileges +thanks to seteuid(). This goes against Exim's developers' wishes ("Exim +(the project) doesn't trust seteuid to work reliably") but, to the best +of our knowledge, seteuid() works everywhere and is the only way to +securely fix dbfn_open(). + +(cherry picked from commit 18da59151dbafa89be61c63580bdb295db36e374) +(cherry picked from commit b05dc3573f4cd476482374b0ac0393153d344338) +--- + doc/ChangeLog | 6 +++ + src/dbfn.c | 110 +++++++++++++++++++++++++----------------- + test/stderr/0275 | 2 +- + test/stderr/0278 | 2 +- + test/stderr/0386 | 2 +- + test/stderr/0388 | 2 +- + test/stderr/0402 | 2 +- + test/stderr/0403 | 2 +- + test/stderr/0404 | 2 +- + test/stderr/0408 | 2 +- + test/stderr/0487 | 2 +- + 11 files changed, 80 insertions(+), 54 deletions(-) + +diff --git a/doc/ChangeLog b/doc/ChangeLog +index 5741fb212..b32347c5b 100644 +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -14,6 +14,12 @@ JH/42 Bug 2545: Fix CHUNKING for all RCPT commands rejected. Previously we + + Exim version 4.92.2 + ------------------- ++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim ++ runtime user. ++ ++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of ++ the Exim runtime user are allowed to create files. ++ + + HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) + +diff --git a/src/dbfn.c b/src/dbfn.c +index 336cfe73e..902756508 100644 +--- a/src/dbfn.c ++++ b/src/dbfn.c +@@ -59,6 +59,66 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg); + + + ++static enum { ++ PRIV_DROPPING, PRIV_DROPPED, ++ PRIV_RESTORING, PRIV_RESTORED ++} priv_state = PRIV_RESTORED; ++ ++static uid_t priv_euid; ++static gid_t priv_egid; ++static gid_t priv_groups[EXIM_GROUPLIST_SIZE + 1]; ++static int priv_ngroups; ++ ++/* Inspired by OpenSSH's temporarily_use_uid(). Thanks! */ ++ ++static void ++priv_drop_temp(const uid_t temp_uid, const gid_t temp_gid) ++{ ++if (priv_state != PRIV_RESTORED) _exit(EXIT_FAILURE); ++priv_state = PRIV_DROPPING; ++ ++priv_euid = geteuid(); ++if (priv_euid == root_uid) ++ { ++ priv_egid = getegid(); ++ priv_ngroups = getgroups(nelem(priv_groups), priv_groups); ++ if (priv_ngroups < 0) _exit(EXIT_FAILURE); ++ ++ if (priv_ngroups > 0 && setgroups(1, &temp_gid) != 0) _exit(EXIT_FAILURE); ++ if (setegid(temp_gid) != 0) _exit(EXIT_FAILURE); ++ if (seteuid(temp_uid) != 0) _exit(EXIT_FAILURE); ++ ++ if (geteuid() != temp_uid) _exit(EXIT_FAILURE); ++ if (getegid() != temp_gid) _exit(EXIT_FAILURE); ++ } ++ ++priv_state = PRIV_DROPPED; ++} ++ ++/* Inspired by OpenSSH's restore_uid(). Thanks! */ ++ ++static void ++priv_restore(void) ++{ ++if (priv_state != PRIV_DROPPED) _exit(EXIT_FAILURE); ++priv_state = PRIV_RESTORING; ++ ++if (priv_euid == root_uid) ++ { ++ if (seteuid(priv_euid) != 0) _exit(EXIT_FAILURE); ++ if (setegid(priv_egid) != 0) _exit(EXIT_FAILURE); ++ if (priv_ngroups > 0 && setgroups(priv_ngroups, priv_groups) != 0) _exit(EXIT_FAILURE); ++ ++ if (geteuid() != priv_euid) _exit(EXIT_FAILURE); ++ if (getegid() != priv_egid) _exit(EXIT_FAILURE); ++ } ++ ++priv_state = PRIV_RESTORED; ++} ++ ++ ++ ++ + /************************************************* + * Open and lock a database file * + *************************************************/ +@@ -89,7 +149,6 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof) + { + int rc, save_errno; + BOOL read_only = flags == O_RDONLY; +-BOOL created = FALSE; + flock_t lock_data; + uschar dirname[256], filename[256]; + +@@ -111,12 +170,13 @@ exists, there is no error. */ + snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory); + snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name); + ++priv_drop_temp(exim_uid, exim_gid); + if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0) + { +- created = TRUE; + (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE); + dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE); + } ++priv_restore(); + + if (dbblock->lockfd < 0) + { +@@ -165,57 +225,17 @@ it easy to pin this down, there are now debug statements on either side of the + open call. */ + + snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name); +-EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); + ++priv_drop_temp(exim_uid, exim_gid); ++EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr)); + if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR) + { + DEBUG(D_hints_lookup) + debug_printf_indent("%s appears not to exist: trying to create\n", filename); +- created = TRUE; + EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr)); + } +- + save_errno = errno; +- +-/* If we are running as root and this is the first access to the database, its +-files will be owned by root. We want them to be owned by exim. We detect this +-situation by noting above when we had to create the lock file or the database +-itself. Because the different dbm libraries use different extensions for their +-files, I don't know of any easier way of arranging this than scanning the +-directory for files with the appropriate base name. At least this deals with +-the lock file at the same time. Also, the directory will typically have only +-half a dozen files, so the scan will be quick. +- +-This code is placed here, before the test for successful opening, because there +-was a case when a file was created, but the DBM library still returned NULL +-because of some problem. It also sorts out the lock file if that was created +-but creation of the database file failed. */ +- +-if (created && geteuid() == root_uid) +- { +- DIR *dd; +- struct dirent *ent; +- uschar *lastname = Ustrrchr(filename, '/') + 1; +- int namelen = Ustrlen(name); +- +- *lastname = 0; +- dd = opendir(CS filename); +- +- while ((ent = readdir(dd))) +- if (Ustrncmp(ent->d_name, name, namelen) == 0) +- { +- struct stat statbuf; +- Ustrcpy(lastname, ent->d_name); +- if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid) +- { +- DEBUG(D_hints_lookup) debug_printf_indent("ensuring %s is owned by exim\n", filename); +- if (Uchown(filename, exim_uid, exim_gid)) +- DEBUG(D_hints_lookup) debug_printf_indent("failed setting %s to owned by exim\n", filename); +- } +- } +- +- closedir(dd); +- } ++priv_restore(); + + /* If the open has failed, return NULL, leaving errno set. If lof is TRUE, + log the event - also for debugging - but debug only if the file just doesn't +-- +2.30.2 + diff --git a/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch new file mode 100644 index 0000000..3e73ae1 --- /dev/null +++ b/debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch @@ -0,0 +1,303 @@ +From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Thu, 25 Mar 2021 22:48:09 +0100 +Subject: [PATCH 26/29] CVE-2020-28014, CVE-2021-27216: Arbitrary PID file + creation, clobbering, and deletion + +Arbitrary PID file creation, clobbering, and deletion. +Patch provided by Qualys. + +(cherry picked from commit 974f32939a922512b27d9f0a8a1cb5dec60e7d37) +(cherry picked from commit 43c6f0b83200b7082353c50187ef75de3704580a) +--- + doc/ChangeLog | 5 + + src/daemon.c | 212 ++++++++++++++++++++++++++++++++++++++---- + src/exim.c | 12 ++- + test/stderr/0433 | 24 +++++ + 4 files changed, 232 insertions(+), 21 deletions(-) + +--- a/doc/ChangeLog ++++ b/doc/ChangeLog +@@ -10,13 +10,18 @@ QS/02 PID file creation/deletion: only p + runtime user. + + QS/01 Creation of (database) files in $spool_dir: only uid=0 or the euid of + the Exim runtime user are allowed to create files. + ++QS/01 Creation of (database) files in $spool_dir: only uid=0 or the uid of ++ the Exim runtime user are allowed to create files. + + HS/01 Handle trailing backslash gracefully. (CVE-2019-15846) + ++QS/02 PID file creation/deletion: only possible if uid=0 or uid is the Exim ++ runtime user. ++ + + Since version 4.92 + ------------------ + + JH/06 Fix buggy handling of autoreply bounce_return_size_limit, and a possible +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -886,10 +886,198 @@ while ((pid = waitpid(-1, &status, WNOHA + } + } + } + + ++static void ++set_pid_file_path(void) ++{ ++if (override_pid_file_path) ++ pid_file_path = override_pid_file_path; ++ ++if (!*pid_file_path) ++ pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); ++ ++if (pid_file_path[0] != '/') ++ log_write(0, LOG_PANIC_DIE, "pid file path %s must be absolute\n", pid_file_path); ++} ++ ++ ++enum pid_op { PID_WRITE, PID_CHECK, PID_DELETE }; ++ ++/* Do various pid file operations as safe as possible. Ideally we'd just ++drop the privileges for creation of the pid file and not care at all about removal of ++the file. FIXME. ++Returns: true on success, false + errno==EACCES otherwise ++*/ ++static BOOL ++operate_on_pid_file(const enum pid_op operation, const pid_t pid) ++{ ++char pid_line[sizeof(int) * 3 + 2]; ++const int pid_len = snprintf(pid_line, sizeof(pid_line), "%d\n", (int)pid); ++BOOL lines_match = FALSE; ++ ++char * path = NULL; ++char * base = NULL; ++char * dir = NULL; ++ ++const int dir_flags = O_RDONLY | O_NONBLOCK; ++const int base_flags = O_NOFOLLOW | O_NONBLOCK; ++const mode_t base_mode = 0644; ++struct stat sb; ++ ++int cwd_fd = -1; ++int dir_fd = -1; ++int base_fd = -1; ++ ++BOOL success = FALSE; ++errno = EACCES; ++ ++set_pid_file_path(); ++if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) goto cleanup; ++if (pid_len < 2 || pid_len >= (int)sizeof(pid_line)) goto cleanup; ++ ++path = CS string_copy(pid_file_path); ++if ((base = Ustrrchr(path, '/')) == NULL) /* should not happen, but who knows */ ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "pid file path \"%s\" does not contain a '/'", pid_file_path); ++ ++dir = (base != path) ? path : "/"; ++*base++ = '\0'; ++ ++if (!dir || !*dir || *dir != '/') goto cleanup; ++if (!base || !*base || strchr(base, '/') != NULL) goto cleanup; ++ ++cwd_fd = open(".", dir_flags); ++if (cwd_fd < 0 || fstat(cwd_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++dir_fd = open(dir, dir_flags); ++if (dir_fd < 0 || fstat(dir_fd, &sb) != 0 || !S_ISDIR(sb.st_mode)) goto cleanup; ++ ++/* emulate openat */ ++if (fchdir(dir_fd) != 0) goto cleanup; ++base_fd = open(base, O_RDONLY | base_flags); ++if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ ++if (base_fd >= 0) ++ { ++ char line[sizeof(pid_line)]; ++ ssize_t len = -1; ++ ++ if (fstat(base_fd, &sb) != 0 || !S_ISREG(sb.st_mode)) goto cleanup; ++ if ((sb.st_mode & 07777) != base_mode || sb.st_nlink != 1) goto cleanup; ++ if (sb.st_size < 2 || sb.st_size >= (off_t)sizeof(line)) goto cleanup; ++ ++ len = read(base_fd, line, sizeof(line)); ++ if (len != (ssize_t)sb.st_size) goto cleanup; ++ line[len] = '\0'; ++ ++ if (strspn(line, "0123456789") != (size_t)len-1) goto cleanup; ++ if (line[len-1] != '\n') goto cleanup; ++ lines_match = (len == pid_len && strcmp(line, pid_line) == 0); ++ } ++ ++if (operation == PID_WRITE) ++ { ++ if (!lines_match) ++ { ++ if (base_fd >= 0) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ (void)close(base_fd); ++ base_fd = -1; ++ } ++ /* emulate openat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ base_fd = open(base, O_WRONLY | O_CREAT | O_EXCL | base_flags, base_mode); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (base_fd < 0) goto cleanup; ++ if (fchmod(base_fd, base_mode) != 0) goto cleanup; ++ if (write(base_fd, pid_line, pid_len) != pid_len) goto cleanup; ++ DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); ++ } ++ } ++else ++ { ++ if (!lines_match) goto cleanup; ++ if (operation == PID_DELETE) ++ { ++ int error = -1; ++ /* emulate unlinkat */ ++ if (fchdir(dir_fd) != 0) goto cleanup; ++ error = unlink(base); ++ if (fchdir(cwd_fd) != 0) ++ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "can't return to previous working dir: %s", strerror(errno)); ++ if (error) goto cleanup; ++ } ++ } ++ ++success = TRUE; ++errno = 0; ++ ++cleanup: ++if (cwd_fd >= 0) (void)close(cwd_fd); ++if (dir_fd >= 0) (void)close(dir_fd); ++if (base_fd >= 0) (void)close(base_fd); ++return success; ++} ++ ++ ++/* Remove the daemon's pidfile. Note: runs with root privilege, ++as a direct child of the daemon. Does not return. */ ++ ++void ++delete_pid_file(void) ++{ ++const BOOL success = operate_on_pid_file(PID_DELETE, getppid()); ++ ++DEBUG(D_any) ++ debug_printf("delete pid file %s %s: %s\n", pid_file_path, ++ success ? "success" : "failure", strerror(errno)); ++ ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ ++/* Called by the daemon; exec a child to get the pid file deleted ++since we may require privs for the containing directory */ ++ ++static void ++daemon_die(void) ++{ ++int pid; ++ ++DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); ++#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) ++tls_watch_invalidate(); ++#endif ++ ++if (f.running_in_test_harness || write_pid) ++ { ++ if ((pid = fork()) == 0) ++ { ++ if (override_pid_file_path) ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, ++ "-oP", override_pid_file_path, "-oPX"); ++ else ++ (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); ++ ++ /* Control never returns here. */ ++ } ++ if (pid > 0) ++ child_close(pid, 1); ++ } ++exim_exit(EXIT_SUCCESS, US""); ++} ++ ++ + + /************************************************* + * Exim Daemon Mainline * + *************************************************/ + +@@ -1538,32 +1726,18 @@ automatically. Consequently, Exim 4 writ + + The variable daemon_write_pid is used to control this. */ + + if (f.running_in_test_harness || write_pid) + { +- FILE *f; +- +- if (override_pid_file_path) +- pid_file_path = override_pid_file_path; +- +- if (pid_file_path[0] == 0) +- pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); +- +- if ((f = modefopen(pid_file_path, "wb", 0644))) +- { +- (void)fprintf(f, "%d\n", (int)getpid()); +- (void)fclose(f); +- DEBUG(D_any) debug_printf("pid written to %s\n", pid_file_path); +- } +- else +- DEBUG(D_any) +- debug_printf("%s\n", string_open_failed(errno, "pid file %s", +- pid_file_path)); ++ const enum pid_op operation = (f.running_in_test_harness ++ || real_uid == root_uid ++ || (real_uid == exim_uid && !override_pid_file_path)) ? PID_WRITE : PID_CHECK; ++ if (!operate_on_pid_file(operation, getpid())) ++ DEBUG(D_any) debug_printf("%s pid file %s: %s\n", (operation == PID_WRITE) ? "write" : "check", pid_file_path, strerror(errno)); + } + + /* Set up the handler for SIGHUP, which causes a restart of the daemon. */ +- + sighup_seen = FALSE; + signal(SIGHUP, sighup_handler); + + /* Give up root privilege at this point (assuming that exim_uid and exim_gid + are not root). The third argument controls the running of initgroups(). +--- a/src/exim.c ++++ b/src/exim.c +@@ -3042,12 +3042,20 @@ for (i = 1; i < argc; i++) + + else if (Ustrcmp(argrest, "o") == 0) {} + + /* -oP : set pid file path for daemon */ + +- else if (Ustrcmp(argrest, "P") == 0) +- override_pid_file_path = argv[++i]; ++ else if (*argrest == 'P') ++ { ++ if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid) ++ exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX " ++ "(uid=%d euid=%d | %d)\n", ++ root_uid, exim_uid, getuid(), geteuid(), real_uid); ++ if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i]; ++ else if (Ustrcmp(argrest, "PX") == 0) delete_pid_file(); ++ else badarg = TRUE; ++ } + + /* -or : set timeout for non-SMTP acceptance + -os : set timeout for SMTP acceptance */ + + else if (*argrest == 'r' || *argrest == 's') diff --git a/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch new file mode 100644 index 0000000..d0dc071 --- /dev/null +++ b/debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch @@ -0,0 +1,57 @@ +From 47a48ed569503d8730bafcfd0f96d27cb72c9454 Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Sat, 1 May 2021 11:21:22 +0200 +Subject: [PATCH 27/29] testsuite: adjustments for CVE-2020-28014, + CVE-2021-27216 (Arbitrary PID file creation) + +--- + src/daemon.c | 32 -------------------------------- + test/stderr/0433 | 24 ------------------------ + 2 files changed, 56 deletions(-) + +diff --git a/src/daemon.c b/src/daemon.c +index 9403472f3..7c15d148c 100644 +--- a/src/daemon.c ++++ b/src/daemon.c +@@ -1044,38 +1044,6 @@ exim_exit(EXIT_SUCCESS, US""); + } + + +-/* Called by the daemon; exec a child to get the pid file deleted +-since we may require privs for the containing directory */ +- +-static void +-daemon_die(void) +-{ +-int pid; +- +-DEBUG(D_any) debug_printf("SIGTERM/SIGINT seen\n"); +-#if defined(SUPPORT_TLS) && (defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)) +-tls_watch_invalidate(); +-#endif +- +-if (f.running_in_test_harness || write_pid) +- { +- if ((pid = fork()) == 0) +- { +- if (override_pid_file_path) +- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, +- "-oP", override_pid_file_path, "-oPX"); +- else +- (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); +- +- /* Control never returns here. */ +- } +- if (pid > 0) +- child_close(pid, 1); +- } +-exim_exit(EXIT_SUCCESS, US""); +-} +- +- + + /************************************************* + * Exim Daemon Mainline * +-- +2.30.2 + diff --git a/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch new file mode 100644 index 0000000..f5965ab --- /dev/null +++ b/debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch @@ -0,0 +1,26 @@ +From 5220dc30120bd79319d465bd7a6e4b21a0881f9a Mon Sep 17 00:00:00 2001 +From: "Heiko Schlittermann (HS12-RIPE)" +Date: Fri, 30 Apr 2021 10:47:45 +0200 +Subject: [PATCH 29/29] Fix BDAT issue for body w/o trailing CRLF (again Bug + 1974) + +(cherry picked from commit 919111edac911ba9c15422eafd7c5bf14d416d26) +--- + src/smtp_in.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 016c44c0f..76784c15f 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -854,6 +854,7 @@ int + bdat_ungetc(int ch) + { + chunking_data_left++; ++bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */ + return lwr_receive_ungetc(ch); + } + +-- +2.30.2 + diff --git a/debian/patches/series b/debian/patches/series index d19d88e..e448ccf 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -26,4 +26,35 @@ 78_02-Fix-buffer-overflow-in-string_vformat.-Bug-2449.patch 79_01-Fix-SPA-authenticator-checking-client-supplied-data-.patch 79_02-Rework-SPA-fix-to-avoid-overflows.-Bug-2571.patch +80_01-GnuTLS-fix-hanging-callout-connections.patch +80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch +80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch +82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch +84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch +84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch +84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch +84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch +84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch +84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch +84_07-Security-Refuse-negative-and-large-store-allocations.patch +84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch +84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch +84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch +84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch +84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch +84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch +84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch +84_15-Security-Safeguard-against-relative-names-for-msglog.patch +84_16-Security-Check-overrun-rcpt_count-integer.patch +84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch +84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch +84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch +84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch +84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch +84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch +84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch +84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch +84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch +84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch +84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch 90_localscan_dlopen.dpatch -- 2.20.1