When restoring a directory, allow symlinks to be followed. The full

logic here gets a little complex, but the net effect is that the
SECURE_SYMLINKS flag will prevent us from ever following a symlink.
Without it, we'll only follow symlinks to dirs.  bsdtar specifies
SECURE_SYMLINKS by default, suppresses it for -P.

I've also beefed up the write_disk_secure test to verify this
behavior.

PR:		bin/126849
This commit is contained in:
Tim Kientzle 2008-09-07 05:22:33 +00:00
parent 3726c6bbc1
commit 11663004bd
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=182838
2 changed files with 88 additions and 3 deletions

View file

@ -907,14 +907,26 @@ restore_entry(struct archive_write_disk *a)
* We know something is in the way, but we don't know what;
* we need to find out before we go any further.
*/
if (lstat(a->name, &a->st) != 0) {
int r = 0;
/*
* The SECURE_SYMLINK logic has already removed a
* symlink to a dir if the client wants that. So
* follow the symlink if we're creating a dir.
*/
if (S_ISDIR(a->mode))
r = stat(a->name, &a->st);
/*
* If it's not a dir (or it's a broken symlink),
* then don't follow it.
*/
if (r != 0 || !S_ISDIR(a->mode))
r = lstat(a->name, &a->st);
if (r != 0) {
archive_set_error(&a->archive, errno,
"Can't stat existing object");
return (ARCHIVE_WARN);
}
/* TODO: if it's a symlink... */
/*
* NO_OVERWRITE_NEWER doesn't apply to directories.
*/

View file

@ -105,6 +105,79 @@ DEFINE_TEST(test_write_disk_secure)
archive_entry_free(ae);
assert(0 == archive_write_finish_entry(a));
/*
* Without security checks, extracting a dir over a link to a
* dir should follow the link.
*/
/* Create a symlink to a dir. */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "link_to_dir3");
archive_entry_set_mode(ae, S_IFLNK | 0777);
archive_entry_set_symlink(ae, "dir");
archive_write_disk_set_options(a, 0);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Extract a dir whose name matches the symlink. */
assert(archive_entry_clear(ae) != NULL);
archive_entry_copy_pathname(ae, "link_to_dir3");
archive_entry_set_mode(ae, S_IFDIR | 0777);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Verify link was followed. */
assertEqualInt(0, lstat("link_to_dir3", &st));
assert(S_ISLNK(st.st_mode));
archive_entry_free(ae);
/*
* As above, but a broken link, so the link should get replaced.
*/
/* Create a symlink to a dir. */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "link_to_dir4");
archive_entry_set_mode(ae, S_IFLNK | 0777);
archive_entry_set_symlink(ae, "nonexistent_dir");
archive_write_disk_set_options(a, 0);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Extract a dir whose name matches the symlink. */
assert(archive_entry_clear(ae) != NULL);
archive_entry_copy_pathname(ae, "link_to_dir4");
archive_entry_set_mode(ae, S_IFDIR | 0777);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Verify link was followed. */
assertEqualInt(0, lstat("link_to_dir4", &st));
assert(S_ISDIR(st.st_mode));
archive_entry_free(ae);
/*
* As above, but a link to a non-dir, so the link should get replaced.
*/
/* Create a regular file and a symlink to it */
assert((ae = archive_entry_new()) != NULL);
archive_entry_copy_pathname(ae, "non_dir");
archive_entry_set_mode(ae, S_IFREG | 0777);
archive_write_disk_set_options(a, 0);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Create symlink to the file. */
archive_entry_copy_pathname(ae, "link_to_dir5");
archive_entry_set_mode(ae, S_IFLNK | 0777);
archive_entry_set_symlink(ae, "non_dir");
archive_write_disk_set_options(a, 0);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Extract a dir whose name matches the symlink. */
assert(archive_entry_clear(ae) != NULL);
archive_entry_copy_pathname(ae, "link_to_dir5");
archive_entry_set_mode(ae, S_IFDIR | 0777);
assert(0 == archive_write_header(a, ae));
assert(0 == archive_write_finish_entry(a));
/* Verify link was followed. */
assertEqualInt(0, lstat("link_to_dir5", &st));
assert(S_ISDIR(st.st_mode));
#if ARCHIVE_VERSION_NUMBER < 2000000
archive_write_finish(a);
#else