Bug#1055155: bookworm-pu: package exim4/4.96-15+deb12u3 (2nd try for new bug)
On 2023-11-04 Salvatore Bonaccorso <carnil@debian.org> wrote:
> On Wed, Nov 01, 2023 at 12:03:37PM +0100, Andreas Metzler wrote:
[...]
> > * 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
> The mentioned CVEs have a typo. I believe this should be
> CVE-2023-42117 and CVE-2023-42119 (and for completeness about the
> libspf2 mentioning CVE-2023-42118).
Thank you, updated.
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-42119). It
+ also includes additional hardening for spf lookups, however CVE-2023-42118
+ 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
Reply to: