]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Change the SQLAR archive extraction algorithm in the CLI so that it
authordrh <>
Mon, 23 Feb 2026 12:19:05 +0000 (12:19 +0000)
committerdrh <>
Mon, 23 Feb 2026 12:19:05 +0000 (12:19 +0000)
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

manifest
manifest.uuid
src/shell.c.in

index 0d6b98c84158c541297147df5d88c5d33e443525..d8e909b52fad81bab14064325d63276f29c5a328 100644 (file)
--- 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.
index b84b3aaf79f5e3978f08eff56f6893df36b65d5f..2c985e691c493dd9d880b65e7b734c46a842dd7e 100644 (file)
@@ -1 +1 @@
-27a5735fb1e194d763ab9fdb933fad4f694fb2f8ad19205d17ac81caebd82548
+7cced53e8c508fbf1816162c5358c77a712f76a38fd18f07171efc3c028a3c57
index f071e09bbfb0f8f99047f273ba27f6ccde0a5f12..fb1dc2c2cdf5aefadd7e121acc767a6e327efe2b 100644 (file)
@@ -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);