diff --git a/lib/libarchive/archive_write_disk.c b/lib/libarchive/archive_write_disk.c index 521c16263aff..4b921e5f8c11 100644 --- a/lib/libarchive/archive_write_disk.c +++ b/lib/libarchive/archive_write_disk.c @@ -859,14 +859,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 9f9b5da6af91..08900d9950de 100644 --- a/lib/libarchive/test/test_write_disk_secure.c +++ b/lib/libarchive/test/test_write_disk_secure.c @@ -105,11 +105,83 @@ 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); -#if ARCHIVE_API_VERSION > 1 - assert(0 == archive_write_finish(a)); -#else + /* + * 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 replaced. */ + 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 replaced. */ + assertEqualInt(0, lstat("link_to_dir5", &st)); + assert(S_ISDIR(st.st_mode)); + archive_entry_free(ae); + +#if ARCHIVE_VERSION_NUMBER < 2000000 archive_write_finish(a); +#else + assert(0 == archive_write_finish(a)); #endif /* Test the entries on disk. */