HexBrain

MariaDB · Report #3896684

Connector/C Out-of-bounds read from unterminated GSSAPI SPN in `parse_server_packet()`

Validstatus
6upvotes
4 Nov 2026disclosed

How it was found

Summary

parse_server_packet() in plugins/auth/auth_gssapi_client.c uses strncpy() to copy the server-supplied Service Principal Name (SPN) into a fixed-size stack buffer without explicit NUL termination. When the server sends an SPN of 256+ bytes with no NUL byte, strncpy(spn, packet, 256) fills the buffer without terminating it. The subsequent strlen(principal_name) in gssapi_client.c:auth_client() then reads past the stack buffer — an out-of-bounds read.

A second parsing flaw exists: when the packet contains no NUL byte at all, strnlen() returns packet_len and the mechanism-copy path reads from packet + packet_len + 1, past the packet boundary.

Originally reported by: Aisle Research

Environment

  • Component: MariaDB Connector/C (mariadb-connector-c)
  • Affected versions: All versions through current 3.4 branch (verified on commit a4a04c52, 82 commits after v3.4.9 tag). The v3.4.9 release is affected.
  • Platform: Linux builds with GSSAPI authentication support enabled. Windows builds use a separate SSPI code path with the same parse_server_packet() entry point.
  • Precondition: The GSSAPI client authentication plugin must be built and used by the connecting application.

Vulnerability Details

The vulnerable parser:

static void parse_server_packet(char *packet, size_t packet_len, char *spn, char *mech)
{
  size_t spn_len;
  spn_len = strnlen(packet, packet_len);
  strncpy(spn, packet, PRINCIPAL_NAME_MAX);   // PRINCIPAL_NAME_MAX = 256
  if (spn_len == packet_len - 1)
  {
    *mech = 0;
  }
  else
  {
    strncpy(mech, packet + spn_len + 1, MECH_NAME_MAX);
  }
}

Flaw 1 — unterminated SPN buffer: strncpy(spn, packet, PRINCIPAL_NAME_MAX) does not write a NUL terminator when the source fills the destination. The caller allocates char spn[PRINCIPAL_NAME_MAX + 1], but the +1 byte is never written. When strlen(principal_name) is later called in auth_client():

gss_buffer_desc principal_name_buf;
principal_name_buf.length= strlen(principal_name);  // OOB read

it reads past the 256-byte buffer until it finds a zero byte on the stack.

Flaw 2 — mechanism-copy past packet end: When the packet contains no NUL byte, strnlen(packet, packet_len) returns packet_len. The condition spn_len == packet_len - 1 is false (it's packet_len == packet_len - 1), so the else branch executes strncpy(mech, packet + spn_len + 1, MECH_NAME_MAX), reading from one byte past the packet boundary.

While other auth plugins treat packets as single opaque blobs, bounded memcpy() + explicit NUL termination is the standard pattern throughout the codebase for parsing variable-length strings from network data (e.g., parsec.c lines 189/226, caching_sha2_pw.c line 383, ed25519.c line 147).

Steps to Reproduce

  1. Build Connector/C with AddressSanitizer and GSSAPI support enabled:

    cmake -DCMAKE_C_FLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer" -DWITH_GSSAPI=ON ..
    make
    
  2. Set up a protocol shim or test server that responds to client authentication with a GSSAPI challenge packet where:

    • packet_len >= 256
    • No NUL byte appears in the first 256 bytes of the SPN field
    • (For flaw 2: no NUL byte anywhere in the packet)
  3. Connect a client configured for GSSAPI authentication.

  4. Observe ASan reporting a stack-buffer-overflow read in auth_client() at the strlen(principal_name) call.

Suggested Fix

Replace strncpy() with bounded memcpy() + explicit NUL termination, and fix the mechanism-parsing boundary check:

diff --git a/plugins/auth/auth_gssapi_client.c b/plugins/auth/auth_gssapi_client.c
index 9c60f8d4..a2acbd51 100644
--- a/plugins/auth/auth_gssapi_client.c
+++ b/plugins/auth/auth_gssapi_client.c
@@ -46,19 +46,28 @@ extern int auth_client(char *principal_name,
                        MYSQL *mysql,
                        MYSQL_PLUGIN_VIO *vio);
 
-static void parse_server_packet(char *packet, size_t packet_len, char *spn, char *mech)
+static void parse_server_packet(const char *packet, size_t packet_len, char *spn, char *mech)
 {
-  size_t spn_len;
+  size_t spn_len, copy_len;
+
   spn_len = strnlen(packet, packet_len);
-  strncpy(spn, packet, PRINCIPAL_NAME_MAX);
-  if (spn_len == packet_len - 1)
+  copy_len = spn_len < PRINCIPAL_NAME_MAX ? spn_len : PRINCIPAL_NAME_MAX;
+  memcpy(spn, packet, copy_len);
+  spn[copy_len] = '\0';
+
+  if (spn_len + 1 >= packet_len)
   {
-    /* Mechanism not included into packet */
-    *mech = 0;
+    *mech = '\0';
   }
   else
   {
-    strncpy(mech, packet + spn_len + 1, MECH_NAME_MAX);
+    const char *mech_src = packet + spn_len + 1;
+    size_t mech_avail = packet_len - spn_len - 1;
+    size_t mech_len = strnlen(mech_src, mech_avail);
+    if (mech_len > MECH_NAME_MAX)
+      mech_len = MECH_NAME_MAX;
+    memcpy(mech, mech_src, mech_len);
+    mech[mech_len] = '\0';
   }
 }

Originally reported by: Aisle Research to Red Hat via Embargo process. Aisle research should be attributed for the finding. To my knowledge this report was not shared outside of Aisle Research and Red Hat.

Impact

Impact

  • Client-side out-of-bounds stack read: potential disclosure of adjacent stack memory (return addresses, other local variables)
  • Client process crash
  • The attack requires a malicious server or MitM position during GSSAPI-authenticated connection setup

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original