ExportXMLWordPrintableJSON

    • 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.

      1. (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).
      2. (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.
      3. (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).
      4. (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.
      5. (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).
      6. (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

      https://docs.google.com/document/d/1osPjSiI-pvMXtrI49QVqwrIK21_nU38fsp7tJlQpDlY/edit?tab=t.0#heading=h.biijq6ke6z0p

            Assignee:
            Boris Dogadov
            Reporter:
            Boris Dogadov
            Ferdinando Papale
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: