Fossil SCM
Improved detection of attempts to write through a symlink. Now also works for "revert", "stash", and "undo/redo".
Commit
f63297b2c521f48798158ead68cbc6d908f7fb9224579f6126cedff0f6aea098
Parent
a6abfb911b31d8f…
5 files changed
+25
+2
+4
-2
+3
+3
-6
+25
| --- src/file.c | ||
| +++ src/file.c | ||
| @@ -370,10 +370,35 @@ | ||
| 370 | 370 | i = j; |
| 371 | 371 | } |
| 372 | 372 | fossil_free(z); |
| 373 | 373 | return 0; |
| 374 | 374 | } |
| 375 | + | |
| 376 | +/* | |
| 377 | +** The file named zFile is suppose to be an in-tree file. Check to | |
| 378 | +** ensure that it will be safe to write to this file by verifying that | |
| 379 | +** there are no symlinks or other non-directory objects in between the | |
| 380 | +** root of the checkout and zFile. | |
| 381 | +** | |
| 382 | +** If a problem is found, print a warning message (using fossil_warning()) | |
| 383 | +** and return non-zero. If everything is ok, return zero. | |
| 384 | +*/ | |
| 385 | +int file_unsafe_in_tree_path(const char *zFile){ | |
| 386 | + int n; | |
| 387 | + if( !file_is_absolute_path(zFile) ){ | |
| 388 | + fossil_panic("%s is not an absolute pathname",zFile); | |
| 389 | + } | |
| 390 | + if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){ | |
| 391 | + fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile); | |
| 392 | + } | |
| 393 | + n = file_nondir_objects_on_path(g.zLocalRoot, zFile); | |
| 394 | + if( n ){ | |
| 395 | + fossil_warning("cannot write to %s because non-directory object %.*s" | |
| 396 | + " is in the way", zFile, n, zFile); | |
| 397 | + } | |
| 398 | + return n; | |
| 399 | +} | |
| 375 | 400 | |
| 376 | 401 | /* |
| 377 | 402 | ** Return 1 if zFilename is a directory. Return 0 if zFilename |
| 378 | 403 | ** does not exist. Return 2 if zFilename exists but is something |
| 379 | 404 | ** other than a directory. |
| 380 | 405 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -370,10 +370,35 @@ | |
| 370 | i = j; |
| 371 | } |
| 372 | fossil_free(z); |
| 373 | return 0; |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 | ** Return 1 if zFilename is a directory. Return 0 if zFilename |
| 378 | ** does not exist. Return 2 if zFilename exists but is something |
| 379 | ** other than a directory. |
| 380 |
| --- src/file.c | |
| +++ src/file.c | |
| @@ -370,10 +370,35 @@ | |
| 370 | i = j; |
| 371 | } |
| 372 | fossil_free(z); |
| 373 | return 0; |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 | ** The file named zFile is suppose to be an in-tree file. Check to |
| 378 | ** ensure that it will be safe to write to this file by verifying that |
| 379 | ** there are no symlinks or other non-directory objects in between the |
| 380 | ** root of the checkout and zFile. |
| 381 | ** |
| 382 | ** If a problem is found, print a warning message (using fossil_warning()) |
| 383 | ** and return non-zero. If everything is ok, return zero. |
| 384 | */ |
| 385 | int file_unsafe_in_tree_path(const char *zFile){ |
| 386 | int n; |
| 387 | if( !file_is_absolute_path(zFile) ){ |
| 388 | fossil_panic("%s is not an absolute pathname",zFile); |
| 389 | } |
| 390 | if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){ |
| 391 | fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile); |
| 392 | } |
| 393 | n = file_nondir_objects_on_path(g.zLocalRoot, zFile); |
| 394 | if( n ){ |
| 395 | fossil_warning("cannot write to %s because non-directory object %.*s" |
| 396 | " is in the way", zFile, n, zFile); |
| 397 | } |
| 398 | return n; |
| 399 | } |
| 400 | |
| 401 | /* |
| 402 | ** Return 1 if zFilename is a directory. Return 0 if zFilename |
| 403 | ** does not exist. Return 2 if zFilename exists but is something |
| 404 | ** other than a directory. |
| 405 |
+2
| --- src/stash.c | ||
| +++ src/stash.c | ||
| @@ -334,10 +334,12 @@ | ||
| 334 | 334 | blob_write_to_file(&delta, zNPath); |
| 335 | 335 | file_setexe(zNPath, isExec); |
| 336 | 336 | }else if( isRemoved ){ |
| 337 | 337 | fossil_print("DELETE %s\n", zOrig); |
| 338 | 338 | file_delete(zOPath); |
| 339 | + }else if( file_unsafe_in_tree_path(zNPath) ){ | |
| 340 | + /* Ignore the unsafe path */ | |
| 339 | 341 | }else{ |
| 340 | 342 | Blob a, b, out, disk; |
| 341 | 343 | int isNewLink = file_islink(zOPath); |
| 342 | 344 | db_ephemeral_blob(&q, 6, &delta); |
| 343 | 345 | blob_read_from_file(&disk, zOPath, RepoFILE); |
| 344 | 346 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -334,10 +334,12 @@ | |
| 334 | blob_write_to_file(&delta, zNPath); |
| 335 | file_setexe(zNPath, isExec); |
| 336 | }else if( isRemoved ){ |
| 337 | fossil_print("DELETE %s\n", zOrig); |
| 338 | file_delete(zOPath); |
| 339 | }else{ |
| 340 | Blob a, b, out, disk; |
| 341 | int isNewLink = file_islink(zOPath); |
| 342 | db_ephemeral_blob(&q, 6, &delta); |
| 343 | blob_read_from_file(&disk, zOPath, RepoFILE); |
| 344 |
| --- src/stash.c | |
| +++ src/stash.c | |
| @@ -334,10 +334,12 @@ | |
| 334 | blob_write_to_file(&delta, zNPath); |
| 335 | file_setexe(zNPath, isExec); |
| 336 | }else if( isRemoved ){ |
| 337 | fossil_print("DELETE %s\n", zOrig); |
| 338 | file_delete(zOPath); |
| 339 | }else if( file_unsafe_in_tree_path(zNPath) ){ |
| 340 | /* Ignore the unsafe path */ |
| 341 | }else{ |
| 342 | Blob a, b, out, disk; |
| 343 | int isNewLink = file_islink(zOPath); |
| 344 | db_ephemeral_blob(&q, 6, &delta); |
| 345 | blob_read_from_file(&disk, zOPath, RepoFILE); |
| 346 |
+4
-2
| --- src/undo.c | ||
| +++ src/undo.c | ||
| @@ -52,11 +52,11 @@ | ||
| 52 | 52 | int new_exe; |
| 53 | 53 | int new_link; |
| 54 | 54 | int old_link; |
| 55 | 55 | Blob current; |
| 56 | 56 | Blob new; |
| 57 | - zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); | |
| 57 | + zFullname = mprintf("%s%s", g.zLocalRoot, zPathname); | |
| 58 | 58 | old_link = db_column_int(&q, 3); |
| 59 | 59 | new_exists = file_size(zFullname, RepoFILE)>=0; |
| 60 | 60 | new_link = file_islink(0); |
| 61 | 61 | if( new_exists ){ |
| 62 | 62 | blob_read_from_file(¤t, zFullname, RepoFILE); |
| @@ -69,11 +69,13 @@ | ||
| 69 | 69 | old_exists = db_column_int(&q, 1); |
| 70 | 70 | old_exe = db_column_int(&q, 2); |
| 71 | 71 | if( old_exists ){ |
| 72 | 72 | db_ephemeral_blob(&q, 0, &new); |
| 73 | 73 | } |
| 74 | - if( old_exists ){ | |
| 74 | + if( file_unsafe_in_tree_path(zFullname) ){ | |
| 75 | + /* do nothign with this unsafe file */ | |
| 76 | + }else if( old_exists ){ | |
| 75 | 77 | if( new_exists ){ |
| 76 | 78 | fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname); |
| 77 | 79 | }else{ |
| 78 | 80 | fossil_print("NEW %s\n", zPathname); |
| 79 | 81 | } |
| 80 | 82 |
| --- src/undo.c | |
| +++ src/undo.c | |
| @@ -52,11 +52,11 @@ | |
| 52 | int new_exe; |
| 53 | int new_link; |
| 54 | int old_link; |
| 55 | Blob current; |
| 56 | Blob new; |
| 57 | zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); |
| 58 | old_link = db_column_int(&q, 3); |
| 59 | new_exists = file_size(zFullname, RepoFILE)>=0; |
| 60 | new_link = file_islink(0); |
| 61 | if( new_exists ){ |
| 62 | blob_read_from_file(¤t, zFullname, RepoFILE); |
| @@ -69,11 +69,13 @@ | |
| 69 | old_exists = db_column_int(&q, 1); |
| 70 | old_exe = db_column_int(&q, 2); |
| 71 | if( old_exists ){ |
| 72 | db_ephemeral_blob(&q, 0, &new); |
| 73 | } |
| 74 | if( old_exists ){ |
| 75 | if( new_exists ){ |
| 76 | fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname); |
| 77 | }else{ |
| 78 | fossil_print("NEW %s\n", zPathname); |
| 79 | } |
| 80 |
| --- src/undo.c | |
| +++ src/undo.c | |
| @@ -52,11 +52,11 @@ | |
| 52 | int new_exe; |
| 53 | int new_link; |
| 54 | int old_link; |
| 55 | Blob current; |
| 56 | Blob new; |
| 57 | zFullname = mprintf("%s%s", g.zLocalRoot, zPathname); |
| 58 | old_link = db_column_int(&q, 3); |
| 59 | new_exists = file_size(zFullname, RepoFILE)>=0; |
| 60 | new_link = file_islink(0); |
| 61 | if( new_exists ){ |
| 62 | blob_read_from_file(¤t, zFullname, RepoFILE); |
| @@ -69,11 +69,13 @@ | |
| 69 | old_exists = db_column_int(&q, 1); |
| 70 | old_exe = db_column_int(&q, 2); |
| 71 | if( old_exists ){ |
| 72 | db_ephemeral_blob(&q, 0, &new); |
| 73 | } |
| 74 | if( file_unsafe_in_tree_path(zFullname) ){ |
| 75 | /* do nothign with this unsafe file */ |
| 76 | }else if( old_exists ){ |
| 77 | if( new_exists ){ |
| 78 | fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname); |
| 79 | }else{ |
| 80 | fossil_print("NEW %s\n", zPathname); |
| 81 | } |
| 82 |
+3
| --- src/update.c | ||
| +++ src/update.c | ||
| @@ -927,14 +927,17 @@ | ||
| 927 | 927 | " SET pathname=origname, origname=NULL" |
| 928 | 928 | " WHERE pathname=%Q AND origname!=pathname;" |
| 929 | 929 | "DELETE FROM vfile WHERE pathname=%Q", |
| 930 | 930 | zFile, zFile |
| 931 | 931 | ); |
| 932 | + }else if( file_unsafe_in_tree_path(zFull) ){ | |
| 933 | + /* Ignore this file */ | |
| 932 | 934 | }else{ |
| 933 | 935 | sqlite3_int64 mtime; |
| 934 | 936 | int rvChnged = 0; |
| 935 | 937 | int rvPerm = manifest_file_mperm(pRvFile); |
| 938 | + int n; | |
| 936 | 939 | |
| 937 | 940 | /* Determine if reverted-to file is different than checked out file. */ |
| 938 | 941 | if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){ |
| 939 | 942 | rvChnged = manifest_file_mperm(pRvFile)!=rvPerm |
| 940 | 943 | || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0; |
| 941 | 944 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -927,14 +927,17 @@ | |
| 927 | " SET pathname=origname, origname=NULL" |
| 928 | " WHERE pathname=%Q AND origname!=pathname;" |
| 929 | "DELETE FROM vfile WHERE pathname=%Q", |
| 930 | zFile, zFile |
| 931 | ); |
| 932 | }else{ |
| 933 | sqlite3_int64 mtime; |
| 934 | int rvChnged = 0; |
| 935 | int rvPerm = manifest_file_mperm(pRvFile); |
| 936 | |
| 937 | /* Determine if reverted-to file is different than checked out file. */ |
| 938 | if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){ |
| 939 | rvChnged = manifest_file_mperm(pRvFile)!=rvPerm |
| 940 | || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0; |
| 941 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -927,14 +927,17 @@ | |
| 927 | " SET pathname=origname, origname=NULL" |
| 928 | " WHERE pathname=%Q AND origname!=pathname;" |
| 929 | "DELETE FROM vfile WHERE pathname=%Q", |
| 930 | zFile, zFile |
| 931 | ); |
| 932 | }else if( file_unsafe_in_tree_path(zFull) ){ |
| 933 | /* Ignore this file */ |
| 934 | }else{ |
| 935 | sqlite3_int64 mtime; |
| 936 | int rvChnged = 0; |
| 937 | int rvPerm = manifest_file_mperm(pRvFile); |
| 938 | int n; |
| 939 | |
| 940 | /* Determine if reverted-to file is different than checked out file. */ |
| 941 | if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){ |
| 942 | rvChnged = manifest_file_mperm(pRvFile)!=rvPerm |
| 943 | || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0; |
| 944 |
+3
-6
| --- src/vfile.c | ||
| +++ src/vfile.c | ||
| @@ -314,10 +314,13 @@ | ||
| 314 | 314 | id = db_column_int(&q, 0); |
| 315 | 315 | zName = db_column_text(&q, 1); |
| 316 | 316 | rid = db_column_int(&q, 2); |
| 317 | 317 | isExe = db_column_int(&q, 3); |
| 318 | 318 | isLink = db_column_int(&q, 4); |
| 319 | + if( file_unsafe_in_tree_path(zName) ){ | |
| 320 | + continue; | |
| 321 | + } | |
| 319 | 322 | content_get(rid, &content); |
| 320 | 323 | if( file_is_the_same(&content, zName) ){ |
| 321 | 324 | blob_reset(&content); |
| 322 | 325 | if( file_setexe(zName, isExe) ){ |
| 323 | 326 | db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", |
| @@ -340,16 +343,10 @@ | ||
| 340 | 343 | blob_reset(&content); |
| 341 | 344 | continue; |
| 342 | 345 | } |
| 343 | 346 | } |
| 344 | 347 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 345 | - n = file_nondir_objects_on_path(g.zLocalRoot, zName); | |
| 346 | - if( n ){ | |
| 347 | - fossil_fatal("cannot write %s because " | |
| 348 | - "non-directory object %.*s is in the way", | |
| 349 | - zName, n, zName); | |
| 350 | - } | |
| 351 | 348 | if( file_isdir(zName, RepoFILE)==1 ){ |
| 352 | 349 | /*TODO(dchest): remove directories? */ |
| 353 | 350 | fossil_fatal("%s is directory, cannot overwrite", zName); |
| 354 | 351 | } |
| 355 | 352 | if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){ |
| 356 | 353 |
| --- src/vfile.c | |
| +++ src/vfile.c | |
| @@ -314,10 +314,13 @@ | |
| 314 | id = db_column_int(&q, 0); |
| 315 | zName = db_column_text(&q, 1); |
| 316 | rid = db_column_int(&q, 2); |
| 317 | isExe = db_column_int(&q, 3); |
| 318 | isLink = db_column_int(&q, 4); |
| 319 | content_get(rid, &content); |
| 320 | if( file_is_the_same(&content, zName) ){ |
| 321 | blob_reset(&content); |
| 322 | if( file_setexe(zName, isExe) ){ |
| 323 | db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", |
| @@ -340,16 +343,10 @@ | |
| 340 | blob_reset(&content); |
| 341 | continue; |
| 342 | } |
| 343 | } |
| 344 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 345 | n = file_nondir_objects_on_path(g.zLocalRoot, zName); |
| 346 | if( n ){ |
| 347 | fossil_fatal("cannot write %s because " |
| 348 | "non-directory object %.*s is in the way", |
| 349 | zName, n, zName); |
| 350 | } |
| 351 | if( file_isdir(zName, RepoFILE)==1 ){ |
| 352 | /*TODO(dchest): remove directories? */ |
| 353 | fossil_fatal("%s is directory, cannot overwrite", zName); |
| 354 | } |
| 355 | if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){ |
| 356 |
| --- src/vfile.c | |
| +++ src/vfile.c | |
| @@ -314,10 +314,13 @@ | |
| 314 | id = db_column_int(&q, 0); |
| 315 | zName = db_column_text(&q, 1); |
| 316 | rid = db_column_int(&q, 2); |
| 317 | isExe = db_column_int(&q, 3); |
| 318 | isLink = db_column_int(&q, 4); |
| 319 | if( file_unsafe_in_tree_path(zName) ){ |
| 320 | continue; |
| 321 | } |
| 322 | content_get(rid, &content); |
| 323 | if( file_is_the_same(&content, zName) ){ |
| 324 | blob_reset(&content); |
| 325 | if( file_setexe(zName, isExe) ){ |
| 326 | db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", |
| @@ -340,16 +343,10 @@ | |
| 343 | blob_reset(&content); |
| 344 | continue; |
| 345 | } |
| 346 | } |
| 347 | if( verbose ) fossil_print("%s\n", &zName[nRepos]); |
| 348 | if( file_isdir(zName, RepoFILE)==1 ){ |
| 349 | /*TODO(dchest): remove directories? */ |
| 350 | fossil_fatal("%s is directory, cannot overwrite", zName); |
| 351 | } |
| 352 | if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){ |
| 353 |