Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian....@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
-- 
`What a good friend you are to him, Dr. Maturin. His other friends are
so grateful to you.'
`I sew his ears on from time to time, sure'
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 <ametz...@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 <jgh146...@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 <ja...@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 <jgh146...@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 <jgh146...@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 <ametz...@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 <jgh146...@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)" <h...@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 <jgh146...@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 <jgh146...@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 <jgh146...@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 <jgh146...@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

Reply via email to