Commit message: evdns: fix searching empty hostnames

From #332:
  Here follows a bug report by **Guido Vranken** via the _Tor bug bounty program_. Please credit Guido accordingly.

  ## Bug report

  The DNS code of Libevent contains this rather obvious OOB read:

  ```c
  static char *
  search_make_new(const struct search_state *const state, int n, const char *const base_name) {
      const size_t base_len = strlen(base_name);
      const char need_to_append_dot = base_name[base_len - 1] == '.' ? 0 : 1;
  ```

  If the length of ```base_name``` is 0, then line 3125 reads 1 byte before the buffer. This will trigger a crash on ASAN-protected builds.

  To reproduce:

  Build libevent with ASAN:
  ```
  $ CFLAGS='-fomit-frame-pointer -fsanitize=address' ./configure && make -j4
  ```
  Put the attached ```resolv.conf``` and ```poc.c``` in the source directory and then do:

  ```
  $ gcc -fsanitize=address -fomit-frame-pointer poc.c .libs/libevent.a
  $ ./a.out
  =================================================================
  ==22201== ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60060000efdf at pc 0x4429da bp 0x7ffe1ed47300 sp 0x7ffe1ed472f8
  READ of size 1 at 0x60060000efdf thread T0
  ```

P.S. we can add a check earlier, but since this is very uncommon, I didn't add it.

Fixes: #332
CVE: CVE-2016-10197
CWE: CWE-125
Vulnerability description: The search_make_new function in evdns.c in libevent before 2.1.6-beta allows attackers to cause a denial of service (out-of-bounds read) via an empty hostname.

Project: libevent
File: evdns.c

dnsname_to_labels(u8 *const buf, size_t buf_len, off_t j,
				  const char *name, const size_t name_len,
				  struct dnslabel_table *table) {
	const char *end = name + name_len;
	int ref = 0;
	u16 t_;

#define APPEND16(x) do {						\
		if (j + 2 > (off_t)buf_len)				\
			goto overflow;					\
		t_ = htons(x);						\
		memcpy(buf + j, &t_, 2);				\
		j += 2;							\
	} while (0)
#define APPEND32(x) do {						\
		if (j + 4 > (off_t)buf_len)				\
			goto overflow;					\
		t32_ = htonl(x);					\
		memcpy(buf + j, &t32_, 4);				\
		j += 4;							\
	} while (0)

	if (name_len > 255) return -2;

	for (;;) {
		const char *const start = name;
		if (table && (ref = dnslabel_table_get_pos(table, name)) >= 0) {
			APPEND16(ref | 0xc000);
			return j;
		}
		name = strchr(name, '.');
		if (!name) {
			const size_t label_len = end - start;
			if (label_len > 63) return -1;
			if ((size_t)(j+label_len+1) > buf_len) return -2;
			if (table) dnslabel_table_add(table, start, j);
			buf[j++] = (ev_uint8_t)label_len;

			memcpy(buf + j, start, label_len);
			j += (int) label_len;
			break;
		} else {
			/* append length of the label. */
			const size_t label_len = name - start;
			if (label_len > 63) return -1;
			if ((size_t)(j+label_len+1) > buf_len) return -2;
			if (table) dnslabel_table_add(table, start, j);
			buf[j++] = (ev_uint8_t)label_len;

			memcpy(buf + j, start, label_len);
			j += (int) label_len;
			/* hop over the '.' */
			name++;
		}
	}

	/* the labels must be terminated by a 0. */
	/* It's possible that the name ended in a . */
	/* in which case the zero is already there */
	if (!j || buf[j-1]) buf[j++] = 0;
	return j;
 overflow:
	return (-2);
}

Project: libevent
File: evdns.c

evdns_base_clear_host_addresses(struct evdns_base *base)
{
	struct hosts_entry *victim;
	EVDNS_LOCK(base);
	while ((victim = TAILQ_FIRST(&base->hostsdb))) {
		TAILQ_REMOVE(&base->hostsdb, victim, next);
		mm_free(victim);
	}
	EVDNS_UNLOCK(base);
}

Project: libevent
File: evdns.c

evdns_base_count_nameservers(struct evdns_base *base)
{
	const struct nameserver *server;
	int n = 0;

	EVDNS_LOCK(base);
	server = base->server_head;
	if (!server)
		goto done;
	do {
		++n;
		server = server->next;
	} while (server != base->server_head);
done:
	EVDNS_UNLOCK(base);
	return n;
}

Project: libevent
File: evdns.c

evdns_base_nameserver_sockaddr_add(struct evdns_base *base,
    const struct sockaddr *sa, ev_socklen_t len, unsigned flags)
{
	int res;
	EVUTIL_ASSERT(base);
	EVDNS_LOCK(base);
	res = evdns_nameserver_add_impl_(base, sa, len);
	EVDNS_UNLOCK(base);
	return res;
}

Project: libevent
File: evdns.c

