SQLite: Integer truncation in findOrCreateAggInfoColumn
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(...
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)