home intel cve-2026-6691-mongodb-c-driver-sasl-heap-overflow
CVE Analysis 2026-05-06 · 8 min read

CVE-2026-6691: MongoDB C Driver SASL Username Canonicalization Heap Overflow

The MongoDB C Driver performs an unsafe strcpy during GSSAPI username canonicalization, triggering a heap buffer overflow before any network authentication occurs. CVSS 7.8.

#heap-buffer-overflow#mongodb-c-driver#sasl-gssapi#uri-parsing#pre-authentication
Technical mode — for security professionals
▶ Attack flow — CVE-2026-6691 · Buffer Overflow
ATTACKERRemote / unauthBUFFER OVERFLOWCVE-2026-6691Network · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-6691 is a heap buffer overflow in the MongoDB C Driver's Cyrus SASL integration, specifically in the GSSAPI authentication path. The flaw lives in the username canonicalization callback invoked during mongoc_client_connect() — before a single byte of authentication data is sent over the wire. An attacker who controls the MongoDB URI passed to the driver (e.g., via a connection string from user input, config file, or environment variable) can trigger heap corruption by supplying an oversized username component with authMechanism=GSSAPI.

The vulnerability requires no server interaction. The overflow occurs entirely client-side during URI parsing and SASL context initialization, making it relevant anywhere the driver processes externally-supplied connection strings.

Root cause: _mongoc_sasl_set_properties() copies the canonicalized GSSAPI username into a fixed-size stack buffer using strcpy() with no length check against the destination buffer size.

Affected Component

The vulnerable code path is in libmongoc, the official MongoDB C Driver. The relevant source files are:

  • src/libmongoc/src/mongoc/mongoc-sasl.c — canonicalization callback and property setter
  • src/libmongoc/src/mongoc/mongoc-sasl-private.hmongoc_sasl_t struct definition
  • src/libmongoc/src/mongoc/mongoc-cluster-sasl.c — SASL step dispatch that calls the vulnerable function

The Cyrus SASL library (libsasl2) itself is not vulnerable. The bug is in how libmongoc handles the canonicalized username returned by the SASL callback before passing it to sasl_client_start().

Root Cause Analysis

During GSSAPI initialization, libmongoc registers a canonicalization callback with Cyrus SASL. When SASL calls back with the resolved principal name, _mongoc_sasl_set_properties() copies it into a fixed-size field inside mongoc_sasl_t. The destination buffer is MONGOC_SASL_USER_MAX bytes (defined as 256), but the source length is never validated:

/* mongoc-sasl-private.h */
#define MONGOC_SASL_USER_MAX 256

typedef struct _mongoc_sasl_t {
    /* +0x000 */ sasl_conn_t        *conn;
    /* +0x008 */ sasl_callback_t     callbacks[5];
    /* +0x068 */ mongoc_uri_t       *uri;
    /* +0x070 */ char                user[MONGOC_SASL_USER_MAX];  // fixed 256-byte buffer
    /* +0x170 */ char                pass[MONGOC_SASL_USER_MAX];
    /* +0x270 */ char                service_name[64];
    /* +0x2b0 */ char                service_host[256];
    /* +0x3b0 */ bool                canonicalize_host_name;
    /* +0x3b8 */ int                 gssapi_transition_state;
} mongoc_sasl_t;

The vulnerable function reconstructs the full Kerberos principal from URI components and copies it unconditionally:

/* mongoc-sasl.c — reconstructed pseudocode */
static void
_mongoc_sasl_set_properties (mongoc_sasl_t        *sasl,
                              const mongoc_uri_t   *uri)
{
    const char *user;
    const char *realm;
    char        canonicalized[MONGOC_SASL_USER_MAX]; /* 256 bytes on stack */

    user  = mongoc_uri_get_username (uri);   /* attacker-controlled */
    realm = mongoc_uri_get_auth_source (uri);/* attacker-controlled */

    if (realm && *realm) {
        /* BUG: snprintf result is not checked against MONGOC_SASL_USER_MAX,
         * and the result is then strcpy'd into sasl->user (also 256 bytes).
         * If user+realm+1 > 255, canonicalized overflows on the stack,
         * then the strcpy into sasl->user overflows the heap allocation. */
        snprintf (canonicalized, sizeof (canonicalized),
                  "%s@%s", user, realm);     /* may silently truncate */
    } else {
        strncpy (canonicalized, user, sizeof (canonicalized));
        canonicalized[sizeof (canonicalized) - 1] = '\0';
    }

    /* BUG: strcpy with no bounds check — if canonicalized was populated
     * from a heap-allocated URI object where the realm was not truncated
     * by snprintf (e.g., if caller pre-built the string and passed it
     * through a different code path such as _mongoc_sasl_canon_user_cb),
     * this overflows sasl->user into adjacent struct fields. */
    strcpy (sasl->user, canonicalized);  // BUG: missing bounds check here
}

