Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

decoder: sanity-check the length of slices/maps before allocating them #19

Merged
merged 5 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 119 additions & 24 deletions xdr3/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,36 @@ import (
"time"
)

const maxInt32 = int(^uint32(0) >> 1)
const maxInt32 = math.MaxInt32

var errMaxSlice = "data exceeds max slice limit"
var errIODecode = "%s while decoding %d bytes"

// DecodeDefaultMaxDepth is the default maximum decoding depth
const DecodeDefaultMaxDepth = 200

// DecodeOptions configures how Decoding is done.
type DecodeOptions struct {
// MaxDepth is the maximum decoding depth (i.e. maximum nesting of data structures).
// It prevents infinite recursions in cyclic datastructures and determines the maximum callstack growth.
// If set to 0, DecodeDefaultMaxDepth will be used.
MaxDepth uint

// MaxInputLen sets the maximum input size. It is used by the decoder to sanity-check
// allocation sizes and avoid heap explosions from doctored inputs.
//
// If set to 0, the decoder will try to figure out the input size by checking whether
// the provided io.Reader implements Len() (e.g. strings.Reader, bytes.Reader and bytes.Buffer do).
// Otherwise, no sanity checks will be done.
MaxInputLen int
}

// DefaultDecodeOptions are the default decoding options.
var DefaultDecodeOptions = DecodeOptions{
MaxDepth: DecodeDefaultMaxDepth,
MaxInputLen: 0,
}

/*
Unmarshal parses XDR-encoded data into the value pointed to by v reading from
reader r and returning the total number of bytes read. An addressable pointer
Expand Down Expand Up @@ -66,7 +88,7 @@ by v and performs a mapping of underlying XDR types to Go types as follows:
Notes and Limitations:

- Automatic unmarshalling of variable and fixed-length arrays of uint8s
requires a special struct tag `xdropaque:"false"` since byte slices
requires a special struct tag xdropaque:"false" since byte slices
and byte arrays are assumed to be opaque data and byte is a Go alias
for uint8 thus indistinguishable under reflection
- Cyclic data structures are not supported and will result in ErrMaxDecodingDepth errors
Expand All @@ -78,23 +100,81 @@ potential issues are unsupported Go types, attempting to decode a value which is
too large to fit into a specified Go type, and exceeding max slice limitations.
*/
func Unmarshal(r io.Reader, v interface{}) (int, error) {
d := Decoder{r: r}
d := NewDecoder(r)
return d.Decode(v)
}

// UnmarshalWithOptions works like Unmarshal but accepts decoding options.
func UnmarshalWithOptions(r io.Reader, v interface{}, options DecodeOptions) (int, error) {
d := NewDecoderWithOptions(r, options)
return d.Decode(v)
}

type lenLeft interface {
Len() int
}

// A Decoder wraps an io.Reader that is expected to provide an XDR-encoded byte
// stream and provides several exposed methods to manually decode various XDR
// primitives without relying on reflection. The NewDecoder function can be
// used to get a new Decoder directly.
//
// Typically, Unmarshal should be used instead of manual decoding. A Decoder
// is exposed so it is possible to perform manual decoding should it be
// is exposed, so it is possible to perform manual decoding should it be
// necessary in complex scenarios where automatic reflection-based decoding
// won't work.
type Decoder struct {
// used to minimize heap allocations during decoding
scratchBuf [8]byte
r io.Reader
l lenLeft
maxDepth uint
}

// readerLenWrapper wraps a reader an initial length and provides a Len() method indicating
// how much input is left
type readerLenWrapper struct {
inner io.Reader
readCount int
initialLen int
}

func (l *readerLenWrapper) Len() int {
return l.initialLen - l.readCount
}

func (l *readerLenWrapper) Read(p []byte) (int, error) {
n, err := l.inner.Read(p)
if n > 0 {
l.readCount += n
}
return n, err
}

// NewDecoder returns a Decoder that can be used to manually decode XDR data
// from a provided reader. Typically, Unmarshal should be used instead of
// manually creating a Decoder.
func NewDecoder(r io.Reader) *Decoder {
return NewDecoderWithOptions(r, DefaultDecodeOptions)
}

