CVE-2020-6072
An exploitable code execution vulnerability exists in the label-parsing functionality of Videolabs libmicrodns 0.1.0. When parsing compressed labels in mDNS messages, the rr_decode
function’s return value is not checked, leading to a double free that could be exploited to execute arbitrary code. An attacker can send an mDNS message to trigger this vulnerability.
Videolabs libmicrodns 0.1.0
https://github.com/videolabs/libmicrodns
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-252: Unchecked Return Value
The libmicrodns library is an mDNS resolver that aims to be simple and compatible cross-platform.
The function mdns_recv
reads and parses an mDNS message:
static int
mdns_recv(const struct mdns_conn* conn, struct mdns_hdr *hdr, struct rr_entry **entries)
{
uint8_t buf[MDNS_PKT_MAXSZ];
size_t num_entry, n;
ssize_t length;
struct rr_entry *entry;
*entries = NULL;
if ((length = recv(conn->sock, (char *) buf, sizeof(buf), 0)) < 0) // [1]
return (MDNS_NETERR);
const uint8_t *ptr = mdns_read_header(buf, length, hdr); // [2]
n = length;
num_entry = hdr->num_qn + hdr->num_ans_rr + hdr->num_add_rr;
for (size_t i = 0; i < num_entry; ++i) {
entry = calloc(1, sizeof(struct rr_entry));
if (!entry)
goto err;
ptr = rr_read(ptr, &n, buf, entry, i >= hdr->num_qn); // [3]
if (!ptr) {
free(entry);
errno = ENOSPC;
goto err;
}
entry->next = *entries;
*entries = entry;
}
...
}
At [1], a message is read from the network. The 12-bytes mDNS header is then parsed at [2]. Based on the header info, the loop parses each resource record (“RR”) using the function rr_read
[3], which in turn calls rr_read_RR
and then rr_decode
.
#define advance(x) ptr += x; *n -= x
/*
* Decodes a DN compressed format (RFC 1035)
* e.g "\x03foo\x03bar\x00" gives "foo.bar"
*/
static const uint8_t *
rr_decode(const uint8_t *ptr, size_t *n, const uint8_t *root, char **ss)
{
char *s;
s = *ss = malloc(MDNS_DN_MAXSZ);
if (!s)
return (NULL);
if (*ptr == 0) {
*s = '\0';
advance(1);
return (ptr);
}
while (*ptr) { // [4]
size_t free_space;
uint16_t len;
free_space = *ss + MDNS_DN_MAXSZ - s;
len = *ptr; // [8]
advance(1);
/* resolve the offset of the pointer (RFC 1035-4.1.4) */
if ((len & 0xC0) == 0xC0) { // [5]
const uint8_t *p;
char *buf;
size_t m;
if (*n < sizeof(len)) // [9]
goto err;
len &= ~0xC0;
len = (len << 8) | *ptr;
advance(1);
p = root + len;
m = ptr - p + *n; // [6]
rr_decode(p, &m, root, &buf); // [7]
if (free_space <= strlen(buf)) { // [10]
free(buf); // [12]
goto err;
}
(void) strcpy(s, buf);
free(buf); // [13]
return (ptr);
}
if (*n <= len || free_space <= len) // [11]
goto err;
strncpy(s, (const char *) ptr, len);
advance(len);
s += len;
*s++ = (*ptr) ? '.' : '\0';
}
advance(1);
return (ptr);
err:
free(*ss);
return (NULL);
}
The function rr_decode
expects 4 parameters:
ptr
: the pointer to the start of the label to parsen
: the number of remaining bytes in the message, starting from ptrroot
: the pointer to the start of the mDNS messagess
: buffer used to build the domain nameAt [4] the function loops for each character in the label and, if a pointer is found [5], the pointed label location and its maximum size is computed at [6], and the rr_decode
function is called recursively [7].
From this point, the function rr_decode
could reach the err
label in 3 different ways:
*n < sizeof(len)
[9], that is having *n == 0 || *n == 1
.*n < len
(i.e. the label size is bigger than the remaining space in the message) or free_space <= len
(i.e. the label size is bigger or equal to the remaining space in the *ss
buffer) [11].When any of those 3 cases are triggered, the code jumps to the err
label, which frees the *ss
buffer previously allocated, and returns NULL
.
However, when the function returns at [7], the NULL
value returned is not checked, possibly leading to a double-free of the buf
(*ss
) buffer at [12] or [13], which could later be exploited by an attacker to execute arbitrary code.
2020-01-30 - Vendor Disclosure
2020-03-20 - Vendor Patched
2020-03-23 - Public Release
Discovered by Claudio Bozzato of Cisco Talos.