Commit message: BUG: dns: Prevent out-of-bounds read in dns_validate_dns_response()

We need to make sure that the record length is not making us read
past the end of the data we received.
Before this patch we could for example read the 16 bytes
corresponding to an AAAA record from the non-initialized part of
the buffer, possibly accessing anything that was left on the stack,
or even past the end of the 8193-byte buffer, depending on the
value of accepted_payload_size.

To be backported to 1.8, probably also 1.7.
CVE: CVE-2018-20102
CWE: CWE-125
Vulnerability description: An out-of-bounds read in dns_validate_dns_response in dns.c was discovered in HAProxy through 1.8.14. Due to a missing check when validating DNS responses, remote attackers might be able read the 16 bytes corresponding to an AAAA record from the non-initialized part of the buffer, possibly accessing anything that was left on the stack, or even past the end of the 8193-byte buffer, depending on the value of accepted_payload_size.

Project: haproxy
File: dns.c

static int cli_io_handler_dump_resolvers_to_buffer(struct appctx *appctx)
{
	struct stream_interface *si = appctx->owner;
	struct dns_resolvers    *resolvers;
	struct dns_nameserver   *ns;

	chunk_reset(&trash);

	switch (appctx->st2) {
	case STAT_ST_INIT:
		appctx->st2 = STAT_ST_LIST; /* let's start producing data */
		/* fall through */

	case STAT_ST_LIST:
		if (LIST_ISEMPTY(&dns_resolvers)) {
			chunk_appendf(&trash, "No resolvers found\n");
		}
		else {
			list_for_each_entry(resolvers, &dns_resolvers, list) {
				if (appctx->ctx.cli.p0 != NULL && appctx->ctx.cli.p0 != resolvers)
					continue;

				chunk_appendf(&trash, "Resolvers section %s\n", resolvers->id);
				list_for_each_entry(ns, &resolvers->nameservers, list) {
					chunk_appendf(&trash, " nameserver %s:\n", ns->id);
					chunk_appendf(&trash, "  sent:        %lld\n", ns->counters.sent);
					chunk_appendf(&trash, "  snd_error:   %lld\n", ns->counters.snd_error);
					chunk_appendf(&trash, "  valid:       %lld\n", ns->counters.valid);
					chunk_appendf(&trash, "  update:      %lld\n", ns->counters.update);
					chunk_appendf(&trash, "  cname:       %lld\n", ns->counters.cname);
					chunk_appendf(&trash, "  cname_error: %lld\n", ns->counters.cname_error);
					chunk_appendf(&trash, "  any_err:     %lld\n", ns->counters.any_err);
					chunk_appendf(&trash, "  nx:          %lld\n", ns->counters.nx);
					chunk_appendf(&trash, "  timeout:     %lld\n", ns->counters.timeout);
					chunk_appendf(&trash, "  refused:     %lld\n", ns->counters.refused);
					chunk_appendf(&trash, "  other:       %lld\n", ns->counters.other);
					chunk_appendf(&trash, "  invalid:     %lld\n", ns->counters.invalid);
					chunk_appendf(&trash, "  too_big:     %lld\n", ns->counters.too_big);
					chunk_appendf(&trash, "  truncated:   %lld\n", ns->counters.truncated);
					chunk_appendf(&trash, "  outdated:    %lld\n",  ns->counters.outdated);
				}
				chunk_appendf(&trash, "\n");
			}
		}

		/* display response */
		if (ci_putchk(si_ic(si), &trash) == -1) {
			/* let's try again later from this session. We add ourselves into
			 * this session's users so that it can remove us upon termination.
			 */
			si_rx_room_blk(si);
			return 0;
		}
		/* fall through */

	default:
		appctx->st2 = STAT_ST_FIN;
		return 1;
	}
}

Project: haproxy
File: dns.c

static int cli_parse_stat_resolvers(char **args, char *payload, struct appctx *appctx, void *private)
{
	struct dns_resolvers *presolvers;

	if (*args[2]) {
		list_for_each_entry(presolvers, &dns_resolvers, list) {
			if (strcmp(presolvers->id, args[2]) == 0) {
				appctx->ctx.cli.p0 = presolvers;
				break;
			}
		}
		if (appctx->ctx.cli.p0 == NULL) {
			appctx->ctx.cli.severity = LOG_ERR;
			appctx->ctx.cli.msg = "Can't find that resolvers section\n";
			appctx->st0 = CLI_ST_PRINT;
			return 1;
		}
	}
	return 0;
}

Project: haproxy
File: dns.c

static void dns_deinit(void)
{
	struct dns_resolvers  *resolvers, *resolversback;
	struct dns_nameserver *ns, *nsback;
	struct dns_resolution *res, *resback;
	struct dns_requester  *req, *reqback;
	struct dns_srvrq      *srvrq, *srvrqback;

	list_for_each_entry_safe(resolvers, resolversback, &dns_resolvers, list) {
		list_for_each_entry_safe(ns, nsback, &resolvers->nameservers, list) {
			free(ns->id);
			free((char *)ns->conf.file);
			if (ns->dgram && ns->dgram->t.sock.fd != -1)
				fd_delete(ns->dgram->t.sock.fd);
			free(ns->dgram);
			LIST_DEL(&ns->list);
			free(ns);
		}

		list_for_each_entry_safe(res, resback, &resolvers->resolutions.curr, list) {
			list_for_each_entry_safe(req, reqback, &res->requesters, list) {
				LIST_DEL(&req->list);
				free(req);
			}
			dns_free_resolution(res);
		}

		list_for_each_entry_safe(res, resback, &resolvers->resolutions.wait, list) {
			list_for_each_entry_safe(req, reqback, &res->requesters, list) {
				LIST_DEL(&req->list);
				free(req);
			}
			dns_free_resolution(res);
		}

		free(resolvers->id);
		free((char *)resolvers->conf.file);
		task_delete(resolvers->t);
		task_free(resolvers->t);
		LIST_DEL(&resolvers->list);
		free(resolvers);
	}

	list_for_each_entry_safe(srvrq, srvrqback, &dns_srvrq_list, list) {
		free(srvrq->name);
		free(srvrq->hostname_dn);
		LIST_DEL(&srvrq->list);
		free(srvrq);
	}
}

Project: haproxy
File: dns.c

int dns_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len)
{
	char *ptr;
	int i, sz;

	if (str_len < dn_len - 1)
		return -1;

	ptr = str;
	for (i = 0; i < dn_len-1; ++i) {
		sz = dn[i];
		if (i)
			*ptr++ = '.';
		memcpy(ptr, dn+i+1, sz);
		ptr += sz;
		i   += sz;
	}
	*ptr++ = '\0';
	return (ptr - str);
}

Project: haproxy
File: dns.c

