abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
技术分析文章:https://abraxaslabs.tech/research/sqlite-rtree-rowid0
类别: UAF 可达性: 本地
SQLite 3.53.4(b09c88c14082339b66c7b7158d609a771e64ca69,SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc)- Hwaci
当向 %_node 执行 INSERT 后仍返回 OK,但 sqlite3_last_insert_rowid() 保持为 0 时,nodeWrite 会以 iNode==0 对一个新 rtree 节点进行哈希。nodeHashDelete 会跳过 0。SplitNode 之后的 nodeRelease 释放了 RtreeNode,而哈希表仍然持有它。之后的 nodeHashLookup 便构成堆上的 use-after-free。同样的 nodeWrite 也存在于 geopoly 路径上。
未演示 RCE。ASan 在 nodeHashLookup 中报告 READ。后续 3.53 分支的修复是在分配到的节点号仍为 0 时返回 SQLITE_CORRUPT_VTAB。该检查不在本 GA 版本中。
| ID | 暂无 CVE |
| 类别 | UAF(RtreeNode 的堆 use-after-free;未演示 RCE) |
| 可达性 | 本地(在 rtree/geopoly 表上使用不可信 SQL 或不可信 .db) |
| CWE | CWE-416 |
| CVSS | 高危:8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| 产品 | SQLite rtree / geopoly(SQLITE_ENABLE_RTREE) |
| 受影响版本 | 3.53.4(b09c88c) |
| 认证 | 攻击者 SQL 或预置的 shadow 表触发器 / WITHOUT ROWID %_node |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:18890。ASan 见证。无利用载荷。 |
在 rtree 或 geopoly 表上,使节点 INSERT 返回 OK,同时 last_insert_rowid() 保持为 0。已记录的方法:将 %_node 替换为 WITHOUT ROWID,或在 shadow 表上预置 BEFORE INSERT RAISE(IGNORE),然后重新打开,使连接的 last rowid 为 0。持续 INSERT 直到发生节点分裂。右兄弟节点被以节点 0 进行哈希、被释放,并且仍然可被查找。
随后宿主进程使用了一个已释放的 RtreeNode。实验环境为 AddressSanitizer 堆 use-after-free。本包中不包含指令指针控制。崩溃是存在的。
SQLITE_VTAB_INNOCUOUS 已设置,因此 rtree 在 trusted_schema 关闭时仍会运行。默认编译不会设置 SQLITE_DEFAULT_DEFENSIVE。不可信 DB 文件中预置的触发器在 rtree 自身的 pWriteNode INSERT 执行时仍会运行。
对 https://github.com/sqlite/sqlite 标签 https://github.com/sqlite/sqlite/tree/version-3.53.4 进行了二十次源码搜寻。规范树是 https://sqlite.org/src。GitHub 是 Fossil 镜像。3.54.0 仍是草稿。这一遗留问题是典型的额外 -DSQLITE_ENABLE_RTREE(官方推荐编译、Windows nmake OPTS、autoconf 回退以及 fuzzcheck 都会设置它)。
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 在拒绝 iNode<=0 之前先查找哈希。命中已释放的零条目就是 UAF。在 iNode<=0 上未命中则是 SQLITE_CORRUPT_VTAB,那是另一条路径。
已记录的弯路:把 %_rowid 的 last_insert_rowid 0 当作此漏洞(那只是给单元格 rowid 赋 0,并非哈希 UAF);期望在此 pin 上出现 SQLITE_CORRUPT_VTAB(那是后续修复);填满 50 个单元格以避免分裂;植入触发器后不重新打开;lookaside 使 ASan 看不到释放;把崩溃称为 RCE。
cd lab
./run.sh
Amalgamation 3.53.4,使用 -DSQLITE_ENABLE_RTREE=1 和 -fsanitize=address。Vendor rtreeI.test 2.0-2.2:填充 51 行(RTREE_MAXCELLS),在 %_node 上使用 BEFORE INSERT RAISE(IGNORE),重新打开后 last_insert_rowid()==0,插入第 52 行(split),然后执行 SELECT *。不带触发器的对照 split 正常。绑定 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
在 sqlite3_last_insert_rowid() 之后,如果 iNode 仍为 0,则返回 SQLITE_CORRUPT_VTAB,并且不执行 nodeHashInsert。branch-3.53 11ce330557 / Git 63b6d705(2026-09-24)正是这样做的。测试期望得到“database disk image is malformed”。而此 GA 版本并未如此。
2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite、nodeHashDelete、nodeRelease、nodeAcquire、SplitNode11ce330557(2026-09-24,Claude / Ada Logics)