[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1055155: marked as done (bookworm-pu: package exim4/4.96-15+deb12u3 (2nd try for new bug))



Your message dated Sat, 09 Dec 2023 10:20:37 +0000
with message-id <83d3a3621a56b9af1e20d36ee9d390a46ab64a8a.camel@adam-barratt.org.uk>
and subject line Closing requests for updates included in 12.3 point release
has caused the Debian Bug report #1055155,
regarding bookworm-pu: package exim4/4.96-15+deb12u3 (2nd try for new bug)
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 owner@bugs.debian.org
immediately.)


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

Hello,

I would like to push another round of cherry-picked upstream fixes to
bookworm, including the update to 4.96.2 to fix two non-DSA minor
security issues.

The changes are included in the new upstream (4.97 rc) uploads to sid which=
 are present in sid and testing.


* Multiple bugfixes from upstream GIT master:
  + 75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch
  + 75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch
    (Upstream bug 2998)
  + 75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch
  + 75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch
    (Upstream bug 3013)
----> ${run expansion breakage, similar to #1025420.
  + 75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch: Fix on-demand
    TLS cert expiry date. Closes: #1043233
    (Upstream bug 3014)
----> This is major hickup, bordering on RC.

  + 75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch
----> Another patch for ${run} expansion breakage.
  + 76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch ((Upstream bug 3023)
  + 76-12-DNS-more-hardening-against-crafted-responses.patch
* tests/basic: Add isolation-container restriction (needs a running
  exim daemon).
* Add ${run } expansion test to tests/basic.
* Update code to 4.96.2, fixing issues with the proxy protocol
  (CVE-2023-42117) and the `dnsdb` lookup subsystem (CVE-2023-42219). It
  also includes additional hardening for spf lookups, however CVE-2023-42218
  was diagnosed as a vulnerability in the libspf2 library and needs to be
  addressed there. Closes: #1053310

cu Andreas
diff -Nru exim4-4.96/debian/changelog exim4-4.96/debian/changelog
--- exim4-4.96/debian/changelog	2023-09-29 22:38:02.000000000 +0200
+++ exim4-4.96/debian/changelog	2023-11-01 07:07:57.000000000 +0100
@@ -1,3 +1,29 @@
+exim4 (4.96-15+deb12u3) bookworm; urgency=medium
+
+  * Multiple bugfixes from upstream GIT master:
+    + 75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch
+    + 75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch
+      (Upstream bug 2998)
+    + 75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch
+    + 75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch
+      (Upstream bug 3013)
+    + 75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch: Fix on-demand
+      TLS cert expiry date. Closes: #1043233
+      (Upstream bug 3014)
+    + 75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch
+    + 76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch ((Upstream bug 3023)
+    + 76-12-DNS-more-hardening-against-crafted-responses.patch
+  * tests/basic: Add isolation-container restriction (needs a running
+    exim daemon).
+  * Add ${run } expansion test to tests/basic.
+  * Update code to 4.96.2, fixing issues with the proxy protocol
+    (CVE-2023-42117) and the `dnsdb` lookup subsystem (CVE-2023-42219). It
+    also includes additional hardening for spf lookups, however CVE-2023-42218
+    was diagnosed as a vulnerability in the libspf2 library and needs to be
+    addressed there. Closes: #1053310
+
+ -- Andreas Metzler <ametzler@debian.org>  Wed, 01 Nov 2023 07:07:57 +0100
+
 exim4 (4.96-15+deb12u2) bookworm-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff -Nru exim4-4.96/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch exim4-4.96/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch
--- exim4-4.96/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,35 @@
+From 4d108e7777e9b8e5fb212c31812fef61529cd414 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 12 Jun 2023 22:13:46 +0100
+Subject: [PATCH] Cancel early-pipe on an observed advertising change
+
+---
+ src/transports/smtp.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/transports/smtp.c b/src/transports/smtp.c
+index c72028ce9..24ee577a2 100644
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -1111,15 +1111,18 @@ if (pending_EHLO)
+       *(tls_out.active.sock < 0
+ 	? &sx->ehlo_resp.cleartext_features : &sx->ehlo_resp.crypted_features) =
+ 	  peer_offered;
+       *ap = authbits;
+       write_ehlo_cache_entry(sx);
+       }
+     else
++      {
+       invalidate_ehlo_cache_entry(sx);
++      sx->early_pipe_active = FALSE;	/* cancel further early-pipe on this conn */
++      }
+ 
+     return OK;		/* just carry on */
+     }
+ # ifdef EXPERIMENTAL_ESMTP_LIMITS
+     /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the
+     cached values and invalidate cache if different.  OK to carry on with
+     connect since values are advisory. */
+-- 
+2.40.1
+
diff -Nru exim4-4.96/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch exim4-4.96/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch
--- exim4-4.96/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,99 @@
+From 1209e3e19e292cee517e43a2ccfe9b44b33bb1dc Mon Sep 17 00:00:00 2001
+From: Jasen Betts <jasen@xnet.co.nz>
+Date: Sun, 23 Jul 2023 13:43:59 +0100
+Subject: [PATCH] Expansions: disallow UTF-16 surrogates from ${utf8clean:...}.
+  Bug 2998
+
+---
+ doc/ChangeLog |  4 ++++
+ src/expand.c      | 27 +++++++++++++++++----------
+ 2 files changed, 21 insertions(+), 10 deletions(-)
+
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -7731,11 +7731,11 @@ NOT_ITEM: ;
+ 
+ 	case EOP_UTF8CLEAN:
+ 	  {
+ 	  int seq_len = 0, index = 0;
+ 	  int bytes_left = 0;
+-	  long codepoint = -1;
++	  ulong codepoint = (ulong)-1;
+ 	  int complete;
+ 	  uschar seq_buff[4];			/* accumulate utf-8 here */
+ 
+ 	  /* Manually track tainting, as we deal in individual chars below */
+ 
+@@ -7761,40 +7761,47 @@ NOT_ITEM: ;
+ 		codepoint = (codepoint << 6) | (c & 0x3f);
+ 		seq_buff[index++] = c;
+ 		if (--bytes_left == 0)		/* codepoint complete */
+ 		  if(codepoint > 0x10FFFF)	/* is it too large? */
+ 		    complete = -1;	/* error (RFC3629 limit) */
++		  else if ( (codepoint & 0x1FF800 ) == 0xD800 ) /* surrogate */
++		    /* A UTF-16 surrogate (which should be one of a pair that
++		    encode a Unicode codepoint that is outside the Basic
++		    Multilingual Plane).  Error, not UTF8.
++		    RFC2279.2 is slightly unclear on this, but 
++		    https://unicodebook.readthedocs.io/issues.html#strict-utf8-decoder
++		    says "Surrogates characters are also invalid in UTF-8:
++		    characters in U+D800—U+DFFF have to be rejected." */
++		    complete = -1;
+ 		  else
+ 		    {		/* finished; output utf-8 sequence */
+ 		    yield = string_catn(yield, seq_buff, seq_len);
+ 		    index = 0;
+ 		    }
+ 		}
+ 	      }
+ 	    else	/* no bytes left: new sequence */
+ 	      {
+-	      if(!(c & 0x80))	/* 1-byte sequence, US-ASCII, keep it */
++	      if (!(c & 0x80))	/* 1-byte sequence, US-ASCII, keep it */
+ 		{
+ 		yield = string_catn(yield, &c, 1);
+ 		continue;
+ 		}
+-	      if((c & 0xe0) == 0xc0)		/* 2-byte sequence */
+-		{
+-		if(c == 0xc0 || c == 0xc1)	/* 0xc0 and 0xc1 are illegal */
++	      if ((c & 0xe0) == 0xc0)		/* 2-byte sequence */
++		if (c == 0xc0 || c == 0xc1)	/* 0xc0 and 0xc1 are illegal */
+ 		  complete = -1;
+ 		else
+ 		  {
+-		    bytes_left = 1;
+-		    codepoint = c & 0x1f;
++		  bytes_left = 1;
++		  codepoint = c & 0x1f;
+ 		  }
+-		}
+-	      else if((c & 0xf0) == 0xe0)		/* 3-byte sequence */
++	      else if ((c & 0xf0) == 0xe0)		/* 3-byte sequence */
+ 		{
+ 		bytes_left = 2;
+ 		codepoint = c & 0x0f;
+ 		}
+-	      else if((c & 0xf8) == 0xf0)		/* 4-byte sequence */
++	      else if ((c & 0xf8) == 0xf0)		/* 4-byte sequence */
+ 		{
+ 		bytes_left = 3;
+ 		codepoint = c & 0x07;
+ 		}
+ 	      else	/* invalid or too long (RFC3629 allows only 4 bytes) */
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -69,10 +69,13 @@ JH/28 Bug 2996: Fix a crash in the smtp
+       to close it tried to use an uninitialized variable.  This would afftect
+       high-volume sites more, especially when running mailing-list-style loads.
+       Pollution of logs was the major effect, as the other process delivered
+       the message.  Found and partly investigated by Graeme Fowler.
+ 
++JH/31 Bug 2998: Fix ${utf8clean:...} to disallow UTF-16 surrogate codepoints.
++      Found and fixed by Jasen Betts. No testcase for this as my usual text
++      editor insists on emitting only valid UTF-8.
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
diff -Nru exim4-4.96/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch exim4-4.96/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch
--- exim4-4.96/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,92 @@
+From 8e9770348dc4173ab83657ee023c22f479ebb712 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 24 Jul 2023 13:30:40 +0100
+Subject: [PATCH] GnuTLS: fix crash with "tls_dhparam = none"
+
+---
+ doc/ChangeLog         |  4 ++++
+ src/tls-gnu.c             | 16 +++++++++-------
+ test/log/2049                 |  7 +++++++
+ test/scripts/2000-GnuTLS/2049 |  8 ++++++++
+ 4 files changed, 28 insertions(+), 7 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -73,10 +73,14 @@ JH/28 Bug 2996: Fix a crash in the smtp
+ 
+ JH/31 Bug 2998: Fix ${utf8clean:...} to disallow UTF-16 surrogate codepoints.
+       Found and fixed by Jasen Betts. No testcase for this as my usual text
+       editor insists on emitting only valid UTF-8.
+ 
++JH/32 Fix "tls_dhparam = none" under GnuTLS.  At least with 3.7.9 this gave
++      a null-indireciton SIGSEGV for the receive process.
++
++
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+       after reception to before a subsequent reception.  This should
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -712,11 +712,11 @@ exist, we generate them. This means that
+ The new file is written as a temporary file and renamed, so that an incomplete
+ file is never present. If two processes both compute some new parameters, you
+ waste a bit of effort, but it doesn't seem worth messing around with locking to
+ prevent this.
+ 
+-Returns:     OK/DEFER/FAIL
++Returns:     OK/DEFER (expansion issue)/FAIL (requested none)
+ */
+ 
+ static int
+ init_server_dh(uschar ** errstr)
+ {
+@@ -750,11 +750,11 @@ if (!exp_tls_dhparam)
+ else if (Ustrcmp(exp_tls_dhparam, "historic") == 0)
+   use_file_in_spool = TRUE;
+ else if (Ustrcmp(exp_tls_dhparam, "none") == 0)
+   {
+   DEBUG(D_tls) debug_printf("Requested no DH parameters\n");
+-  return OK;
++  return FAIL;
+   }
+ else if (exp_tls_dhparam[0] != '/')
+   {
+   if (!(m.data = US std_dh_prime_named(exp_tls_dhparam)))
+     return tls_error(US"No standard prime named", exp_tls_dhparam, NULL, errstr);
+@@ -1971,27 +1971,29 @@ Arguments:
+ 
+ Returns:          OK/DEFER/FAIL
+ */
+ 
+ static int
+-tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr)
++tls_set_remaining_x509(exim_gnutls_state_st * state, uschar ** errstr)
+ {
+-int rc;
+-const host_item *host = state->host;  /* macro should be reconsidered? */
++int rc = OK;
++const host_item * host = state->host;  /* macro should be reconsidered? */
+ 
+ /* Create D-H parameters, or read them from the cache file. This function does
+ its own SMTP error messaging. This only happens for the server, TLS D-H ignores
+ client-side params. */
+ 
+ if (!state->host)
+   {
+   if (!dh_server_params)
+-    if ((rc = init_server_dh(errstr)) != OK) return rc;
++    if ((rc = init_server_dh(errstr)) == DEFER) return rc;
+ 
+   /* Unnecessary & discouraged with 3.6.0 or later, according to docs.  But without it,
+   no DHE- ciphers are advertised. */
+-  gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
++
++  if (rc == OK)
++    gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params);
+   }
+ 
+ /* Link the credentials to the session. */
+ 
+ if ((rc = gnutls_credentials_set(state->session,
diff -Nru exim4-4.96/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch exim4-4.96/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch
--- exim4-4.96/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,294 @@
+From 6707bfa9fb78858de938a1abca2846c820c5ded7 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 3 Aug 2023 18:40:42 +0100
+Subject: [PATCH 2/2] Fix $recipients expansion when used within ${run...}. 
+ Bug 3013
+
+Broken-by: cfe6acff2ddc
+---
+ doc/ChangeLog          |  3 +++
+ src/deliver.c              |  2 +-
+ src/expand.c               |  5 ++---
+ src/functions.h            |  2 +-
+ src/macros.h               |  5 +++++
+ src/routers/queryprogram.c |  3 +--
+ src/smtp_in.c              |  4 ++--
+ src/transport.c            | 18 +++++++++---------
+ src/transports/lmtp.c      |  4 ++--
+ src/transports/pipe.c      | 11 ++++++-----
+ src/transports/smtp.c      |  2 +-
+ test/log/0635                  |  2 +-
+ 12 files changed, 34 insertions(+), 27 deletions(-)
+
+--- a/src/deliver.c
++++ b/src/deliver.c
+@@ -2382,11 +2382,11 @@ if ((pid = exim_fork(US"delivery-local")
+ 
+     if (tp->filter_command)
+       {
+       ok = transport_set_up_command(&transport_filter_argv,
+         tp->filter_command,
+-        TRUE, PANIC, addr, FALSE, US"transport filter", NULL);
++        TSUC_EXPAND_ARGS, PANIC, addr, US"transport filter", NULL);
+       transport_filter_timeout = tp->filter_timeout;
+       }
+     else transport_filter_argv = NULL;
+ 
+     if (ok)
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -5527,11 +5527,11 @@ while (*s)
+ 
+     case EITEM_RUN:
+       {
+       FILE * f;
+       const uschar * arg, ** argv;
+-      BOOL late_expand = TRUE;
++      unsigned late_expand = TSUC_EXPAND_ARGS | TSUC_ALLOW_TAINTED_ARGS | TSUC_ALLOW_RECIPIENTS;
+ 
+       if (expand_forbid & RDO_RUN)
+         {
+         expand_string_message = US"running a command is not permitted";
+         goto EXPAND_FAILED;
+@@ -5540,11 +5540,11 @@ while (*s)
+       /* Handle options to the "run" */
+ 
+       while (*s == ',')
+ 	{
+ 	if (Ustrncmp(++s, "preexpand", 9) == 0)
+-	  { late_expand = FALSE; s += 9; }
++	  { late_expand = 0; s += 9; }
+ 	else
+ 	  {
+ 	  const uschar * t = s;
+ 	  while (isalpha(*++t)) ;
+ 	  expand_string_message = string_sprintf("bad option '%.*s' for run",
+@@ -5599,11 +5599,10 @@ while (*s)
+         if (!transport_set_up_command(&argv,    /* anchor for arg list */
+             arg,                                /* raw command */
+ 	    late_expand,		/* expand args if not already done */
+             0,                          /* not relevant when... */
+             NULL,                       /* no transporting address */
+-	    late_expand,		/* allow tainted args, when expand-after-split */
+             US"${run} expansion",       /* for error messages */
+             &expand_string_message))    /* where to put error message */
+           goto EXPAND_FAILED;
+ 
+         /* Create the child process, making it a group leader. */
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -616,11 +616,11 @@ extern BOOL    transport_pass_socket(con
+ 			, unsigned, unsigned, unsigned
+ #endif
+ 			);
+ extern uschar *transport_rcpt_address(address_item *, BOOL);
+ extern BOOL    transport_set_up_command(const uschar ***, const uschar *,
+-		 BOOL, int, address_item *, BOOL, const uschar *, uschar **);
++		 unsigned, int, address_item *, const uschar *, uschar **);
+ extern void    transport_update_waiting(host_item *, uschar *);
+ extern BOOL    transport_write_block(transport_ctx *, uschar *, int, BOOL);
+ extern void    transport_write_reset(int);
+ extern BOOL    transport_write_string(int, const char *, ...);
+ extern BOOL    transport_headers_send(transport_ctx *,
+--- a/src/macros.h
++++ b/src/macros.h
+@@ -1112,6 +1112,11 @@ should not be one active. */
+ 
+ #define NOTIFIER_SOCKET_NAME	"exim_daemon_notify"
+ #define NOTIFY_MSG_QRUN		1	/* Notify message types */
+ #define NOTIFY_QUEUE_SIZE_REQ	2
+ 
++/* Flags for transport_set_up_command() */
++#define TSUC_EXPAND_ARGS       BIT(0)
++#define TSUC_ALLOW_TAINTED_ARGS        BIT(1)
++#define TSUC_ALLOW_RECIPIENTS  BIT(2)
++
+ /* End of macros.h */
+--- a/src/routers/queryprogram.c
++++ b/src/routers/queryprogram.c
+@@ -286,14 +286,13 @@ if (curr_uid != root_uid && (uid != curr
+ 
+ /* Set up the command to run */
+ 
+ if (!transport_set_up_command(&argvptr, /* anchor for arg list */
+     ob->command,                        /* raw command */
+-    TRUE,                               /* expand the arguments */
++    TSUC_EXPAND_ARGS,                   /* arguments expanded but must not be tainted */
+     0,                                  /* not relevant when... */
+     NULL,                               /* no transporting address */
+-    FALSE,				/* args must be untainted */
+     US"queryprogram router",            /* for error messages */
+     &addr->message))                    /* where to put error message */
+   return DEFER;
+ 
+ /* Create the child process, making it a group leader. */
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5840,12 +5840,12 @@ while (done <= 0)
+ 	{
+ 	uschar *error;
+ 	BOOL rc;
+ 	etrn_command = smtp_etrn_command;
+ 	deliver_domain = smtp_cmd_data;
+-	rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
+-	  FALSE, US"ETRN processing", &error);
++	rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL,
++	  US"ETRN processing", &error);
+ 	deliver_domain = NULL;
+ 	if (!rc)
+ 	  {
+ 	  log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
+ 	    error);
+--- a/src/transport.c
++++ b/src/transport.c
+@@ -2082,34 +2082,34 @@ return FALSE;
+ *          Set up direct (non-shell) command     *
+ *************************************************/
+ 
+ /* This function is called when a command line is to be parsed and executed
+ directly, without the use of /bin/sh. It is called by the pipe transport,
+-the queryprogram router, and also from the main delivery code when setting up a
++the queryprogram router, for any ${run } expansion,
++and also from the main delivery code when setting up a
+ transport filter process. The code for ETRN also makes use of this; in that
+ case, no addresses are passed.
+ 
+ Arguments:
+   argvptr            pointer to anchor for argv vector
+   cmd                points to the command string (modified IN PLACE)
+-  expand_arguments   true if expansion is to occur
++  flags		     bits for expand-args, allow taint, allow $recipients
+   expand_failed      error value to set if expansion fails; not relevant if
+                      addr == NULL
+   addr               chain of addresses, or NULL
+-  allow_tainted_args as it says; used for ${run}
+   etext              text for use in error messages
+   errptr             where to put error message if addr is NULL;
+                      otherwise it is put in the first address
+ 
+ Returns:             TRUE if all went well; otherwise an error will be
+                      set in the first address and FALSE returned
+ */
+ 
+ BOOL
+ transport_set_up_command(const uschar *** argvptr, const uschar * cmd,
+-  BOOL expand_arguments, int expand_failed, address_item * addr,
+-  BOOL allow_tainted_args, const uschar * etext, uschar ** errptr)
++  unsigned flags, int expand_failed, address_item * addr,
++  const uschar * etext, uschar ** errptr)
+ {
+ const uschar ** argv, * s;
+ int address_count = 0, argcount = 0, max_args;
+ 
+ /* Get store in which to build an argument list. Count the number of addresses
+@@ -2180,14 +2180,14 @@ DEBUG(D_transport)
+   debug_printf("direct command:\n");
+   for (int i = 0; argv[i]; i++)
+     debug_printf("  argv[%d] = '%s'\n", i, string_printing(argv[i]));
+   }
+ 
+-if (expand_arguments)
++if (flags & TSUC_EXPAND_ARGS)
+   {
+-  BOOL allow_dollar_recipients = addr && addr->parent
+-    && Ustrcmp(addr->parent->address, "system-filter") == 0;
++  BOOL allow_dollar_recipients = (flags & TSUC_ALLOW_RECIPIENTS)
++    || (addr && addr->parent && Ustrcmp(addr->parent->address, "system-filter") == 0);	/*XXX could we check this at caller? */
+ 
+   for (int i = 0; argv[i]; i++)
+     {
+     DEBUG(D_expand) debug_printf_indent("arg %d\n", i);
+ 
+@@ -2370,11 +2370,11 @@ if (expand_arguments)
+ 	{			/* hack, would be good to not need it */
+ 	DEBUG(D_transport)
+ 	  debug_printf("SPECIFIC TESTSUITE EXEMPTION: tainted arg '%s'\n",
+ 		      expanded_arg);
+ 	}
+-      else if (  !allow_tainted_args
++      else if (  !(flags & TSUC_ALLOW_TAINTED_ARGS)
+ 	      && arg_is_tainted(expanded_arg, i, addr, etext, errptr))
+ 	return FALSE;
+       argv[i] = expanded_arg;
+       }
+     }
+--- a/src/transports/lmtp.c
++++ b/src/transports/lmtp.c
+@@ -487,12 +487,12 @@ argument list and expanding the items. *
+ 
+ if (ob->cmd)
+   {
+   DEBUG(D_transport) debug_printf("using command %s\n", ob->cmd);
+   sprintf(CS buffer, "%.50s transport", tblock->name);
+-  if (!transport_set_up_command(&argv, ob->cmd, TRUE, PANIC, addrlist, FALSE,
+-	buffer, NULL))
++  if (!transport_set_up_command(&argv, ob->cmd, TSUC_EXPAND_ARGS, PANIC,
++	addrlist, buffer, NULL))
+     return FALSE;
+ 
+   /* If the -N option is set, can't do any more. Presume all has gone well. */
+   if (f.dont_deliver)
+     goto MINUS_N;
+--- a/src/transports/pipe.c
++++ b/src/transports/pipe.c
+@@ -289,24 +289,25 @@ Arguments:
+ Returns:             TRUE if all went well; otherwise an error will be
+                      set in the first address and FALSE returned
+ */
+ 
+ static BOOL
+-set_up_direct_command(const uschar ***argvptr, uschar *cmd,
+-  BOOL expand_arguments, int expand_fail, address_item *addr, uschar *tname,
+-  pipe_transport_options_block *ob)
++set_up_direct_command(const uschar *** argvptr, uschar * cmd,
++  BOOL expand_arguments, int expand_fail, address_item * addr, uschar * tname,
++  pipe_transport_options_block * ob)
+ {
+ BOOL permitted = FALSE;
+ const uschar **argv;
+ 
+ /* Set up "transport <name>" to be put in any error messages, and then
+ call the common function for creating an argument list and expanding
+ the items if necessary. If it fails, this function fails (error information
+ is in the addresses). */
+ 
+-if (!transport_set_up_command(argvptr, cmd, expand_arguments, expand_fail,
+-      addr, FALSE, string_sprintf("%.50s transport", tname), NULL))
++if (!transport_set_up_command(argvptr, cmd,
++      expand_arguments ? TSUC_EXPAND_ARGS : 0,
++      expand_fail, addr, string_sprintf("%.50s transport", tname), NULL))
+   return FALSE;
+ 
+ /* Point to the set-up arguments. */
+ 
+ argv = *argvptr;
+--- a/src/transports/smtp.c
++++ b/src/transports/smtp.c
+@@ -3803,11 +3803,11 @@ if (tblock->filter_command)
+ 
+   /* On failure, copy the error to all addresses, abandon the SMTP call, and
+   yield ERROR. */
+ 
+   if (!transport_set_up_command(&transport_filter_argv,
+-	tblock->filter_command, TRUE, DEFER, addrlist, FALSE,
++	tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist,
+ 	string_sprintf("%.50s transport filter", tblock->name), NULL))
+     {
+     set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+       FALSE, &sx->delivery_start);
+     yield = ERROR;
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -76,10 +76,12 @@ JH/31 Bug 2998: Fix ${utf8clean:...} to
+       editor insists on emitting only valid UTF-8.
+ 
+ JH/32 Fix "tls_dhparam = none" under GnuTLS.  At least with 3.7.9 this gave
+       a null-indireciton SIGSEGV for the receive process.
+ 
++JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}.
++      In 4.96 this would expand to empty.
+ 
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
diff -Nru exim4-4.96/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch exim4-4.96/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch
--- exim4-4.96/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,42 @@
+From 36bc854c86908ee921225c1d30e35c4d59eed822 Mon Sep 17 00:00:00 2001
+From: Andreas Metzler <ametzler@bebt.de>
+Date: Mon, 14 Aug 2023 17:27:16 +0100
+Subject: [PATCH] GnuTLS: fix autogen cert expiry date.  Bug 3014
+
+Broken-by: 48e9099006
+---
+ doc/ChangeLog | 3 +++
+ src/tls-gnu.c     | 2 +-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -79,10 +79,13 @@ JH/32 Fix "tls_dhparam = none" under Gnu
+       a null-indireciton SIGSEGV for the receive process.
+ 
+ JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}.
+       In 4.96 this would expand to empty.
+ 
++JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server
++      certificate.  Find and fix by Andreas Metzler.
++
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+       after reception to before a subsequent reception.  This should
+--- a/src/tls-gnu.c
++++ b/src/tls-gnu.c
+@@ -1001,11 +1001,11 @@ if ((rc = gnutls_x509_privkey_generate(p
+ where = US"configuring cert";
+ now = 1;
+ if (  (rc = gnutls_x509_crt_set_version(cert, 3))
+    || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now)))
+    || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL)))
+-   || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60))	/* 2 hour */
++   || (rc = gnutls_x509_crt_set_expiration_time(cert, now + (long)2 * 60 * 60))	/* 2 hour */
+    || (rc = gnutls_x509_crt_set_key(cert, pkey))
+ 
+    || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
+ 	      GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2))
+    || (rc = gnutls_x509_crt_set_dn_by_oid(cert,
diff -Nru exim4-4.96/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch exim4-4.96/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch
--- exim4-4.96/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,77 @@
+From 21b172df101c2c52faf0cc56a502395451975be9 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Thu, 24 Aug 2023 15:51:21 +0100
+Subject: [PATCH 2/2] Re-fix live variable $value free.  The inital fix
+ resulted in $value from ${run...} not being available later, which is a
+ documented feature.
+
+Broken=by: cf3fecb9e873
+---
+ doc/doc-docbook/spec.xfpt |  1 +
+ doc/ChangeLog     |  4 ++--
+ src/exim.c            |  3 ++-
+ test/confs/0635           |  1 +
+ test/log/0635             |  1 +
+ test/mail/0635.CALLER     | 13 +++++++++++++
+ 6 files changed, 20 insertions(+), 3 deletions(-)
+ create mode 100644 test/mail/0635.CALLER
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -76,10 +76,13 @@ JH/31 Bug 2998: Fix ${utf8clean:...} to
+       editor insists on emitting only valid UTF-8.
+ 
+ JH/32 Fix "tls_dhparam = none" under GnuTLS.  At least with 3.7.9 this gave
+       a null-indireciton SIGSEGV for the receive process.
+ 
++JH/33 Fix free for live variable $value created by a ${run ...} expansion during
++      -bh use.  Internal checking would spot this and take a panic.
++
+ JH/34 Bug 3013: Fix use of $recipients within arguments for ${run...}.
+       In 4.96 this would expand to empty.
+ 
+ JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server
+       certificate.  Find and fix by Andreas Metzler.
+--- a/src/exim.c
++++ b/src/exim.c
+@@ -5754,11 +5754,11 @@ for (BOOL more = TRUE; more; )
+     for (int i = 0; i < count; i++)
+       {
+       int start, end, domain;
+       uschar * errmess;
+       /* There can be multiple addresses, so EXIM_DISPLAYMAIL_MAX (tuned for 1) is too short.
+-       * We'll still want to cap it to something, just in case. */
++      We'll still want to cap it to something, just in case. */
+       uschar * s = string_copy_taint(
+ 	exim_str_fail_toolong(list[i], BIG_BUFFER_SIZE, "address argument"),
+ 	GET_TAINTED);
+ 
+       /* Loop for each comma-separated address */
+@@ -6089,10 +6089,11 @@ MORELOOP:
+   callout_address = NULL;
+   sending_ip_address = NULL;
+   deliver_localpart_data = deliver_domain_data =
+   recipient_data = sender_data = NULL;
+   acl_var_m = NULL;
++  lookup_value = NULL;                            /* Can be set by ACL */
+ 
+   store_reset(reset_point);
+   }
+ 
+ exim_exit(EXIT_SUCCESS);   /* Never returns */
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -9650,10 +9650,13 @@ ${run <options> {<command arg list>}{<st
+     If the command requires shell idioms, such as the > redirect operator, the
+     shell must be invoked directly, such as with:
+ 
+     ${run{/bin/bash -c "/usr/bin/id >/tmp/id"}{yes}{yes}}
+ 
++    Note that $value will not persist beyond the reception of a single
++    message.
++
+     The return code from the command is put in the variable $runrc, and this
+     remains set afterwards, so in a filter file you can do things like this:
+ 
+     if "${run{x y z}{}}$runrc" is 1 then ...
+       elif $runrc is 2 then ...
diff -Nru exim4-4.96/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch exim4-4.96/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch
--- exim4-4.96/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,309 @@
+From a95acb1c19c2e3600ef327c71318e33316d34440 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 5 Oct 2023 22:49:57 +0200
+Subject: [PATCH 1/3] fix: string_is_ip_address (CVE-2023-42117) Bug 3031
+
+---
+ doc/ChangeLog | 206 ++++++++++++++++++++++++++++++++++++++++++
+ src/expand.c      |  14 ++-
+ src/functions.h   |   1 +
+ src/string.c      | 200 +++++++++++++++++++++-------------------
+ 4 files changed, 323 insertions(+), 98 deletions(-)
+
+diff --git a/src/expand.c b/src/expand.c
+index 36c9f423b..4986e4657 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -2646,17 +2646,25 @@ switch(cond_type = identify_operator(&s, &opname))
+       }
+     *yield = (Ustat(sub[0], &statbuf) == 0) == testfor;
+     break;
+ 
+     case ECOND_ISIP:
+     case ECOND_ISIP4:
+     case ECOND_ISIP6:
+-    rc = string_is_ip_address(sub[0], NULL);
+-    *yield = ((cond_type == ECOND_ISIP)? (rc != 0) :
+-             (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
++    {
++      const uschar *errp;
++      const uschar **errpp;
++      DEBUG(D_expand) errpp = &errp; else errpp = 0;
++      if (0 == (rc = string_is_ip_addressX(sub[0], NULL, errpp)))
++        DEBUG(D_expand) debug_printf("failed: %s\n", errp);
++
++      *yield = ( cond_type == ECOND_ISIP  ? rc != 0 :
++                 cond_type == ECOND_ISIP4 ? rc == 4 : rc == 6) == testfor;
++    }
++
+     break;
+ 
+     /* Various authentication tests - all optionally compiled */
+ 
+     case ECOND_PAM:
+     #ifdef SUPPORT_PAM
+     rc = auth_call_pam(sub[0], &expand_string_message);
+diff --git a/src/functions.h b/src/functions.h
+index 224666cb1..3c8104d25 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -552,14 +552,15 @@ extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
+ extern int     string_compare_by_pointer(const void *, const void *);
+ extern uschar *string_copy_dnsdomain(uschar *);
+ extern uschar *string_copy_malloc(const uschar *);
+ extern uschar *string_dequote(const uschar **);
+ extern uschar *string_format_size(int, uschar *);
+ extern int     string_interpret_escape(const uschar **);
+ extern int     string_is_ip_address(const uschar *, int *);
++extern int     string_is_ip_addressX(const uschar *, int *, const uschar **);
+ #ifdef SUPPORT_I18N
+ extern BOOL    string_is_utf8(const uschar *);
+ #endif
+ extern const uschar *string_printing2(const uschar *, int);
+ extern uschar *string_split_message(uschar *);
+ extern uschar *string_unprinting(uschar *);
+ #ifdef SUPPORT_I18N
+diff --git a/src/string.c b/src/string.c
+index a5161bb31..9aefc2b58 100644
+--- a/src/string.c
++++ b/src/string.c
+@@ -25,131 +25,141 @@ address (assuming HAVE_IPV6 is set). If a mask is permitted and one is present,
+ and maskptr is not NULL, its offset is placed there.
+ 
+ Arguments:
+   s         a string
+   maskptr   NULL if no mask is permitted to follow
+             otherwise, points to an int where the offset of '/' is placed
+             if there is no / followed by trailing digits, *maskptr is set 0
++  errp      NULL if no diagnostic information is required, and if the netmask
++            length should not be checked. Otherwise it is set pointing to a short
++            descriptive text.
+ 
+ Returns:    0 if the string is not a textual representation of an IP address
+             4 if it is an IPv4 address
+             6 if it is an IPv6 address
+-*/
+ 
++The legacy string_is_ip_address() function follows below.
++*/
+ int
+-string_is_ip_address(const uschar *s, int *maskptr)
+-{
+-int yield = 4;
++string_is_ip_addressX(const uschar *ip_addr, int *maskptr, const uschar **errp) {
++  struct addrinfo hints;
++  struct addrinfo *res;
+ 
+-/* If an optional mask is permitted, check for it. If found, pass back the
+-offset. */
++  uschar *slash, *percent;
+ 
+-if (maskptr)
++  uschar *endp = 0;
++  long int mask = 0;
++  const uschar *addr = 0;
++
++  /* If there is a slash, but we didn't request a (optional) netmask,
++  we return failure, as we do if the mask isn't a pure numerical value,
++  or if it is negative. The actual length is checked later, once we know
++  the address family. */
++  if (slash = Ustrchr(ip_addr, '/'))
+   {
+-  const uschar *ss = s + Ustrlen(s);
+-  *maskptr = 0;
+-  if (s != ss && isdigit(*(--ss)))
++    if (!maskptr)
+     {
+-    while (ss > s && isdigit(ss[-1])) ss--;
+-    if (ss > s && *(--ss) == '/') *maskptr = ss - s;
++      if (errp) *errp = "netmask found, but not requested";
++      return 0;
+     }
+-  }
+-
+-/* A colon anywhere in the string => IPv6 address */
+-
+-if (Ustrchr(s, ':') != NULL)
+-  {
+-  BOOL had_double_colon = FALSE;
+-  BOOL v4end = FALSE;
+-
+-  yield = 6;
+-
+-  /* An IPv6 address must start with hex digit or double colon. A single
+-  colon is invalid. */
+-
+-  if (*s == ':' && *(++s) != ':') return 0;
+-
+-  /* Now read up to 8 components consisting of up to 4 hex digits each. There
+-  may be one and only one appearance of double colon, which implies any number
+-  of binary zero bits. The number of preceding components is held in count. */
+ 
+-  for (int count = 0; count < 8; count++)
++    uschar *rest;
++    mask = Ustrtol(slash+1, &rest, 10);
++    if (*rest || mask < 0)
+     {
+-    /* If the end of the string is reached before reading 8 components, the
+-    address is valid provided a double colon has been read. This also applies
+-    if we hit the / that introduces a mask or the % that introduces the
+-    interface specifier (scope id) of a link-local address. */
+-
+-    if (*s == 0 || *s == '%' || *s == '/') return had_double_colon ? yield : 0;
+-
+-    /* If a component starts with an additional colon, we have hit a double
+-    colon. This is permitted to appear once only, and counts as at least
+-    one component. The final component may be of this form. */
+-
+-    if (*s == ':')
+-      {
+-      if (had_double_colon) return 0;
+-      had_double_colon = TRUE;
+-      s++;
+-      continue;
+-      }
+-
+-    /* If the remainder of the string contains a dot but no colons, we
+-    can expect a trailing IPv4 address. This is valid if either there has
+-    been no double-colon and this is the 7th component (with the IPv4 address
+-    being the 7th & 8th components), OR if there has been a double-colon
+-    and fewer than 6 components. */
+-
+-    if (Ustrchr(s, ':') == NULL && Ustrchr(s, '.') != NULL)
+-      {
+-      if ((!had_double_colon && count != 6) ||
+-          (had_double_colon && count > 6)) return 0;
+-      v4end = TRUE;
+-      yield = 6;
+-      break;
+-      }
+-
+-    /* Check for at least one and not more than 4 hex digits for this
+-    component. */
+-
+-    if (!isxdigit(*s++)) return 0;
+-    if (isxdigit(*s) && isxdigit(*(++s)) && isxdigit(*(++s))) s++;
+-
+-    /* If the component is terminated by colon and there is more to
+-    follow, skip over the colon. If there is no more to follow the address is
+-    invalid. */
+-
+-    if (*s == ':' && *(++s) == 0) return 0;
++      if (errp) *errp = "netmask not numeric or <0";
++      return 0;
+     }
+ 
+-  /* If about to handle a trailing IPv4 address, drop through. Otherwise
+-  all is well if we are at the end of the string or at the mask or at a percent
+-  sign, which introduces the interface specifier (scope id) of a link local
+-  address. */
++    *maskptr = slash - ip_addr;     /* offset of the slash */
++    endp = slash;
++  } else if (maskptr) *maskptr = 0; /* no slash found */
+ 
+-  if (!v4end)
+-    return (*s == 0 || *s == '%' ||
+-           (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0;
++  /* The interface-ID suffix (%<id>) is optional (for IPv6). If it
++  exists, we check it syntactically. Later, if we know the address
++  family is IPv4, we might reject it.
++  The interface-ID is mutually exclusive with the netmask, to the
++  best of my knowledge. */
++  if (percent = Ustrchr(ip_addr, '%'))
++  {
++    if (slash)
++    {
++      if (errp) *errp = "interface-ID and netmask are mutually exclusive";
++      return 0;
++    }
++    for (uschar *p = percent+1; *p; p++)
++        if (!isalnum(*p) && !ispunct(*p))
++        {
++          if (errp) *errp = "interface-ID must match [[:alnum:][:punct:]]";
++          return 0;
++        }
++    endp = percent;
+   }
+ 
+-/* Test for IPv4 address, which may be the tail-end of an IPv6 address. */
+-
+-for (int i = 0; i < 4; i++)
++  /* inet_pton() can't parse netmasks and interface IDs, so work on a shortened copy
++  allocated on the current stack */
++  if (endp) {
++    ptrdiff_t l = endp - ip_addr;
++    if (l > 255)
++    {
++      if (errp) *errp = "rudiculous long ip address string";
++      return 0;
++    }
++    addr = alloca(l+1); /* *BSD does not have strndupa() */
++    Ustrncpy((uschar *)addr, ip_addr, l);
++    ((uschar*)addr)[l] = '\0';
++  } else addr = ip_addr;
++
++  int af;
++  union { /* we do not need this, but inet_pton() needs a place for storage */
++    struct in_addr sa4;
++    struct in6_addr sa6;
++  } sa;
++
++  af = Ustrchr(addr, ':') ? AF_INET6 : AF_INET;
++  if (!inet_pton(af, addr, &sa))
+   {
+-  long n;
+-  uschar * end;
+-
+-  if (i != 0 && *s++ != '.') return 0;
+-  n = strtol(CCS s, CSS &end, 10);
+-  if (n > 255 || n < 0 || end <= s || end > s+3) return 0;
+-  s = end;
++    if (errp) *errp = af == AF_INET6 ? "IP address string not parsable as IPv6"
++                                     : "IP address string not parsable IPv4";
++    return 0;
+   }
++  /* we do not check the values of the mask here, as
++  this is done on the callers side (but I don't understand why), so
++  actually I'd like to do it here, but it breaks at least 0002 */
++  switch (af)
++  {
++    case AF_INET6:
++        if (errp && mask > 128)
++        {
++          *errp = "IPv6 netmask value must not be >128";
++          return 0;
++        }
++        return 6;
++    case AF_INET:
++        if (percent)
++        {
++          if (errp) *errp = "IPv4 address string must not have an interface-ID";
++          return 0;
++        }
++        if (errp && mask > 32) {
++          *errp = "IPv4 netmask value must not be >32";
++          return 0;
++        }
++        return 4;
++    default:
++        if (errp) *errp = "unknown address family (should not happen)";
++        return 0;
++ }
++}
+ 
+-return !*s || (*s == '/' && maskptr && *maskptr != 0) ? yield : 0;
++int
++string_is_ip_address(const uschar *ip_addr, int *maskptr) {
++  return string_is_ip_addressX(ip_addr, maskptr, 0);
+ }
++
+ #endif  /* COMPILE_UTILITY */
+ 
+ 
+ /*************************************************
+ *              Format message size               *
+ *************************************************/
+ 
+-- 
+2.42.0
+
diff -Nru exim4-4.96/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch exim4-4.96/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch
--- exim4-4.96/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/76-02-SPF-harden-against-crafted-DNS-responses.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,64 @@
+From 654056e44fc93a0ee7c09d1228933e8af6862206 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 10 Oct 2023 12:45:27 +0100
+Subject: [PATCH 2/3] SPF: harden against crafted DNS responses
+
+(cherry picked from commit 4f07f38374f8662c318699fb30432273ffcfe0d3)
+---
+ src/spf.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/src/spf.c b/src/spf.c
+index db6eea3a8..1981d81b6 100644
+--- a/src/spf.c
++++ b/src/spf.c
+@@ -116,14 +116,15 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+     {
+     const uschar * s = rr->data;
+ 
+     srr.ttl = rr->ttl;
+     switch(rr_type)
+       {
+       case T_MX:
++	if (rr->size < 2) continue;
+ 	s += 2;	/* skip the MX precedence field */
+       case T_PTR:
+ 	{
+ 	uschar * buf = store_malloc(256);
+ 	(void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s,
+ 	  (DN_EXPAND_ARG4_TYPE)buf, 256);
+ 	s = buf;
+@@ -131,24 +132,28 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
+ 	}
+ 
+       case T_TXT:
+ 	{
+ 	gstring * g = NULL;
+ 	uschar chunk_len;
+ 
++	if (rr->size < 1+6) continue;		/* min for version str */
+ 	if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0)
+ 	  {
+ 	  HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n",
+ 	    (int) s[0], s+1);
+ 	  continue;
+ 	  }
+ 
+-	for (int off = 0; off < rr->size; off += chunk_len)
++	/* require 1 byte for the chunk_len */
++	for (int off = 0; off < rr->size - 1; off += chunk_len)
+ 	  {
+-	  if (!(chunk_len = s[off++])) break;
++	  if (  !(chunk_len = s[off++])
++	     || rr->size < off + chunk_len	/* ignore bogus size chunks */
++	     ) break;
+ 	  g = string_catn(g, s+off, chunk_len);
+ 	  }
+ 	if (!g)
+ 	  continue;
+ 	gstring_release_unused(g);
+ 	s = string_copy_malloc(string_from_gstring(g));
+ 	DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s);
+-- 
+2.42.0
+
diff -Nru exim4-4.96/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch exim4-4.96/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch
--- exim4-4.96/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,244 @@
+From f6b1f8e7d642f82d830a71b78699a4349e0158e1 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Tue, 10 Oct 2023 23:03:28 +0100
+Subject: [PATCH 3/3] Harden dnsdb against crafted DNS responses.  Bug 3033
+
+(cherry picked from commit 8787c8994f07c23c3664d76926e02f07314d699d)
+---
+ doc/ChangeLog   |  3 ++
+ src/dns.c           | 11 +++---
+ src/lookups/dnsdb.c | 78 +++++++++++++++++++++++++++--------------
+ 3 files changed, 59 insertions(+), 33 deletions(-)
+
+diff --git a/src/dns.c b/src/dns.c
+index 7d7ee0c04..8dc3695a1 100644
+--- a/src/dns.c
++++ b/src/dns.c
+@@ -300,15 +300,15 @@ return string_from_gstring(g);
+ 
+ /* Increment the aptr in dnss, checking against dnsa length.
+ Return: TRUE for a bad result
+ */
+ static BOOL
+ dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta)
+ {
+-return (dnss->aptr += delta) >= dnsa->answer + dnsa->answerlen;
++return (dnss->aptr += delta) > dnsa->answer + dnsa->answerlen;
+ }
+ 
+ /*************************************************
+ *       Get next DNS record from answer block    *
+ *************************************************/
+ 
+ /* Call this with reset == RESET_ANSWERS to scan the answer block, reset ==
+@@ -384,15 +384,15 @@ if (reset != RESET_NEXT)
+       namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+         dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+       if (namelen < 0) goto null_return;
+       /* skip name, type, class & TTL */
+       TRACE trace = "A-hdr";
+       if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return;
+       GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
+-      /* skip over it */
++      /* skip over it, checking for a bogus size */
+       TRACE trace = "A-skip";
+       if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return;
+       }
+     dnss->rrcount = reset == RESET_AUTHORITY
+       ? ntohs(h->nscount) : ntohs(h->arcount);
+     TRACE debug_printf("%s: reset (%s rrcount %d)\n", __FUNCTION__,
+       reset == RESET_AUTHORITY ? "NS" : "AR", dnss->rrcount);
+@@ -424,18 +424,17 @@ if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
+ GETSHORT(dnss->srr.type, dnss->aptr);		/* Record type */
+ TRACE trace = "R-class";
+ if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return;	/* Don't want class */
+ GETLONG(dnss->srr.ttl, dnss->aptr);		/* TTL */
+ GETSHORT(dnss->srr.size, dnss->aptr);		/* Size of data portion */
+ dnss->srr.data = dnss->aptr;			/* The record's data follows */
+ 
+-/* Unchecked increment ok here since no further access on this iteration;
+-will be checked on next at "R-name". */
+-
+-dnss->aptr += dnss->srr.size;			/* Advance to next RR */
++/* skip over it, checking for a bogus size */
++if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size))
++  goto null_return;
+ 
+ /* Return a pointer to the dns_record structure within the dns_answer. This is
+ for convenience so that the scans can use nice-looking for loops. */
+ 
+ TRACE debug_printf("%s: return %s\n", __FUNCTION__, dns_text_type(dnss->srr.type));
+ return &dnss->srr;
+ 
+diff --git a/src/lookups/dnsdb.c b/src/lookups/dnsdb.c
+index 355be1b5d..020dc9a52 100644
+--- a/src/lookups/dnsdb.c
++++ b/src/lookups/dnsdb.c
+@@ -394,80 +394,101 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+       /* Other kinds of record just have one piece of data each, but there may be
+       several of them, of course. */
+ 
+       if (yield->ptr) yield = string_catn(yield, outsep, 1);
+ 
+       if (type == T_TXT || type == T_SPF)
+         {
+-        if (outsep2 == NULL)	/* output only the first item of data */
+-          yield = string_catn(yield, US (rr->data+1), (rr->data)[0]);
++        if (!outsep2)			/* output only the first item of data */
++	  {
++	  uschar n = (rr->data)[0];
++	  /* size byte + data bytes must not excced the RRs length */
++	  if (n + 1 <= rr->size)
++	    yield = string_catn(yield, US (rr->data+1), n);
++	  }
+         else
+           {
+           /* output all items */
+           int data_offset = 0;
+           while (data_offset < rr->size)
+             {
+-            uschar chunk_len = (rr->data)[data_offset++];
+-            if (outsep2[0] != '\0' && data_offset != 1)
++            uschar chunk_len = (rr->data)[data_offset];
++	    int remain = rr->size - data_offset;
++
++	    /* Apparently there are resolvers that do not check RRs before passing
++	    them on, and glibc fails to do so.  So every application must...
++	    Check for chunk len exceeding RR */
++
++	    if (chunk_len > remain)
++	      chunk_len = remain;
++
++            if (*outsep2  && data_offset != 0)
+               yield = string_catn(yield, outsep2, 1);
+-            yield = string_catn(yield, US ((rr->data)+data_offset), chunk_len);
++            yield = string_catn(yield, US ((rr->data) + ++data_offset), --chunk_len);
+             data_offset += chunk_len;
+             }
+           }
+         }
+       else if (type == T_TLSA)
+-        {
+-        uint8_t usage, selector, matching_type;
+-        uint16_t payload_length;
+-        uschar s[MAX_TLSA_EXPANDED_SIZE];
+-	uschar * sp = s;
+-        uschar * p = US rr->data;
++	if (rr->size < 3)
++	  continue;
++	else
++	  {
++	  uint8_t usage, selector, matching_type;
++	  uint16_t payload_length;
++	  uschar s[MAX_TLSA_EXPANDED_SIZE];
++	  uschar * sp = s;
++	  uschar * p = US rr->data;
++
++	  usage = *p++;
++	  selector = *p++;
++	  matching_type = *p++;
++	  /* What's left after removing the first 3 bytes above */
++	  payload_length = rr->size - 3;
++	  sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
++		  selector, *outsep2, matching_type, *outsep2);
++	  /* Now append the cert/identifier, one hex char at a time */
++	  while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
++	    sp += sprintf(CS sp, "%02x", *p++);
+ 
+-        usage = *p++;
+-        selector = *p++;
+-        matching_type = *p++;
+-        /* What's left after removing the first 3 bytes above */
+-        payload_length = rr->size - 3;
+-        sp += sprintf(CS s, "%d%c%d%c%d%c", usage, *outsep2,
+-		selector, *outsep2, matching_type, *outsep2);
+-        /* Now append the cert/identifier, one hex char at a time */
+-	while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4))
+-          sp += sprintf(CS sp, "%02x", *p++);
+-
+-        yield = string_cat(yield, s);
+-        }
++	  yield = string_cat(yield, s);
++	  }
+       else   /* T_CNAME, T_CSA, T_MX, T_MXH, T_NS, T_PTR, T_SOA, T_SRV */
+         {
+         int priority, weight, port;
+         uschar s[264];
+         uschar * p = US rr->data;
+ 
+ 	switch (type)
+ 	  {
+ 	  case T_MXH:
++	    if (rr->size < sizeof(u_int16_t)) continue;
+ 	    /* mxh ignores the priority number and includes only the hostnames */
+ 	    GETSHORT(priority, p);
+ 	    break;
+ 
+ 	  case T_MX:
++	    if (rr->size < sizeof(u_int16_t)) continue;
+ 	    GETSHORT(priority, p);
+ 	    sprintf(CS s, "%d%c", priority, *outsep2);
+ 	    yield = string_cat(yield, s);
+ 	    break;
+ 
+ 	  case T_SRV:
++	    if (rr->size < 3*sizeof(u_int16_t)) continue;
+ 	    GETSHORT(priority, p);
+ 	    GETSHORT(weight, p);
+ 	    GETSHORT(port, p);
+ 	    sprintf(CS s, "%d%c%d%c%d%c", priority, *outsep2,
+ 			      weight, *outsep2, port, *outsep2);
+ 	    yield = string_cat(yield, s);
+ 	    break;
+ 
+ 	  case T_CSA:
++	    if (rr->size < 3*sizeof(u_int16_t)) continue;
+ 	    /* See acl_verify_csa() for more comments about CSA. */
+ 	    GETSHORT(priority, p);
+ 	    GETSHORT(weight, p);
+ 	    GETSHORT(port, p);
+ 
+ 	    if (priority != 1) continue;      /* CSA version must be 1 */
+ 
+@@ -510,15 +531,15 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+             "domain=%s", dns_text_type(type), domain);
+           break;
+           }
+         else yield = string_cat(yield, s);
+ 
+ 	if (type == T_SOA && outsep2 != NULL)
+ 	  {
+-	  unsigned long serial, refresh, retry, expire, minimum;
++	  unsigned long serial = 0, refresh = 0, retry = 0, expire = 0, minimum = 0;
+ 
+ 	  p += rc;
+ 	  yield = string_catn(yield, outsep2, 1);
+ 
+ 	  rc = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
+ 	    (DN_EXPAND_ARG4_TYPE)s, sizeof(s));
+ 	  if (rc < 0)
+@@ -526,16 +547,19 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+ 	    log_write(0, LOG_MAIN, "responsible-mailbox truncated: type=%s "
+ 	      "domain=%s", dns_text_type(type), domain);
+ 	    break;
+ 	    }
+ 	  else yield = string_cat(yield, s);
+ 
+ 	  p += rc;
+-	  GETLONG(serial, p); GETLONG(refresh, p);
+-	  GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
++	  if (rr->size >= p - rr->data - 5*sizeof(u_int32_t))
++	    {
++	    GETLONG(serial, p); GETLONG(refresh, p);
++	    GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
++	    }
+ 	  sprintf(CS s, "%c%lu%c%lu%c%lu%c%lu%c%lu",
+ 	    *outsep2, serial, *outsep2, refresh,
+ 	    *outsep2, retry,  *outsep2, expire,  *outsep2, minimum);
+ 	  yield = string_cat(yield, s);
+ 	  }
+         }
+       }    /* Loop for list of returned records */
+-- 
+2.42.0
+
diff -Nru exim4-4.96/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch exim4-4.96/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch
--- exim4-4.96/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,56 @@
+From 0f8814a1d8db65d1815a6a544a08fb2b6b9207ed Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Mon, 11 Sep 2023 15:50:35 +0100
+Subject: [PATCH] Fix ${tr...} and empty-strings.  Bug 3023
+
+(cherry picked from commit b015574531cf18b2126edb9da5a99dad659207dd)
+---
+ doc/ChangeLog        | 3 +++
+ src/expand.c             | 9 ++++-----
+ test/scripts/0000-Basic/0002 | 1 +
+ test/stdout/0002             | 1 +
+ 4 files changed, 9 insertions(+), 5 deletions(-)
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -85,10 +85,13 @@ JH/34 Bug 3013: Fix use of $recipients w
+       In 4.96 this would expand to empty.
+ 
+ JH/35 Bug 3014: GnuTLS: fix expiry date for an auto-generated server
+       certificate.  Find and fix by Andreas Metzler.
+ 
++JH/39 Bug 3023: Fix crash induced by some combinations of zero-length strings
++      and ${tr...}.  Found and diagnosed by Heiko Schlichting.
++
+ Exim version 4.96
+ -----------------
+ 
+ JH/01 Move the wait-for-next-tick (needed for unique message IDs) from
+       after reception to before a subsequent reception.  This should
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -5696,20 +5696,19 @@ while (*s)
+         case 1: goto EXPAND_FAILED_CURLY;
+         case 2:
+         case 3: goto EXPAND_FAILED;
+         }
+ 
+-      yield = string_cat(yield, sub[0]);
+-      o2m = Ustrlen(sub[2]) - 1;
+-
+-      if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++)
++      if (  (yield = string_cat(yield, sub[0]))
++         && (o2m = Ustrlen(sub[2]) - 1) >= 0)
++	  for (; oldptr < yield->ptr; oldptr++)
+         {
+         uschar *m = Ustrrchr(sub[1], yield->s[oldptr]);
+         if (m)
+           {
+           int o = m - sub[1];
+-          yield->s[oldptr] = sub[2][(o < o2m)? o : o2m];
++          yield->s[oldptr] = sub[2][o < o2m ? o : o2m];
+           }
+         }
+ 
+       if (skipping) continue;
+       break;
diff -Nru exim4-4.96/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch exim4-4.96/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch
--- exim4-4.96/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch	1970-01-01 01:00:00.000000000 +0100
+++ exim4-4.96/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch	2023-11-01 07:03:21.000000000 +0100
@@ -0,0 +1,219 @@
+From b94ea1bd61485a97c2d0dc2cab4c4d86ffe82e89 Mon Sep 17 00:00:00 2001
+From: Jeremy Harris <jgh146exb@wizmail.org>
+Date: Sun, 15 Oct 2023 12:15:06 +0100
+Subject: [PATCH] DNS: more hardening against crafted responses
+
+---
+ src/acl.c           |  1 +
+ src/dns.c           | 39 ++++++++++++++++++++++++++++++---------
+ src/functions.h     | 16 ++++++++++++++++
+ src/host.c          |  3 +++
+ src/lookups/dnsdb.c | 10 +++++-----
+ 5 files changed, 55 insertions(+), 14 deletions(-)
+
+diff --git a/src/acl.c b/src/acl.c
+index 118e4b35d..302dedaeb 100644
+--- a/src/acl.c
++++ b/src/acl.c
+@@ -1434,6 +1434,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+ 
+   /* Extract the numerical SRV fields (p is incremented) */
+ 
++  if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue;
+   GETSHORT(priority, p);
+   GETSHORT(weight, p);
+   GETSHORT(port, p);
+diff --git a/src/dns.c b/src/dns.c
+index db566f2e8..1347deec8 100644
+--- a/src/dns.c
++++ b/src/dns.c
+@@ -299,13 +299,23 @@ return string_from_gstring(g);
+ 
+ 
+ 
++/* Check a pointer for being past the end of a dns answer.
++Exactly one past the end is defined as ok.
++Return TRUE iff bad.
++*/
++static BOOL
++dnsa_bad_ptr(const dns_answer * dnsa, const uschar * ptr)
++{
++return ptr > dnsa->answer + dnsa->answerlen;
++}
++
+ /* Increment the aptr in dnss, checking against dnsa length.
+ Return: TRUE for a bad result
+ */
+ static BOOL
+ dnss_inc_aptr(const dns_answer * dnsa, dns_scan * dnss, unsigned delta)
+ {
+-return (dnss->aptr += delta) > dnsa->answer + dnsa->answerlen;
++return dnsa_bad_ptr(dnsa, dnss->aptr += delta);
+ }
+ 
+ /*************************************************
+@@ -385,10 +395,14 @@ if (reset != RESET_NEXT)
+       namelen = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+         dnss->aptr, (DN_EXPAND_ARG4_TYPE) &dnss->srr.name, DNS_MAXNAME);
+       if (namelen < 0) goto null_return;
++
+       /* skip name, type, class & TTL */
+       TRACE trace = "A-hdr";
+       if (dnss_inc_aptr(dnsa, dnss, namelen+8)) goto null_return;
++
++      if (dnsa_bad_ptr(dnsa, dnss->aptr + sizeof(uint16_t))) goto null_return;
+       GETSHORT(dnss->srr.size, dnss->aptr); /* size of data portion */
++
+       /* skip over it, checking for a bogus size */
+       TRACE trace = "A-skip";
+       if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size)) goto null_return;
+@@ -422,12 +436,18 @@ from the following bytes. */
+ TRACE trace = "R-name";
+ if (dnss_inc_aptr(dnsa, dnss, namelen)) goto null_return;
+ 
+-GETSHORT(dnss->srr.type, dnss->aptr);		/* Record type */
++/* Check space for type, class, TTL & data-size-word */
++if (dnsa_bad_ptr(dnsa, dnss->aptr + 3 * sizeof(uint16_t) + sizeof(uint32_t)))
++  goto null_return;
++
++GETSHORT(dnss->srr.type, dnss->aptr);			/* Record type */
++
+ TRACE trace = "R-class";
+-if (dnss_inc_aptr(dnsa, dnss, 2)) goto null_return;	/* Don't want class */
+-GETLONG(dnss->srr.ttl, dnss->aptr);		/* TTL */
+-GETSHORT(dnss->srr.size, dnss->aptr);		/* Size of data portion */
+-dnss->srr.data = dnss->aptr;			/* The record's data follows */
++(void) dnss_inc_aptr(dnsa, dnss, sizeof(uint16_t));	/* skip class */
++
++GETLONG(dnss->srr.ttl, dnss->aptr);			/* TTL */
++GETSHORT(dnss->srr.size, dnss->aptr);			/* Size of data portion */
++dnss->srr.data = dnss->aptr;				/* The record's data follows */
+ 
+ /* skip over it, checking for a bogus size */
+ if (dnss_inc_aptr(dnsa, dnss, dnss->srr.size))
+@@ -743,17 +763,17 @@ if (fake_dnsa_len_for_fail(dnsa, type))
+     /* Skip the mname & rname strings */
+ 
+     if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+-	p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
++	p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0)
+       break;
+     p += len;
+     if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
+-	p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
++	p, (DN_EXPAND_ARG4_TYPE)discard_buf, sizeof(discard_buf))) < 0)
+       break;
+     p += len;
+ 
+     /* Skip the SOA serial, refresh, retry & expire.  Grab the TTL */
+ 
+-    if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ)
++    if (dnsa_bad_ptr(dnsa, p + 5 * INT32SZ))
+       break;
+     p += 4 * INT32SZ;
+     GETLONG(ttl, p);
+@@ -1257,6 +1277,7 @@ switch (type)
+ 	const uschar * p = rr->data;
+ 
+ 	/* Extract the numerical SRV fields (p is incremented) */
++	if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue;
+ 	GETSHORT(priority, p);
+ 	GETSHORT(dummy_weight, p);
+ 	GETSHORT(port, p);
+diff --git a/src/functions.h b/src/functions.h
+index 8f85165e7..39119ca09 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -1110,6 +1110,22 @@ store_free_dns_answer_trc(dns_answer * dnsa, const uschar * func, unsigned line)
+ store_free_3(dnsa, CCS func, line);
+ }
+ 
++
++/* Check for an RR being large enough.  Return TRUE iff bad. */
++static inline BOOL
++rr_bad_size(const dns_record * rr, size_t minbytes)
++{
++return rr->size < minbytes;
++}
++
++/* Check for an RR having further data beyond a given pointer.
++Return TRUE iff bad. */
++static inline BOOL
++rr_bad_increment(const dns_record * rr, const uschar * ptr, size_t minbytes)
++{
++return rr_bad_size(rr, ptr - rr->data + minbytes);
++}
++
+ /******************************************************************************/
+ /* Routines with knowledge of spool layout */
+ 
+diff --git a/src/host.c b/src/host.c
+index 3e5a88660..ce7ca2bab 100644
+--- a/src/host.c
++++ b/src/host.c
+@@ -2725,6 +2725,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+   const uschar * s = rr->data;	/* MUST be unsigned for GETSHORT */
+   uschar data[256];
+ 
++  if (rr_bad_size(rr, sizeof(uint16_t))) continue;
+   GETSHORT(precedence, s);      /* Pointer s is advanced */
+ 
+   /* For MX records, we use a random "weight" which causes multiple records of
+@@ -2737,6 +2738,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     /* SRV records are specified with a port and a weight. The weight is used
+     in a special algorithm. However, to start with, we just use it to order the
+     records of equal priority (precedence). */
++
++    if (rr_bad_increment(rr, s, 2 * sizeof(uint16_t))) continue;
+     GETSHORT(weight, s);
+     GETSHORT(port, s);
+     }
+diff --git a/src/lookups/dnsdb.c b/src/lookups/dnsdb.c
+index 35a946447..fcf80e3dd 100644
+--- a/src/lookups/dnsdb.c
++++ b/src/lookups/dnsdb.c
+@@ -452,20 +452,20 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+ 	switch (type)
+ 	  {
+ 	  case T_MXH:
+-	    if (rr->size < sizeof(u_int16_t)) continue;
++	    if (rr_bad_size(rr, sizeof(u_int16_t))) continue;
+ 	    /* mxh ignores the priority number and includes only the hostnames */
+ 	    GETSHORT(priority, p);
+ 	    break;
+ 
+ 	  case T_MX:
+-	    if (rr->size < sizeof(u_int16_t)) continue;
++	    if (rr_bad_size(rr, sizeof(u_int16_t))) continue;
+ 	    GETSHORT(priority, p);
+ 	    sprintf(CS s, "%d%c", priority, *outsep2);
+ 	    yield = string_cat(yield, s);
+ 	    break;
+ 
+ 	  case T_SRV:
+-	    if (rr->size < 3*sizeof(u_int16_t)) continue;
++	    if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue;
+ 	    GETSHORT(priority, p);
+ 	    GETSHORT(weight, p);
+ 	    GETSHORT(port, p);
+@@ -475,7 +475,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+ 	    break;
+ 
+ 	  case T_CSA:
+-	    if (rr->size < 3*sizeof(u_int16_t)) continue;
++	    if (rr_bad_size(rr, 3*sizeof(u_int16_t))) continue;
+ 	    /* See acl_verify_csa() for more comments about CSA. */
+ 	    GETSHORT(priority, p);
+ 	    GETSHORT(weight, p);
+@@ -542,7 +542,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
+ 	  else yield = string_cat(yield, s);
+ 
+ 	  p += rc;
+-	  if (rr->size >= p - rr->data - 5*sizeof(u_int32_t))
++	  if (!rr_bad_increment(rr, p, 5 * sizeof(u_int32_t)))
+ 	    {
+ 	    GETLONG(serial, p); GETLONG(refresh, p);
+ 	    GETLONG(retry,  p); GETLONG(expire,  p); GETLONG(minimum, p);
+-- 
+2.42.0
+
diff -Nru exim4-4.96/debian/patches/series exim4-4.96/debian/patches/series
--- exim4-4.96/debian/patches/series	2023-09-29 22:38:02.000000000 +0200
+++ exim4-4.96/debian/patches/series	2023-11-01 07:03:21.000000000 +0100
@@ -37,4 +37,15 @@
 75_72-Auths-use-uschar-more-in-spa-authenticator.patch
 75_73-Auths-fix-possible-OOB-write-in-SPA-authenticator.-B.patch
 75_74-Auths-fix-possible-OOB-read-in-SPA-authenticator.-Bu.patch
+75_74-Cancel-early-pipe-on-an-observed-advertising-change.patch
+75_76-Expansions-disallow-UTF-16-surrogates-from-utf8clean.patch
+75_77-GnuTLS-fix-crash-with-tls_dhparam-none.patch
+75_79-Fix-recipients-expansion-when-used-within-run.-.-Bug.patch
+75_82-GnuTLS-fix-autogen-cert-expiry-date.-Bug-3014.patch
+75_83-Re-fix-live-variable-value-free.-The-inital-fix-resu.patch
+76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch
+76-02-SPF-harden-against-crafted-DNS-responses.patch
+76-03-Harden-dnsdb-against-crafted-DNS-responses.-Bug-3033.patch
+76-10-Fix-tr.-and-empty-strings.-Bug-3023.patch
+76-12-DNS-more-hardening-against-crafted-responses.patch
 90_localscan_dlopen.dpatch
diff -Nru exim4-4.96/debian/tests/basic exim4-4.96/debian/tests/basic
--- exim4-4.96/debian/tests/basic	2023-09-29 22:38:02.000000000 +0200
+++ exim4-4.96/debian/tests/basic	2023-10-22 14:26:23.000000000 +0200
@@ -40,6 +40,15 @@
 	false
 fi
 
+runandshow $exim -be \
+	'${run{/bin/echo -n foo}{success}{error}} runrc[$runrc] value[$value]'
+rc="$($exim -be \
+	'${run{/bin/echo -n foo}{success}{error}} runrc[$runrc] value[$value]')"
+if [ "$rc" != "success runrc[0] value[foo]" ]; then
+	echo wrong expansion result $rc
+	false
+fi
+
 runandshow swaks -s localhost -tlso -q ehlo
 runandshow swaks -s localhost -tlso -f root@localhost -t postmaster@localhost \
        -q rcpt
diff -Nru exim4-4.96/debian/tests/control exim4-4.96/debian/tests/control
--- exim4-4.96/debian/tests/control	2023-09-29 22:38:02.000000000 +0200
+++ exim4-4.96/debian/tests/control	2023-10-22 14:26:23.000000000 +0200
@@ -3,4 +3,4 @@
  exim4,
  libnet-ssleay-perl,
  swaks,
-Restrictions: allow-stderr
+Restrictions: allow-stderr, isolation-container

Attachment: signature.asc
Description: PGP signature


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

Hi,

Each of the updates discussed in these requests was included in this
morning's 12.3 bookworm point release.

Regards,

Adam

--- End Message ---

Reply to: