print_mac() used to output into caller-supplied buffer with no
length checking. The various callers supplied different
length buffers and did their own, ad-hoc, length checking before
calling print_mac(). This was an accident waiting to happen and
it had actually happened in couple of places, with buffer
overflows possible.
print_mac() now returns the address of its own buffer, which
is grown as required.
Thanks to Nicholas Carlini <npc@anthropic.com> for spotting this problem.
Also ripple effects for most of its callers.
This fixes an OOB write caused by bad pointer arithmetic
calculating the pointer-to-end-of-packet used before.
Thanks to Donghyeon Jeong for spotting the bug.
The EDNS0 option iteration loop read a 2-byte len field from the
packet and used it directly in:
opt.scope_netmask = p[3]; // requires len >= 4
memcmp(p, &opt, len) // reads len bytes from packet
Neither access was guarded against len exceeding the remaining RDATA.
A crafted DNS reply whose OPT RDATA contains a CLIENT_SUBNET option
with an oversized len field would cause memcmp to read past the end of
the packet buffer, leaking heap memory.
Risk: currently the only caller that reaches this branch is
forward.c:process_reply(), which (before the companion fix) passed an
OPT-RR-scoped plen that made skip_name() return NULL first, so the
unsafe code was never reached. With that companion fix applied the
path is live: a rogue upstream server or on-path attacker can craft a
reply with a malformed CLIENT_SUBNET len and trigger the OOB read,
potentially leaking adjacent heap contents.
Fix: break out of the loop if the option body would extend past the
RDATA boundary.
Signed-off-by: Dominik <dl6er@dl6er.de>
The options which cause dnsmasq to decorate a DNS query with the MAC
address on the originating client can fail when the query is sent
using TCP.
In TCP mode, dnsmasq spawns a new process to handle each TCP connection.
These child processes do not have an open netlink socket, which is
needed to read the kernel ARP table, so the process of adding the
client's MAC address as an EDNS0 option silently fails.
This is fixed by this patch by updating dnsmasq's ARP cache in the
main process just before forking the TCP-handler child process.
This ensures that the copy of the ARP cache inherited by the
TCP-handler contains the information required without the need to
read the kernel ARP table.
Thanks to Bruno Ravara for spotting and characterising this bug.
GCC complains that writing the five-character "ODNS\0" string into
a four-element char magic[4] array truncates the NUL character.
The warning's rationale is that this is incompatible with C++, or
maybe non-intentional.
GCC 8 has added a nonstring variable attribute, clang 20.1 does
not yet support this, but clang's Git head does.
Add an ATTRIBUTE_NONSTRING macro, currently only defined on GCC >= 8
as __attribute__ ((nonstring)). This successfully suppresses
the warning on Fedora Linux 42's default compiler.
The alternative would be to replace the "ODNS" literal by {0} and
instead memcpy(opt.magic, "ODNS", sizeof(opt.magic)); on the next line,
which is correct, C++ compatible, but also less concise.
If dnsmasq is configured to add an EDNS client subnet to a query,
it is careful to suppress use of the cache, since a cached answer may
not be valid for a query with a different client subnet.
Extend this behaviour to queries which arrive a dnsmasq
already carrying an EDNS client subnet.
This change is rather more involved than may seem necessary at first sight,
since the existing code relies on all queries being decorated by dnsmasq
and therefore not cached, so there is no chance that an incoming query
might hit the cache and cache lookup don't need to be suppressed, just
cache insertion. When downstream queries may be a mix of client-subnet
bearing and plain vanilla, it can't be assumed that the answers are never
in the cache, and queries with subnets must not do lookups.
I am attaching an incremental git-am ready patch to go on top your Git HEAD,
to fix all sorts of issues and make this conforming C99 with default
options set,
and fix another load of warnings you receive when setting the compiler
to pick the nits,
-pedantic-errors -std=c99 (or c11, c18, c2x).
It changes many void * to uint8_t * to make the "increment by bytes"
explicit.
You can't do:
void *foo;
// ...
foo += 2.
In the post 2020 flag-day world, we limit UDP packets to 1232 bytes
which can go anywhere, so the dodgy code to try and determine the
functional maxmimum packet size on the path from upstream servers
is obsolete.
This was kinda strange before, with a lot of cargo-cult copied code,
and no clear strategy.
Now it works like this:
When talking upstream we always add a pseudoheader, and set the
UDP packet size to --edns-packet-max unless we've had problems
talking to a server, when it's reduced to 1280 if that fixes things.
Answering queries from downstream, we get the answer (either from
upstream or local data) If local data won't fit the advertised size
(or 512 if there's not pseudoheader) return truncated. If upstream
returns truncated, do likewise. If upstream is OK, but the answer is
too big for downstream, truncate the answer.
If a NODATA answer is returned instead of actual data for A or AAAA
queries because of the existence of --filter-A or --filter-AAAA
config options, then mark the replies with an EDE "filtered" tag.
Basic patch by Petr Menšík, tweaked by Simon Kelley to apply onto
the preceding caching patches.
If we add the EDNS client subnet option, or the client's
MAC address, then the reply we get back may very depending on
that. Since the cache is ignorant of such things, it's not safe to
cache such replies. This patch determines when a dangerous EDNS
option is being added and disables caching.
Note that for much the same reason, we can't combine multiple
queries for the same question when dangerous EDNS options are
being added, and the code now handles that in the same way. This
query combining is required for security against cache poisoning,
so disabling the cache has a security function as well as a
correctness one.
This was the source of a large number of #ifdefs, originally
included for use with old embedded libc versions. I'm
sure no-one wants or needs IPv6-free code these days, so this
is a move towards more maintainable code.
Fix out-of-memory Dos vulnerability. An attacker which can
send malicious DNS queries to dnsmasq can trigger memory
allocations in the add_pseudoheader function
The allocated memory is never freed which leads to a DoS
through memory exhaustion. dnsmasq is vulnerable only
if one of the following option is specified:
--add-mac, --add-cpe-id or --add-subnet.
Fix DoS in DNS. Invalid boundary checks in the
add_pseudoheader function allows a memcpy call with negative
size An attacker which can send malicious DNS queries
to dnsmasq can trigger a DoS remotely.
dnsmasq is vulnerable only if one of the following option is
specified: --add-mac, --add-cpe-id or --add-subnet.