Commit 0c9dfffe authored by Nick Mathewson's avatar Nick Mathewson 🥄
Browse files

Implement the 0x20-hack to make DNS poisoning harder against us, especially...

Implement the 0x20-hack to make DNS poisoning harder against us, especially when resolving large names.  Add a cfg option to disable it, since apparently 3/10 of a percent of servers get it wrong.

svn:r17171
parent c8a5e2d5
...@@ -292,6 +292,7 @@ static config_var_t _option_vars[] = { ...@@ -292,6 +292,7 @@ static config_var_t _option_vars[] = {
V(ServerDNSAllowBrokenResolvConf, BOOL, "0"), V(ServerDNSAllowBrokenResolvConf, BOOL, "0"),
V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"), V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"),
V(ServerDNSDetectHijacking, BOOL, "1"), V(ServerDNSDetectHijacking, BOOL, "1"),
V(ServerDNSRandomizeCase, BOOL, "1"),
V(ServerDNSResolvConfFile, STRING, NULL), V(ServerDNSResolvConfFile, STRING, NULL),
V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSSearchDomains, BOOL, "0"),
V(ServerDNSTestAddresses, CSV, V(ServerDNSTestAddresses, CSV,
......
...@@ -184,13 +184,10 @@ evdns_log_cb(int warn, const char *msg) ...@@ -184,13 +184,10 @@ evdns_log_cb(int warn, const char *msg)
log(severity, LD_EXIT, "eventdns: %s", msg); log(severity, LD_EXIT, "eventdns: %s", msg);
} }
/** Helper: generate a good random transaction ID. */ static void
static uint16_t randfn(char *b, size_t n)
dns_get_transaction_id(void)
{ {
uint16_t result; crypto_rand(b,n);
crypto_rand((void*)&result, sizeof(result));
return result;
} }
/** Initialize the DNS subsystem; called by the OR process. */ /** Initialize the DNS subsystem; called by the OR process. */
...@@ -198,9 +195,15 @@ int ...@@ -198,9 +195,15 @@ int
dns_init(void) dns_init(void)
{ {
init_cache_map(); init_cache_map();
evdns_set_transaction_id_fn(dns_get_transaction_id); evdns_set_random_bytes_fn(randfn);
if (server_mode(get_options())) if (get_options()->ServerDNSRandomizeCase)
return configure_nameservers(1); evdns_set_option("randomize-case", "1", DNS_OPTIONS_ALL);
else
evdns_set_option("randomize-case", "0", DNS_OPTIONS_ALL);
if (server_mode(get_options())) {
int r = configure_nameservers(1);
return r;
}
return 0; return 0;
} }
......
...@@ -156,7 +156,6 @@ typedef unsigned int uint; ...@@ -156,7 +156,6 @@ typedef unsigned int uint;
struct request { struct request {
u8 *request; /* the dns packet data */ u8 *request; /* the dns packet data */
char *name; /* the name we requested. */
unsigned int request_len; unsigned int request_len;
int reissue_count; int reissue_count;
int tx_count; /* the number of times that this packet has been sent */ int tx_count; /* the number of times that this packet has been sent */
...@@ -308,6 +307,9 @@ static int global_max_retransmits = 3; /* number of times we'll retransmit a req ...@@ -308,6 +307,9 @@ static int global_max_retransmits = 3; /* number of times we'll retransmit a req
/* number of timeouts in a row before we consider this server to be down */ /* number of timeouts in a row before we consider this server to be down */
static int global_max_nameserver_timeout = 3; static int global_max_nameserver_timeout = 3;
/* DOCDOC */
static int global_randomize_case = 1;
/* These are the timeout values for nameservers. If we find a nameserver is down */ /* These are the timeout values for nameservers. If we find a nameserver is down */
/* we try to probe it at intervals as given below. Values are in seconds. */ /* we try to probe it at intervals as given below. Values are in seconds. */
static const struct timeval global_nameserver_timeouts[] = {{10, 0}, {60, 0}, {300, 0}, {900, 0}, {3600, 0}}; static const struct timeval global_nameserver_timeouts[] = {{10, 0}, {60, 0}, {300, 0}, {900, 0}, {3600, 0}};
...@@ -378,6 +380,9 @@ inet_aton(const char *c, struct in_addr *addr) ...@@ -378,6 +380,9 @@ inet_aton(const char *c, struct in_addr *addr)
#define ISSPACE(c) isspace((int)(unsigned char)(c)) #define ISSPACE(c) isspace((int)(unsigned char)(c))
#define ISDIGIT(c) isdigit((int)(unsigned char)(c)) #define ISDIGIT(c) isdigit((int)(unsigned char)(c))
#define ISALPHA(c) isalpha((int)(unsigned char)(c))
#define TOLOWER(c) (char)tolower((int)(unsigned char)(c))
#define TOUPPER(c) (char)toupper((int)(unsigned char)(c))
#ifndef NDEBUG #ifndef NDEBUG
static const char * static const char *
...@@ -863,9 +868,10 @@ name_parse(u8 *packet, int length, int *idx, char *name_out, size_t name_out_len ...@@ -863,9 +868,10 @@ name_parse(u8 *packet, int length, int *idx, char *name_out, size_t name_out_len
static int static int
reply_parse(u8 *packet, int length) { reply_parse(u8 *packet, int length) {
int j = 0; /* index into packet */ int j = 0; /* index into packet */
int k;
u16 _t; /* used by the macros */ u16 _t; /* used by the macros */
u32 _t32; /* used by the macros */ u32 _t32; /* used by the macros */
char tmp_name[256]; /* used by the macros */ char tmp_name[256], cmp_name[256]; /* used by the macros */
u16 trans_id, questions, answers, authority, additional, datalength; u16 trans_id, questions, answers, authority, additional, datalength;
u16 flags = 0; u16 flags = 0;
...@@ -898,11 +904,27 @@ reply_parse(u8 *packet, int length) { ...@@ -898,11 +904,27 @@ reply_parse(u8 *packet, int length) {
} }
/* if (!answers) return; */ /* must have an answer of some form */ /* if (!answers) return; */ /* must have an answer of some form */
/* This macro copies a name in the DNS reply into tmp_name */ /* This macro skips a name in the DNS reply. */
#define GET_NAME \ #define GET_NAME \
do { tmp_name[0] = '\0'; \ do { tmp_name[0] = '\0'; \
if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0) \ if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\
goto err; \ goto err; \
} while(0)
#define TEST_NAME \
do { tmp_name[0] = '\0'; \
cmp_name[0] = '\0'; \
k = j; \
if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\
goto err; \
if (name_parse(req->request, req->request_len, &k, cmp_name, sizeof(cmp_name))<0) \
goto err; \
if (global_randomize_case) { \
if (strcmp(tmp_name, cmp_name) == 0) \
name_matches = 1; /* we ignore mismatching names */ \
} else { \
if (strcasecmp(tmp_name, cmp_name) == 0) \
name_matches = 1; \
} \
} while(0) } while(0)
reply.type = req->request_type; reply.type = req->request_type;
...@@ -912,10 +934,8 @@ reply_parse(u8 *packet, int length) { ...@@ -912,10 +934,8 @@ reply_parse(u8 *packet, int length) {
/* the question looks like /* the question looks like
* <label:name><u16:type><u16:class> * <label:name><u16:type><u16:class>
*/ */
GET_NAME; TEST_NAME;
j += 4; j += 4;
if (!strcasecmp(req->name, tmp_name))
name_matches = 1;
if (j >= length) goto err; if (j >= length) goto err;
} }
...@@ -1127,6 +1147,32 @@ default_transaction_id_fn(void) ...@@ -1127,6 +1147,32 @@ default_transaction_id_fn(void)
static uint16_t (*trans_id_function)(void) = default_transaction_id_fn; static 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 void
evdns_set_transaction_id_fn(uint16_t (*fn)(void)) evdns_set_transaction_id_fn(uint16_t (*fn)(void))
{ {
...@@ -1134,6 +1180,14 @@ evdns_set_transaction_id_fn(uint16_t (*fn)(void)) ...@@ -1134,6 +1180,14 @@ evdns_set_transaction_id_fn(uint16_t (*fn)(void))
trans_id_function = fn; trans_id_function = fn;
else else
trans_id_function = default_transaction_id_fn; 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 */ /* Try to choose a strong transaction id which isn't already in flight */
...@@ -2366,20 +2420,32 @@ request_new(int type, const char *name, int flags, ...@@ -2366,20 +2420,32 @@ request_new(int type, const char *name, int flags,
const u16 trans_id = issuing_now ? transaction_id_pick() : 0xffff; const u16 trans_id = issuing_now ? transaction_id_pick() : 0xffff;
/* the request data is alloced in a single block with the header */ /* the request data is alloced in a single block with the header */
struct request *const req = struct request *const req =
(struct request *) malloc(sizeof(struct request) + request_max_len (struct request *) malloc(sizeof(struct request) + request_max_len);
+ name_len + 1); char namebuf[256];
int rlen; int rlen;
(void) flags; (void) flags;
if (!req) return NULL; if (!req) return NULL;
memset(req, 0, sizeof(struct request)); memset(req, 0, sizeof(struct request));
if (global_randomize_case) {
unsigned i;
char randbits[32];
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] = TOLOWER(namebuf[i]);
else
namebuf[i] = TOUPPER(namebuf[i]);
}
}
name = namebuf;
}
/* request data lives just after the header */ /* request data lives just after the header */
req->request = ((u8 *) req) + sizeof(struct request); req->request = ((u8 *) req) + sizeof(struct request);
/* A copy of the name sits after the request data */
req->name = ((char *)req) + sizeof(struct request) + request_max_len;
strlcpy(req->name, name, name_len + 1);
/* denotes that the request data shouldn't be free()ed */ /* denotes that the request data shouldn't be free()ed */
req->request_appended = 1; req->request_appended = 1;
rlen = evdns_request_data_build(name, name_len, trans_id, rlen = evdns_request_data_build(name, name_len, trans_id,
...@@ -2819,6 +2885,11 @@ evdns_set_option(const char *option, const char *val, int flags) ...@@ -2819,6 +2885,11 @@ evdns_set_option(const char *option, const char *val, int flags)
if (!(flags & DNS_OPTION_MISC)) return 0; if (!(flags & DNS_OPTION_MISC)) return 0;
log(EVDNS_LOG_DEBUG, "Setting retries to %d", retries); log(EVDNS_LOG_DEBUG, "Setting retries to %d", retries);
global_max_retransmits = retries; global_max_retransmits = retries;
} else if (!strncmp(option, "randomize-case:", 15)) {
int randcase = strtoint(val);
if (!(flags & DNS_OPTION_MISC)) return 0;
log(EVDNS_LOG_DEBUG, "Setting randomize_case to %d", randcase);
global_randomize_case = randcase;
} }
return 0; return 0;
} }
...@@ -3251,7 +3322,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data) ...@@ -3251,7 +3322,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data)
} }
} }
r = evdns_request_respond(req, 0); r = evdns_server_request_respond(req, 0);
if (r<0) if (r<0)
printf("eeek, couldn't send reply.\n"); printf("eeek, couldn't send reply.\n");
} }
......
...@@ -283,6 +283,7 @@ typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg); ...@@ -283,6 +283,7 @@ typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg);
void evdns_set_log_fn(evdns_debug_log_fn_type fn); void evdns_set_log_fn(evdns_debug_log_fn_type fn);
void evdns_set_transaction_id_fn(uint16_t (*fn)(void)); void evdns_set_transaction_id_fn(uint16_t (*fn)(void));
void evdns_set_random_bytes_fn(void (*fn)(char *, size_t));
#define DNS_NO_SEARCH 1 #define DNS_NO_SEARCH 1
......
...@@ -2397,6 +2397,8 @@ typedef struct { ...@@ -2397,6 +2397,8 @@ typedef struct {
* the local domains. */ * the local domains. */
int ServerDNSDetectHijacking; /**< Boolean: If true, check for DNS failure int ServerDNSDetectHijacking; /**< Boolean: If true, check for DNS failure
* hijacking. */ * hijacking. */
int ServerDNSRandomizeCase; /**< Boolean: Use the 0x20-hack to prevent
* DNS poisoning attacks. */
char *ServerDNSResolvConfFile; /**< If provided, we configure our internal char *ServerDNSResolvConfFile; /**< If provided, we configure our internal
* resolver from the file here rather than from * resolver from the file here rather than from
* /etc/resolv.conf (Unix) or the registry (Windows). */ * /etc/resolv.conf (Unix) or the registry (Windows). */
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment