CVE-2023-31470: Out-of-bounds Write (CWE-787)
value: SmartDNS through 41 before 56d0332 allows an out-of-bounds write because of a stack-based buffer overflow in the _dns_encode_domain function in the dns.c file, via a crafted DNS request.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 9.8
Published: 2023-04-28T21:15Z
Repository: https://github.com/pymumu/smartdns
Language: C
Files changed: src/dns.c, src/dns.h, src/util.c
Commit date: 2023-04-22 19:06:44 +0800
Commit message: dns: fix crash issue

Diff: diff --git a/src/dns.c b/src/dns.c
index c2b0700..42a3b20 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -272,10 +272,14 @@ static int _dns_encode_domain(struct dns_context *context, const char *domain)
 	/*[len]string[len]string...[0]0 */
 	while (_dns_left_len(context) > 1 && *domain != 0) {
 		total_len++;
 		if (dict_offset >= 0) {
 			int offset = 0xc000 | dict_offset;
+			if (_dns_left_len(context) < 2) {
+				return -1;
+			}
+
 			_dns_write_short(&ptr_num, offset);
 			context->ptr++;
 			ptr_num = NULL;
 			return total_len;
 		}
@@ -293,10 +297,14 @@ static int _dns_encode_domain(struct dns_context *context, const char *domain)
 		num++;
 		context->ptr++;
 		domain++;
 	}
 
+	if (_dns_left_len(context) < 1) {
+		return -1;
+	}
+
 	*ptr_num = num;
 
 	if (total_len > 1) {
 		/* if domain is '\0', [domain] is '\0' */
 		*(context->ptr) = 0;
@@ -573,11 +581,11 @@ struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_bu
 	rr_nested_buffer->rr_head_len = len;
 
 	return rr_nested_buffer;
 }
 
-int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len)
+int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len)
 {
 	if (rr_nested == NULL || data == NULL || data_len <= 0) {
 		return -1;
 	}
 
@@ -601,10 +609,16 @@ int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype)
 	unsigned char *ptr = rr_nested->rr_len_ptr;
 	if (ptr == NULL || _dns_left_len(&rr_nested->context) < 2) {
 		return -1;
 	}
 
+	/* NO SVC keys, reset ptr */
+	if (len <= 14) {
+		rr_nested->context.ptr = rr_nested->rr_start;
+		return 0;
+	}
+
 	_dns_write_short(&ptr, len - rr_nested->rr_head_len);
 
 	return _dns_rr_add_end(rr_nested->context.packet, rr_nested->type, rtype, len);
 }
 
@@ -1074,11 +1088,16 @@ int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet
 	svcparam_buffer = dns_add_rr_nested_start(svcparam_buffer, packet, type, DNS_T_HTTPS, domain, ttl);
 	if (svcparam_buffer == NULL) {
 		return -1;
 	}
 
-	int target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;
+	int target_len = 0;
+	if (target == NULL) {
+		target = "";
+	}
+
+	target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;
 	if (_dns_left_len(&svcparam_buffer->context) < 2 + target_len) {
 		return -1;
 	}
 
 	/* add rr data */
@@ -1115,10 +1134,26 @@ int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port)
 	dns_add_rr_nested_memcpy(svcparam, &value, 2);
 
 	return 0;
 }
 
+int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len)
+{
+	if (_dns_left_len(&svcparam->context) < 2 + 2 + alpn_len) {
+		return -1;
+	}
+
+	unsigned short value = DNS_HTTPS_T_ALPN;
+	dns_add_rr_nested_memcpy(svcparam, &value, 2);
+
+	value = alpn_len;
+	dns_add_rr_nested_memcpy(svcparam, &value, 2);
+	dns_add_rr_nested_memcpy(svcparam, alpn, alpn_len);
+
+	return 0;
+}
+
 int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
 {
 	if (_dns_left_len(&svcparam->context) < 2 + 2 + ech_len) {
 		return -1;
 	}
@@ -1131,11 +1166,11 @@ int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len)
 	dns_add_rr_nested_memcpy(svcparam, ech, ech_len);
 
 	return 0;
 }
 
-int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], int addr_num)
+int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
 {
 	if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_A_LEN) {
 		return -1;
 	}
 
@@ -1149,11 +1184,12 @@ int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][
 		dns_add_rr_nested_memcpy(svcparam, addr[i], DNS_RR_A_LEN);
 	}
 
 	return 0;
 }