static int dns_finalize_config(void)
{
	struct dns_resolvers *resolvers;
	struct proxy	     *px;
	int err_code = 0;

	/* allocate pool of resolution per resolvers */
	list_for_each_entry(resolvers, &dns_resolvers, list) {
		struct dns_nameserver *ns;
		struct task           *t;

		/* Check if we can create the socket with nameservers info */
		list_for_each_entry(ns, &resolvers->nameservers, list) {
			struct dgram_conn *dgram = NULL;
			int fd;

			/* Check nameserver info */
			if ((fd = socket(ns->addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
				ha_alert("config : resolvers '%s': can't create socket for nameserver '%s'.\n",
					 resolvers->id, ns->id);
				err_code |= (ERR_ALERT|ERR_ABORT);
				continue;
			}
			if (connect(fd, (struct sockaddr*)&ns->addr, get_addr_len(&ns->addr)) == -1) {
				ha_alert("config : resolvers '%s': can't connect socket for nameserver '%s'.\n",
					 resolvers->id, ns->id);
				close(fd);
				err_code |= (ERR_ALERT|ERR_ABORT);
				continue;
			}
			close(fd);

			/* Create dgram structure that will hold the UPD socket
			 * and attach it on the current nameserver */
			if ((dgram = calloc(1, sizeof(*dgram))) == NULL) {
				ha_alert("config: resolvers '%s' : out of memory.\n",
					 resolvers->id);
				err_code |= (ERR_ALERT|ERR_ABORT);
				goto err;
			}

			/* Leave dgram partially initialized, no FD attached for
			 * now. */
			dgram->owner     = ns;
			dgram->data      = &resolve_dgram_cb;
			dgram->t.sock.fd = -1;
			ns->dgram        = dgram;
		}

		/* Create the task associated to the resolvers section */
		if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
			ha_alert("config : resolvers '%s' : out of memory.\n", resolvers->id);
			err_code |= (ERR_ALERT|ERR_ABORT);
			goto err;
		}

		/* Update task's parameters */
		t->process   = dns_process_resolvers;
		t->context   = resolvers;
		resolvers->t = t;
		task_wakeup(t, TASK_WOKEN_INIT);
	}

	for (px = proxies_list; px; px = px->next) {
		struct server *srv;

		for (srv = px->srv; srv; srv = srv->next) {
			struct dns_resolvers *resolvers;

			if (!srv->resolvers_id)
				continue;

			if ((resolvers = find_resolvers_by_id(srv->resolvers_id)) == NULL) {
				ha_alert("config : %s '%s', server '%s': unable to find required resolvers '%s'\n",
					 proxy_type_str(px), px->id, srv->id, srv->resolvers_id);
				err_code |= (ERR_ALERT|ERR_ABORT);
				continue;
			}
			srv->resolvers = resolvers;

			if (srv->srvrq && !srv->srvrq->resolvers) {
				srv->srvrq->resolvers = srv->resolvers;
				if (dns_link_resolution(srv->srvrq, OBJ_TYPE_SRVRQ, 0) == -1) {
					ha_alert("config : %s '%s' : unable to set DNS resolution for server '%s'.\n",
						 proxy_type_str(px), px->id, srv->id);
					err_code |= (ERR_ALERT|ERR_ABORT);
					continue;
				}
			}
			if (dns_link_resolution(srv, OBJ_TYPE_SERVER, 0) == -1) {
				ha_alert("config : %s '%s', unable to set DNS resolution for server '%s'.\n",
					 proxy_type_str(px), px->id, srv->id);
				err_code |= (ERR_ALERT|ERR_ABORT);
				continue;
			}
		}
	}

	if (err_code & (ERR_ALERT|ERR_ABORT))
		goto err;

	return err_code;
  err:
	dns_deinit();
	return err_code;

}

Project: haproxy
File: dns.c

static void dns_free_resolution(struct dns_resolution *resolution)
{
	struct dns_requester *req, *reqback;

	/* clean up configuration */
	dns_reset_resolution(resolution);
	resolution->hostname_dn = NULL;
	resolution->hostname_dn_len = 0;

	list_for_each_entry_safe(req, reqback, &resolution->requesters, list) {
		LIST_DEL(&req->list);
		req->resolution = NULL;
	}

	LIST_DEL(&resolution->list);
	pool_free(dns_resolution_pool, resolution);
}

Project: haproxy
File: dns.c

int dns_get_ip_from_response(struct dns_response_packet *dns_p,
                             struct dns_options *dns_opts, void *currentip,
                             short currentip_sin_family,
                             void **newip, short *newip_sin_family,
                             void *owner)
{
	struct dns_answer_item *record;
	int family_priority;
	int currentip_found;
	unsigned char *newip4, *newip6;
	int currentip_sel;
	int j;
	int score, max_score;
	int allowed_duplicated_ip;

	family_priority   = dns_opts->family_prio;
	allowed_duplicated_ip = dns_opts->accept_duplicate_ip;
	*newip = newip4   = newip6 = NULL;
	currentip_found   = 0;
	*newip_sin_family = AF_UNSPEC;
	max_score         = -1;

	/* Select an IP regarding configuration preference.
	 * Top priority is the preferred network ip version,
	 * second priority is the preferred network.
	 * the last priority is the currently used IP,
	 *
	 * For these three priorities, a score is calculated. The
	 * weight are:
	 *  8 - preferred ip version.
	 *  4 - preferred network.
	 *  2 - if the ip in the record is not affected to any other server in the same backend (duplication)
	 *  1 - current ip.
	 * The result with the biggest score is returned.
	 */

	list_for_each_entry(record, &dns_p->answer_list, list) {
		void *ip;
		unsigned char ip_type;

		if (record->type == DNS_RTYPE_A) {
			ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
			ip_type = AF_INET;
		}
		else if (record->type == DNS_RTYPE_AAAA) {
			ip_type = AF_INET6;
			ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
		}
		else
			continue;
		score = 0;

		/* Check for preferred ip protocol. */
		if (ip_type == family_priority)
			score += 8;

		/* Check for preferred network. */
		for (j = 0; j < dns_opts->pref_net_nb; j++) {

			/* Compare only the same adresses class. */
			if (dns_opts->pref_net[j].family != ip_type)
				continue;

			if ((ip_type == AF_INET &&
			     in_net_ipv4(ip,
			                 &dns_opts->pref_net[j].mask.in4,
			                 &dns_opts->pref_net[j].addr.in4)) ||
			    (ip_type == AF_INET6 &&
			     in_net_ipv6(ip,
			                 &dns_opts->pref_net[j].mask.in6,
			                 &dns_opts->pref_net[j].addr.in6))) {
				score += 4;
				break;
			}
		}

		/* Check if the IP found in the record is already affected to a
		 * member of a group.  If not, the score should be incremented
		 * by 2. */
		if (owner && snr_check_ip_callback(owner, ip, &ip_type)) {
			if (!allowed_duplicated_ip) {
				continue;
			}
		} else {
			score += 2;
		}

		/* Check for current ip matching. */
		if (ip_type == currentip_sin_family &&
		    ((currentip_sin_family == AF_INET &&
		      !memcmp(ip, currentip, 4)) ||
		     (currentip_sin_family == AF_INET6 &&
		      !memcmp(ip, currentip, 16)))) {
			score++;
			currentip_sel = 1;
		}
		else
			currentip_sel = 0;

		/* Keep the address if the score is better than the previous
		 * score. The maximum score is 15, if this value is reached, we
		 * break the parsing. Implicitly, this score is reached the ip
		 * selected is the current ip. */
		if (score > max_score) {
			if (ip_type == AF_INET)
				newip4 = ip;
			else
				newip6 = ip;
			currentip_found = currentip_sel;
			if (score == 15)
				return DNS_UPD_NO;
			max_score = score;
		}
	} /* list for each record entries */

	/* No IP found in the response */
	if (!newip4 && !newip6)
		return DNS_UPD_NO_IP_FOUND;

	/* Case when the caller looks first for an IPv4 address */
	if (family_priority == AF_INET) {
		if (newip4) {
			*newip = newip4;
			*newip_sin_family = AF_INET;
		}
		else if (newip6) {
			*newip = newip6;
			*newip_sin_family = AF_INET6;
		}
		if (!currentip_found)
			goto not_found;
	}
	/* Case when the caller looks first for an IPv6 address */
	else if (family_priority == AF_INET6) {
		if (newip6) {
			*newip = newip6;
			*newip_sin_family = AF_INET6;
		}
		else if (newip4) {
			*newip = newip4;
			*newip_sin_family = AF_INET;
		}
		if (!currentip_found)
			goto not_found;
	}
	/* Case when the caller have no preference (we prefer IPv6) */
	else if (family_priority == AF_UNSPEC) {
		if (newip6) {
			*newip = newip6;
			*newip_sin_family = AF_INET6;
		}
		else if (newip4) {
			*newip = newip4;
			*newip_sin_family = AF_INET;
		}
		if (!currentip_found)
			goto not_found;
	}

	/* No reason why we should change the server's IP address */
	return DNS_UPD_NO;

 not_found:
	list_for_each_entry(record, &dns_p->answer_list, list) {
		/* Move the first record to the end of the list, for internal
		 * round robin */
		LIST_DEL(&record->list);
		LIST_ADDQ(&dns_p->answer_list, &record->list);
		break;
	}
	return DNS_UPD_SRVIP_NOT_FOUND;
}

Project: haproxy
File: dns.c

int dns_hostname_validation(const char *string, char **err)
{
	const char *c, *d;
	int i;

	if (strlen(string) > DNS_MAX_NAME_SIZE) {
		if (err)
			*err = DNS_TOO_LONG_FQDN;
		return 0;
	}

	c = string;
	while (*c) {
		d = c;

		i = 0;
		while (*d != '.' && *d && i <= DNS_MAX_LABEL_SIZE) {
			i++;
			if (!((*d == '-') || (*d == '_') ||
			      ((*d >= 'a') && (*d <= 'z')) ||
			      ((*d >= 'A') && (*d <= 'Z')) ||
			      ((*d >= '0') && (*d <= '9')))) {
				if (err)
					*err = DNS_INVALID_CHARACTER;
				return 0;
			}
			d++;
		}

		if ((i >= DNS_MAX_LABEL_SIZE) && (d[i] != '.')) {
			if (err)
				*err = DNS_LABEL_TOO_LONG;
			return 0;
		}

		if (*d == '\0')
			goto out;

		c = ++d;
	}
 out:
	return 1;
}

Project: haproxy
File: dns.c

int dns_link_resolution(void *requester, int requester_type, int requester_locked)
{
	struct dns_resolution *res = NULL;
	struct dns_requester  *req;
	struct dns_resolvers  *resolvers;
	struct server         *srv   = NULL;
	struct dns_srvrq      *srvrq = NULL;
	char **hostname_dn;
	int   hostname_dn_len, query_type;

	switch (requester_type) {
		case OBJ_TYPE_SERVER:
			srv             = (struct server *)requester;
			hostname_dn     = &srv->hostname_dn;
			hostname_dn_len = srv->hostname_dn_len;
			resolvers       = srv->resolvers;
			query_type      = ((srv->dns_opts.family_prio == AF_INET)
					   ? DNS_RTYPE_A
					   : DNS_RTYPE_AAAA);
			break;

		case OBJ_TYPE_SRVRQ:
			srvrq           = (struct dns_srvrq *)requester;
			hostname_dn     = &srvrq->hostname_dn;
			hostname_dn_len = srvrq->hostname_dn_len;
			resolvers       = srvrq->resolvers;
			query_type      = DNS_RTYPE_SRV;
			break;

		default:
			goto err;
	}

	/* Get a resolution from the resolvers' wait queue or pool */
	if ((res = dns_pick_resolution(resolvers, hostname_dn, hostname_dn_len, query_type)) == NULL)
		goto err;

	if (srv) {
		if (!requester_locked)
			HA_SPIN_LOCK(SERVER_LOCK, &srv->lock);
		if (srv->dns_requester == NULL) {
			if ((req = calloc(1, sizeof(*req))) == NULL) {
				if (!requester_locked)
					HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock);
				goto err;
			}
			req->owner         = &srv->obj_type;
			srv->dns_requester = req;
		}
		else
			req = srv->dns_requester;
		if (!requester_locked)
			HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock);
	}
	else if (srvrq) {
		if (srvrq->dns_requester == NULL) {
			if ((req = calloc(1, sizeof(*req))) == NULL)
				goto err;
			req->owner           = &srvrq->obj_type;
			srvrq->dns_requester = req;
		}
		else
			req = srvrq->dns_requester;
	}
	else
		goto err;

	req->resolution         = res;
	req->requester_cb       = snr_resolution_cb;
	req->requester_error_cb = snr_resolution_error_cb;

	LIST_ADDQ(&res->requesters, &req->list);
	return 0;

  err:
	if (res && LIST_ISEMPTY(&res->requesters))
		dns_free_resolution(res);
	return -1;
}

