summaryrefslogtreecommitdiffstats
path: root/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch')
-rw-r--r--debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch219
1 files changed, 219 insertions, 0 deletions
diff --git a/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch b/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch
new file mode 100644
index 0000000..150c92c
--- /dev/null
+++ b/debian/patches/76-12-DNS-more-hardening-against-crafted-responses.patch
@@ -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
+