Subversion Repositories

?revision_form?Rev ?revision_input??revision_submit??revision_endform?

Rev 29 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#! /bin/sh /usr/share/dpatch/dpatch-run
## 50_permanent_include_errors.dpatch by Shevek <shevek@anarres.org>, edited by Magnus Holmgren
##
## DP: Fix CVE-2008-2469 - buffer overflows handling DNS responses.

@DPATCH@

--- libspf2/src/libspf2/spf_dns_resolv.c.orig   2008-09-20 19:36:57.000000000 +0200
+++ libspf2/src/libspf2/spf_dns_resolv.c        2008-09-20 19:39:08.000000000 +0200
@@ -110,7 +110,8 @@
     int                nrec;
     int                cnt;
 
-    u_char     response[2048];
+       u_char  *responsebuf;
+       size_t   responselen;
 
     int                dns_len;
 
@@ -127,11 +128,13 @@
     char       name_buf[ NS_MAXDNAME ];
     int                prio;
 
-    int                rdlen;
-    const u_char       *rdata, *rdata_end;
+       size_t  rdlen;
+       const u_char    *rdata;
 
+#if HAVE_DECL_RES_NINIT
        void                            *res_spec;
        struct __res_state      *res_state;
+#endif
 
        SPF_ASSERT_NOTNULL(spf_dns_server);
 
@@ -140,10 +143,12 @@
        SPF_ASSERT_NOTNULL(spfhook);
 #endif
 
+#if HAVE_DECL_RES_NINIT
        res_spec = pthread_getspecific(res_state_key);
        if (res_spec == NULL) {
                res_state = (struct __res_state *)
                                                malloc(sizeof(struct __res_state));
+               memset(res_state, 0, sizeof(struct __res_state));
                if (res_ninit(res_state) != 0) {
                        SPF_error("Failed to call res_ninit()");
                }
@@ -152,20 +157,45 @@
        else {
                res_state = (struct __res_state *)res_spec;
        }
+#endif
+
+       responselen = 2048;
+       responsebuf = (u_char *)malloc(responselen);
+       memset(responsebuf, 0, responselen);
+
+       /*
+        * Retry the lookup until our response buffer is big enough.
+        *
+        * This loop repeats until either we fail a lookup or we succeed.
+        * The size of the response buffer is monotonic increasing, so eventually we
+        * must either succeed, or we try to malloc more RAM than we can.
+        *
+        * The Linux man pages do not describe res_nquery adequately. Solaris says:
+        *
+        * The res_nquery() and res_query() routines return a length that may be bigger
+        * than anslen. In that case, retry the query with a larger buf. The answer to the
+        * second query may be larger still], so it is recommended that you supply a buf
+        * larger than the answer returned by the previous query. answer must be large
+        * enough to receive a maximum UDP response from the server or parts of the answer
+        * will be silently discarded. The default maximum UDP response size is 512 bytes.
+        */
+       for (;;) {
 
     /*
      * try resolving the name
      */
 #if HAVE_DECL_RES_NINIT
        dns_len = res_nquery(res_state, domain, ns_c_in, rr_type,
-                        response, sizeof(response));
+                                responsebuf, responselen);
 #else
     dns_len = res_query(domain, ns_c_in, rr_type,
-                        response, sizeof(response));
+                                responsebuf, responselen);
 #endif
 
        if ( dns_len < 0 ) {
+                       /* We failed to perform a lookup. */
                /* This block returns unconditionally. */
+                       free(responsebuf);
                if ( spf_dns_server->debug )
                        SPF_debugf( "query failed: err = %d  %s (%d): %s",
                                dns_len, hstrerror( SPF_h_errno ), SPF_h_errno,
@@ -178,6 +208,25 @@
                return SPF_dns_rr_new_init(spf_dns_server,
                                                domain, rr_type, 0, SPF_h_errno);
        }
+               else if (dns_len > responselen) {
+                       /* We managed a lookup but our buffer was too small. */
+                       responselen = dns_len + (dns_len >> 1);
+#if 0
+                       /* Sanity-trap - we should never hit this. */
+                       if (responselen > 1048576) {    /* One megabyte. */
+                               free(responsebuf);
+                               return SPF_dns_rr_new_init(spf_dns_server,
+                                                               domain, rr_type, 0, SPF_h_errno);
+                       }
+#endif
+                       responsebuf = realloc(responsebuf, responselen);
+               }
+               else {
+                       /* We managed a lookup, and our buffer was large enough. */
+                       responselen = dns_len;
+                       break;
+               }
+       }
 
     /*
      * initialize stuff
@@ -185,12 +234,13 @@
        spfrr = SPF_dns_rr_new_init(spf_dns_server,
                                        domain, rr_type, 0, NETDB_SUCCESS);
 
-    err = ns_initparse( response, dns_len, &ns_handle );
+       err = ns_initparse(responsebuf, responselen, &ns_handle);
 
        if ( err < 0 ) {        /* 0 or -1 */
                if ( spf_dns_server->debug )
                        SPF_debugf( "ns_initparse failed: err = %d  %s (%d)",
                                err, strerror( errno ), errno );
