From 2d0e0c7a54f73d10d7afa15691c08cf5ec1e4ee2 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Fri, 10 Apr 2026 12:40:29 +0100 Subject: [PATCH] Tweak DHCPv6 replay decapsulation. RFC 8415 doesn't exclude the possiblity that a relay-forward message could contain _more_than_one relay-message option. The existing code could, under certain circumstances, access freed stack memory in this case. Thanks to Dejan Alvadzijevic and his fuzzer for finding this condition. This patch fixes things so that dnsmasq does at least sensible things with multiple relay-message options, and no longer has the (remote) possibility of accessing dead memory. --- src/rfc3315.c | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/rfc3315.c b/src/rfc3315.c index c281764..3c316a1 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -24,7 +24,7 @@ struct state { int clid_len, ia_type, interface, hostname_auth, lease_allocate; char *client_hostname, *hostname, *domain, *send_domain; struct dhcp_context *context; - struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr; + struct in6_addr *relay_address, *fallback, *ll_addr, *ula_addr; unsigned int xid, fqdn_flags, iaid; char *iface_name; void *packet_options, *end; @@ -34,7 +34,7 @@ struct state { }; static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz, - struct in6_addr *client_addr, time_t now); + struct in6_addr *client_addr, struct in6_addr *link_address, time_t now); static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbuff, size_t sz, time_t now); static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts); static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string); @@ -99,9 +99,8 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int state.ula_addr = ula_addr; state.mac_len = 0; state.tags = NULL; - state.link_address = NULL; - - if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, now)) + + if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, NULL, now)) return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT; return 0; @@ -109,7 +108,7 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int multicast_dest, int /* This cost me blood to write, it will probably cost you blood to understand - srk. */ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t sz, - struct in6_addr *client_addr, time_t now) + struct in6_addr *client_addr, struct in6_addr *link_address, time_t now) { uint8_t *end = inbuff + sz; uint8_t *opts = inbuff + 34; @@ -136,7 +135,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t link_address == NULL means there's no relay in use, so we try and find the client's MAC address from the local ND cache. */ - if (!state->link_address) + if (!link_address) get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now); else { @@ -144,9 +143,9 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t struct shared_network *share = NULL; state->context = NULL; - if (!IN6_IS_ADDR_LOOPBACK(state->link_address) && - !IN6_IS_ADDR_LINKLOCAL(state->link_address) && - !IN6_IS_ADDR_MULTICAST(state->link_address)) + if (!IN6_IS_ADDR_LOOPBACK(link_address) && + !IN6_IS_ADDR_LINKLOCAL(link_address) && + !IN6_IS_ADDR_MULTICAST(link_address)) for (c = daemon->dhcp6; c; c = c->next) { for (share = daemon->shared_networks; share; share = share->next) @@ -155,7 +154,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t continue; if (share->if_index != 0 || - !IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6)) + !IN6_ARE_ADDR_EQUAL(link_address, &share->match_addr6)) continue; if ((c->flags & CONTEXT_DHCP) && @@ -168,8 +167,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t if (share || ((c->flags & CONTEXT_DHCP) && !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && - is_same_net6(state->link_address, &c->start6, c->prefix) && - is_same_net6(state->link_address, &c->end6, c->prefix))) + is_same_net6(link_address, &c->start6, c->prefix) && + is_same_net6(link_address, &c->end6, c->prefix))) { c->preferred = c->valid = 0xffffffff; c->current = state->context; @@ -179,7 +178,7 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t if (!state->context) { - inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(AF_INET6, link_address, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCPv6 request from relay at %s"), daemon->addrbuff); @@ -194,6 +193,8 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t return 0; } + state->relay_address = link_address; + return dhcp6_no_relay(state, msg_type, inbuff, sz, now); } @@ -253,11 +254,12 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t /* the packet data is unaligned, copy to aligned storage */ memcpy(&align, inbuff + 2, IN6ADDRSZ); - - /* RFC6221 para 4 */ - if (!IN6_IS_ADDR_UNSPECIFIED(&align)) - state->link_address = &align; - if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, now)) + /* RFC6221 para 4 says if link_address in encapulation + is zero, ignore it, and, by implication, use the link + address of any enclosing encapsulation or, failing that + of the arrival interface on the the server. */ + if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, + IN6_IS_ADDR_UNSPECIFIED(&align) ? link_address : &align, now)) return 0; } else @@ -1977,10 +1979,10 @@ static void update_leases(struct state *state, struct dhcp_context *context, str } } - if (state->link_address) - inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); + if (state->relay_address) + inet_ntop(AF_INET6, state->relay_address, daemon->addrbuff, ADDRSTRLEN); - lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0); + lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->relay_address ? strlen(daemon->addrbuff) : 0, 0); if ((opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2))) {