evdns_base_parse_hosts_line(struct evdns_base *base, char *line)
{
	char *strtok_state;
	static const char *const delims = " \t";
	char *const addr = strtok_r(line, delims, &strtok_state);
	char *hostname, *hash;
	struct sockaddr_storage ss;
	int socklen = sizeof(ss);
	ASSERT_LOCKED(base);

#define NEXT_TOKEN strtok_r(NULL, delims, &strtok_state)

	if (!addr || *addr == '#')
		return 0;

	memset(&ss, 0, sizeof(ss));
	if (evutil_parse_sockaddr_port(addr, (struct sockaddr*)&ss, &socklen)<0)
		return -1;
	if (socklen > (int)sizeof(struct sockaddr_in6))
		return -1;

	if (sockaddr_getport((struct sockaddr*)&ss))
		return -1;

	while ((hostname = NEXT_TOKEN)) {
		struct hosts_entry *he;
		size_t namelen;
		if ((hash = strchr(hostname, '#'))) {
			if (hash == hostname)
				return 0;
			*hash = '\0';
		}

		namelen = strlen(hostname);

		he = mm_calloc(1, sizeof(struct hosts_entry)+namelen);
		if (!he)
			return -1;
		EVUTIL_ASSERT(socklen <= (int)sizeof(he->addr));
		memcpy(&he->addr, &ss, socklen);
		memcpy(he->hostname, hostname, namelen+1);
		he->addrlen = socklen;

		TAILQ_INSERT_TAIL(&base->hostsdb, he, next);

		if (hash)
			return 0;
	}

	return 0;
#undef NEXT_TOKEN
}

Project: libevent
File: evdns.c

evdns_cancel_request(struct evdns_base *base, struct evdns_request *handle)
{
	struct request *req;

	if (!handle->current_req)
		return;

	if (!base) {
		/* This redundancy is silly; can we fix it? (Not for 2.0) XXXX */
		base = handle->base;
		if (!base)
			base = handle->current_req->base;
	}

	EVDNS_LOCK(base);
	if (handle->pending_cb) {
		EVDNS_UNLOCK(base);
		return;
	}

	req = handle->current_req;
	ASSERT_VALID_REQUEST(req);

	reply_schedule_callback(req, 0, DNS_ERR_CANCEL, NULL);
	if (req->ns) {
		/* remove from inflight queue */
		request_finished(req, &REQ_HEAD(base, req->trans_id), 1);
	} else {
		/* remove from global_waiting head */
		request_finished(req, &base->req_waiting_head, 1);
	}
	EVDNS_UNLOCK(base);
}

Project: libevent
File: evdns.c

evdns_resolv_conf_parse(int flags, const char *const filename) {
	if (!current_base)
		current_base = evdns_base_new(NULL, 0);
	return evdns_base_resolv_conf_parse(current_base, flags, filename);
}

Project: libevent
File: evdns.c

evdns_set_random_bytes_fn(void (*fn)(char *, size_t))
{
}

Project: libevent
File: evdns.c

evdns_transmit(struct evdns_base *base) {
	char did_try_to_transmit = 0;
	int i;

	ASSERT_LOCKED(base);
	for (i = 0; i < base->n_req_heads; ++i) {
		if (base->req_heads[i]) {
			struct request *const started_at = base->req_heads[i], *req = started_at;
			/* first transmit all the requests which are currently waiting */
			do {
				if (req->transmit_me) {
					did_try_to_transmit = 1;
					evdns_request_transmit(req);
				}

				req = req->next;
			} while (req != started_at);
		}
	}

	return did_try_to_transmit;
}

Project: libevent
File: evdns.c

free_getaddrinfo_request(struct evdns_getaddrinfo_request *data)
{
	/* DO NOT CALL this if either of the requests is pending.  Only once
	 * both callbacks have been invoked is it safe to free the request */
	if (data->pending_result)
		evutil_freeaddrinfo(data->pending_result);
	if (data->cname_result)
		mm_free(data->cname_result);
	event_del(&data->timeout);
	mm_free(data);
	return;
}

Project: libevent
File: evdns.c