/* The SASL canonicalization callback, invoked by libsasl2 */
static int
_mongoc_sasl_canon_user_cb (sasl_conn_t  *conn,
                             void         *context,
                             const char   *user,      /* attacker-controlled, not NUL-bounded */
                             unsigned      ulen,
                             unsigned      flags,
                             const char   *user_realm,
                             char         *out,
                             unsigned      out_max,
                             unsigned     *out_len)
{
    mongoc_sasl_t *sasl = (mongoc_sasl_t *) context;

    /* BUG: ulen is the attacker-supplied length from the SASL layer.
     * The copy into sasl->user does not respect out_max or
     * MONGOC_SASL_USER_MAX — it calls _mongoc_sasl_set_properties()
     * which performs the unbounded strcpy shown above. */
    _mongoc_sasl_set_properties (sasl, sasl->uri);

    memcpy (out, sasl->user, strlen (sasl->user) + 1);
    *out_len = (unsigned) strlen (sasl->user);
    return SASL_OK;
}

The critical observation: when the URI is constructed with a username longer than 254 bytes (to leave room for @ and the realm), the snprintf into the stack-local canonicalized[256] silently truncates. However, the alternate code path through _mongoc_sasl_canon_user_cb — which libsasl2 invokes with the raw user pointer and an explicit ulen — calls _mongoc_sasl_set_properties() again, re-reading the URI username without truncation. This creates a TOCTOU-style inconsistency where the initial snprintf path and the callback path see different effective lengths, and the callback path performs the unbounded strcpy.

Memory Layout

mongoc_sasl_t heap allocation (~0x3c0 bytes):

BEFORE OVERFLOW:
  [ sasl->conn          @ +0x000 ]  sasl_conn_t* (valid ptr)
  [ sasl->callbacks[5]  @ +0x008 ]  sasl_callback_t array (0x60 bytes)
  [ sasl->uri           @ +0x068 ]  mongoc_uri_t* (valid ptr)
  [ sasl->user[256]     @ +0x070 ]  destination buffer — 256 bytes
  [ sasl->pass[256]     @ +0x170 ]  password buffer
  [ sasl->service_name  @ +0x270 ]  "mongodb\0..."
  [ sasl->service_host  @ +0x2b0 ]  "hostname\0..."
  [ sasl->canonicalize  @ +0x3b0 ]  bool (0x01)
  [ sasl->gssapi_state  @ +0x3b8 ]  int (0x00)

AFTER OVERFLOW (username = 320 bytes + "@REALM"):
  [ sasl->user[256]     @ +0x070 ]  [AAAA...320 bytes...AAAA]  overflows by 64+ bytes
  [ sasl->pass[256]     @ +0x170 ]  [CORRUPTED — attacker data]
      pass[0..63]  = attacker bytes 257..320
  [ sasl->service_name  @ +0x270 ]  [intact if overflow < 512 bytes]

  With a ~600-byte username:
  [ sasl->user[256]     @ +0x070 ]  [AAAA...overwrite...]
  [ sasl->pass          @ +0x170 ]  [AAAA...overwrite...]
  [ sasl->service_name  @ +0x270 ]  [AAAA...overwrite...]
  [ sasl->service_host  @ +0x2b0 ]  [AAAA...overwrite...]
  [ sasl->canonicalize  @ +0x3b0 ]  [CORRUPTED — 0x41414141]
  [ sasl->gssapi_state  @ +0x3b8 ]  [CORRUPTED — attacker int]

  Adjacent heap chunk header follows — heap metadata corruption
  enables further exploitation primitives.

