Skip to content

Update embedded dnsmasq to v2.93#2890

Merged
DL6ER merged 120 commits into
developmentfrom
update/dnsmasq
Jun 13, 2026
Merged

Update embedded dnsmasq to v2.93#2890
DL6ER merged 120 commits into
developmentfrom
update/dnsmasq

Conversation

@DL6ER

@DL6ER DL6ER commented May 11, 2026

Copy link
Copy Markdown
Member

What does this implement/fix?

Update embedded dnsmasq to released v2.93

Highlights

Security

(all of these have already been merged into master and are released separately)

  • CVE-2026-2291 - heap overflow via long domain names (fixed by reworked storage allocation for domain names).
  • CVE-2026-4890 - DNSSEC denial-of-service via crafted NSEC bitmap (infinite loop in window iteration).
  • CVE-2026-4891 - crash via crafted RRSIG with truncated rdlen.
  • CVE-2026-4892 - buffer overflow in privileged DHCP helper with oversized DHCPv6 CLIDs.
  • CVE-2026-4893 - EDNS Client Subnet validation bypass with --add-subnet.
  • CVE-2026-5172 - heap OOB read in extract_addresses() via crafted RR rdlen.

DNSSEC correctness

  • Wildcard validation corner case for queries that match a wildcard but require no non-existence proof.
  • DNSSEC validation no longer fails when replies carry spurious RRSIGs from unsigned zones.
  • Signed CNAME replies to DS queries are now accepted as proof that no DS exists, fixing DS/zone-break detection.

DHCP and inotify

  • Fix DHCPv6 regression introduced in 2.92 that broke setups using a DHCP relay.
  • inotify watches are now created after privilege drop, so file permission checks match the runtime user.

Further fixes and improvements made by the Pi-hole team

  • dump: check lseek() return in pcap record-counting loop.
  • domain: fix strncat buffer-size argument in is_rev_synth().
  • lease: check parse_hex() return value before use as length.
  • edns0: bounds-check option length before memcmp in check_source().
  • rfc3315: fix integer underflow / heap overflow in log6_opts STATUS_CODE.
  • dhcp-common: fix OOB reads in DHCPv6 option_string() decoders.
  • dhcp-common: bounds-check label index before reading in OT_RFC1035_NAME decoder.
  • rfc2131: fix off-by-one in BOOTP filename netid NUL termination.
  • helper: fix OOB read in grab_extradata_lua bounds check.
  • prettyprint_time(): NUL-terminate buf when t == 0.
  • rand64() must share global outleft with rand16() / rand32().
  • reply_query(): move fd-match guard out of loop.
  • expand_workspace_real(): zero the correct byte count.

Related issue or feature (if applicable): N/A

Pull request in docs with documentation (if applicable): N/A


By submitting this pull request, I confirm the following:

  1. I have read and understood the contributors guide, as well as this entire template. I understand which branch to base my commits and Pull Requests against.
  2. I have commented my proposed changes within the code.
  3. I am willing to help maintain this change if there are issues with it later.
  4. It is compatible with the EUPL 1.2 license
  5. I have squashed any insignificant commits. (git rebase)

Checklist:

  • The code change is tested and works locally.
  • I based my code and PRs against the repositories development branch.
  • I signed off all commits. Pi-hole enforces the DCO for all contributions
  • I signed all my commits. Pi-hole requires signatures to verify authorship
  • I have read the above and my PR is ready for review.

simonkelley and others added 30 commits May 11, 2026 19:12
A malformed DHCP request can cause out-bounds memory reads, and probably SEGV.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
…ions.

A general rewrite and consolidation of the code that determines
if an incoming TCP connection is allowed, based on --address and --interface.

This was prompted by a bug found by Sławomir Zborowski where a TCP
connection for a valid IPv4 address on one interface which arrived
via a second interface which didn't have an IPv4 address would be
wrongly rejected.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
These can't be held forever by a client that opens a connection and
then sends nothing, but we can still be DoSsed by a server which
accepts a connection and never replies.

If the TCP process times out, it sends that information to the
parent so that the UDP query can be unblocked. As the TCP
timeout is 150s it's highly unlikely that any client will
still be waiting, but the point is to free resources.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
If we have a wildcard record *.example.com and recieve a query for
a.example.com then that's OK, but we have to check that there isn't
an actual a.example.com record. The corner case is when we get a
query for *.example.com in that case the non-existence check
is not required, but was being done.

Thanks to Jan Breig for spotting this.

Signed-off-by: Dominik <dl6er@dl6er.de>
FreeBSD 15.0 has added Linux-compatible inotify support, so
enable it by looking if the version matches. Since FreeBSD inotify has
seen a few bug fixes in 2025H2, so only enable it if
__FreeBSD_version >= 1500068.  The latter can be checked through
osreldate.h or sys/param.h; the latter defines more macros that clash
with dnsmasq's, such as MIN and MAX, so use the former.

