From 0450f9e28a3730933fa3c450bf7bc2894184d019 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Sun, 10 May 2026 16:49:59 +0100 Subject: [PATCH] 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 b95d5a25777cc8910d2c2a921c68c778a3b30498 to the encapsulated vendor and user options. --- src/rfc3315.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/rfc3315.c b/src/rfc3315.c index 3c316a1..f0bff01 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -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 */