+               free(responsebuf);
                return spfrr;
     }
 
@@ -226,6 +276,7 @@
                        if ( spf_dns_server->debug > 1 )
                                SPF_debugf( "ns_parserr failed: err = %d  %s (%d)",
                                        err, strerror( errno ), errno );
+                               free(responsebuf);
                        return spfrr;
                        }
 
@@ -257,8 +308,8 @@
                                break;
 
                        case ns_t_ns:
-                               err = ns_name_uncompress( response,
-                                                         response + sizeof( response ),
+                               err = ns_name_uncompress( responsebuf,
+                                                         responsebuf + responselen,
                                                          rdata,
                                                          name_buf, sizeof( name_buf ) );
                                if ( err < 0 )          /* 0 or -1 */
@@ -271,8 +322,8 @@
                                break;
 
                        case ns_t_cname:
-                               err = ns_name_uncompress( response,
-                                                         response + sizeof( response ),
+                               err = ns_name_uncompress( responsebuf,
+                                                         responsebuf + responselen,
                                                          rdata,
                                                          name_buf, sizeof( name_buf ) );
                                if ( err < 0 )          /* 0 or -1 */
@@ -286,8 +337,8 @@
 
                        case ns_t_mx:
                                prio = ns_get16( rdata );
-                               err = ns_name_uncompress( response,
-                                                         response + sizeof( response ),
+                               err = ns_name_uncompress( responsebuf,
+                                                         responsebuf + sizeof( responselen ),
                                                          rdata + NS_INT16SZ,
                                                          name_buf, sizeof( name_buf ) );
                                if ( err < 0 )          /* 0 or -1 */
@@ -300,14 +351,13 @@
                                break;
 
                        case ns_t_txt:
-                               rdata_end = rdata + rdlen;
                                SPF_debugf( "TXT: (%d) \"%.*s\"",
                                        rdlen, rdlen-1, rdata+1 );
                                break;
 
                        case ns_t_ptr:
-                               err = ns_name_uncompress( response,
-                                                         response + sizeof( response ),
+                               err = ns_name_uncompress( responsebuf,
+                                                         responsebuf + responselen,
                                                          rdata,
                                                          name_buf, sizeof( name_buf ) );
                                if ( err < 0 )          /* 0 or -1 */
@@ -341,18 +391,21 @@
                        {
                        case ns_t_a:
                        if ( SPF_dns_rr_buf_realloc( spfrr, cnt,
-                                                       sizeof( spfrr->rr[cnt]->a ) ) != SPF_E_SUCCESS )
+                                                               sizeof(spfrr->rr[cnt]->a)) != SPF_E_SUCCESS) {
+                                               free(responsebuf);
                                return spfrr;
-                       memmove( &spfrr->rr[cnt]->a, rdata, sizeof( spfrr->rr[cnt]->a ) );
+                                       }
+                                       memcpy(&spfrr->rr[cnt]->a, rdata, sizeof(spfrr->rr[cnt]->a));
                        cnt++;
                        break;
 
                        case ns_t_aaaa:
                        if ( SPF_dns_rr_buf_realloc( spfrr, cnt,
-                                                       sizeof( spfrr->rr[cnt]->aaaa ) ) != SPF_E_SUCCESS )
+                                                               sizeof(spfrr->rr[cnt]->aaaa)) != SPF_E_SUCCESS) {
+                                               free(responsebuf);
                                return spfrr;
-                       memmove( &spfrr->rr[cnt]->aaaa, rdata, sizeof( spfrr->rr[cnt]->aaaa ) );
-
+                                       }
+                                       memcpy(&spfrr->rr[cnt]->aaaa, rdata, sizeof(spfrr->rr[cnt]->aaaa));
                        cnt++;
                        break;
 
@@ -364,8 +417,8 @@
                        break;
 
                        case ns_t_mx:
-                       err = ns_name_uncompress( response,
-                                                 response + sizeof( response ),
+                                       err = ns_name_uncompress(responsebuf,
+                                                                       responsebuf + responselen,
                                                  rdata + NS_INT16SZ,
                                                  name_buf, sizeof( name_buf ) );
                        if ( err < 0 )          /* 0 or -1 */
@@ -373,12 +426,15 @@
                                if ( spf_dns_server->debug > 1 )
                                SPF_debugf( "ns_name_uncompress failed: err = %d  %s (%d)",
                                        err, strerror( errno ), errno );
+                                               free(responsebuf);
                                return spfrr;
                        }
 
                        if ( SPF_dns_rr_buf_realloc( spfrr, cnt,
-                                                       strlen( name_buf ) + 1 ) != SPF_E_SUCCESS )
+                                                                       strlen(name_buf) + 1 ) != SPF_E_SUCCESS) {
+                                               free(responsebuf);
                                return spfrr;
+                                       }
                        strcpy( spfrr->rr[cnt]->mx, name_buf );
 
                        cnt++;
@@ -390,8 +446,12 @@
                                u_char *src, *dst;
                                size_t len;
 
-                               if ( SPF_dns_rr_buf_realloc( spfrr, cnt, rdlen ) != SPF_E_SUCCESS )
+                                               /* Just rdlen is enough because there is at least one
+                                                * length byte. */
+                                               if (SPF_dns_rr_buf_realloc(spfrr, cnt, rdlen) != SPF_E_SUCCESS) {
+                                                       free(responsebuf);
                                return spfrr;
+                                               }
 
                                dst = (u_char *)(spfrr->rr[cnt]->txt);
                                len = 0;
@@ -400,15 +460,22 @@
                                {
                                len = *src;
                                src++;
+                                                       rdlen--;
+
+                                                       /* Avoid buffer overrun if len is junk. */
+                                                       if (len > rdlen)
+                                                               len = rdlen;
                                memcpy( dst, src, len );
                                dst += len;
                                src += len;
-                               rdlen -= len + 1;
+                                                       rdlen -= len;
                                }
                                *dst = '\0';
                        } else {
-                               if ( SPF_dns_rr_buf_realloc( spfrr, cnt, 1 ) != SPF_E_SUCCESS )
+                                               if (SPF_dns_rr_buf_realloc(spfrr, cnt, 1) != SPF_E_SUCCESS) {
+                                                       free(responsebuf);
                                return spfrr;
+                                               }
                                spfrr->rr[cnt]->txt[0] = '\0';
                        }
 
@@ -416,8 +483,8 @@
                        break;
 
                        case ns_t_ptr:
-                       err = ns_name_uncompress( response,
-                                                 response + sizeof( response ),
+                                       err = ns_name_uncompress(responsebuf,
+                                                                       responsebuf + responselen,
                                                  rdata,
                                                  name_buf, sizeof( name_buf ) );
                        if ( err < 0 )          /* 0 or -1 */
@@ -425,12 +492,15 @@
                                if ( spf_dns_server->debug > 1 )
                                SPF_debugf( "ns_name_uncompress failed: err = %d  %s (%d)",
                                        err, strerror( errno ), errno );
+                                               free(responsebuf);
                                return spfrr;
                        }
 
                        if ( SPF_dns_rr_buf_realloc( spfrr, cnt,
-                                                       strlen( name_buf ) + 1 ) != SPF_E_SUCCESS )
+                                                                       strlen(name_buf) + 1) != SPF_E_SUCCESS) {
+                                               free(responsebuf);
                                return spfrr;
+                                       }
                        strcpy( spfrr->rr[cnt]->ptr, name_buf );
 
                        cnt++;
@@ -447,6 +517,7 @@
     if ( spfrr->num_rr == 0 )
                spfrr->herrno = NO_DATA;
 
+       free(responsebuf);
     return spfrr;
 }