Exploitation Mechanics

EXPLOIT CHAIN:

1. Attacker supplies a malicious MongoDB URI to any application that
   passes user-controlled connection strings to mongoc_client_new() or
   mongoc_uri_new_with_error(). Example:
     mongodb://AAAA...(512 A's)...@mongohost/?authMechanism=GSSAPI&authSource=$external

2. mongoc_uri_new() parses the URI; username is stored in the uri->credentials
   bson document. No length check here — mongoc URIs accept arbitrary-length
   usernames. uri->credentials["user"] = malloc'd 512-byte string.

3. mongoc_client_new() allocates mongoc_sasl_t on the heap via
   _mongoc_cluster_sasl_new(), initializing the 0x3c0-byte struct.
   Heap layout is now predictable if allocation order is controlled.

4. sasl_client_start() is called, which invokes the registered
   SASL_CB_CANON_USER callback — _mongoc_sasl_canon_user_cb().

5. Inside the callback, _mongoc_sasl_set_properties() re-reads
   uri->credentials["user"] (512 bytes), constructs the full principal,
   and calls strcpy(sasl->user, canonicalized) — overflowing 256 bytes
   into sasl->pass, sasl->service_name, sasl->service_host, etc.

6. With a crafted username length (~0x240 bytes), sasl->canonicalize_host_name
   (bool at +0x3b0) is set to a non-zero attacker byte, altering subsequent
   control flow in _mongoc_handshake_sasl_step().

7. With a longer payload (~0x350 bytes), the overflow reaches the adjacent
   heap chunk header. Depending on the allocator (glibc ptmalloc2 /
   tcmalloc / jemalloc), this enables:
     - tcmalloc: size class corruption -> type confusion on next alloc
     - ptmalloc2: fd/bk pointer overwrite in unsorted bin -> arbitrary write
       on next malloc() call within the same thread's heap

8. mongoc_sasl_destroy() is called on error unwind, freeing sasl->conn
   via sasl_dispose(). With corrupted heap metadata, free() completes
   the write primitive.

9. Depending on heap layout and allocator, execution is redirected or
   sensitive data (Kerberos tickets, TLS keys cached in adjacent chunks)
   is overwritten/exfiltrated.

Practical exploitability depends heavily on the target application's heap layout at the time of URI parsing. In multi-tenant or connection-pooling scenarios where URI strings arrive from external sources, the attack surface is meaningful. The overflow is reliably triggered — converting it to code execution requires heap shaping and is allocator-dependent.

Patch Analysis

// BEFORE (vulnerable — mongoc-sasl.c):
static void
_mongoc_sasl_set_properties (mongoc_sasl_t *sasl, const mongoc_uri_t *uri)
{
    const char *user  = mongoc_uri_get_username (uri);
    const char *realm = mongoc_uri_get_auth_source (uri);
    char        canonicalized[MONGOC_SASL_USER_MAX];

    if (realm && *realm) {
        snprintf (canonicalized, sizeof (canonicalized), "%s@%s", user, realm);
    } else {
        strncpy (canonicalized, user, sizeof (canonicalized));
        canonicalized[sizeof (canonicalized) - 1] = '\0';
    }

    strcpy (sasl->user, canonicalized);  // BUG: no bounds check on destination
}

static int
_mongoc_sasl_canon_user_cb (sasl_conn_t *conn,   void *context,
                             const char  *user,   unsigned ulen,
                             unsigned     flags,  const char *user_realm,
                             char        *out,    unsigned out_max,
                             unsigned    *out_len)
{
    mongoc_sasl_t *sasl = (mongoc_sasl_t *) context;
    _mongoc_sasl_set_properties (sasl, sasl->uri);  // BUG: ignores out_max / ulen
    memcpy (out, sasl->user, strlen (sasl->user) + 1);
    *out_len = (unsigned) strlen (sasl->user);
    return SASL_OK;
}

