SQLite: Integer truncation in findOrCreateAggInfoColumn
HighCVE-2025-6965 · Published Aug 25, 2025
## Summary An integer truncation vulnerability exists in SQLite's handling of aggregate queries with a very large number of distinct column references. When the number of columns processed in an aggregate context exceeds 32,767, the index used to track these columns is truncated from a 32-bit integer to a signed 16-bit integer, resulting in a negative value ## Severity High - The exploitation of this vulnerability can lead to remote code execution and potential for significant damage. ## Proof of Concept ### Vulnerability Details An integer truncation vulnerability exists in SQLite's handling of aggregate queries with a very large number of distinct column references. When the number of columns processed in an aggregate context exceeds 32,767, the index used to track these columns is truncated from a 32-bit integer to a signed 16-bit integer, resulting in a negative value [1]. In debug builds, this invalid value leads to assertion failures [2][4]. In non-debug builds, the corrupted index is later used to access an array, leading to a heap-buffer-overflow. In sqlite3ExprCodeTarget, the out-of-bounds values are used to construct a potentially invalid VDBE instruction. In agginfoP...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| security-research Product | < 3.50.2 | 3.50.2 |
Details and references
## Summary An integer truncation vulnerability exists in SQLite's handling of aggregate queries with a very large number of distinct column references. When the number of columns processed in an aggregate context exceeds 32,767, the index used to track these columns is truncated from a 32-bit integer to a signed 16-bit integer, resulting in a negative value ## Severity High - The exploitation of this vulnerability can lead to remote code execution and potential for significant damage. ## Proof of Concept ### Vulnerability Details An integer truncation vulnerability exists in SQLite's handling of aggregate queries with a very large number of distinct column references. When the number of columns processed in an aggregate context exceeds 32,767, the index used to track these columns is truncated from a 32-bit integer to a signed 16-bit integer, resulting in a negative value [1]. In debug builds, this invalid value leads to assertion failures [2][4]. In non-debug builds, the corrupted index is later used to access an array, leading to a heap-buffer-overflow. In sqlite3ExprCodeTarget, the out-of-bounds values are used to construct a potentially invalid VDBE instruction. In agginfoPersistExprCb, the out-of-bounds index read from an array [5] is followed by an out-of-bounds write to the same index [6], leading to memory corruption. ```java static void findOrCreateAggInfoColumn( Parse *pParse, /* Parsing context */ AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */ Expr *pExpr /* Expr describing the column to find or insert */ ) { struct AggInfo_col *pCol; int k; [...] k = addAggInfoColumn(pParse->db, pAggInfo); if( k<0 ){ /* OOM on resize */ assert( pParse->db->mallocFailed ); return; } [...] pExpr->iAgg = (i16)k; // *** 1 *** } static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ int i; pInfo->aCol = sqlite3ArrayAllocate( db, pInfo->aCol, sizeof(pInfo->aCol[0]), &pInfo->nColumn, &i ); return i; } SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ [...] switch( op ){ case TK_AGG_COLUMN: { AggInfo *pAggInfo = pExpr->pAggInfo; struct AggInfo_col *pCol; assert( pAggInfo!=0 ); assert( pExpr->iAgg>=0 ); // *** 2 *** [...] pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ return AggInfoColumnReg(pAggInfo, pExpr->iAgg); }else if( pAggInfo->useSortingIdx ){ Table *pTab = pCol->pTab; sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); // *** 3 *** [...] } static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ if( ALWAYS(!ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced)) && pExpr->pAggInfo!=0 ){ AggInfo *pAggInfo = pExpr->pAggInfo; int iAgg = pExpr->iAgg; Parse *pParse = pWalker->pParse; sqlite3 *db = pParse->db; assert( iAgg>=0 ); // *** 4 *** if( pExpr->op!=TK_AGG_FUNCTION ){ if( iAgg<pAggInfo->nColumn && pAggInfo->aCol[iAgg].pCExpr==pExpr // *** 5 *** ){ pExpr = sqlite3ExprDup(db, pExpr, 0); if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ pAggInfo->aCol[iAgg].pCExpr = pExpr; // *** 6 *** } } [...] } ``` ## Further Analysis The first reproduction case triggers an out-of-bounds access in sqlite3ExprCodeTarget: ```python import math REQUIRED_REFS = 65535 COLS_PER_TABLE = 2000 GROUP_SIZE = 500 # Calculate how many times the table must be aliased. NUM_ALIASES = math.ceil(REQUIRED_REFS / COLS_PER_TABLE) # 1. Create a single wide table. create_table_sql = f"CREATE TABLE w({', '.join(f'c{i} INT' for i in range(COLS_PER_TABLE))});" insert_sql = "INSERT INTO w DEFAULT VALUES;" # 2. Build the FROM clause with self-joi
- Severity from
- GitHub (reviewed advisory)
More Google advisories
All Google| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Sep 82025 | FFmpeg - Heap-buffer-overflow write in jpeg2000dec | High | No fix yet |
| Sep 82025 | ChatGPT Agent - XSS on file://home/oai/redirect.html | Medium | No fix yet |
| Aug 182025 | OpenAI Operator - Click on arbitrary origin by TOCTOU attack | High | No fix yet |
| Aug 152025 | SQLite - Integer Overflow in FTS5 Extension | Medium | 3.50.3 |
| Aug 142025 | tar-fs Link Directory Traversal Vulnerability | Critical | 3.0.9 |
| Jul 312025 | Python Tar Filter Bypass Vulnerability | High7.5 | No fix yet |