Project: haproxy
File: dns.c

static struct dns_resolution *dns_pick_resolution(struct dns_resolvers *resolvers,
						  char **hostname_dn, int hostname_dn_len,
						  int query_type)
{
	struct dns_resolution *res;

	if (!*hostname_dn)
		goto from_pool;

	/* Search for same hostname and query type in resolutions.curr */
	list_for_each_entry(res, &resolvers->resolutions.curr, list) {
		if (!res->hostname_dn)
			continue;
		if ((query_type == res->prefered_query_type) &&
		    hostname_dn_len == res->hostname_dn_len  &&
		    !memcmp(*hostname_dn, res->hostname_dn, hostname_dn_len))
			return res;
	}

	/* Search for same hostname and query type in resolutions.wait */
	list_for_each_entry(res, &resolvers->resolutions.wait, list) {
		if (!res->hostname_dn)
			continue;
		if ((query_type == res->prefered_query_type) &&
		    hostname_dn_len == res->hostname_dn_len  &&
		    !memcmp(*hostname_dn, res->hostname_dn, hostname_dn_len))
			return res;
	}

  from_pool:
	/* No resolution could be found, so let's allocate a new one */
	res = pool_alloc(dns_resolution_pool);
	if (res) {
		memset(res, 0, sizeof(*res));
		res->resolvers  = resolvers;
		res->uuid       = resolution_uuid;
		res->status     = RSLV_STATUS_NONE;
		res->step       = RSLV_STEP_NONE;
		res->last_valid = now_ms;

		LIST_INIT(&res->requesters);
		LIST_INIT(&res->response.answer_list);

		res->prefered_query_type = query_type;
		res->query_type          = query_type;
		res->hostname_dn         = *hostname_dn;
		res->hostname_dn_len     = hostname_dn_len;

		++resolution_uuid;

		/* Move the resolution to the resolvers wait queue */
		LIST_ADDQ(&resolvers->resolutions.wait, &res->list);
	}
	return res;
}