Signed-off-by: Dominik <dl6er@dl6er.de>
The presence of wrong RRSIG RRs in replies causes DNSSEC
validation to fail even when the RRs do not require validation
because the zone is unsigned.

This patch solves the problem and tidies up the logic.

Included is some fixes for hostname_issubdomain() which suffered
some confusions about argument order :) I've clarified that and
checked every to the function to make sure they are using the
correct argument order.

Note that, at the time of this commit, Google DNS appears to have
the same bug, so if you're using 8.8.8.8 or friends as upstream,
resolving the broken zones (eg rivcoed.org) will still fail.

Thanks to Petr Menšík for the bug report.

Signed-off-by: Dominik <dl6er@dl6er.de>
This may be useful for embedded systems for example:
log-queries without only_failed places a heavy load on the
NAND flash memory. At the same time, logging requests
is necessary to troubleshoot network issues.

Signed-off-by: Dominik <dl6er@dl6er.de>
If we're doing DNSSEC validation and fail because the upstream
reply to our query is SERVFAIL, log this as the reason in
validation result line.

This will make maintainers lives easier when they get reports
of "wrong" validation failure, which is sometimes an upstream problem.

Signed-off-by: Dominik <dl6er@dl6er.de>
A CNAME reply to a DNSSEC query was confusing the validation logic.
It now accepts a signed CNAME reply to a DS query as proof that no DS
exists at the domain. This fixes the DS/zone break detection logic.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
This was functionally correct, but every call
malloced a new buffer and freed the previous one, rather
than only doing that when the buffer needed expansion.

Signed-off-by: Dominik <dl6er@dl6er.de>
Log all blocks malloced and all blocks freed.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
This fixes the plethora of 64k buffers that got allocated when doing
DNSSEC over TCP.

By using the UDP buffer is pass the query into tcp_talk() and
allowing tcp_talk to allocate its output buffer one the size
of the reply is known, we only need to allocate as much memory as
is required. The final reply to the TCP query still needs the 64k
buffer because answer_request() and answer_auth() are not capable
of extending their output buffers.

Signed-off-by: Dominik <dl6er@dl6er.de>
In the DNS TCP code, there are a couple of places where
we have a buffer containing a message which we need to
send via TCP. The DNS protocol is that this is sent as
<16-bit length in network order><message>

Making two write calls, one for the length and one
for the message causes the TCP stack to send two packets,
one for each. A single packet containing both is
preferable from a performance POV.

Implement a scatter-gather version of our read_write()
wrapper and use it where necessary to send TCP DNS messages.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
…p of free/strdup

Signed-off-by: Dominik <dl6er@dl6er.de>
This almost certainly never worked and was never use, and
it's rendered obsolete in RFC 9915.

Signed-off-by: Dominik <dl6er@dl6er.de>
Anything sent by a client must be multicast. A relay can unicast
to a server.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
While this won't do harm on systems that do 2's completement,
it triggers the compilers' undefined-behavior sanitizer and
fixes sanitizer error such as the one below (where the 1694... will
vary) and is distracting while debugging.

dnssec.c:1427:12: runtime error: left shift of 1694604366 by 1 places
cannot be represented in type 'int'

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
If iovcnt is exhausted and the first vector element's operation
is satisfied, the while loop will read past the end of the
iovec array.  This triggers the address sanitizer and leads to
undefined program state.  Avoid reading too far.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
DL6ER and others added 16 commits June 8, 2026 19:53
Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
Stop using the namebuff buffer to read config file
lines. This reduced in size in
014e909f787e808bb35daa546d3f8f3663918de2 and caused
regressions. We now resize the buffer as needed,
so there is no practical limit.

Use the same code to remove the same limit when reading
/etc/ethers and /etc/resolv.conf. Also when copying output
from the lease-change script into the log.

Thanks to Kevin Darbyshire-Bryant for finding the regresssion.

Signed-off-by: Dominik <dl6er@dl6er.de>
commit 1269f074f86bb959863012063060a3a082d37dc4 broke
hostname_issubdomain() and then accidentally fixed
some of the problems by inverting the argument order
in the new uses of the function it introduced.
All the pre-existing calls to hostname_issubdomain()
were left in a non-working state.

This fixes hostname_issubdomain() back to the state
before 1269f074f86bb959863012063060a3a082d37dc4, adds
some logic to give correct answers when comparing
to the root domain, and gets right the new calls to
hostname_issubdomain()  added in
1269f074f86bb959863012063060a3a082d37dc4

