Module Name:    src
Committed By:   christos
Date:           Sun Jan 12 16:10:49 UTC 2020

Modified Files:
        src/external/bsd/libarchive/dist/libarchive: archive_write_disk_posix.c
        src/external/bsd/libarchive/dist/libarchive/test:
            test_write_disk_secure.c test_write_disk_secure744.c
            test_write_disk_secure746.c
        src/external/bsd/libarchive/dist/tar/test: test_option_U_upper.c
            test_symlink_dir.c

Log Message:
Leave pre-existing symlinks alone on extraction

When libarchive encounters an existing symbolic link during extraction
it removes that symbolic link first before overwriting it, unless
it is told that it can trust symlinks from the archive.

Placing symbolic links on known paths in the extracting subdirectory
is a simple way that a system administrator can place data at a
different location without having the overhead of a mountpoint.

Trusting symlinks from an archive is never safe because they can
maliciously overwrite files outside of the extraction directory.

This patch adds a linked-list to track of the symbolic links that
were created during extraction so that it does not trust them. This
way during extraction, libarchive can remove the symlinks it created,
but leave the pre-existing ones alone.

Unit-tests were adjusted for this new behavior.

(this is pull request 1300)


To generate a diff of this commit:
cvs rdiff -u -r1.2 -r1.3 \
    src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c
cvs rdiff -u -r1.1.1.3 -r1.2 \
    src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c
cvs rdiff -u -r1.1.1.1 -r1.2 \
    
src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c \
    src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c
cvs rdiff -u -r1.1.1.2 -r1.2 \
    src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c
cvs rdiff -u -r1.1.1.3 -r1.2 \
    src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c
diff -u src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c:1.2 src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c:1.3
--- src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c:1.2	Sun Jan 12 11:08:31 2020
+++ src/external/bsd/libarchive/dist/libarchive/archive_write_disk_posix.c	Sun Jan 12 11:10:48 2020
@@ -188,6 +188,12 @@ struct fixup_entry {
 	char			*name;
 };
 
+struct symlink_entry {
+	dev_t sc_dev;
+	ino_t sc_ino;
+	struct symlink_entry *sc_next;
+};
+
 /*
  * We use a bitmask to track which operations remain to be done for
  * this file.  In particular, this helps us avoid unnecessary
@@ -223,6 +229,7 @@ struct archive_write_disk {
 	mode_t			 user_umask;
 	struct fixup_entry	*fixup_list;
 	struct fixup_entry	*current_fixup;
+	struct symlink_entry	*symlink_list;
 	int64_t			 user_uid;
 	int			 skip_file_set;
 	int64_t			 skip_file_dev;
@@ -358,8 +365,9 @@ struct archive_write_disk {
 static int	la_opendirat(int, const char *);
 static void	fsobj_error(int *, struct archive_string *, int, const char *,
 		    const char *);
-static int	check_symlinks_fsobj(char *, int *, struct archive_string *,
-		    int);
+static int	check_symlinks_fsobj(struct archive_write_disk *, char *, int *,
+		    struct archive_string *, int);
+
 static int	check_symlinks(struct archive_write_disk *);
 static int	create_filesystem_object(struct archive_write_disk *);
 static struct fixup_entry *current_fixup(struct archive_write_disk *,
@@ -409,6 +417,39 @@ static ssize_t	_archive_write_disk_data_
 		    size_t, int64_t);
 
 static int
+symlink_add(struct archive_write_disk *a, const struct stat *st)
+{
+	struct symlink_entry *sc = malloc(sizeof(*sc));
+	if (sc == NULL)
+		return errno;
+	sc->sc_next = a->symlink_list;
+	a->symlink_list = sc->sc_next;
+	sc->sc_dev = st->st_dev;
+	sc->sc_ino = st->st_ino;
+	return 0;
+}
+
+static int
+symlink_find(struct archive_write_disk *a, const struct stat *st)
+{
+	for (struct symlink_entry *sc = a->symlink_list; sc; sc = sc->sc_next)
+		if (st->st_ino == sc->sc_ino && st->st_dev == sc->sc_dev)
+			return 1;
+	return 0;
+}
+
+static void
+symlink_free(struct archive_write_disk *a)
+{
+	for (struct symlink_entry *sc = a->symlink_list; sc; ) {
+		struct symlink_entry *next = sc->sc_next;
+		free(sc);
+		sc = next;
+	}
+	a->symlink_list = NULL;
+}
+
+static int
 la_mktemp(struct archive_write_disk *a)
 {
 	int oerrno, fd;
@@ -2245,7 +2286,7 @@ create_filesystem_object(struct archive_
 			 */
 			return (EPERM);
 		}
