Merge branch 'debian' master
authorClinton Ebadi <clinton@unknownlamer.org>
Sat, 7 Aug 2021 18:56:29 +0000 (14:56 -0400)
committerClinton Ebadi <clinton@unknownlamer.org>
Sat, 7 Aug 2021 18:56:29 +0000 (14:56 -0400)
34 files changed:
debian/README.Debian.xml
debian/changelog
debian/patches/80_01-GnuTLS-fix-hanging-callout-connections.patch [new file with mode: 0644]
debian/patches/80_02-GnuTLS-tls_write-wait-after-uncorking-the-session.patch [new file with mode: 0644]
debian/patches/80_03-GnuTLS-Do-not-care-about-corked-data-when-uncorking.patch [new file with mode: 0644]
debian/patches/82_TLS-use-RFC-6125-rules-for-certifucate-name-checks-w.patch [new file with mode: 0644]
debian/patches/84_01-CVE-2020-28025-Heap-out-of-bounds-read-in-pdkim_fini.patch [new file with mode: 0644]
debian/patches/84_02-CVE-2020-28018-Use-after-free-in-tls-openssl.c.patch [new file with mode: 0644]
debian/patches/84_03-CVE-2020-28023-Out-of-bounds-read-in-smtp_setup_msg.patch [new file with mode: 0644]
debian/patches/84_04-CVE-2020-28010-Heap-out-of-bounds-write-in-main.patch [new file with mode: 0644]
debian/patches/84_05-CVE-2020-28011-Heap-buffer-overflow-in-queue_run.patch [new file with mode: 0644]
debian/patches/84_06-CVE-2020-28013-Heap-buffer-overflow-in-parse_fix_phr.patch [new file with mode: 0644]
debian/patches/84_07-Security-Refuse-negative-and-large-store-allocations.patch [new file with mode: 0644]
debian/patches/84_08-CVE-2020-28017-Integer-overflow-in-receive_add_recip.patch [new file with mode: 0644]
debian/patches/84_09-CVE-2020-28022-Heap-out-of-bounds-read-and-write-in-.patch [new file with mode: 0644]
debian/patches/84_10-CVE-2020-28026-Line-truncation-and-injection-in-spoo.patch [new file with mode: 0644]
debian/patches/84_11-CVE-2020-28015-28021-New-line-injection-into-spool-h.patch [new file with mode: 0644]
debian/patches/84_12-CVE-2020-28009-Integer-overflow-in-get_stdinput.patch [new file with mode: 0644]
debian/patches/84_13-CVE-2020-28024-Heap-buffer-underflow-in-smtp_ungetc.patch [new file with mode: 0644]
debian/patches/84_14-CVE-2020-28012-Missing-close-on-exec-flag-for-privil.patch [new file with mode: 0644]
debian/patches/84_15-Security-Safeguard-against-relative-names-for-msglog.patch [new file with mode: 0644]
debian/patches/84_16-Security-Check-overrun-rcpt_count-integer.patch [new file with mode: 0644]
debian/patches/84_17-Security-Always-exit-when-LOG_PANIC_DIE-is-set.patch [new file with mode: 0644]
debian/patches/84_18-Security-Fix-off-by-one-in-smtp-transport-read-respo.patch [new file with mode: 0644]
debian/patches/84_19-Security-Avoid-decrement-of-dkim_collect_input-if-al.patch [new file with mode: 0644]
debian/patches/84_20-Security-Leave-a-clean-smtp_out-input-buffer-even-in.patch [new file with mode: 0644]
debian/patches/84_21-Security-Avoid-modification-of-constant-data-in-dkim.patch [new file with mode: 0644]
debian/patches/84_22-CVE-2020-28019-Failure-to-reset-function-pointer-aft.patch [new file with mode: 0644]
debian/patches/84_23-CVE-2020-28007-Link-attack-in-Exim-s-log-directory.patch [new file with mode: 0644]
debian/patches/84_24-CVE-2020-28008-Assorted-attacks-in-Exim-s-spool-dire.patch [new file with mode: 0644]
debian/patches/84_26-CVE-2020-28014-CVE-2021-27216-Arbitrary-PID-file-cre.patch [new file with mode: 0644]
debian/patches/84_27-testsuite-adjustments-for-CVE-2020-28014-CVE-2021-27.patch [new file with mode: 0644]
debian/patches/84_29-Fix-BDAT-issue-for-body-w-o-trailing-CRLF-again-Bug-.patch [new file with mode: 0644]
debian/patches/series

