From 31a9e5c5d96bf8d220fb1eb5ec30c5afc4f657d1 Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 2 Jun 2026 18:23:55 +0000 Subject: [PATCH 01/12] Update the session module so that it can apply changesets containing two or more UPDATE changes that form a dependency loop - so that no single UPDATE can be applied independently without violating a constraint. FossilOrigin-Name: 919d393a3bc483bf58be1f8d6c2ef70f570d63cc9ad8d8df6a6562fb270ea7e5 --- ext/session/sessionG.test | 5 +- ext/session/sessionconflict2.test | 298 +++++++++++++++++++++++ ext/session/sessionfault3.test | 41 ++++ ext/session/sqlite3session.c | 390 +++++++++++++++++++++++++++--- ext/session/sqlite3session.h | 12 + ext/session/test_session.c | 3 + manifest | 24 +- manifest.uuid | 2 +- 8 files changed, 731 insertions(+), 44 deletions(-) create mode 100755 ext/session/sessionconflict2.test diff --git a/ext/session/sessionG.test b/ext/session/sessionG.test index 1ebcc926a5..58713a5b62 100644 --- a/ext/session/sessionG.test +++ b/ext/session/sessionG.test @@ -82,6 +82,9 @@ do_test 2.2.1 { # It is not possible to apply the changeset generated by the following # SQL, as none of the three updated rows may be updated as part of the # first pass. + # + # UPDATE 19/05/2026 - it is now possible to apply such an update. + # do_then_apply_sql -ignorenoop { UPDATE t1 SET b=0 WHERE a=1; UPDATE t1 SET b=1 WHERE a=2; @@ -89,7 +92,7 @@ do_test 2.2.1 { UPDATE t1 SET b=3 WHERE a=1; } db2 eval { SELECT a, b FROM t1 } -} {1 1 2 2 3 3} +} {1 3 2 1 3 2} do_test 2.2.2 { db eval { SELECT a, b FROM t1 } } {1 3 2 1 3 2} #------------------------------------------------------------------------- diff --git a/ext/session/sessionconflict2.test b/ext/session/sessionconflict2.test new file mode 100755 index 0000000000..d3d28bb5d6 --- /dev/null +++ b/ext/session/sessionconflict2.test @@ -0,0 +1,298 @@ +# 2026 May 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. +# + +if {![info exists testdir]} { + set testdir [file join [file dirname [info script]] .. .. test] +} +source [file join [file dirname [info script]] session_common.tcl] +source $testdir/tester.tcl +ifcapable !session {finish_test; return} + +set testprefix sessionconflict2 + +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 1.0 { + do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + } +} {} + +do_test 1.1 { + do_then_apply_sql { + UPDATE t1 SET c=NULL WHERE a=1; + UPDATE t1 SET c=1 WHERE a=3; + UPDATE t1 SET c=3 WHERE a=1; + } +} {} + +do_execsql_test -db db 1.2 { + SELECT rowid, * FROM t1 +} { + 1 1 1 3 + 2 2 2 2 + 3 3 3 1 +} + +do_execsql_test -db db2 1.3 { + SELECT rowid, * FROM t1 +} { + 1 1 1 3 + 2 2 2 2 + 3 3 3 1 +} + +#-------------------------------------------------------------------------- +reset_db +db2 close +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 2.0 { + do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b, c UNIQUE) WITHOUT ROWID; + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + } +} {} + +do_test 2.1 { + do_then_apply_sql { + UPDATE t1 SET c=NULL WHERE a=1; + UPDATE t1 SET c=1 WHERE a=3; + UPDATE t1 SET c=3 WHERE a=1; + } +} {} + +do_execsql_test -db db 2.2 { + SELECT * FROM t1 +} { + 1 1 3 + 2 2 2 + 3 3 1 +} + +do_execsql_test -db db2 2.3 { + SELECT * FROM t1 +} { + 1 1 3 + 2 2 2 + 3 3 1 +} + +#-------------------------------------------------------------------------- +reset_db +db2 close +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 3.0 { + do_common_sql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c UNIQUE); + INSERT INTO t1 VALUES(1, 1, 1); + INSERT INTO t1 VALUES(2, 2, 2); + INSERT INTO t1 VALUES(3, 3, 3); + } +} {} + +do_test 3.1 { + do_then_apply_sql { + UPDATE t1 SET c=NULL WHERE a=1; + UPDATE t1 SET c=1 WHERE a=3; + UPDATE t1 SET c=3 WHERE a=1; + } +} {} + +do_execsql_test -db db 3.2 { + SELECT rowid, * FROM t1 +} { + 1 1 1 3 + 2 2 2 2 + 3 3 3 1 +} + +do_execsql_test -db db2 3.3 { + SELECT rowid, * FROM t1 +} { + 1 1 1 3 + 2 2 2 2 + 3 3 3 1 +} + +#------------------------------------------------------------------------- +db2 close +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 + +set ::conflict_list [list] +proc xConflict {args} { + lappend ::conflict_list $args + return "OMIT" +} + +proc do_conflict_test {tn bNoUpdateLoop script clist} { + + uplevel [list do_test $tn.1 [subst -nocommands { + sqlite3session S db "main" + S attach * + eval {$script} + set ::changeset [S changeset] + S delete + }] {}] + + if {$bNoUpdateLoop} { + uplevel [list do_test $tn.2.no { + set ::conflict_list [list] + sqlite3changeset_apply_v2 -noupdateloop db2 $::changeset xConflict + set ::conflict_list + } [list {*}$clist]] + } else { + uplevel [list do_test $tn.2 { + set ::conflict_list [list] + sqlite3changeset_apply_v2 db2 $::changeset xConflict + set ::conflict_list + } [list {*}$clist]] + } +} + +do_test 4.0 { + do_common_sql { + CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE); + WITH s(i) AS ( + SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10 + ) + INSERT INTO t1 SELECT i, i, i, i FROM s; + } +} {} + +proc swap {tbl pkcol valcol pk1 pk2} { + set val1 [db one "SELECT $valcol FROM $tbl WHERE $pkcol = \$pk1"] + set val2 [db one "SELECT $valcol FROM $tbl WHERE $pkcol = \$pk2"] + + db eval " + UPDATE $tbl SET $valcol = NULL WHERE $pkcol IN (\$pk1, \$pk2); + UPDATE $tbl SET $valcol = \$val2 WHERE $pkcol = \$pk1; + UPDATE $tbl SET $valcol = \$val1 WHERE $pkcol = \$pk2; + " +} + +do_conflict_test 4.1.1 0 { + swap t1 a c 4 5 + swap t1 a c 2 3 + swap t1 a c 8 1 +} { +} + +do_execsql_test -db db 4.1.2 { + SELECT a, c FROM t1 +} { + 1 8 2 3 3 2 4 5 5 4 6 6 7 7 8 1 9 9 10 10 +} +do_execsql_test -db db2 4.1.3 { + SELECT a, c FROM t1 +} { + 1 8 2 3 3 2 4 5 5 4 6 6 7 7 8 1 9 9 10 10 +} + +do_conflict_test 4.2.1 0 { + swap t1 a d 10 9 + swap t1 a d 8 7 + swap t1 a d 7 6 + swap t1 a d 5 4 + swap t1 a d 4 8 +} { +} + +do_execsql_test -db db 4.2.2 { + SELECT a, d FROM t1 +} { + 1 1 2 2 3 3 4 7 5 4 6 8 7 6 8 5 9 10 10 9 +} +do_execsql_test -db db2 4.2.3 { + SELECT a, d FROM t1 +} { + 1 1 2 2 3 3 4 7 5 4 6 8 7 6 8 5 9 10 10 9 +} + +do_execsql_test -db db2 4.3 { + INSERT INTO t1(a, b, c, d) VALUES(11, 11, 11, 11); +} + +do_conflict_test 4.3.1 0 { + swap t1 a c 3 6 + db eval { UPDATE t1 SET d=11 WHERE a=10; } + swap t1 a d 4 8 +} { + {UPDATE t1 CONSTRAINT {i 10 {} {} {} {} i 9} {{} {} {} {} {} {} i 11}} +} + +do_conflict_test 4.3.2 0 { + swap t1 a c 1 2 + swap t1 a c 3 4 + db eval { UPDATE t1 SET c=11 WHERE a=10; } + swap t1 a d 5 6 + swap t1 a d 4 5 +} { + {UPDATE t1 CONSTRAINT {i 10 {} {} i 10 {} {}} {{} {} {} {} i 11 {} {}}} +} + +do_conflict_test 4.3.3 0 { + swap t1 a c 1 2 + swap t1 a c 2 3 + swap t1 a c 3 4 + swap t1 a c 4 1 +} { +} +db2 close + +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 + +do_test 5.0 { + do_common_sql { + CREATE TABLE t1(a INT PRIMARY KEY, b UNIQUE); + INSERT INTO t1 VALUES('one', 'one'); + INSERT INTO t1 VALUES('two', 'two'); + INSERT INTO t1 VALUES('three', 'three'); + INSERT INTO t1 VALUES('four', 'four'); + INSERT INTO t1 VALUES('five', 'five'); + } +} {} + +do_conflict_test 5.1 1 { +} { +} + +do_conflict_test 5.2 1 { + swap t1 a b one two +} { + {UPDATE t1 CONSTRAINT {t two t two} {{} {} t one}} + {UPDATE t1 CONSTRAINT {t one t one} {{} {} t two}} +} + +do_conflict_test 5.2 0 { + swap t1 a b four five +} { +} + + +finish_test + diff --git a/ext/session/sessionfault3.test b/ext/session/sessionfault3.test index f2bcb89417..3e7dc6167e 100644 --- a/ext/session/sessionfault3.test +++ b/ext/session/sessionfault3.test @@ -94,4 +94,45 @@ do_faultsim_test 2 -faults oom-t* -prep { catch { S delete } } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(a PRIMARY KEY, b UNIQUE); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); + INSERT INTO t1 VALUES(4, 'four'); + INSERT INTO t1 VALUES(5, 'five'); +} +faultsim_save_and_close +faultsim_restore_and_reopen + +set C [changeset_from_sql { + UPDATE t1 SET b=NULL WHERE a IN (1, 3, 5); + UPDATE t1 SET b='three' WHERE a=1; + UPDATE t1 SET b='five' WHERE a=3; + UPDATE t1 SET b='one' WHERE a=5; +}] + +do_execsql_test 3.1 { + SELECT * FROM t1 +} { + 1 three 2 two 3 five 4 four 5 one +} + +proc xConflict {args} { + lappend ::conflict_list $args + return "OMIT" +} + +do_faultsim_test 3 -faults oom* -prep { + faultsim_restore_and_reopen + db eval {SELECT * FROM sqlite_schema} +} -body { + sqlite3changeset_apply_v2 db $::C xConflict + set {} {} +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} + finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 3634013ac4..c94664cf84 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -1545,6 +1545,16 @@ static int sessionPrepareDfltStmt( return rc; } +/* +** Finalize statement pStmt. If (*pRc) is SQLITE_OK when this function is +** called, set it to the results of the sqlite3_finalize() call. Or, if +** it is already set to an error code, leave it as is. +*/ +static void sessionFinalizeStmt(sqlite3_stmt *pStmt, int *pRc){ + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ) *pRc = rc; +} + /* ** Table pTab has one or more existing change-records with old.* records ** with fewer than pTab->nCol columns. This function updates all such @@ -1567,9 +1577,8 @@ static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){ } } + sessionFinalizeStmt(pStmt, &rc); pSession->rc = rc; - rc = sqlite3_finalize(pStmt); - if( pSession->rc==SQLITE_OK ) pSession->rc = rc; return pSession->rc; } @@ -2895,11 +2904,11 @@ static int sessionSelectStmt( ); sessionAppendStr(&cols, "tbl, ?2, stat", &rc); }else{ - #if 0 +#if 0 if( bRowid ){ sessionAppendStr(&cols, SESSIONS_ROWID, &rc); } - #endif +#endif for(i=0; iin.aData[pIter->in.iCurrent]; int nBlob = pIter->in.iNext - pIter->in.iCurrent; sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); - return SQLITE_OK; + return rc; }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE || eType==SQLITE_CHANGESET_CONFLICT ){ @@ -5176,7 +5186,264 @@ static int sessionApplyOneWithRetry( } /* -** Retry the changes accumulated in the pApply->constraints buffer. +** Create an iterator to iterate through the retry buffer pRetry. +*/ +static int sessionRetryIterInit( + SessionBuffer *pRetry, /* Buffer to iterate through */ + int bPatchset, /* True for patchset, false for changeset */ + const char *zTab, /* Table name */ + SessionApplyCtx *pApply, /* Session apply context */ + sqlite3_changeset_iter **ppIter /* OUT: New iterator */ +){ + sqlite3_changeset_iter *pRet = 0; + int rc = SQLITE_OK; + + rc = sessionChangesetStart( + &pRet, 0, 0, pRetry->nBuf, pRetry->aBuf, pApply->bInvertConstraints, 1 + ); + if( rc==SQLITE_OK ){ + size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); + pRet->bPatchset = bPatchset; + pRet->zTab = (char*)zTab; + pRet->nCol = pApply->nCol; + pRet->abPK = pApply->abPK; + sessionBufferGrow(&pRet->tblhdr, nByte, &rc); + pRet->apValue = (sqlite3_value**)pRet->tblhdr.aBuf; + if( rc==SQLITE_OK ){ + memset(pRet->apValue, 0, nByte); + }else{ + sqlite3changeset_finalize(pRet); + pRet = 0; + } + } + + *ppIter = pRet; + return rc; +} + +/* +** Attempt to apply all the changes in retry buffer pRetry to the database. +** Except, if parameter iSkip is greater than or equal to 0, skip change +** iSkip. +*/ +static int sessionApplyRetryBuffer( + SessionBuffer *pRetry, /* Buffer to apply changes from */ + int iSkip, /* If >=0, index of change to omit */ + sqlite3 *db, /* Database handle */ + int bPatchset, /* True for patchset, false for changeset */ + const char *zTab, /* Name of table to write to */ + SessionApplyCtx *pApply, /* Apply context */ + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int rc = SQLITE_OK; + int rc2 = SQLITE_OK; + int ii = 0; + sqlite3_changeset_iter *pIter = 0; + + assert( pApply->constraints.nBuf==0 ); + + rc = sessionRetryIterInit(pRetry, bPatchset, zTab, pApply, &pIter); + + for(ii=0; rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter); ii++){ + if( ii!=iSkip ){ + rc = sessionApplyOneWithRetry(db, pIter, pApply, xConflict, pCtx); + } + } + + rc2 = sqlite3changeset_finalize(pIter); + if( rc==SQLITE_OK ) rc = rc2; + assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); + + return rc; +} + +/* +** Check if table zTab in the "main" database of db is a WITHOUT ROWID +** table. +** +** If no error occurs, return SQLITE_OK and set output variable (*pbWR) to +** true if zTab is a WITHOUT ROWID table, or false otherwise. Or, if an +** error does occur, return an SQLite error code. The final value of (*pbWR) +** is undefined in this case. +*/ +static int sessionTableIsWithoutRowid(sqlite3 *db, const char *zTab, int *pbWR){ + sqlite3_stmt *pList = 0; + char *zSql = 0; + int rc = SQLITE_OK; + + zSql = sqlite3_mprintf("PRAGMA table_list = %Q", zTab); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &pList, 0); + sqlite3_free(zSql); + } + + if( rc==SQLITE_OK ){ + sqlite3_step(pList); + *pbWR = sqlite3_column_int(pList, 4); + rc = sqlite3_finalize(pList); + } + + return rc; +} + +/* +** Iterator pUp points to an UPDATE change. This function deletes the +** affected row from the database and creates an INSERT statement that +** may be used to reinsert the row as it is after the UPDATE change +** has been applied. +** +** If successful, SQLITE_OK is returned and output variable (*ppInsert) +** is left pointing to a prepared INSERT statement. It is the responsibility +** of the caller to eventually free this statement using sqlite3_finalize(). +** Or, if an error occurs, an SQLite error code is returned and (*ppInsert) +** set to NULL. pApply->zErr may be set to an error message in this case. +*/ +static int sessionUpdateToDeleteInsert( + sqlite3 *db, /* Database to write to */ + const char *zTab, /* Table name */ + SessionApplyCtx *pApply, /* Apply context */ + sqlite3_changeset_iter *pUp, /* Iterator pointing to UPDATE change */ + sqlite3_stmt **ppInsert /* OUT: INSERT statement */ +){ + sqlite3_stmt *pRet = 0; /* The INSERT statement */ + sqlite3_stmt *pSelect = 0; /* SELECT to read current values of row */ + int rc = SQLITE_OK; + int bWR = 0; + + rc = sessionTableIsWithoutRowid(db, zTab, &bWR); + if( rc==SQLITE_OK ){ + char *zSelect = 0; + char *zInsert = 0; + SessionBuffer cols = {0, 0, 0}; + SessionBuffer insbind = {0, 0, 0}; + SessionBuffer pkcols = {0, 0, 0}; + SessionBuffer selbind = {0, 0, 0}; + + const char *zComma = ""; + const char *zComma2 = ""; + int ii; + for(ii=0; iinCol; ii++){ + sessionAppendStr(&cols, zComma, &rc); + sessionAppendIdent(&cols, pApply->azCol[ii], &rc); + sessionAppendStr(&insbind, zComma, &rc); + sessionAppendStr(&insbind, "?", &rc); + zComma = ", "; + + if( pApply->abPK[ii] ){ + sessionAppendStr(&pkcols, zComma2, &rc); + sessionAppendIdent(&pkcols, pApply->azCol[ii], &rc); + sessionAppendStr(&selbind, zComma2, &rc); + sessionAppendPrintf(&selbind, &rc, "?%d", ii+1); + zComma2 = ", "; + } + } + if( bWR==0 ){ + sessionAppendStr(&cols, zComma, &rc); + sessionAppendStr(&cols, SESSIONS_ROWID, &rc); + sessionAppendStr(&insbind, zComma, &rc); + sessionAppendStr(&insbind, "?", &rc); + } + + if( rc==SQLITE_OK ){ + zSelect = sqlite3_mprintf("SELECT %s FROM %Q WHERE (%s) IS (%s)", + cols.aBuf, zTab, pkcols.aBuf, selbind.aBuf + ); + if( zSelect==0 ) rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + zInsert = sqlite3_mprintf("INSERT INTO %Q(%s) VALUES(%s)", + zTab, cols.aBuf, insbind.aBuf + ); + if( zInsert==0 ) rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &pSelect, &pApply->zErr, zSelect); + } + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &pRet, &pApply->zErr, zInsert); + } + + sqlite3_free(zSelect); + sqlite3_free(zInsert); + sqlite3_free(cols.aBuf); + sqlite3_free(insbind.aBuf); + sqlite3_free(pkcols.aBuf); + sqlite3_free(selbind.aBuf); + } + + if( rc==SQLITE_OK ){ + rc = sessionBindRow( + pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pSelect + ); + } + + if( rc==SQLITE_OK && sqlite3_step(pSelect)==SQLITE_ROW ){ + int iCol; + for(iCol=0; iColnCol; iCol++){ + sqlite3_value *pVal = pUp->apValue[iCol+pApply->nCol]; + if( pVal==0 ){ + pVal = sqlite3_column_value(pSelect, iCol); + } + rc = sqlite3_bind_value(pRet, iCol+1, pVal); + } + if( bWR==0 ){ + sqlite3_bind_int64(pRet, iCol+1, sqlite3_column_int64(pSelect, iCol)); + } + } + sessionFinalizeStmt(pSelect, &rc); + + /* Delete the row from the database. */ + if( rc==SQLITE_OK ){ + rc = sessionBindRow( + pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pApply->pDelete + ); + sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pApply->pDelete); + rc = sqlite3_reset(pApply->pDelete); + } + + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pRet); + pRet = 0; + } + + *ppInsert = pRet; + return rc; +} + +/* +** Retry the changes accumulated in the pApply->constraints buffer. The +** pApply->constraints buffer contains all changes to table zTab that +** could not be applied due to SQLITE_CONSTRAINT errors. This function +** attempts to apply them as follows: +** +** 1) It runs through the buffer and attempts to retry each change, +** removing any that are successfully applied from the buffer. This +** is repeated until no further progress can be made. +** +** 2) For each UPDATE change in the buffer, try the following in a +** savepoint transaction: +** +** a) DELETE the affected row, +** b) Attempt step (1) with remaining changes, +** c) Attempt to INSERT a row equivalent to the one that would be +** created by applying this UPDATE change. +** +** If the INSERT in (c) succeeds, the savepoint is committed and all +** successfully applied changes are removed from the buffer. Step (2) +** is then repeated. +** +** 3) Once step (2) has been attempted for each UPDATE in the change, +** a final attempt is made to apply each remaining change. This time, +** if an SQLITE_CONSTRAINT error is encountered, the conflict handler +** is invoked and the user has to decide whether to omit the change +** or rollback the entire _apply() operation. */ static int sessionRetryConstraints( sqlite3 *db, @@ -5187,41 +5454,101 @@ static int sessionRetryConstraints( void *pCtx /* First argument passed to xConflict */ ){ int rc = SQLITE_OK; + int iUpdate = 0; + /* Step (1) */ while( pApply->constraints.nBuf ){ - sqlite3_changeset_iter *pIter2 = 0; SessionBuffer cons = pApply->constraints; memset(&pApply->constraints, 0, sizeof(SessionBuffer)); - rc = sessionChangesetStart( - &pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1 + rc = sessionApplyRetryBuffer( + &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx ); - if( rc==SQLITE_OK ){ - size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); - int rc2; - pIter2->bPatchset = bPatchset; - pIter2->zTab = (char*)zTab; - pIter2->nCol = pApply->nCol; - pIter2->abPK = pApply->abPK; - sessionBufferGrow(&pIter2->tblhdr, nByte, &rc); - pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf; - if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); - - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ - rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); - } - - rc2 = sqlite3changeset_finalize(pIter2); - if( rc==SQLITE_OK ) rc = rc2; - } - assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); sqlite3_free(cons.aBuf); if( rc!=SQLITE_OK ) break; - if( pApply->constraints.nBuf>=cons.nBuf ){ - /* No progress was made on the last round. */ - pApply->bDeferConstraints = 0; + + /* If no progress has been made this round, break out of the loop. */ + if( pApply->constraints.nBuf>=cons.nBuf ) break; + } + + /* Step (2) */ + while( rc==SQLITE_OK && pApply->constraints.nBuf && !pApply->bNoUpdateLoop ){ + SessionBuffer cons = {0, 0, 0}; + sqlite3_changeset_iter *pUp = 0; + sqlite3_stmt *pInsert = 0; + int iSkip = 0; + + rc = sessionRetryIterInit( + &pApply->constraints, bPatchset, zTab, pApply, &pUp + ); + if( rc==SQLITE_OK ){ + int iThis = -1; + while( SQLITE_ROW==sqlite3changeset_next(pUp) ){ + if( pUp->op==SQLITE_UPDATE ) iThis++; + if( iThis==iUpdate ) break; + iSkip++; + } + if( iThis==iUpdate ){ + rc = sqlite3_exec(db, "SAVEPOINT update_op", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sessionUpdateToDeleteInsert(db, zTab, pApply, pUp, &pInsert); + } + } + sqlite3changeset_finalize(pUp); + if( iThis!=iUpdate ) break; } + + if( rc==SQLITE_OK ){ + cons = pApply->constraints; + + while( rc==SQLITE_OK && pApply->constraints.nBuf>0 ){ + SessionBuffer app = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + rc = sessionApplyRetryBuffer( + &app, iSkip, db, bPatchset, zTab, pApply, xConflict, pCtx + ); + if( app.aBuf!=cons.aBuf ){ + sqlite3_free(app.aBuf); + } + if( pApply->constraints.nBuf>=app.nBuf ){ + break; + } + iSkip = -1; + } + } + + iUpdate++; + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_finalize(pInsert); + if( rc==SQLITE_CONSTRAINT ){ + rc = sqlite3_exec(db, "ROLLBACK TO update_op", 0, 0, 0); + sqlite3_free(pApply->constraints.aBuf); + pApply->constraints = cons; + memset(&cons, 0, sizeof(cons)); + }else if( rc==SQLITE_OK ){ + iUpdate = 0; + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE update_op", 0, 0, 0); + } + }else{ + sqlite3_finalize(pInsert); + } + + sqlite3_free(cons.aBuf); + } + + /* Step (3) */ + if( rc==SQLITE_OK && pApply->constraints.nBuf ){ + SessionBuffer cons = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + pApply->bDeferConstraints = 0; + rc = sessionApplyRetryBuffer( + &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx + ); + sqlite3_free(cons.aBuf); } return rc; @@ -5275,6 +5602,7 @@ static int sessionChangesetApply( sApply.bRebase = (ppRebase && pnRebase); sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); + sApply.bNoUpdateLoop = !!(flags & SQLITE_CHANGESETAPPLY_NOUPDATELOOP); if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); } diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index fb2336d326..045a1ffeef 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1366,11 +1366,23 @@ int sqlite3changeset_apply_v3( ** database behave as if they were declared with "ON UPDATE NO ACTION ON ** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL ** or SET DEFAULT. +** +**
SQLITE_CHANGESETAPPLY_NOUPDATELOOP
+** Sometimes, a changeset contains two or more update statements such that +** although after applying all updates the database will contain no +** constraint violations, no single update can be applied before the others. +** The simplest example of this is a pair of UPDATEs that have "swapped" +** two column values with a UNIQUE constraint. +**

+** Usually, sqlite3changeset_apply() and similar functions work hard to try +** to find a way to apply such a changeset. However, if this flag is set, +** then all such updates are considered CONSTRAINT conflicts. */ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 #define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 #define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 +#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010 /* ** CAPI3REF: Constants Passed To The Conflict Handler diff --git a/ext/session/test_session.c b/ext/session/test_session.c index be516e5825..47e50caa74 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -914,6 +914,9 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( } else if( n>2 && n<=11 && 0==sqlite3_strnicmp("-ignorenoop", z1, n) ){ flags |= SQLITE_CHANGESETAPPLY_IGNORENOOP; + } + else if( n>3 && n<=13 && 0==sqlite3_strnicmp("-noupdateloop", z1, n) ){ + flags |= SQLITE_CHANGESETAPPLY_NOUPDATELOOP; }else{ break; } diff --git a/manifest b/manifest index 4e2ac9b887..e9edbcbd68 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sa\sNEVER()\sthat\sis\sactually\sreachable.\s\sThis\ssame\sNEVER()\swas\sremoved\nfrom\strunk\sat\scheck-in\s[0de3d95500b7ecd4]. -D 2026-06-02T13:44:21.252 +C Update\sthe\ssession\smodule\sso\sthat\sit\scan\sapply\schangesets\scontaining\stwo\sor\smore\sUPDATE\schanges\sthat\sform\sa\sdependency\sloop\s-\sso\sthat\sno\ssingle\sUPDATE\scan\sbe\sapplied\sindependently\swithout\sviolating\sa\sconstraint. +D 2026-06-02T18:23:55.109 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -544,7 +544,7 @@ F ext/session/sessionC.test de98b5e173fd86c79af0d0541534398d2ea75dc0d5d74a00103e F ext/session/sessionD.test 470ff917dc849e2eb78142ade63aaabd729d773833cff0ff01bca0eda68a21ce F ext/session/sessionE.test b2010949c9d7415306f64e3c2072ddabc4b8250c98478d3c0c4d064bce83111d F ext/session/sessionF.test d37ed800881e742c208df443537bf29aa49fd56eac520d0f0c6df3e6320f3401 -F ext/session/sessionG.test 3efe388282d641b65485b5462e67851002cd91a282dc95b685d085eb8efdad0a +F ext/session/sessionG.test 64c2b69531aebdb36d5977a5f832d77e4c8bda0c746a6c630adf23660bb1c7c2 F ext/session/sessionH.test 71bbff6b1abb2c4ac62b84dee53273c37e0b21e5fde3aed80929403e091ef859 F ext/session/sessionI.test 11e7b6729fc942982a5104a40132f70a2e964d64d60dc5809b8206465af74822 F ext/session/session_common.tcl a31f537a929a695a852d241c9434f2847cadf329856401921139fbb03a5a7697 @@ -557,10 +557,11 @@ F ext/session/sessionblob.test 87faf667870b72f08e91969abd9f52a383ab7b514506ee194 F ext/session/sessionchange.test 6618cb1c1338a4b6df173b6ac42d09623fb71269962abf23ebb7617fe9f45a50 F ext/session/sessionchange2.test 6b5b7e3d1cc4ede43817f7fb68e3771aac4aa6500adb21a458b3e5a9fd841f83 F ext/session/sessionconflict.test 19e4a53795c4c930bfec49e809311e09b2a9e202d9446e56d7a8b139046a0c07 x +F ext/session/sessionconflict2.test d7f4caf59360dbca8a4698b9a3a322adf6f547f810ad41d131cacb730e02dd5e x F ext/session/sessiondiff.test e89f7aedcdd89e5ebac3a455224eb553a171e9586fc3e1e6a7b3388d2648ba8d F ext/session/sessionfault.test c2b43d01213b389a3f518e90775fca2120812ba51e50444c4066962263e45c11 F ext/session/sessionfault2.test b0d6a7c1d7398a7e800d84657404909c7d385965ea8576dc79ed344c46fbf41c -F ext/session/sessionfault3.test 9397819ec25b0960c5bc03c78613f9cb5cacc970f83e817aec1775c2a839a787 +F ext/session/sessionfault3.test aea5331fa6dbe5ca4e19826605e624c0e1767545411479f27c5ef82b41046925 F ext/session/sessioninvert.test 7ccb7609a2c11e4e13e606df439bf3d484ba8e455d0bd3aa8d4828a940e1a242 x F ext/session/sessionmem.test f2a735db84a3e9e19f571033b725b0b2daf847f3f28b1da55a0c1a4e74f1de09 F ext/session/sessionnoact.test 2cf060c12a7a23e663f0ec796561e58638c5c10a846653d37be886414b06ddc9 @@ -571,9 +572,9 @@ F ext/session/sessionrowid.test 85187c2f1b38861a5844868126f69f9ec62223a03449a98a F ext/session/sessionsize.test 8fcf4685993c3dbaa46a24183940ab9f5aa9ed0d23e5fb63bfffbdb56134b795 F ext/session/sessionstat1.test 5e718d5888c0c49bbb33a7a4f816366db85f59f6a4f97544a806421b85dc2dec F ext/session/sessionwor.test 6fd9a2256442cebde5b2284936ae9e0d54bde692d0f5fd009ecef8511f4cf3fc -F ext/session/sqlite3session.c e36c91f273e4d2ce11c9e3aaba160038c9703cda1feeb79a96bb00f3de1a6d5e -F ext/session/sqlite3session.h 063e7bf7be2fff874456f452a224b5b3013b25682d108933b0351c93a1279b9c -F ext/session/test_session.c 9435a0d2c67b6c693bbf943657eeb83198efe06f796de80a6fd563013fa20bcc +F ext/session/sqlite3session.c 1010718d9d88eeb1efd1bf3d6bcbc85e598e9d5bb094c2d202515622d2233481 +F ext/session/sqlite3session.h ca7c4422c1514a95056cc8d333217df6b1829d39058126b1de85d10cd62d7a9c +F ext/session/test_session.c 95fbf8fc721fdb1a0d00268f930d6763c5299ed766b317f9dd3cf9ca5262e337 F ext/wasm/GNUmakefile 68c750f173106d9d63f12c1edf1256c6f4bad9894b155da5db64322f4912de4b F ext/wasm/README-dist.txt f01081a850ce38a56706af6b481e3a7878e24e42b314cfcd4b129f0f8427066a F ext/wasm/README.md 2e87804e12c98f1d194b7a06162a88441d33bb443efcfe00dc6565a780d2f259 @@ -2198,8 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 4c0455efe57d1e3c27327e942a1509de3eb7b0902bc3b90473e2cba8df90139b -R 14c268820e8475568c33473b72a882db -U drh -Z 72720f8bcf018c13d516ed36dbb636da +P 46e74947f155199f5ce2440e8d5f19849d0daeb1d9d47381b0f84388901eab4c +Q +32c762bbb187e3bc964dcaad0949ebeb4da21331be842a5868feeac6088790ca +R e809b7ef1093cc510f207b67ac790177 +U dan +Z ca58af6e9f4882ad16a9a2a61f15e915 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8c59dda39d..980f76555b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -46e74947f155199f5ce2440e8d5f19849d0daeb1d9d47381b0f84388901eab4c +919d393a3bc483bf58be1f8d6c2ef70f570d63cc9ad8d8df6a6562fb270ea7e5 From 42be6fdf229bebd450d7fc501cfa5ac0392f0f58 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 11:05:22 +0000 Subject: [PATCH 02/12] Make the (unused, untested, and unsupported) ext/misc/compress.c routines responsive to OOM conditions. FossilOrigin-Name: 2d1dc1c6c679d920e4f5d79acefbf318183ef37bc65bc78dc50f8512f317c1f5 --- ext/misc/compress.c | 8 ++++++++ manifest | 16 ++++++++-------- manifest.uuid | 2 +- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ext/misc/compress.c b/ext/misc/compress.c index 48ea5182d7..a6b4e23e8d 100644 --- a/ext/misc/compress.c +++ b/ext/misc/compress.c @@ -60,6 +60,10 @@ static void compressFunc( nIn = sqlite3_value_bytes(argv[0]); nOut = 13 + nIn + (nIn+999)/1000; pOut = sqlite3_malloc64( nOut+5 ); + if( pOut==0 ){ + sqlite3_result_error_nomem(context); + return; + } for(i=4; i>=0; i--){ x[i] = (nIn >> (7*(4-i)))&0x7f; } @@ -99,6 +103,10 @@ static void uncompressFunc( if( (pIn[i]&0x80)!=0 ){ i++; break; } } pOut = sqlite3_malloc64( nOut+1 ); + if( pOut==0 ){ + sqlite3_result_error_nomem(context); + return; + } rc = uncompress(pOut, &nOut, &pIn[i], nIn-i); if( rc==Z_OK ){ sqlite3_result_blob(context, pOut, nOut, sqlite3_free); diff --git a/manifest b/manifest index e9edbcbd68..0d18468e70 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\sthe\ssession\smodule\sso\sthat\sit\scan\sapply\schangesets\scontaining\stwo\sor\smore\sUPDATE\schanges\sthat\sform\sa\sdependency\sloop\s-\sso\sthat\sno\ssingle\sUPDATE\scan\sbe\sapplied\sindependently\swithout\sviolating\sa\sconstraint. -D 2026-06-02T18:23:55.109 +C Make\sthe\s(unused,\suntested,\sand\sunsupported)\sext/misc/compress.c\sroutines\nresponsive\sto\sOOM\sconditions. +D 2026-06-03T11:05:22.210 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -369,7 +369,7 @@ F ext/misc/btreeinfo.c 13bc9e9f1c13cde370d0e4a6a2683e9f1926a4cead7fb72c71871b11a F ext/misc/cksumvfs.c 9d7d0cf1a8893ac5d48922bfe9f3f217b4a61a6265f559263a02bb2001259913 F ext/misc/closure.c c983987a8d7846c3e52b1885ed3e20af7d4ca52a81a8f94ec6d1cd68f93acc86 F ext/misc/completion.c 3f5db28e88c3313103b2dd86d910a2944fd500c46754e473493968ce81e994a4 -F ext/misc/compress.c 8191118b9b73e7796c961790db62d35d9b0fb724b045e005a5713dc9e0795565 +F ext/misc/compress.c 5cc142aa82d1589a31c384657d0418c0eb0871348a2201e5dca32d24a0dd6654 F ext/misc/csv.c 5e9d4dd749e762c144104c0f01db5bf4458735b19081ebe481a64e589a66687a F ext/misc/dbdump.c 678f1b9ae2317b4473f65d03132a2482c3f4b08920799ed80feedd2941a06680 F ext/misc/decimal.c 23698283d9365ce66d54b5bb97c01e69b4aa7ac804f226f9117a0d42efd15a65 @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 46e74947f155199f5ce2440e8d5f19849d0daeb1d9d47381b0f84388901eab4c -Q +32c762bbb187e3bc964dcaad0949ebeb4da21331be842a5868feeac6088790ca -R e809b7ef1093cc510f207b67ac790177 -U dan -Z ca58af6e9f4882ad16a9a2a61f15e915 +P 919d393a3bc483bf58be1f8d6c2ef70f570d63cc9ad8d8df6a6562fb270ea7e5 +Q +e3120e2a4339d51210645b14d075abba27dd97bd6bd6d42f445dd5baf3d337e3 +R 11517b618b9e89b5ae2e54a90751a6fe +U drh +Z 13de7e4b75d7c2df03440814ccc7665b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 980f76555b..9d8074c3fa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -919d393a3bc483bf58be1f8d6c2ef70f570d63cc9ad8d8df6a6562fb270ea7e5 +2d1dc1c6c679d920e4f5d79acefbf318183ef37bc65bc78dc50f8512f317c1f5 From f7915390d5b31acf11cc3d144d1779e217a564fa Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 11:30:01 +0000 Subject: [PATCH 03/12] The prefix_length() function should stop at the first NUL terminator. FossilOrigin-Name: b5f9dc646df5059e546ceb1404a62aadeff4a33163d9b3758e78a33ff3c23467 --- ext/misc/prefixes.c | 2 +- manifest | 16 ++++++++-------- manifest.uuid | 2 +- test/prefixes.test | 4 ++++ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/ext/misc/prefixes.c b/ext/misc/prefixes.c index 9637b92fbe..ab60a22a90 100644 --- a/ext/misc/prefixes.c +++ b/ext/misc/prefixes.c @@ -296,7 +296,7 @@ static void prefixLengthFunc( nByte = (nL > nR ? nL : nR); for(i=0; i Date: Wed, 3 Jun 2026 12:04:07 +0000 Subject: [PATCH 04/12] Avoid excessive recursion and stack overflow in fts3 when processing a corrupt database. FossilOrigin-Name: c32ddb6a3fe2b8cd5f8a8a133bd2d4061243a3bc0e7e29a96b4d0e5e6090977f --- ext/fts3/fts3.c | 12 ++++++++++- manifest | 16 +++++++-------- manifest.uuid | 2 +- test/fts3corrupt7.test | 45 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 368e9b189a..863e7839d1 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -302,6 +302,12 @@ SQLITE_EXTENSION_INIT1 #endif + +/* +** Assume any b-tree layer with more levels than this is corrupt. +*/ +#define FTS3_MAX_BTREE_HEIGHT 48 + typedef struct Fts3HashWrapper Fts3HashWrapper; struct Fts3HashWrapper { Fts3Hash hash; /* Hash table */ @@ -2018,7 +2024,11 @@ static int fts3SelectLeaf( assert( piLeaf || piLeaf2 ); fts3GetVarint32(zNode, &iHeight); - rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); + if( iHeight>FTS3_MAX_BTREE_HEIGHT ){ + rc = FTS_CORRUPT_VTAB; + }else{ + rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); + } assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); if( rc==SQLITE_OK && iHeight>1 ){ diff --git a/manifest b/manifest index 8466acccd9..8874c08c16 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C The\sprefix_length()\sfunction\sshould\sstop\sat\sthe\sfirst\sNUL\sterminator. -D 2026-06-03T11:30:01.798 +C Avoid\sexcessive\srecursion\sand\sstack\soverflow\sin\sfts3\swhen\sprocessing\sa\scorrupt\ndatabase. +D 2026-06-03T12:04:07.869 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -79,7 +79,7 @@ F ext/fts3/README.content b9078d0843a094d86af0d48dffbff13c906702b4c3558012e67b9c F ext/fts3/README.syntax b72477722e9b4fe43f8403227d790a1c94221bfad15c27863a4b36d1052e892b F ext/fts3/README.tokenizers b92bdeb8b46503f0dd301d364efc5ef59ef9fa8e2758b8e742f39fa93a2e422d F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c 6cc7bbc307f27e7b6ee2e1d5ff63ffff4df3b42529dfe00eb34ddded417961b3 +F ext/fts3/fts3.c 1716994c40715223431d98e5132c40a3c1a00c011c5bde2270bad1bd06be3ccd F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3Int.h 277f32f304e82f4397fc2a74793c0a95318b7abb9670b519e4805a00946cbd9b F ext/fts3/fts3_aux.c c105f6502df588f49a383eb22aed953844fb0e31265361a0cc8dd73037b37e39 @@ -1166,7 +1166,7 @@ F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cd F test/fts3corrupt4.test c7f414fe29b97a478d15c90382c4ae077a2bbd2283bf8c63bf66dadaaed3edb8 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525 -F test/fts3corrupt7.test 1da31776e24bb91d3c028e663456b61280b121a74496ccf2fef3fe33790ad2b0 +F test/fts3corrupt7.test 446901d400cad0d896165fd568dca7174140b695806f8d219833cceb7889e36e F test/fts3cov.test 1e5ecea0e4c1394cea97adcfb9fd3d2d5998fd563dacf465f413e6c7fa5cffb3 F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 2d1dc1c6c679d920e4f5d79acefbf318183ef37bc65bc78dc50f8512f317c1f5 -Q +a9b8bdea0683bb03015ea1ee38f75636c80c189eed4b786b6d3d654a831cfdfb -R 9323c8b5ec325e0161ec7b66d56658f3 +P b5f9dc646df5059e546ceb1404a62aadeff4a33163d9b3758e78a33ff3c23467 +Q +21369378769195f20f839231f625582552eaa3f79044ca3e3efb7f9476b515a8 +R d7a1da736302ff0f4eb39d108db9460e U drh -Z 8642700f1b3d092d4edddae03cad5946 +Z 9a32bc29740450280217675caa06ea13 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9699c749b1..dfbdd3fca4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b5f9dc646df5059e546ceb1404a62aadeff4a33163d9b3758e78a33ff3c23467 +c32ddb6a3fe2b8cd5f8a8a133bd2d4061243a3bc0e7e29a96b4d0e5e6090977f diff --git a/test/fts3corrupt7.test b/test/fts3corrupt7.test index 6cf9c9a9dc..6394820c79 100644 --- a/test/fts3corrupt7.test +++ b/test/fts3corrupt7.test @@ -277,4 +277,49 @@ do_catchsql_test 2.1 { SELECT 0 FROM t1 WHERE t1 MATCH 'rtree NEAR rtree"json1 enable"'; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db + +set DEPTH 40000 + +proc fts3_put_varint {v} { + set l [list] + while {$v >= 0x80} { + lappend l [expr ($v & 0x7F) | 0x80] + set v [expr $v >> 7] + } + lappend l [expr $v & 0x7F] + + binary format cu* $l +} + +proc make_interior_node {height child_blockid} { + binary format a*a* [fts3_put_varint $height] [fts3_put_varint $child_blockid] +} +db func make_interior_node make_interior_node + +do_execsql_test 3.0 { + CREATE VIRTUAL TABLE fts USING fts3(content TEXT); + INSERT INTO fts(content) VALUES ('hello world'); + DELETE FROM fts_segdir; + INSERT INTO fts_segdir + (level, idx, start_block, leaves_end_block, end_block, root) VALUES + (0, 0, 100, 100 + $DEPTH, 100 + $DEPTH, make_interior_node($DEPTH+1, 100)); +} + +do_test 3.1 { + execsql BEGIN + for {set ii 0} {$ii<$DEPTH} {incr ii} { + execsql { + INSERT INTO fts_segments(blockid, block) + VALUES($ii+100, make_interior_node($DEPTH-$ii, 101+$ii)) + } + } + execsql COMMIT +} {} + +do_catchsql_test 3.2 { + SELECT * FROM fts WHERE fts MATCH 'x'; +} {1 {database disk image is malformed}} + finish_test From c911dda3d3b8232b76222cfe8930811a6e38d43d Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 13:16:20 +0000 Subject: [PATCH 05/12] Fix a potential UAF bug in the zipfile extension. FossilOrigin-Name: 46fbc23c16b3d104605f5abe5d7a5e779021e264bde409f2d9939de9b5cb9b37 --- ext/misc/zipfile.c | 1 + manifest | 16 ++++++++-------- manifest.uuid | 2 +- test/zipfile.test | 11 +++++++++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index fe0941823d..40b14f148d 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -488,6 +488,7 @@ static void zipfileResetCursor(ZipfileCsr *pCsr){ pNext = p->pNext; zipfileEntryFree(p); } + pCsr->pFreeEntry = 0; } /* diff --git a/manifest b/manifest index 8874c08c16..eb3d7a4a17 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Avoid\sexcessive\srecursion\sand\sstack\soverflow\sin\sfts3\swhen\sprocessing\sa\scorrupt\ndatabase. -D 2026-06-03T12:04:07.869 +C Fix\sa\spotential\sUAF\sbug\sin\sthe\szipfile\sextension. +D 2026-06-03T13:16:20.850 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -417,7 +417,7 @@ F ext/misc/vtablog.c 6c0c11c4822ab6c1a205718ea7c6d1bb561d96b27104b9c1fe84d01aa62 F ext/misc/vtshim.c f5ab480d1e33fa46a0b138359bedc9979e32798d72348e04bbe6093f9ae95c7b F ext/misc/wholenumber.c aa5e6d786fe8d79bc100ea0e852249c026a91ae65a5c1bcb2b869cd1a7cdd6d5 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c -F ext/misc/zipfile.c 38211d2b23e1fcb57af2d7c597458112e6c3d6130bf97dc4c224816d9f8218fa +F ext/misc/zipfile.c 58d535e6f177709c3f3607e19aa8e1b4c3c57c1f2c78bd4cdfac15e0b2f53e5a F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee F ext/qrf/README.md 9e644615d7d7b77ef7e9db798765679e50c5ed12eda48bce21c9ef9eb4715e9d F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430 @@ -2102,7 +2102,7 @@ F test/writecrash.test 13520af28f376bfc8c0bcd130efc1fff20bb165198e8b94cf153f1f75 F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b1d99e F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc -F test/zipfile.test 3aa05e7311f01679d81a2b49ddb7189b433dfda17580d7e8f9e904e87ee99440 +F test/zipfile.test 7b5c2f43ce9f7c882237e74822a331d94f4f83891e7a64c503519b434d06a5bf F test/zipfile2.test 21afaffcf4f7769df38bf16e4a9c4dfa6ba1b0f5b695f844ec61fafb92db0db7 F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P b5f9dc646df5059e546ceb1404a62aadeff4a33163d9b3758e78a33ff3c23467 -Q +21369378769195f20f839231f625582552eaa3f79044ca3e3efb7f9476b515a8 -R d7a1da736302ff0f4eb39d108db9460e +P c32ddb6a3fe2b8cd5f8a8a133bd2d4061243a3bc0e7e29a96b4d0e5e6090977f +Q +1fb5e9169ace6bea2bdf9013f39002c1ce5dc9ce51d6007bec22d91f456c15f0 +R a44611ce0f7837b5b17f84d541e0d6d5 U drh -Z 9a32bc29740450280217675caa06ea13 +Z 7a252a0b8444f7f230e7f12f5658f78d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dfbdd3fca4..2044db265b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c32ddb6a3fe2b8cd5f8a8a133bd2d4061243a3bc0e7e29a96b4d0e5e6090977f +46fbc23c16b3d104605f5abe5d7a5e779021e264bde409f2d9939de9b5cb9b37 diff --git a/test/zipfile.test b/test/zipfile.test index fe4f41503a..9924dddeb5 100644 --- a/test/zipfile.test +++ b/test/zipfile.test @@ -930,4 +930,15 @@ if {[catch {db one {SELECT length( zeroblob(1200000000) )}}]==0} { } {1 {out of memory}} } +# Bug 2026-06-03T10:58:51Z +do_catchsql_test 25.0 { + WITH t(v) AS ( + SELECT unhex( + '504B03041400000000000000000000000000000000000000000001000000 + 61504B01021E031400000000000000000000000000000000000000000001 + 000000000000000000000000000000000061504B05060000000001000100 + 2F0000001F0000000000',char(10,13,32)) + UNION ALL SELECT 'x' + ) SELECT z.name FROM t, zipfile(t.v) AS z; +} {1 {cannot open file: x}} finish_test From e7d405200ac43f21473e02fdbfbe4ede71db31e3 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 14:24:19 +0000 Subject: [PATCH 06/12] Fix another potential buffer overrun that could occur in fts5 when processing corrupt records. FossilOrigin-Name: e583a723fa501d0c53ad124b571bcd6a42d76daba1ea983d3a547763c2c94c22 --- ext/fts5/fts5_index.c | 3 +- ext/fts5/test/fts5corrupt5.test | 136 ++++++++++++++++++++++++++++++++ manifest | 16 ++-- manifest.uuid | 2 +- 4 files changed, 146 insertions(+), 11 deletions(-) diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 70c781b61b..f027c18748 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -3556,8 +3556,7 @@ static void fts5PoslistFilterCallback( do { while( ieState ){ fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); diff --git a/ext/fts5/test/fts5corrupt5.test b/ext/fts5/test/fts5corrupt5.test index caffb0eeb9..65529c861a 100644 --- a/ext/fts5/test/fts5corrupt5.test +++ b/ext/fts5/test/fts5corrupt5.test @@ -1998,6 +1998,142 @@ do_catchsql_test 12.1 { SELECT rowid FROM ft('a:aaa') } {0 1} +#------------------------------------------------------------------------- +reset_db +do_test 13.0 { + sqlite3 db {} + db deserialize [decode_hexdb { +.open --hexdb +| size 24576 pagesize 4096 filename vuln_001.db +| page 1 offset 0 +| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. +| 16: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 06 .....@ ........ +| 32: 00 00 00 00 00 00 00 00 00 00 00 06 00 00 00 04 ................ +| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................ +| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................ +| 96: 00 2e 76 89 0d 00 00 00 06 0e 09 00 0f ca 0f 77 ..v............w +| 112: 0f 0f 0e b7 0e 5e 0e 09 00 00 00 00 00 00 00 00 .....^.......... +| 3584: 00 00 00 00 00 00 00 00 00 53 06 06 17 1d 1d 01 .........S...... +| 3600: 7b 74 61 62 6c 65 74 5f 63 6f 6e 66 69 67 74 5f .tablet_configt_ +| 3616: 63 6f 6e 66 69 67 06 43 52 45 41 54 45 20 54 41 config.CREATE TA +| 3632: 42 4c 45 20 27 74 5f 63 6f 6e 66 69 67 27 28 6b BLE 't_config'(k +| 3648: 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 76 29 PRIMARY KEY, v) +| 3664: 20 57 49 54 48 4f 55 54 20 52 4f 57 49 44 57 05 WITHOUT ROWIDW. +| 3680: 06 17 1f 1f 01 7f 74 61 62 6c 65 74 5f 64 6f 63 ......tablet_doc +| 3696: 73 69 7a 65 74 5f 64 6f 63 73 69 7a 65 05 43 52 sizet_docsize.CR +| 3712: 45 41 54 45 20 54 41 42 4c 45 20 27 74 5f 64 6f EATE TABLE 't_do +| 3728: 63 73 69 7a 65 27 28 69 64 20 49 4e 54 45 47 45 csize'(id INTEGE +| 3744: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 73 R PRIMARY KEY, s +| 3760: 7a 20 42 4c 4f 42 29 56 04 06 17 1f 1f 01 7d 74 z BLOB)V.......t +| 3776: 61 62 6c 65 74 5f 63 6f 6e 74 65 6e 74 74 5f 63 ablet_contentt_c +| 3792: 6f 6e 74 65 6e 74 04 43 52 45 41 54 45 20 54 41 ontent.CREATE TA +| 3808: 42 4c 45 20 27 74 5f 63 6f 6e 74 65 6e 74 27 28 BLE 't_content'( +| 3824: 69 64 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 id INTEGER PRIMA +| 3840: 52 59 20 4b 45 59 2c 20 63 30 2c 20 63 31 29 66 RY KEY, c0, c1)f +| 3856: 03 07 17 17 17 01 81 2b 74 61 62 6c 65 74 5f 69 .......+tablet_i +| 3872: 64 78 74 5f 69 64 78 03 43 52 45 41 54 45 20 54 dxt_idx.CREATE T +| 3888: 41 42 4c 45 20 27 74 5f 69 64 78 27 28 73 65 67 ABLE 't_idx'(seg +| 3904: 69 64 2c 20 74 65 72 6d 2c 20 70 67 6e 6f 2c 20 id, term, pgno, +| 3920: 50 52 49 4d 41 52 59 20 4b 45 59 28 73 65 67 69 PRIMARY KEY(segi +| 3936: 64 2c 20 74 65 72 6d 29 29 20 57 49 54 48 4f 55 d, term)) WITHOU +| 3952: 54 20 52 4f 57 49 44 51 02 06 17 19 19 01 7f 74 T ROWIDQ.......t +| 3968: 61 62 6c 65 74 5f 64 61 74 61 74 5f 64 61 74 61 ablet_datat_data +| 3984: 02 43 52 45 41 54 45 20 54 41 42 4c 45 20 27 74 .CREATE TABLE 't +| 4000: 5f 64 61 74 61 27 28 69 64 20 49 4e 54 45 47 45 _data'(id INTEGE +| 4016: 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c 20 62 R PRIMARY KEY, b +| 4032: 6c 6f 63 6b 20 42 4c 4f 42 29 34 01 06 17 0f 0f lock BLOB)4..... +| 4048: 08 5b 74 61 62 6c 65 74 74 43 52 45 41 54 45 20 .[tablettCREATE +| 4064: 56 49 52 54 55 41 4c 20 54 41 42 4c 45 20 74 20 VIRTUAL TABLE t +| 4080: 55 53 49 4e 47 20 66 74 73 35 28 61 2c 20 62 29 USING fts5(a, b) +| page 2 offset 4096 +| 0: 0d 00 00 00 04 0b da 00 0f e7 0f ef 0f ce 0b da ................ +| 3024: 00 00 00 00 00 00 00 00 00 00 87 6c 84 80 80 80 ...........l.... +| 3040: 80 02 04 00 8f 5c 00 00 03 e8 80 80 80 80 80 80 ................ +| 3056: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3072: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3088: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3104: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3120: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3136: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3152: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3168: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3184: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3200: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3216: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3232: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3248: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3264: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3280: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3296: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3312: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3328: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3344: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3360: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3376: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3392: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3408: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3424: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3440: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3456: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3472: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3488: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3504: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3520: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3536: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3552: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3568: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3584: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3600: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3616: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3632: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3648: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3664: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3680: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3696: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3712: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3728: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3744: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3760: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3776: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3792: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3808: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3824: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3840: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3856: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3872: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3888: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3904: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3920: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3936: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3952: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3968: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 3984: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 4000: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 4016: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ................ +| 4032: 80 80 80 80 80 80 80 80 80 80 80 80 80 80 12 84 ................ +| 4048: 80 80 80 80 01 03 00 2a 00 0b 00 0e 06 30 68 65 .......*.....0he +| 4064: 6c 6c 6f 01 04 04 04 06 01 03 00 12 01 02 02 0f llo............. +| 4080: 0a 03 00 24 00 00 00 00 01 01 01 00 01 01 01 02 ...$............ +| page 3 offset 8192 +| 0: 0a 00 00 00 01 0f fa 00 0f fa 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 00 05 04 09 0c 01 02 ................ +| page 4 offset 12288 +| 0: 0d 00 00 00 01 0f e8 00 0f e8 00 00 00 00 00 00 ................ +| 4064: 00 00 00 00 00 00 00 00 16 01 04 00 23 1b 68 65 ............#.he +| 4080: 6c 6c 6f 20 77 6f 72 6c 64 66 6f 6f 20 62 61 72 llo worldfoo bar +| page 5 offset 16384 +| 0: 0d 00 00 00 01 0f f9 00 0f f9 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 00 00 00 00 00 05 01 03 00 10 02 02 ................ +| page 6 offset 20480 +| 0: 0a 00 00 00 01 0f f4 00 0f f4 00 00 00 00 00 00 ................ +| 4080: 00 00 00 00 0b 03 1b 01 76 65 72 73 69 6f 6e 04 ........version. +| end vuln_001.db +}]} {} + +do_catchsql_test 13.1 { + SELECT * FROM t('a:hello') +} {0 {{hello world} {foo bar}}} + sqlite3_fts5_may_be_corrupt 0 finish_test diff --git a/manifest b/manifest index eb3d7a4a17..a80f54eb5e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\spotential\sUAF\sbug\sin\sthe\szipfile\sextension. -D 2026-06-03T13:16:20.850 +C Fix\sanother\spotential\sbuffer\soverrun\sthat\scould\soccur\sin\sfts5\swhen\s\nprocessing\scorrupt\srecords. +D 2026-06-03T14:24:19.073 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -113,7 +113,7 @@ F ext/fts5/fts5_buffer.c dcc3f0352339fe79c9d8abbc1c2009bc3469206467880bf43558447 F ext/fts5/fts5_config.c bfba970fe1e4eed18ee57c8d51458e226db9a960ddf775c5e50e3d76603a667e F ext/fts5/fts5_expr.c 71d48e8cf0358deace4949276647d317ff7665db6db09f40b81e2e7fe6664c7c F ext/fts5/fts5_hash.c d5871df92ce3fa210a650cf419ee916b87c29977e86084d06612edf772bff6f5 -F ext/fts5/fts5_index.c 87faf9a9ca65ec7be0886e13e9f7571d0d2416ab88e77a06d57bbaf3734bd51f +F ext/fts5/fts5_index.c 4760c8d1480b72cdf93fd1e4d3464c3bcbdf834fa16094f0db28d3bd4e35cd9f F ext/fts5/fts5_main.c b0fed47b3b4420ba6810373480a75bc28a9c0b7d16478d19a396436fb3ff17d7 F ext/fts5/fts5_storage.c 19bc7c4cbe1e6a2dd9849ef7d84b5ca1fcbf194cefc3e386b901e00e08bf05c2 F ext/fts5/fts5_tcl.c 2be6cc14f9448f720fd4418339cd202961a0801ea9424cb3d9de946f8f5a051c @@ -164,7 +164,7 @@ F ext/fts5/test/fts5corrupt.test 237fce1c3261bb3a5bec333b0f0dbf5b105ec32627ef14c F ext/fts5/test/fts5corrupt2.test 4a03a158c2cb617c9f76d26b35c1ef2534124bc0bbddcea38dfd5b170ebea27b F ext/fts5/test/fts5corrupt3.test 121a8a7622dfe1be1bc55cbe70eddd6a3416f76a837dc8c06a11a32e781595a4 F ext/fts5/test/fts5corrupt4.test dc08d19f5b8943e95a7778a7d8da592042504faf18dd93f68f7d7a0d7d7dd733 -F ext/fts5/test/fts5corrupt5.test bdf6c04a1c9176507c8c0e66842b78b3fbcafccde20a41bb22a1b19896784b54 +F ext/fts5/test/fts5corrupt5.test b9085599389721b38f080f501660c931cd608f8ecbc93c23644344f74ef7aa21 F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06 F ext/fts5/test/fts5corrupt7.test 814aab492d7a09abb5bfdd81cc66fc206d7f3868f9a3bae91876e02efc466fb3 F ext/fts5/test/fts5corrupt8.test 0b10750caf8aa23fa1c379ca4caf6130d41454505e4d5315590f4061eedcbe44 @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P c32ddb6a3fe2b8cd5f8a8a133bd2d4061243a3bc0e7e29a96b4d0e5e6090977f -Q +1fb5e9169ace6bea2bdf9013f39002c1ce5dc9ce51d6007bec22d91f456c15f0 -R a44611ce0f7837b5b17f84d541e0d6d5 +P 46fbc23c16b3d104605f5abe5d7a5e779021e264bde409f2d9939de9b5cb9b37 +Q +6ee44b199512b8cac604bf062f893a9047af4b5bfc881bb7cb69ae42d0a0adb4 +R 5e80354010cc44677928bdcb896516c8 U drh -Z 7a252a0b8444f7f230e7f12f5658f78d +Z 77c6067ee17713b547a1a9d882df067e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2044db265b..ebb8c40f81 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -46fbc23c16b3d104605f5abe5d7a5e779021e264bde409f2d9939de9b5cb9b37 +e583a723fa501d0c53ad124b571bcd6a42d76daba1ea983d3a547763c2c94c22 From 6c1aa834f6ee5474dcb38122d8ceebd8a1409e0c Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 15:02:29 +0000 Subject: [PATCH 07/12] Fix another buffer overread in fts5 that could occur when processing corrupt records. FossilOrigin-Name: 8427cac76455ecbef224ceaccbb37b79338015bcfe67209ae462f36ffefa8466 --- ext/fts5/fts5_index.c | 5 +++++ ext/fts5/test/fts5corrupt7.test | 36 +++++++++++++++++++++++++++++++++ manifest | 16 +++++++-------- manifest.uuid | 2 +- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index f027c18748..ce55337022 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5300,6 +5300,11 @@ static void fts5DoSecureDelete( }else{ iStart = fts5GetU16(&aPg[0]); } + if( iStart>nPg ){ + FTS5_CORRUPT_IDX(p); + sqlite3_free(aIdx); + return; + } iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); assert_nc( iSOP<=pSeg->iLeafOffset ); diff --git a/ext/fts5/test/fts5corrupt7.test b/ext/fts5/test/fts5corrupt7.test index 23061a1cb5..84a586928f 100644 --- a/ext/fts5/test/fts5corrupt7.test +++ b/ext/fts5/test/fts5corrupt7.test @@ -125,4 +125,40 @@ do_catchsql_test 2.3 { DELETE FROM t1 WHERE rowid = 1 } {/.*fts5: corrupt.*/} +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + PRAGMA page_size=4096; + PRAGMA journal_mode=DELETE; + CREATE VIRTUAL TABLE t USING fts5(x, detail=none); + + WITH s(i) AS ( + VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<5000 + ) + INSERT INTO t(rowid, x) SELECT i, 'vulnerabilitytest' FROM s; + + INSERT INTO t(t) VALUES('optimize'); + INSERT INTO t(t, rank) VALUES('secure-delete', 1); +} {delete} + +do_test 3.1 { + db eval { SELECT rowid AS rowid, block FROM t_data ORDER BY rowid } { + if {$rowid>=10 && [string length $block]>=4} { + binary scan $block Su first_rowid_off + set pgno [expr ($rowid & 0x7FFFFFFF)] + if {$pgno>=2 && $first_rowid_off>0} break + } + } + + set bad [binary format a*a* "\xFF\xFF" [string range $block 2 end]] + db eval { + UPDATE t_data SET block = $bad WHERE rowid=$rowid + } +} {} + +do_catchsql_test 3.2 { + DELETE FROM t WHERE rowid=4500; +} {1 {fts5: corruption in table "t"}} + + finish_test diff --git a/manifest b/manifest index a80f54eb5e..93d8cfb562 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sanother\spotential\sbuffer\soverrun\sthat\scould\soccur\sin\sfts5\swhen\s\nprocessing\scorrupt\srecords. -D 2026-06-03T14:24:19.073 +C Fix\sanother\sbuffer\soverread\sin\sfts5\sthat\scould\soccur\swhen\sprocessing\ncorrupt\srecords. +D 2026-06-03T15:02:29.677 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -113,7 +113,7 @@ F ext/fts5/fts5_buffer.c dcc3f0352339fe79c9d8abbc1c2009bc3469206467880bf43558447 F ext/fts5/fts5_config.c bfba970fe1e4eed18ee57c8d51458e226db9a960ddf775c5e50e3d76603a667e F ext/fts5/fts5_expr.c 71d48e8cf0358deace4949276647d317ff7665db6db09f40b81e2e7fe6664c7c F ext/fts5/fts5_hash.c d5871df92ce3fa210a650cf419ee916b87c29977e86084d06612edf772bff6f5 -F ext/fts5/fts5_index.c 4760c8d1480b72cdf93fd1e4d3464c3bcbdf834fa16094f0db28d3bd4e35cd9f +F ext/fts5/fts5_index.c 1627d9c184dec6be913647ba1e31a906e825b8460c9575e1c367ee27fe847f0c F ext/fts5/fts5_main.c b0fed47b3b4420ba6810373480a75bc28a9c0b7d16478d19a396436fb3ff17d7 F ext/fts5/fts5_storage.c 19bc7c4cbe1e6a2dd9849ef7d84b5ca1fcbf194cefc3e386b901e00e08bf05c2 F ext/fts5/fts5_tcl.c 2be6cc14f9448f720fd4418339cd202961a0801ea9424cb3d9de946f8f5a051c @@ -166,7 +166,7 @@ F ext/fts5/test/fts5corrupt3.test 121a8a7622dfe1be1bc55cbe70eddd6a3416f76a837dc8 F ext/fts5/test/fts5corrupt4.test dc08d19f5b8943e95a7778a7d8da592042504faf18dd93f68f7d7a0d7d7dd733 F ext/fts5/test/fts5corrupt5.test b9085599389721b38f080f501660c931cd608f8ecbc93c23644344f74ef7aa21 F ext/fts5/test/fts5corrupt6.test 2d72db743db7b5d9c9a6d0cfef24d799ed1aa5e8192b66c40e871a37ed9eed06 -F ext/fts5/test/fts5corrupt7.test 814aab492d7a09abb5bfdd81cc66fc206d7f3868f9a3bae91876e02efc466fb3 +F ext/fts5/test/fts5corrupt7.test 9664c15360e8b649ad76f457a0bbf5a7271b8eff1a8ee141ea039bc63240c934 F ext/fts5/test/fts5corrupt8.test 0b10750caf8aa23fa1c379ca4caf6130d41454505e4d5315590f4061eedcbe44 F ext/fts5/test/fts5corrupt9.test 4253b9b59f33effac8b67da72ec34309c738aca2d5e8e2656bfbbd6a489a1dfe F ext/fts5/test/fts5corruptA.test 7b31551444569420903d34ae50a55a1227d16969264f0b50de2dc812bc0b3414 @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 46fbc23c16b3d104605f5abe5d7a5e779021e264bde409f2d9939de9b5cb9b37 -Q +6ee44b199512b8cac604bf062f893a9047af4b5bfc881bb7cb69ae42d0a0adb4 -R 5e80354010cc44677928bdcb896516c8 +P e583a723fa501d0c53ad124b571bcd6a42d76daba1ea983d3a547763c2c94c22 +Q +b5337c87cc314e6830615e4efe2d4723fa7cedf87ce404f60d6e520aeab77cbc +R e0ece0eb9b1b23241fb865df499d9eba U drh -Z 77c6067ee17713b547a1a9d882df067e +Z 4d9479f361615ba543c2192b707f51df # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ebb8c40f81..fb2e99e60a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e583a723fa501d0c53ad124b571bcd6a42d76daba1ea983d3a547763c2c94c22 +8427cac76455ecbef224ceaccbb37b79338015bcfe67209ae462f36ffefa8466 From ceb66403bbf30f49b35ffb582b56aeea70f4cbbb Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 15:54:58 +0000 Subject: [PATCH 08/12] Fix an integer overflow problem in fts3 that could lead to a buffer overwrite on platforms where size_t is 32-bits. FossilOrigin-Name: 1389ec7bf6a7f68406adae6fd532497b2496f01fb5b465e18bd683da3f8f53ad --- ext/fts3/fts3_aux.c | 2 +- manifest | 16 ++++++++-------- manifest.uuid | 2 +- test/fts3corrupt7.test | 23 +++++++++++++++++++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/ext/fts3/fts3_aux.c b/ext/fts3/fts3_aux.c index 0d88d014d5..f8f05682fc 100644 --- a/ext/fts3/fts3_aux.c +++ b/ext/fts3/fts3_aux.c @@ -341,7 +341,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ /* State 3. The integer just read is a column number. */ default: assert( eState==3 ); iCol = (int)v; - if( iCol<1 || iCol>0x3fffffff ){ + if( iCol<1 || iCol>(pFts3->nColumn+1) ){ rc = SQLITE_CORRUPT_VTAB; break; } diff --git a/manifest b/manifest index 93d8cfb562..fd9c3b6049 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sanother\sbuffer\soverread\sin\sfts5\sthat\scould\soccur\swhen\sprocessing\ncorrupt\srecords. -D 2026-06-03T15:02:29.677 +C Fix\san\sinteger\soverflow\sproblem\sin\sfts3\sthat\scould\slead\sto\sa\sbuffer\soverwrite\son\splatforms\swhere\ssize_t\sis\s32-bits. +D 2026-06-03T15:54:58.907 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -82,7 +82,7 @@ F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d F ext/fts3/fts3.c 1716994c40715223431d98e5132c40a3c1a00c011c5bde2270bad1bd06be3ccd F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe F ext/fts3/fts3Int.h 277f32f304e82f4397fc2a74793c0a95318b7abb9670b519e4805a00946cbd9b -F ext/fts3/fts3_aux.c c105f6502df588f49a383eb22aed953844fb0e31265361a0cc8dd73037b37e39 +F ext/fts3/fts3_aux.c bbb614abcc995bf748cba4717e9fd8de67898e33c80f60379f6feb2358189b4b F ext/fts3/fts3_expr.c 907dd64deb881c0586d5ecbabc96cfebdbe2ff9e7a052d379fd3b606edfa21ea F ext/fts3/fts3_hash.c d9dba473741445789330c7513d4f65737c92df23c3212784312931641814672a F ext/fts3/fts3_hash.h 39cf6874dc239d6b4e30479b1975fe5b22a3caaf @@ -1166,7 +1166,7 @@ F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cd F test/fts3corrupt4.test c7f414fe29b97a478d15c90382c4ae077a2bbd2283bf8c63bf66dadaaed3edb8 F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5 F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525 -F test/fts3corrupt7.test 446901d400cad0d896165fd568dca7174140b695806f8d219833cceb7889e36e +F test/fts3corrupt7.test 93622a4336b161a733accbd66311d93749660243cdda268fd647c21e1e680770 F test/fts3cov.test 1e5ecea0e4c1394cea97adcfb9fd3d2d5998fd563dacf465f413e6c7fa5cffb3 F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de @@ -2199,9 +2199,9 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P e583a723fa501d0c53ad124b571bcd6a42d76daba1ea983d3a547763c2c94c22 -Q +b5337c87cc314e6830615e4efe2d4723fa7cedf87ce404f60d6e520aeab77cbc -R e0ece0eb9b1b23241fb865df499d9eba +P 8427cac76455ecbef224ceaccbb37b79338015bcfe67209ae462f36ffefa8466 +Q +ef0c66d2a2e0b8f523633efca66a97236bb5a502525eb6a00a28a350ec7c76e6 +R 910838b4128744ec40874da2ee3790b3 U drh -Z 4d9479f361615ba543c2192b707f51df +Z 6a2717f78ffa1ab06bf4822cb2b57eaf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index fb2e99e60a..d688712429 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8427cac76455ecbef224ceaccbb37b79338015bcfe67209ae462f36ffefa8466 +1389ec7bf6a7f68406adae6fd532497b2496f01fb5b465e18bd683da3f8f53ad diff --git a/test/fts3corrupt7.test b/test/fts3corrupt7.test index 6394820c79..ec5f1454b8 100644 --- a/test/fts3corrupt7.test +++ b/test/fts3corrupt7.test @@ -322,4 +322,27 @@ do_catchsql_test 3.2 { SELECT * FROM fts WHERE fts MATCH 'x'; } {1 {database disk image is malformed}} +#------------------------------------------------------------------------- +reset_db + +do_execsql_test 4.1 { + CREATE VIRTUAL TABLE t1 USING fts4(content); + DELETE FROM t1_segments; + DELETE FROM t1_segdir; +} + +do_execsql_test 4.2 { + INSERT INTO t1_segdir + (level, idx, start_block, leaves_end_block, end_block, root) + VALUES(0, 0, 0, 0, 0, X'000568656C6C6F08010201FFFFFF7F00'); +} + +do_execsql_test 4.3 { + CREATE VIRTUAL TABLE t1_terms USING fts4aux(t1); +} + +do_catchsql_test 4.4 { + SELECT * FROM t1_terms; +} {1 {database disk image is malformed}} + finish_test From c867ce4b89de3a637d35b1121353ea5b785f4496 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 16:23:45 +0000 Subject: [PATCH 09/12] Use tail recursion on the larger of the two partitions when doing a quicksort as part of the implementation of the median() function, to avoid excess stack usage on pathological inputs. FossilOrigin-Name: b8fce837cf633af7e489e9750bcd1764e96f9ad7ea01847d5ef9c7060f01a4bf --- ext/misc/percentile.c | 74 ++++++++++++++++++++-------------------- manifest | 18 +++++----- manifest.uuid | 2 +- src/func.c | 79 +++++++++++++++++++++---------------------- test/percentile.test | 11 ++++++ 5 files changed, 97 insertions(+), 87 deletions(-) diff --git a/ext/misc/percentile.c b/ext/misc/percentile.c index 98e45cc3a8..c15512b29e 100644 --- a/ext/misc/percentile.c +++ b/ext/misc/percentile.c @@ -340,45 +340,45 @@ static void percentSort(double *a, unsigned int n){ int iGt; /* Entries at or after a[iGt] are greater than rPivot */ int i; /* Loop counter */ double rPivot; /* The pivot value */ - - assert( n>=2 ); - if( a[0]>a[n-1] ){ - SWAP_DOUBLE(a[0],a[n-1]) - } - if( n==2 ) return; - iGt = n-1; - i = n/2; - if( a[0]>a[i] ){ - SWAP_DOUBLE(a[0],a[i]) - }else if( a[i]>a[iGt] ){ - SWAP_DOUBLE(a[i],a[iGt]) - } - if( n==3 ) return; - rPivot = a[i]; - iLt = i = 1; - do{ - if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) - iLt++; - i++; - }else if( a[i]>rPivot ){ - do{ - iGt--; - }while( iGt>i && a[iGt]>rPivot ); - SWAP_DOUBLE(a[i],a[iGt]) - }else{ - i++; + + while( n>=2 ){ + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( in/2 ){ + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + n = iLt; + }else{ + if( iLt>=2 ) percentSort(a, iLt); + a += iGt; + n -= iGt; } - }while( i=2 ) percentSort(a, iLt); - if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); - -/* Uncomment for testing */ -#if 0 - for(i=0; i=2 ); - if( a[0]>a[n-1] ){ - SWAP_DOUBLE(a[0],a[n-1]) - } - if( n==2 ) return; - iGt = n-1; - i = n/2; - if( a[0]>a[i] ){ - SWAP_DOUBLE(a[0],a[i]) - }else if( a[i]>a[iGt] ){ - SWAP_DOUBLE(a[i],a[iGt]) - } - if( n==3 ) return; - rPivot = a[i]; - iLt = i = 1; - do{ - if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) - iLt++; - i++; - }else if( a[i]>rPivot ){ - do{ - iGt--; - }while( iGt>i && a[iGt]>rPivot ); - SWAP_DOUBLE(a[i],a[iGt]) - }else{ - i++; - } - }while( i=2 ) percentSort(a, iLt); - if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); - -/* Uncomment for testing */ -#if 0 - for(i=0; i=2 ){ + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( in/2 ){ + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + n = iLt; + }else{ + if( iLt>=2 ) percentSort(a, iLt); + a += iGt; + n -= iGt; + } + } +} /* ** The "inverse" function for percentile(Y,P) is called to remove a diff --git a/test/percentile.test b/test/percentile.test index 25096a953c..d1c4750455 100644 --- a/test/percentile.test +++ b/test/percentile.test @@ -592,4 +592,15 @@ do_execsql_test percentile-6.0 { SELECT median(iif(n%2,0.1,1.0)) FROM c; } 0.55 +# Bug 2026-06-03T07:26:42Z +# Use tail recursion in percentSort() to prevent stack overflow +# +do_execsql_test percentile-7.0 { + WITH RECURSIVE t1(n,x) AS ( + VALUES(1,1.0) + UNION ALL + SELECT n+1, if(n%2,n*2.0,999998.0-2.0*n) FROM t1 WHERE n<1000000 + ) SELECT median(x) FROM t1; +} 499998.0 + finish_test From b9e656f6f7129d7370850376f0129a00fc5e851e Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 16:58:01 +0000 Subject: [PATCH 10/12] Fix a faulty assert() in rtree that could fire when processing an unusually large record. FossilOrigin-Name: 004d85c5f1bf04409ce85d5131f1d9697f25f531046e60160e78b0bae7f55123 --- ext/rtree/rtree.c | 2 +- ext/rtree/rtreeA.test | 29 +++++++++++++++++++++++++++++ manifest | 16 ++++++++-------- manifest.uuid | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 721acfae12..5041cf3db2 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -1402,7 +1402,7 @@ static int nodeRowidIndex( ){ int ii; int nCell = NCELL(pNode); - assert( nCell<200 ); + assert( nCell<65536 && nCell>=0 ); for(ii=0; ii Date: Wed, 3 Jun 2026 18:51:23 +0000 Subject: [PATCH 11/12] Fix harmless compiler warning. FossilOrigin-Name: ac00a18dcf132180295d41299f31fbc68087a27b61782d00b9633dd9661ded2b --- manifest | 13 ++++++------- manifest.uuid | 2 +- src/whereexpr.c | 2 -- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/manifest b/manifest index 1fa2a17e3b..661ad76ffe 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sfaulty\sassert()\sin\srtree\sthat\scould\sfire\swhen\sprocessing\san\sunusually\slarge\srecord. -D 2026-06-03T16:58:01.686 +C Fix\sharmless\scompiler\swarning. +D 2026-06-03T18:51:23.167 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -820,7 +820,7 @@ F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014 F src/where.c a1caeb656f74e58de7e7310ee91c6dfb40e975c7dd9b18e61a965aea46a610f2 F src/whereInt.h 8d94cb116c9e06205c3d5ac87af065fc044f8cf08bfdccd94b6ea1c1308e65da F src/wherecode.c bc39ccbe3648f01157038b16cc55bdbff128590972b7185521b5526dc2815765 -F src/whereexpr.c e167672449982d8b79bf3a2463324affc03399979689f735ac3a99ce87e14e2a +F src/whereexpr.c 61b1c9d98d39f6bcd79fcb4ebb4c9478effb259fefac4c7d3e78e4957f97f75a F src/window.c c0a38cd32473e8e8e7bc435039f914a36ca42465506dc491c65870c01ddac9fb F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test 4d7a34d328e58ca2a2d78fd76c27614a41ca7ddf4312ded9c68c04f430b3b47d @@ -2199,9 +2199,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P b8fce837cf633af7e489e9750bcd1764e96f9ad7ea01847d5ef9c7060f01a4bf -Q +e7335dd377a73fc2b913076ee659ed2b0c4a131b16e04b1b55fae677d6b56e8a -R 6be25d01f95abb70a7dd5dab78e15dac +P 004d85c5f1bf04409ce85d5131f1d9697f25f531046e60160e78b0bae7f55123 +R 58c568d42a018658d9c21e84f3d9ab9d U drh -Z a214f5f4ad648fbe73aedf83376ec0a6 +Z 90a4dd0e232c39450fe3f4e4d6b819e5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 94373e1e35..4ff6f85cf7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -004d85c5f1bf04409ce85d5131f1d9697f25f531046e60160e78b0bae7f55123 +ac00a18dcf132180295d41299f31fbc68087a27b61782d00b9633dd9661ded2b diff --git a/src/whereexpr.c b/src/whereexpr.c index 334c469038..e69ade52e3 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -963,7 +963,6 @@ static void exprAnalyzeOrTerm( */ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ char aff1, aff2; - CollSeq *pColl; if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; /* (1) */ if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; /* (2) */ if( ExprHasProperty(pExpr, EP_OuterON|EP_Collate) ) return 0; /* (3) */ @@ -981,7 +980,6 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ ){ return 0; /* (5) */ } - pColl = sqlite3ExprCompareCollSeq(pParse, pExpr); if( !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight) ){ return 0; /* (6) */ } From ccc132c5be20ab5c755c97a08d06b1b592fef330 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 3 Jun 2026 19:12:13 +0000 Subject: [PATCH 12/12] Version 3.53.2 FossilOrigin-Name: d6e03d8c777cfa2d35e3b60d8ec3e0187f3e9f99d8e2ee9cac695fd6fcdf1a24 --- manifest | 10 ++++++---- manifest.tags | 2 ++ manifest.uuid | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/manifest b/manifest index 661ad76ffe..bdf5b9fc4d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sharmless\scompiler\swarning. -D 2026-06-03T18:51:23.167 +C Version\s3.53.2 +D 2026-06-03T19:12:13.350 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -2199,8 +2199,10 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 004d85c5f1bf04409ce85d5131f1d9697f25f531046e60160e78b0bae7f55123 +P ac00a18dcf132180295d41299f31fbc68087a27b61782d00b9633dd9661ded2b R 58c568d42a018658d9c21e84f3d9ab9d +T +sym-release * +T +sym-version-3.53.2 * U drh -Z 90a4dd0e232c39450fe3f4e4d6b819e5 +Z a64dcb64a02448f95d399730d70f1fe2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index 8d97ab7c23..65b82c47d1 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,4 @@ branch branch-3.53 +tag release tag branch-3.53 +tag version-3.53.2 diff --git a/manifest.uuid b/manifest.uuid index 4ff6f85cf7..0c8e5f71b2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ac00a18dcf132180295d41299f31fbc68087a27b61782d00b9633dd9661ded2b +d6e03d8c777cfa2d35e3b60d8ec3e0187f3e9f99d8e2ee9cac695fd6fcdf1a24