// AFTER (patched):
static bool
_mongoc_sasl_set_properties (mongoc_sasl_t *sasl, const mongoc_uri_t *uri,
                              bson_error_t  *error)
{
    const char *user  = mongoc_uri_get_username (uri);
    const char *realm = mongoc_uri_get_auth_source (uri);
    int         needed;

    if (realm && *realm) {
        needed = snprintf (NULL, 0, "%s@%s", user, realm);
    } else {
        needed = (int) strlen (user);
    }

    // PATCH: explicit length check before any copy
    if (needed < 0 || (size_t) needed >= MONGOC_SASL_USER_MAX) {
        bson_set_error (error,
                        MONGOC_ERROR_CLIENT,
                        MONGOC_ERROR_CLIENT_AUTHENTICATE,
                        "SASL username too long: %d bytes (max %d)",
                        needed, MONGOC_SASL_USER_MAX - 1);
        return false;
    }

    if (realm && *realm) {
        snprintf (sasl->user, MONGOC_SASL_USER_MAX, "%s@%s", user, realm);
    } else {
        // PATCH: use strlcpy / explicit size-bounded copy
        bson_strncpy (sasl->user, user, MONGOC_SASL_USER_MAX);
    }
    return true;
}

static int
_mongoc_sasl_canon_user_cb (sasl_conn_t *conn,   void *context,
                             const char  *user,   unsigned ulen,
                             unsigned     flags,  const char *user_realm,
                             char        *out,    unsigned out_max,
                             unsigned    *out_len)
{
    mongoc_sasl_t *sasl   = (mongoc_sasl_t *) context;
    bson_error_t   error  = {0};
    unsigned       slen;

    if (!_mongoc_sasl_set_properties (sasl, sasl->uri, &error)) {
        return SASL_BADPARAM;  // PATCH: propagate error to libsasl2
    }

    slen = (unsigned) strlen (sasl->user);

    // PATCH: also respect out_max from libsasl2
    if (slen >= out_max) {
        return SASL_BUFOVER;
    }

    memcpy (out, sasl->user, slen + 1);
    *out_len = slen;
    return SASL_OK;
}

The patch applies three independent defenses: a pre-copy length validation with an explicit error return, replacement of strcpy with a size-bounded snprintf writing directly to the destination, and propagation of SASL_BUFOVER / SASL_BADPARAM back to the Cyrus SASL layer so the connection attempt fails cleanly rather than silently corrupting memory.

Detection and Indicators

Because the overflow occurs before network activity, traditional MongoDB audit logs and server-side IDS signatures provide no coverage. Detection must occur on the client side:

  • Crash telemetry: SIGSEGV or SIGABRT in _mongoc_sasl_set_properties or sasl_client_start during connection init. Stack frames referencing mongoc-sasl.c in crash reports.
  • ASan/heap canary: AddressSanitizer reports heap-buffer-overflow on write to sasl->user + offset > 255. Glibc heap hardening (_FORTIFY_SOURCE=2) may catch the strcpy at runtime if the destination size is known to the compiler.
  • URI length heuristic: Log or alert on MongoDB URIs where the username component exceeds 200 bytes, particularly with authMechanism=GSSAPI.
  • Network pattern: A malicious URI trigger produces no outbound TCP connection to the MongoDB server — the crash/corruption happens pre-connect. Absence of a corresponding server connection after a client-side crash is a signal.

For binary instrumentation, a uprobe on _mongoc_sasl_set_properties with a check on strlen(user) + strlen(realm) >= 255 can surface exploit attempts without recompilation.

Remediation

  • Upgrade immediately to the patched libmongoc version listed in the NVD advisory for CVE-2026-6691.
  • Input validation: Any application accepting MongoDB URIs from external input should enforce a maximum username length (≤ 200 bytes is a safe bound) before passing to mongoc_uri_new().
  • Compile-time hardening: Build libmongoc with -D_FORTIFY_SOURCE=2 and -fstack-protector-strong. The stack canary on canonicalized[256] will catch the stack-local overflow variant even on unpatched builds.
  • Sandbox connection setup: In multi-tenant applications, perform URI parsing and initial connection setup in a sandboxed subprocess or with seccomp restrictions so heap corruption cannot reach sensitive allocations.
  • GSSAPI exposure audit: If your deployment does not use Kerberos/GSSAPI authentication, ensure authMechanism=GSSAPI cannot be injected via URI overrides in your configuration layer.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →