Fossil

Check-in [f63297b2c5]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Improved detection of attempts to write through a symlink. Now also works for "revert", "stash", and "undo/redo".
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | sec2020
Files: files | file ages | folders
SHA3-256: f63297b2c521f48798158ead68cbc6d908f7fb9224579f6126cedff0f6aea098
User & Date: drh 2020-08-19 12:08:45.770
Context
2020-08-19
12:58
Merge additional symlink fixes. Back out comment-only changes from url.c. check-in: 0ea17c2b11 user: drh tags: sec2020-2.12-patch
12:22
Additional defenses against doing "fossil add" of files that are beneath symlinks. check-in: 928b023cb7 user: drh tags: sec2020
12:08
Improved detection of attempts to write through a symlink. Now also works for "revert", "stash", and "undo/redo". check-in: f63297b2c5 user: drh tags: sec2020
00:15
Do not allow the "fossil add" command to add files beneath a symlink. check-in: a6abfb911b user: drh tags: sec2020
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/file.c.
368
369
370
371
372
373
374

























375
376
377
378
379
380
381
    }
    z[j] = '/';
    i = j;
  }
  fossil_free(z);
  return 0;
}


























/*
** Return 1 if zFilename is a directory.  Return 0 if zFilename
** does not exist.  Return 2 if zFilename exists but is something
** other than a directory.
*/
int file_isdir(const char *zFilename, int eFType){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
    }
    z[j] = '/';
    i = j;
  }
  fossil_free(z);
  return 0;
}

/*
** The file named zFile is suppose to be an in-tree file.  Check to
** ensure that it will be safe to write to this file by verifying that
** there are no symlinks or other non-directory objects in between the
** root of the checkout and zFile.
**
** If a problem is found, print a warning message (using fossil_warning())
** and return non-zero.  If everything is ok, return zero.
*/
int file_unsafe_in_tree_path(const char *zFile){
  int n;
  if( !file_is_absolute_path(zFile) ){
    fossil_panic("%s is not an absolute pathname",zFile);
  }
  if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){
    fossil_panic("%s is not a prefix of %s", g.zLocalRoot, zFile);
  }
  n = file_nondir_objects_on_path(g.zLocalRoot, zFile);
  if( n ){
    fossil_warning("cannot write to %s because non-directory object %.*s"
                   " is in the way", zFile, n, zFile);
  }
  return n;
}

