
SQLite 3.53.4 - 高 - ローカル - UAF - rtree last_insert_rowid 0 ノードハッシュ
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
Class: UAF Reach: Local
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite は、%_node への INSERT 後も sqlite3_last_insert_rowid() が 0 のままであるにもかかわらず OK を返す場合に、iNode==0 の新しい rtree ノードをハッシュ化する。nodeHashDelete は 0 をスキップする。SplitNode 後の nodeRelease は、ハッシュがまだ保持している RtreeNode を解放する。その後の nodeHashLookup はヒープの use-after-free となる。同じ nodeWrite は geopoly パス上にも存在する。
RCE は実証されていない。ASan は nodeHashLookup で READ を報告する。後の branch-3.53 の修正は、割り当てられたノード番号が依然として 0 の場合に SQLITE_CORRUPT_VTAB を返すというものである。そのチェックはこの GA には存在しない。
| ID | CVE 未割り当て |
| Class | UAF (RtreeNode のヒープ use-after-free、RCE は未実証) |
| Reach | Local (rtree/geopoly テーブル上の信頼できない SQL または信頼できない .db) |
| CWE | CWE-416 |
| CVSS | High: 8.1 CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Product | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Affected | 3.53.4 (b09c88c) |
| Auth | 攻撃者の SQL、または仕込まれたシャドウテーブルトリガ / WITHOUT ROWID %_node |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18890 のみ。ASan による証拠。エクスプロイトペイロードなし。 |
rtree または geopoly テーブル上で、ノード INSERT が OK を報告する一方で last_insert_rowid() が 0 のままになるようにする。文書化されている方法: %_node を WITHOUT ROWID に置き換える、またはシャドウテーブルに BEFORE INSERT RAISE(IGNORE) を仕込み、その後再接続して接続の last rowid を 0 にする。ノードが分割されるまで INSERT する。右側の兄弟ノードはノード 0 としてハッシュ化され、解放され、それでも発見可能なままとなる。
その後、ホストプロセスは解放済みの RtreeNode を使用する。ラボでは AddressSanitizer の heap-use-after-free が確認される。命令ポインタの制御はこのパックには含まれない。クラッシュは含まれる。
SQLITE_VTAB_INNOCUOUS が設定されているため、trusted_schema がオフでも rtree は実行される。デフォルトのコンパイルでは SQLITE_DEFAULT_DEFENSIVE は設定されない。信頼できない DB ファイルに事前に仕込まれたトリガは、rtree 自身の pWriteNode INSERT が実行されるときにも依然として実行される。
https://github.com/sqlite/sqlite のタグ https://github.com/sqlite/sqlite/tree/version-3.53.4 を 20 回にわたりソース探索した。正規のツリーは 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 ではない); このピンで SQLITE_CORRUPT_VTAB を期待すること (それは後の修正である); 分割が起きないように 50 セルを埋めること; トリガを仕込んで再接続しないこと; lookaside が ASan から解放を隠すこと; クラッシュを RCE と呼ぶこと。
cd lab
./run.sh
-DSQLITE_ENABLE_RTREE=1 と -fsanitize=address を付けた Amalgamation 3.53.4。ベンダーの rtreeI.test 2.0-2.2: 51 行を埋め (RTREE_MAXCELLS)、%_node に BEFORE INSERT RAISE(IGNORE) を仕込み、再接続して last_insert_rowid()==0 とし、52 行目を INSERT し (分割)、その後 SELECT * を実行する。トリガなしの対照分割は OK である。127.0.0.1:18890 にバインドする。ハーネスは 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 はそうしない。
b09c88c14082339b66c7b7158d609a771e64ca69)2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bccnodeWrite、nodeHashDelete、nodeRelease、nodeAcquire、SplitNode11ce330557 (2026-09-24, Claude / Ada Logics)