-		r = check_symlinks_fsobj(linkname_copy, &error_number,
+		r = check_symlinks_fsobj(a, linkname_copy, &error_number,
 		    &error_string, a->flags);
 		if (r != ARCHIVE_OK) {
 			archive_set_error(&a->archive, error_number, "%s",
@@ -2298,7 +2339,18 @@ create_filesystem_object(struct archive_
 	linkname = archive_entry_symlink(a->entry);
 	if (linkname != NULL) {
 #if HAVE_SYMLINK
-		return symlink(linkname, a->name) ? errno : 0;
+		int error = symlink(linkname, a->name) ? errno : 0;
+		if (error == 0) {
+#ifdef HAVE_LSTAT
+			r = lstat(a->name, &st);
+#else
+			r = la_stat(a->name, &st);
+#endif
+			if (r == -1)
+				return errno;
+			error = symlink_add(a, &st);
+		}
+		return error;
 #else
 		return (EPERM);
 #endif
@@ -2477,6 +2529,7 @@ _archive_write_disk_close(struct archive
 		p = next;
 	}
 	a->fixup_list = NULL;
+	symlink_free(a);
 	return (ret);
 }
 
@@ -2643,8 +2696,8 @@ fsobj_error(int *a_eno, struct archive_s
  * ARCHIVE_OK if there are none, otherwise puts an error in errmsg.
  */
 static int
-check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
-    int flags)
+check_symlinks_fsobj(struct archive_write_disk *a, char *path, int *a_eno,
+    struct archive_string *a_estr, int flags)
 {
 #if !defined(HAVE_LSTAT) && \
     !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT))
@@ -2774,7 +2827,18 @@ check_symlinks_fsobj(char *path, int *a_
 				head = tail + 1;
 			}
 		} else if (S_ISLNK(st.st_mode)) {
+			/*
+			 * We maintain a cache containing symlinks we
+			 * created, so that we don't trust them.
+			 */
+			int preexisting = !symlink_find(a, &st);
 			if (last) {
+				if (preexisting &&
+				    (flags & ARCHIVE_EXTRACT_UNLINK) == 0) {
+					/* Leave it alone */
+					res = ARCHIVE_OK;
+					break;
+				}
 				/*
 				 * Last element is symlink; remove it
 				 * so we can overwrite it with the
@@ -2829,7 +2893,7 @@ check_symlinks_fsobj(char *path, int *a_
 					break;
 				}
 				tail[0] = c;
-			} else if ((flags &
+			} else if (preexisting || (flags &
 			    ARCHIVE_EXTRACT_SECURE_SYMLINKS) == 0) {
 				/*
 				 * We are not the last element and we want to
@@ -2939,7 +3003,7 @@ check_symlinks(struct archive_write_disk
 	int error_number;
 	int rc;
 	archive_string_init(&error_string);
-	rc = check_symlinks_fsobj(a->name, &error_number, &error_string,
+	rc = check_symlinks_fsobj(a, a->name, &error_number, &error_string,
 	    a->flags);
 	if (rc != ARCHIVE_OK) {
 		archive_set_error(&a->archive, error_number, "%s",

Index: src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c
diff -u src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c:1.1.1.3 src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c:1.2
--- src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c:1.1.1.3	Thu Apr 20 08:55:49 2017
+++ src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure.c	Sun Jan 12 11:10:49 2020
@@ -74,6 +74,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assert(0 == archive_write_header(a, ae));
 	assert(0 == archive_write_finish_entry(a));
 
+#if 0
 	/* But with security checks enabled, this should fail. */
 	assert(archive_entry_clear(ae) != NULL);
 	archive_entry_copy_pathname(ae, "link_to_dir/fileb");
@@ -83,6 +84,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
 	archive_entry_free(ae);
 	assert(0 == archive_write_finish_entry(a));
+#endif
 
 	/* Write an absolute symlink to /tmp. */
 	assert((ae = archive_entry_new()) != NULL);
@@ -93,6 +95,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assert(0 == archive_write_header(a, ae));
 	assert(0 == archive_write_finish_entry(a));
 
+#if 0
 	/* With security checks enabled, this should fail. */
 	assert(archive_entry_clear(ae) != NULL);
 	archive_entry_copy_pathname(ae, "/tmp/libarchive_test-test_write_disk_secure-absolute_symlink/libarchive_test-test_write_disk_secure-absolute_symlink_path.tmp");
@@ -104,6 +107,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assertFileNotExists("/tmp/libarchive_test-test_write_disk_secure-absolute_symlink/libarchive_test-test_write_disk_secure-absolute_symlink_path.tmp");
 	assert(0 == unlink("/tmp/libarchive_test-test_write_disk_secure-absolute_symlink"));
 	unlink("/tmp/libarchive_test-test_write_disk_secure-absolute_symlink_path.tmp");
+#endif
 
 	/* Create another link. */
 	assert((ae = archive_entry_new()) != NULL);
@@ -135,6 +139,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assert(0 == archive_write_header(a, ae));
 	assert(0 == archive_write_finish_entry(a));
 
+#if 0
 	/* But with security checks enabled, this should fail. */
 	assert(archive_entry_clear(ae) != NULL);
 	archive_entry_copy_pathname(ae, "dir/nested_link_to_dir/filed");
@@ -144,6 +149,7 @@ DEFINE_TEST(test_write_disk_secure)
 	assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae));
 	archive_entry_free(ae);
 	assert(0 == archive_write_finish_entry(a));
+#endif
 
 	/*
 	 * Without security checks, extracting a dir over a link to a

Index: src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c
diff -u src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c:1.1.1.1 src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c:1.2
--- src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c:1.1.1.1	Thu Apr 20 08:55:52 2017
+++ src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure744.c	Sun Jan 12 11:10:49 2020
@@ -36,7 +36,7 @@ DEFINE_TEST(test_write_disk_secure744)
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
 	skipping("archive_write_disk security checks not supported on Windows");
-#else
+#elif 0
 	struct archive *a;
 	struct archive_entry *ae;
 	size_t buff_size = 8192;
Index: src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c
diff -u src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c:1.1.1.1 src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c:1.2
--- src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c:1.1.1.1	Thu Apr 20 08:55:52 2017
+++ src/external/bsd/libarchive/dist/libarchive/test/test_write_disk_secure746.c	Sun Jan 12 11:10:49 2020
@@ -86,7 +86,7 @@ DEFINE_TEST(test_write_disk_secure746b)
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
 	skipping("archive_write_disk security checks not supported on Windows");
-#else
+#elif 0
 	struct archive *a;
 	struct archive_entry *ae;
 

Index: src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c
diff -u src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c:1.1.1.2 src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c:1.2
--- src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c:1.1.1.2	Wed Jul 24 09:50:41 2019
+++ src/external/bsd/libarchive/dist/tar/test/test_option_U_upper.c	Sun Jan 12 11:10:49 2020
@@ -75,17 +75,17 @@ DEFINE_TEST(test_option_U_upper)
 	if (!canSymlink())
 		return;
 
-	/* Test 3: Intermediate dir symlink causes error by default */
+	/* Test 3: Intermediate dir symlink preserves it */
 	assertMakeDir("test3", 0755);
 	assertChdir("test3");
 	assertMakeDir("realDir", 0755);
 	assertMakeSymlink("d1", "realDir", 1);
 	r = systemf("%s -xf ../archive.tar d1/file1 >test.out 2>test.err", testprog);
-	assert(r != 0);
+	assert(r == 0);
 	assertIsSymlink("d1", "realDir", 1);
-	assertFileNotExists("d1/file1");
+	assertFileContents("d1/file1", 8, "d1/file1");
 	assertEmptyFile("test.out");
-	assertNonEmptyFile("test.err");
+	assertEmptyFile("test.err");
 	assertChdir("..");
 
 	/* Test 4: Intermediate dir symlink gets removed with -U */

Index: src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c
diff -u src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c:1.1.1.3 src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c:1.2
--- src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c:1.1.1.3	Wed Jul 24 09:50:41 2019
+++ src/external/bsd/libarchive/dist/tar/test/test_symlink_dir.c	Sun Jan 12 11:10:49 2020
@@ -83,7 +83,7 @@ DEFINE_TEST(test_symlink_dir)
 		/* "file2" is a symlink to non-existing "real_file2" */
 		assertMakeSymlink("dest1/file2", "real_file2", 0);
 	}
-	assertEqualInt(0, systemf("%s -xf test.tar -C dest1", testprog));
+	assertEqualInt(0, systemf("%s -xUf test.tar -C dest1", testprog));
 
 	/* dest1/dir symlink should be replaced */
 	failure("symlink to dir was followed when it shouldn't be");

Reply via email to