
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는 %_node에 대한 INSERT가 여전히 OK를 보고한 뒤에도 sqlite3_last_insert_rowid()가 0으로 남아 있을 때 iNode==0인 새 rtree 노드를 해시한다. nodeHashDelete는 0을 건너뛴다. SplitNode 이후의 nodeRelease는 해시가 여전히 RtreeNode를 보유하고 있는데도 이를 해제한다. 이후의 nodeHashLookup은 힙 use-after-free이다. 동일한 nodeWrite가 geopoly 경로에도 있다.
RCE는 입증되지 않았다. nodeHashLookup에서 ASan READ. 이후 branch-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 또는 심어둔 섀도 테이블 트리거 / 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이다. 명령 포인터 제어는 이 팩에 없다. 크래시는 있다.
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에 대한 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을 거부하기 전에 해시를 조회한다. 해제된 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)