]> granicus.if.org Git - libevent/commitdiff
Implement increased DSN-poisoning resistance via the 0x20 hack.
authorNick Mathewson <nickm@torproject.org>
Wed, 3 Dec 2008 20:09:13 +0000 (20:09 +0000)
committerNick Mathewson <nickm@torproject.org>
Wed, 3 Dec 2008 20:09:13 +0000 (20:09 +0000)
svn:r958

ChangeLog
evdns.c
include/event2/dns.h
test/regress_dns.c

index 5a6bf083b0d9709ad7642c456cc88a9afb89b219..98f374e6f1befdabe57c2f897959dbf8acfdbfd0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/evdns.c b/evdns.c
index 2c99baa12ea7a5931a8896a5c4ea974a305d8a75..ab309848ab5e369ad8b610d0d75447d3f44db5cc 100644 (file)
--- a/evdns.c
+++ b/evdns.c
 #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
index c37af4cdc54dcc1694fd0d97e8b1009cabf74840..bfebcb73f39799c101740ffb293ce1a07cd6c15f 100644 (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
 
 /*
index a4b08e5312bc0f1c745ef5cb15aec2d9f1348c4b..0e48eef7508c3ae08514f8f9dd3aab51edb9f61a 100644 (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 {