// NewDecoderWithOptions works like NewDecoder but allows supplying decoding options.
func NewDecoderWithOptions(r io.Reader, options DecodeOptions) *Decoder {
maxDepth := options.MaxDepth
if maxDepth < 1 {
maxDepth = DecodeDefaultMaxDepth
}
if l, ok := r.(lenLeft); ok {
return &Decoder{r: r, l: l, maxDepth: maxDepth}
}
if options.MaxInputLen > 0 {
rlw := &readerLenWrapper{
inner: r,
initialLen: options.MaxInputLen,
}
return &Decoder{r: rlw, l: rlw, maxDepth: maxDepth}
}
return &Decoder{r: r, l: nil, maxDepth: options.MaxDepth}
}

// DecodeInt treats the next 4 bytes as an XDR encoded integer and returns the
Expand Down Expand Up @@ -383,6 +463,7 @@ func (d *Decoder) DecodeOpaque(maxSize int) ([]byte, int, error) {
return nil, n, err
}

maxSize = d.mergeInputLenAndMaxSize(maxSize)
if maxSize == 0 {
maxSize = maxInt32
}
Expand Down Expand Up @@ -422,6 +503,7 @@ func (d *Decoder) DecodeString(maxSize int) (string, int, error) {
return "", n, err
}

maxSize = d.mergeInputLenAndMaxSize(maxSize)
if maxSize == 0 {
maxSize = maxInt32
}
Expand Down Expand Up @@ -477,7 +559,7 @@ func (d *Decoder) decodeFixedArray(v reflect.Value, ignoreOpaque bool, maxDepth
// elements of the same type as the array represented by the reflection value.
// The number of elements is obtained by first decoding the unsigned integer
// element count. Then each element is decoded into the passed array. The
// ignoreOpaque flag controls whether or not uint8 (byte) elements should be
// ignoreOpaque flag controls whether uint8 (byte) elements should be
// decoded individually or as a variable sequence of opaque data. It returns
// the number of bytes actually read.
//
Expand All @@ -495,6 +577,7 @@ func (d *Decoder) decodeArray(v reflect.Value, ignoreOpaque bool, maxSize int, m
return n, err
}

maxSize = d.mergeInputLenAndMaxSize(maxSize)
if maxSize == 0 {
maxSize = maxInt32
}
Expand Down Expand Up @@ -591,7 +674,8 @@ func (d *Decoder) decodeUnion(v reflect.Value, maxDepth uint) (int, error) {

vv := v.FieldByName(arm)

vv.Set(reflect.New(vv.Type().Elem()))
vvet := vv.Type().Elem()
vv.Set(reflect.New(vvet))

field, ok := v.Type().FieldByName(arm)
if !ok {
Expand Down Expand Up @@ -621,8 +705,8 @@ func (d *Decoder) decodeUnion(v reflect.Value, maxDepth uint) (int, error) {

// decodeStruct treats the next bytes as a series of XDR encoded elements
// of the same type as the exported fields of the struct represented by the
// passed reflection value. Pointers are automatically indirected and
// allocated as necessary. It returns the the number of bytes actually read.
// passed reflection value. Pointers are automatically indirected and
// allocated as necessary. It returns the number of bytes actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the elements.
Expand Down Expand Up @@ -724,6 +808,11 @@ func (d *Decoder) decodeMap(v reflect.Value, maxDepth uint) (int, error) {
if err != nil {
return n, err
}
if left, ok := d.InputLen(); ok {
if uint(left) < uint(dataLen) {
return n, unmarshalError("decodeMap", ErrOverflow, errMaxSlice, dataLen, nil)
}
}

// Allocate storage for the underlying map if needed.
vt := v.Type()
Expand Down Expand Up @@ -756,7 +845,7 @@ func (d *Decoder) decodeMap(v reflect.Value, maxDepth uint) (int, error) {
// decodeInterface examines the interface represented by the passed reflection
// value to detect whether it is an interface that can be decoded into and
// if it is, extracts the underlying value to pass back into the decode function
// for decoding according to its type. It returns the the number of bytes
// for decoding according to its type. It returns the number of bytes
// actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
Expand Down Expand Up @@ -786,6 +875,15 @@ func (d *Decoder) decodeInterface(v reflect.Value, maxDepth uint) (int, error) {
return d.decode(ve, 0, maxDepth)
}

func (d *Decoder) mergeInputLenAndMaxSize(maxSize int) int {
if left, ok := d.InputLen(); ok {
if maxSize == 0 || left < maxSize {
return left
}
}
return maxSize
}

// decode is the main workhorse for unmarshalling via reflection. It uses
// the passed reflection value to choose the XDR primitives to decode from
// the encapsulated reader. It is a recursive function,
Expand Down Expand Up @@ -993,7 +1091,7 @@ func setPtrToNil(v *reflect.Value) error {
return nil
}

func allocPtrIfNil(v *reflect.Value) error {
func (d *Decoder) allocPtrIfNil(v *reflect.Value) error {
if v.Kind() != reflect.Ptr {
msg := fmt.Sprintf("value is not a pointer: '%v'",
v.Type().String())
Expand All @@ -1010,7 +1108,8 @@ func allocPtrIfNil(v *reflect.Value) error {
return err
}
if isNil {
v.Set(reflect.New(v.Type().Elem()))
vet := v.Type().Elem()
v.Set(reflect.New(vet))
}
return nil
}
Expand All @@ -1030,7 +1129,7 @@ func (d *Decoder) decodePtr(v reflect.Value, maxDepth uint) (int, error) {
return n, err
}

if err = allocPtrIfNil(&v); err != nil {
if err = d.allocPtrIfNil(&v); err != nil {
return n, err
}

Expand All @@ -1042,7 +1141,7 @@ func (d *Decoder) decodePtr(v reflect.Value, maxDepth uint) (int, error) {
// otherwise returns the passed value.
func (d *Decoder) indirectIfPtr(v reflect.Value) (reflect.Value, error) {
if v.Kind() == reflect.Ptr {
err := allocPtrIfNil(&v)
err := d.allocPtrIfNil(&v)
return v.Elem(), err
}
return v, nil
Expand All @@ -1053,11 +1152,6 @@ func (d *Decoder) indirectIfPtr(v reflect.Value) (reflect.Value, error) {
// data instead of a user-supplied reader. See the Unmarhsal documentation for
// specifics. Decode(v) is equivalent to DecodeWithMaxDepth(v, DecodeDefaultMaxDepth)
func (d *Decoder) Decode(v interface{}) (int, error) {
return d.DecodeWithMaxDepth(v, DecodeDefaultMaxDepth)
}

// DecodeWithMaxDepth behaves like Decode, except an explicit maximum decoding depth is used
func (d *Decoder) DecodeWithMaxDepth(v interface{}, maxDepth uint) (int, error) {
if v == nil {
msg := "can't unmarshal to nil interface"
return 0, unmarshalError("Unmarshal", ErrNilInterface, msg, nil,
Expand All @@ -1078,12 +1172,13 @@ func (d *Decoder) DecodeWithMaxDepth(v interface{}, maxDepth uint) (int, error)
return 0, err
}

return d.decode(vv.Elem(), 0, maxDepth)
return d.decode(vv.Elem(), 0, d.maxDepth)
}

// NewDecoder returns a Decoder that can be used to manually decode XDR data
// from a provided reader. Typically, Unmarshal should be used instead of
// manually creating a Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
// InputLen returns the size left to read from the decoder's input if available
func (d *Decoder) InputLen() (int, bool) {
if d.l == nil {
return 0, false
}
return d.l.Len(), true
}
47 changes: 42 additions & 5 deletions xdr3/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package xdr_test

import (
"bytes"
"encoding/base64"
"fmt"
"math"
"reflect"
Expand Down Expand Up @@ -361,7 +362,7 @@ func TestUnmarshal(t *testing.T) {
// element no extra bytes, 1 map element not enough bytes for
// key, 1 map element not enough bytes for value.
{[]byte{0x00, 0x00, 0x00}, map[string]uint32{}, 3, &UnmarshalError{ErrorCode: ErrIO}},
{[]byte{0x00, 0x00, 0x00, 0x01}, map[string]uint32{}, 4, &UnmarshalError{ErrorCode: ErrIO}},
{[]byte{0x00, 0x00, 0x00, 0x01}, map[string]uint32{}, 4, &UnmarshalError{ErrorCode: ErrOverflow}},
{[]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, map[string]uint32{}, 7, &UnmarshalError{ErrorCode: ErrIO}},
{[]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x6D, 0x61, 0x70, 0x31},
map[string]uint32{}, 12, &UnmarshalError{ErrorCode: ErrIO}},
Expand Down Expand Up @@ -1143,15 +1144,51 @@ func TestDecodeMaxDepth(t *testing.T) {
}

bufCopy := buf
decoder := NewDecoder(&bufCopy)
decoder := NewDecoderWithOptions(&bufCopy, DecodeOptions{MaxDepth: 3})
var s structWithPointer
_, err = decoder.DecodeWithMaxDepth(&s, 3)
_, err = decoder.Decode(&s)
if err != nil {
t.Error("unexpected error")
}

bufCopy = buf
decoder = NewDecoder(&bufCopy)
_, err = decoder.DecodeWithMaxDepth(&s, 2)
decoder = NewDecoderWithOptions(&bufCopy, DecodeOptions{MaxDepth: 2})
_, err = decoder.Decode(&s)
assertError(t, "", err, &UnmarshalError{ErrorCode: ErrMaxDecodingDepth})
}

func TestDecodeMaxAllocationCheck_ImplicitLenReader(t *testing.T) {
var buf bytes.Buffer
_, err := Marshal(&buf, "thisstringis23charslong")
if err != nil {
t.Error("unexpected error")
}

// Reduce the buffer size so that the length of the buffer
// is shorter than the encoded XDR length
buf.Truncate(buf.Len() - 4)

decoder := NewDecoder(&buf)
var s string
_, err = decoder.Decode(&s)
assertError(t, "", err, &UnmarshalError{ErrorCode: ErrOverflow})
}

func TestDecodeMaxAllocationCheck_ExplicitLenReader(t *testing.T) {
var buf bytes.Buffer
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
_, err := Marshal(encoder, "thisstringis23charslong")
if err != nil {
t.Error("unexpected error")
}

xdrLen := base64.StdEncoding.DecodedLen(buf.Len())
// Reduce the buffer size so that the length of the buffer
// is shorter than the encoded XDR length
reducedLen := xdrLen - 4

decoder := NewDecoderWithOptions(&buf, DecodeOptions{MaxInputLen: reducedLen})
var s string
_, err = decoder.Decode(&s)
assertError(t, "", err, &UnmarshalError{ErrorCode: ErrOverflow})
}
3 changes: 2 additions & 1 deletion xdr3/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ const (
ErrNotSettable

// ErrOverflow indicates that the data in question is too large to fit
// into the corresponding Go or XDR data type. For example, an integer
// into the corresponding Go or XDR data type or that allocating it exceeds the
// maximum allocation size limit. For example, an integer
// decoded from XDR that is too large to fit into a target type of int8,
// or opaque data that exceeds the max length of a Go slice.
ErrOverflow
Expand Down
2 changes: 1 addition & 1 deletion xdr3/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TstEncode(w io.Writer) func(v reflect.Value) (int, error) {

// TstDecode creates a new Decoder for the passed reader and returns the
// internal decode function on the Decoder.
func TstDecode(r io.Reader) func(v reflect.Value, maxSize int, maxDepth uint) (int, error) {
func TstDecode(r io.Reader) func(v reflect.Value, maxLen int, maxDepth uint) (int, error) {
dec := NewDecoder(r)
return dec.decode
}