Your message dated Sat, 16 May 2026 11:07:42 +0000
with message-id <[email protected]>
and subject line Released with 12.14
has caused the Debian Bug report #1135539,
regarding bookworm-pu: package exim4/4.96-15+deb12u8
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1135539: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135539
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
Control: affects -1 + src:exim4
User: [email protected]
Usertags: pu

Hello,

after discussion with the security team I would like to fix a couple
CVEs and unrelated to that a interoperability issue via
stable/oldstable uploads:

a) All the CVE fixes from the recent security update 4.99.2:
* CVE-2026-40684  Possible crash with malicious DNS data when using musl
  libc ...
  While we do not use musl libc, it is small contained patch, so I would
  still prefer to inculde it.
* CVE-2026-40685  Possible OOB read/write on corrupt JSON in header
  configurations using json operators on invalid externally-provided input
  could trigger heap corruption.
  As far I understand this also does not hit our binaries, since we do
  not build with JSON looks enabled. Howver users can build private
  packages from our sources. One-line change.
* CVE-2026-40686  Possible OOB read with large UTF8 trailing character
  ... Another tiny change, applies to Debian.
* CVE-2026-40687  Possible OOB read/write with SPA authenticator.
  This is client side and needs a hostile/compromised external
  counterpart. This patch is rather big and required some handholding to
  apply.  For bookworm this required cherry-picking another upstream
  change to let the patch apply.

b) Fix GnuTLS hostname verify of a server certificate with a
   zero-length Subject. These are now being handed out by LetsEncrypt; note
   that this means they carry no DN (as well as no SN, that having decreed
   deprecated in favour of SANs).

This is also a small change and something our DSA would appreciate.
Upstream discussion starts here:
https://lists.exim.org/lurker/message/20260413.184322.ecbabb9e.en.html

TIA, cu Andreas

