Fossil SCM
Do not attempt to extract symbolic links from ZIP archive. See [https://sqlite.org/forum/forumpost/2026-02-21T11:04:36z|SQLite Forum Post 2026-02-21T11:04:36z] for an explanation of why this is a potential vulnerability. We could, in theory, enhance ZIP archive unpacking to handle symbolic links safely, but as the use of symbolic links in ZIP archives is rare, and because ZIP archive extraction by Fossil is rare, doing so might create more problems than it solves.
Commit
31ce0d31c4d0cbe64cb55973e0c77596964b143522f71b5e49c6971d4c263672
Parent
a363d26bec0a68b…
2 files changed
+5
-3
+8
-2
+5
-3
| --- src/xsystem.c | ||
| +++ src/xsystem.c | ||
| @@ -484,17 +484,19 @@ | ||
| 484 | 484 | a[0] = argv[0]; |
| 485 | 485 | a[1] = (char*)zZipfile; |
| 486 | 486 | if( doList ){ |
| 487 | 487 | a[2] = ".mode column"; |
| 488 | 488 | a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date," |
| 489 | - " time(mtime,'unixepoch') AS Time, name AS Name" | |
| 489 | + " time(mtime,'unixepoch') AS Time," | |
| 490 | + " if(((mode>>12)&15)=10,name||' -> '||data,name) AS Name" | |
| 490 | 491 | " FROM zip;"; |
| 491 | 492 | n = 4; |
| 492 | 493 | }else{ |
| 493 | 494 | a[2] = ".mode list"; |
| 494 | - a[3] = "SELECT if(writefile(name,data,mode,mtime) IS NULL," | |
| 495 | - "'error: '||name,'extracting: '||name) FROM zip;"; | |
| 495 | + a[3] = "SELECT if(((mode>>12)&15)==10,'symlink-ignored: '||name," | |
| 496 | + "writefile(name,data,mode,mtime) IS NULL," | |
| 497 | + "'error: '||name,'extracting: '||name) FROM zip"; | |
| 496 | 498 | n = 4; |
| 497 | 499 | } |
| 498 | 500 | a[n] = 0; |
| 499 | 501 | sqlite3_shell(n,a); |
| 500 | 502 | } |
| 501 | 503 |
| --- src/xsystem.c | |
| +++ src/xsystem.c | |
| @@ -484,17 +484,19 @@ | |
| 484 | a[0] = argv[0]; |
| 485 | a[1] = (char*)zZipfile; |
| 486 | if( doList ){ |
| 487 | a[2] = ".mode column"; |
| 488 | a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date," |
| 489 | " time(mtime,'unixepoch') AS Time, name AS Name" |
| 490 | " FROM zip;"; |
| 491 | n = 4; |
| 492 | }else{ |
| 493 | a[2] = ".mode list"; |
| 494 | a[3] = "SELECT if(writefile(name,data,mode,mtime) IS NULL," |
| 495 | "'error: '||name,'extracting: '||name) FROM zip;"; |
| 496 | n = 4; |
| 497 | } |
| 498 | a[n] = 0; |
| 499 | sqlite3_shell(n,a); |
| 500 | } |
| 501 |
| --- src/xsystem.c | |
| +++ src/xsystem.c | |
| @@ -484,17 +484,19 @@ | |
| 484 | a[0] = argv[0]; |
| 485 | a[1] = (char*)zZipfile; |
| 486 | if( doList ){ |
| 487 | a[2] = ".mode column"; |
| 488 | a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date," |
| 489 | " time(mtime,'unixepoch') AS Time," |
| 490 | " if(((mode>>12)&15)=10,name||' -> '||data,name) AS Name" |
| 491 | " FROM zip;"; |
| 492 | n = 4; |
| 493 | }else{ |
| 494 | a[2] = ".mode list"; |
| 495 | a[3] = "SELECT if(((mode>>12)&15)==10,'symlink-ignored: '||name," |
| 496 | "writefile(name,data,mode,mtime) IS NULL," |
| 497 | "'error: '||name,'extracting: '||name) FROM zip"; |
| 498 | n = 4; |
| 499 | } |
| 500 | a[n] = 0; |
| 501 | sqlite3_shell(n,a); |
| 502 | } |
| 503 |
+8
-2
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -624,11 +624,14 @@ | ||
| 624 | 624 | if( g.argc>3 ){ |
| 625 | 625 | fossil_fatal("extra arguments after \"fossil test-filezip -l ARCHIVE\""); |
| 626 | 626 | } |
| 627 | 627 | sqlite3_zipfile_init(g.db, 0, 0); |
| 628 | 628 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 629 | - db_prepare(&q, "SELECT sz, datetime(mtime,'unixepoch'), name FROM z1"); | |
| 629 | + db_prepare(&q, | |
| 630 | + "SELECT sz, datetime(mtime,'unixepoch')," | |
| 631 | + " if(((mode>>12)&15)==10,name||' -> '||data,name) FROM z1" | |
| 632 | + ); | |
| 630 | 633 | while( db_step(&q)==SQLITE_ROW ){ |
| 631 | 634 | int sz = db_column_int(&q, 0); |
| 632 | 635 | szTotal += sz; |
| 633 | 636 | if( nRow==0 ){ |
| 634 | 637 | fossil_print(" Length Date Time Name\n"); |
| @@ -652,11 +655,14 @@ | ||
| 652 | 655 | fossil_fatal("extra arguments after \"fossil test-filezip -x ARCHIVE\""); |
| 653 | 656 | } |
| 654 | 657 | sqlite3_zipfile_init(g.db, 0, 0); |
| 655 | 658 | sqlite3_fileio_init(g.db, 0, 0); |
| 656 | 659 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 657 | - db_multi_exec("SELECT writefile(name,data) FROM z1"); | |
| 660 | + db_multi_exec( | |
| 661 | + "SELECT writefile(name,data) FROM z1" | |
| 662 | + " WHERE ((mode>>12)&15)!=10" | |
| 663 | + ); | |
| 658 | 664 | }else{ |
| 659 | 665 | /* Without the -x or -l options, construct a new ZIP archive */ |
| 660 | 666 | int i; |
| 661 | 667 | Blob zip; |
| 662 | 668 | Blob file; |
| 663 | 669 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -624,11 +624,14 @@ | |
| 624 | if( g.argc>3 ){ |
| 625 | fossil_fatal("extra arguments after \"fossil test-filezip -l ARCHIVE\""); |
| 626 | } |
| 627 | sqlite3_zipfile_init(g.db, 0, 0); |
| 628 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 629 | db_prepare(&q, "SELECT sz, datetime(mtime,'unixepoch'), name FROM z1"); |
| 630 | while( db_step(&q)==SQLITE_ROW ){ |
| 631 | int sz = db_column_int(&q, 0); |
| 632 | szTotal += sz; |
| 633 | if( nRow==0 ){ |
| 634 | fossil_print(" Length Date Time Name\n"); |
| @@ -652,11 +655,14 @@ | |
| 652 | fossil_fatal("extra arguments after \"fossil test-filezip -x ARCHIVE\""); |
| 653 | } |
| 654 | sqlite3_zipfile_init(g.db, 0, 0); |
| 655 | sqlite3_fileio_init(g.db, 0, 0); |
| 656 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 657 | db_multi_exec("SELECT writefile(name,data) FROM z1"); |
| 658 | }else{ |
| 659 | /* Without the -x or -l options, construct a new ZIP archive */ |
| 660 | int i; |
| 661 | Blob zip; |
| 662 | Blob file; |
| 663 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -624,11 +624,14 @@ | |
| 624 | if( g.argc>3 ){ |
| 625 | fossil_fatal("extra arguments after \"fossil test-filezip -l ARCHIVE\""); |
| 626 | } |
| 627 | sqlite3_zipfile_init(g.db, 0, 0); |
| 628 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 629 | db_prepare(&q, |
| 630 | "SELECT sz, datetime(mtime,'unixepoch')," |
| 631 | " if(((mode>>12)&15)==10,name||' -> '||data,name) FROM z1" |
| 632 | ); |
| 633 | while( db_step(&q)==SQLITE_ROW ){ |
| 634 | int sz = db_column_int(&q, 0); |
| 635 | szTotal += sz; |
| 636 | if( nRow==0 ){ |
| 637 | fossil_print(" Length Date Time Name\n"); |
| @@ -652,11 +655,14 @@ | |
| 655 | fossil_fatal("extra arguments after \"fossil test-filezip -x ARCHIVE\""); |
| 656 | } |
| 657 | sqlite3_zipfile_init(g.db, 0, 0); |
| 658 | sqlite3_fileio_init(g.db, 0, 0); |
| 659 | db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
| 660 | db_multi_exec( |
| 661 | "SELECT writefile(name,data) FROM z1" |
| 662 | " WHERE ((mode>>12)&15)!=10" |
| 663 | ); |
| 664 | }else{ |
| 665 | /* Without the -x or -l options, construct a new ZIP archive */ |
| 666 | int i; |
| 667 | Blob zip; |
| 668 | Blob file; |
| 669 |