Skip to content

Commit

Permalink
Merge pull request #32 from monoculum/len
Browse files Browse the repository at this point in the history
Limit the array size that gets allocated
  • Loading branch information
emilgpa authored Sep 5, 2020
2 parents 6f3cce7 + faccc45 commit d7a8fbd
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 10 deletions.
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
ErrCodeUnknownType // Unknown type.
ErrCodeUnknownField // No struct field for passed parameter (will never be used if IgnoreUnknownKeys is set).
ErrCodeRange // Number is out of range (e.g. parsing 300 in uint8 would overflow).
ErrCodeArraySize // Array longer than MaxSize.
)

type Error struct {
Expand Down
49 changes: 39 additions & 10 deletions formam.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package formam

import (
"encoding"
"errors"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)

const tagName = "formam"
// Default options.
const (
tagName = "formam"
maxSize = 16000
)

// pathMap holds the values of a map with its key and values correspondent
type pathMap struct {
Expand Down Expand Up @@ -79,6 +84,13 @@ type DecoderOptions struct {
// Ignore unknown form fields. By default unknown fields are an error
// (although all valid keys will still be decoded).
IgnoreUnknownKeys bool

// The maximum array size that formam will create. This is limited to
// prevent malicious input to create huge arrays to starve the server of
// memory.
//
// The default is 16,000; set to -1 to disable.
MaxSize int
}

// RegisterCustomType registers a functions for decoding custom types.
Expand Down Expand Up @@ -112,6 +124,9 @@ func NewDecoder(opts *DecoderOptions) *Decoder {
if dec.opts.TagName == "" {
dec.opts.TagName = tagName
}
if dec.opts.MaxSize == 0 {
dec.opts.MaxSize = maxSize
}
return dec
}

Expand Down Expand Up @@ -139,6 +154,7 @@ func Decode(vs url.Values, dst interface{}) error {
formValues: vs,
opts: &DecoderOptions{
TagName: tagName,
MaxSize: maxSize,
},
}
return dec.init()
Expand Down Expand Up @@ -300,7 +316,10 @@ func (dec *Decoder) traverse() error {
return newError(ErrCodeArrayIndex, dec.field, dec.path, "slice index is not a number: %s", err)
}
if dec.curr.Len() <= index {
dec.expandSlice(index + 1)
err := dec.expandSlice(index + 1)
if err != nil {
return newError(ErrCodeArraySize, dec.field, dec.path, "%s", err)
}
}
dec.curr = dec.curr.Index(index)
case reflect.Map:
Expand Down Expand Up @@ -389,7 +408,10 @@ func (dec *Decoder) decode() error {
if dec.bracket == "" {
// not has index, so to decode all values in the slice
// only for slices
dec.expandSlice(len(dec.values))
err := dec.expandSlice(len(dec.values))
if err != nil {
return newError(ErrCodeArraySize, dec.field, dec.path, "%s", err)
}
if err := dec.setValues(); err != nil {
return err
}
Expand All @@ -401,7 +423,10 @@ func (dec *Decoder) decode() error {
}
// only for slices
if dec.curr.Len() <= index {
dec.expandSlice(index + 1)
err := dec.expandSlice(index + 1)
if err != nil {
return newError(ErrCodeArraySize, dec.field, dec.path, "%s", err)
}
}
dec.curr = dec.curr.Index(index)
return dec.decode()
Expand Down Expand Up @@ -558,18 +583,22 @@ func (dec *Decoder) findStructField() error {
}

// expandSlice expands the length and capacity of the current slice.
func (dec *Decoder) expandSlice(length int) {
// check if the length passed by arguments is greater
// than current length.
// If the length is not greater than current,
// then return and not expand the slice
func (dec *Decoder) expandSlice(length int) error {
// Check if the length passed by arguments is greater than the current
// length.
currLen := dec.curr.Len()
if currLen > length {
return
return nil
}

if dec.opts.MaxSize >= 0 && length > dec.opts.MaxSize {
return errors.New("array size " + strconv.Itoa(length) + " is longer than MaxSize " + strconv.Itoa(dec.opts.MaxSize))
}

n := reflect.MakeSlice(dec.curr.Type(), length, length)
reflect.Copy(n, dec.curr)
dec.curr.Set(n)
return nil
}

// setValues set the values in current slice/array
Expand Down
62 changes: 62 additions & 0 deletions formam_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"strconv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -923,3 +924,64 @@ func TestArrayIgnore(t *testing.T) {
t.Fatalf("\nout: %s\nwant: %s", out, want)
}
}

// #31
func TestArrayLength(t *testing.T) {
tests := []struct {
arrayLen int
maxSize int
wantError string
}{
{15000, 0, ""},
{15999, 0, ""},
{16000, 0, "array size 16001 is longer than MaxSize 16000"},
{8, 8, "array size 9 is longer than MaxSize 8"},

{26000, -1, ""},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
type Malicious []struct {
X string
}

vals := url.Values{
fmt.Sprintf("[%d].X", tt.arrayLen): {"yeah nah"},
}

var m Malicious
dec := formam.NewDecoder(&formam.DecoderOptions{MaxSize: tt.maxSize})
err := dec.Decode(vals, &m)

if !errorContains(err, tt.wantError) {
t.Fatalf("wrong error: %s", err)
}

if err == nil {
if len(m) != tt.arrayLen+1 {
t.Errorf("%d != %d", len(m), tt.arrayLen+1)
}
} else {
if len(m) != 0 {
t.Errorf("len(n) is not 0: %d", len(m))
}
}
})
}
}

// errorContains checks if the error message in out contains the text in
// want.
//
// This is safe when out is nil. Use an empty string for want if you want to
// test that err is nil.
func errorContains(out error, want string) bool {
if out == nil {
return want == ""
}
if want == "" {
return false
}
return strings.Contains(out.Error(), want)
}

0 comments on commit d7a8fbd

Please sign in to comment.