Native heap use-after-free via cancellation racing KMS credential fetch in reactive encryption

XMLWordPrintableJSON

    • None
    • Java Drivers
    • Not Needed
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Cancelling an encrypted operation in the reactive driver can crash the application process.

      The driver holds a native libmongocrypt context for the duration of a client-side encryption operation. On
      cancellation — a client disconnect or an operation timeout — Crypt frees that context from doFinally,
      on the cancelling thread, with no check that another thread is still inside a state-machine step. That step
      then writes into freed native memory. Result: hard JVM crash, or silent corruption in the component holding
      encryption keys.

      Confirmed, not theoretical: a Spring Boot WebFlux service on the reactive driver was killed by one aborted
      HTTP request, crashing in mongocrypt_ctx_provide_kms_providers. Reproduced 2/2.

      Scope

      Affected:

      • driver-reactive-streamsCrypt closes the context from doFinally while callbacks may be running.
      • mongodb-cryptMongoCryptContextImpl.close() frees with no synchronisation, and sets its closed
        flag only after the free.
      • driver-kotlin-coroutine and driver-scala, transitively.
      • Apps using on-demand KMS credentials: an empty provider document plus a kmsProviderPropertySupplier.
        Includes local, not just cloud providers.

      Not affected:

      • driver-sync — single-threaded loop, closes on the same thread.
      • Apps supplying KMS credentials explicitly, for the widest window only. Narrower windows remain.

      Wider than the SECBUG-4142 states: it names one method, but all seven siblings share the defect, as do the
      collinfo / markings / keys / finish callbacks in Crypt. A fix scoped to
      provideKmsProviderCredentials closes the report without closing the bug.

      Requirements

      • No native call may run against a freed context. Calls after close must throw IllegalStateException.
      • Cover every method on MongoCryptContextImpl, not just the one named.
      • close() idempotent and safe to call concurrently.
      • Regression test that cancels mid-operation and fails against current main.
      • Any lock covers the native call only — must not block the canceller for a network round trip.
      • No public API change; no behaviour change on the non-cancelled path.
      • Human review required (mongodb-crypt is security-critical per AGENTS.md).
      • The one-line isTrue("open", !closed) guard must not be accepted as the resolution — see Disputed.

      Disputed

      • Exposure frequency — libmongocrypt only requests credentials on a key-cache miss, so the window shuts
        once the cache is warm and reopens on expiry (~60s, keyExpirationMS). Measured: 25 concurrent aborted
        requests, no crash, 2 credential fetches. Roughly one vulnerable request per key per TTL, not one per request.
      • Root cause as stated — framed as a missing guard, implying a one-line fix. It is check-then-act against a
        volatile boolean, and close() frees before publishing the flag. A PoC with the guard applied still
        reproduces 20/20.
      • Cloud-only framinglocal reaches the same state. Only aws is covered by our tests, which is
        likely why this was missed.

      Resolution Ideas

      • Guard every native call and close() with one lock via Locks.withLock(). Smallest correct fix; the
        canceller blocks only for a native call, since the credential fetch happens in Crypt beforehand. Preferred.
      • Set closed = true before mongocrypt_ctx_destroy. Needed regardless, not sufficient alone.
      • Reference-count the context — last in-flight caller destroys. Avoids blocking the canceller, more state.
      • Move lifetime ownership into Crypt: defer close until any in-flight step completes. Fixes it at the
        causing layer; larger change.
      • Add the missing guard for consistency with its siblings — alongside a real fix, never as the fix.
      • Follow-up ticket: the blocking HttpURLConnection credential fetch does not belong on a reactive path.
        Separate defect; would shrink this window as a side effect.

            Assignee:
            Nabil Hachicha
            Reporter:
            Nabil Hachicha
            Slav Babanin
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: