mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
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
101 lines
3.3 KiB
Go
101 lines
3.3 KiB
Go
package tailnet_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/v3/sloggers/slogtest"
|
|
"github.com/coder/coder/v2/tailnet"
|
|
"github.com/coder/coder/v2/tailnet/proto"
|
|
"github.com/coder/coder/v2/tailnet/test"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestCoordinateeAuthNilNode(t *testing.T) {
|
|
t.Parallel()
|
|
for name, auth := range map[string]tailnet.CoordinateeAuth{
|
|
"Agent": tailnet.AgentCoordinateeAuth{ID: uuid.New()},
|
|
"Client": tailnet.ClientCoordinateeAuth{AgentID: uuid.New()},
|
|
"ClientUser": tailnet.ClientUserCoordinateeAuth{},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
require.NotPanics(t, func() {
|
|
require.EqualError(t, auth.Authorize(ctx, &proto.CoordinateRequest{
|
|
UpdateSelf: &proto.CoordinateRequest_UpdateSelf{},
|
|
}), "update_self node is required")
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCoordinator_InvalidRequests(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
coordinator := tailnet.NewCoordinator(slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}))
|
|
t.Cleanup(func() { require.NoError(t, coordinator.Close()) })
|
|
test.InvalidCoordinateRequestTest(ctx, t, coordinator)
|
|
}
|
|
|
|
func TestCoordinator_RequestPanic(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
coordinator := tailnet.NewCoordinator(slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}))
|
|
t.Cleanup(func() { require.NoError(t, coordinator.Close()) })
|
|
test.CoordinateRequestPanicTest(ctx, t, coordinator)
|
|
}
|
|
|
|
func TestValidateCoordinateRequest(t *testing.T) {
|
|
t.Parallel()
|
|
validRFH := &proto.CoordinateRequest_ReadyForHandshake{Id: tailnet.UUIDToByteSlice(uuid.New())}
|
|
for _, tc := range []struct {
|
|
name string
|
|
req *proto.CoordinateRequest
|
|
err string // empty means the request is accepted
|
|
}{
|
|
{name: "NilRequest", err: "coordinate request is required"},
|
|
{name: "EmptyRequest", req: &proto.CoordinateRequest{}},
|
|
{
|
|
name: "NilNode",
|
|
req: &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{}},
|
|
err: "update_self node is required",
|
|
},
|
|
{
|
|
name: "EmptyNode",
|
|
req: &proto.CoordinateRequest{UpdateSelf: &proto.CoordinateRequest_UpdateSelf{Node: &proto.Node{}}},
|
|
},
|
|
{name: "NilReadyForHandshake", req: &proto.CoordinateRequest{ReadyForHandshake: nil}},
|
|
{
|
|
name: "EmptyReadyForHandshake",
|
|
req: &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{}},
|
|
},
|
|
{
|
|
name: "ValidReadyForHandshake",
|
|
req: &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{validRFH}},
|
|
},
|
|
{
|
|
name: "NilReadyForHandshakeEntry",
|
|
req: &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{nil}},
|
|
err: "ready_for_handshake entries must not be nil",
|
|
},
|
|
{
|
|
name: "NilReadyForHandshakeAfterValid",
|
|
req: &proto.CoordinateRequest{ReadyForHandshake: []*proto.CoordinateRequest_ReadyForHandshake{validRFH, nil}},
|
|
err: "ready_for_handshake entries must not be nil",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
err := tailnet.ValidateCoordinateRequest(tc.req)
|
|
if tc.err == "" {
|
|
require.NoError(t, err)
|
|
return
|
|
}
|
|
require.EqualError(t, err, tc.err)
|
|
})
|
|
}
|
|
}
|