#! /bin/sh /usr/share/dpatch/dpatch-run ## 50_permanent_include_errors.dpatch by Shevek , 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; }