Skip to content

Commit cd59f2f

Browse files
Merge pull request from GHSA-3669-72x9-r9p3
* fixes the security advisory by limiting the slice creation based on configurable maxSize * address review comment
1 parent 180f71e commit cd59f2f

File tree

2 files changed

+137
-6
lines changed

2 files changed

+137
-6
lines changed

decoder.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,21 @@ import (
1212
"strings"
1313
)
1414

15+
const (
16+
defaultMaxSize = 16000
17+
)
18+
1519
// NewDecoder returns a new Decoder.
1620
func NewDecoder() *Decoder {
17-
return &Decoder{cache: newCache()}
21+
return &Decoder{cache: newCache(), maxSize: defaultMaxSize}
1822
}
1923

2024
// Decoder decodes values from a map[string][]string to a struct.
2125
type Decoder struct {
2226
cache *cache
2327
zeroEmpty bool
2428
ignoreUnknownKeys bool
29+
maxSize int
2530
}
2631

2732
// SetAliasTag changes the tag used to locate custom field aliases.
@@ -54,6 +59,13 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) {
5459
d.ignoreUnknownKeys = i
5560
}
5661

62+
// MaxSize limits the size of slices for URL nested arrays or object arrays.
63+
// Choose MaxSize carefully; large values may create many zero-value slice elements.
64+
// Example: "items.100000=apple" would create a slice with 100,000 empty strings.
65+
func (d *Decoder) MaxSize(size int) {
66+
d.maxSize = size
67+
}
68+
5769
// RegisterConverter registers a converter function for a custom type.
5870
func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
5971
d.cache.registerConverter(value, converterFunc)
@@ -302,6 +314,10 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
302314
// Slice of structs. Let's go recursive.
303315
if len(parts) > 1 {
304316
idx := parts[0].index
317+
// a defensive check to avoid creating a large slice based on user input index
318+
if idx > d.maxSize {
319+
return fmt.Errorf("%v index %d is larger than the configured maxSize %d", v.Kind(), idx, d.maxSize)
320+
}
305321
if v.IsNil() || v.Len() < idx+1 {
306322
value := reflect.MakeSlice(t, idx+1, idx+1)
307323
if v.Len() < idx+1 {

decoder_test.go

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,7 +2063,7 @@ type S24 struct {
20632063

20642064
type S24e struct {
20652065
*S24
2066-
F2 string `schema:"F2"`
2066+
F2 string `schema:"F2"`
20672067
}
20682068

20692069
func TestUnmarshallToEmbeddedNoData(t *testing.T) {
@@ -2074,13 +2074,14 @@ func TestUnmarshallToEmbeddedNoData(t *testing.T) {
20742074
s := &S24e{}
20752075

20762076
decoder := NewDecoder()
2077-
err := decoder.Decode(s, data);
2078-
2077+
err := decoder.Decode(s, data)
2078+
20792079
expectedErr := `schema: invalid path "F3"`
20802080
if err.Error() != expectedErr {
20812081
t.Fatalf("got %q, want %q", err, expectedErr)
20822082
}
20832083
}
2084+
20842085
type S25ee struct {
20852086
F3 string `schema:"F3"`
20862087
}
@@ -2095,14 +2096,13 @@ type S25 struct {
20952096
F1 string `schema:"F1"`
20962097
}
20972098

2098-
func TestDoubleEmbedded(t *testing.T){
2099+
func TestDoubleEmbedded(t *testing.T) {
20992100
data := map[string][]string{
21002101
"F1": {"raw a"},
21012102
"F2": {"raw b"},
21022103
"F3": {"raw c"},
21032104
}
21042105

2105-
21062106
s := S25{}
21072107
decoder := NewDecoder()
21082108

@@ -2412,3 +2412,118 @@ func TestDefaultsAreNotSupportedForStructsAndStructSlices(t *testing.T) {
24122412
t.Errorf("decoding should fail with error msg %s got %q", expected, err)
24132413
}
24142414
}
2415+
2416+
func TestDecoder_MaxSize(t *testing.T) {
2417+
t.Parallel()
2418+
2419+
type Nested struct {
2420+
Val int
2421+
NestedValues []struct {
2422+
NVal int
2423+
}
2424+
}
2425+
type NestedSlices struct {
2426+
Values []Nested
2427+
}
2428+
2429+
testcases := []struct {
2430+
name string
2431+
maxSize int
2432+
decoderInput func() (dst NestedSlices, src map[string][]string)
2433+
expectedDecoded NestedSlices
2434+
expectedErr MultiError
2435+
}{
2436+
{
2437+
name: "no error on decoding under max size",
2438+
maxSize: 10,
2439+
decoderInput: func() (dst NestedSlices, src map[string][]string) {
2440+
return dst, map[string][]string{
2441+
"Values.1.Val": {"132"},
2442+
"Values.1.NestedValues.1.NVal": {"1"},
2443+
"Values.1.NestedValues.2.NVal": {"2"},
2444+
"Values.1.NestedValues.3.NVal": {"3"},
2445+
}
2446+
},
2447+
expectedDecoded: NestedSlices{
2448+
Values: []Nested{
2449+
{
2450+
Val: 0,
2451+
NestedValues: nil,
2452+
},
2453+
{
2454+
Val: 132, NestedValues: []struct{ NVal int }{
2455+
{NVal: 0},
2456+
{NVal: 1},
2457+
{NVal: 2},
2458+
{NVal: 3},
2459+
},
2460+
},
2461+
},
2462+
},
2463+
expectedErr: nil,
2464+
},
2465+
{
2466+
name: "error on decoding above max size",
2467+
maxSize: 1,
2468+
decoderInput: func() (dst NestedSlices, src map[string][]string) {
2469+
return dst, map[string][]string{
2470+
"Values.1.Val": {"132"},
2471+
"Values.1.NestedValues.1.NVal": {"1"},
2472+
"Values.1.NestedValues.2.NVal": {"2"},
2473+
"Values.1.NestedValues.3.NVal": {"3"},
2474+
}
2475+
},
2476+
expectedErr: MultiError{
2477+
"Values.1.NestedValues.2.NVal": errors.New("slice index 2 is larger than the configured maxSize 1"),
2478+
"Values.1.NestedValues.3.NVal": errors.New("slice index 3 is larger than the configured maxSize 1"),
2479+
},
2480+
},
2481+
}
2482+
2483+
for _, tc := range testcases {
2484+
tc := tc
2485+
t.Run(tc.name, func(t *testing.T) {
2486+
t.Parallel()
2487+
dec := NewDecoder()
2488+
dec.MaxSize(tc.maxSize)
2489+
dst, src := tc.decoderInput()
2490+
err := dec.Decode(&dst, src)
2491+
2492+
if tc.expectedErr != nil {
2493+
var gotErr MultiError
2494+
if !errors.As(err, &gotErr) {
2495+
t.Errorf("decoder error is not of type %T", gotErr)
2496+
}
2497+
if !reflect.DeepEqual(gotErr, tc.expectedErr) {
2498+
t.Errorf("expected %v, got %v", tc.expectedErr, gotErr)
2499+
}
2500+
} else {
2501+
if !reflect.DeepEqual(dst, tc.expectedDecoded) {
2502+
t.Errorf("expected %v, got %v", tc.expectedDecoded, dst)
2503+
}
2504+
}
2505+
})
2506+
}
2507+
}
2508+
2509+
func TestDecoder_SetMaxSize(t *testing.T) {
2510+
2511+
t.Run("default maxsize should be equal to given constant", func(t *testing.T) {
2512+
t.Parallel()
2513+
dec := NewDecoder()
2514+
if !reflect.DeepEqual(dec.maxSize, defaultMaxSize) {
2515+
t.Errorf("unexpected default max size")
2516+
}
2517+
})
2518+
2519+
t.Run("configured maxsize should be set properly", func(t *testing.T) {
2520+
t.Parallel()
2521+
configuredMaxSize := 50
2522+
limitedMaxSizeDecoder := NewDecoder()
2523+
limitedMaxSizeDecoder.MaxSize(configuredMaxSize)
2524+
if !reflect.DeepEqual(limitedMaxSizeDecoder.maxSize, configuredMaxSize) {
2525+
t.Errorf("invalid decoder maxsize, expected: %d, got: %d",
2526+
configuredMaxSize, limitedMaxSizeDecoder.maxSize)
2527+
}
2528+
})
2529+
}

0 commit comments

Comments
 (0)