Fix OOB read when parsing DHCPv6 userclass or vendorclass options.

Credit to Haiyang Huang for finding this.

The fix extends the improved framework for DHCPv6 options added in
b95d5a2577
to the encapsulated vendor and user options.
This commit is contained in:
Simon Kelley committed 2026-05-10 16:49:59 +01:00
1 parent 0637bcf010
commit 0450f9e28a
1 file changed
+27 -7
+27 -7
View File
@@ -41,6 +41,7 @@ static void log6_packet(struct state *state, char *type, struct in6_addr *addr,
static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
static void *opt6_find (uint8_t *opts, uint8_t *end, unsigned int search, int minsize);
static void *opt6_first(uint8_t *opt, uint8_t *end);
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end);
static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
static void get_context_tag(struct state *state, struct dhcp_context *context);
static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
@@ -65,9 +66,9 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
#define opt6_next(opt, end) (opt6_first(opt6_ptr((opt), opt6_len((opt))), (end)))
#define opt6_user_vendor_ptr(opt, i) ((void *)&(((uint8_t *)(opt))[2+(i)]))
#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
#define opt6_user_vendor_next(opt, end) (opt6_next(((uint8_t *) opt) - 2, end))
#define opt6_user_vendor_len(opt) ((int)(opt6_uint((opt), -4, 2)))
#define opt6_user_vendor_next(opt, end) (opt6_user_vendor_first(opt6_user_vendor_ptr((opt), opt6_user_vendor_len((opt))), (end)))
unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int interface, char *iface_name,
struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
@@ -414,7 +415,8 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
}
/* Note that format if user/vendor classes is different to DHCP options - no option types. */
for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, offset), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
{
@@ -1927,7 +1929,8 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
if (opt6_len(opt) >= 6)
for (enc_opt = opt6_ptr(opt, 4); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 4), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
{
lease->vendorclass_count++;
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
@@ -1987,7 +1990,8 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
if ((opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
{
void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
for (enc_opt = opt6_ptr(opt, 0); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
for (enc_opt = opt6_user_vendor_first(opt6_ptr(opt, 0), enc_end);
enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
lease_add_extradata(lease, opt6_user_vendor_ptr(enc_opt, 0), opt6_user_vendor_len(enc_opt), 0);
}
}
@@ -2130,7 +2134,23 @@ static void *opt6_first(uint8_t *opt, uint8_t *end)
return opt;
}
static void *opt6_user_vendor_first(uint8_t *opt, uint8_t *end)
{
if (!opt)
return NULL;
/* make sure we have length. */
if ((uint8_t *)opt6_user_vendor_ptr(opt, 0) > end)
return NULL;
/* make sure we have bytes promised by length. */
if ((uint8_t *)opt6_user_vendor_ptr(opt, opt6_user_vendor_len(opt)) > end)
return NULL;
return opt;
}
static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
{
/* this worries about unaligned data and byte order */