Thanks to Jean Thomas for spotting this problem.

Signed-off-by: Dominik <dl6er@dl6er.de>
Before this fix, the code could read a couple of bytes beyond
valid data when an invalid too-short packet was recieved.

Thanks to Sokhna Walo DIAKHATE <sokhnawalodiakhate@esp.sn>
for the bug report.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
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.

Signed-off-by: Dominik <dl6er@dl6er.de>
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.

Signed-off-by: Dominik <dl6er@dl6er.de>
Full credit to Jerry Tom <goonsomeway@gmail.com> for finding this.

When DNSSEC validation falls back to TCP (via a forked child process),
the parent uses a uid-based check to detect whether a frec has been
freed and reused before the child returns. It doesn't, however check for
the more likely scenario which is that the frec has simply been
freed via the timeout path in get_new_frec(). This leads to a
use-after-free on an already freed frec.

Signed-off-by: Dominik <dl6er@dl6er.de>
The current local address range is fd00::/8, but RFC 4193
para 3.1/3.2 reserves fc00::/8 for future use as local addresses,
so add that to the list.

Thanks to Michalis Vasileiadis for spotting this omission.

Signed-off-by: Dominik <dl6er@dl6er.de>
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.

Signed-off-by: Dominik <dl6er@dl6er.de>
Assigning the result of getc() to a char makes comparing it to EOF
unreliable, at the very least. You're never too old to make newbie
mistakes.

Thanks to Sebastian Gottschall  for spotting this.

Signed-off-by: Dominik <dl6er@dl6er.de>
If we stop reading a file before getting to the end, get_line_alloc()
will not clean up the memory it allocated. Give get_line_alloc()
the ability to do a forced cleanup by calling it with a NULL file,
and use that ability where we stop reading a file early.

Also clear the state variables when we free the memory, to
pre-empt use-after-free.

Signed-off-by: Dominik <dl6er@dl6er.de>
It turns out that subtracting NULL from a pointer to turn it into
an integer is not a good idea, because subtracting a NULL pointer
is undefined behaviour in C.

The correct way to do this is simply to cast the pointer to
uintptr_t. The only fly in this ointment is that the C standard
does not mandate support for uintptr_t.  In practice, I doubt
anyone is running dnsmasq on the long-dead architectures where
this might be a problem.

Thanks to Matthias Andree for the heads-up.

Signed-off-by: Dominik <dl6er@dl6er.de>
get_rdata() contains another instance of a crash caused by
a false value of RDLEN in a resource-record.

An RR containing name which is longer than the rdlen of the RR can
get get_rdata into a state where it calculates the length of the data
following the name as a negative number which is then promoted
to a very large positive number, and causing SIGSEGV.

This can be triggered by crafted packets from malicous DNS servers,
but only when DNSSEC validation is enabled. The attack packets do not
have to be correctly DNSSEC signed.

Thanks to Tristan G. for spotting this problem.

Also fix error handling for RRs with a fixed size fields in the specification
where the rrsize indicates that there are zero bytes in that position.

Signed-off-by: Dominik <dl6er@dl6er.de>
Signed-off-by: Dominik <dl6er@dl6er.de>
@DL6ER DL6ER changed the title Update embedded dnsmasq to v2.93rc1 Update embedded dnsmasq to v2.93 Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Conflicts have been resolved.

@DL6ER

DL6ER commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

This will soon be released as FTL v6.7

…print_mac()'s buffer handling

Also fix add_pseudoheader() call sites for the v2.93 size_t out_size signature.

Signed-off-by: DL6ER <dl6er@dl6er.de>
@rdwebdesign

Copy link
Copy Markdown
Member

Are these errors in the tests expected?

Signed-off-by: Dominik <dl6er@dl6er.de>
@DL6ER

DL6ER commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Fixed

@rdwebdesign rdwebdesign left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

Note:
All 6 CVE fixes were already in v2.92.2 (master branch hotfix).
Now v2.93 will add them to development branch.

@darkexplosiveqwx

Copy link
Copy Markdown
Contributor

Note:
All 6 CVE fixes were already in v2.92.2 (master branch hotfix).
Now v2.93 will add them to development branch.

Doesn't development also currently have v2.92.2 via #2889?

@DL6ER

DL6ER commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

TL;DR: Yes, both master and development are already fine given the CVE fixes.

The fixes that are part of v2.92.2 were, however, only minimal backwards-compatible fixes for the given vulnerabilities. The v2.93 rewrites some of the affected features in a way to avoid (instead of circumvent) the underlying issues. This makes the code smaller, use less memory and also fix some additional edge-cases and non-critical bugs.

@DL6ER DL6ER merged commit 57eb60d into development Jun 13, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.