dhcp-common: fix OOB reads in DHCPv6 option_string() decoders

Two decoders in option_string() read beyond the end of the supplied
option-value buffer when a malformed DHCPv6 option is received:

1. OT_RFC1035_NAME: after advancing i past the last label (i = l),
   val[i] was read at line 885 to decide whether to append a '.'.
   If the label ends exactly at opt_len, i == opt_len and val[i]
   is one byte past the option data.  Fixed by guarding with
   i < opt_len before the read.

2. OT_CSTRING: the loop opened with while(1) and called GETSHORT
   (a 2-byte read) before checking that two bytes remained, so a
   1- or 0-byte option value caused an immediate 1-byte OOB read.
   Additionally, if the length field exceeded the remaining option
   data the inner body-copy loop read past the end.  Fixed by
   changing the loop condition to while(i+2 <= opt_len) and adding
   a check that the body fits before copying.

Both reads land inside the received packet buffer (no crash), but
they allow a DHCPv6 client to cause dnsmasq to read unrelated packet
bytes and emit them in syslog output.

Security: OOB reads of up to option-length bytes from a
network-supplied DHCPv6 packet; reachable only with --log-dhcp
enabled; no memory corruption, crash, or sensitive data disclosure
beyond adjacent packet bytes.

Signed-off-by: Dominik <dl6er@dl6er.de>
This commit is contained in:
Dominik authored and Simon Kelley committed 2026-03-30 18:09:43 +01:00
1 parent 8ceb8ee9b5
commit ca509cb611
1 file changed
+6 -7
+6 -7
View File
@@ -892,24 +892,23 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
unsigned char *p;
i = 0, j = 0;
while (1)
while (i + 2 <= opt_len)
{
p = &val[i];
GETSHORT(len, p);
if (i + 2 + len > opt_len)
break; /* malformed: body extends beyond option */
for (k = 0; k < len && j < buf_len; k++)
{
char c = *p++;
if (isprint((unsigned char)c))
buf[j++] = c;
}
i += len +2;
if (i >= opt_len)
break;
if (j < buf_len)
i += len + 2;
if (i < opt_len && j < buf_len)
buf[j++] = ',';
}
}
}
#endif
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{