mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2026-05-22 22:06:29 +03:00
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:
@@ -118,26 +118,41 @@ func (m *version_metadata) decode(r io.Reader, password []byte) error {
|
|||||||
|
|
||||||
for len(bs) >= 4 {
|
for len(bs) >= 4 {
|
||||||
op := binary.BigEndian.Uint16(bs[:2])
|
op := binary.BigEndian.Uint16(bs[:2])
|
||||||
oplen := binary.BigEndian.Uint16(bs[2:4])
|
oplen := int(binary.BigEndian.Uint16(bs[2:4]))
|
||||||
if bs = bs[4:]; len(bs) < int(oplen) {
|
if bs = bs[4:]; len(bs) < oplen {
|
||||||
break
|
return ErrHandshakeInvalidLength
|
||||||
}
|
}
|
||||||
|
field := bs[:oplen]
|
||||||
switch op {
|
switch op {
|
||||||
case metaVersionMajor:
|
case metaVersionMajor:
|
||||||
m.majorVer = binary.BigEndian.Uint16(bs[:2])
|
if len(field) != 2 {
|
||||||
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
m.majorVer = binary.BigEndian.Uint16(field)
|
||||||
|
|
||||||
case metaVersionMinor:
|
case metaVersionMinor:
|
||||||
m.minorVer = binary.BigEndian.Uint16(bs[:2])
|
if len(field) != 2 {
|
||||||
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
m.minorVer = binary.BigEndian.Uint16(field)
|
||||||
|
|
||||||
case metaPublicKey:
|
case metaPublicKey:
|
||||||
m.publicKey = make(ed25519.PublicKey, ed25519.PublicKeySize)
|
if len(field) != ed25519.PublicKeySize {
|
||||||
copy(m.publicKey, bs[:ed25519.PublicKeySize])
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
m.publicKey = append(m.publicKey[:0], field...)
|
||||||
|
|
||||||
case metaPriority:
|
case metaPriority:
|
||||||
m.priority = bs[0]
|
if len(field) != 1 {
|
||||||
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
m.priority = field[0]
|
||||||
}
|
}
|
||||||
bs = bs[oplen:]
|
bs = bs[oplen:]
|
||||||
}
|
}
|
||||||
|
if len(bs) != 0 {
|
||||||
|
return ErrHandshakeInvalidLength
|
||||||
|
}
|
||||||
|
|
||||||
hasher, err := blake2b.New512(password)
|
hasher, err := blake2b.New512(password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package core
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2b"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVersionPasswordAuth(t *testing.T) {
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,14 +26,18 @@ func (m *multicastAdvertisement) MarshalBinary() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *multicastAdvertisement) UnmarshalBinary(b []byte) error {
|
func (m *multicastAdvertisement) UnmarshalBinary(b []byte) error {
|
||||||
if len(b) < ed25519.PublicKeySize+8 {
|
const headerLen = ed25519.PublicKeySize + 8
|
||||||
|
if len(b) < headerLen {
|
||||||
return fmt.Errorf("invalid multicast beacon")
|
return fmt.Errorf("invalid multicast beacon")
|
||||||
}
|
}
|
||||||
m.MajorVersion = binary.BigEndian.Uint16(b[0:2])
|
m.MajorVersion = binary.BigEndian.Uint16(b[0:2])
|
||||||
m.MinorVersion = binary.BigEndian.Uint16(b[2:4])
|
m.MinorVersion = binary.BigEndian.Uint16(b[2:4])
|
||||||
m.PublicKey = append(m.PublicKey[:0], b[4:4+ed25519.PublicKeySize]...)
|
m.PublicKey = append(m.PublicKey[:0], b[4:4+ed25519.PublicKeySize]...)
|
||||||
m.Port = binary.BigEndian.Uint16(b[4+ed25519.PublicKeySize : 6+ed25519.PublicKeySize])
|
m.Port = binary.BigEndian.Uint16(b[4+ed25519.PublicKeySize : 6+ed25519.PublicKeySize])
|
||||||
dl := binary.BigEndian.Uint16(b[6+ed25519.PublicKeySize : 8+ed25519.PublicKeySize])
|
dl := int(binary.BigEndian.Uint16(b[6+ed25519.PublicKeySize : 8+ed25519.PublicKeySize]))
|
||||||
m.Hash = append(m.Hash[:0], b[8+ed25519.PublicKeySize:8+ed25519.PublicKeySize+dl]...)
|
if len(b) < headerLen+dl {
|
||||||
|
return fmt.Errorf("invalid multicast beacon")
|
||||||
|
}
|
||||||
|
m.Hash = append(m.Hash[:0], b[headerLen:headerLen+dl]...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package multicast
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"encoding/binary"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -36,3 +37,20 @@ func TestMulticastAdvertisementRoundTrip(t *testing.T) {
|
|||||||
t.Fatalf("differences found after round-trip")
|
t.Fatalf("differences found after round-trip")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMulticastAdvertisementRejectsTruncatedHash(t *testing.T) {
|
||||||
|
pk, _, err := ed25519.GenerateKey(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, ed25519.PublicKeySize+8)
|
||||||
|
copy(b[4:], pk)
|
||||||
|
binary.BigEndian.PutUint16(b[4+ed25519.PublicKeySize:6+ed25519.PublicKeySize], 9001)
|
||||||
|
binary.BigEndian.PutUint16(b[6+ed25519.PublicKeySize:8+ed25519.PublicKeySize], 32)
|
||||||
|
|
||||||
|
var adv multicastAdvertisement
|
||||||
|
if err := adv.UnmarshalBinary(b); err == nil {
|
||||||
|
t.Fatal("expected truncated beacon to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user