-
Type:
Task
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: None
-
None
-
0.2
-
Dotnet Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Summary
Anyone who can feed a corrupted BSON document to an app using this driver can read leftover bytes from other requests on that thread, or crash the request.
Impact
Disclosure of up to ~16 KB of residual data from other documents processed on the same thread (other users' content, potentially credentials/PII) returned to the attacker through the application, or an unhandled IndexOutOfRangeException aborting the request.
Severity: high (upper bound critical)
Exploitability: likely_exploitable — The attacker fully controls the malformed array shape and can probe repeatedly by varying the declared length, but each disclosure probe needs a stale zero byte at the post-parse check position (PrimitivesArrayReader.cs:192) and an application channel that echoes the deserialized values; failed probes cost only an exception.
Customer data: customer_content, pii, credentials (api_response)
Location
- src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:90 in ReadBsonArray
- src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:100 in ReadBsonArray
- src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:192 in ReadBsonArray
- src/MongoDB.Bson/IO/ThreadStaticBuffer.cs:96 in RentBuffer
- src/MongoDB.Bson/Serialization/Serializers/MemorySerializer.cs:162 in Deserialize
Reproduction / trigger path
Analyzed trigger path from static analysis — not an executed PoC. Confirm with a concrete repro before handing off.
Attacker controls: Full control over the malformed array's bytes: length prefix (probe position), element type, name termination, and value truncation; repeated probes walk the stale region a few bytes (1-16 per element read) at a time. No control over what the stale region contains — it is whatever earlier operations on the thread left behind.
- (source) src/MongoDB.Bson/Serialization/BsonSerializer.cs:128 in Deserialize<TNominalType>(byte[]) — Attacker-supplied BSON bytes enter a BsonBinaryReader with no validation of inner element structure (Stream overload at line 144; driver wire responses and re-parsed RawBson/LazyBson slices are equivalent binary producers).
- (hop) src/MongoDB.Bson/Serialization/Serializers/MemorySerializer.cs:162 in MemorySerializerBase.Deserialize — Default-registered serializer (BsonSerializer.cs:821 -> CollectionsSerializationProvider.cs:48-49) routes the Array case into PrimitivesArrayReader for every primitive TItem.
- (hop) src/MongoDB.Bson/IO/ByteBufferStream.cs:491 in ReadSlice — Only the outer 4-byte length is checked against the stream end; the inner element bytes pass through verbatim (called via BsonBinaryReader.ReadRawBsonArray at BsonBinaryReader.cs:530).
- (hop) src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:74 in ReadBsonArray — Rents the reused, never-cleared thread-static buffer (min 256 bytes), copies only array.Length bytes, then spans the WHOLE buffer at line 78 — stale slack bytes from prior operations sit beyond array.Length.
- (sink) src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:90 in ReadBsonArray — Unbounded name-skip and unbounded value reads (lines 100-179) index past array.Length: stale bytes are decoded into array values (or IndexOutOfRangeException if the walk exits the span).
- (hop) src/MongoDB.Bson/Serialization/Serializers/MemorySerializer.cs:164 in MemorySerializerBase.Deserialize — Egress: the fabricated values are wrapped in the Memory<T> result and returned to the application, which may expose them to the attacker.
Root cause
- ReadBsonArray rents a per-thread buffer at least as large as the array (min 256 bytes, power-of-2 rounded) and copies only array.Length bytes into it (src/MongoDB.Bson/Serialization/Serializers/PrimitivesArrayReader.cs:74-77), but builds its parsing span over the ENTIRE buffer (line 78).
- The element-name skip loop 'while (span[index] != 0) index++' (line 90) and every fixed-size value read via span.Slice(index) (lines 100-179) have no bound against array.Length, so an unterminated name or truncated final value walks the parser into the buffer's slack region; the end-of-document check (line 192) is silently satisfied by any stale zero byte.
- The thread-static buffer is reused across all operations on the thread and never cleared (src/MongoDB.Bson/IO/ThreadStaticBuffer.cs:96-102); ByteBufferStream fills the same buffer with string content of other documents read and written on that thread (src/MongoDB.Bson/IO/ByteBufferStream.cs:532, 585, 731).
- No upstream validation exists: ReadRawBsonArray/ReadSlice check only the outer length prefix against the stream end (src/MongoDB.Bson/IO/BsonBinaryReader.cs:525-540, src/MongoDB.Bson/IO/ByteBufferStream.cs:491-500), and the fabricated values are returned to the application at src/MongoDB.Bson/Serialization/Serializers/MemorySerializer.cs:162-164.
Ownership
Assigned teams: @mongodb/dbx-csharp-dotnet
Filed from Aegis finding 47fcd61829e3 (scan scan-e837ccfa32ab) · primitive: Bounded stale-buffer read: malformed BSON array indexes past its declared length into an uncleared per-thread buffer, returning leftover cross-request bytes as array values. · categories: Read Primitive, PII Exposure, Crash