Skip to content

Commit a00ebd7

Browse files
urvisavlaclaude
andcommitted
decoder: raise default max depth to 1500 and add DecodeUnlimitedDepth
The previous default decoding depth of 250 was too shallow for some legitimately deep XDR. Raise the default to 1500 for untrusted, user-supplied input, and add a DecodeUnlimitedDepth sentinel (math.MaxUint) so callers decoding trusted XDR emitted by stellar-core can disable the limit entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a87d4d0 commit a00ebd7

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

xdr3/decode.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ const maxInt32 = math.MaxInt32
3232
var errMaxSlice = "data exceeds max slice limit"
3333
var errIODecode = "%s while decoding %d bytes"
3434

35-
// DecodeDefaultMaxDepth is the default maximum decoding depth
36-
const DecodeDefaultMaxDepth = 250
35+
// DecodeDefaultMaxDepth is the default maximum decoding depth.
36+
const DecodeDefaultMaxDepth = 1500
37+
38+
// DecodeUnlimitedDepth disables the maximum decoding depth limit. Only use it
39+
// for trusted input.
40+
const DecodeUnlimitedDepth = uint(math.MaxUint)
3741

3842
// MaxPrealloc is the maximum number of elements pre-allocated when decoding
3943
// variable-length arrays. Arrays larger than this are grown incrementally via
@@ -45,6 +49,7 @@ type DecodeOptions struct {
4549
// MaxDepth is the maximum decoding depth (i.e. maximum nesting of data structures).
4650
// It prevents infinite recursions in cyclic datastructures and determines the maximum callstack growth.
4751
// If set to 0, DecodeDefaultMaxDepth will be used.
52+
// Set it to DecodeUnlimitedDepth to disable the limit.
4853
MaxDepth uint
4954

5055
// MaxInputLen sets the maximum input size. It is used by the decoder to sanity-check

xdr3/decode_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,41 @@ func TestDecodeMaxDepth(t *testing.T) {
11571157
assertError(t, "", err, &UnmarshalError{ErrorCode: ErrMaxDecodingDepth})
11581158
}
11591159

1160+
// linkedNode builds arbitrarily deep nesting for depth-limit tests.
1161+
type linkedNode struct {
1162+
Next *linkedNode
1163+
}
1164+
1165+
func buildLinkedChain(depth int) *linkedNode {
1166+
var head *linkedNode
1167+
for i := 0; i < depth; i++ {
1168+
head = &linkedNode{Next: head}
1169+
}
1170+
return head
1171+
}
1172+
1173+
func TestDecodeUnlimitedDepth(t *testing.T) {
1174+
depth := DecodeDefaultMaxDepth + 100
1175+
var buf bytes.Buffer
1176+
if _, err := Marshal(&buf, buildLinkedChain(depth)); err != nil {
1177+
t.Fatalf("unexpected marshal error: %v", err)
1178+
}
1179+
1180+
// Default limit rejects the deeply nested input.
1181+
bufCopy := buf
1182+
var s linkedNode
1183+
_, err := NewDecoder(&bufCopy).Decode(&s)
1184+
assertError(t, "", err, &UnmarshalError{ErrorCode: ErrMaxDecodingDepth})
1185+
1186+
// DecodeUnlimitedDepth decodes it.
1187+
bufCopy = buf
1188+
var s2 linkedNode
1189+
_, err = NewDecoderWithOptions(&bufCopy, DecodeOptions{MaxDepth: DecodeUnlimitedDepth}).Decode(&s2)
1190+
if err != nil {
1191+
t.Fatalf("unexpected error decoding with unlimited depth: %v", err)
1192+
}
1193+
}
1194+
11601195
func TestDecodeMaxAllocationCheck_ImplicitLenReader(t *testing.T) {
11611196
var buf bytes.Buffer
11621197
_, err := Marshal(&buf, "thisstringis23charslong")

0 commit comments

Comments
 (0)