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.
This commit is contained in:
Simon Kelley committed 2026-04-10 13:35:27 +01:00
1 parent cfc2244c2a
commit 2d0e0c7a54
1 file changed
+24 -22
+24 -22
View File
@@ -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)))
{