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
signature.asc
Description: PGP signature