Project: haproxy
File: dns.c

static struct task *dns_process_resolvers(struct task *t, void *context, unsigned short state)
{
	struct dns_resolvers  *resolvers = context;
	struct dns_resolution *res, *resback;
	int exp;

	HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock);

	/* Handle all expired resolutions from the active list */
	list_for_each_entry_safe(res, resback, &resolvers->resolutions.curr, list) {
		/* When we find the first resolution in the future, then we can
		 * stop here */
		exp = tick_add(res->last_query, resolvers->timeout.retry);
		if (!tick_is_expired(exp, now_ms))
			break;

		/* If current resolution has been tried too many times and
		 * finishes in timeout we update its status and remove it from
		 * the list */
		if (!res->try) {
			struct dns_requester *req;

			/* Notify the result to the requesters */
			if (!res->nb_responses)
				res->status = RSLV_STATUS_TIMEOUT;
			list_for_each_entry(req, &res->requesters, list)
				req->requester_error_cb(req, res->status);

			/* Clean up resolution info and remove it from the
			 * current list */
			dns_reset_resolution(res);
			LIST_DEL(&res->list);
			LIST_ADDQ(&resolvers->resolutions.wait, &res->list);
		}
		else {
			/* Otherwise resend the DNS query and requeue the resolution */
			if (!res->nb_responses || res->prefered_query_type != res->query_type) {
				/* No response received (a real timeout) or fallback already done */
				res->query_type = res->prefered_query_type;
				res->try--;
			}
			else {
				/* Fallback from A to AAAA or the opposite and re-send
				 * the resolution immediately. try counter is not
				 * decremented. */
				if (res->prefered_query_type == DNS_RTYPE_A)
					res->query_type = DNS_RTYPE_AAAA;
				else if (res->prefered_query_type == DNS_RTYPE_AAAA)
					res->query_type = DNS_RTYPE_A;
				else
					res->try--;
			}
			dns_send_query(res);
		}
	}

	/* Handle all resolutions in the wait list */
	list_for_each_entry_safe(res, resback, &resolvers->resolutions.wait, list) {
		exp = tick_add(res->last_resolution, dns_resolution_timeout(res));
		if (tick_isset(res->last_resolution) && !tick_is_expired(exp, now_ms))
			continue;

		if (dns_run_resolution(res) != 1) {
			res->last_resolution = now_ms;
			LIST_DEL(&res->list);
			LIST_ADDQ(&resolvers->resolutions.wait, &res->list);
		}
	}

	dns_update_resolvers_timeout(resolvers);
	HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock);
	return t;
}

