From 161d186b5c6bfe3f189adfc130da8dc573f595d5 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 31 May 2026 01:14:36 -0500 Subject: [PATCH 1/4] Implement recursion limit in reader Signed-off-by: HD Moore --- bson/unmarshal_test.go | 33 +++++++++++++++++++++++++++++++++ bson/value_reader.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/bson/unmarshal_test.go b/bson/unmarshal_test.go index be9d7e238e..80d1cf220a 100644 --- a/bson/unmarshal_test.go +++ b/bson/unmarshal_test.go @@ -8,6 +8,7 @@ package bson import ( "bytes" + "errors" "math/rand" "reflect" "sync" @@ -39,6 +40,38 @@ func TestUnmarshal(t *testing.T) { } } +func TestUnmarshalRejectsTooDeepDocumentNesting(t *testing.T) { + t.Helper() + + inner := bsoncore.BuildDocument(nil, bsoncore.AppendInt32Element(nil, "a", 1)) + for depth := 1; depth < maxDocumentNestingDepth+1; depth++ { + inner = bsoncore.BuildDocument(nil, bsoncore.AppendDocumentElement(nil, "a", inner)) + } + + var got M + err := Unmarshal(inner, &got) + if !errors.Is(err, errMaxDocumentNestingDepth) { + t.Fatalf("expected errMaxDocumentNestingDepth, got %v", err) + } +} + +func TestUnmarshalRejectsTooDeepArrayNesting(t *testing.T) { + t.Helper() + + inner := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendInt32Element(nil, "0", 1)) + for depth := 1; depth < maxDocumentNestingDepth+1; depth++ { + inner = bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendArrayElement(nil, "0", inner)) + } + + data := bsoncore.BuildDocument(nil, bsoncore.AppendArrayElement(nil, "a", inner)) + + var got M + err := Unmarshal(data, &got) + if !errors.Is(err, errMaxDocumentNestingDepth) { + t.Fatalf("expected errMaxDocumentNestingDepth, got %v", err) + } +} + func TestUnmarshalWithRegistry(t *testing.T) { t.Parallel() diff --git a/bson/value_reader.go b/bson/value_reader.go index e5bcc1985f..4207ac4e4d 100644 --- a/bson/value_reader.go +++ b/bson/value_reader.go @@ -60,8 +60,13 @@ type vrState struct { mode mode vType Type end int64 + depth int } +const maxDocumentNestingDepth = 100 + +var errMaxDocumentNestingDepth = errors.New("maximum BSON nesting depth exceeded") + var vrPool = sync.Pool{ New: func() any { return &valueReader{ @@ -143,11 +148,20 @@ func newBufferedDocumentReader(b []byte) *valueReader { vr.stack[0] = vrState{ mode: mTopLevel, end: int64(len(b)), + depth: 1, } return vr } +func (vr *valueReader) nextContainerDepth() (int, error) { + depth := vr.stack[vr.frame].depth + 1 + if depth > maxDocumentNestingDepth { + return 0, errMaxDocumentNestingDepth + } + return depth, nil +} + func (vr *valueReader) advanceFrame() { if vr.frame+1 >= int64(len(vr.stack)) { // We need to grow the stack length := len(vr.stack) @@ -366,6 +380,11 @@ func (vr *valueReader) ReadArray() (ArrayReader, error) { return nil, err } + depth, err := vr.nextContainerDepth() + if err != nil { + return nil, err + } + // Push a new frame for the array. vr.advanceFrame() @@ -378,6 +397,7 @@ func (vr *valueReader) ReadArray() (ArrayReader, error) { // Compute the end position: current position + total size - length. vr.stack[vr.frame].mode = mArray vr.stack[vr.frame].end = vr.src.pos() + int64(size) - 4 + vr.stack[vr.frame].depth = depth return vr, nil } @@ -469,6 +489,11 @@ func (vr *valueReader) ReadDocument() (DocumentReader, error) { return nil, vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue}) } + depth, err := vr.nextContainerDepth() + if err != nil { + return nil, err + } + vr.advanceFrame() size, err := vr.readLength() @@ -478,6 +503,7 @@ func (vr *valueReader) ReadDocument() (DocumentReader, error) { vr.stack[vr.frame].mode = mDocument vr.stack[vr.frame].end = int64(size) + vr.src.pos() - 4 + vr.stack[vr.frame].depth = depth return vr, nil } @@ -506,6 +532,10 @@ func (vr *valueReader) ReadCodeWithScope() (string, DocumentReader, error) { } code := string(buf[:len(buf)-1]) + depth, err := vr.nextContainerDepth() + if err != nil { + return "", nil, err + } vr.advanceFrame() // Use readLength to ensure that we are not out of bounds. @@ -516,6 +546,7 @@ func (vr *valueReader) ReadCodeWithScope() (string, DocumentReader, error) { vr.stack[vr.frame].mode = mCodeWithScope vr.stack[vr.frame].end = vr.src.pos() + int64(size) - 4 + vr.stack[vr.frame].depth = depth // The total length should equal: // 4 (total length) + strLength + 4 (the length of str itself) + (document length) @@ -833,6 +864,7 @@ func (vr *valueReader) ReadElement() (string, ValueReader, error) { vr.stack[vr.frame].mode = mElement vr.stack[vr.frame].vType = Type(t) + vr.stack[vr.frame].depth = vr.stack[vr.frame-1].depth return name, vr, nil } @@ -868,6 +900,7 @@ func (vr *valueReader) ReadValue() (ValueReader, error) { vr.stack[vr.frame].mode = mValue vr.stack[vr.frame].vType = Type(t) + vr.stack[vr.frame].depth = vr.stack[vr.frame-1].depth return vr, nil } From c18e9e1a4c6f524349f70aefc81f56853f03bab5 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 31 May 2026 01:15:30 -0500 Subject: [PATCH 2/4] Bump dependencies for security alerts Signed-off-by: HD Moore --- go.mod | 10 +++++----- go.sum | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 75280ce510..dda7a5ac9c 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module go.mongodb.org/mongo-driver/v2 -go 1.19 +go 1.25.0 require ( github.com/davecgh/go-spew v1.1.1 github.com/google/go-cmp v0.6.0 - github.com/klauspost/compress v1.17.6 + github.com/klauspost/compress v1.18.6 github.com/xdg-go/scram v1.2.0 github.com/xdg-go/stringprep v1.0.4 github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 - golang.org/x/crypto v0.33.0 - golang.org/x/sync v0.11.0 + golang.org/x/crypto v0.52.0 + golang.org/x/sync v0.20.0 ) require ( github.com/xdg-go/pbkdf2 v1.0.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/text v0.37.0 // indirect ) replace golang.org/x/net/http2 => golang.org/x/net/http2 v0.23.0 // GODRIVER-3225 diff --git a/go.sum b/go.sum index 25b3d8bc66..c5e1a4e151 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= @@ -17,6 +19,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -25,6 +29,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -38,6 +44,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From b01ba42a6b2d57b82af58b9e84a7844cc8927310 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 31 May 2026 01:37:51 -0500 Subject: [PATCH 3/4] Reverse unintended go version bump Signed-off-by: HD Moore --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index dda7a5ac9c..ff02e84c86 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module go.mongodb.org/mongo-driver/v2 -go 1.25.0 +go 1.21 require ( github.com/davecgh/go-spew v1.1.1 From 6031b8050dadca7b47ec2c13c0aa9b0883dc8453 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 31 May 2026 01:40:35 -0500 Subject: [PATCH 4/4] Address PR feedback Signed-off-by: HD Moore --- bson/unmarshal_test.go | 8 ++------ bson/value_reader.go | 15 +++++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bson/unmarshal_test.go b/bson/unmarshal_test.go index 80d1cf220a..584b0d65a4 100644 --- a/bson/unmarshal_test.go +++ b/bson/unmarshal_test.go @@ -41,8 +41,6 @@ func TestUnmarshal(t *testing.T) { } func TestUnmarshalRejectsTooDeepDocumentNesting(t *testing.T) { - t.Helper() - inner := bsoncore.BuildDocument(nil, bsoncore.AppendInt32Element(nil, "a", 1)) for depth := 1; depth < maxDocumentNestingDepth+1; depth++ { inner = bsoncore.BuildDocument(nil, bsoncore.AppendDocumentElement(nil, "a", inner)) @@ -56,11 +54,9 @@ func TestUnmarshalRejectsTooDeepDocumentNesting(t *testing.T) { } func TestUnmarshalRejectsTooDeepArrayNesting(t *testing.T) { - t.Helper() - - inner := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendInt32Element(nil, "0", 1)) + inner := bsoncore.BuildArray(nil, bsoncore.Value{Type: bsoncore.TypeInt32, Data: bsoncore.AppendInt32(nil, 1)}) for depth := 1; depth < maxDocumentNestingDepth+1; depth++ { - inner = bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendArrayElement(nil, "0", inner)) + inner = bsoncore.BuildArray(nil, bsoncore.Value{Type: bsoncore.TypeArray, Data: inner}) } data := bsoncore.BuildDocument(nil, bsoncore.AppendArrayElement(nil, "a", inner)) diff --git a/bson/value_reader.go b/bson/value_reader.go index 4207ac4e4d..37ee736925 100644 --- a/bson/value_reader.go +++ b/bson/value_reader.go @@ -69,8 +69,14 @@ var errMaxDocumentNestingDepth = errors.New("maximum BSON nesting depth exceeded var vrPool = sync.Pool{ New: func() any { + stack := make([]vrState, 1, 5) + stack[0] = vrState{ + mode: mTopLevel, + depth: 1, + } + return &valueReader{ - stack: make([]vrState, 1, 5), + stack: stack, } }, } @@ -108,7 +114,8 @@ func putBufferedDocumentReader(vr *valueReader) { func NewDocumentReader(r io.Reader) ValueReader { stack := make([]vrState, 1, 5) stack[0] = vrState{ - mode: mTopLevel, + mode: mTopLevel, + depth: 1, } return &valueReader{ @@ -146,8 +153,8 @@ func newBufferedDocumentReader(b []byte) *valueReader { } vr.stack[0] = vrState{ - mode: mTopLevel, - end: int64(len(b)), + mode: mTopLevel, + end: int64(len(b)), depth: 1, }