-int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], int addr_num)
+
+int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
 {
 	if (_dns_left_len(&svcparam->context) < 4 + addr_num * DNS_RR_AAAA_LEN) {
 		return -1;
 	}
 
@@ -1173,45 +1209,52 @@ int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][
 int dns_add_HTTPS_end(struct dns_rr_nested *svcparam)
 {
 	return dns_add_rr_nested_end(svcparam, DNS_T_HTTPS);
 }
 
-struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl,
-													int *priority, char *target, int target_size)
+int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
+								int *ttl, int *priority, char *target, int target_size)
 {
 	int qtype = 0;
 	unsigned char *data = NULL;
 	int rr_len = 0;
 
 	data = dns_get_rr_nested_start(rrs, domain, maxsize, &qtype, ttl, &rr_len);
 	if (data == NULL) {
-		return NULL;
+		return -1;
 	}
 
 	if (qtype != DNS_T_HTTPS) {
-		return NULL;
+		return -1;
 	}
 
 	if (rr_len < 2) {
-		return NULL;
+		return -1;
 	}
 
 	*priority = _dns_read_short(&data);
 	rr_len -= 2;
 	if (rr_len <= 0) {
-		return NULL;
+		return -1;
 	}
 
 	int len = strnlen((char *)data, rr_len);
 	safe_strncpy(target, (char *)data, target_size);
 	data += len + 1;
 	rr_len -= len + 1;
-	if (rr_len <= 0) {
-		return NULL;
+	if (rr_len < 0) {
+		return -1;
+	}
+
+	if (rr_len == 0) {
+		*https_param = NULL;
+		return 0;
 	}
 
-	return (struct dns_https_param *)data;
+	*https_param = (struct dns_https_param *)data;
+
+	return 0;
 }
 
 struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *param)
 {
 	return dns_get_rr_nested_next(rrs, param, sizeof(struct dns_https_param) + param->len);
@@ -1923,16 +1966,20 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 	unsigned char *rr_start = NULL;
 	int ttl = 0;
 	int priority = 0;
 	struct dns_https_param *param = NULL;
 
-	param = dns_get_HTTPS_svcparm_start(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
-	if (param == NULL) {
-		tlog(TLOG_ERROR, "get https param failed.");
-		return -1;
+	ret =
+		dns_get_HTTPS_svcparm_start(rrs, &param, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
+	if (ret < 0) {
+		tlog(TLOG_DEBUG, "get https param failed.");
+		return 0;
 	}
 
+	qtype = DNS_T_HTTPS;
+	qclass = DNS_C_IN;
+
 	ret = _dns_encode_rr_head(context, domain, qtype, qclass, ttl, 0, &rr_len_ptr);
 	if (ret < 0) {
 		return -1;
 	}
 
@@ -1952,10 +1999,14 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 	for (; param != NULL; param = dns_get_HTTPS_svcparm_next(rrs, param)) {
 		if (context->ptr - start > rrs->len || _dns_left_len(context) <= 0) {
 			return -1;
 		}
 
+		if (param->len + 4 > _dns_left_len(context)) {
+			return -1;
+		}
+
 		_dns_write_short(&context->ptr, param->key);
 		_dns_write_short(&context->ptr, param->len);
 		switch (param->key) {
 		case DNS_HTTPS_T_MANDATORY:
 		case DNS_HTTPS_T_NO_DEFAULT_ALPN:
@@ -1972,10 +2023,14 @@ static int _dns_encode_HTTPS(struct dns_context *context, struct dns_rrs *rrs)
 			context->ptr -= 4;
 			break;
 		}
 	}
 
+	if (_dns_left_len(context) < 2) {
+		return -1;
+	}
+
 	_dns_write_short(&rr_len_ptr, context->ptr - rr_start);
 
 	return 0;
 }
 
diff --git a/src/dns.h b/src/dns.h
index ad24a13..8bd9ee6 100644
--- a/src/dns.h
+++ b/src/dns.h
@@ -28,10 +28,12 @@ extern "C" {
 #define DNS_MAX_CNAME_LEN 256
 #define DNS_MAX_OPT_LEN 256
 #define DNS_IN_PACKSIZE (512 * 8)
 #define DNS_PACKSIZE (512 * 12)
 #define DNS_DEFAULT_PACKET_SIZE 512
+#define DNS_MAX_ALPN_LEN 32
+#define DNS_MAX_ECH_LEN 256
 
 #define DNS_ADDR_FAMILY_IP 1
 #define DNS_ADDR_FAMILY_IPV6 2
 
 /*
@@ -234,11 +236,11 @@ struct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs)
 struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count);
 
 struct dns_rr_nested *dns_add_rr_nested_start(struct dns_rr_nested *rr_nested_buffer, struct dns_packet *packet,
 											  dns_rr_type type, dns_type_t rtype, const char *domain, int ttl);
 int dns_add_rr_nested_end(struct dns_rr_nested *rr_nested, dns_type_t rtype);
-int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len);
+int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len);
 
 void *dns_get_rr_nested_start(struct dns_rrs *rrs, char *domain, int maxsize, int *qtype, int *ttl, int *rr_len);
 void *dns_get_rr_nested_next(struct dns_rrs *rrs, void *rr_nested, int rr_nested_len);
 
 /*
@@ -278,23 +280,30 @@ int dns_get_OPT_ECS(struct dns_rrs *rrs, unsigned short *opt_code, unsigned shor
 
 int dns_add_OPT_TCP_KEEPALIVE(struct dns_packet *packet, unsigned short timeout);
 int dns_get_OPT_TCP_KEEPALIVE(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len,
 							  unsigned short *timeout);
 
+/* the key must be added in orders, or dig will report FORMERR */
 int dns_add_HTTPS_start(struct dns_rr_nested *svcparam_buffer, struct dns_packet *packet, dns_rr_type type,
 						const char *domain, int ttl, int priority, const char *target);
 int dns_HTTPS_add_raw(struct dns_rr_nested *svcparam, unsigned short key, unsigned char *value, unsigned short len);
-int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port);
-int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn);
+/* key 1, alph */
+int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len);
+/* key 2, no default alph */
 int dns_HTTPS_add_no_default_alpn(struct dns_rr_nested *svcparam);
-int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], int addr_num);
-int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], int addr_num);
+/* key 3, port */
+int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port);
+/* key 4, ipv4 */
+int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
+/* key 5, ech */
 int dns_HTTPS_add_ech(struct dns_rr_nested *svcparam, void *ech, int ech_len);