Project: haproxy
File: dns.c

int dns_read_name(unsigned char *buffer, unsigned char *bufend,
		  unsigned char *name, char *destination, int dest_len,
		  int *offset, unsigned int depth)
{
	int nb_bytes = 0, n = 0;
	int label_len;
	unsigned char *reader = name;
	char *dest = destination;

	while (1) {
		if (reader >= bufend)
			goto err;

		/* Name compression is in use */
		if ((*reader & 0xc0) == 0xc0) {
			if (reader + 1 >= bufend)
				goto err;

			/* Must point BEFORE current position */
			if ((buffer + reader[1]) > reader)
				goto err;

			if (depth++ > 100)
				goto err;

			n = dns_read_name(buffer, bufend, buffer + reader[1],
					  dest, dest_len - nb_bytes, offset, depth);
			if (n == 0)
				goto err;

			dest     += n;
			nb_bytes += n;
			goto out;
		}

		label_len = *reader;
		if (label_len == 0)
			goto out;

		/* Check if:
		 *  - we won't read outside the buffer
		 *  - there is enough place in the destination
		 */
		if ((reader + label_len >= bufend) || (nb_bytes + label_len >= dest_len))
			goto err;

		/* +1 to take label len + label string */
		label_len++;

		memcpy(dest, reader, label_len);

		dest     += label_len;
		nb_bytes += label_len;
		reader   += label_len;
	}

  out:
	/* offset computation:
	 * parse from <name> until finding either NULL or a pointer "c0xx"
	 */
	reader  = name;
	*offset = 0;
	while (reader < bufend) {
		if ((reader[0] & 0xc0) == 0xc0) {
			*offset += 2;
			break;
		}
		else if (*reader == 0) {
			*offset += 1;
			break;
		}
		*offset += 1;
		++reader;
	}
	return nb_bytes;

  err:
	return 0;
}

Project: haproxy
File: dns.c

