Reports
MEDIA[CORRUPCIÓN DE MEMORIA]#3896684

MariaDB Connector/C: lectura fuera de límites en el cliente GSSAPI por un SPN sin terminador enviado por el servidor

El plugin GSSAPI de MariaDB Connector/C copiaba con strncpy() el SPN que envía el servidor sin cerrarlo con NUL. Un servidor malicioso o un MitM podía provocar lecturas fuera de límites en la pila del cliente y tumbar el proceso.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

El fallo está en la biblioteca cliente de MariaDB, Connector/C (mariadb-connector-c), concretamente en el plugin de autenticación GSSAPI. Cuando un cliente se autentica por GSSAPI, el servidor le envía un paquete con el Service Principal Name (SPN) y, opcionalmente, el nombre del mecanismo. La función que lo interpreta, parse_server_packet() en plugins/auth/auth_gssapi_client.c, confía demasiado en ese paquete y tiene dos errores de manejo de cadenas en C:

  1. SPN sin terminador. Copia el SPN con strncpy(spn, packet, PRINCIPAL_NAME_MAX), donde PRINCIPAL_NAME_MAX vale 256. Si el SPN mide 256 bytes o más y no contiene ningún byte NUL, strncpy() llena el búfer pero no escribe el terminador. El búfer del llamador se declara como char spn[PRINCIPAL_NAME_MAX + 1], pero ese byte extra nunca se escribe. Más tarde, auth_client() (en gssapi_client.c) calcula la longitud con strlen(principal_name) y sigue leyendo la pila más allá del búfer hasta encontrar un cero.
  2. Lectura pasado el final del paquete. Si el paquete no contiene ningún NUL, strnlen(packet, packet_len) devuelve packet_len. La comprobación spn_len == packet_len - 1 resulta falsa, así que se ejecuta la rama que copia el mecanismo desde packet + spn_len + 1, es decir, un byte después del final del paquete.

El código afectado:

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);
  }
}

Y el punto donde se produce la lectura fuera de límites del primer fallo:

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

El reporte subraya que el resto del código de Connector/C ya usa el patrón correcto para leer cadenas de longitud variable que llegan por la red, memcpy() acotado más terminador NUL explícito (cita parsec.c, caching_sha2_pw.c y ed25519.c); este plugin era la excepción.

Datos del caso:

  • Versiones afectadas: todas hasta la rama 3.4 actual en el momento del reporte (comprobado en el commit a4a04c52, 82 commits después de la etiqueta v3.4.9). La versión 3.4.9 está afectada.
  • Plataforma: compilaciones de Linux con soporte GSSAPI. En Windows se usa otro camino (SSPI), pero con la misma función de entrada parse_server_packet().
  • Requisito: la aplicación tiene que tener compilado y usar el plugin de autenticación GSSAPI.
  • Debilidad: Out-of-bounds Read (CWE-125).
  • Origen: el hallazgo es de Aisle Research, que lo comunicó a Red Hat por un proceso de embargo. El reporte en HackerOne lo envió mschorm, que pide que el hallazgo se atribuya a Aisle Research.
  • Fechas: enviado el 28 de julio de 2026 y divulgado el 4 de noviembre de 2026, con el reporte marcado como resuelto.

Pasos de reproducción

  1. Compilar Connector/C con AddressSanitizer y con GSSAPI activado:

`` cmake -DCMAKE_C_FLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer" -DWITH_GSSAPI=ON .. make ``

  1. Montar un servidor de pruebas (o un intermediario que hable el protocolo) que, durante la autenticación, responda al cliente con un paquete de desafío GSSAPI que cumpla:
  • packet_len >= 256.
  • Ningún byte NUL en los primeros 256 bytes del campo SPN.
  • Para el segundo fallo: ningún byte NUL en todo el paquete.
  1. Conectar un cliente configurado para autenticarse por GSSAPI contra ese servidor.
  2. AddressSanitizer informa de un stack-buffer-overflow de lectura en auth_client(), en la llamada a strlen(principal_name).

Impacto

El ataque se lanza contra el cliente, no contra el servidor: hace falta controlar el servidor al que se conecta la aplicación o estar en posición de intermediario (MitM) durante el inicio de una conexión autenticada por GSSAPI. Con eso, según el reporte:

  • Se lee memoria de la pila contigua al búfer, lo que puede revelar datos adyacentes como direcciones de retorno u otras variables locales.
  • Se puede hacer que el proceso cliente se caiga.

Remediación

El reporte propone sustituir strncpy() por un memcpy() acotado con terminador NUL explícito, y corregir la comprobación de límites al leer el mecanismo, de modo que nunca se lea más allá de packet_len:

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';
   }
 }

Las claves del parche son tres: copiar como mucho PRINCIPAL_NAME_MAX bytes y escribir siempre el '\0'; tratar como "sin mecanismo" el caso en que no queda nada después del SPN (spn_len + 1 >= packet_len), que incluye el paquete sin ningún NUL; y medir el mecanismo con strnlen() limitado a los bytes que de verdad quedan en el paquete. El reporte figura como resuelto, pero no detalla si el parche aplicado fue exactamente este.

Qué aprender de este caso

  • Busca strncpy(dst, src, N) donde N es el tamaño completo del búfer de destino: si src puede tener N bytes o más, dst queda sin terminar, y el fallo no aparece ahí sino más adelante, en el primer strlen(), printf("%s") o similar que lo use.
  • Cuando un protocolo envía varias cadenas seguidas separadas por NUL, revisa qué pasa si el NUL no llega: aquí strnlen() devolvía la longitud completa del paquete y el cálculo packet + spn_len + 1 apuntaba ya fuera de él.
  • Reservar N + 1 bytes no basta si nadie escribe el terminador: el patrón seguro es memcpy() de min(len, N) bytes seguido de dst[copia] = '\0', como ya hacían otros plugins de la misma biblioteca.
  • Los plugins de autenticación del lado cliente procesan datos del servidor antes de que exista ninguna confianza en él; al auditar conectores de bases de datos, trata las respuestas del servidor como entrada hostil, igual que las peticiones de un cliente.