From d889856fc36f434179d9c451604b7859e29c8cde Mon Sep 17 00:00:00 2001 From: Maciej Kula Date: Wed, 20 May 2026 03:03:40 +0200 Subject: [PATCH] samp(go): reject invalid UTF-8 channel text --- go/samp.go | 33 ++++++++++++++++----------------- go/samp_test.go | 24 ++++++++++++++++++++++++ go/types.go | 17 +++++++++++++++-- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/go/samp.go b/go/samp.go index 381b28c..406301d 100644 --- a/go/samp.go +++ b/go/samp.go @@ -227,15 +227,7 @@ func DecodeRemark(remark RemarkBytes) (Remark, error) { if err != nil { return nil, err } - cn, err := ChannelNameParse(name) - if err != nil { - return nil, err - } - cd, err := ChannelDescriptionParse(desc) - if err != nil { - return nil, err - } - return ChannelCreateRemark{Name: cn, Description: cd}, nil + return ChannelCreateRemark{Name: name, Description: desc}, nil case 0x04: if len(data) < 19 { @@ -268,27 +260,34 @@ func DecodeRemark(remark RemarkBytes) (Remark, error) { } } -func decodeChannelCreatePayload(data []byte) (string, string, error) { +func decodeChannelCreatePayload(data []byte) (ChannelName, ChannelDescription, error) { if len(data) < 2 { - return "", "", ErrInsufficientData + return ChannelName{}, ChannelDescription{}, ErrInsufficientData } nameLen := int(data[0]) if nameLen == 0 || nameLen > ChannelNameMax { - return "", "", ErrInvalidChannelName + return ChannelName{}, ChannelDescription{}, ErrInvalidChannelName } if len(data) < 1+nameLen+1 { - return "", "", ErrInsufficientData + return ChannelName{}, ChannelDescription{}, ErrInsufficientData + } + name, err := ChannelNameFromBytes(data[1 : 1+nameLen]) + if err != nil { + return ChannelName{}, ChannelDescription{}, err } - name := string(data[1 : 1+nameLen]) descOff := 1 + nameLen descLen := int(data[descOff]) if descLen > ChannelDescMax { - return "", "", ErrInvalidChannelDesc + return ChannelName{}, ChannelDescription{}, ErrInvalidChannelDesc } if len(data) < descOff+1+descLen { - return "", "", ErrInsufficientData + return ChannelName{}, ChannelDescription{}, ErrInsufficientData + } + desc, err := ChannelDescriptionFromBytes(data[descOff+1 : descOff+1+descLen]) + if err != nil { + return ChannelName{}, ChannelDescription{}, err } - return name, string(data[descOff+1 : descOff+1+descLen]), nil + return name, desc, nil } func EncodeThreadContent(thread, replyTo, continues BlockRef, body []byte) Plaintext { diff --git a/go/samp_test.go b/go/samp_test.go index 4ebd8f7..1667738 100644 --- a/go/samp_test.go +++ b/go/samp_test.go @@ -24,6 +24,30 @@ func TestDecodeChannelCreateDescTooLongByte(t *testing.T) { require.ErrorIs(t, err, ErrInvalidChannelDesc) } +func TestDecodeChannelCreateInvalidUTF8Name(t *testing.T) { + data := []byte{0x13, 0x02, 0xff, 0xfe, 0x00} + _, err := DecodeRemark(RemarkBytesFromBytes(data)) + require.ErrorIs(t, err, ErrInvalidChannelName) +} + +func TestDecodeChannelCreateInvalidUTF8Description(t *testing.T) { + data := []byte{0x13, 0x01, 0x41, 0x02, 0xff, 0xfe} + _, err := DecodeRemark(RemarkBytesFromBytes(data)) + require.ErrorIs(t, err, ErrInvalidChannelDesc) +} + +func TestChannelTextConstructorsRejectInvalidUTF8(t *testing.T) { + _, err := ChannelNameParse(string([]byte{0xff})) + require.ErrorIs(t, err, ErrInvalidChannelName) + _, err = ChannelNameFromBytes([]byte{0xff}) + require.ErrorIs(t, err, ErrInvalidChannelName) + + _, err = ChannelDescriptionParse(string([]byte{0xfe})) + require.ErrorIs(t, err, ErrInvalidChannelDesc) + _, err = ChannelDescriptionFromBytes([]byte{0xfe}) + require.ErrorIs(t, err, ErrInvalidChannelDesc) +} + func TestEncodeGroupRemarkLayout(t *testing.T) { nonce := NonceFromBytes([12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) ephPub := EphPubkeyFromBytes([32]byte{0xAA}) diff --git a/go/types.go b/go/types.go index aa6906e..56b9e3e 100644 --- a/go/types.go +++ b/go/types.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "fmt" + "unicode/utf8" ) var ( @@ -175,22 +176,34 @@ func (c CallArgs) Len() int { return len(c.b) } type ChannelName struct{ s string } func ChannelNameParse(s string) (ChannelName, error) { - if len(s) == 0 || len(s) > ChannelNameMax { + if len(s) == 0 || len(s) > ChannelNameMax || !utf8.ValidString(s) { return ChannelName{}, ErrInvalidChannelName } return ChannelName{s}, nil } +func ChannelNameFromBytes(b []byte) (ChannelName, error) { + if len(b) == 0 || len(b) > ChannelNameMax || !utf8.Valid(b) { + return ChannelName{}, ErrInvalidChannelName + } + return ChannelName{string(b)}, nil +} func (c ChannelName) String() string { return c.s } func (c ChannelName) Len() int { return len(c.s) } type ChannelDescription struct{ s string } func ChannelDescriptionParse(s string) (ChannelDescription, error) { - if len(s) > ChannelDescMax { + if len(s) > ChannelDescMax || !utf8.ValidString(s) { return ChannelDescription{}, ErrInvalidChannelDesc } return ChannelDescription{s}, nil } +func ChannelDescriptionFromBytes(b []byte) (ChannelDescription, error) { + if len(b) > ChannelDescMax || !utf8.Valid(b) { + return ChannelDescription{}, ErrInvalidChannelDesc + } + return ChannelDescription{string(b)}, nil +} func (c ChannelDescription) String() string { return c.s } func (c ChannelDescription) Len() int { return len(c.s) }