static void dns_resolve_recv(struct dgram_conn *dgram)
{
	struct dns_nameserver *ns, *tmpns;
	struct dns_resolvers  *resolvers;
	struct dns_resolution *res;
	struct dns_query_item *query;
	unsigned char  buf[DNS_MAX_UDP_MESSAGE + 1];
	unsigned char *bufend;
	int fd, buflen, dns_resp;
	int max_answer_records;
	unsigned short query_id;
	struct eb32_node *eb;
	struct dns_requester *req;

	fd = dgram->t.sock.fd;

	/* check if ready for reading */
	if (!fd_recv_ready(fd))
		return;

	/* no need to go further if we can't retrieve the nameserver */
	if ((ns = dgram->owner) == NULL)
		return;

	resolvers = ns->resolvers;
	HA_SPIN_LOCK(DNS_LOCK, &resolvers->lock);

	/* process all pending input messages */
	while (1) {
		/* read message received */
		memset(buf, '\0', resolvers->accepted_payload_size + 1);
		if ((buflen = recv(fd, (char*)buf , resolvers->accepted_payload_size + 1, 0)) < 0) {
			/* FIXME : for now we consider EAGAIN only */
			fd_cant_recv(fd);
			break;
		}

		/* message too big */
		if (buflen > resolvers->accepted_payload_size) {
			ns->counters.too_big++;
			continue;
		}

		/* initializing variables */
		bufend = buf + buflen;	/* pointer to mark the end of the buffer */

		/* read the query id from the packet (16 bits) */
		if (buf + 2 > bufend) {
			ns->counters.invalid++;
			continue;
		}
		query_id = dns_response_get_query_id(buf);

		/* search the query_id in the pending resolution tree */
		eb = eb32_lookup(&resolvers->query_ids, query_id);
		if (eb == NULL) {
			/* unknown query id means an outdated response and can be safely ignored */
			ns->counters.outdated++;
			continue;
		}

		/* known query id means a resolution in prgress */
		res = eb32_entry(eb, struct dns_resolution, qid);
		if (!res) {
			ns->counters.outdated++;
			continue;
		}

		/* number of responses received */
		res->nb_responses++;

		max_answer_records = (resolvers->accepted_payload_size - DNS_HEADER_SIZE) / DNS_MIN_RECORD_SIZE;
		dns_resp = dns_validate_dns_response(buf, bufend, res, max_answer_records);

		switch (dns_resp) {
			case DNS_RESP_VALID:
				break;

			case DNS_RESP_INVALID:
			case DNS_RESP_QUERY_COUNT_ERROR:
			case DNS_RESP_WRONG_NAME:
				res->status = RSLV_STATUS_INVALID;
				ns->counters.invalid++;
				break;

			case DNS_RESP_NX_DOMAIN:
				res->status = RSLV_STATUS_NX;
				ns->counters.nx++;
				break;

			case DNS_RESP_REFUSED:
				res->status = RSLV_STATUS_REFUSED;
				ns->counters.refused++;
				break;

			case DNS_RESP_ANCOUNT_ZERO:
				res->status = RSLV_STATUS_OTHER;
				ns->counters.any_err++;
				break;

			case DNS_RESP_CNAME_ERROR:
				res->status = RSLV_STATUS_OTHER;
				ns->counters.cname_error++;
				break;

			case DNS_RESP_TRUNCATED:
				res->status = RSLV_STATUS_OTHER;
				ns->counters.truncated++;
				break;

			case DNS_RESP_NO_EXPECTED_RECORD:
			case DNS_RESP_ERROR:
			case DNS_RESP_INTERNAL:
				res->status = RSLV_STATUS_OTHER;
				ns->counters.other++;
				break;
		}

		/* Wait all nameservers response to handle errors */
		if (dns_resp != DNS_RESP_VALID && res->nb_responses < resolvers->nb_nameservers)
			continue;

		/* Process error codes */
		if (dns_resp != DNS_RESP_VALID)  {
			if (res->prefered_query_type != res->query_type) {
				/* The fallback on the query type was already performed,
				 * so check the try counter. If it falls to 0, we can
				 * report an error. Else, wait the next attempt. */
				if (!res->try)
					goto report_res_error;
			}
			else {
				/* Fallback from A to AAAA or the opposite and re-send
				 * the resolution immediately. try counter is not
				 * decremented. */
				if (res->prefered_query_type == DNS_RTYPE_A) {
					res->query_type = DNS_RTYPE_AAAA;
					dns_send_query(res);
				}
				else if (res->prefered_query_type == DNS_RTYPE_AAAA) {
					res->query_type = DNS_RTYPE_A;
					dns_send_query(res);
				}
			}
			continue;
		}

		/* Now let's check the query's dname corresponds to the one we
		 * sent. We can check only the first query of the list. We send
		 * one query at a time so we get one query in the response */
		query = LIST_NEXT(&res->response.query_list, struct dns_query_item *, list);
		if (query && memcmp(query->name, res->hostname_dn, res->hostname_dn_len) != 0) {
			dns_resp = DNS_RESP_WRONG_NAME;
			ns->counters.other++;
			goto report_res_error;
		}

		/* So the resolution succeeded */
		res->status     = RSLV_STATUS_VALID;
		res->last_valid = now_ms;
		ns->counters.valid++;
		goto report_res_success;

	report_res_error:
		list_for_each_entry(req, &res->requesters, list)
			req->requester_error_cb(req, dns_resp);
		dns_reset_resolution(res);
		LIST_DEL(&res->list);
		LIST_ADDQ(&resolvers->resolutions.wait, &res->list);
		continue;

	report_res_success:
		/* Only the 1rst requester s managed by the server, others are
		 * from the cache */
		tmpns = ns;
		list_for_each_entry(req, &res->requesters, list) {
			struct server *s = objt_server(req->owner);

			if (s)
				HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
			req->requester_cb(req, tmpns);
			if (s)
				HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
			tmpns = NULL;
		}

		dns_reset_resolution(res);
		LIST_DEL(&res->list);
		LIST_ADDQ(&resolvers->resolutions.wait, &res->list);
		continue;
	}
	dns_update_resolvers_timeout(resolvers);
	HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock);
}

