[exim-cvs] Harden dnsdb against crafted DNS responses. Bug …

Góra strony
Delete this message
Reply to this message
Autor: Exim Git Commits Mailing List
Data:  
Dla: exim-cvs
Temat: [exim-cvs] Harden dnsdb against crafted DNS responses. Bug 3033
Gitweb: https://git.exim.org/exim.git/commitdiff/f6b1f8e7d642f82d830a71b78699a4349e0158e1
Commit:     f6b1f8e7d642f82d830a71b78699a4349e0158e1
Parent:     654056e44fc93a0ee7c09d1228933e8af6862206
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Tue Oct 10 23:03:28 2023 +0100
Committer:  Heiko Schlittermann (HS12-RIPE) <hs@???>
CommitDate: Sat Oct 14 23:50:37 2023 +0200


    Harden dnsdb against crafted DNS responses.  Bug 3033


    (cherry picked from commit 8787c8994f07c23c3664d76926e02f07314d699d)
---
 doc/doc-txt/ChangeLog   |  3 ++
 src/src/dns.c           | 11 ++++---
 src/src/lookups/dnsdb.c | 78 ++++++++++++++++++++++++++++++++-----------------
 3 files changed, 59 insertions(+), 33 deletions(-)


diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index c36718d7e..f7ab3c005 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -225,6 +225,9 @@ JH/39 Bug 3023: Fix crash induced by some combinations of zero-length strings
 JH/40 Support list of dkim results in the dkim_status ACL condition, making
       it more usable in the data ACL.


+JH/43 Bug 3033: Harden dnsdb lookups against crafted DNS responses.
+      CVE-2023-42219
+


Exim version 4.96
-----------------
diff --git a/src/src/dns.c b/src/src/dns.c
index 7d7ee0c04..8dc3695a1 100644
--- a/src/src/dns.c
+++ b/src/src/dns.c
@@ -304,7 +304,7 @@ 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;
}

 /*************************************************
@@ -388,7 +388,7 @@ if (reset != RESET_NEXT)
       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;
       }
@@ -428,10 +428,9 @@ 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. */
diff --git a/src/src/lookups/dnsdb.c b/src/src/lookups/dnsdb.c
index 355be1b5d..020dc9a52 100644
--- a/src/src/lookups/dnsdb.c
+++ b/src/src/lookups/dnsdb.c
@@ -398,43 +398,60 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))

       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;
@@ -444,17 +461,20 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
     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);
@@ -464,6 +484,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
         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);
@@ -514,7 +535,7 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))


     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);
@@ -530,8 +551,11 @@ while ((domain = string_nextinlist(&keystring, &sep, NULL, 0)))
       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);


--
## subscription configuration (requires account):
## https://lists.exim.org/mailman3/postorius/lists/exim-cvs.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-cvs-unsubscribe@???
## Exim details at http://www.exim.org/
## Please use the Wiki with this list - http://wiki.exim.org/