From: drh <> Date: Mon, 23 Feb 2026 12:19:05 +0000 (+0000) Subject: Change the SQLAR archive extraction algorithm in the CLI so that it X-Git-Tag: version-3.52.0~40 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ffd9892a023ea3b66a0dc8de39cc8829e39bb1ec;p=thirdparty%2Fsqlite.git Change the SQLAR archive extraction algorithm in the CLI so that it uses the newly enhanced realpath() SQL function to guard against attacks that use symlinks to try to write files outside of the destination directory. [forum:/forumpost/641b09daa17d9086|Forum post 641b09daa17d9086]. FossilOrigin-Name: 7cced53e8c508fbf1816162c5358c77a712f76a38fd18f07171efc3c028a3c57 --- diff --git a/manifest b/manifest index 0d6b98c841..d8e909b52f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enhance\sthe\srealpath()\sSQL\sfunction\sin\sthe\sfileio.c\sextension\nso\sthat\sit\sworks\sever\sfor\spathnames\sthat\sdo\snot\sexist. -D 2026-02-23T11:44:30.800 +C Change\sthe\sSQLAR\sarchive\sextraction\salgorithm\sin\sthe\sCLI\sso\sthat\sit\nuses\sthe\snewly\senhanced\srealpath()\sSQL\sfunction\sto\sguard\sagainst\nattacks\sthat\suse\ssymlinks\sto\stry\sto\swrite\sfiles\soutside\sof\sthe\ndestination\sdirectory.\n[forum:/forumpost/641b09daa17d9086|Forum\spost\s641b09daa17d9086]. +D 2026-02-23T12:19:05.604 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -739,7 +739,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 615d62112f5c14fb24facf9391492b42403875bfd4288db6ba10d7e6fbc22c4c -F src/shell.c.in 096db72b94687e46d7805e96c2a28fd1525fc6735d8967b9ce37b0b9dbbb9c90 +F src/shell.c.in 2acdfca982deb70cdfefb8b422822d4e0234fe4dde6ff2bd9020b26445853917 F src/sqlite.h.in c7582608c8270428b288a529f4a4170298a19548266b55edaa2e70ce8d607f0e F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca @@ -2195,8 +2195,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 9719034d4d3becda127dc294f7f58ded9c982509c690dd55b56310912957eb51 -R bf2d4ba90ab9cecc4df495425f391fd6 +P 27a5735fb1e194d763ab9fdb933fad4f694fb2f8ad19205d17ac81caebd82548 +R 5f3ef586ef29ee2dd47a3c1f07f1d05a U drh -Z 7b2a1df61967b73bec8a68ebbc48bbf5 +Z 1ae541096a3f2b3c5c84a58179012397 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b84b3aaf79..2c985e691c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -27a5735fb1e194d763ab9fdb933fad4f694fb2f8ad19205d17ac81caebd82548 +7cced53e8c508fbf1816162c5358c77a712f76a38fd18f07171efc3c028a3c57 diff --git a/src/shell.c.in b/src/shell.c.in index f071e09bbf..fb1dc2c2cd 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -6665,14 +6665,15 @@ static int arRemoveCommand(ArCommand *pAr){ */ static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = + "WITH dest(dpath,dlen) AS (SELECT realpath($dir),length(realpath($dir)))\n" "SELECT ($dir || name),\n" - " writefile(($dir || name), %s, mode, mtime)\n" - " FROM %s\n" + " CASE WHEN $dryrun THEN 0\n" + " ELSE writefile($dir||name, %s, mode, mtime) END\n" + " FROM dest CROSS JOIN %s\n" " WHERE (%s)\n" - " AND NOT ((mode&0xf000)==0x8000 AND $pass>0)\n" /* Files on pass 0 */ - " AND NOT (data IS NULL AND $pass==1)\n" /* Dirs on passes 0,2 */ - " AND NOT ((mode&0xf000)==0xa000 AND $pass<>1)\n" /* Symlink on pass 1 */ - " AND name NOT GLOB '*..[/\\]*'\n"; + " AND (data IS NULL OR $pass==0)\n" /* Dirs both passes */ + " AND dpath=substr(realpath($dir||name),1,dlen)\n" /* No escapes */ + " AND name NOT GLOB '*..[/\\]*'\n"; /* No /../ in paths */ const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", @@ -6707,29 +6708,28 @@ static int arExtractCommand(ArCommand *pAr){ if( rc==SQLITE_OK ){ j = sqlite3_bind_parameter_index(pSql, "$dir"); sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); + j = sqlite3_bind_parameter_index(pSql, "$dryrun"); + sqlite3_bind_int(pSql, j, pAr->bDryRun); - /* Run the SELECT statement thrice: - ** (1) writefile() all files and directories, but not symlinks - ** (2) writefile() for symlinks - ** (3) writefile() for directory again - ** Symlinks are created after everything else to prevent writing content - ** through a symlink that was created by the extraction. - ** The third pass is so that the timestamps for extracted directories + /* Run the SELECT statement twice + ** (0) writefile() all files and directories + ** (1) writefile() for directory again + ** The second pass is so that the timestamps for extracted directories ** will be reset to the value in the archive, since populating them - ** in the previous passes will have changed the timestamp. */ - for(i=0; i<3; i++){ + ** in the first pass will have changed the timestamp. */ + for(i=0; i<2; i++){ j = sqlite3_bind_parameter_index(pSql, "$pass"); sqlite3_bind_int(pSql, j, i); if( pAr->bDryRun ){ cli_printf(pAr->out, "%s\n", sqlite3_sql(pSql)); - break; - }else{ - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ - if( i==0 && pAr->bVerbose ){ - cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); - } + if( pAr->bVerbose==0 ) break; + } + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){ + if( i==0 && pAr->bVerbose ){ + cli_printf(pAr->out, "%s\n", sqlite3_column_text(pSql, 0)); } } + if( pAr->bDryRun ) break; shellReset(&rc, pSql); } shellFinalize(&rc, pSql);