Skip to content

Commit d736ed9

Browse files
perf: use fixed buffer
1 parent ce41bde commit d736ed9

9 files changed

Lines changed: 830 additions & 15 deletions

binary/decoder.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ type Decoder struct {
116116
currentFieldOpt option
117117

118118
encoding Encoding
119+
120+
// maxSliceLen caps the number of elements a wire-declared slice length
121+
// prefix is allowed to claim before MakeSlice is called. Zero means
122+
// unlimited (backward compatible). Non-zero callers typically set this
123+
// when parsing untrusted input (e.g. RPC/WS payloads) to bound the
124+
// allocation that can result from a malicious length prefix.
125+
//
126+
// The natural bound "l <= Remaining()" is already enforced — but it
127+
// treats every element as taking at least 1 wire byte, so a []BigStruct
128+
// where BigStruct is 1 KiB in memory can still produce a 1 KiB * l
129+
// allocation from only l wire bytes. maxSliceLen lets the caller cap
130+
// the element count directly.
131+
maxSliceLen int
132+
133+
// maxMapLen is the analogous cap for map length prefixes. Unlike
134+
// slices, maps historically had no bound at all: a length of 2^32
135+
// would run 2^32 SetMapIndex iterations. With maxMapLen set, or by
136+
// virtue of the Remaining()/2 lower bound always enforced now, the
137+
// decoder fails fast instead.
138+
maxMapLen int
119139
}
120140

121141
// Reset resets the decoder to decode a new message.
@@ -152,6 +172,116 @@ func (dec *Decoder) SetEncoding(enc Encoding) {
152172
dec.encoding = enc
153173
}
154174

175+
// SetMaxSliceLen sets a hard cap on wire-declared slice lengths. A length
176+
// prefix that claims more than n elements makes Decode fail with an error
177+
// before any MakeSlice is called. Pass 0 to disable (unlimited, the
178+
// default).
179+
//
180+
// Use this when decoding untrusted input. A safe starting value is an
181+
// application-specific bound, e.g. 256 for Solana transaction account
182+
// lists or 1024 for instruction data blobs.
183+
func (dec *Decoder) SetMaxSliceLen(n int) *Decoder {
184+
dec.maxSliceLen = n
185+
return dec
186+
}
187+
188+
// SetMaxMapLen sets a hard cap on wire-declared map lengths. See
189+
// SetMaxSliceLen for rationale. Defaults to 0 (unlimited).
190+
func (dec *Decoder) SetMaxMapLen(n int) *Decoder {
191+
dec.maxMapLen = n
192+
return dec
193+
}
194+
195+
// MaxSliceLen returns the configured slice-length cap, or 0 for unlimited.
196+
func (dec *Decoder) MaxSliceLen() int { return dec.maxSliceLen }
197+
198+
// MaxMapLen returns the configured map-length cap, or 0 for unlimited.
199+
func (dec *Decoder) MaxMapLen() int { return dec.maxMapLen }
200+
201+
// ErrSliceLenTooLarge is returned when a decoded slice length prefix
202+
// exceeds the caller-configured MaxSliceLen cap or is negative. When the
203+
// length merely overruns the wire buffer (i.e. there are not enough
204+
// bytes left to decode l elements), the decoder returns
205+
// io.ErrUnexpectedEOF instead to preserve backward compatibility with
206+
// error-handling code that has long keyed off of it.
207+
var ErrSliceLenTooLarge = errors.New("decode: slice length exceeds bound")
208+
209+
// ErrMapLenTooLarge is returned when a decoded map length prefix exceeds
210+
// the MaxMapLen cap or is negative. As with ErrSliceLenTooLarge, the
211+
// "not enough bytes" case returns io.ErrUnexpectedEOF.
212+
var ErrMapLenTooLarge = errors.New("decode: map length exceeds bound")
213+
214+
// checkSliceLen validates a wire-declared slice length before it is used
215+
// to allocate a slice. elemMinSize is the minimum number of wire bytes a
216+
// single element must consume — pass 1 for variable-size element types
217+
// (strings, nested slices, general structs), or the exact fixed size for
218+
// PoD elements. Returns ErrSliceLenTooLarge for pathological inputs
219+
// (negative length, cap violation), or io.ErrUnexpectedEOF when the
220+
// claimed payload simply won't fit in Remaining() bytes.
221+
//
222+
// Uses int64 arithmetic so l * elemMinSize cannot wrap on 32-bit hosts.
223+
func (dec *Decoder) checkSliceLen(l, elemMinSize int) error {
224+
if l < 0 {
225+
return fmt.Errorf("%w: negative length %d", ErrSliceLenTooLarge, l)
226+
}
227+
if dec.maxSliceLen > 0 && l > dec.maxSliceLen {
228+
return fmt.Errorf("%w: length %d > MaxSliceLen=%d", ErrSliceLenTooLarge, l, dec.maxSliceLen)
229+
}
230+
if elemMinSize <= 0 {
231+
elemMinSize = 1
232+
}
233+
if int64(l)*int64(elemMinSize) > int64(dec.Remaining()) {
234+
return io.ErrUnexpectedEOF
235+
}
236+
return nil
237+
}
238+
239+
// sliceElemMinWireSize returns a conservative lower bound on how many wire
240+
// bytes a single element of the given type must consume. Used by
241+
// checkSliceLen to tighten the "wire length * elem >= allocation" check
242+
// for homogeneous fixed-size element kinds. For variable-size elements
243+
// (structs, strings, nested slices) it returns 1 — the true lower bound
244+
// without knowing the concrete wire layout.
245+
//
246+
// Note: this is *wire* size, not Go memory size. For a type alias like
247+
// `type PublicKey [32]byte` the wire form is 32 bytes regardless of how
248+
// the Go type is declared.
249+
func sliceElemMinWireSize(t reflect.Type) int {
250+
switch t.Kind() {
251+
case reflect.Uint8, reflect.Int8, reflect.Bool:
252+
return 1
253+
case reflect.Uint16, reflect.Int16:
254+
return TypeSizeUint16
255+
case reflect.Uint32, reflect.Int32, reflect.Float32:
256+
return TypeSizeUint32
257+
case reflect.Uint64, reflect.Int64, reflect.Float64:
258+
return TypeSizeUint64
259+
case reflect.Array:
260+
// [N]T with T fixed-size becomes N * minWireSize(T). Recurse.
261+
per := sliceElemMinWireSize(t.Elem())
262+
return t.Len() * per
263+
default:
264+
return 1
265+
}
266+
}
267+
268+
// checkMapLen validates a wire-declared map length before MakeMap /
269+
// SetMapIndex loops run. Each entry consumes at least two wire bytes
270+
// (one for key, one for value) so Remaining()/2 is the natural upper
271+
// bound in addition to the caller's MaxMapLen cap.
272+
func (dec *Decoder) checkMapLen(l int) error {
273+
if l < 0 {
274+
return fmt.Errorf("%w: negative length %d", ErrMapLenTooLarge, l)
275+
}
276+
if dec.maxMapLen > 0 && l > dec.maxMapLen {
277+
return fmt.Errorf("%w: length %d > MaxMapLen=%d", ErrMapLenTooLarge, l, dec.maxMapLen)
278+
}
279+
if int64(l)*2 > int64(dec.Remaining()) {
280+
return io.ErrUnexpectedEOF
281+
}
282+
return nil
283+
}
284+
155285
func NewBinDecoder(data []byte) *Decoder {
156286
return NewDecoderWithEncoding(data, EncodingBin)
157287
}

binary/decoder_bin.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package bin
2020
import (
2121
"encoding/binary"
2222
"fmt"
23-
"io"
2423
"reflect"
2524

2625
"go.uber.org/zap"
@@ -189,8 +188,8 @@ func (dec *Decoder) decodeBin(rv reflect.Value, opt option) (err error) {
189188
zlog.Debug("reading slice", zap.Int("len", l), typeField("type", rv))
190189
}
191190

192-
if l > dec.Remaining() {
193-
return io.ErrUnexpectedEOF
191+
if err := dec.checkSliceLen(l, sliceElemMinWireSize(rv.Type().Elem())); err != nil {
192+
return err
194193
}
195194

196195
switch k := rv.Type().Elem().Kind(); k {
@@ -222,6 +221,9 @@ func (dec *Decoder) decodeBin(rv reflect.Value, opt option) (err error) {
222221
if err != nil {
223222
return err
224223
}
224+
if err := dec.checkMapLen(l); err != nil {
225+
return err
226+
}
225227
if l == 0 {
226228
// If the map has no content, keep it nil.
227229
return nil

binary/decoder_borsh.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package bin
2020
import (
2121
"errors"
2222
"fmt"
23-
"io"
2423
"reflect"
2524

2625
"go.uber.org/zap"
@@ -230,8 +229,8 @@ func (dec *Decoder) decodeBorsh(rv reflect.Value, opt option) (err error) {
230229
// Empty slices are left nil
231230
return
232231
}
233-
if l > dec.Remaining() {
234-
return io.ErrUnexpectedEOF
232+
if err := dec.checkSliceLen(l, sliceElemMinWireSize(rv.Type().Elem())); err != nil {
233+
return err
235234
}
236235

237236
switch k := rv.Type().Elem().Kind(); k {
@@ -266,6 +265,9 @@ func (dec *Decoder) decodeBorsh(rv reflect.Value, opt option) (err error) {
266265
// If the map has no content, keep it nil.
267266
return nil
268267
}
268+
if err := dec.checkMapLen(int(l)); err != nil {
269+
return err
270+
}
269271
rv.Set(reflect.MakeMap(rt))
270272
mapOpt := option{Order: opt.Order}
271273
for i := 0; i < int(l); i++ {

0 commit comments

Comments
 (0)