nameserver_failed(struct nameserver *const ns, const char *msg) {
	struct request *req, *started_at;
	struct evdns_base *base = ns->base;
	int i;
	char addrbuf[128];

	ASSERT_LOCKED(base);
	/* if this nameserver has already been marked as failed */
	/* then don't do anything */
	if (!ns->state) return;

	log(EVDNS_LOG_MSG, "Nameserver %s has failed: %s",
	    evutil_format_sockaddr_port_(
		    (struct sockaddr *)&ns->address,
		    addrbuf, sizeof(addrbuf)),
	    msg);

	base->global_good_nameservers--;
	EVUTIL_ASSERT(base->global_good_nameservers >= 0);
	if (base->global_good_nameservers == 0) {
		log(EVDNS_LOG_MSG, "All nameservers have failed");
	}

	ns->state = 0;
	ns->failed_times = 1;

	if (evtimer_add(&ns->timeout_event,
		&base->global_nameserver_probe_initial_timeout) < 0) {
		log(EVDNS_LOG_WARN,
		    "Error from libevent when adding timer event for %s",
		    evutil_format_sockaddr_port_(
			    (struct sockaddr *)&ns->address,
			    addrbuf, sizeof(addrbuf)));
		/* ???? Do more? */
	}

	/* walk the list of inflight requests to see if any can be reassigned to */
	/* a different server. Requests in the waiting queue don't have a */
	/* nameserver assigned yet */

	/* if we don't have *any* good nameservers then there's no point */
	/* trying to reassign requests to one */
	if (!base->global_good_nameservers) return;

	for (i = 0; i < base->n_req_heads; ++i) {
		req = started_at = base->req_heads[i];
		if (req) {
			do {
				if (req->tx_count == 0 && req->ns == ns) {
					/* still waiting to go out, can be moved */
					/* to another server */
					request_swap_ns(req, nameserver_pick(base));
				}
				req = req->next;
			} while (req != started_at);
		}
	}
}

Project: libevent
File: evdns.c

nameserver_probe_failed(struct nameserver *const ns) {
	struct timeval timeout;
	int i;

	ASSERT_LOCKED(ns->base);
	(void) evtimer_del(&ns->timeout_event);
	if (ns->state == 1) {
		/* This can happen if the nameserver acts in a way which makes us mark */
		/* it as bad and then starts sending good replies. */
		return;
	}

#define MAX_PROBE_TIMEOUT 3600
#define TIMEOUT_BACKOFF_FACTOR 3

	memcpy(&timeout, &ns->base->global_nameserver_probe_initial_timeout,
	    sizeof(struct timeval));
	for (i=ns->failed_times; i > 0 && timeout.tv_sec < MAX_PROBE_TIMEOUT; --i) {
		timeout.tv_sec *= TIMEOUT_BACKOFF_FACTOR;
		timeout.tv_usec *= TIMEOUT_BACKOFF_FACTOR;
		if (timeout.tv_usec > 1000000) {
			timeout.tv_sec += timeout.tv_usec / 1000000;
			timeout.tv_usec %= 1000000;
		}
	}
	if (timeout.tv_sec > MAX_PROBE_TIMEOUT) {
		timeout.tv_sec = MAX_PROBE_TIMEOUT;
		timeout.tv_usec = 0;
	}

	ns->failed_times++;

	if (evtimer_add(&ns->timeout_event, &timeout) < 0) {
		char addrbuf[128];
		log(EVDNS_LOG_WARN,
		    "Error from libevent when adding timer event for %s",
		    evutil_format_sockaddr_port_(
			    (struct sockaddr *)&ns->address,
			    addrbuf, sizeof(addrbuf)));
	}
}

Project: libevent
File: evdns.c

reply_run_callback(struct event_callback *d, void *user_pointer)
{
	struct deferred_reply_callback *cb =
	    EVUTIL_UPCAST(d, struct deferred_reply_callback, deferred);

	switch (cb->request_type) {
	case TYPE_A:
		if (cb->have_reply)
			cb->user_callback(DNS_ERR_NONE, DNS_IPv4_A,
			    cb->reply.data.a.addrcount, cb->ttl,
			    cb->reply.data.a.addresses,
			    user_pointer);
		else
			cb->user_callback(cb->err, 0, 0, cb->ttl, NULL, user_pointer);
		break;
	case TYPE_PTR:
		if (cb->have_reply) {
			char *name = cb->reply.data.ptr.name;
			cb->user_callback(DNS_ERR_NONE, DNS_PTR, 1, cb->ttl,
			    &name, user_pointer);
		} else {
			cb->user_callback(cb->err, 0, 0, cb->ttl, NULL, user_pointer);
		}
		break;
	case TYPE_AAAA:
		if (cb->have_reply)
			cb->user_callback(DNS_ERR_NONE, DNS_IPv6_AAAA,
			    cb->reply.data.aaaa.addrcount, cb->ttl,
			    cb->reply.data.aaaa.addresses,
			    user_pointer);
		else
			cb->user_callback(cb->err, 0, 0, cb->ttl, NULL, user_pointer);
		break;
	default:
		EVUTIL_ASSERT(0);
	}

	if (cb->handle && cb->handle->pending_cb) {
		mm_free(cb->handle);
	}

	mm_free(cb);
}

Project: libevent
File: evdns.c

search_request_finished(struct evdns_request *const handle) {
	ASSERT_LOCKED(handle->current_req->base);
	if (handle->search_state) {
		search_state_decref(handle->search_state);
		handle->search_state = NULL;
	}
	if (handle->search_origname) {
		mm_free(handle->search_origname);
		handle->search_origname = NULL;
	}
}