Implement increased DSN-poisoning resistance via the 0x20 hack.

svn:r958
This commit is contained in:
Nick Mathewson 2008-12-03 20:09:13 +00:00
parent 1eeb96aa88
commit dd73168556
4 changed files with 99 additions and 12 deletions

View File

@ -130,7 +130,9 @@ Changes in current version:
o Allow setting of local port for evhttp connections to support millions of connections from a single system; from Richard Jones.
o Clear the timer cache when leaving the event loop; reported by Robin Haberkorn
o Fix a typo in setting the global event base; reported by lance.
o Set the 0x20 bit on outgoing alphabetic characters in DNS requests randomly, and insist on a match in replies. This helps resist DNS poisoning attacks.
Changes in 1.4.0:
o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr.
o demote most http warnings to debug messages

77
evdns.c
View File

@ -103,6 +103,7 @@
#include "evutil.h"
#include "log.h"
#include "mm-internal.h"
#include "strlcpy-internal.h"
#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
@ -317,6 +318,8 @@ struct evdns_base {
int global_max_retransmits; /* number of times we'll retransmit a request which timed out */
/* number of timeouts in a row before we consider this server to be down */
int global_max_nameserver_timeout;
/* true iff we will use the 0x20 hack to prevent poisoning attacks. */
int global_randomize_case;
struct search_state *global_search_state;
};
@ -813,6 +816,7 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) {
u16 _t; /* used by the macros */
u32 _t32; /* used by the macros */
char tmp_name[256], cmp_name[256]; /* used by the macros */
int name_matches = 0;
u16 trans_id, questions, answers, authority, additional, datalength;
u16 flags = 0;
@ -858,8 +862,13 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) {
goto err; \
if (name_parse(req->request, req->request_len, &k, cmp_name, sizeof(cmp_name))<0) \
goto err; \
if (memcmp(tmp_name, cmp_name, strlen (tmp_name)) != 0) \
return (-1); /* we ignore mismatching names */ \
if (base->global_randomize_case) { \
if (strcmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} else { \
if (strcasecmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} \
} while(0)
reply.type = req->request_type;
@ -874,6 +883,9 @@ reply_parse(struct evdns_base *base, u8 *packet, int length) {
if (j > length) goto err;
}
if (!name_matches)
goto err;
/* now we have the answer section which looks like
* <label:name><u16:type><u16:class><u32:ttl><u16:len><data...>
*/
@ -1085,6 +1097,32 @@ default_transaction_id_fn(void)
static ev_uint16_t (*trans_id_function)(void) = default_transaction_id_fn;
static void
default_random_bytes_fn(char *buf, size_t n)
{
unsigned i;
for (i = 0; i < n-1; i += 2) {
u16 tid = trans_id_function();
buf[i] = (tid >> 8) & 0xff;
buf[i+1] = tid & 0xff;
}
if (i < n) {
u16 tid = trans_id_function();
buf[i] = tid & 0xff;
}
}
static void (*rand_bytes_function)(char *buf, size_t n) =
default_random_bytes_fn;
static u16
trans_id_from_random_bytes_fn(void)
{
u16 tid;
rand_bytes_function((char*) &tid, sizeof(tid));
return tid;
}
void
evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void))
{
@ -1092,6 +1130,14 @@ evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void))
trans_id_function = fn;
else
trans_id_function = default_transaction_id_fn;
rand_bytes_function = default_random_bytes_fn;
}
void
evdns_set_random_bytes_fn(void (*fn)(char *, size_t))
{
rand_bytes_function = fn;
trans_id_function = trans_id_from_random_bytes_fn;
}
/* Try to choose a strong transaction id which isn't already in flight */
@ -2276,6 +2322,10 @@ string_num_dots(const char *s) {
return count;
}
/* Helper: provide a working isalpha implementation on platforms with funny
* ideas about character types and isalpha behavior. */
#define ISALPHA(c) isalpha((int)(unsigned char)(c))
static struct evdns_request *
request_new(struct evdns_base *base, int type, const char *name, int flags,
evdns_callback_type callback, void *user_ptr) {
@ -2289,12 +2339,29 @@ request_new(struct evdns_base *base, int type, const char *name, int flags,
struct evdns_request *const req =
(struct evdns_request *) mm_malloc(sizeof(struct evdns_request) + request_max_len);
int rlen;
char namebuf[256];
(void) flags;
if (!req) return NULL;
memset(req, 0, sizeof(struct evdns_request));
req->base = base;
if (base->global_randomize_case) {
unsigned i;
char randbits[(sizeof(namebuf)+7)/8];
strlcpy(namebuf, name, sizeof(namebuf));
rand_bytes_function(randbits, (name_len+7)/8);
for (i = 0; i < name_len; ++i) {
if (ISALPHA(namebuf[i])) {
if ((randbits[i >> 3] & (1<<(i & 7))))
namebuf[i] |= 0x20;
else
namebuf[i] &= ~0x20;
}
}
name = namebuf;
}
/* request data lives just after the header */
req->request = ((u8 *) req) + sizeof(struct evdns_request);
/* denotes that the request data shouldn't be free()ed */
@ -2303,6 +2370,7 @@ request_new(struct evdns_base *base, int type, const char *name, int flags,
type, CLASS_INET, req->request, request_max_len);
if (rlen < 0)
goto err1;
req->request_len = rlen;
req->trans_id = trans_id;
req->tx_count = 0;
@ -2821,6 +2889,10 @@ evdns_base_set_option(struct evdns_base *base,
if (!(flags & DNS_OPTION_MISC)) return 0;
log(EVDNS_LOG_DEBUG, "Setting retries to %d", retries);
base->global_max_retransmits = retries;
} else if (!strncmp(option, "randomize-case:", 15)) {
int randcase = strtoint(val);
if (!(flags & DNS_OPTION_MISC)) return 0;
base->global_randomize_case = randcase;
}
return 0;
}
@ -3168,6 +3240,7 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers)
base->global_max_retransmits = 3;
base->global_max_nameserver_timeout = 3;
base->global_search_state = NULL;
base->global_randomize_case = 1;
if (initialize_nameservers) {
int r;
#ifdef WIN32

View File

@ -380,7 +380,7 @@ struct evdns_request *evdns_base_resolve_reverse_ipv6(struct evdns_base *base, s
The currently available configuration options are:
ndots, timeout, max-timeouts, max-inflight, and attempts
ndots, timeout, max-timeouts, max-inflight, attempts, randomize-case.
@param base the evdns_base to which to apply this operation
@param option the name of the configuration option to be modified
@ -473,12 +473,21 @@ void evdns_set_log_fn(evdns_debug_log_fn_type fn);
/**
Set a callback that will be invoked to generate transaction IDs. By
default, we pick transaction IDs based on the current clock time.
default, we pick transaction IDs based on the current clock time, which
is bad for security.
@param fn the new callback, or NULL to use the default.
*/
void evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void));
/**
Set a callback used to generate random bytes. By default, we use
the same function as passed to evdns_set_transaction_id_fn to generate
bytes two at a time. If a function is provided here, it's also used
to generate transaction IDs.
*/
void evdns_set_random_bytes_fn(void (*fn)(char *, size_t));
#define DNS_NO_SEARCH 1
/*

View File

@ -198,24 +198,27 @@ dns_server_request_cb(struct evdns_server_request *req, void *data)
ans.s_addr = htonl(0xc0a80b0bUL); /* 192.168.11.11 */
if (req->questions[i]->type == EVDNS_TYPE_A &&
req->questions[i]->dns_question_class == EVDNS_CLASS_INET &&
!strcmp(req->questions[i]->name, "zz.example.com")) {
r = evdns_server_request_add_a_reply(req, "zz.example.com",
!strcasecmp(req->questions[i]->name, "zz.example.com")) {
r = evdns_server_request_add_a_reply(req,
req->questions[i]->name,
1, &ans.s_addr, 12345);
if (r<0)
dns_ok = 0;
} else if (req->questions[i]->type == EVDNS_TYPE_AAAA &&
req->questions[i]->dns_question_class == EVDNS_CLASS_INET &&
!strcmp(req->questions[i]->name, "zz.example.com")) {
!strcasecmp(req->questions[i]->name, "zz.example.com")) {
char addr6[17] = "abcdefghijklmnop";
r = evdns_server_request_add_aaaa_reply(req, "zz.example.com",
r = evdns_server_request_add_aaaa_reply(req,
req->questions[i]->name,
1, addr6, 123);
if (r<0)
dns_ok = 0;
} else if (req->questions[i]->type == EVDNS_TYPE_PTR &&
req->questions[i]->dns_question_class == EVDNS_CLASS_INET &&
!strcmp(req->questions[i]->name, TEST_ARPA)) {
r = evdns_server_request_add_ptr_reply(req, NULL, TEST_ARPA,
"ZZ.EXAMPLE.COM", 54321);
!strcasecmp(req->questions[i]->name, TEST_ARPA)) {
r = evdns_server_request_add_ptr_reply(req, NULL,
req->questions[i]->name,
"ZZ.EXAMPLE.COM", 54321);
if (r<0)
dns_ok = 0;
} else {