Skip to content

fix: prevent tailnet coordination panics - #29300

Merged
dylanhuff-at-coder merged 3 commits into
mainfrom
dylan/plat-595-security-nil-node-in-tailnet-updateself-crashes-entire
Sep 22, 2026
Merged

dylanhuff-at-coder merged 3 commits into
mainfrom
dylan/plat-595-security-nil-node-in-tailnet-updateself-crashes-entire

Conversation

@dylanhuff-at-coder

@dylanhuff-at-coder dylanhuff-at-coder commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Malformed coordinate requests can panic per-connection coordinator goroutines and terminate coderd. Disconnect requests and nested cleanup of peers with full response buffers can also attempt to send on or close an already-closed channel.

Validate request structure before authorization or state mutation in both tailnet coordinators, stop processing after disconnect, and avoid removing a peer twice during nested cleanup. Unexpected per-connection panics close the affected connection through the existing lost-peer cleanup.

https://linear.app/codercom/issue/PLAT-595/security-nil-node-in-tailnet-updateself-crashes-entire-coderd-process

@linear-code

linear-code Bot commented Sep 14, 2026

Copy link
Copy Markdown

PLAT-595

@dylanhuff-at-coder dylanhuff-at-coder changed the title fix: validate tailnet coordination requests fix: prevent tailnet coordination panics Sep 14, 2026
@dylanhuff-at-coder
dylanhuff-at-coder marked this pull request as ready for review September 15, 2026 15:18
@dylanhuff-at-coder
dylanhuff-at-coder requested review from a team and geokat September 15, 2026 15:18

@geokat geokat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm, except for one potential P2 and some nits.

Comment thread tailnet/peer.go

func (p *peer) reqLoop(ctx context.Context, logger slog.Logger, handler func(context.Context, *peer, *proto.CoordinateRequest) error) error {
func (p *peer) reqLoop(ctx context.Context, logger slog.Logger, handler func(context.Context, *peer, *proto.CoordinateRequest) error) (err error) {
defer func() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a heads-up, this recovery is not at the top of the goroutine, so if the rest of the goroutine's clean up code panics, coderd will still crash. While the original issue asks for a recovery around "coordinator per-connection goroutine".

On the other hand, the bug seems to be covered, so this is a non-blocking nit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, added recovery around the outer goroutine too, with a regression test for a panic during cleanup.

Comment thread tailnet/coordinator.go
return xerrors.Errorf("node update failed: %w", err)
}
}
if req.AddTunnel != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

An agent found this:

nodeUpdateLocked can now return successfully after recursive fan-out removes p. A compound request then continues into AddTunnel using the stale peer, potentially creating an orphan tunnel or sending on its closed response channel. Recheck c.peers[p.id] == p and return ErrAlreadyRemoved before continuing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the peer recheck after fan-out, plus regression cases for compound requests with AddTunnel and ReadyForHandshake.

Comment thread enterprise/tailnet/pgcoord.go Outdated
func (m *mapper) run() {
defer func() {
if recovered := recover(); recovered != nil {
m.logger.Error(m.ctx, "panic mapping peer responses (recovered)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would "panic processing peer mappings (recovered)" be more clear?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that reads better. Updated.

Comment thread tailnet/test/requests.go Outdated
{
name: "NilReadyForHandshake",
req: &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{nil}},
err: "ready_for_handshake entry is required",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Should this say ready_for_handshake entries must not be nil? “Entry is required” could imply the list must be non-empty, although an empty list is valid here I think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, an empty list is valid. Updated the message and test expectations.

Contain cleanup panics at the connection goroutine boundary and stop compound requests when node update fan-out removes their peer. Clarify recovered mapping and nil handshake entry messages.

Generated by Coder Agents.
@dylanhuff-at-coder
dylanhuff-at-coder merged commit ae76be4 into main Sep 22, 2026
27 of 28 checks passed
@dylanhuff-at-coder
dylanhuff-at-coder deleted the dylan/plat-595-security-nil-node-in-tailnet-updateself-crashes-entire branch September 22, 2026 16:31
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants