MariaDB · Report #3896507
Server crash (stack-overflow DoS) via unbounded recursion in MariaDB WKB GEOMETRYCOLLECTION parser
How it was found
Reported via email as per policy to [email protected] on July 4 but received no response., hence reporting here.
Server crash (stack-overflow DoS) via unbounded recursion in MariaDB WKB GEOMETRYCOLLECTION parser
Target: MariaDB Server 13.1.0-alpha, source rev bd366d81b80aa65fc76ec570fa0bf218a996853b (fresh, 2026-07-01)
Component: spatial / GIS WKB parser — sql/spatial.cc
Class: CWE-674 Uncontrolled Recursion → stack exhaustion → SIGSEGV (Denial of Service)
Status: Confirmed end-to-end against a live ASan server (SIGSEGV reproduced; depth-differential proven).
Summary
Gis_geometry_collection::init_from_wkb() and ::get_data_size() recurse once per nested
child geometry when parsing a WKB (Well-Known Binary) GEOMETRYCOLLECTION, with no
recursion-depth limit and no check_stack_overrun() call. A single crafted value with a
few thousand levels of nested GEOMETRYCOLLECTION exhausts the thread stack and crashes the
entire server (all connections dropped). Reachable by any user who can call a spatial
function or write a GEOMETRY value — i.e. a low-privileged authenticated attacker.
Notably, MariaDB guards recursion everywhere else (check_stack_overrun is used in
item.cc, item_func.cc, item_cmpfunc.cc, item_subselect.cc, item_jsonfunc.cc,
json_table.cc, json_schema.cc, …). The geometry WKB parser is the outlier — it has no
such guard (grep -rn check_stack_overrun sql/spatial.cc sql/spatial.h sql/item_geofunc.cc
→ no matches). This is a cross-path asymmetry: the protection pattern exists project-wide but
was never applied to this parser.
Root cause
sql/spatial.cc:
uint Gis_geometry_collection::init_from_wkb(const char *wkb, uint len, ...) { // :4414
...
while (n_geom--) { // :4429 (attacker n_geom)
Geometry_buffer buffer; // :4431 stack frame per level
Geometry *geom = create_by_typeid(&buffer, wkb_type);
if (!(g_len = geom->init_from_wkb(wkb, len, bo, res))) // :4447 RECURSE
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 RECURSE
}
}
Each nesting level consumes one C++ stack frame (holding a Geometry_buffer). With the
default thread_stack (~292 KB), a few thousand levels overflow the stack → SIGSEGV inside
the recursion. There is no check_stack_overrun() and no depth cap on either path.
Reachable entry points (whole geometry front-end family — check_stack_overrun appears
ZERO times in sql/spatial.cc and sql/item_geofunc.cc):
- WKB —
Item_func_geometry_from_wkb::val_str→Geometry::create_from_wkb→Gis_geometry_collection::init_from_wkb(spatial.cc:4414, recurse :4447).ST_GeomFromWKB,ST_GeomCollFromWKB. - WKT (text) —
Geometry::create_from_wkt(spatial.cc:393) →Gis_geometry_collection::init_from_wkt→ recurses viacreate_from_wkt(spatial.cc:4342).ST_GeomFromText,ST_GeomCollFromText. - GeoJSON —
Gis_geometry_collection::init_from_jsonrecurses via childinit_from_json(spatial.cc:767).ST_GeomFromGeoJSON. - Stored value —
Field_geom::store→is_binary_valid()→get_data_size()(recursive form) —INSERT/UPDATEof a crafted value into aGEOMETRYcolumn.
All four share the single missing check_stack_overrun in the geometry-collection recursion.
Evidence (live ASan server, MariaDB 13.1.0-alpha, -DWITH_ASAN=ON)
Malicious WKB = little-endian nested GEOMETRYCOLLECTION (type 7):
0107000000 + 010000000107000000 × K + 00000000 (each 9-byte unit adds one nesting level).
Depth differential on one server instance (--thread-stack=299008):
| Test | K (nesting) | Result |
|---|---|---|
Sanity: ST_GeomFromWKB(<point>) | 0 | returns normally |
Shallow: SELECT ST_GeomFromWKB(0x…) | 50 | rc=0, server alive |
Deep: SELECT ST_GeomFromWKB(0x…) | 120000 | client rc=1, server DIED, exit/sig=139 (SIGSEGV) |
Reproduced across two independent runs. A normal/shallow geometry parses fine; only the deep nesting crashes — conclusively a depth-dependent recursion (stack overflow), not a malformed- byte parse bug.
WKT front-end (sibling entry point) — independently confirmed on the same server:
SELECT ST_GeomFromText('GEOMETRYCOLLECTION(' ×K ... 'POINT(1 1)' ')' ×K): K=50 returns
normally (server alive); K=100000 → server DIED, exit signal 139 (SIGSEGV). Same
depth-dependent stack overflow via the WKT create_from_wkt recursion (spatial.cc:4342).
GeoJSON (ST_GeomFromGeoJSON → init_from_json, spatial.cc:767) recurses identically with no
guard (code-confirmed). Reproducer: reports/mariadb_poc/wkt_poc.sh. (ASan emits no textual report here because the stack is exhausted before its
handler can run — the process takes a raw SIGSEGV, which is the stack-overflow signature.)
Reproducer scripts: reports/mariadb_poc/wkb_poc.sh, wkb_diff.sh (generate the WKB and
drive the server). Minimal manual repro:
-- build 0x0107000000 + 010000000107000000*120000 + 00000000 and:
SELECT ST_GeomFromWKB(0x0107000000...00000000); -- server crashes (SIGSEGV)
Impact
| Aspect | Assessment |
|---|---|
| Effect | Full mariadbd crash (SIGSEGV) → all client connections dropped; repeatable = sustained DoS |
| Privilege | Low — any account able to run a spatial function or write a GEOMETRY column |
| Vector | AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H → CVSS 3.1 ≈ 6.5 (Medium) |
| RCE | No — stack exhaustion crash, not a controllable memory write |
Suggested fix
Call check_stack_overrun() at the top of Gis_geometry_collection::init_from_wkb and
::get_data_size (as done throughout the rest of the server), or enforce a maximum
GEOMETRYCOLLECTION nesting depth, returning a clean parse error instead of recursing.
Impact
| Aspect | Assessment |
|---|---|
| Effect | Full mariadbd crash (SIGSEGV) → all client connections dropped; repeatable = sustained DoS |
| Privilege | Low — any account able to run a spatial function or write a GEOMETRY column |
| Vector | AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H → CVSS 3.1 ≈ 6.5 (Medium) |
| RCE | No — stack exhaustion crash, not a controllable memory write |