fix: reject malformed network input in parsers (#1340)

## Summary
- validate handshake metadata field lengths before fixed-size reads
- reject truncated multicast advertisements before slicing the hash
payload
- add regression tests for malformed and truncated input

## Why
Both parsers currently trust length information from the incoming
payload a bit too much. Malformed network input can reach fixed-size
reads/slices and panic the process instead of being rejected cleanly.

## Testing
- go test ./...
This commit is contained in:
Alex Melan
2026-04-11 14:04:12 +03:00
committed by GitHub
parent 2527290bfd
commit bc72b106b7
4 changed files with 126 additions and 11 deletions

View File

@@ -118,26 +118,41 @@ func (m *version_metadata) decode(r io.Reader, password []byte) error {
for len(bs) >= 4 {
op := binary.BigEndian.Uint16(bs[:2])
oplen := binary.BigEndian.Uint16(bs[2:4])
if bs = bs[4:]; len(bs) < int(oplen) {
break
oplen := int(binary.BigEndian.Uint16(bs[2:4]))
if bs = bs[4:]; len(bs) < oplen {
return ErrHandshakeInvalidLength
}
field := bs[:oplen]
switch op {
case metaVersionMajor:
m.majorVer = binary.BigEndian.Uint16(bs[:2])
if len(field) != 2 {
return ErrHandshakeInvalidLength
}
m.majorVer = binary.BigEndian.Uint16(field)
case metaVersionMinor:
m.minorVer = binary.BigEndian.Uint16(bs[:2])
if len(field) != 2 {
return ErrHandshakeInvalidLength
}
m.minorVer = binary.BigEndian.Uint16(field)
case metaPublicKey:
m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
copy(m.publicKey, bs[:ed25519.PublicKeySize])
if len(field) != ed25519.PublicKeySize {
return ErrHandshakeInvalidLength
}
m.publicKey = append(m.publicKey[:0], field...)
case metaPriority:
m.priority = bs[0]
if len(field) != 1 {
return ErrHandshakeInvalidLength
}
m.priority = field[0]
}
bs = bs[oplen:]
}
if len(bs) != 0 {
return ErrHandshakeInvalidLength
}
hasher, err := blake2b.New512(password)
if err != nil {

View File

@@ -3,8 +3,11 @@ package core
import (
"bytes"
"crypto/ed25519"
"encoding/binary"
"reflect"
"testing"
"golang.org/x/crypto/blake2b"
)
func TestVersionPasswordAuth(t *testing.T) {
@@ -76,3 +79,78 @@ func TestVersionRoundtrip(t *testing.T) {
}
}
}
func TestVersionDecodeRejectsMalformedFieldLengths(t *testing.T) {
password := []byte("pw")
for _, tt := range []struct {
name string
op uint16
field []byte
}{
{name: "major short", op: metaVersionMajor, field: []byte{1}},
{name: "minor short", op: metaVersionMinor, field: []byte{1}},
{name: "public key short", op: metaPublicKey, field: []byte{1}},
{name: "priority empty", op: metaPriority, field: nil},
} {
t.Run(tt.name, func(t *testing.T) {
msg := malformedVersionHandshake(t, tt.op, tt.field, password)
var decoded version_metadata
if err := decoded.decode(bytes.NewReader(msg), password); err != ErrHandshakeInvalidLength {
t.Fatalf("expected %q, got %v", ErrHandshakeInvalidLength, err)
}
})
}
}
func TestVersionDecodeRejectsTrailingBytes(t *testing.T) {
password := []byte("pw")
pk, sk, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
hasher, err := blake2b.New512(password)
if err != nil {
t.Fatal(err)
}
if _, err = hasher.Write(pk); err != nil {
t.Fatal(err)
}
sig := ed25519.Sign(sk, hasher.Sum(nil))
body := append([]byte{1, 2, 3}, sig...)
msg := append([]byte{'m', 'e', 't', 'a', 0, 0}, body...)
binary.BigEndian.PutUint16(msg[4:6], uint16(len(body)))
var decoded version_metadata
if err := decoded.decode(bytes.NewReader(msg), password); err != ErrHandshakeInvalidLength {
t.Fatalf("expected %q, got %v", ErrHandshakeInvalidLength, err)
}
}
func malformedVersionHandshake(t *testing.T, op uint16, field []byte, password []byte) []byte {
t.Helper()
pk, sk, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatal(err)
}
hasher, err := blake2b.New512(password)
if err != nil {
t.Fatal(err)
}
if _, err = hasher.Write(pk); err != nil {
t.Fatal(err)
}
sig := ed25519.Sign(sk, hasher.Sum(nil))
body := make([]byte, 0, 4+len(field)+len(sig))
body = binary.BigEndian.AppendUint16(body, op)
body = binary.BigEndian.AppendUint16(body, uint16(len(field)))
body = append(body, field...)
body = append(body, sig...)
msg := append([]byte{'m', 'e', 't', 'a', 0, 0}, body...)
binary.BigEndian.PutUint16(msg[4:6], uint16(len(body)))
return msg
}