
SQLite 3.53.4 — высокий — локальный — UAF — rtree last_insert_rowid 0 node hash
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-rtree-rowid0
Класс: UAF Достижимость: Локальная
SQLite 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69, SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc) - Hwaci
nodeWrite хеширует новый узел rtree с iNode==0, когда sqlite3_last_insert_rowid() остаётся равным 0 после INSERT в %_node, который при этом всё ещё сообщает OK. nodeHashDelete пропускает 0. nodeRelease после SplitNode освобождает RtreeNode, пока хеш всё ещё содержит его. Последующий nodeHashLookup — это heap use-after-free. Тот же nodeWrite присутствует и на пути geopoly.
RCE не продемонстрирован. ASan READ в nodeHashLookup. Более позднее исправление в branch-3.53 — это SQLITE_CORRUPT_VTAB, когда назначенный номер узла всё ещё равен 0. Этой проверки нет в данном GA.
| ID | CVE пока нет |
| Класс | UAF (heap use-after-free для RtreeNode; RCE не продемонстрирован) |
| Достижимость | Локальная (недоверенный SQL или недоверенный .db на таблице rtree/geopoly) |
| 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 |
| Продукт | SQLite rtree / geopoly (SQLITE_ENABLE_RTREE) |
| Затронуто | 3.53.4 (b09c88c) |
| Аутентификация | SQL атакующего или внедрённый триггер теневой таблицы / 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 или внедрить BEFORE INSERT RAISE(IGNORE) на теневой таблице, затем переоткрыть соединение, чтобы last rowid соединения стал 0. Выполнять INSERT до расщепления узла. Правый сосед хешируется как узел 0, освобождается и всё ещё может быть найден.
Затем процесс-хост использует освобождённый RtreeNode. В лаборатории — AddressSanitizer heap-use-after-free. Контроль над instruction pointer в этот пакет не входит. А падение — входит.
SQLITE_VTAB_INNOCUOUS установлен, поэтому rtree всё ещё работает при выключенном trusted_schema. Компиляция по умолчанию не устанавливает SQLITE_DEFAULT_DEFENSIVE. Предварительно внедрённый триггер в недоверенном файле БД всё равно срабатывает, когда выполняется собственный INSERT pWriteNode в rtree.
Двадцать поисков по исходникам 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 fallback и 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, что является другим путём.
Уже зафиксированные неверные повороты: трактовка last_insert_rowid 0 для %_rowid как этой ошибки (это присваивает rowid ячейки 0, а не хеш-UAF); ожидание 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), BEFORE INSERT RAISE(IGNORE) на %_node, переоткрыть last_insert_rowid()==0, выполнить INSERT 52-й строки (расщепление), затем SELECT *. Контрольное расщепление без триггера проходит нормально. Привязка к 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)