
SQLite 3.53.4 - Élevé - Local - UAF - rtree last_insert_rowid 0 node hash
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
Classe : UAF Portée : Locale
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite calcule le hash d'un nouveau nœud rtree avec iNode==0 lorsque sqlite3_last_insert_rowid() reste à 0 après un INSERT dans %_node qui rapporte néanmoins OK. nodeHashDelete ignore 0. nodeRelease après SplitNode libère le RtreeNode alors que la table de hachage le détient encore. Un nodeHashLookup ultérieur constitue un use-after-free sur le tas. Le même nodeWrite se trouve sur le chemin geopoly.
RCE non démontrée. Lecture ASan dans nodeHashLookup. Le correctif ultérieur de la branche 3.53 est SQLITE_CORRUPT_VTAB lorsque le numéro de nœud attribué est toujours 0. Cette vérification n'est pas présente dans cette GA.
| ID | pas encore de CVE |
| Classe | UAF (use-after-free sur le tas d'un RtreeNode ; RCE non démontrée) |
| Portée | Locale (SQL non fiable ou fichier .db non fiable sur une table rtree/geopoly) |
| CWE | CWE-416 |
| CVSS | Élevé : 8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Produit | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Affecté | 3.53.4 (b09c88c) |
| Auth | SQL de l'attaquant ou un trigger de table fantôme préinstallé / %_node WITHOUT ROWID |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18890 uniquement. Témoin ASan. Aucune charge d'exploitation. |
Sur une table rtree ou geopoly, faire en sorte qu'un INSERT de nœud rapporte OK alors que last_insert_rowid() reste à 0. Méthodes documentées : remplacer %_node par WITHOUT ROWID, ou installer BEFORE INSERT RAISE(IGNORE) sur la table fantôme, puis rouvrir afin que le last rowid de la connexion soit 0. Insérer jusqu'à un split de nœud. Le frère droit est haché comme nœud 0, libéré, et reste trouvable.
Le processus hôte utilise alors un RtreeNode libéré. Le lab est un heap-use-after-free sous AddressSanitizer. Le contrôle du pointeur d'instruction n'est pas dans ce pack. Le crash, si.
SQLITE_VTAB_INNOCUOUS est défini, donc rtree s'exécute toujours avec trusted_schema désactivé. La compilation par défaut ne définit pas SQLITE_DEFAULT_DEFENSIVE. Un trigger préinstallé dans un fichier DB non fiable s'exécute toujours lorsque l'INSERT pWriteNode de rtree s'exécute.
Vingt recherches dans les sources de https://github.com/sqlite/sqlite tag https://github.com/sqlite/sqlite/tree/version-3.53.4. L'arbre canonique est https://sqlite.org/src. GitHub est un miroir Fossil. 3.54.0 est encore un brouillon. Ce reliquat est typique de l'option supplémentaire -DSQLITE_ENABLE_RTREE (compilation officielle recommandée, OPTS nmake Windows, repli autoconf, et fuzzcheck la définissent tous).
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 consulte la table de hachage avant de rejeter iNode<=0. Une correspondance sur l'entrée zéro libérée constitue l'UAF. Une absence de correspondance sur iNode<=0 donne SQLITE_CORRUPT_VTAB, qui est un chemin différent.
Fausses pistes déjà consignées : traiter le last_insert_rowid 0 de %_rowid comme ce bug (cela attribue le rowid de cellule 0, pas l'UAF de hachage) ; attendre SQLITE_CORRUPT_VTAB sur ce pin (c'est le correctif ultérieur) ; remplir 50 cellules pour qu'il n'y ait pas de split ; installer le trigger sans rouvrir ; lookaside masquant la libération à ASan ; qualifier le crash de RCE.
cd lab
./run.sh
Amalgamation 3.53.4 avec -DSQLITE_ENABLE_RTREE=1 et -fsanitize=address. Vendor rtreeI.test 2.0-2.2 : remplir 51 lignes (RTREE_MAXCELLS), BEFORE INSERT RAISE(IGNORE) sur %_node, rouvrir last_insert_rowid()==0, INSERT la 52e ligne (split), puis SELECT *. Le split de contrôle sans le trigger est OK. Bind 127.0.0.1:18890. Le harness est 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
Après sqlite3_last_insert_rowid(), si iNode est toujours 0, retourner SQLITE_CORRUPT_VTAB et ne pas faire nodeHashInsert. branch-3.53 11ce330557 / Git 63b6d705 (2026-09-24) fait cela. Le test attend « database disk image is malformed ». Cette GA ne le fait pas.
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite, nodeHashDelete, nodeRelease, nodeAcquire, SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)