
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
Classe: UAF Alcance: Local
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite faz hash de um novo nó rtree com iNode==0 quando sqlite3_last_insert_rowid() permanece 0 após um INSERT em %_node que ainda reporta OK. nodeHashDelete ignora 0. nodeRelease após SplitNode libera o RtreeNode enquanto o hash ainda o mantém. Posteriormente, nodeHashLookup é um heap use-after-free. O mesmo nodeWrite está no caminho do geopoly.
RCE não demonstrado. Leitura ASan em nodeHashLookup. A correção posterior do branch-3.53 é SQLITE_CORRUPT_VTAB quando o número de nó atribuído ainda é 0. Essa verificação não está nesta GA.
| ID | ainda sem CVE |
| Classe | UAF (heap use-after-free de RtreeNode; RCE não demonstrado) |
| Alcance | Local (SQL não confiável ou .db não confiável em uma tabela 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 |
| Produto | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Afetado | 3.53.4 (b09c88c) |
| Autenticação | SQL do atacante ou um trigger plantado na shadow table / %_node WITHOUT ROWID |
| Licença | GNU Affero GPL v3.0 |
| Lab | Apenas 127.0.0.1:18890. Testemunho ASan. Sem payload de exploit. |
Em uma tabela rtree ou geopoly, faça o INSERT de nó reportar OK enquanto last_insert_rowid() permanece 0. Formas documentadas: substituir %_node por WITHOUT ROWID, ou plantar BEFORE INSERT RAISE(IGNORE) na shadow table, depois reabrir para que o last rowid da conexão seja 0. Faça INSERT até ocorrer um split de nó. O irmão direito recebe hash como nó 0, é liberado e ainda pode ser encontrado.
O processo hospedeiro então usa um RtreeNode liberado. O lab é AddressSanitizer heap-use-after-free. Controle de instruction pointer não está neste pacote. O crash está.
SQLITE_VTAB_INNOCUOUS está definido, então o rtree ainda executa com trusted_schema desligado. A compilação padrão não define SQLITE_DEFAULT_DEFENSIVE. Um trigger pré-plantado em um arquivo DB não confiável ainda é executado quando o próprio INSERT pWriteNode do rtree é executado.
Vinte buscas no código-fonte de https://github.com/sqlite/sqlite tag https://github.com/sqlite/sqlite/tree/version-3.53.4. A árvore canônica é https://sqlite.org/src. O GitHub é um espelho Fossil. 3.54.0 ainda é um rascunho. Esse resquício é típico do -DSQLITE_ENABLE_RTREE extra (compilação oficial recomendada, OPTS do nmake do Windows, fallback do autoconf e fuzzcheck todos o definem).
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 o hash antes de rejeitar iNode<=0. Um hit na entrada zero liberada é o UAF. Um miss em iNode<=0 é SQLITE_CORRUPT_VTAB, que é um caminho diferente.
Caminhos errados já registrados: tratar %_rowid last_insert_rowid 0 como este bug (isso atribui cell rowid 0, não o hash UAF); esperar SQLITE_CORRUPT_VTAB neste pin (essa é a correção posterior); preencher 50 células para que não haja split; plantar o trigger e não reabrir; lookaside escondendo a liberação do ASan; chamar o crash de RCE.
cd lab
./run.sh
Amalgamation 3.53.4 com -DSQLITE_ENABLE_RTREE=1 e -fsanitize=address. Vendor rtreeI.test 2.0-2.2: preencher 51 linhas (RTREE_MAXCELLS), BEFORE INSERT RAISE(IGNORE) em %_node, reabrir last_insert_rowid()==0, INSERT da 52ª linha (split), depois SELECT *. Split de controle sem o trigger está OK. Bind 127.0.0.1:18890. Harness é 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
Após sqlite3_last_insert_rowid(), se iNode ainda for 0, retorne SQLITE_CORRUPT_VTAB e não faça nodeHashInsert. branch-3.53 11ce330557 / Git 63b6d705 (2026-09-24) faz isso. O teste espera "database disk image is malformed". Esta GA não.
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite, nodeHashDelete, nodeRelease, nodeAcquire, SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)