* May 30 2007
+- Shmulik Regev brought cryptographically secure transaction IDs:
+
+ The c-ares library implementation uses a DNS "Transaction ID" field that is
+ seeded with a pseudo random number (based on gettimeofday) which is
+ incremented (++) between consecutive calls and is therefore rather
+ predictable. In general, predictability of DNS Transaction ID is a well
+ known security problem (e.g.
+ http://bak.spc.org/dms/archive/dns_id_attack.txt) and makes a c-ares based
+ implementation vulnerable to DNS poisoning. Credit goes to Amit Klein
+ (Trusteer) for identifying this problem.
+
+ The patch I wrote changes the implementation to use a more secure way of
+ generating unique IDs. It starts by obtaining a key with reasonable entropy
+ which is used with an RC4 stream to generate the cryptographically secure
+ transaction IDs.
+
+ Note that the key generation code (in ares_init:randomize_key) has two
+ versions, the Windows specific one uses a cryptographically safe function
+ provided (but undocumented :) by the operating system (described at
+ http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx). The
+ default implementation is a bit naive and uses the standard 'rand'
+ function. Surely a better way to generate random keys exists for other
+ platforms.
+
+ The patch can be tested by using the adig utility and using the '-s' option.
+
- Brad House added ares_save_options() and ares_destroy_options() that can be
used to keep options for later re-usal when ares_init_options() is used.
static int set_search(ares_channel channel, const char *str);
static int set_options(ares_channel channel, const char *str);
static const char *try_option(const char *p, const char *q, const char *opt);
+static void init_id_key(rc4_key* key,int key_data_len);
+
#ifndef WIN32
static int sortlist_alloc(struct apattern **sortlist, int *nsort, struct apattern *pat);
static int ip_addr(const char *s, int len, struct in_addr *addr);
#endif
#define ARES_CONFIG_CHECK(x) (x->lookups && x->nsort > -1 && \
- x->nservers > -1 && \
+ x->nservers > -1 && \
x->ndomains > -1 && \
- x->ndots > -1 && x->timeout > -1 && \
- x->tries > -1)
+ x->ndots > -1 && x->timeout > -1 && \
+ x->tries > -1)
int ares_init(ares_channel *channelptr)
{
int i;
int status = ARES_SUCCESS;
struct server_state *server;
- struct timeval tv;
#ifdef CURLDEBUG
const char *env = getenv("CARES_MEMDEBUG");
server->qtail = NULL;
}
- /* Choose a somewhat random query ID. The main point is to avoid
- * collisions with stale queries. An attacker trying to spoof a DNS
- * answer also has to guess the query ID, but it's only a 16-bit
- * field, so there's not much to be done about that.
- */
- gettimeofday(&tv, NULL);
- channel->next_id = (unsigned short)
- ((tv.tv_sec ^ tv.tv_usec ^ getpid()) & 0xffff);
+ init_id_key(&channel->id_key, ARES_ID_KEY_LEN);
+ channel->next_id = ares__generate_new_id(&channel->id_key);
channel->queries = NULL;
*channelptr = channel;
pat->mask.addr.addr4.s_addr = htonl(IN_CLASSC_NET);
}
#endif
+/* initialize an rc4 key. If possible a cryptographically secure random key
+ is generated using a suitable function (for example win32's RtlGenRandom as
+ described in
+ http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx
+ otherwise the code defaults to cross-platform albeit less secure mechanism
+ using rand
+*/
+static void randomize_key(unsigned char* key,int key_data_len)
+{
+ int randomized = 0;
+#ifdef WIN32
+ HMODULE lib=LoadLibrary("ADVAPI32.DLL");
+ if (lib) {
+ BOOLEAN (APIENTRY *pfn)(void*, ULONG) =
+ (BOOLEAN (APIENTRY *)(void*,ULONG))GetProcAddress(lib,"SystemFunction036");
+ if (pfn && pfn(key,key_data_len) )
+ randomized = 1;
+
+ FreeLibrary(lib);
+ }
+#endif
+
+ if ( !randomized ) {
+ int counter;
+ for (counter=0;counter<key_data_len;counter++)
+ key[counter]=rand() % 256;
+ }
+}
+
+static void init_id_key(rc4_key* key,int key_data_len)
+{
+ unsigned char index1;
+ unsigned char index2;
+ unsigned char* state;
+ short counter;
+ unsigned char *key_data_ptr = 0;
+
+ key_data_ptr = calloc(1,key_data_len);
+ randomize_key(key->state,key_data_len);
+ state = &key->state[0];
+ for(counter = 0; counter < 256; counter++)
+ state[counter] = counter;
+ key->x = 0;
+ key->y = 0;
+ index1 = 0;
+ index2 = 0;
+ for(counter = 0; counter < 256; counter++)
+ {
+ index2 = (key_data_ptr[index1] + state[counter] +
+ index2) % 256;
+ ARES_SWAP_BYTE(&state[counter], &state[index2]);
+
+ index1 = (index1 + 1) % key_data_len;
+ }
+ free(key_data_ptr);
+
+}
+
+short ares__generate_new_id(rc4_key* key)
+{
+ short r;
+ ares__rc4(key, (unsigned char *)&r, sizeof(r));
+ return r;
+}
#endif
+#define ARES_ID_KEY_LEN 31
+
#include "ares_ipv6.h"
struct send_request {
unsigned short type;
};
+typedef struct rc4_key
+{
+ unsigned char state[256];
+ unsigned char x;
+ unsigned char y;
+} rc4_key;
+
struct ares_channeldata {
/* Configuration data */
int flags;
/* ID to use for next query */
unsigned short next_id;
+ /* key to use when generating new ids */
+ rc4_key id_key;
/* Active queries */
struct query *queries;
void *sock_state_cb_data;
};
+void ares__rc4(rc4_key* key,unsigned char *buffer_ptr, int buffer_len);
void ares__send_query(ares_channel channel, struct query *query, time_t now);
void ares__close_sockets(ares_channel channel, struct server_state *server);
int ares__get_hostent(FILE *fp, int family, struct hostent **host);
int ares__read_line(FILE *fp, char **buf, int *bufsize);
+short ares__generate_new_id(rc4_key* key);
+
+#define ARES_SWAP_BYTE(a,b) \
+ { unsigned char swapByte = *(a); *(a) = *(b); *(b) = swapByte; }
#define SOCK_STATE_CALLBACK(c, s, r, w) \
do { \
static void qcallback(void *arg, int status, unsigned char *abuf, int alen);
+void ares__rc4(rc4_key* key, unsigned char *buffer_ptr, int buffer_len)
+{
+ unsigned char x;
+ unsigned char y;
+ unsigned char* state;
+ unsigned char xorIndex;
+ short counter;
+
+ x = key->x;
+ y = key->y;
+
+ state = &key->state[0];
+ for(counter = 0; counter < buffer_len; counter ++)
+ {
+ x = (x + 1) % 256;
+ y = (state[x] + y) % 256;
+ ARES_SWAP_BYTE(&state[x], &state[y]);
+
+ xorIndex = (state[x] + state[y]) % 256;
+
+ buffer_ptr[counter] ^= state[xorIndex];
+ }
+ key->x = x;
+ key->y = y;
+}
+
+static struct query* find_query_by_id(ares_channel channel, int id)
+{
+ int qid;
+ struct query* q;
+ DNS_HEADER_SET_QID(((unsigned char*)&qid), id);
+
+ /* Find the query corresponding to this packet. */
+ for (q = channel->queries; q; q = q->next)
+ {
+ if (q->qid == qid)
+ return q;
+ }
+ return NULL;
+}
+
+
+/* a unique query id is generated using an rc4 key. Since the id may already
+ be used by a running query (as infrequent as it may be), a lookup is
+ performed per id generation. In practice this search should happen only
+ once per newly generated id
+*/
+static int generate_unique_id(ares_channel channel)
+{
+ int id;
+
+ do {
+ id = ares__generate_new_id(&channel->id_key);
+ } while (find_query_by_id(channel,id));
+
+ return id;
+}
+
void ares_query(ares_channel channel, const char *name, int dnsclass,
int type, ares_callback callback, void *arg)
{
rd = !(channel->flags & ARES_FLAG_NORECURSE);
status = ares_mkquery(name, dnsclass, type, channel->next_id, rd, &qbuf,
&qlen);
- channel->next_id++;
+ channel->next_id = generate_unique_id(channel);
+
if (status != ARES_SUCCESS)
{
callback(arg, status, NULL, 0);