
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
Análisis: https://abraxaslabs.tech/research/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 aun así informa OK. nodeHashDelete omite el 0. nodeRelease tras SplitNode libera el RtreeNode mientras el hash todavía lo retiene. Un nodeHashLookup posterior es un use-after-free de heap. El mismo nodeWrite está en la ruta de geopoly.
No se demuestra RCE. Lectura ASan en nodeHashLookup. La corrección posterior de la rama 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 demuestra 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) |
| Autenticación | SQL del atacante o un trigger plantado en la tabla shadow / %_node WITHOUT ROWID |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | 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, y luego reabrir para que el último 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 usa entonces un RtreeNode liberado. El laboratorio es un heap-use-after-free de AddressSanitizer. El control del puntero de instrucción no está en este paquete. El crash sí.
SQLITE_VTAB_INNOCUOUS está activado, por lo que rtree sigue ejecutándose con trusted_schema desactivado. La compilación por defecto no establece SQLITE_DEFAULT_DEFENSIVE. Un trigger previamente plantado en un archivo DB no confiable sigue ejecutándose cuando se ejecuta el INSERT de pWriteNode del propio rtree.
Veinte búsquedas en el código fuente de https://github.com/sqlite/sqlite etiqueta 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 -DSQLITE_ENABLE_RTREE adicional (la compilación oficial recomendada, las OPTS de nmake en Windows, el fallback de autoconf y fuzzcheck lo activan 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 consulta 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.
Errores ya registrados: tratar %_rowid last_insert_rowid 0 como este bug (eso asigna cell rowid 0, no el UAF del hash); esperar SQLITE_CORRUPT_VTAB en este pin (esa es la corrección posterior); llenar 50 celdas para que no haya división; plantar el disparador y no reabrir; lookaside ocultando la liberación a ASan; llamar al crash RCE.
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 *. La división de control sin el trigger está 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. La prueba espera "database disk image is malformed". Esta GA no lo hace.
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite, nodeHashDelete, nodeRelease, nodeAcquire, SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)