Skip to content

ipn/ipnlocal: don't panic on an over-long name in the peerAPI DNS debug mode - #21308

Merged
bradfitz merged 1 commit into
tailscale:mainfrom
Dev-next-gen:fix-peerapi-dns-long-name
Sep 16, 2026
Merged

bradfitz merged 1 commit into
tailscale:mainfrom
Dev-next-gen:fix-peerapi-dns-long-name

Conversation

@Dev-next-gen

Copy link
Copy Markdown
Contributor

dnsQueryForName builds the query for GET /dns-query?q=<name>, the peerAPI's
interactive debug mode for humans. I noticed the name goes from the peer's query
string into dnsmessage.MustNewName with nothing done to it but appending a trailing
dot, and dnsmessage.NewName fails once the name is longer than 255 bytes, so a peer
that types a long enough name panics the handler:

panic: creating name: insufficient data for calculated length type

golang.org/x/net/dns/dnsmessage.MustNewName({_, _})
	golang.org/x/[email protected]/dns/dnsmessage/message.go:1943 +0x1d4
tailscale.com/ipn/ipnlocal.dnsQueryForName({0x12cc48822371, 0xff}, {0x0?, 0x130?})
	ipn/ipnlocal/peerapi.go:934 +0x152
tailscale.com/ipn/ipnlocal.(*peerAPIHandler).handleDNSQuery(...)
	ipn/ipnlocal/peerapi.go:837 +0x175

The panic happens before the query reaches the resolver, so the nameAllowed filter
from isPeerAPIDNSAllowed never gets a say and only sourceAllowed has to be true:
the user's own untagged devices, and any peer an extension hook lets through, such as
a client using this node as an exit node with DNS proxying allowed. http.Server
recovers it, so tailscaled itself survives; the peer's connection is dropped instead
of answered and the node logs a panic trace per request.

Just under the limit the name was mishandled too. A 255-byte name is accepted by
NewName but rejected by Question when it packs the name, and that error was
dropped, so b.Finish() returned a well-formed query with no question in it, which
was then handed to the resolver.

dnsQueryForName now uses NewName, checks the error from Question, and returns
the error from Finish; handleDNSQuery turns a name it cannot build a query for
into the 400 it already uses for the other malformed-request cases. The only caller is
the ?q= debug path, so nothing outside this file changes shape.

The test is built on the existing TestPeerAPIPrettyReplyCNAME harness so it goes
through the real handler. On main it panics at peerapi.go:934; with the fix it gets
a 400. The whole package passes:

$ go test ./ipn/ipnlocal/ -run TestPeerAPIDNSQueryLongName -count=1   # before
--- FAIL: TestPeerAPIDNSQueryLongName (0.01s)
panic: creating name: insufficient data for calculated length type [recovered, repanicked]
FAIL	tailscale.com/ipn/ipnlocal

$ go test ./ipn/ipnlocal/ -count=1                                    # after
ok  	tailscale.com/ipn/ipnlocal	13.100s

Fixes #21307

Found by a defect-hunting pipeline I build and run (Dev-next-gen), using Claude Code with Anthropic's Claude Opus 5.

…ug mode

dnsQueryForName builds the query for GET /dns-query?q=<name>, the peerAPI's
interactive debug mode. The name comes from the peer's query string and the
only thing done to it is appending a trailing dot, but the query is built
with dnsmessage.MustNewName, which panics as soon as the name is longer than
255 bytes.

The panic happens before the query reaches the resolver, so the nameAllowed
filter never gets a say and only sourceAllowed has to be true: any of the
user's own untagged devices, and any peer an extension hook lets through,
such as a client using this node as an exit node with DNS proxying allowed.
http.Server recovers it, so tailscaled survives, but the peer's connection is
dropped instead of answered and the node logs a panic trace per request.

Just under the limit the name was mishandled too: a 255-byte name is accepted
by NewName but rejected by Question when it packs the name, and that error
was dropped, so Finish returned a well-formed query with no question in it
which was then handed to the resolver.

Have dnsQueryForName use NewName, check the error from Question, and return
the error from Finish. handleDNSQuery turns a name it cannot build a query
for into the 400 it already uses for the other malformed-request cases.

Fixes tailscale#21307

Change-Id: I7c1a4b7e4f9a1d2c3b5e8f0a6d4c2b9e1f3a7d50
Signed-off-by: leoca <[email protected]>
@bradfitz
bradfitz merged commit ec1e07c into tailscale:main Sep 16, 2026
56 checks passed
Dev-next-gen added a commit to Dev-next-gen/Dev-next-gen that referenced this pull request Sep 16, 2026
tailscale/tailscale#21308, merged today. Unrelated to the embargoed
net/packet work.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ipn/ipnlocal: peerAPI DNS debug mode panics on an over-long ?q= name

2 participants