/*
** Return 1 if zFilename is a directory.  Return 0 if zFilename
** does not exist.  Return 2 if zFilename exists but is something
** other than a directory.
*/
int file_isdir(const char *zFilename, int eFType){
Changes to src/stash.c.
332
333
334
335
336
337
338


339
340
341
342
343
344
345
      db_multi_exec("INSERT OR IGNORE INTO sfile(pathname) VALUES(%Q)", zNew);
      db_ephemeral_blob(&q, 6, &delta);
      blob_write_to_file(&delta, zNPath);
      file_setexe(zNPath, isExec);
    }else if( isRemoved ){
      fossil_print("DELETE %s\n", zOrig);
      file_delete(zOPath);


    }else{
      Blob a, b, out, disk;
      int isNewLink = file_islink(zOPath);
      db_ephemeral_blob(&q, 6, &delta);
      blob_read_from_file(&disk, zOPath, RepoFILE);
      content_get(rid, &a);
      blob_delta_apply(&a, &delta, &b);







>
>







332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
      db_multi_exec("INSERT OR IGNORE INTO sfile(pathname) VALUES(%Q)", zNew);
      db_ephemeral_blob(&q, 6, &delta);
      blob_write_to_file(&delta, zNPath);
      file_setexe(zNPath, isExec);
    }else if( isRemoved ){
      fossil_print("DELETE %s\n", zOrig);
      file_delete(zOPath);
    }else if( file_unsafe_in_tree_path(zNPath) ){
      /* Ignore the unsafe path */
    }else{
      Blob a, b, out, disk;
      int isNewLink = file_islink(zOPath);
      db_ephemeral_blob(&q, 6, &delta);
      blob_read_from_file(&disk, zOPath, RepoFILE);
      content_get(rid, &a);
      blob_delta_apply(&a, &delta, &b);
Changes to src/undo.c.
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73


74
75
76
77
78
79
80
81
    int new_exists;
    int old_exe;
    int new_exe;
    int new_link;
    int old_link;
    Blob current;
    Blob new;
    zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname);
    old_link = db_column_int(&q, 3);
    new_exists = file_size(zFullname, RepoFILE)>=0;
    new_link = file_islink(0);
    if( new_exists ){
      blob_read_from_file(&current, zFullname, RepoFILE);
      new_exe = file_isexe(0,0);
    }else{
      blob_zero(&current);
      new_exe = 0;
    }
    blob_zero(&new);
    old_exists = db_column_int(&q, 1);
    old_exe = db_column_int(&q, 2);
    if( old_exists ){
      db_ephemeral_blob(&q, 0, &new);
    }


    if( old_exists ){
      if( new_exists ){
        fossil_print("%s   %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
      }else{
        fossil_print("NEW    %s\n", zPathname);
      }
      if( new_exists && (new_link || old_link) ){
        file_delete(zFullname);







|
















>
>
|







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    int new_exists;
    int old_exe;
    int new_exe;
    int new_link;
    int old_link;
    Blob current;
    Blob new;
    zFullname = mprintf("%s%s", g.zLocalRoot, zPathname);
    old_link = db_column_int(&q, 3);
    new_exists = file_size(zFullname, RepoFILE)>=0;
    new_link = file_islink(0);
    if( new_exists ){
      blob_read_from_file(&current, zFullname, RepoFILE);
      new_exe = file_isexe(0,0);
    }else{
      blob_zero(&current);
      new_exe = 0;
    }
    blob_zero(&new);
    old_exists = db_column_int(&q, 1);
    old_exe = db_column_int(&q, 2);
    if( old_exists ){
      db_ephemeral_blob(&q, 0, &new);
    }
    if( file_unsafe_in_tree_path(zFullname) ){
      /* do nothign with this unsafe file */
    }else if( old_exists ){
      if( new_exists ){
        fossil_print("%s   %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
      }else{
        fossil_print("NEW    %s\n", zPathname);
      }
      if( new_exists && (new_link || old_link) ){
        file_delete(zFullname);
Changes to src/update.c.
925
926
927
928
929
930
931


932
933
934
935

936
937
938
939
940
941
942
      db_multi_exec(
        "UPDATE OR REPLACE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname;"
        "DELETE FROM vfile WHERE pathname=%Q",
        zFile, zFile
      );


    }else{
      sqlite3_int64 mtime;
      int rvChnged = 0;
      int rvPerm = manifest_file_mperm(pRvFile);


      /* Determine if reverted-to file is different than checked out file. */
      if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){
        rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
                || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
      }








>
>




>







925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
      db_multi_exec(
        "UPDATE OR REPLACE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname;"
        "DELETE FROM vfile WHERE pathname=%Q",
        zFile, zFile
      );
    }else if( file_unsafe_in_tree_path(zFull) ){
      /* Ignore this file */
    }else{
      sqlite3_int64 mtime;
      int rvChnged = 0;
      int rvPerm = manifest_file_mperm(pRvFile);
      int n;

      /* Determine if reverted-to file is different than checked out file. */
      if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){
        rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
                || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
      }

Changes to src/vfile.c.
312
313
314
315
316
317
318



319
320
321
322
323
324
325
    int n;

    id = db_column_int(&q, 0);
    zName = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);
    isExe = db_column_int(&q, 3);
    isLink = db_column_int(&q, 4);



    content_get(rid, &content);
    if( file_is_the_same(&content, zName) ){
      blob_reset(&content);
      if( file_setexe(zName, isExe) ){
        db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
                      file_mtime(zName, RepoFILE), id);
      }







>
>
>







312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
    int n;

    id = db_column_int(&q, 0);
    zName = db_column_text(&q, 1);
    rid = db_column_int(&q, 2);
    isExe = db_column_int(&q, 3);
    isLink = db_column_int(&q, 4);
    if( file_unsafe_in_tree_path(zName) ){
      continue;
    }
    content_get(rid, &content);
    if( file_is_the_same(&content, zName) ){
      blob_reset(&content);
      if( file_setexe(zName, isExe) ){
        db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
                      file_mtime(zName, RepoFILE), id);
      }
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
        promptFlag = 0;
      } else if( cReply!='y' && cReply!='Y' ){
        blob_reset(&content);
        continue;
      }
    }
    if( verbose ) fossil_print("%s\n", &zName[nRepos]);
    n = file_nondir_objects_on_path(g.zLocalRoot, zName);
    if( n ){
      fossil_fatal("cannot write %s because "
                   "non-directory object %.*s is in the way",
                   zName, n, zName);
    }
    if( file_isdir(zName, RepoFILE)==1 ){
      /*TODO(dchest): remove directories? */
      fossil_fatal("%s is directory, cannot overwrite", zName);
    }
    if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){
      file_delete(zName);
    }







<
<
<
<
<
<







341
342
343
344
345
346
347






348
349
350
351
352
353
354
        promptFlag = 0;
      } else if( cReply!='y' && cReply!='Y' ){
        blob_reset(&content);
        continue;
      }
    }
    if( verbose ) fossil_print("%s\n", &zName[nRepos]);






    if( file_isdir(zName, RepoFILE)==1 ){
      /*TODO(dchest): remove directories? */
      fossil_fatal("%s is directory, cannot overwrite", zName);
    }
    if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){
      file_delete(zName);
    }