[Dnsmasq-discuss] [PATCH] bpf.c: fix memory leak in arp_enumerate() on BSD
Sagie Duchovne-Nave
sagie.duchovne at gmail.com
Sun Apr 26 16:18:06 UTC 2026
arp_enumerate() allocates a heap buffer via expand_buf() to hold the
kernel ARP table dump retrieved through sysctl(NET_RT_FLAGS). This
buffer is never freed on any return path -- neither the early error
returns nor the normal return after iteration -- causing a leak on
every call.
The leak is most acute in the DHCPv6 path. get_client_mac() calls
find_mac() up to five times per packet with lazy=0. Because the
'updated' flag is local to each find_mac() invocation, a cached
ARP_EMPTY entry for an unresolvable IPv6 address does not short-
circuit the kernel lookup: each call falls through to iface_enumerate()
-> arp_enumerate(), leaking one buffer per call. This yields up to
five leaked allocations per DHCPv6 SOLICIT packet. The leak size per
call equals the full system-wide IPv4 ARP table dump across all
interfaces.
The condition is readily triggered by a DHCPv6 client whose MAC
address cannot be resolved via NDP -- which is the common case on
FreeBSD, because arp_enumerate() queries NET_RT_FLAGS/RTF_LLINFO,
which returns IPv4 ARP entries only; IPv6 NDP neighbour entries are
not included. As a result every IPv6 MAC lookup fails unconditionally
on FreeBSD, every failed lookup produces an ARP_EMPTY record that is
never promoted, and every subsequent packet for that client leaks five
buffers.
Fix: free buff.iov_base on all return paths in arp_enumerate(),
including the early returns inside the retry loop where iov_base may
already be non-NULL from a prior expand_buf() call.
Reported against: FreeBSD 14, dnsmasq 2.91
Observed symptom: steady process memory growth correlated with DHCPv6
SOLICIT traffic from a client whose NDP entry is absent from the
kernel table (confirmed by disabling the client stopping the balloon).
Signed-off-by: Sagie D.
---
bpf.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/bpf.c b/bpf.c
index XXXXXXX..XXXXXXX 100644
--- a/bpf.c
+++ b/bpf.c
@@ -xx,12 +xx,18 @@ int arp_enumerate(void *parm, callback_t callback)
while (1)
{
if (!expand_buf(&buff, needed))
- return 0;
+ {
+ free(buff.iov_base);
+ return 0;
+ }
if ((rc = sysctl(mib, 6, buff.iov_base, &needed, NULL, 0)) == 0 ||
errno != ENOMEM)
break;
needed += needed / 8;
}
if (rc == -1)
- return 0;
+ {
+ free(buff.iov_base);
+ return 0;
+ }
for (next = buff.iov_base ; next < (char *)buff.iov_base + needed;
next += rtm->rtm_msglen)
{
rtm = (struct rt_msghdr *)next;
sin2 = (struct sockaddr_inarp *)(rtm + 1);
sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
if (!callback.af_unspec(AF_INET, &sin2->sin_addr, LLADDR(sdl),
sdl->sdl_alen, parm))
- return 0;
+ {
+ free(buff.iov_base);
+ return 0;
+ }
}
- return 1;
+ free(buff.iov_base);
+ return 1;
}
More information about the Dnsmasq-discuss
mailing list