diff --git a/bson/unmarshal_test.go b/bson/unmarshal_test.go index be9d7e238..584b0d65a 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,34 @@ func TestUnmarshal(t *testing.T) { } } +func TestUnmarshalRejectsTooDeepDocumentNesting(t *testing.T) { + 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) { + inner := bsoncore.BuildArray(nil, bsoncore.Value{Type: bsoncore.TypeInt32, Data: bsoncore.AppendInt32(nil, 1)}) + for depth := 1; depth < maxDocumentNestingDepth+1; depth++ { + inner = bsoncore.BuildArray(nil, bsoncore.Value{Type: bsoncore.TypeArray, Data: 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 e5bcc1985..37ee73692 100644 --- a/bson/value_reader.go +++ b/bson/value_reader.go @@ -60,12 +60,23 @@ 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 { + stack := make([]vrState, 1, 5) + stack[0] = vrState{ + mode: mTopLevel, + depth: 1, + } + return &valueReader{ - stack: make([]vrState, 1, 5), + stack: stack, } }, } @@ -103,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{ @@ -141,13 +153,22 @@ func newBufferedDocumentReader(b []byte) *valueReader { } vr.stack[0] = vrState{ - mode: mTopLevel, - end: int64(len(b)), + 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 +387,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 +404,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 +496,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 +510,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 +539,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 +553,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 +871,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 +907,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 } diff --git a/go.mod b/go.mod index 75280ce51..ff02e84c8 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module go.mongodb.org/mongo-driver/v2 -go 1.19 +go 1.21 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 25b3d8bc6..c5e1a4e15 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=