Resumen
Desde la versión 10.7, MariaDB incluye SFORMAT(), una función que construye cadenas a partir de una plantilla de formato. Una de las opciones de esa plantilla es el ancho del campo: {:10} da formato al valor con un ancho de 10 caracteres.
El fallo está en que el servidor no limitaba ese ancho a un tamaño razonable. El analizador rechaza los números por encima de 2147483647, pero ese valor exacto sí lo acepta, y con él mariadbd intenta reservar cerca de 2 GB de memoria para generar una sola cadena. La reserva se hace al construir el resultado, antes de que se compruebe el tamaño de la respuesta con límites como max_allowed_packet, de modo que esa protección no sirve aquí.
Basta una cuenta que pueda conectarse y lanzar un SELECT normal: no hace falta acceso a ninguna tabla, ni privilegios de fichero o de administrador, ni interacción de nadie. Lanzando varias de estas consultas en paralelo, el proceso se queda sin memoria y el sistema lo termina (OOM), con lo que la base de datos deja de dar servicio.
El equipo XlabAI de Tencent Xuanwu Lab lo reprodujo sobre una compilación propia del código oficial (13.1.0-MariaDB) y dejó que MariaDB confirmase el rango exacto de versiones afectadas. Lo clasificaron como CWE-789 (reserva de memoria con un tamaño excesivo), con un CVSS 3.1 de 6.5 (Media):
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Pasos de reproducción
- Consigue una cuenta de MariaDB con privilegios mínimos. En la prueba del investigador, la cuenta solo tenía
USAGEy se conectaba por TCP. - Comprueba cómo funciona la función con un ancho normal:
``sql SELECT SFORMAT('{:10}', 'x'); ``
- Repite la llamada con el ancho máximo que acepta el analizador. Envolverla en
LENGTH()no evita el problema, porque el servidor tiene que generar la cadena entera antes de medirla:
``sql SELECT LENGTH(SFORMAT('{:2147483647}', 'x')); ``
- Lanza esa misma consulta desde varias conexiones a la vez. Cada una hace que el servidor intente reservar casi 2 GB.
- Cuando la memoria disponible se acaba, el sistema mata
mariadbdy el servicio cae.
El investigador lo automatizó con unos scripts (en el directorio poc/ de su reporte) que compilan MariaDB desde el código fuente en la imagen local mariadb-source-sformat:13.1-preview, arrancan un contenedor con la memoria limitada, crean el usuario con solo USAGE y lanzan las consultas concurrentes por una conexión TCP autenticada:
cd poc
chmod +x *.sh
./build-local-image.sh
./run-local-target.sh
./run-poc.sh
Si el servidor es vulnerable, la salida incluye:
AUTHENTICATED_LOW_PRIVILEGE_TCP=ok
SFORMAT_LOW_PRIVILEGE=ok
SERVER_AFTER_STRESS=down
RESULT: VULNERABLE_CONFIRMED
Impacto
Un usuario de la base de datos con una cuenta de bajos privilegios podía tirar el servicio con unas pocas consultas SQL normales concurrentes. Es especialmente relevante allí donde terceros tienen cuentas en el mismo servidor, aunque sean muy limitadas.
El propio reporte acota el alcance: el impacto es la disponibilidad. No hay pruebas de que permita leer o modificar datos, escalar privilegios ni ejecutar código.
Qué aprender de este caso
- En cualquier función que acepte un ancho, una precisión o un número de repeticiones (
SFORMAT,LPAD,REPEAT, funciones tipoprintf), prueba el mayor valor que acepta el analizador, no solo uno más allá: el límite de análisis no tiene por qué coincidir con un límite de memoria razonable. - Los límites que se aplican sobre la respuesta, como
max_allowed_packet, no protegen de lo que el servidor reserva mientras construye el resultado. Envolver la llamada enLENGTH()es una buena forma de comprobarlo sin descargar nada. - Al programar, el tamaño del resultado se tiene que calcular y comparar con un tope antes de reservar memoria, no después de generarlo.
- Los fallos de este tipo suelen necesitar concurrencia para tumbar el servicio: una sola consulta grande puede salir bien y varias a la vez no. Inclúyelo en la prueba antes de descartar el problema.