-- 
"You people are noisy," Nia said.
I made the gesture of agreement.
diff --git a/debian/changelog b/debian/changelog
index 5e267e24..3f4b9491 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,30 @@
+exim4 (4.96-15+deb12u8) bookworm; urgency=medium
+
+  * Fix GnuTLS hostname verify of a server certificate with a zero-length
+    Subject. Patch from upstream GIT master (Closes: #1134984)
+  * Pull CVE-fixes from 4.99.2
+    +CVE-2026-40684  Possible crash with malicious DNS data when using musl
+     libc On systems using musl libc (not glibc) due to an oddity in octal
+     printing it is possible to crash the connection instance when malformed
+     DNS data is present in PTR records.
+    +CVE-2026-40685  Possible OOB read/write on corrupt JSON in header
+     configurations using json operators on invalid externally-provided input
+     could trigger heap corruption.
+    +CVE-2026-40686  Possible OOB read with large UTF8 trailing characters
+     configurations using utf8 operators on malformed utf8 in headers could
+     trigger OOB reads and might trigger some data leak if error messages are
+     required for subsequent emails in the current connection and similar
+     malformed headers are present.
+    +CVE-2026-40687  Possible OOB read/write with SPA authenticator in
+     configurations using the SPA authentication driver to a
+     hostile/compromised external SPA/NTLM connection it is possible to
+     trigger an OOB read/write and crash the connection instance or possibly
+     leak heap data to the instance.
+    +As a pre-dependeny to the patchset also add the fix for upstream Bug
+     3106 from 4.99.
+
+ -- Andreas Metzler <[email protected]>  Sat, 02 May 2026 11:33:47 +0200
+
 exim4 (4.96-15+deb12u7) bookworm-security; urgency=high
 
   * Fix use-after-free (requiring local command-line access) notified by
diff --git a/debian/patches/82_01-GnuTLS-fix-hostname-verify-of-server-cert-for-empty-.patch b/debian/patches/82_01-GnuTLS-fix-hostname-verify-of-server-cert-for-empty-.patch
new file mode 100644
index 00000000..c62e5496
--- /dev/null
+++ b/debian/patches/82_01-GnuTLS-fix-hostname-verify-of-server-cert-for-empty-.patch
@@ -0,0 +1,84 @@
+From 371e5210218746e876fd71c888fdb666c85ceb56 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Sun, 19 Apr 2026 15:14:14 +0100
+Subject: [PATCH] GnuTLS: fix hostname verify of server cert for empty Subject.
+  Bug 3215
+
+---
+ doc/ChangeLog |  6 ++++++
+ src/tls-gnu.c     | 27 +++++++++++++++++----------
+ 2 files changed, 23 insertions(+), 10 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -110,10 +110,16 @@ JH/06 Bug 3054: Fix dnsdb lookup for a T
+ JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in
+       LF-only mode (as detected from the first header line).  Previously we did
+       accept that in (normal) CRLF mode; this has been raised as a possible
+       attack scenario (under the name "smtp smuggling", CVE-2023-51766).
+ 
++JH/33 Bug 3215: Fix GnuTLS hostname verify of a server certificate with a
++      zero-length Subject. These are now being handed out by LetsEncrypt; note
++      that this means they carry no DN (as well as no SN, that having decreed
++      deprecated in favour of SANs). The $tls_*peerdn variables relating to
++      these certificates will be empty strings.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -2234,11 +2234,10 @@ gnutls_protocol_t protocol;
+ gnutls_cipher_algorithm_t cipher;
+ gnutls_kx_algorithm_t kx;
+ gnutls_mac_algorithm_t mac;
+ gnutls_certificate_type_t ct;
+ gnutls_x509_crt_t crt;
+-uschar * dn_buf;
+ size_t sz;
+ 
+ if (state->have_set_peerdn)
+   return OK;
+ state->have_set_peerdn = TRUE;
+@@ -2356,22 +2355,30 @@ if ((ct = gnutls_certificate_type_get(se
+ rc = import_cert(&cert_list[0], &crt);
+ exim_gnutls_peer_err(US"cert 0");
+ 
+ state->tlsp->peercert = state->peercert = crt;
+ 
++state->peerdn = US"";
+ sz = 0;
+-rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
+-if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
++if (!(rc = gnutls_x509_crt_get_dn(crt, NULL, &sz)))
++  { DEBUG(D_tls) debug_printf_indent("TLS: zero-length DN\n"); }
++else if (rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
++  { DEBUG(D_tls) debug_printf_indent("TLS: no DN\n"); }
++else
+   {
+-  exim_gnutls_peer_err(US"getting size for cert DN failed");
+-  return FAIL; /* should not happen */
+-  }
+-dn_buf = store_get_perm(sz, GET_TAINTED);
+-rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
+-exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
++  uschar * dn_buf;
++  if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
++    {
++    exim_gnutls_peer_err(US"getting size for cert DN failed");
++    return FAIL; /* should not happen */
++    }
++  dn_buf = store_get_perm(sz, GET_TAINTED);
++  rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
++  exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+ 
+-state->peerdn = dn_buf;
++  state->peerdn = dn_buf;
++  }
+ 
+ return OK;
+ #undef exim_gnutls_peer_err
+ }
+ 
diff --git a/debian/patches/82_02-Support-musl-libc-dn_expand-oddity.patch b/debian/patches/82_02-Support-musl-libc-dn_expand-oddity.patch
new file mode 100644
index 00000000..99693f8a
--- /dev/null
+++ b/debian/patches/82_02-Support-musl-libc-dn_expand-oddity.patch
@@ -0,0 +1,75 @@
+From 628bbaca7672748d941a12e7cd5f0122a4e18c81 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Tue, 28 Apr 2026 14:47:32 +0100
+Subject: [PATCH 1/4] Support musl libc dn_expand oddity
+
+CVE-2026-40684
+---
+ doc/ChangeLog                            | 16 ++++++++++++++++
+ .../CVE2026-40684.assessment                     | 12 ++++++++++++
+ src/string.c                                 | 12 ++++++------
+ 3 files changed, 34 insertions(+), 6 deletions(-)
+ create mode 100644 doc/doc-txt/exim-security-2026-04.1/CVE2026-40684.assessment
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -116,10 +116,14 @@ JH/33 Bug 3215: Fix GnuTLS hostname veri
+       zero-length Subject. These are now being handed out by LetsEncrypt; note
+       that this means they carry no DN (as well as no SN, that having decreed
+       deprecated in favour of SANs). The $tls_*peerdn variables relating to
+       these certificates will be empty strings.
+ 
++JH/34 CVE-2026-40684: A crafted DNS record could cause a crash of the Exim
++      process acessing it, when operating with musl libc. This could be the
++      daemon. An Exim using Gnu libc is not affeected.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- /dev/null
++++ b/doc/doc-txt/exim-security-2026-04.1/CVE2026-40684.assessment
+@@ -0,0 +1,12 @@
++CVE2026-40684
++
++Vulnerability conditions
++------------------------
++
++- Exim build/run using musl libc (not gnulibc)
++- Deamon running, accepting connections
++
++Impact
++------
++
++Remote-triggered crash, via crafted PTR record
+--- a/src/string.c
++++ b/src/string.c
+@@ -579,21 +579,21 @@ string_copy_dnsdomain(uschar * s)
+ {
+ uschar * yield;
+ uschar * ss = yield = store_get(Ustrlen(s) + 1, GET_TAINTED);	/* always treat as tainted */
+ 
+ while (*s)
+-  {
+   if (*s != '\\')
+     *ss++ = *s++;
+-  else if (isdigit(s[1]))
+-    {
+-    *ss++ = (s[1] - '0')*100 + (s[2] - '0')*10 + s[3] - '0';
+-    s += 4;
++  else if (isdigit(*++s)) /* Apparently, musl libc dn_expand seen doing \DD */
++    {	/* and \D also. We can only hope not when a real digit follows. */
++    uschar c = *s++ - '0';
++    if (isdigit(*s)) c = c * 10 + *s++ - '0';
++    if (isdigit(*s)) c = c * 10 + *s++ - '0';
++    *ss++ = c;
+     }
+   else if (*++s)
+     *ss++ = *s++;
+-  }
+ 
+ *ss = 0;
+ return yield;
+ }
+ 
diff --git a/debian/patches/82_03-when-dewrap-only-skip-if-associated-char.patch b/debian/patches/82_03-when-dewrap-only-skip-if-associated-char.patch
new file mode 100644
index 00000000..8c06a59f
--- /dev/null
+++ b/debian/patches/82_03-when-dewrap-only-skip-if-associated-char.patch
@@ -0,0 +1,58 @@
+From 9fdc057e71b87c87a0d3d2288b2810a0efaaba57 Mon Sep 17 00:00:00 2001
+From: Bernard Quatermass <[email protected]>
+Date: Mon, 23 Mar 2026 16:43:51 +0000
+Subject: [PATCH 2/4] when dewrap, only skip \ if associated char
+
+CVE2026-40685
+---
+ doc/ChangeLog                                 |  5 ++++-
+ .../exim-security-2026-04.1/CVE2026-40685.assessment  | 11 +++++++++++
+ src/expand.c                                      |  2 +-
+ 3 files changed, 16 insertions(+), 2 deletions(-)
+ create mode 100644 doc/doc-txt/exim-security-2026-04.1/CVE2026-40685.assessment
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -120,10 +120,13 @@ JH/33 Bug 3215: Fix GnuTLS hostname veri
+ 
+ JH/34 CVE-2026-40684: A crafted DNS record could cause a crash of the Exim
+       process acessing it, when operating with musl libc. This could be the
+       daemon. An Exim using Gnu libc is not affeected.
+ 
++BQ/02 CVE-2026-40685: JSON string expansions could, when fed crafted source
++      strings, corrupt the heap.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- /dev/null
++++ b/doc/doc-txt/exim-security-2026-04.1/CVE2026-40685.assessment
+@@ -0,0 +1,11 @@
++CVE2026-40685
++
++Vulnerability conditions
++------------------------
++
++- Config uses json operators on externally-provided input
++
++Impact
++------
++
++- Remote-triggered heap corruption
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -2278,11 +2278,11 @@ if (Uskip_whitespace(&p) == *wrap)
+   {
+   s = ++p;
+   wrap++;
+   while (*p)
+     {
+-    if (*p == '\\') p++;
++    if (*p == '\\' && *(p+1)) p++;
+     else if (!quotesmode && *p == wrap[-1]) depth++;
+     else if (*p == *wrap)
+       if (depth == 0)
+ 	{
+ 	*p = '\0';
diff --git a/debian/patches/82_04-Expansions-harden-for-malformed-UTF-8.patch b/debian/patches/82_04-Expansions-harden-for-malformed-UTF-8.patch
new file mode 100644
index 00000000..46b7faa9
--- /dev/null
+++ b/debian/patches/82_04-Expansions-harden-for-malformed-UTF-8.patch
@@ -0,0 +1,59 @@
+From f2570bde16fb4d4a1242ff363a4c4eecf6372efc Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Mon, 23 Mar 2026 15:10:28 +0000
+Subject: [PATCH 3/4] Expansions: harden for malformed UTF-8
+
+CVE2026-40686
+---
+ doc/ChangeLog                                 |  4 ++++
+ .../exim-security-2026-04.1/CVE2026-40686.assessment  | 11 +++++++++++
+ src/expand.c                                      |  2 +-
+ 3 files changed, 16 insertions(+), 1 deletion(-)
+ create mode 100644 doc/doc-txt/exim-security-2026-04.1/CVE2026-40686.assessment
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -123,10 +123,14 @@ JH/34 CVE-2026-40684: A crafted DNS reco
+       daemon. An Exim using Gnu libc is not affeected.
+ 
+ BQ/02 CVE-2026-40685: JSON string expansions could, when fed crafted source
+       strings, corrupt the heap.
+ 
++JH/35 CVE-2026-40686: The ${from_utf8:} expansion operator, fed malformed input,
++      could read into the heap. If the result was used for an SMTP rejection
++      message, data exfiltration would be possible.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- /dev/null
++++ b/doc/doc-txt/exim-security-2026-04.1/CVE2026-40686.assessment
+@@ -0,0 +1,11 @@
++CVE2026-40686
++
++Vulnerability conditions
++------------------------
++
++- Config using UTF-8 operations on externally-provided input
++
++Impact
++------
++
++- Heap data exfiltration
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -884,11 +884,11 @@ static int utf8_table2[] = { 0xff, 0x1f,
+   if ((c & 0xc0) == 0xc0) \
+     { \
+     int a = utf8_table1[c & 0x3f];  /* Number of additional bytes */ \
+     int s = 6*a; \
+     c = (c & utf8_table2[a]) << s; \
+-    while (a-- > 0) \
++    while (a-- > 0 && *ptr) \
+       { \
+       s -= 6; \
+       c |= (*ptr++ & 0x3f) << s; \
+       } \
+     }
diff --git a/debian/patches/82_05-Fix-SPA-authenticator.-Bug-3106.patch b/debian/patches/82_05-Fix-SPA-authenticator.-Bug-3106.patch
new file mode 100644
index 00000000..e4788e83
--- /dev/null
+++ b/debian/patches/82_05-Fix-SPA-authenticator.-Bug-3106.patch
@@ -0,0 +1,413 @@
+From a731c6050a1510734776851aaff5ad2f32fa3ae5 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Mon, 5 Aug 2024 12:51:12 +0100
+Subject: [PATCH] Fix SPA authenticator.  Bug 3106
+
+---
+ doc/ChangeLog    |   6 ++
+ src/auths/auth-spa.c | 205 ++++++++++++++++-----------------------
+ src/auths/auth-spa.h |  17 ++--
+ src/auths/spa.c      |   2 +-
+ 4 files changed, 99 insertions(+), 131 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -127,10 +127,16 @@ BQ/02 CVE-2026-40685: JSON string expans
+ 
+ JH/35 CVE-2026-40686: The ${from_utf8:} expansion operator, fed malformed input,
+       could read into the heap. If the result was used for an SMTP rejection
+       message, data exfiltration would be possible.
+ 
++JH/07 Bug 3106: Fix coding in SPA authenticator. A macro argument was not
++      properly parenthesized, resulting in a logic error.  While the simple
++      fix was provided by Andrew Aitchison, the over-large code block resulting
++      from this macro made me want to replace it with a real function so more
++      extensive rework becamse needed.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- a/src/auths/auth-spa.c
++++ b/src/auths/auth-spa.c
+@@ -1200,59 +1200,71 @@ A = B = C = D = 0;
+ char versionString[] = "libntlm version 0.21";
+ 
+ /* Utility routines that handle NTLM auth structures. */
+ 
+ /* The [IS]VAL macros are to take care of byte order for non-Intel
+- * Machines -- I think this file is OK, but it hasn't been tested.
+- * The other files (the ones stolen from Samba) should be OK.
+- */
+-
+-
+-/* I am not crazy about these macros -- they seem to have gotten
+- * a bit complex.  A new scheme for handling string/buffer fields
+- * in the structures probably needs to be designed
+- */
+-
+-#define spa_bytes_add(ptr, header, buf, count) \
+-{ \
+-if (  buf && (count) != 0	/* we hate -Wint-in-bool-contex */ \
+-   && ptr->bufIndex + count < sizeof(ptr->buffer)		\
+-   ) \
+-  { \
+-  SSVAL(&ptr->header.len,0,count); \
+-  SSVAL(&ptr->header.maxlen,0,count); \
+-  SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \
+-  memcpy(ptr->buffer+ptr->bufIndex, buf, count); \
+-  ptr->bufIndex += count; \
+-  } \
+-else \
+-  { \
+-  ptr->header.len = \
+-  ptr->header.maxlen = 0; \
+-  SIVAL(&ptr->header.offset,0,((ptr->buffer - ((uint8x*)ptr)) + ptr->bufIndex)); \
+-  } \
+-}
+-
+-#define spa_string_add(ptr, header, string) \
+-{ \
+-uschar * p = string; \
+-int len = 0; \
+-if (p) len = Ustrlen(p); \
+-spa_bytes_add(ptr, header, p, len); \
+-}
+-
+-#define spa_unicode_add_string(ptr, header, string) \
+-{ \
+-uschar * p = string; \
+-uschar * b = NULL; \
+-int len = 0; \
+-if (p) \
+-  { \
+-  len = Ustrlen(p); \
+-  b = US strToUnicode(CS p); \
+-  } \
+-spa_bytes_add(ptr, header, b, len*2); \
++Machines -- I think this file is OK, but it hasn't been tested.
++The other files (the ones stolen from Samba) should be OK.  */
++
++
++/* Append a string to the buffer and point the header struct at that. */
++
++static void
++spa_bytes_add(SPAbuf * buffer, size_t off, SPAStrHeader * header,
++  const uschar * src, int count)
++{
++off += buffer->bufIndex;
++if (  src && count != 0			/* we hate -Wint-in-bool-contex */
++   && buffer->bufIndex + count < sizeof(buffer->buffer)
++   )
++  {
++  SSVAL(&header->len, 0, count);
++  SSVAL(&header->maxlen, 0, count);
++  SIVAL(&header->offset, 0, off);
++  memcpy(buffer->buffer + buffer->bufIndex, src, count);
++  buffer->bufIndex += count;
++  }
++else
++  {
++  header->len = header->maxlen = 0;
++  SIVAL(&header->offset, 0, off);
++  }
++}
++
++static void
++spa_string_add(SPAbuf * buffer, size_t off, SPAStrHeader * header,
++  const uschar * string)
++{
++int len = string ? Ustrlen(string) : 0;
++spa_bytes_add(buffer, off, header, string, len);
++}
++
++static uschar *
++strToUnicode(const uschar * p)
++{
++static uschar buf[1024];
++size_t l = Ustrlen(p);
++
++assert (l * 2 < sizeof buf);
++
++for (int i = 0; l--; ) { buf[i++] = *p++; buf[i++] = 0; }
++return buf;
++}
++
++static void
++spa_unicode_add_string(SPAbuf * buffer, size_t off, SPAStrHeader * header,
++  const uschar * string)
++{
++const uschar * p = string;
++uschar * b = NULL;
++int len = 0;
++if (p)
++  {
++  len = Ustrlen(p);
++  b = US strToUnicode(p);
++  }
++spa_bytes_add(buffer, off, header, b, len*2);
+ }
+ 
+ 
+ #ifdef notdef
+ 
+@@ -1290,28 +1302,10 @@ for (i = 0; i < len; ++i)
+ buf[i] = '\0';
+ return buf;
+ }
+ 
+ static uschar *
+-strToUnicode (char *p)
+-{
+-static uschar buf[1024];
+-size_t l = strlen (p);
+-int i = 0;
+-
+-assert (l * 2 < sizeof buf);
+-
+-while (l--)
+-  {
+-  buf[i++] = *p++;
+-  buf[i++] = 0;
+-  }
+-
+-return buf;
+-}
+-
+-static uschar *
+ toString (char *p, size_t len)
+ {
+ static uschar buf[1024];
+ 
+ assert (len + 1 < sizeof buf);
+@@ -1401,16 +1395,18 @@ if (p)
+   if (!domain)
+     domain = p + 1;
+   *p = '\0';
+   }
+ 
+-request->bufIndex = 0;
++request->buf.bufIndex = 0;
+ memcpy (request->ident, "NTLMSSP\0\0\0", 8);
+ SIVAL (&request->msgType, 0, 1);
+ SIVAL (&request->flags, 0, 0x0000b207);      /* have to figure out what these mean */
+-spa_string_add (request, user, u);
+-spa_string_add (request, domain, domain);
++spa_string_add(&request->buf, offsetof(SPAAuthRequest, buf), &request->user,
++		u);
++spa_string_add(&request->buf, offsetof(SPAAuthRequest, buf), &request->domain,
++		domain);
+ }
+ 
+ 
+ 
+ void
+@@ -1424,11 +1420,11 @@ int random_seed = (int)time(NULL) ^ ((p
+ /* Ensure challenge data is cleared, in case it isn't all used. This
+ patch added by PH on suggestion of Russell King */
+ 
+ memset(challenge, 0, sizeof(SPAAuthChallenge));
+ 
+-challenge->bufIndex = 0;
++challenge->buf.bufIndex = 0;
+ memcpy (challenge->ident, "NTLMSSP\0", 8);
+ SIVAL (&challenge->msgType, 0, 2);
+ SIVAL (&challenge->flags, 0, 0x00008201);
+ SIVAL (&challenge->uDomain.len, 0, 0x0000);
+ SIVAL (&challenge->uDomain.maxlen, 0, 0x0000);
+@@ -1446,58 +1442,16 @@ memcpy(challenge->challengeData,chalstr,
+ }
+ 
+ 
+ 
+ 
+-/* This is the original source of this function, preserved here for reference.
++/* The original version of this function is available in git.
+ The new version below was re-organized by PH following a patch and some further
+ suggestions from Mark Lyda to fix the problem that is described at the head of
+ this module. At the same time, I removed the untidiness in the code below that
+-involves the "d" and "domain" variables. */
+-
+-#ifdef NEVER
+-void
+-spa_build_auth_response (SPAAuthChallenge * challenge,
+-                        SPAAuthResponse * response, char *user,
+-                        char *password)
+-{
+-uint8x lmRespData[24];
+-uint8x ntRespData[24];
+-char *d = strdup (GetUnicodeString (challenge, uDomain));
+-char *domain = d;
+-char *u = strdup (user);
+-char *p = strchr (u, '@');
+-
+-if (p)
+-  {
+-  domain = p + 1;
+-  *p = '\0';
+-  }
+-
+-spa_smb_encrypt (US password, challenge->challengeData, lmRespData);
+-spa_smb_nt_encrypt (US password, challenge->challengeData, ntRespData);
+-
+-response->bufIndex = 0;
+-memcpy (response->ident, "NTLMSSP\0\0\0", 8);
+-SIVAL (&response->msgType, 0, 3);
+-
+-spa_bytes_add (response, lmResponse, lmRespData, 24);
+-spa_bytes_add (response, ntResponse, ntRespData, 24);
+-spa_unicode_add_string (response, uDomain, domain);
+-spa_unicode_add_string (response, uUser, u);
+-spa_unicode_add_string (response, uWks, u);
+-spa_string_add (response, sessionKey, NULL);
+-
+-response->flags = challenge->flags;
+-
+-free (d);
+-free (u);
+-}
+-#endif
+-
+-
+-/* This is the re-organized version (see comments above) */
++involves the "d" and "domain" variables.
++Further modified by JGH to replace complex macro "functions" with real ones. */
+ 
+ void
+ spa_build_auth_response (SPAAuthChallenge * challenge,
+                         SPAAuthResponse * response, uschar * user,
+                         uschar * password)
+@@ -1507,10 +1461,12 @@ uint8x ntRespData[24];
+ uint32x cf = IVAL(&challenge->flags, 0);
+ uschar * u = string_copy(user);
+ uschar * p = Ustrchr(u, '@');
+ uschar * d = NULL;
+ uschar * domain;
++SPAbuf * buf = &response->buf;
++const size_t off = offsetof(SPAAuthResponse, buf);
+ 
+ if (p)
+   {
+   domain = p + 1;
+   *p = '\0';
+@@ -1521,28 +1477,31 @@ else domain = d = string_copy(cf & 0x1
+   : CUS get_challenge_str(challenge, &challenge->uDomain));
+ 
+ spa_smb_encrypt(password, challenge->challengeData, lmRespData);
+ spa_smb_nt_encrypt(password, challenge->challengeData, ntRespData);
+ 
+-response->bufIndex = 0;
++buf->bufIndex = 0;
+ memcpy (response->ident, "NTLMSSP\0\0\0", 8);
+ SIVAL (&response->msgType, 0, 3);
+ 
+-spa_bytes_add(response, lmResponse, lmRespData, cf & 0x200 ? 24 : 0);
+-spa_bytes_add(response, ntResponse, ntRespData, cf & 0x8000 ? 24 : 0);
++spa_bytes_add(buf, off, &response->lmResponse, lmRespData, cf & 0x200 ? 24 : 0);
++spa_bytes_add(buf, off, &response->ntResponse, ntRespData, cf & 0x8000 ? 24 : 0);
+ 
+-if (cf & 0x1) {      /* Unicode Text */
+-     spa_unicode_add_string(response, uDomain, domain);
+-     spa_unicode_add_string(response, uUser, u);
+-     spa_unicode_add_string(response, uWks, u);
+-} else {             /* OEM Text */
+-     spa_string_add(response, uDomain, domain);
+-     spa_string_add(response, uUser, u);
+-     spa_string_add(response, uWks, u);
+-}
++if (cf & 0x1)		/* Unicode Text */
++  {
++  spa_unicode_add_string(buf, off, &response->uDomain, domain);
++  spa_unicode_add_string(buf, off, &response->uUser, u);
++  spa_unicode_add_string(buf, off, &response->uWks, u);
++  }
++else
++  {			/* OEM Text */
++  spa_string_add(buf, off, &response->uDomain, domain);
++  spa_string_add(buf, off, &response->uUser, u);
++  spa_string_add(buf, off, &response->uWks, u);
++  }
+ 
+-spa_string_add(response, sessionKey, NULL);
++spa_string_add(buf, off, &response->sessionKey, NULL);
+ response->flags = challenge->flags;
+ }
+ 
+ 
+ #endif   /*!MACRO_PREDEF*/
+--- a/src/auths/auth-spa.h
++++ b/src/auths/auth-spa.h
+@@ -35,31 +35,35 @@ typedef struct
+        uint32x         offset;
+ } SPAStrHeader;
+ 
+ typedef struct
+ {
++	uint8x	buffer[1024];
++	uint32x	bufIndex;
++} SPAbuf;
++
++typedef struct
++{
+        char         ident[8];
+        uint32x         msgType;
+        SPAStrHeader    uDomain;
+        uint32x         flags;
+        uint8x         challengeData[8];
+        uint8x         reserved[8];
+        SPAStrHeader    emptyString;
+-       uint8x         buffer[1024];
+-       uint32x         bufIndex;
++       SPAbuf		buf;
+ } SPAAuthChallenge;
+ 
+ 
+ typedef struct
+ {
+        char         ident[8];
+        uint32x         msgType;
+        uint32x         flags;
+        SPAStrHeader    user;
+        SPAStrHeader    domain;
+-       uint8x         buffer[1024];
+-       uint32x         bufIndex;
++       SPAbuf		buf;
+ } SPAAuthRequest;
+ 
+ typedef struct
+ {
+        char         ident[8];
+@@ -69,15 +73,14 @@ typedef struct
+        SPAStrHeader    uDomain;
+        SPAStrHeader    uUser;
+        SPAStrHeader    uWks;
+        SPAStrHeader    sessionKey;
+        uint32x         flags;
+-       uint8x         buffer[1024];
+-       uint32x         bufIndex;
++       SPAbuf		buf;
+ } SPAAuthResponse;
+ 
+-#define spa_request_length(ptr) (((ptr)->buffer - (uint8x*)(ptr)) + (ptr)->bufIndex)
++#define spa_request_length(ptr) (((uint8x*)&(ptr)->buf - (uint8x*)(ptr)) + (ptr)->buf.bufIndex)
+ 
+ void spa_bits_to_base64 (unsigned char *, const unsigned char *, int);
+ int spa_base64_to_bits(char *, int, const char *);
+ void spa_build_auth_response (SPAAuthChallenge * challenge,
+        SPAAuthResponse * response, uschar * user, uschar * password);
+--- a/src/auths/spa.c
++++ b/src/auths/spa.c
+@@ -190,11 +190,11 @@ that causes failure if the size of msgbu
+   int i;
+   char * p;
+   int len = SVAL(&responseptr->uUser.len,0)/2;
+ 
+   if (  (off = IVAL(&responseptr->uUser.offset,0)) >= sizeof(SPAAuthResponse)
+-     || len >= sizeof(responseptr->buffer)/2
++     || len >= sizeof(responseptr->buf.buffer)/2
+      || (p = (CS responseptr) + off) + len*2 >= CS (responseptr+1)
+      )
+     {
+     DEBUG(D_auth)
+       debug_printf("auth_spa_server(): bad uUser spec in response\n");
diff --git a/debian/patches/82_06-SPA-authenticator-harden-buffer-usage.patch b/debian/patches/82_06-SPA-authenticator-harden-buffer-usage.patch
new file mode 100644
index 00000000..10070488
--- /dev/null
+++ b/debian/patches/82_06-SPA-authenticator-harden-buffer-usage.patch
@@ -0,0 +1,258 @@
+From 68b963b9f75ca27b38e1c0f8c87037990199f505 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <[email protected]>
+Date: Tue, 10 Mar 2026 21:29:52 +0000
+Subject: [PATCH 4/4] SPA authenticator: harden buffer usage
+
+CVE-2026-40687
+---
+ doc/ChangeLog                         |   4 +
+ .../CVE2026-40687.assessment                  |  12 ++
+ src/auths/auth-spa.c                      | 113 +++---------------
+ src/auths/auth-spa.h                      |   1 -
+ 4 files changed, 35 insertions(+), 95 deletions(-)
+ create mode 100644 doc/doc-txt/exim-security-2026-04.1/CVE2026-40687.assessment
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -133,10 +133,14 @@ JH/07 Bug 3106: Fix coding in SPA authen
+       properly parenthesized, resulting in a logic error.  While the simple
+       fix was provided by Andrew Aitchison, the over-large code block resulting
+       from this macro made me want to replace it with a real function so more
+       extensive rework becamse needed.
+ 
++JH/36 CVE-2026-40687: The spa authenticator used an unitialized buffer, which
++      could result in a leak of data. It also had potential for wrting past the
++      end of static buffers, by choice of data provided by the client.
++
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+--- /dev/null
++++ b/doc/doc-txt/exim-security-2026-04.1/CVE2026-40687.assessment
+@@ -0,0 +1,12 @@
++CVE2026-40687
++
++Vulnerability conditions
++------------------------
++
++- Config uses the "spa" authenticator driver
++
++Impact
++------
++
++- Remote-triggered crash (only of connection process, not daemon)
++- Infoleak
+--- a/src/auths/auth-spa.c
++++ b/src/auths/auth-spa.c
+@@ -162,11 +162,10 @@ int main (int argc, char ** argv)
+ 
+ extern int DEBUGLEVEL;
+ 
+ #include "../exim.h"
+ #include "auth-spa.h"
+-#include <assert.h>
+ 
+ 
+ #ifndef _BYTEORDER_H
+ # define _BYTEORDER_H
+ 
+@@ -410,10 +409,12 @@ spa_base64_to_bits (char *out, int outle
+ /* base 64 to raw bytes in quasi-big-endian order, returning count of bytes */
+ {
+ int len = 0;
+ uschar digit1, digit2, digit3, digit4;
+ 
++memset(out, 0, outlength);
++
+ if (in[0] == '+' && in[1] == ' ')
+   in += 2;
+ if (*in == '\r')
+   return (0);
+ 
+@@ -1237,63 +1238,37 @@ spa_string_add(SPAbuf * buffer, size_t o
+ int len = string ? Ustrlen(string) : 0;
+ spa_bytes_add(buffer, off, header, string, len);
+ }
+ 
+ static uschar *
+-strToUnicode(const uschar * p)
++strToUnicode(const uschar * p, int len)
+ {
+-static uschar buf[1024];
+-size_t l = Ustrlen(p);
+-
+-assert (l * 2 < sizeof buf);
+-
+-for (int i = 0; l--; ) { buf[i++] = *p++; buf[i++] = 0; }
++uschar * buf = store_get(len * 2, p);
++for (int i = 0; len--; ) { buf[i++] = *p++; buf[i++] = 0; }
+ return buf;
+ }
+ 
+ static void
+ spa_unicode_add_string(SPAbuf * buffer, size_t off, SPAStrHeader * header,
+   const uschar * string)
+ {
+-const uschar * p = string;
+-uschar * b = NULL;
++const uschar * p = string, * b = NULL;
+ int len = 0;
+ if (p)
+   {
+   len = Ustrlen(p);
+-  b = US strToUnicode(p);
++  b = strToUnicode(p, len);
+   }
+ spa_bytes_add(buffer, off, header, b, len*2);
+ }
+ 
+ 
+-#ifdef notdef
+-
+-#define DumpBuffer(fp, structPtr, header) \
+- dumpRaw(fp,(US structPtr)+IVAL(&structPtr->header.offset,0),SVAL(&structPtr->header.len,0))
+-
+-
+-static void
+-dumpRaw (FILE * fp, uschar *buf, size_t len)
++uschar *
++unicodeToString (char * p, size_t len)
+ {
+ int i;
+-
+-for (i = 0; i < len; ++i)
+-  fprintf (fp, "%02x ", buf[i]);
+-
+-fprintf (fp, "\n");
+-}
+-
+-#endif
+-
+-char *
+-unicodeToString (char *p, size_t len)
+-{
+-int i;
+-static char buf[1024];
+-
+-assert (len + 1 < sizeof buf);
++uschar * buf = store_get((int)len + 1, p);
+ 
+ for (i = 0; i < len; ++i)
+   {
+   buf[i] = *p & 0x7f;
+   p += 2;
+@@ -1302,89 +1277,37 @@ for (i = 0; i < len; ++i)
+ buf[i] = '\0';
+ return buf;
+ }
+ 
+ static uschar *
+-toString (char *p, size_t len)
++toString (const char *p, size_t len)
+ {
+-static uschar buf[1024];
+-
+-assert (len + 1 < sizeof buf);
++uschar * buf = store_get((int)len + 1, p);
+ 
+ memcpy (buf, p, len);
+-buf[len] = 0;
++buf[len] = '\0';
+ return buf;
+ }
+ 
+ static inline uschar *
+ get_challenge_unistr(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
+ {
+-int off = IVAL(&hdr->offset, 0);
+-int len = SVAL(&hdr->len, 0);
+-return off + len < sizeof(SPAAuthChallenge)
+-  ? US unicodeToString(CS challenge + off, len/2) : US"";
+-}
++int offset = IVAL(&hdr->offset, 0), len = SVAL(&hdr->len, 0);
+ 
+-static inline uschar *
+-get_challenge_str(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
+-{
+-int off = IVAL(&hdr->offset, 0);
+-int len = SVAL(&hdr->len, 0);
+-return off + len < sizeof(SPAAuthChallenge)
+-  ? US toString(CS challenge + off, len) : US"";
++return offset + len < sizeof(SPAAuthChallenge)
++  ? unicodeToString(CS challenge + offset, len/2) : US"";
+ }
+ 
+-#ifdef notdef
+-
+-#define GetUnicodeString(structPtr, header) \
+- unicodeToString(((char*)structPtr) + IVAL(&structPtr->header.offset,0) , SVAL(&structPtr->header.len,0)/2)
+-
+-#define GetString(structPtr, header) \
+- toString(((CS structPtr) + IVAL(&structPtr->header.offset,0)), SVAL(&structPtr->header.len,0))
+-
+-
+-void
+-dumpSmbNtlmAuthRequest (FILE * fp, SPAAuthRequest * request)
++static uschar *
++get_challenge_str(SPAAuthChallenge * challenge, SPAStrHeader * hdr)
+ {
+-fprintf (fp, "NTLM Request:\n");
+-fprintf (fp, "      Ident = %s\n", request->ident);
+-fprintf (fp, "      mType = %d\n", IVAL (&request->msgType, 0));
+-fprintf (fp, "      Flags = %08x\n", IVAL (&request->flags, 0));
+-fprintf (fp, "       User = %s\n", GetString (request, user));
+-fprintf (fp, "     Domain = %s\n", GetString (request, domain));
+-}
++int offset = IVAL(&hdr->offset, 0), len = SVAL(&hdr->len, 0);
+ 
+-void
+-dumpSmbNtlmAuthChallenge (FILE * fp, SPAAuthChallenge * challenge)
+-{
+-fprintf (fp, "NTLM Challenge:\n");
+-fprintf (fp, "      Ident = %s\n", challenge->ident);
+-fprintf (fp, "      mType = %d\n", IVAL (&challenge->msgType, 0));
+-fprintf (fp, "     Domain = %s\n", GetUnicodeString (challenge, uDomain));
+-fprintf (fp, "      Flags = %08x\n", IVAL (&challenge->flags, 0));
+-fprintf (fp, "  Challenge = ");
+-dumpRaw (fp, challenge->challengeData, 8);
++return offset + len < sizeof(SPAAuthChallenge)
++  ? toString(CS challenge + offset, len) : US"";
+ }
+ 
+-void
+-dumpSmbNtlmAuthResponse (FILE * fp, SPAAuthResponse * response)
+-{
+-fprintf (fp, "NTLM Response:\n");
+-fprintf (fp, "      Ident = %s\n", response->ident);
+-fprintf (fp, "      mType = %d\n", IVAL (&response->msgType, 0));
+-fprintf (fp, "     LmResp = ");
+-DumpBuffer (fp, response, lmResponse);
+-fprintf (fp, "     NTResp = ");
+-DumpBuffer (fp, response, ntResponse);
+-fprintf (fp, "     Domain = %s\n", GetUnicodeString (response, uDomain));
+-fprintf (fp, "       User = %s\n", GetUnicodeString (response, uUser));
+-fprintf (fp, "        Wks = %s\n", GetUnicodeString (response, uWks));
+-fprintf (fp, "       sKey = ");
+-DumpBuffer (fp, response, sessionKey);
+-fprintf (fp, "      Flags = %08x\n", IVAL (&response->flags, 0));
+-}
+-#endif
+ 
+ void
+ spa_build_auth_request (SPAAuthRequest * request, uschar * user, uschar * domain)
+ {
+ uschar * u = string_copy(user);
+--- a/src/auths/auth-spa.h
++++ b/src/auths/auth-spa.h
+@@ -88,8 +88,8 @@ void spa_build_auth_request (SPAAuthRequ
+        uschar * domain);
+ extern void spa_smb_encrypt (unsigned char * passwd, unsigned char * c8,
+                              unsigned char * p24);
+ extern void spa_smb_nt_encrypt (unsigned char * passwd, unsigned char * c8,
+                                 unsigned char * p24);
+-extern char *unicodeToString(char *p, size_t len);
++extern uschar *unicodeToString(char *p, size_t len);
+ extern void spa_build_auth_challenge(SPAAuthRequest *, SPAAuthChallenge *);
+ 
diff --git a/debian/patches/series b/debian/patches/series
index 7fe80937..d9eb8562 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -55,4 +55,10 @@
 78_03-Compiler-quietening.patch
 80_Lookups-fix-dbmnz-crash-on-zero-length-datum.-Bug-30.patch
 81_CVE-2025-30232.patch
+82_01-GnuTLS-fix-hostname-verify-of-server-cert-for-empty-.patch
+82_02-Support-musl-libc-dn_expand-oddity.patch
+82_03-when-dewrap-only-skip-if-associated-char.patch
+82_04-Expansions-harden-for-malformed-UTF-8.patch
+82_05-Fix-SPA-authenticator.-Bug-3106.patch
+82_06-SPA-authenticator-harden-buffer-usage.patch
 90_localscan_dlopen.dpatch

Attachment: signature.asc
Description: PGP signature


--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 12.14

This update has been released as part of Debian 12.14.

--- End Message ---

Reply via email to