SPF: harden against crafted DNS responses
[exim.git] / src / src / spf.c
index 3a1912a918573522b9608251056a107205467e87..1981d81b630963506231c3b038bfa6ad7e5af0f7 100644 (file)
@@ -3,9 +3,9 @@
 *************************************************/
 
 /* SPF support.
+   Copyright (c) The Exim Maintainers 2015 - 2022
    Copyright (c) Tom Kistner <tom@duncanthrax.net> 2004 - 2014
    License: GPL
-   Copyright (c) The Exim Maintainers 2015 - 2020
 */
 
 /* Code for calling spf checks via libspf-alt. Called from acl.c. */
@@ -34,15 +34,17 @@ SPF_response_t  *spf_response_2mx = NULL;
 SPF_dns_rr_t  * spf_nxdomain = NULL;
 
 
-void
-spf_lib_version_report(FILE * fp)
+gstring *
+spf_lib_version_report(gstring * g)
 {
 int maj, min, patch;
+
 SPF_get_lib_version(&maj, &min, &patch);
-fprintf(fp, "Library version: spf2: Compile: %d.%d.%d\n",
+g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n",
        SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH);
-fprintf(fp, "                       Runtime: %d.%d.%d\n",
+g = string_fmt_append(g,    "                       Runtime: %d.%d.%d\n",
         maj, min, patch);
+return g;
 }
 
 
@@ -80,6 +82,7 @@ if (rr_type == T_SPF)
   HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n");
   srr.herrno = NO_DATA;
   SPF_dns_rr_dup(&spfrr, &srr);
+  store_free_dns_answer(dnsa);
   return spfrr;
   }
 
@@ -100,6 +103,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
 if (found == 0)
   {
   SPF_dns_rr_dup(&spfrr, &srr);
+  store_free_dns_answer(dnsa);
   return spfrr;
   }
 
@@ -116,6 +120,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
     switch(rr_type)
       {
       case T_MX:
+       if (rr->size < 2) continue;
        s += 2; /* skip the MX precedence field */
       case T_PTR:
        {
@@ -131,6 +136,7 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
        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",
@@ -138,9 +144,12 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
          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)
@@ -171,6 +180,7 @@ if (!(srr.num_rr = found))
 
 /* spfrr->rr must have been malloc()d for this */
 SPF_dns_rr_dup(&spfrr, &srr);
+store_free_dns_answer(dnsa);
 return spfrr;
 }