diff --git a/tailcat.go b/tailcat.go index 52a78b2f8..2c84857cf 100644 --- a/tailcat.go +++ b/tailcat.go @@ -174,6 +174,9 @@ func (p NodePublic) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements encoding.BinaryUnmarshaler for CBOR deserialization. func (p *NodePublic) UnmarshalBinary(x []byte) error { + if len(x) != key.NodePublicRawLen { + return fmt.Errorf("invalid node public key length %d, want %d", len(x), key.NodePublicRawLen) + } p.NodePublic = key.NodePublicFromRaw32(go4mem.B(x)) return nil } diff --git a/tailcat_test.go b/tailcat_test.go index 39fd0551c..d1e1fe51c 100644 --- a/tailcat_test.go +++ b/tailcat_test.go @@ -5,6 +5,7 @@ package tailcat import ( "context" + "encoding/base64" "errors" "fmt" "io" @@ -16,6 +17,7 @@ import ( "testing" "time" + "github.com/fxamacker/cbor/v2" "github.com/google/go-cmp/cmp" "go4.org/mem" "tailscale.com/tailcfg" @@ -422,6 +424,40 @@ func TestConnBlob(t *testing.T) { } } +func TestParseConnBlobMalformedPublicKey(t *testing.T) { + for name, keyBytes := range map[string][]byte{ + "short": make([]byte, key.NodePublicRawLen-1), + "long": make([]byte, key.NodePublicRawLen+1), + } { + t.Run(name, func(t *testing.T) { + raw, err := cbor.Marshal(map[string][]byte{"p": keyBytes}) + if err != nil { + t.Fatal(err) + } + cb := ConnBlob("tc" + base64.RawURLEncoding.EncodeToString(raw)) + assertParseError := func(name string, parse func() error) { + t.Helper() + defer func() { + if r := recover(); r != nil { + t.Fatalf("%s panicked: %v", name, r) + } + }() + if err := parse(); err == nil { + t.Errorf("%s unexpectedly accepted malformed public key", name) + } + } + assertParseError("ParseConnBlob", func() error { + _, err := ParseConnBlob(cb) + return err + }) + assertParseError("ParseConnBlobRaw", func() error { + _, err := ParseConnBlobRaw(cb) + return err + }) + }) + } +} + // TestFetchDERPMapMemoryCache verifies the default in-memory DERP map // cache: a second fetch of the same URL within the freshness window // makes no network request.