From: drh <> Date: Mon, 23 Feb 2026 01:34:14 +0000 (+0000) Subject: When doing an SQLAR archive extraction in the CLI, postpone creating symlinks until... X-Git-Tag: version-3.52.0~42 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9dac613f0437952476d977409318c456d1d2dd3e;p=thirdparty%2Fsqlite.git When doing an SQLAR archive extraction in the CLI, postpone creating symlinks until after all files and directories have been created. This prevents a hostile archive from creating a symlink through which it can subsequently write content outside of the target directory. [forum:forumpost/9e176adfef91c207|Forum post 9e176adfef91c207]. FossilOrigin-Name: 9719034d4d3becda127dc294f7f58ded9c982509c690dd55b56310912957eb51 --- diff --git a/manifest b/manifest index f368ebcd13..0ad630f8bd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improved\simplementation\sof\srealpath()\sin\sthe\sfileio.c\sextension\sthat\sdoes\snot\srequire\nthe\slast\selement\sof\sthe\spath\sto\sactually\sexist. -D 2026-02-23T00:57:00.749 +C When\sdoing\san\sSQLAR\sarchive\sextraction\sin\sthe\sCLI,\spostpone\screating\ssymlinks\suntil\safter\nall\sfiles\sand\sdirectories\shave\sbeen\screated.\s\sThis\sprevents\sa\shostile\sarchive\sfrom\ncreating\sa\ssymlink\sthrough\swhich\sit\scan\ssubsequently\swrite\scontent\soutside\sof\sthe\starget\ndirectory.\s\s[forum:forumpost/9e176adfef91c207|Forum\spost\s9e176adfef91c207]. +D 2026-02-23T01:34:14.113 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 3e87584890a4e9797865e4771689d8d1aca3b0f824f973192784716ecfa320a2 +F src/shell.c.in 096db72b94687e46d7805e96c2a28fd1525fc6735d8967b9ce37b0b9dbbb9c90 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 8bb8941930378b436f1353603be194644568b55fe347475be0caddddad40efa3 -R 655393f8c2d5cab161b45421e875ddda +P 4df4999484d9008d8af3c9c340810e0cf5f57161ba053ed5501276b450577039 +R 536f614f7f299837eaceb0448191c5cb U drh -Z 730476000580ff31324acec73800a8e3 +Z 1a29d5706c125cd77c3ea8fb58018127 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dbb7963f69..75b99ad74c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4df4999484d9008d8af3c9c340810e0cf5f57161ba053ed5501276b450577039 +9719034d4d3becda127dc294f7f58ded9c982509c690dd55b56310912957eb51 diff --git a/src/shell.c.in b/src/shell.c.in index 8eaa060812..f071e09bbf 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -6665,11 +6665,14 @@ static int arRemoveCommand(ArCommand *pAr){ */ static int arExtractCommand(ArCommand *pAr){ const char *zSql1 = - "SELECT " - " ($dir || name)," - " writefile(($dir || name), %s, mode, mtime) " - "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)" - " AND name NOT GLOB '*..[/\\]*'"; + "SELECT ($dir || name),\n" + " writefile(($dir || name), %s, mode, mtime)\n" + " FROM %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"; const char *azExtraArg[] = { "sqlar_uncompress(data, sz)", @@ -6705,16 +6708,21 @@ static int arExtractCommand(ArCommand *pAr){ j = sqlite3_bind_parameter_index(pSql, "$dir"); sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC); - /* Run the SELECT statement twice. The first time, writefile() is called - ** for all archive members that should be extracted. The second time, - ** only for the directories. This is because the timestamps for - ** extracted directories must be reset after they are populated (as - ** populating them changes the timestamp). */ - for(i=0; i<2; i++){ - j = sqlite3_bind_parameter_index(pSql, "$dirOnly"); + /* 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 + ** 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++){ + 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 ){