Compare commits

...
3 Commits
Author SHA1 Message Date
Simon Kelley 03fd0c19ef Clear down SLAAC-name data when a DHCP lease loses its hostname.
If a DHCPv4 lease loses its hostname, then the DHCPv6 name-address mapping
derived from it become invalid and need to be cleared.

Similarly, when a hostname is added to an existing lease, new
SLAAC-name data needs to be generated.

Thanks Martin Vlach (and Claude) for finding a symptom of this in
the SLAAC logging code. This patch fixes that and does the correct
thing more generally.
2026-10-01 20:43:10 +01:00
Martin VlachandClaude Sonnet 5.5 baeb7430e2 Don't bump arrsize in poll_listen() until the realloc has succeeded.
If whine_realloc() fails in poll_listen(), arrsize has already been
doubled while pollfds keeps its old size. The next call then sees
arrsize != nfds, skips the extension and writes past the end of the
array (or through NULL if the very first allocation failed).
poll_reset() only clears nfds, so this sticks around until restart.

Only update arrsize once the reallocation has succeeded.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 15:40:31 +01:00
朱耀辉 f90df6c532 Fix heap OOB read in zone_status()
Credit to Yaohui Zhu (朱耀辉) for finding this.

CNVD-C-2026-590208 applies.

Summary

zone_status() (src/dnssec.c:1867 in the 2.93 release; the same
construct is in current git master, 2.93-29-gad92083) computes
&name[name_start-2] in its backtrack loop without checking that
name_start >= 2. When name_start == 1 the loop starts at name[-1]
and dereferences it immediately; the guard (p != name) can never
fire for p < name, so the scan walks backwards through the heap
until it finds a '.' byte or hits an unmapped page. Under ASAN this
is a deterministic heap-buffer-overflow READ; on a plain build the
process dies with SIGSEGV.

A single, ordinary query plus one crafted upstream reply is enough.
My reproducer starts the ASAN binary with --dnssec and the root
trust anchor, with a mock upstream; on the first query the mock
answers with a captured crafted reply (a response carrying RRSIG(46),
NSEC(47) with an empty/root owner name, and NSEC3(50) records in the
authority section) and the process crashes on the first query.
2026-10-01 15:34:53 +01:00
5 changed files with 87 additions and 81 deletions

No files matched your search

+4
View File
@@ -8,6 +8,10 @@ version 2.94
constructed DHCPv6/RA - DHCPv6 and RA are inhibited by
--no-dhcpv4-interface when using constructed address ranges.
Thanks to David Nichols for discovery, diagnosis and patch.
Fix heap OOB read in zone_status. Thanks to Yaohui Zhu (朱耀辉)
for spotting and reporting this. A crafted reply can crash dnsmasq.
CNVD-C-2026-590208 applies.
version 2.93
+1 -1
View File
@@ -1934,7 +1934,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
return STAT_INSECURE;
}
if (name_start == 0)
if (name_start <= 1)
break;
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
+12 -10
View File
@@ -1027,6 +1027,13 @@ static void kill_name(struct dhcp_lease *lease)
lease->old_hostname = lease->hostname;
lease->hostname = lease->fqdn = NULL;
#ifdef HAVE_DHCP6
/* A lease which had a hostname when slaac_add_addrs() was called on it
may have slaac-address records. Since it no longer has a hostname,
they are no-longer valid, so update to remove them */
slaac_add_addrs(lease, 0, 0);
#endif
}
void lease_calc_fqdns(void)
@@ -1151,6 +1158,10 @@ void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, ch
file_dirty = 1;
dns_dirty = 1;
lease->flags |= LEASE_CHANGED; /* run script on change */
#ifdef HAVE_DHCP6
slaac_add_addrs(lease, dnsmasq_time(), 0);
#endif
}
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
@@ -1250,15 +1261,6 @@ int do_script_run(time_t now)
}
else
{
#ifdef HAVE_DHCP6
struct slaac_address *slaac, *tmp;
for (slaac = lease->slaac_address; slaac; slaac = tmp)
{
tmp = slaac->next;
free(slaac);
}
#endif
kill_name(lease);
#ifdef HAVE_SCRIPT
queue_script(ACTION_DEL, lease, lease->old_hostname, now);
#endif
@@ -1267,7 +1269,7 @@ int do_script_run(time_t now)
#endif
old_leases = lease->next;
free(lease->hostname);
kill_name(lease); /* frees slaac_addr, hostname and fqdn */
free(lease->clid);
free(lease->extradata);
free(lease->agent_id);
+3 -3
View File
@@ -100,13 +100,13 @@ void poll_listen(int fd, short event)
{
/* Array too small. Extend. */
struct pollfd *new;
nfds_t newsize = (arrsize == 0) ? 64 : arrsize * 2;
arrsize = (arrsize == 0) ? 64 : arrsize * 2;
if (!(new = whine_realloc(pollfds, arrsize * sizeof(struct pollfd))))
if (!(new = whine_realloc(pollfds, newsize * sizeof(struct pollfd))))
return;
pollfds = new;
arrsize = newsize;
}
memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+67 -67
View File
@@ -27,87 +27,87 @@ void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
struct slaac_address *slaac, *old, **up;
struct dhcp_context *context;
int dns_dirty = 0;
if (!(lease->flags & LEASE_HAVE_HWADDR) ||
(lease->flags & (LEASE_TA | LEASE_NA)) ||
lease->last_interface == 0 ||
!lease->hostname)
return ;
old = lease->slaac_address;
lease->slaac_address = NULL;
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
if ((lease->flags & LEASE_HAVE_HWADDR) &&
!(lease->flags & (LEASE_TA | LEASE_NA)) &&
lease->last_interface != 0 &&
lease->hostname)
{
for (context = daemon->dhcp6; context; context = context->next)
if ((context->flags & CONTEXT_RA_NAME) &&
!(context->flags & CONTEXT_OLD) &&
lease->last_interface == context->if_index)
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
struct in6_addr addr = context->start6;
if (lease->hwaddr_len == 6 &&
(lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
{
/* convert MAC address to EUI-64 */
memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
addr.s6_addr[11] = 0xff;
addr.s6_addr[12] = 0xfe;
}
#if defined(ARPHRD_EUI64)
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
else if (lease->hwaddr_len == 8 &&
lease->hwaddr_type == ARPHRD_EUI64)
memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
#endif
#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
else if (lease->clid_len == 9 &&
lease->clid[0] == ARPHRD_EUI64 &&
lease->hwaddr_type == ARPHRD_IEEE1394)
/* FireWire has EUI-64 identifier as clid */
memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
#endif
else
continue;
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
else
continue;
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
addr.s6_addr[8] ^= 0x02;
/* check if we already have this one */
for (up = &old, slaac = old; slaac; slaac = slaac->next)
{
if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
{
*up = slaac->next;
/* recheck when DHCPv4 goes through init-reboot */
if (force)
{
slaac->ping_time = now;
slaac->backoff = 1;
dns_dirty = 1;
}
break;
}
up = &slaac->next;
}
/* No, make new one */
if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
{
slaac->ping_time = now;
slaac->backoff = 1;
slaac->addr = addr;
/* Do RA's to prod it */
ra_start_unsolicited(now, context);
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
if (slaac)
{
slaac->next = lease->slaac_address;
lease->slaac_address = slaac;
}
}
}
if (old || dns_dirty)
lease_update_dns(1);
/* Free any no reused */
/* Free any not reused */
for (; old; old = slaac)
{
slaac = old->next;