From: drh <> Date: Fri, 1 May 2026 13:56:19 +0000 (+0000) Subject: Harden the recovery extension against SQL injections coming from the X-Git-Tag: version-3.53.1~4 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=20847aca95871cb078ba40a25670d2eea31f51c3;p=thirdparty%2Fsqlite.git Harden the recovery extension against SQL injections coming from the sqlite_schema table of the database being recovered. FossilOrigin-Name: 555401fe048a51ecaed3ef672723b6ef8e1340c7028c11a17731abbc399bc078 --- diff --git a/ext/recover/sqlite3recover.c b/ext/recover/sqlite3recover.c index d50aa25ea0..eedf178917 100644 --- a/ext/recover/sqlite3recover.c +++ b/ext/recover/sqlite3recover.c @@ -532,18 +532,39 @@ static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){ } } +/* +** Run a single SQL statement in zSql. If zSql contains two or more +** SQL statements separated by ';', only the first is run. +** +** Return the sqlite3_finalizer() or sqlite3_prepare() result code +** from running the zSql statement. +*/ +static int recoverOneStmt(sqlite3 *db, const char *zSql){ + sqlite3_stmt *pStmt = 0; + int rc; + if( zSql==0 ) return SQLITE_OK; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + return rc; + } + while( SQLITE_ROW==sqlite3_step(pStmt) ){} + return sqlite3_finalize(pStmt); +} + /* ** This function is a no-op if recover handle p already contains an error ** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this ** case. ** -** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK. -** Or, if an error occurs, leave an error code and message in the recover -** handle and return a copy of the error code. +** Otherwise, execute a single SQL statment in zSql. Even if zSql contains +** two or more SQL statements separated by ';', only execute the first one. +** If successful, return SQLITE_OK. Or, if an error occurs, leave an error +** code and message in the recover handle and return a copy of the error code. */ static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){ if( p->errCode==SQLITE_OK ){ - int rc = sqlite3_exec(db, zSql, 0, 0, 0); + int rc = recoverOneStmt(db, zSql); if( rc ){ recoverDbError(p, db); } @@ -943,7 +964,8 @@ static void recoverTransferSettings(sqlite3_recover *p){ } recoverFinalize(p, p1); } - recoverExec(p, db2, "CREATE TABLE t1(a); DROP TABLE t1;"); + recoverExec(p, db2, "CREATE TABLE t1(a)"); + recoverExec(p, db2, "DROP TABLE t1"); if( p->errCode==SQLITE_OK ){ sqlite3 *db = p->dbOut; @@ -1025,12 +1047,12 @@ static int recoverOpenOutput(sqlite3_recover *p){ static void recoverOpenRecovery(sqlite3_recover *p){ char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb); recoverExec(p, p->dbOut, zSql); - recoverExec(p, p->dbOut, - "PRAGMA writable_schema = 1;" - "CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);" - "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);" - ); sqlite3_free(zSql); + recoverExec(p, p->dbOut, "PRAGMA writable_schema = 1"); + recoverExec(p, p->dbOut, + "CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT)"); + recoverExec(p, p->dbOut, + "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql)"); } @@ -1170,7 +1192,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){ ")" "SELECT rootpage, tbl, isVirtual, name, sql" " FROM dbschema " - " WHERE tbl OR isIndex" + " WHERE (tbl OR isIndex) AND sql GLOB 'CREATE *'" " ORDER BY tbl DESC, name=='sqlite_sequence' DESC" ); @@ -1196,7 +1218,7 @@ static int recoverWriteSchema1(sqlite3_recover *p){ zName, zName, zSql )); } - rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + rc = recoverOneStmt(p->dbOut, zSql); if( rc==SQLITE_OK ){ recoverSqlCallback(p, zSql); if( bTable && !bVirtual ){ @@ -1238,15 +1260,17 @@ static int recoverWriteSchema2(sqlite3_recover *p){ p->bSlowIndexes ? "SELECT rootpage, sql FROM recovery.schema " " WHERE type!='table' AND type!='index'" + " AND sql GLOB 'CREATE *'" : "SELECT rootpage, sql FROM recovery.schema " " WHERE type!='table' AND (type!='index' OR sql NOT LIKE '%unique%')" + " AND sql GLOB 'CREATE *'" ); if( pSelect ){ while( sqlite3_step(pSelect)==SQLITE_ROW ){ const char *zSql = (const char*)sqlite3_column_text(pSelect, 1); - int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0); + int rc = recoverOneStmt(p->dbOut, zSql); if( rc==SQLITE_OK ){ recoverSqlCallback(p, zSql); }else if( rc!=SQLITE_ERROR ){ @@ -2624,7 +2648,7 @@ static void recoverStep(sqlite3_recover *p){ if( bUseWrapper ) recoverUninstallWrapper(p); }while( p->errCode==SQLITE_NOTADB && (bUseWrapper--) - && SQLITE_OK==sqlite3_exec(p->dbIn, "ROLLBACK", 0, 0, 0) + && SQLITE_OK==recoverOneStmt(p->dbIn, "ROLLBACK") ); } @@ -2689,7 +2713,7 @@ static void recoverStep(sqlite3_recover *p){ ** database. Regardless of whether or not an error has occurred, make ** an attempt to end the read transaction on the input database. */ recoverExec(p, p->dbOut, "COMMIT"); - rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + rc = recoverOneStmt(p->dbIn, "END"); if( p->errCode==SQLITE_OK ) p->errCode = rc; recoverSqlCallback(p, "PRAGMA writable_schema = off"); @@ -2885,7 +2909,7 @@ int sqlite3_recover_finish(sqlite3_recover *p){ }else{ recoverFinalCleanup(p); if( p->bCloseTransaction && sqlite3_get_autocommit(p->dbIn)==0 ){ - rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0); + rc = recoverOneStmt(p->dbIn, "END"); if( p->errCode==SQLITE_OK ) p->errCode = rc; } rc = p->errCode; diff --git a/manifest b/manifest index 3c0717eb62..1bdf3501e8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\scrash\sthat\scould\soccur\sif\ssqlite3_deserialize()\swas\sused\sto\soverwrite\sa\sdatabase\swith\san\sopen\stransaction\son\sit.\sBug\sintroduced\sby\s[fc42d31d6fca21ab]\son\s2018-03-07\sand\sfirst\sreleased\sin\sversion\s3.23.0.\sThis\sis\snot\sa\svulnerability\sas\san\sattacker\sthat\scan\sexploit\sthis\salready\shas\sthe\sability\sto\sexecute\sarbitrary\scode.\sForum\spost\s[forum:39134ba029\s|\s39134ba029]. -D 2026-04-27T12:28:23.589 +C Harden\sthe\srecovery\sextension\sagainst\sSQL\sinjections\scoming\sfrom\sthe\nsqlite_schema\stable\sof\sthe\sdatabase\sbeing\srecovered. +D 2026-05-01T13:56:19.868 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -486,7 +486,7 @@ F ext/recover/recoverpgsz.test 88766fcb810e52ee05335c456d4e5fb06d02b73d3ccb48c52 F ext/recover/recoverrowid.test f948bf4024a5f41b0e21b8af80c60564c5b5d78c05a8d64fc00787715ff9f45f F ext/recover/recoverslowidx.test c90d59c46bb8924a973ac6fbc38f3163cee38cc240256addcab1cf1a322c37dc F ext/recover/recoversql.test e66d01f95302a223bcd3fd42b5ee58dc2b53d70afa90b0d00e41e4b8eab20486 -F ext/recover/sqlite3recover.c 56c216332ea91233d6d820d429f3384adbec9ecedda67aa98186b691d427cc57 +F ext/recover/sqlite3recover.c dfbfca5a8e4909333e5aa3749bcffb5dd7e396de9541c338f8cc843b2e20217e F ext/recover/sqlite3recover.h 011c799f02deb70ab685916f6f538e6bb32c4e0025e79bfd0e24ff9c74820959 F ext/recover/test_recover.c 3d0fb1df7823f5bc22a0b93955034d16a2dfa2eb1e443e9a0123a77f120599a3 F ext/rtree/README 734aa36238bcd2dee91db5dba107d5fcbdb02396612811377a8ad50f1272b1c1 @@ -2197,9 +2197,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P e3f318bf52932460890333c6dcae352ab4b0c943d7be67734649eff1ff9fc9fc -Q +77662cce9aa452ba5820e5f1a864a802763bd83b937cdb16e7025219eff7bd08 -R 13f45488f2adbf7d8419fcbad47e0b59 +P b7b0745d6def6b141c900884cd97f10f3afedcbfb4bd9ce2409ee2380a8602dd +R 11219c7cfe7bdcf239f28e0e5affe87f U drh -Z 52f7987a045aaf068eaa89a2ec557d9d +Z 6e7e7042c5556e0b59775c06541d4c66 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2c1fae267b..37ca49fffc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b7b0745d6def6b141c900884cd97f10f3afedcbfb4bd9ce2409ee2380a8602dd +555401fe048a51ecaed3ef672723b6ef8e1340c7028c11a17731abbc399bc078