
SQLite 3.53.4 - Alta - Local - UAF - rtree last_insert_rowid 0 node hash
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
Clase: UAF Alcance: Local
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite calcula el hash de un nuevo nodo rtree con iNode==0 cuando sqlite3_last_insert_rowid() permanece en 0 tras un INSERT en %_node que aún así informa OK. nodeHashDelete omite el 0. nodeRelease después de SplitNode libera el RtreeNode mientras el hash todavía lo retiene. Posteriormente, nodeHashLookup es un use-after-free de heap. El mismo nodeWrite está en la ruta de geopoly.
No se demostró RCE. Lectura ASan en nodeHashLookup. El arreglo posterior de branch-3.53 es SQLITE_CORRUPT_VTAB cuando el número de nodo asignado sigue siendo 0. Esa comprobación no está en esta GA.
| ID | aún sin CVE |
| Clase | UAF (use-after-free de heap de RtreeNode; no se demostró RCE) |
| Alcance | Local (SQL no confiable o .db no confiable en una tabla rtree/geopoly) |
| CWE | CWE-416 |
| CVSS | Alto: 8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Producto | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Afectado | 3.53.4 (b09c88c) |
| Auth | SQL del atacante o un trigger plantado en la tabla shadow / %_node WITHOUT ROWID |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1:18890. Testigo ASan. Sin payload de exploit. |
En una tabla rtree o geopoly, hacer que el INSERT de nodo informe OK mientras last_insert_rowid() permanece en 0. Formas documentadas: reemplazar %_node por WITHOUT ROWID, o plantar BEFORE INSERT RAISE(IGNORE) en la tabla shadow, luego reabrir para que el last rowid de la conexión sea 0. Hacer INSERT hasta que se divida un nodo. El hermano derecho se hashea como nodo 0, se libera y sigue siendo localizable.
El proceso anfitrión entonces usa un RtreeNode liberado. El lab es AddressSanitizer heap-use-after-free. El control del instruction pointer no está en este paquete. El crash sí.
SQLITE_VTAB_INNOCUOUS está establecido, así que rtree sigue ejecutándose con trusted_schema desactivado. La compilación por defecto no establece SQLITE_DEFAULT_DEFENSIVE. Un trigger pre-plantado en un archivo DB no confiable aún se ejecuta cuando el propio INSERT de pWriteNode de rtree se ejecuta.
Veinte búsquedas en el código fuente de https://github.com/sqlite/sqlite tag https://github.com/sqlite/sqlite/tree/version-3.53.4. El árbol canónico es https://sqlite.org/src. GitHub es un espejo de Fossil. 3.54.0 sigue siendo un borrador. Este remanente es típico del extra -DSQLITE_ENABLE_RTREE (compilación oficial recomendada, OPTS de nmake en Windows, fallback de autoconf y fuzzcheck lo establecen todos).
if( pNode->iNode==0 && rc==SQLITE_OK ){
pNode->iNode = sqlite3_last_insert_rowid(pRtree->db);
nodeHashInsert(pRtree, pNode);
}
static void nodeHashDelete(Rtree *pRtree, RtreeNode *pNode){
RtreeNode **pp;
if( pNode->iNode!=0 ){
pp = &pRtree->aHash[nodeHash(pNode->iNode)];
for( ; (*pp)!=pNode; pp = &(*pp)->pNext){ assert(*pp); }
*pp = pNode->pNext;
pNode->pNext = 0;
}
}
nodeAcquire busca en el hash antes de rechazar iNode<=0. Un acierto en la entrada cero liberada es el UAF. Un fallo en iNode<=0 es SQLITE_CORRUPT_VTAB, que es una ruta diferente.
Caminos equivocados ya registrados: tratar el last_insert_rowid 0 de %_rowid como este bug (eso asigna el rowid de celda 0, no el UAF del hash); esperar SQLITE_CORRUPT_VTAB en este pin (ese es el arreglo posterior); llenar 50 celdas para que no haya split; plantar el trigger y no reabrir; que lookaside oculte la liberación a ASan; llamar RCE al crash.
cd lab
./run.sh
Amalgamation 3.53.4 con -DSQLITE_ENABLE_RTREE=1 y -fsanitize=address. Vendor rtreeI.test 2.0-2.2: llenar 51 filas (RTREE_MAXCELLS), BEFORE INSERT RAISE(IGNORE) en %_node, reabrir last_insert_rowid()==0, INSERT de la fila 52 (split), luego SELECT *. El split de control sin el trigger es OK. Bind 127.0.0.1:18890. El harness es lab/harness.c.
control-split=ok
reopen-last-rowid=0
witness-before-split=SQLITE-RTREE-ROWID0-WITNESS
ERROR: AddressSanitizer: heap-use-after-free
READ of size 8 ... nodeHashLookup
freed by ... nodeRelease / SplitNode
SUCCESS sqlite-rtree-rowid0 how=asan-uaf-nodehash SQLITE-RTREE-ROWID0-WITNESS
Después de sqlite3_last_insert_rowid(), si iNode sigue siendo 0, devolver SQLITE_CORRUPT_VTAB y no hacer nodeHashInsert. branch-3.53 11ce330557 / Git 63b6d705 (2026-09-24) hace eso. El test espera "database disk image is malformed". Esta GA no.
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite, nodeHashDelete, nodeRelease, nodeAcquire, SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)