Summary
An integer overflow exists in the FTS5 extension. It occurs when the size of an array of tombstone pointers is calculated and truncated into a 32-bit integer. A pointer to partially controlled data can then be written out of bounds.
Severity
Moderate - The overflow can be triggered by either an attacker who is able to execute arbitrary queries or an attacker that can make an application process a controlled SQLite DB file.
Proof of Concept
echo "SELECT * FROM articles WHERE articles MATCH 'whatever'" | ./sqlite3 /tmp/poc.sql
=================================================================
==3811642==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5030000012f0 at pc 0x55eafca6599b bp 0x7ffdd1591570 sp 0x7ffdd1591568
READ of size 8 at 0x5030000012f0 thread T0
Further Analysis
The vulnerability occurs in fts5SegIterAllocTombstone() when nByte is calculated as the total size for an array of pointers:
https://github.com/sqlite/sqlite/blob/d2b9cc099d66fe220eb1c4883f865ec94cb37f52/ext/fts5/fts5_index.c#L1952-L1964
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
const int nTomb = pIter->pSeg->nPgTombstone;
if( nTomb>0 ){
int nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
Fts5TombstoneArray *pNew;
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
if( pNew ){
pNew->nTombstone = nTomb;
pNew->nRef = 1;
pIter->pTombArray = pNew;
}
}
}
https://github.com/sqlite/sqlite/blob/d2b9cc099d66fe220eb1c4883f865ec94cb37f52/ext/fts5/fts5_index.c#L566-L568
/* Size (in bytes) of an Fts5TombstoneArray holding up to N tombstones */
#define SZ_FTS5TOMBSTONEARRAY(N) \
(offsetof(Fts5TombstoneArray,apTombstone)+(N)*sizeof(Fts5Data*))
nByte
is a signed 32-bit integer. While the multiplication done in the SZ_FTS5TOMBSTONEARRAY
macro is a 64-bit multiplication, the result gets truncated to 32-bit should the resulting value exceed 2^32 - 1.
pIter->pSeg->nPgTombstone
is a fully attacker controlled, 32-bit value that is stored within the metadata tables for a FTS5 table:
https://github.com/sqlite/sqlite/blob/837dc09bce7de8971c7488b70cf5da93c60fbed0/ext/fts5/fts5_index.c#L1159
i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone);
By setting nPgTombstone
to e.g. 1610612738, nByte
becomes 32. As a result, only 32 bytes are allocated for an array that is supposed to fit 1610612738 entries.
This array is then used in the fts5MultiIterIsDeleted()
function. An array index is calculated using the Rowid
of the current context and is bound checked by the number of Tombstone pointers. Since nTombstone
is the original value that was not overflowed, the iPg
entry can point out of bounds. If the entry at iPg
is 0, a structure is allocated and a pointer to it is written out of bounds:
https://github.com/sqlite/sqlite/blob/837dc09bce7de8971c7488b70cf5da93c60fbed0/ext/fts5/fts5_index.c#L3302-L3314
if( pSeg->pLeaf && pArray ){
/* Figure out which page the rowid might be present on. */
int iPg = ((u64)pSeg->iRowid) % pArray->nTombstone;
assert( iPg>=0 );
/* If tombstone hash page iPg has not yet been loaded from the
** database, load it now. */
if( pArray->apTombstone[iPg]==0 ){
pArray->apTombstone[iPg] = fts5DataRead(pIter->pIndex,
FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg)
);
if( pArray->apTombstone[iPg]==0 ) return 0;
}
Fix can be found here: https://sqlite.org/src/info/63595b74956a9391
Timeline
Date reported: 07/15/2025
Date fixed: 07/16/2025
Date disclosed: 08/15/2025
Summary
An integer overflow exists in the FTS5 extension. It occurs when the size of an array of tombstone pointers is calculated and truncated into a 32-bit integer. A pointer to partially controlled data can then be written out of bounds.
Severity
Moderate - The overflow can be triggered by either an attacker who is able to execute arbitrary queries or an attacker that can make an application process a controlled SQLite DB file.
Proof of Concept
Further Analysis
The vulnerability occurs in fts5SegIterAllocTombstone() when nByte is calculated as the total size for an array of pointers:
https://github.com/sqlite/sqlite/blob/d2b9cc099d66fe220eb1c4883f865ec94cb37f52/ext/fts5/fts5_index.c#L1952-L1964
https://github.com/sqlite/sqlite/blob/d2b9cc099d66fe220eb1c4883f865ec94cb37f52/ext/fts5/fts5_index.c#L566-L568
nByte
is a signed 32-bit integer. While the multiplication done in theSZ_FTS5TOMBSTONEARRAY
macro is a 64-bit multiplication, the result gets truncated to 32-bit should the resulting value exceed 2^32 - 1.pIter->pSeg->nPgTombstone
is a fully attacker controlled, 32-bit value that is stored within the metadata tables for a FTS5 table:https://github.com/sqlite/sqlite/blob/837dc09bce7de8971c7488b70cf5da93c60fbed0/ext/fts5/fts5_index.c#L1159
i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone);
By setting
nPgTombstone
to e.g. 1610612738,nByte
becomes 32. As a result, only 32 bytes are allocated for an array that is supposed to fit 1610612738 entries.This array is then used in the
fts5MultiIterIsDeleted()
function. An array index is calculated using theRowid
of the current context and is bound checked by the number of Tombstone pointers. SincenTombstone
is the original value that was not overflowed, theiPg
entry can point out of bounds. If the entry atiPg
is 0, a structure is allocated and a pointer to it is written out of bounds:https://github.com/sqlite/sqlite/blob/837dc09bce7de8971c7488b70cf5da93c60fbed0/ext/fts5/fts5_index.c#L3302-L3314
Fix can be found here: https://sqlite.org/src/info/63595b74956a9391
Timeline
Date reported: 07/15/2025
Date fixed: 07/16/2025
Date disclosed: 08/15/2025