+/* key 6, ipv6*/
+int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
 int dns_add_HTTPS_end(struct dns_rr_nested *svcparam);
 
-struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl,
-													int *priority, char *target, int target_size);
+int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
+								int *ttl, int *priority, char *target, int target_size);
 struct dns_https_param *dns_get_HTTPS_svcparm_next(struct dns_rrs *rrs, struct dns_https_param *parm);
 
 /*
  * Packet operation
  */
diff --git a/src/util.c b/src/util.c
index d7546e8..77c5ce0 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1599,14 +1599,15 @@ static int _dns_debug_display(struct dns_packet *packet)
 			case DNS_T_HTTPS: {
 				char name[DNS_MAX_CNAME_LEN] = {0};
 				char target[DNS_MAX_CNAME_LEN] = {0};
 				struct dns_https_param *p = NULL;
 				int priority = 0;
+				int ret = 0;
 
-				p = dns_get_HTTPS_svcparm_start(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
+				ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
 												DNS_MAX_CNAME_LEN);
-				if (p == NULL) {
+				if (ret != 0) {
 					printf("get HTTPS svcparm failed\n");
 					break;
 				}
 
 				printf("domain: %s HTTPS: %s TTL: %d priority: %d\n", name, target, ttl, priority);
@@ -1615,11 +1616,27 @@ static int _dns_debug_display(struct dns_packet *packet)
 					switch (p->key) {
 					case DNS_HTTPS_T_MANDATORY: {
 						printf("  HTTPS: mandatory: %s\n", p->value);
 					} break;
 					case DNS_HTTPS_T_ALPN: {
-						printf("  HTTPS: alpn: %s\n", p->value);
+						char alph[64] = {0};
+						int total_alph_len = 0;
+						char *ptr = (char *)p->value;
+						do {
+							int alphlen = *ptr;
+							memcpy(alph + total_alph_len, ptr + 1, alphlen);
+							total_alph_len += alphlen;
+							ptr += alphlen + 1;
+							alph[total_alph_len] = ',';
+							total_alph_len++;
+							alph[total_alph_len] = ' ';
+							total_alph_len++;
+						} while (ptr - (char *)p->value < p->len);
+						if (total_alph_len > 2) {
+							alph[total_alph_len - 2] = '\0';
+						}
+						printf("  HTTPS: alpn: %s\n", alph);
 					} break;
 					case DNS_HTTPS_T_NO_DEFAULT_ALPN: {
 						printf("  HTTPS: no_default_alpn: %s\n", p->value);
 					} break;
 					case DNS_HTTPS_T_PORT: {

Vulnerable code: int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len)
	int target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;
int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], int addr_num)
int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], int addr_num)
struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl,
													int *priority, char *target, int target_size)
		return NULL;
		return NULL;
		return NULL;
		return NULL;
	if (rr_len <= 0) {
		return NULL;
	return (struct dns_https_param *)data;
	param = dns_get_HTTPS_svcparm_start(rrs, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
	if (param == NULL) {
		tlog(TLOG_ERROR, "get https param failed.");
		return -1;
int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, void *data, int data_len);
int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port);
int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn);
int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_A_LEN], int addr_num);
int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char addr[][DNS_RR_AAAA_LEN], int addr_num);
struct dns_https_param *dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl,
													int *priority, char *target, int target_size);
				p = dns_get_HTTPS_svcparm_start(rrs, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
				if (p == NULL) {
						printf("  HTTPS: alpn: %s\n", p->value);
Fixed code: 			if (_dns_left_len(context) < 2) {
				return -1;
			}

	if (_dns_left_len(context) < 1) {
		return -1;
	}

int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len)
	/* NO SVC keys, reset ptr */
	if (len <= 14) {
		rr_nested->context.ptr = rr_nested->rr_start;
		return 0;
	}

	int target_len = 0;
	if (target == NULL) {
		target = "";
	}

	target_len = strnlen(target, DNS_MAX_CNAME_LEN) + 1;
int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len)
{
	if (_dns_left_len(&svcparam->context) < 2 + 2 + alpn_len) {
		return -1;
	}

	unsigned short value = DNS_HTTPS_T_ALPN;
	dns_add_rr_nested_memcpy(svcparam, &value, 2);

	value = alpn_len;
	dns_add_rr_nested_memcpy(svcparam, &value, 2);
	dns_add_rr_nested_memcpy(svcparam, alpn, alpn_len);

	return 0;
}

