perf: sync protocol and core hardening updates
This commit is contained in:
parent
152fed3b87
commit
4390ebf5a9
283 changed files with 29231 additions and 2295 deletions
263
internal/compat/layerwire/walk_limits_test.go
Normal file
263
internal/compat/layerwire/walk_limits_test.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
package layerwire
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/gotd/td/bin"
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
func TestValidateCanonicalRequestFlaggedHotPathAllocatesNothing(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
req := &tg.MessagesSendMessageRequest{
|
||||
Peer: &tg.InputPeerSelf{},
|
||||
Message: "hello",
|
||||
RandomID: 7,
|
||||
}
|
||||
if err := req.Encode(&body); err != nil {
|
||||
t.Fatalf("encode request: %v", err)
|
||||
}
|
||||
if err := ValidateCanonicalRequest(body.Buf); err != nil {
|
||||
t.Fatalf("validate request: %v", err)
|
||||
}
|
||||
if allocs := testing.AllocsPerRun(1000, func() {
|
||||
if err := ValidateCanonicalRequest(body.Buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}); allocs != 0 {
|
||||
t.Fatalf("canonical request preflight allocations = %.2f, want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCanonicalRequestVectorLimits(t *testing.T) {
|
||||
editCloseFriends := canonical.byName["contacts.editCloseFriends"]
|
||||
if editCloseFriends == nil {
|
||||
t.Fatal("contacts.editCloseFriends missing from canonical schema")
|
||||
}
|
||||
|
||||
t.Run("explicit_5000_override", func(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
body.PutID(editCloseFriends.crc)
|
||||
body.PutVectorHeader(5000)
|
||||
for i := 0; i < 5000; i++ {
|
||||
body.PutLong(int64(i))
|
||||
}
|
||||
if err := ValidateCanonicalRequest(body.Buf); err != nil {
|
||||
t.Fatalf("validate legal 5000-element close-friends request: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override_stops_at_5000", func(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
body.PutID(editCloseFriends.crc)
|
||||
body.PutVectorHeader(5001)
|
||||
err := ValidateCanonicalRequest(body.Buf)
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default_4096", func(t *testing.T) {
|
||||
getMessages := canonical.byName["messages.getMessages"]
|
||||
var body bin.Buffer
|
||||
body.PutID(getMessages.crc)
|
||||
body.PutVectorHeader(defaultMaxVectorElements + 1)
|
||||
err := ValidateCanonicalRequest(body.Buf)
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("max_int32_count_rejected_before_iteration", func(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
body.PutID(editCloseFriends.crc)
|
||||
body.PutID(vectorTypeID)
|
||||
body.PutInt32(math.MaxInt32)
|
||||
err := ValidateCanonicalRequest(body.Buf)
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateCanonicalRequestDepthLimit(t *testing.T) {
|
||||
invoke := canonical.byName["invokeWithoutUpdates"]
|
||||
leaf := canonical.byName["help.getConfig"]
|
||||
if invoke == nil || leaf == nil {
|
||||
t.Fatal("generic wrapper methods missing from canonical schema")
|
||||
}
|
||||
request := func(wrappers int) []byte {
|
||||
var body bin.Buffer
|
||||
for i := 0; i < wrappers; i++ {
|
||||
body.PutID(invoke.crc)
|
||||
}
|
||||
body.PutID(leaf.crc)
|
||||
return body.Buf
|
||||
}
|
||||
if err := ValidateCanonicalRequest(request(defaultMaxWalkDepth - 1)); err != nil {
|
||||
t.Fatalf("depth exactly %d rejected: %v", defaultMaxWalkDepth, err)
|
||||
}
|
||||
err := ValidateCanonicalRequest(request(defaultMaxWalkDepth))
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("depth %d error = %v, want ErrResourceLimit", defaultMaxWalkDepth+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLBytesSkipIsZeroCopyAndBounded(t *testing.T) {
|
||||
var encoded bin.Buffer
|
||||
encoded.PutBytes([]byte("payload"))
|
||||
fieldLen := len(encoded.Buf)
|
||||
raw := append(encoded.Copy(), 0xaa, 0xbb, 0xcc, 0xdd)
|
||||
b := &bin.Buffer{Buf: raw}
|
||||
walk := newWalkState()
|
||||
if err := walk.skipTLBytes(b, "bytes"); err != nil {
|
||||
t.Fatalf("skip bytes: %v", err)
|
||||
}
|
||||
if len(b.Buf) != 4 || &b.Buf[0] != &raw[fieldLen] {
|
||||
t.Fatalf("walker did not retain the original backing buffer")
|
||||
}
|
||||
|
||||
t.Run("per_field_budget", func(t *testing.T) {
|
||||
limited := newWalkState()
|
||||
limited.limits.maxFieldBytes = 3
|
||||
probe := &bin.Buffer{Buf: encoded.Copy()}
|
||||
err := limited.skipTLBytes(probe, "bytes")
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("aggregate_budget", func(t *testing.T) {
|
||||
limited := newWalkState()
|
||||
limited.limits.maxTotalBytes = 10
|
||||
first := &bin.Buffer{Buf: encoded.Copy()}
|
||||
if err := limited.skipTLBytes(first, "bytes"); err != nil {
|
||||
t.Fatalf("first field: %v", err)
|
||||
}
|
||||
second := &bin.Buffer{Buf: encoded.Copy()}
|
||||
err := limited.skipTLBytes(second, "bytes")
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("second error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("truncated_payload_is_malformed", func(t *testing.T) {
|
||||
importAuth := canonical.byName["auth.importAuthorization"]
|
||||
var body bin.Buffer
|
||||
body.PutID(importAuth.crc)
|
||||
body.PutLong(1)
|
||||
body.Put([]byte{5, 'a', 'b'}) // declares five bytes, lacks payload/padding
|
||||
err := ValidateCanonicalRequest(body.Buf)
|
||||
if !errors.Is(err, ErrMalformed) || errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want only ErrMalformed", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundTransformsShareWalkerBudgets(t *testing.T) {
|
||||
t.Run("canonical_alias", func(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
body.PutID(0x41d41ade) // DrKLO messages.forwardMessages alias
|
||||
body.PutUint32(0)
|
||||
body.PutID(canonical.byName["inputPeerEmpty"].crc)
|
||||
body.PutID(vectorTypeID)
|
||||
body.PutInt32(math.MaxInt32)
|
||||
_, ok, err := UpgradeInbound(0x41d41ade, &body)
|
||||
if !ok || !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("ok=%v error=%v, want matched ErrResourceLimit", ok, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("drift_body_transform", func(t *testing.T) {
|
||||
var body bin.Buffer
|
||||
body.PutID(0x2e1ee318) // DrKLO langpack.getStrings body transform
|
||||
body.PutString("en")
|
||||
body.PutID(vectorTypeID)
|
||||
body.PutInt32(math.MaxInt32)
|
||||
_, ok, err := UpgradeInbound(0x2e1ee318, &body)
|
||||
if !ok || !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("ok=%v error=%v, want matched ErrResourceLimit", ok, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("outbound_structural_transform", func(t *testing.T) {
|
||||
poll := canonical.byName["pollAnswerVoters"]
|
||||
var body bin.Buffer
|
||||
body.PutID(poll.crc)
|
||||
body.PutUint32(1 << 2)
|
||||
body.PutBytes(nil)
|
||||
body.PutInt(1)
|
||||
body.PutID(vectorTypeID)
|
||||
body.PutInt32(math.MaxInt32)
|
||||
_, err := Transcode(body.Buf, CanonicalLayer-1)
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWalkerArithmeticAndMalformedClassification(t *testing.T) {
|
||||
if defaultMaxWalkUnits != 131072 {
|
||||
t.Fatalf("default constructor/vector budget = %d, want 131072", defaultMaxWalkUnits)
|
||||
}
|
||||
if _, ok := checkedMulInt(math.MaxInt, 2); ok {
|
||||
t.Fatal("checkedMulInt accepted overflow")
|
||||
}
|
||||
if _, ok := checkedAddUint64(math.MaxUint64, 1); ok {
|
||||
t.Fatal("checkedAddUint64 accepted overflow")
|
||||
}
|
||||
|
||||
t.Run("aggregate_constructor_and_vector_units", func(t *testing.T) {
|
||||
editCloseFriends := canonical.byName["contacts.editCloseFriends"]
|
||||
var body bin.Buffer
|
||||
body.PutID(editCloseFriends.crc)
|
||||
body.PutVectorHeader(4)
|
||||
for i := 0; i < 4; i++ {
|
||||
body.PutLong(int64(i))
|
||||
}
|
||||
walk := newWalkState()
|
||||
walk.limits.maxUnits = 4 // top constructor + four elements needs five
|
||||
probe := &bin.Buffer{Buf: body.Buf}
|
||||
err := walk.skipObject(canonical, probe, 1)
|
||||
if !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want ErrResourceLimit", err)
|
||||
}
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body []byte
|
||||
}{
|
||||
{name: "empty"},
|
||||
{name: "unknown_constructor", body: []byte{1, 2, 3, 4}},
|
||||
{name: "trailing_bytes", body: append(methodIDBytes(canonical.byName["help.getConfig"].crc), 0, 0, 0, 0)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateCanonicalRequest(tt.body)
|
||||
if !errors.Is(err, ErrMalformed) || errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("error = %v, want only ErrMalformed", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func methodIDBytes(id uint32) []byte {
|
||||
var b bin.Buffer
|
||||
b.PutID(id)
|
||||
return b.Buf
|
||||
}
|
||||
|
||||
func FuzzValidateCanonicalRequest(f *testing.F) {
|
||||
f.Add(methodIDBytes(canonical.byName["help.getConfig"].crc))
|
||||
f.Add([]byte{})
|
||||
f.Add([]byte{1, 2, 3, 4})
|
||||
f.Fuzz(func(t *testing.T, body []byte) {
|
||||
err := ValidateCanonicalRequest(body)
|
||||
if err != nil && !errors.Is(err, ErrMalformed) && !errors.Is(err, ErrResourceLimit) {
|
||||
t.Fatalf("unclassified walker error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue