
SQLite 3.53.4 - Hoch - Lokal - UAF - rtree last_insert_rowid 0 node hash
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
Write-up: https://abraxaslabs.tech/research/sqlite-rtree-rowid0
Klasse: UAF Reichweite: Lokal
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite hasht einen neuen rtree-Knoten mit iNode==0, wenn sqlite3_last_insert_rowid() nach einem INSERT in %_node 0 bleibt, obwohl dieser weiterhin OK meldet. nodeHashDelete überspringt 0. nodeRelease gibt nach SplitNode den RtreeNode frei, während der Hash ihn noch hält. Ein späteres nodeHashLookup ist ein Heap-Use-after-free. Derselbe nodeWrite liegt auf dem geopoly-Pfad.
Keine nachgewiesene RCE. ASan-READ in nodeHashLookup. Der spätere Fix in Branch 3.53 ist SQLITE_CORRUPT_VTAB, wenn die zugewiesene Knotennummer noch 0 ist. Diese Prüfung fehlt in dieser GA.
| ID | noch keine CVE |
| Klasse | UAF (Heap-Use-after-free eines RtreeNode; keine nachgewiesene RCE) |
| Reichweite | Lokal (nicht vertrauenswürdiges SQL oder nicht vertrauenswürdige .db auf einer rtree/geopoly-Tabelle) |
| CWE | CWE-416 |
| CVSS | Hoch: 8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Produkt | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Betroffen | 3.53.4 (b09c88c) |
| Auth | Angreifer-SQL oder ein eingeschleuster Shadow-Table-Trigger / WITHOUT ROWID %_node |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1:18890. ASan-Nachweis. Keine Exploit-Payload. |
Auf einer rtree- oder geopoly-Tabelle dafür sorgen, dass ein Knoten-INSERT OK meldet, während last_insert_rowid() 0 bleibt. Dokumentierte Wege: %_node durch WITHOUT ROWID ersetzen oder BEFORE INSERT RAISE(IGNORE) auf der Shadow-Tabelle platzieren und dann erneut öffnen, sodass die letzte Rowid der Verbindung 0 ist. INSERT ausführen, bis ein Knoten-Split erfolgt. Das rechte Geschwister wird als Knoten 0 gehasht, freigegeben und bleibt weiterhin auffindbar.
Der Host-Prozess verwendet dann einen freigegebenen RtreeNode. Das Lab zeigt einen AddressSanitizer-Heap-Use-after-free. Kontrolle über den Instruction Pointer ist nicht in diesem Paket enthalten. Der Absturz schon.
SQLITE_VTAB_INNOCUOUS ist gesetzt, daher läuft rtree auch mit deaktiviertem trusted_schema. Die Standardkompilierung setzt SQLITE_DEFAULT_DEFENSIVE nicht. Ein vorab platzierter Trigger in einer nicht vertrauenswürdigen DB-Datei wird weiterhin ausgeführt, wenn rtrees eigener pWriteNode-INSERT läuft.
Zwanzig Quellcode-Durchsuchungen von https://github.com/sqlite/sqlite Tag https://github.com/sqlite/sqlite/tree/version-3.53.4. Der kanonische Baum ist https://sqlite.org/src. GitHub ist ein Fossil-Mirror. 3.54.0 ist noch ein Entwurf. Dieser Überrest ist typisch für das zusätzliche -DSQLITE_ENABLE_RTREE (offizielle empfohlene Kompilierung, Windows nmake OPTS, autoconf-Fallback und fuzzcheck setzen es alle).
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 schlägt den Hash nach, bevor iNode<=0 abgelehnt wird. Ein Treffer auf dem freigegebenen Null-Eintrag ist die UAF. Ein Fehlschlag bei iNode<=0 ist SQLITE_CORRUPT_VTAB, was ein anderer Pfad ist.
Bereits festgehaltene Irrwege: %_rowid last_insert_rowid 0 als diesen Bug zu behandeln (das weist der Zelle rowid 0 zu, nicht die Hash-UAF); SQLITE_CORRUPT_VTAB bei diesem Pin zu erwarten (das ist der spätere Fix); 50 Zellen zu füllen, sodass es keinen Split gibt; den Trigger zu platzieren und nicht erneut zu öffnen; Lookaside, das die Freigabe vor ASan verbirgt; den Absturz als RCE zu bezeichnen.
cd lab
./run.sh
Amalgamation 3.53.4 mit -DSQLITE_ENABLE_RTREE=1 und -fsanitize=address. Vendor rtreeI.test 2.0-2.2: 51 Zeilen füllen (RTREE_MAXCELLS), BEFORE INSERT RAISE(IGNORE) auf %_node, last_insert_rowid()==0 erneut öffnen, die 52. Zeile einfügen (Split), dann SELECT *. Kontroll-Split ohne den Trigger ist OK. Binde 127.0.0.1:18890. Harness ist 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
Wenn iNode nach sqlite3_last_insert_rowid() immer noch 0 ist, wird SQLITE_CORRUPT_VTAB zurückgegeben und kein nodeHashInsert durchgeführt. branch-3.53 11ce330557 / Git 63b6d705 (2026-09-24) macht das. Der Test erwartet „database disk image is malformed“. Dieses GA nicht.
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite, nodeHashDelete, nodeRelease, nodeAcquire, SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)