On 2026-09-24 Salvatore Bonaccorso <[email protected]> wrote:
> On Thu, Sep 24, 2026 at 09:59:32PM +0200, Salvatore Bonaccorso wrote:
> > On Thu, Sep 24, 2026 at 07:02:00PM +0200, Andreas Metzler wrote:
[...]
> > > the second one (CVE-2026-94055 / GnuTLS) only applies to versions >=
> > > 4.99, i.e. stable and earlier are fine. tls_early_banner was not
> > > available in earlier releases.
> > 
> > Alright, thanks will update the tracker.

> Hmm ist that correct? The upstream advisory say:

> - Exim versions from 4.98 up to and including 4.100 are affected.
> - The installation must be built with GnuTLS 3.6.4 or later, and configured
>    to accept TLS-on-connect.
> - The configuration must enable the tls_early_banner_hosts option
>    (a non-default setting).

Hello Salvatore,

I have asked upstream to confirm/reject this.
https://lists.exim.org/lurker/message/20260925.044831.5cb31b15.en.html

Propopsed patch attched.

cu Andreas
-- 
"You people are noisy," Nia said.
I made the gesture of agreement.
diff --git a/debian/changelog b/debian/changelog
index 23c4308e..b11117d1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,23 @@
+exim4 (4.98.2-1+deb13u5) trixie-security; urgency=high
+
+  * Cherry-pick relevant changes from security release 4.100.1.
+    CVE-2026-94054 EXIM-Security-2026-09-12.1 (GCVE-25-2026-09-50-1)
+     Exim before 4.100.1, when Proxy-Protocol is used with an attacker-
+     controlled proxy, has an out-of-bounds write.
+    CVE-2026-94056 EXIM-Security-2026-09-12.2 (GCVE-25-2026-09-55-1)
+     Exim before 4.100.1, when Proxy-Protocol is used with an attacker-
+     controlled proxy, allows attackers to read certain uninitialized
+     data from stack memory.
+    CVE-2026-94057 EXIM-Security-2026-09-12.4 (GCVE-25-2026-09-56-1)
+     Exim before 4.100.1 allows SMTP smuggling in which the received
+     message does not match any sent message, and instead depends on
+     crafted data sent after a rejection during DATA processing.
+    Closes: #1148506
+    (CVE-2026-94055 is not relevant - TLS Early banner support was
+    introduced in 4.99.)
+
+ -- Andreas Metzler <[email protected]>  Fri, 25 Sep 2026 11:25:38 +0200
+
 exim4 (4.98.2-1+deb13u4) trixie-security; urgency=high
 
   * Fix two local privilege escalation issues.