int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)

int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num)
int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
								int *ttl, int *priority, char *target, int target_size)
		return -1;
		return -1;
		return -1;
		return -1;
	if (rr_len < 0) {
		return -1;
	}

	if (rr_len == 0) {
		*https_param = NULL;
		return 0;
	*https_param = (struct dns_https_param *)data;

	return 0;
	ret =
		dns_get_HTTPS_svcparm_start(rrs, &param, domain, DNS_MAX_CNAME_LEN, &ttl, &priority, target, DNS_MAX_CNAME_LEN);
	if (ret < 0) {
		tlog(TLOG_DEBUG, "get https param failed.");
		return 0;
	qtype = DNS_T_HTTPS;
	qclass = DNS_C_IN;

		if (param->len + 4 > _dns_left_len(context)) {
			return -1;
		}

	if (_dns_left_len(context) < 2) {
		return -1;
	}

#define DNS_MAX_ALPN_LEN 32
#define DNS_MAX_ECH_LEN 256
int dns_add_rr_nested_memcpy(struct dns_rr_nested *rr_nested, const void *data, int data_len);
/* the key must be added in orders, or dig will report FORMERR */
/* key 1, alph */
int dns_HTTPS_add_alpn(struct dns_rr_nested *svcparam, const char *alpn, int alpn_len);
/* key 2, no default alph */
/* key 3, port */
int dns_HTTPS_add_port(struct dns_rr_nested *svcparam, unsigned short port);
/* key 4, ipv4 */
int dns_HTTPS_add_ipv4hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
/* key 5, ech */
/* key 6, ipv6*/
int dns_HTTPS_add_ipv6hint(struct dns_rr_nested *svcparam, unsigned char *addr[], int addr_num);
int dns_get_HTTPS_svcparm_start(struct dns_rrs *rrs, struct dns_https_param **https_param, char *domain, int maxsize,
								int *ttl, int *priority, char *target, int target_size);
				int ret = 0;
				ret = dns_get_HTTPS_svcparm_start(rrs, &p, name, DNS_MAX_CNAME_LEN, &ttl, &priority, target,
				if (ret != 0) {
						char alph[64] = {0};
						int total_alph_len = 0;
						char *ptr = (char *)p->value;
						do {
							int alphlen = *ptr;
							memcpy(alph + total_alph_len, ptr + 1, alphlen);
							total_alph_len += alphlen;
							ptr += alphlen + 1;
							alph[total_alph_len] = ',';
							total_alph_len++;
							alph[total_alph_len] = ' ';
							total_alph_len++;
						} while (ptr - (char *)p->value < p->len);
						if (total_alph_len > 2) {
							alph[total_alph_len - 2] = '\0';
						}
						printf("  HTTPS: alpn: %s\n", alph);