Project: haproxy
File: dns.c

int dns_str_to_dn_label(const char *str, int str_len, char *dn, int dn_len)
{
	int i, offset;

	if (dn_len < str_len + 1)
		return -1;

	/* First byte of dn will be used to store the length of the first
	 * label */
	offset = 0;
	for (i = 0; i < str_len; ++i) {
		if (str[i] == '.') {
			/* 2 or more consecutive dots is invalid */
			if (i == offset)
				return -1;

			dn[offset] = (i - offset);
			offset = i+1;
			continue;
		}
		dn[i+1] = str[i];
	}
	dn[offset] = (i - offset - 1);
	dn[i] = '\0';
	return i;
}

Project: haproxy
File: dns.c

void dns_unlink_resolution(struct dns_requester *requester)
{
	struct dns_resolution *res;
	struct dns_requester  *req;

	/* Nothing to do */
	if (!requester || !requester->resolution)
		return;
	res = requester->resolution;

	/* Clean up the requester */
	LIST_DEL(&requester->list);
	requester->resolution = NULL;

	/* We need to find another requester linked on this resolution */
	if (!LIST_ISEMPTY(&res->requesters))
		req = LIST_NEXT(&res->requesters, struct dns_requester *, list);
	else {
		dns_free_resolution(res);
		return;
	}

	/* Move hostname_dn related pointers to the next requester */
	switch (obj_type(req->owner)) {
		case OBJ_TYPE_SERVER:
			res->hostname_dn     = __objt_server(req->owner)->hostname_dn;
			res->hostname_dn_len = __objt_server(req->owner)->hostname_dn_len;
			break;
		case OBJ_TYPE_SRVRQ:
			res->hostname_dn     = __objt_dns_srvrq(req->owner)->hostname_dn;
			res->hostname_dn_len = __objt_dns_srvrq(req->owner)->hostname_dn_len;
			break;
		default:
			res->hostname_dn     = NULL;
			res->hostname_dn_len = 0;
			break;
	}
}

Project: haproxy
File: dns.c

struct dns_srvrq *new_dns_srvrq(struct server *srv, char *fqdn)
{
	struct proxy     *px    = srv->proxy;
	struct dns_srvrq *srvrq = NULL;
	int fqdn_len, hostname_dn_len;

	fqdn_len = strlen(fqdn);
	hostname_dn_len = dns_str_to_dn_label(fqdn, fqdn_len + 1, trash.area,
					      trash.size);
	if (hostname_dn_len == -1) {
		ha_alert("config : %s '%s', server '%s': failed to parse FQDN '%s'\n",
			 proxy_type_str(px), px->id, srv->id, fqdn);
		goto err;
	}

	if ((srvrq = calloc(1, sizeof(*srvrq))) == NULL) {
		ha_alert("config : %s '%s', server '%s': out of memory\n",
			 proxy_type_str(px), px->id, srv->id);
		goto err;
	}
	srvrq->obj_type        = OBJ_TYPE_SRVRQ;
	srvrq->proxy           = px;
	srvrq->name            = strdup(fqdn);
	srvrq->hostname_dn     = strdup(trash.area);
	srvrq->hostname_dn_len = hostname_dn_len;
	if (!srvrq->name || !srvrq->hostname_dn) {
		ha_alert("config : %s '%s', server '%s': out of memory\n",
			 proxy_type_str(px), px->id, srv->id);
		goto err;
	}
	LIST_ADDQ(&dns_srvrq_list, &srvrq->list);
	return srvrq;

  err:
	if (srvrq) {
		free(srvrq->name);
		free(srvrq->hostname_dn);
		free(srvrq);
	}
	return NULL;
}