0,0 → 1,325 |
#! /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; |
} |
|