From 0c9dfffe5a19da330f3c2b6daa33c40aa786ba06 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 29 Oct 2008 19:20:02 +0000 Subject: [PATCH] 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 --- src/or/config.c | 1 + src/or/dns.c | 21 ++++++---- src/or/eventdns.c | 105 ++++++++++++++++++++++++++++++++++++++-------- src/or/eventdns.h | 1 + src/or/or.h | 2 + 5 files changed, 104 insertions(+), 26 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index a704b7e6ca..bd42860011 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -292,6 +292,7 @@ static config_var_t _option_vars[] = { V(ServerDNSAllowBrokenResolvConf, BOOL, "0"), V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"), V(ServerDNSDetectHijacking, BOOL, "1"), + V(ServerDNSRandomizeCase, BOOL, "1"), V(ServerDNSResolvConfFile, STRING, NULL), V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSTestAddresses, CSV, diff --git a/src/or/dns.c b/src/or/dns.c index aa251b4322..03dc85f421 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -184,13 +184,10 @@ evdns_log_cb(int warn, const char *msg) log(severity, LD_EXIT, "eventdns: %s", msg); } -/** Helper: generate a good random transaction ID. */ -static uint16_t -dns_get_transaction_id(void) +static void +randfn(char *b, size_t n) { - uint16_t result; - crypto_rand((void*)&result, sizeof(result)); - return result; + crypto_rand(b,n); } /** Initialize the DNS subsystem; called by the OR process. */ @@ -198,9 +195,15 @@ int dns_init(void) { init_cache_map(); - evdns_set_transaction_id_fn(dns_get_transaction_id); - if (server_mode(get_options())) - return configure_nameservers(1); + evdns_set_random_bytes_fn(randfn); + if (get_options()->ServerDNSRandomizeCase) + 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; } diff --git a/src/or/eventdns.c b/src/or/eventdns.c index 1a3e344c5c..204ebc17be 100644 --- a/src/or/eventdns.c +++ b/src/or/eventdns.c @@ -156,7 +156,6 @@ typedef unsigned int uint; struct request { u8 *request; /* the dns packet data */ - char *name; /* the name we requested. */ unsigned int request_len; int reissue_count; 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 /* number of timeouts in a row before we consider this server to be down */ 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 */ /* 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}}; @@ -378,6 +380,9 @@ inet_aton(const char *c, struct in_addr *addr) #define ISSPACE(c) isspace((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 static const char * @@ -863,9 +868,10 @@ name_parse(u8 *packet, int length, int *idx, char *name_out, size_t name_out_len static int reply_parse(u8 *packet, int length) { int j = 0; /* index into packet */ + int k; u16 _t; /* 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 flags = 0; @@ -898,11 +904,27 @@ reply_parse(u8 *packet, int length) { } /* if (!answers) return; */ /* must have an answer of some form */ - /* This macro copies a name in the DNS reply into tmp_name */ -#define GET_NAME \ - do { tmp_name[0] = '\0'; \ - if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0) \ - goto err; \ + /* This macro skips a name in the DNS reply. */ +#define GET_NAME \ + do { tmp_name[0] = '\0'; \ + if (name_parse(packet, length, &j, tmp_name, sizeof(tmp_name))<0)\ + 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) reply.type = req->request_type; @@ -912,10 +934,8 @@ reply_parse(u8 *packet, int length) { /* the question looks like * */ - GET_NAME; + TEST_NAME; j += 4; - if (!strcasecmp(req->name, tmp_name)) - name_matches = 1; if (j >= length) goto err; } @@ -1127,6 +1147,32 @@ default_transaction_id_fn(void) 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 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; 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 */ @@ -2366,20 +2420,32 @@ request_new(int type, const char *name, int flags, const u16 trans_id = issuing_now ? transaction_id_pick() : 0xffff; /* the request data is alloced in a single block with the header */ struct request *const req = - (struct request *) malloc(sizeof(struct request) + request_max_len - + name_len + 1); + (struct request *) malloc(sizeof(struct request) + request_max_len); + char namebuf[256]; int rlen; (void) flags; if (!req) return NULL; 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 */ 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 */ req->request_appended = 1; 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) if (!(flags & DNS_OPTION_MISC)) return 0; log(EVDNS_LOG_DEBUG, "Setting retries to %d", 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; } @@ -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) printf("eeek, couldn't send reply.\n"); } diff --git a/src/or/eventdns.h b/src/or/eventdns.h index 5073c797a5..d1c34ade7c 100644 --- a/src/or/eventdns.h +++ b/src/or/eventdns.h @@ -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_transaction_id_fn(uint16_t (*fn)(void)); +void evdns_set_random_bytes_fn(void (*fn)(char *, size_t)); #define DNS_NO_SEARCH 1 diff --git a/src/or/or.h b/src/or/or.h index 147cffca76..4d1b7de2fe 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2397,6 +2397,8 @@ typedef struct { * the local domains. */ int ServerDNSDetectHijacking; /**< Boolean: If true, check for DNS failure * hijacking. */ + int ServerDNSRandomizeCase; /**< Boolean: Use the 0x20-hack to prevent + * DNS poisoning attacks. */ char *ServerDNSResolvConfFile; /**< If provided, we configure our internal * resolver from the file here rather than from * /etc/resolv.conf (Unix) or the registry (Windows). */