diff --git a/transcoders.go b/transcoders.go index 03b39ae..1152693 100644 --- a/transcoders.go +++ b/transcoders.go @@ -138,7 +138,7 @@ func ip4BtS(b []byte) (string, error) { return net.IP(b).String(), nil } -var TranscoderPort = NewTranscoderFromFunctions(portStB, portBtS, nil) +var TranscoderPort = NewTranscoderFromFunctions(portStB, portBtS, portValidate) func portStB(s string) ([]byte, error) { i, err := strconv.ParseUint(s, 10, 16) @@ -151,10 +151,20 @@ func portStB(s string) ([]byte, error) { } func portBtS(b []byte) (string, error) { + if len(b) < 2 { + return "", fmt.Errorf("port: byte slice too short: %d bytes, want 2", len(b)) + } i := binary.BigEndian.Uint16(b) return strconv.FormatUint(uint64(i), 10), nil } +func portValidate(b []byte) error { + if len(b) != 2 { + return fmt.Errorf("port: invalid length: %d bytes, want 2", len(b)) + } + return nil +} + var TranscoderOnion = NewTranscoderFromFunctions(onionStB, onionBtS, onionValidate) func onionStB(s string) ([]byte, error) { @@ -191,6 +201,9 @@ func onionStB(s string) ([]byte, error) { } func onionBtS(b []byte) (string, error) { + if len(b) != 12 { + return "", fmt.Errorf("invalid len for onion addr: got %d expected 12", len(b)) + } addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:10])) port := binary.BigEndian.Uint16(b[10:12]) if port == 0 { @@ -245,6 +258,9 @@ func onion3StB(s string) ([]byte, error) { } func onion3BtS(b []byte) (string, error) { + if len(b) != 37 { + return "", fmt.Errorf("invalid len for onion addr: got %d expected 37", len(b)) + } addr := strings.ToLower(base32.StdEncoding.EncodeToString(b[0:35])) port := binary.BigEndian.Uint16(b[35:37]) if port < 1 { diff --git a/transcoders_test.go b/transcoders_test.go new file mode 100644 index 0000000..1666b91 --- /dev/null +++ b/transcoders_test.go @@ -0,0 +1,55 @@ +package multiaddr + +import "testing" + +// Regression test for https://github.com/multiformats/go-multiaddr/issues/288. +// onionBtS and onion3BtS used to index their input at fixed offsets without +// a length check, panicking with `slice bounds out of range` when called with +// short input (e.g. directly via the exported TranscoderOnion / TranscoderOnion3). +func TestOnionTranscodersRejectShortInput(t *testing.T) { + cases := []struct { + name string + tr Transcoder + b []byte + }{ + {"onion-short", TranscoderOnion, []byte{0x00, 0x01}}, + {"onion-empty", TranscoderOnion, nil}, + {"onion3-short", TranscoderOnion3, []byte{0x00, 0x01}}, + {"onion3-empty", TranscoderOnion3, nil}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if _, err := tc.tr.BytesToString(tc.b); err == nil { + t.Fatalf("expected error for short input, got nil") + } + }) + } +} + +// portBtS used to call binary.BigEndian.Uint16 without checking len(b) >= 2. +// Direct callers of TranscoderPort (outside the codec.go length checks) could +// panic with `index out of range`. The internal guard and the new validator +// must turn both into normal errors. +func TestTranscoderPortRejectsShortInput(t *testing.T) { + t.Run("BytesToString-short", func(t *testing.T) { + if _, err := TranscoderPort.BytesToString(nil); err == nil { + t.Fatalf("expected error on nil input") + } + if _, err := TranscoderPort.BytesToString([]byte{0x00}); err == nil { + t.Fatalf("expected error on 1-byte input") + } + }) + t.Run("Validate-rejects-wrong-length", func(t *testing.T) { + if err := TranscoderPort.ValidateBytes(nil); err == nil { + t.Fatalf("expected validate error on nil input") + } + if err := TranscoderPort.ValidateBytes([]byte{0x00, 0x01, 0x02}); err == nil { + t.Fatalf("expected validate error on 3-byte input") + } + }) + t.Run("Validate-accepts-2-byte", func(t *testing.T) { + if err := TranscoderPort.ValidateBytes([]byte{0x00, 0x50}); err != nil { + t.Fatalf("expected no error on 2-byte input, got %v", err) + } + }) +}