Resumen
MariaDB puede analizar geometrías espaciales en varios formatos (WKB binario, WKT en texto y GeoJSON). Cuando el valor es un GEOMETRYCOLLECTION, el servidor procesa cada geometría hija llamándose a sí mismo una vez por cada nivel de anidamiento. El problema es que esa recursión no tiene tope: no comprueba cuántos niveles lleva ni cuánto espacio de pila le queda al hilo.
El fallo está en sql/spatial.cc, en los métodos Gis_geometry_collection::init_from_wkb() y Gis_geometry_collection::get_data_size(). Ninguno llama a check_stack_overrun(), la función que el propio MariaDB usa para frenar la recursión en casi todo el resto del servidor (en item.cc, item_func.cc, item_cmpfunc.cc, item_subselect.cc, item_jsonfunc.cc, json_table.cc, json_schema.cc…). El parser de geometrías es la excepción: la protección existe en todo el proyecto, pero aquí no se aplicó.
El resultado es una denegación de servicio: un único valor con unos cuantos miles de niveles de GEOMETRYCOLLECTION anidados agota la pila del hilo y provoca un SIGSEGV que tumba el proceso mariadbd completo, no solo la conexión del atacante. Todas las conexiones se caen. Lo puede disparar cualquier cuenta con permiso para ejecutar una función espacial o escribir un valor en una columna GEOMETRY, es decir, un atacante autenticado con privilegios bajos.
El investigador clasifica el caso como CWE-674 (recursión no controlada) y propone un vector CVSS 3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H, es decir, aproximadamente 6.5 (severidad media). No permite ejecución de código: es un agotamiento de pila, no una escritura de memoria controlable.
Dónde está el fallo
El patrón recursivo es el siguiente, en sql/spatial.cc:
uint Gis_geometry_collection::init_from_wkb(const char *wkb, uint len, ...) { // :4414
...
while (n_geom--) { // :4429 (n_geom lo controla el atacante)
Geometry_buffer buffer; // :4431 un marco de pila por nivel
Geometry *geom = create_by_typeid(&buffer, wkb_type);
if (!(g_len = geom->init_from_wkb(wkb, len, bo, res))) // :4447 RECURSIÓN
return 0;
...
}
}
uint32 Gis_geometry_collection::get_data_size() const { // :4284
...
while (n_objects--) {
if ((object_size = geom->get_data_size()) == GET_SIZE_ERROR) ... // :4308 RECURSIÓN
}
}
Cada nivel de anidamiento consume un marco de pila (que además aloja un Geometry_buffer). Con el tamaño de pila por defecto (thread_stack, ~292 KB), bastan unos pocos miles de niveles para desbordarla. No hay check_stack_overrun() ni ningún límite de profundidad en ninguna de las dos rutas.
Lo relevante es que no es un único endpoint: toda la familia de funciones de entrada de geometrías comparte el mismo defecto, porque check_stack_overrun no aparece ni una sola vez en sql/spatial.cc ni en sql/item_geofunc.cc. Las vías de entrada son:
- WKB (binario) —
ST_GeomFromWKB,ST_GeomCollFromWKB, que llegan aGis_geometry_collection::init_from_wkb(recursión enspatial.cc:4447). - WKT (texto) —
ST_GeomFromText,ST_GeomCollFromText, que recurren víacreate_from_wkt(spatial.cc:4342). - GeoJSON —
ST_GeomFromGeoJSON, que recurre víainit_from_json(spatial.cc:767). - Valor almacenado — un
INSERT/UPDATEcon un valor manipulado en una columnaGEOMETRY, que pasa porField_geom::store→is_binary_valid()→get_data_size().
Pasos de reproducción
El investigador lo confirmó contra un servidor MariaDB 13.1.0-alpha compilado con AddressSanitizer (-DWITH_ASAN=ON), arrancado con --thread-stack=299008.
El valor WKB malicioso es un GEOMETRYCOLLECTION (tipo 7) anidado en little-endian. Se construye concatenando:
0107000000 + 010000000107000000 × K + 00000000
Cada bloque de 9 bytes (010000000107000000) añade un nivel de anidamiento, controlado por K.
Sobre una misma instancia del servidor, el comportamiento depende exclusivamente de la profundidad:
| Prueba | K (niveles) | Resultado | |---|---|---| | Control: ST_GeomFromWKB(<punto>) | 0 | responde con normalidad | | Superficial: SELECT ST_GeomFromWKB(0x…) | 50 | rc=0, servidor vivo | | Profunda: SELECT ST_GeomFromWKB(0x…) | 120000 | cliente rc=1, servidor caído, señal 139 (SIGSEGV) |
Repro manual mínima:
-- construir 0x0107000000 + 010000000107000000*120000 + 00000000 y:
SELECT ST_GeomFromWKB(0x0107000000...00000000); -- el servidor cae (SIGSEGV)
El resultado se reprodujo en dos ejecuciones independientes. Una geometría normal o poco anidada se analiza sin problema; solo la versión muy anidada provoca la caída, lo que confirma que es un desbordamiento de pila dependiente de la profundidad y no un error de parseo por bytes mal formados.
La vía WKT se confirmó por separado en el mismo servidor: SELECT ST_GeomFromText('GEOMETRYCOLLECTION(' ×K ... 'POINT(1 1)' ')' ×K) responde con normalidad para K=50 y tumba el servidor (señal 139) con K=100000, por la misma recursión en create_from_wkt (spatial.cc:4342). La vía GeoJSON recurre igual y sin protección, confirmado por inspección del código.
Un detalle del diagnóstico: AddressSanitizer no llega a emitir su informe de texto habitual, porque la pila se agota antes de que su manejador pueda ejecutarse; el proceso recibe un SIGSEGV en crudo, que es precisamente la firma de un desbordamiento de pila.
Impacto
- Efecto: caída completa del proceso
mariadbd(SIGSEGV). Se pierden todas las conexiones de clientes, no solo la del atacante. Como es repetible, permite una denegación de servicio sostenida. - Privilegio necesario: bajo. Basta con cualquier cuenta que pueda ejecutar una función espacial o escribir en una columna
GEOMETRY. - Sin RCE: es un agotamiento de pila, no una escritura de memoria controlable, así que no deriva en ejecución de código.
Remediación
El investigador propone llamar a check_stack_overrun() al principio de Gis_geometry_collection::init_from_wkb y de ::get_data_size (como ya se hace en el resto del servidor), o bien imponer una profundidad máxima de anidamiento para GEOMETRYCOLLECTION, devolviendo un error de parseo limpio en lugar de seguir recurriendo.
Qué aprender de este caso
- Cuando un parser procesa estructuras anidadas (geometrías WKB/WKT, JSON, XML, colecciones recursivas), revisa si hay un tope de profundidad o una comprobación de pila en cada rama recursiva: aquí la protección existía en todo el proyecto (
check_stack_overrun) pero faltaba justo enspatial.cc. Un buen punto de auditoría es buscar la función de guardia del propio proyecto y listar qué ficheros recursivos no la llaman. - El contador de elementos hijos del formato (aquí
n_geom/n_objects, leídos directamente del valor de entrada) lo controla el atacante: cualquier bucle o recursión gobernado por un número que viene del dato sin validar es candidato a agotamiento de recursos. - El mismo defecto suele alcanzarse por varias puertas de entrada que comparten el motor interno: en este caso WKB, WKT, GeoJSON y el propio
INSERT/UPDATEen una columna del tipo. Arreglar o auditar solo una función de API deja el resto expuesto; conviene tapar el núcleo común. - Conviene distinguir un fallo dependiente de la profundidad de uno de bytes mal formados: probar con valores poco y muy anidados (50 vs. 120000 niveles) sobre la misma instancia aísla el desbordamiento de pila de un error de parseo normal.