From 02ce672bbc07905ebf44d957200a6c633201dee0 Mon Sep 17 00:00:00 2001 From: kientzle Date: Sun, 7 Sep 2008 05:22:33 +0000 Subject: 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 --- lib/libarchive/archive_write_disk.c | 18 +++++-- lib/libarchive/test/test_write_disk_secure.c | 73 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c index b7313bc..b63a7b7 100644 --- a/lib/libarchive/archive_write_disk.c +++ b/lib/libarchive/archive_write_disk.c @@ -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. */ diff --git a/lib/libarchive/test/test_write_disk_secure.c b/lib/libarchive/test/test_write_disk_secure.c index 94e09eb..e095555 100644 --- a/lib/libarchive/test/test_write_disk_secure.c +++ b/lib/libarchive/test/test_write_disk_secure.c @@ -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 -- cgit v1.1