diff --git a/debian/patches/85_0001-Proxy-protocol-Fix-OOB-read.patch b/debian/patches/85_0001-Proxy-protocol-Fix-OOB-read.patch
new file mode 100644
index 00000000..cc33e24d
--- /dev/null
+++ b/debian/patches/85_0001-Proxy-protocol-Fix-OOB-read.patch
@@ -0,0 +1,95 @@
+From 8ead2b003af9225f712e85d246cd5544ef04ef91 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Thu, 27 Aug 2026 15:57:39 +0100
+Subject: [PATCH 1/4] Proxy-protocol: Fix OOB read
+
+---
+ doc/ChangeLog    |  9 +++++++++
+ src/proxy.c | 17 ++++++-----------
+ 2 files changed, 15 insertions(+), 11 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -1,9 +1,12 @@
+ This document describes *changes* to previous versions, that might
+ affect Exim's operation, with an unchanged configuration file.  For new
+ options, and new features, see the NewStuff file next to this ChangeLog.
+ 
++JH/01 Proxy Protocol: fix an OOB read in V1 protocol header parsing
++      (GCVE-25-2026-09-50-1).
++
+ JH/02 Do not expand a local_part gotten from a .forward file, under
+       force_command in a pipe transport. (GCVE-25-2026-07-45-3)
+ 
+ JH/01 Restrict named-queue names.  Previously, files could be corrupted by
+       poor choices of name. (GCVE-25-2026-07-45-1)
+--- a/src/proxy.c
++++ b/src/proxy.c
+@@ -378,24 +378,18 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig,
+       goto proxyfail;
+     }
+   }
+ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
+   {
+-  uschar *p;
+-  uschar *end;
+-  uschar *sp;     /* Utility variables follow */
+-  int     tmp_port;
+-  int     r2;
+-  char   *endc;
++  uschar * p, * end, * sp, * endc;     /* Utility variables follow sp */
++  int tmp_port, r2;
+ 
+   /* get the rest of the line */
+   r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
+   if (r2 == -1)
+     goto proxyfail;
+-  ret += r2;
+-
+-  p = string_copy(hdr.v1.line);
++  p = string_copyn(hdr.v1.line, ret = Ustrlen(hdr.v1.line));
+   end = memchr(p, '\r', ret - 1);
+ 
+   if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n')
+     {
+     DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
+@@ -425,12 +419,11 @@ else if (ret >= 8 && memcmp(hdr.v1.line,
+   else
+     {
+     DEBUG(D_receive) debug_printf("Invalid TCP type\n");
+     goto proxyfail;
+     }
+-
+-  p += Ustrlen(iptype);
++  p += Ustrlen(iptype);	/* the field sizes happen to match our iptype strings */
+   if (!isspace(*p++))
+     {
+     DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
+     goto proxyfail;
+     }
+@@ -470,11 +463,11 @@ else if (ret >= 8 && memcmp(hdr.v1.line,
+     {
+     DEBUG(D_receive) debug_printf("Did not find proxied src port\n");
+     goto proxyfail;
+     }
+   *sp = '\0';
+-  tmp_port = strtol(CCS p, &endc, 10);
++  tmp_port = strtol(CCS p, CSS &endc, 10);
+   if (*endc || tmp_port == 0)
+     {
+     DEBUG(D_receive)
+       debug_printf("Proxied src port '%s' not an integer\n", p);
+     goto proxyfail;
+@@ -485,11 +478,11 @@ else if (ret >= 8 && memcmp(hdr.v1.line,
+   if ((sp = Ustrchr(p, '\0')) == NULL)
+     {
+     DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
+     goto proxyfail;
+     }
+-  tmp_port = strtol(CCS p, &endc, 10);
++  tmp_port = strtol(CCS p, CSS &endc, 10);
+   if (*endc || tmp_port == 0)
+     {
+     DEBUG(D_receive)
+       debug_printf("Proxy dest port '%s' not an integer\n", p);
+     goto proxyfail;
diff --git a/debian/patches/85_0002-Proxy-protocol-account-for-short-received-V2-header.patch b/debian/patches/85_0002-Proxy-protocol-account-for-short-received-V2-header.patch
new file mode 100644
index 00000000..8147682f
--- /dev/null
+++ b/debian/patches/85_0002-Proxy-protocol-account-for-short-received-V2-header.patch
@@ -0,0 +1,68 @@
+From 2e3b687715f049bab7786c739a168b5842734d29 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Thu, 10 Sep 2026 14:52:02 +0100
+Subject: [PATCH 2/4] Proxy-protocol: account for short received V2 header
+
+---
+ doc/ChangeLog    |  5 +++++
+ src/proxy.c | 17 ++++++++++-------
+ 2 files changed, 15 insertions(+), 7 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -3,10 +3,15 @@ affect Exim's operation, with an unchang
+ options, and new features, see the NewStuff file next to this ChangeLog.
+ 
+ JH/01 Proxy Protocol: fix an OOB read in V1 protocol header parsing
+       (GCVE-25-2026-09-50-1).
+ 
++JH/02 Proxy-protocol: account for incomplete V2 protocol header reception.
++      Previously uninitialised data was then used for the length field of the
++      header, giving possible data leakage to an attacker.  This would require
++      a buggy or compromised proxy.  (GCVE-25-2026-09-55-1).
++
+ JH/02 Do not expand a local_part gotten from a .forward file, under
+       force_command in a pipe transport. (GCVE-25-2026-07-45-3)
+ 
+ JH/01 Restrict named-queue names.  Previously, files could be corrupted by
+       poor choices of name. (GCVE-25-2026-07-45-1)
+--- a/src/proxy.c
++++ b/src/proxy.c
+@@ -221,27 +221,30 @@ do
+ if (ret == -1)
+   goto proxyfail;
+ DEBUG(D_receive) proxy_debug(US &hdr, 0, ret);
+ 
+ /* For v2, handle reading the length, and then the rest. */
+-if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
++if (ret == PROXY_INITIAL_READ && memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0)
+   {
+   int retmore;
+   uint8_t ver;
+ 
+   DEBUG(D_receive) debug_printf("v2\n");
+ 
+   /* First get the length fields. */
+   do
+     {
+-    retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ);
+-    } while (retmore == -1 && errno == EINTR && !had_command_timeout);
+-  if (retmore == -1)
+-    goto proxyfail;
+-  DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
++    do
++      {
++      retmore = read(fd, US &hdr + ret, PROXY_V2_HEADER_SIZE - ret);
++      } while (retmore == -1 && errno == EINTR && !had_command_timeout);
++    if (retmore <= 0)
++      goto proxyfail;
++    DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
+ 
+-  ret += retmore;
++    ret += retmore;
++    } while (ret < PROXY_V2_HEADER_SIZE);
+ 
+   ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
+ 
+   /* May 2014: haproxy combined the version and command into one byte to
+   allow two full bytes for the length field in order to proxy SSL
diff --git a/debian/patches/85_0004-Avoid-more-SMTP-smuggling-attack-types.patch b/debian/patches/85_0004-Avoid-more-SMTP-smuggling-attack-types.patch
new file mode 100644
index 00000000..233d08b8
--- /dev/null
+++ b/debian/patches/85_0004-Avoid-more-SMTP-smuggling-attack-types.patch
@@ -0,0 +1,169 @@
+From fb7ccaa78db25a5816a3c4413d2e6caa44aaf822 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Fri, 11 Sep 2026 12:53:24 +0100
+Subject: [PATCH 4/4] Avoid more "SMTP smuggling" attack types
+
+---
+ doc/ChangeLog |  3 +++
+ src/receive.c     | 25 ++++++++++++-------------
+ 2 files changed, 15 insertions(+), 13 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -8,10 +8,13 @@ JH/01 Proxy Protocol: fix an OOB read in
+ JH/02 Proxy-protocol: account for incomplete V2 protocol header reception.
+       Previously uninitialised data was then used for the length field of the
+       header, giving possible data leakage to an attacker.  This would require
+       a buggy or compromised proxy.  (GCVE-25-2026-09-55-1).
+ 
++JH/04 Avoid more "SMTP smuggling" attack types.  This extends the fixes for
++      CVE-2023-51766.  (GCVE-25-2026-09-56-1).
++
+ JH/02 Do not expand a local_part gotten from a .forward file, under
+       force_command in a pipe transport. (GCVE-25-2026-07-45-3)
+ 
+ JH/01 Restrict named-queue names.  Previously, files could be corrupted by
+       poor choices of name. (GCVE-25-2026-07-45-1)
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -26,10 +26,11 @@ extern int dcc_ok;
+ 
+ static int     data_fd = -1;
+ static uschar *spool_name = US"";
+ 
+ enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
++static BOOL first_line_ended_crlf;
+ 
+ #ifdef HAVE_LOCAL_SCAN
+ jmp_buf local_scan_env;		/* error-handling context for local_scan */
+ unsigned had_local_scan_crash;
+ unsigned had_local_scan_timeout;
+@@ -833,17 +834,16 @@ followed by SMTP commands is a possible
+ the first (header) line for the message has a proper CRLF then enforce
+ that for the body: convert bare LF to a space.
+ 
+ Arguments:
+   fout		a FILE to which to write the message; NULL if skipping
+-  strict_crlf	require full CRLF sequence as a line ending
+ 
+ Returns:    One of the END_xxx values indicating why it stopped reading
+ */
+ 
+ static int
+-read_message_data_smtp(FILE * fout, BOOL strict_crlf)
++read_message_data_smtp(FILE * fout)
+ {
+ enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
+ 	      s_linestart;
+ int linelength = 0, ch;
+ 
+@@ -867,11 +867,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_
+ 	{
+ 	ch_state = s_had_cr;
+ 	continue;			/* Don't write the CR */
+ 	}
+       if (ch == '\n')			/* Bare LF at end of line */
+-	if (strict_crlf)
++	if (first_line_ended_crlf)	/* strict CRLF mode (normal case) */
+ 	  ch = ' ';			/* replace LF with space */
+ 	else
+ 	  {				/* treat as line ending */
+ 	  ch_state = s_linestart;
+ 	  body_linecount++;
+@@ -899,11 +899,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_
+ 	}
+       break;
+ 
+     case s_had_nl_dot:			/* After [CR] LF . */
+       if (ch == '\n')			/* [CR] LF . LF */
+-	if (strict_crlf)
++	if (first_line_ended_crlf)	/* strict CRLF mode (normal case) */
+ 	  ch = ' ';			/* replace LF with space */
+ 	else
+ 	  return END_DOT;
+       else if (ch == '\r')		/* [CR] LF . CR */
+ 	{
+@@ -1145,16 +1145,15 @@ tidily.
+ Argument:    a FILE from which to read the message
+ Returns:     nothing
+ */
+ 
+ void
+-receive_swallow_smtp(void)
++receive_swallow_smtp()
+ {
+ if (message_ended >= END_NOTENDED)
+   message_ended = chunking_state <= CHUNKING_OFFERED
+-     ? read_message_data_smtp(NULL, FALSE)
+-     : read_message_bdat_smtp_wire(NULL);
++     ? read_message_data_smtp(NULL) : read_message_bdat_smtp_wire(NULL);
+ }
+ 
+ 
+ 
+ /*************************************************
+@@ -1717,11 +1716,10 @@ const int id_resolution = BASE_62 == 62
+ 
+ int ptr = 0;
+ 
+ BOOL contains_resent_headers = FALSE;
+ BOOL extracted_ignored = FALSE;
+-BOOL first_line_ended_crlf = TRUE_UNSET;
+ BOOL smtp_yield = TRUE;
+ BOOL yield = FALSE;
+ 
+ BOOL resents_exist = FALSE;
+ uschar *resent_prefix = US"";
+@@ -1909,11 +1907,11 @@ inside them, so that writing them out re
+ 
+ Loop for each character of each header; the next structure for chaining the
+ header is set up already, with ptr the offset of the next character in
+ next->text. */
+ 
+-for (;;)
++for (first_line_ended_crlf = TRUE_UNSET; ;)
+   {
+   int ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+ 
+   /* If we hit EOF on a SMTP connection, it's an error, since incoming
+   SMTP must have a correct "." terminator. */
+@@ -3188,11 +3186,12 @@ Having created it, send the headers to t
+ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
+   {
+   if (received_count > received_headers_max)
+     {
+     cancel_cutthrough_connection(TRUE, US"too many headers");
+-    if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
++    if (smtp_input)
++      receive_swallow_smtp();
+     log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+       "Too many \"Received\" headers",
+       sender_address,
+       sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
+       sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
+@@ -3273,11 +3272,11 @@ message id or "next" line. */
+ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+   {
+   if (smtp_input)
+     {
+     message_ended = chunking_state <= CHUNKING_OFFERED
+-      ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
++      ? read_message_data_smtp(spool_data_file)
+       : spool_wireformat
+       ? read_message_bdat_smtp_wire(spool_data_file)
+       : read_message_bdat_smtp(spool_data_file);
+     receive_linecount++;                /* The terminating "." line */
+     }
+@@ -3306,11 +3305,11 @@ if (!ferror(spool_data_file) && !(receiv
+     message; we want to see the ident value even for non-remote messages. */
+ 
+     case END_SIZE:
+       Uunlink(spool_name);                /* Lose the data file when closed */
+       cancel_cutthrough_connection(TRUE, US"mail too big");
+-      if (smtp_input) receive_swallow_smtp();  /* Swallow incoming SMTP */
++      if (smtp_input) receive_swallow_smtp();
+ 
+       log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+ 	"message too big: read=%d max=%d",
+ 	sender_address,
+ 	sender_fullhost ? " H=" : "",
diff --git a/debian/patches/series b/debian/patches/series
index 1668b32e..97c2adf6 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -22,4 +22,7 @@
 83-Security-fix-PROXYv2-uninitialised-stack-disclosure-.patch
 84_01-Restrict-names-permitted-for-named-queues.patch
 84_02-Do-not-expand-local_part-in-a-pipe-transport-under-f.patch
+85_0001-Proxy-protocol-Fix-OOB-read.patch
+85_0002-Proxy-protocol-account-for-short-received-V2-header.patch
+85_0004-Avoid-more-SMTP-smuggling-attack-types.patch
 90_localscan_dlopen.dpatch

Attachment: signature.asc
Description: PGP signature

Reply via email to