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:
- SPN sin terminador. Copia el SPN con
strncpy(spn, packet, PRINCIPAL_NAME_MAX), dondePRINCIPAL_NAME_MAXvale 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 comochar spn[PRINCIPAL_NAME_MAX + 1], pero ese byte extra nunca se escribe. Más tarde,auth_client()(engssapi_client.c) calcula la longitud constrlen(principal_name)y sigue leyendo la pila más allá del búfer hasta encontrar un cero. - Lectura pasado el final del paquete. Si el paquete no contiene ningún NUL,
strnlen(packet, packet_len)devuelvepacket_len. La comprobaciónspn_len == packet_len - 1resulta falsa, así que se ejecuta la rama que copia el mecanismo desdepacket + 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.4actual en el momento del reporte (comprobado en el commita4a04c52, 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
- 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 ``
- 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.
- Conectar un cliente configurado para autenticarse por GSSAPI contra ese servidor.
- AddressSanitizer informa de un
stack-buffer-overflowde lectura enauth_client(), en la llamada astrlen(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)dondeNes el tamaño completo del búfer de destino: sisrcpuede tenerNbytes o más,dstqueda sin terminar, y el fallo no aparece ahí sino más adelante, en el primerstrlen(),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álculopacket + spn_len + 1apuntaba ya fuera de él. - Reservar
N + 1bytes no basta si nadie escribe el terminador: el patrón seguro esmemcpy()demin(len, N)bytes seguido dedst[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.