Files
brianmcgee 4ff7d20b8e
PR Size Review Check / pr-size-review-gate (pull_request) Successful in 46s
sizelint / sizelint (pull_request) Successful in 1m0s
buildbot/nix-build gitea:clan/data-mesher#checks.aarch64-linux.nixos-data-mesher-basic Build done.
buildbot/nix-eval Build done.
buildbot/nix-build Build done.
gitea-mq/buildbot/nix-eval Build done.
gitea-mq/buildbot/nix-build Build done.
gitea-mq Merge queue passed
libp2p: limit msgpack decode size on all peer-facing streams
Three msgpack decoders read directly from unbounded libp2p streams: file-transfer server (`FileRequest`), file-transfer client (`FileResponse`), and the outbound state-exchange response. A malicious peer can stream arbitrarily large strings into decoded fields, causing the victim to allocate memory until OOM.

Wrap all peer-facing decoders in `io.LimitReader`. The inbound
state-exchange handler (`handleStream`) already had this protection; the other three paths were missed.
2026-04-08 11:45:22 +01:00

165 lines
3.0 KiB
Go

package msgpack_test
import (
"bytes"
"io"
"net"
"strings"
"testing"
"git.clan.lol/clan/data-mesher/pkg/msgpack"
"github.com/stretchr/testify/require"
)
func TestNetIPExtension(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ip net.IP
}{
{
name: "IPv4",
ip: net.ParseIP("192.168.1.1").To4(),
},
{
name: "IPv6",
ip: net.ParseIP("2001:db8::1"),
},
{
name: "IPv4-mapped IPv6",
ip: net.ParseIP("::ffff:192.168.1.1"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Test direct marshal/unmarshal
data, err := msgpack.Marshal(tt.ip)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var result net.IP
if err := msgpack.Unmarshal(data, &result); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if !result.Equal(tt.ip) {
t.Errorf("IP mismatch: got %v, want %v", result, tt.ip)
}
})
}
}
func TestNetIPInStruct(t *testing.T) {
t.Parallel()
type testStruct struct {
Name string
IP net.IP
Port int
}
tests := []struct {
name string
data testStruct
}{
{
name: "struct with IPv4",
data: testStruct{
Name: "server1",
IP: net.ParseIP("10.0.0.1").To4(),
Port: 8080,
},
},
{
name: "struct with IPv6",
data: testStruct{
Name: "server2",
IP: net.ParseIP("fe80::1"),
Port: 443,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
data, err := msgpack.Marshal(tt.data)
if err != nil {
t.Fatalf("Marshal failed: %v", err)
}
var result testStruct
if err := msgpack.Unmarshal(data, &result); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if result.Name != tt.data.Name {
t.Errorf("Name mismatch: got %q, want %q", result.Name, tt.data.Name)
}
if !result.IP.Equal(tt.data.IP) {
t.Errorf("IP mismatch: got %v, want %v", result.IP, tt.data.IP)
}
if result.Port != tt.data.Port {
t.Errorf("Port mismatch: got %d, want %d", result.Port, tt.data.Port)
}
})
}
}
func TestLimitedDecoder_RejectsOversizedPayload(t *testing.T) {
t.Parallel()
as := require.New(t)
type msg struct {
Data string `codec:"data"`
}
const limit = 1024
// encode a message larger than the limit
payload := msg{Data: strings.Repeat("A", limit*2)}
encoded, err := msgpack.Marshal(payload)
as.NoError(err)
as.Greater(len(encoded), limit)
// decoding through a LimitReader must fail
decoder := msgpack.NewDecoder(io.LimitReader(bytes.NewReader(encoded), limit))
var result msg
as.Error(decoder.Decode(&result))
}
func TestLimitedDecoder_AcceptsPayloadWithinLimit(t *testing.T) {
t.Parallel()
as := require.New(t)
type msg struct {
Data string `codec:"data"`
}
const limit = 1024
payload := msg{Data: "hello"}
encoded, err := msgpack.Marshal(payload)
as.NoError(err)
as.Less(len(encoded), limit)
decoder := msgpack.NewDecoder(io.LimitReader(bytes.NewReader(encoded), limit))
var result msg
as.NoError(decoder.Decode(&result))
as.Equal(payload.Data, result.Data)
}