index 77b4a37..df8f4a3 100644 (file)
        </para>
        <para>
          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.
+       </para>
+       <para>
+               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.
+       </para>
+       <para>
+               The certificate presented by the remote host is checked
+               against the system CA certificate store
+               (<filename>/etc/ssl/certs/</filename>) and the verification
+               result is logged (CV=...). However successful certificate
+               verification is <emphasis>not enforced</emphasis> by default.
+               This can be changed by setting tls_verify_hosts = * on the
+               respective transport.
+       </para>
+       <para>
+               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.
+       </para>
+       <para>
+         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.
        </para>
-       <para>
-         The certificate
-         presented by the remote host is not checked unless you
-         specify a tls_verify_certificate option on the transport.
-       </para>
        <para id="tls_client_certicate">
                To make exim send a TLS certificate to the remote host set
                REMOTE_SMTP_TLS_CERTIFICATE/REMOTE_SMTP_PRIVATEKEY or for
index b74e500..7bcce1f 100644 (file)
@@ -1,8 +1,55 @@
-exim4 (4.92-8+deb10u4~hcoop1) unstable; urgency=medium
+exim4 (4.92-8+deb10u6~hcoop1) unstable; urgency=medium
 
   * HCoop build
 
- -- Clinton Ebadi <clinton@unknownlamer.org>  Sat, 20 Jun 2020 16:32:29 -0400
+ -- Clinton Ebadi <clinton@unknownlamer.org>  Sat, 07 Aug 2021 14:56:12 -0400
+
+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 <ametzler@debian.org>  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 <ametzler@debian.org>  Thu, 18 Mar 2021 09:10:15 +0100
 
 exim4 (4.92-8+deb10u4) buster-security; urgency=high
 
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 (file)
index 0000000..0f36d73
--- /dev/null
@@ -0,0 +1,83 @@
+From 97c5e07c220b55d1c506a1798c9ce3ae3105adea Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+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 (file)
index 0000000..9998618
--- /dev/null
@@ -0,0 +1,73 @@
+From 783cb0301d9ceef2748956c3f91762275b7b45e5 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 (file)
index 0000000..da8748f
--- /dev/null
@@ -0,0 +1,55 @@
+From 3afb07f2c63fb6dc3983b28e7cdaf11fceb741d1 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 (file)
index 0000000..10e54e3
--- /dev/null
@@ -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 (file)
index 0000000..7c79753
--- /dev/null
@@ -0,0 +1,44 @@
+From 9db12ffa00aa1dcbe60eec543307f405e35cfe15 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..3a488b3
--- /dev/null
@@ -0,0 +1,33 @@
+From 86cafc842feb6223476568921c2d3e06c706cc31 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..28af9cc
--- /dev/null
@@ -0,0 +1,58 @@
+From 4cfadd994e5ab6e57cc43164d1e3198bb4faedbb Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..9e3d368
--- /dev/null
@@ -0,0 +1,42 @@
+From 5987d0dfe88ee6081b72857bc8085c7d2afd53a3 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..086644b
--- /dev/null
@@ -0,0 +1,39 @@
+From 9970ba4d8b9477d98c722221b6b7b97f03104b9f Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..6acdecc
--- /dev/null
@@ -0,0 +1,34 @@
+From 0f6c3d3f7efb5d66dabf69c36e06912d89ff96fc Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..53b4492
--- /dev/null
@@ -0,0 +1,74 @@
+From 5fd6af5815401dc60e8fe4309258911aa41d3013 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..d621b70
--- /dev/null
@@ -0,0 +1,49 @@
+From b5052a65ed1ba81269ac5a03b4505aa9d55ce084 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..1ace416
--- /dev/null
@@ -0,0 +1,61 @@
+From f46455c848def70d686d7b164df75b27f8dae04d Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..3864de2
--- /dev/null
@@ -0,0 +1,102 @@
+From 327f647a849c3974e7107b5386421b0058c15b29 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..1d2cc7e
--- /dev/null
@@ -0,0 +1,69 @@
+From ac8f49ef90e768a63ed3dca50e2b2c6e8d333bfd Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..acde64a
--- /dev/null
@@ -0,0 +1,61 @@
+From 2cb94a53eb9186bd405120543301e1240b895d86 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..4545ff3
--- /dev/null
@@ -0,0 +1,41 @@
+From 7ea481a6471cdad3a674b767f808357b3c7fc721 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..c9b2f65
--- /dev/null
@@ -0,0 +1,31 @@
+From a1f36d86760def10138c1053eb3b1882b281fcd9 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..7b2607a
--- /dev/null
@@ -0,0 +1,41 @@
+From 0d5d8fc918c4b999a2d5b025d94e25e43680377d Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..f8bda54
--- /dev/null
@@ -0,0 +1,39 @@
+From 56aadff97bc4e45e6a2ce25cfb9a98a4ae4bec79 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..a9eee56
--- /dev/null
@@ -0,0 +1,24 @@
+From 9b1ba71e66d18b1ae185e4d83788dc913f903a56 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..47d67d2
--- /dev/null
@@ -0,0 +1,47 @@
+From 28335a4704d8d615fd61e05ea6e435a4cd24e4df Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..a2b52fe
--- /dev/null
@@ -0,0 +1,59 @@
+From 031ae594f6e68511117f6d39ce238b0c5215d8d1 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 <dkim_collect_input>
+(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 <dkim_signatures>, 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 (file)
index 0000000..acf17d3
--- /dev/null
@@ -0,0 +1,67 @@
+From 6b647c508aced6961f00e139f0337e2c8aba9eb7 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..b723d0f
--- /dev/null
@@ -0,0 +1,89 @@
+From a4e1b7755ebbdee2689d40683ba69f09e38a8d7f Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 <lineending+8>
+(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 (file)
index 0000000..0d44293
--- /dev/null
@@ -0,0 +1,135 @@
+From 1663ab541b37675dec5bcf235605568ff36ac65a Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..211abe3
--- /dev/null
@@ -0,0 +1,542 @@
+From 99ae249e9857e80ff4d65b2388bc68c624dcb739 Mon Sep 17 00:00:00 2001
+From: Qualys Security Advisory <qsa@qualys.com>
+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 (file)
index 0000000..2bda99c
--- /dev/null
@@ -0,0 +1,205 @@
+From 5fec3406547fd1e46838a76f000102beb6bfe468 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 (file)
index 0000000..3e73ae1
--- /dev/null
@@ -0,0 +1,303 @@
+From c166890023f56388cb3482cff3def04171a488c4 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 <name>: 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 <n>: set timeout for non-SMTP acceptance
+        -os <n>: 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 (file)
index 0000000..d0dc071
--- /dev/null
@@ -0,0 +1,57 @@
+From 47a48ed569503d8730bafcfd0f96d27cb72c9454 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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 (file)
index 0000000..f5965ab
--- /dev/null
@@ -0,0 +1,26 @@
+From 5220dc30120bd79319d465bd7a6e4b21a0881f9a Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+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
+
index d0f267d..649fb4f 100644 (file)
 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