Blob Blame History Raw
From b837c72c423b744a2e6c554742877173406dbfa0 Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Sat, 25 May 2019 23:46:59 +0200
Subject: [PATCH] archive_write_disk_posix: open a fd when processing fixup
 entries

---
 libarchive/archive_write_disk_posix.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index 70b27b50..0583fbd1 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -182,6 +182,7 @@ struct fixup_entry {
 	void			*mac_metadata;
 	int			 fixup; /* bitmask of what needs fixing */
 	char			*name;
+	int			 fd;
 };
 
 /*
@@ -2354,20 +2355,31 @@ _archive_write_disk_close(struct archive *_a)
 
 	while (p != NULL) {
 		a->pst = NULL; /* Mark stat cache as out-of-date. */
+		if (p->fd < 0 && p->fixup &
+		    (TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) {
+			p->fd = open(p->name,
+			    O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
+		}
 		if (p->fixup & TODO_TIMES) {
-			set_times(a, -1, p->mode, p->name,
+			set_times(a, p->fd, p->mode, p->name,
 			    p->atime, p->atime_nanos,
 			    p->birthtime, p->birthtime_nanos,
 			    p->mtime, p->mtime_nanos,
 			    p->ctime, p->ctime_nanos);
 		}
-		if (p->fixup & TODO_MODE_BASE)
+		if (p->fixup & TODO_MODE_BASE) {
+#ifdef HAVE_FCHMOD
+			if (p->fd >= 0)
+				fchmod(p->fd, p->mode);
+			else
+#endif
 			chmod(p->name, p->mode);
+		}
 		if (p->fixup & TODO_ACLS)
-			archive_write_disk_set_acls(&a->archive, -1, p->name,
-			    &p->acl, p->mode);
+			archive_write_disk_set_acls(&a->archive, p->fd,
+			    p->name, &p->acl, p->mode);
 		if (p->fixup & TODO_FFLAGS)
-			set_fflags_platform(a, -1, p->name,
+			set_fflags_platform(a, p->fd, p->name,
 			    p->mode, p->fflags_set, 0);
 		if (p->fixup & TODO_MAC_METADATA)
 			set_mac_metadata(a, p->name, p->mac_metadata,
@@ -2376,6 +2388,8 @@ _archive_write_disk_close(struct archive *_a)
 		archive_acl_clear(&p->acl);
 		free(p->mac_metadata);
 		free(p->name);
+		if (p->fd >= 0)
+			close(p->fd);	
 		free(p);
 		p = next;
 	}
@@ -2510,6 +2524,7 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
 	a->fixup_list = fe;
 	fe->fixup = 0;
 	fe->name = strdup(pathname);
+	fe->fd = -1;
 	return (fe);
 }
 
-- 
2.31.1

From 6d5204058ed51e11588a438737e9033305cfd248 Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Thu, 6 Jun 2019 15:12:11 +0200
Subject: [PATCH] archive_write_disk_posix changes - private file descriptor in
 _archive_write_disk_close() - use la_opendirat() in edit_deep_directories()

---
 libarchive/archive_write_disk_posix.c | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index 89941c64..b1a0bb38 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -186,7 +186,6 @@ struct fixup_entry {
 	void			*mac_metadata;
 	int			 fixup; /* bitmask of what needs fixing */
 	char			*name;
-	int			 fd;
 };
 
 /*
@@ -1947,7 +1946,7 @@ edit_deep_directories(struct archive_write_disk *a)
 		return;
 
 	/* Try to record our starting dir. */
-	a->restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC);
+	a->restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC | O_DIRECTORY);
 	__archive_ensure_cloexec_flag(a->restore_pwd);
 	if (a->restore_pwd < 0)
 		return;
@@ -2380,7 +2379,7 @@ _archive_write_disk_close(struct archive *_a)
 {
 	struct archive_write_disk *a = (struct archive_write_disk *)_a;
 	struct fixup_entry *next, *p;
-	int ret;
+	int fd, ret;
 
 	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
 	    ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
@@ -2391,14 +2390,15 @@ _archive_write_disk_close(struct archive *_a)
 	p = sort_dir_list(a->fixup_list);
 
 	while (p != NULL) {
+		fd = -1;
 		a->pst = NULL; /* Mark stat cache as out-of-date. */
-		if (p->fd < 0 && p->fixup &
+		if (p->fixup &
 		    (TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) {
-			p->fd = open(p->name,
+			fd = open(p->name,
 			    O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
 		}
 		if (p->fixup & TODO_TIMES) {
-			set_times(a, p->fd, p->mode, p->name,
+			set_times(a, fd, p->mode, p->name,
 			    p->atime, p->atime_nanos,
 			    p->birthtime, p->birthtime_nanos,
 			    p->mtime, p->mtime_nanos,
@@ -2406,17 +2406,17 @@ _archive_write_disk_close(struct archive *_a)
 		}
 		if (p->fixup & TODO_MODE_BASE) {
 #ifdef HAVE_FCHMOD
-			if (p->fd >= 0)
-				fchmod(p->fd, p->mode);
+			if (fd >= 0)
+				fchmod(fd, p->mode);
 			else
 #endif
 			chmod(p->name, p->mode);
 		}
 		if (p->fixup & TODO_ACLS)
-			archive_write_disk_set_acls(&a->archive, p->fd,
+			archive_write_disk_set_acls(&a->archive, fd,
 			    p->name, &p->acl, p->mode);
 		if (p->fixup & TODO_FFLAGS)
-			set_fflags_platform(a, p->fd, p->name,
+			set_fflags_platform(a, fd, p->name,
 			    p->mode, p->fflags_set, 0);
 		if (p->fixup & TODO_MAC_METADATA)
 			set_mac_metadata(a, p->name, p->mac_metadata,
@@ -2425,8 +2425,8 @@ _archive_write_disk_close(struct archive *_a)
 		archive_acl_clear(&p->acl);
 		free(p->mac_metadata);
 		free(p->name);
-		if (p->fd >= 0)
-			close(p->fd);	
+		if (fd >= 0)
+			close(fd);
 		free(p);
 		p = next;
 	}
@@ -2561,7 +2561,6 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
 	a->fixup_list = fe;
 	fe->fixup = 0;
 	fe->name = strdup(pathname);
-	fe->fd = -1;
 	return (fe);
 }
 
-- 
2.31.1

From e2ad1a2c3064fa9eba6274b3641c4c1beed25c0b Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Sun, 22 Aug 2021 03:53:28 +0200
Subject: [PATCH] Never follow symlinks when setting file flags on Linux

When opening a file descriptor to set file flags on linux, ensure
no symbolic links are followed. This fixes the case when an archive
contains a directory entry followed by a symlink entry with the same
path. The fixup code would modify file flags of the symlink target.
---
 libarchive/archive_write_disk_posix.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index ba4e65df..8474617e 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -3927,7 +3927,8 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
 
 	/* If we weren't given an fd, open it ourselves. */
 	if (myfd < 0) {
-		myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC);
+		myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY |
+		    O_CLOEXEC | O_NOFOLLOW);
 		__archive_ensure_cloexec_flag(myfd);
 	}
 	if (myfd < 0)
-- 
2.31.1

From b41daecb5ccb4c8e3b2c53fd6147109fc12c3043 Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Fri, 20 Aug 2021 01:50:27 +0200
Subject: [PATCH] Do not follow symlinks when processing the fixup list

Use lchmod() instead of chmod() and tell the remaining functions that the
real file to be modified is a symbolic link.

Fixes #1566
---
 Makefile.am                             |  1 +
 libarchive/archive_write_disk_posix.c   | 24 +++++++-
 libarchive/test/CMakeLists.txt          |  1 +
 libarchive/test/test_write_disk_fixup.c | 77 +++++++++++++++++++++++++
 4 files changed, 102 insertions(+), 1 deletion(-)
 create mode 100644 libarchive/test/test_write_disk_fixup.c

diff --git a/Makefile.am b/Makefile.am
index 58edb74e..c93a82e9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -560,6 +560,7 @@ libarchive_test_SOURCES= \
 	libarchive/test/test_write_disk.c \
 	libarchive/test/test_write_disk_appledouble.c \
 	libarchive/test/test_write_disk_failures.c \
+	libarchive/test/test_write_disk_fixup.c \
 	libarchive/test/test_write_disk_hardlink.c \
 	libarchive/test/test_write_disk_hfs_compression.c \
 	libarchive/test/test_write_disk_lookup.c \
diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index 8474617e..fcd733af 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -2461,6 +2461,7 @@ _archive_write_disk_close(struct archive *_a)
 {
 	struct archive_write_disk *a = (struct archive_write_disk *)_a;
 	struct fixup_entry *next, *p;
+	struct stat st;
 	int fd, ret;
 
 	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -2478,6 +2479,20 @@ _archive_write_disk_close(struct archive *_a)
 		    (TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) {
 			fd = open(p->name,
 			    O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
+			if (fd == -1) {
+				/* If we cannot lstat, skip entry */
+				if (lstat(p->name, &st) != 0)
+					goto skip_fixup_entry;
+				/*
+				 * If we deal with a symbolic link, mark
+				 * it in the fixup mode to ensure no
+				 * modifications are made to its target.
+				 */
+				if (S_ISLNK(st.st_mode)) {
+					p->mode &= ~S_IFMT;
+					p->mode |= S_IFLNK;
+				}
+			}
 		}
 		if (p->fixup & TODO_TIMES) {
 			set_times(a, fd, p->mode, p->name,
@@ -2492,7 +2507,12 @@ _archive_write_disk_close(struct archive *_a)
 				fchmod(fd, p->mode);
 			else
 #endif
-			chmod(p->name, p->mode);
+#ifdef HAVE_LCHMOD
+			lchmod(p->name, p->mode);
+#else
+			if (!S_ISLNK(p->mode))
+				chmod(p->name, p->mode);
+#endif
 		}
 		if (p->fixup & TODO_ACLS)
 			archive_write_disk_set_acls(&a->archive, fd,
@@ -2503,6 +2523,7 @@ _archive_write_disk_close(struct archive *_a)
 		if (p->fixup & TODO_MAC_METADATA)
 			set_mac_metadata(a, p->name, p->mac_metadata,
 					 p->mac_metadata_size);
+skip_fixup_entry:
 		next = p->next;
 		archive_acl_clear(&p->acl);
 		free(p->mac_metadata);
@@ -2643,6 +2664,7 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
 	fe->next = a->fixup_list;
 	a->fixup_list = fe;
 	fe->fixup = 0;
+	fe->mode = 0;
 	fe->name = strdup(pathname);
 	return (fe);
 }
diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt
index b26f679c..53cc3e22 100644
--- a/libarchive/test/CMakeLists.txt
+++ b/libarchive/test/CMakeLists.txt
@@ -209,6 +209,7 @@ IF(ENABLE_TEST)
     test_write_disk.c
     test_write_disk_appledouble.c
     test_write_disk_failures.c
+    test_write_disk_fixup.c
     test_write_disk_hardlink.c
     test_write_disk_hfs_compression.c
     test_write_disk_lookup.c
diff --git a/libarchive/test/test_write_disk_fixup.c b/libarchive/test/test_write_disk_fixup.c
new file mode 100644
index 00000000..153cc3a9
--- /dev/null
+++ b/libarchive/test/test_write_disk_fixup.c
@@ -0,0 +1,77 @@
+/*-
+ * Copyright (c) 2021 Martin Matuska
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+
+/*
+ * Test fixup entries don't follow symlinks
+ */
+DEFINE_TEST(test_write_disk_fixup)
+{
+	struct archive *ad;
+	struct archive_entry *ae;
+	int r;
+
+	if (!canSymlink()) {
+		skipping("Symlinks not supported");
+		return;
+	}
+
+	/* Write entries to disk. */
+	assert((ad = archive_write_disk_new()) != NULL);
+
+	/*
+	 * Create a file
+	 */
+	assertMakeFile("victim", 0600, "a");
+
+	/*
+	 * Create a directory and a symlink with the same name
+	 */
+
+	/* Directory: dir */
+        assert((ae = archive_entry_new()) != NULL);
+        archive_entry_copy_pathname(ae, "dir");
+        archive_entry_set_mode(ae, AE_IFDIR | 0606);
+	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+        archive_entry_free(ae);
+
+	/* Symbolic Link: dir -> foo */
+	assert((ae = archive_entry_new()) != NULL);
+	archive_entry_copy_pathname(ae, "dir");
+	archive_entry_set_mode(ae, AE_IFLNK | 0777);
+	archive_entry_set_size(ae, 0);
+	archive_entry_copy_symlink(ae, "victim");
+	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
+	if (r >= ARCHIVE_WARN)
+		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+	archive_entry_free(ae);
+
+	assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
+
+	/* Test the entries on disk. */
+	assertIsSymlink("dir", "victim");
+	assertFileMode("victim", 0600);
+}
-- 
2.31.1

From 8a1bd5c18e896f0411a991240ce0d772bb02c840 Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Fri, 27 Aug 2021 10:56:28 +0200
Subject: [PATCH] Fix following symlinks when processing the fixup list

The previous fix in b41daecb5 was incomplete. Fixup entries are
given the original path without calling cleanup_pathname().
To make sure we don't follow a symlink, we must strip trailing
slashes from the path.

The fixup entries are always directories. Make sure we try to modify
only directories by providing O_DIRECTORY to open() (if supported)
and if it fails to check directory via lstat().

Fixes #1566
---
 libarchive/archive_write_disk_posix.c   | 62 +++++++++++++++++--------
 libarchive/test/test_write_disk_fixup.c | 44 ++++++++++++++----
 2 files changed, 78 insertions(+), 28 deletions(-)

diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index fcd733af..aadc5871 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -2462,6 +2462,7 @@ _archive_write_disk_close(struct archive *_a)
 	struct archive_write_disk *a = (struct archive_write_disk *)_a;
 	struct fixup_entry *next, *p;
 	struct stat st;
+	char *c;
 	int fd, ret;
 
 	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -2475,24 +2476,49 @@ _archive_write_disk_close(struct archive *_a)
 	while (p != NULL) {
 		fd = -1;
 		a->pst = NULL; /* Mark stat cache as out-of-date. */
-		if (p->fixup &
-		    (TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) {
-			fd = open(p->name,
-			    O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
+
+		/* We must strip trailing slashes from the path to avoid
+		   dereferencing symbolic links to directories */
+		c = p->name;
+		while (*c != '\0')
+			c++;
+		while (c != p->name && *(c - 1) == '/') {
+			c--;
+			*c = '\0';
+		}
+
+		if (p->fixup == 0)
+			goto skip_fixup_entry;
+		else {
+			fd = open(p->name, O_BINARY | O_NOFOLLOW | O_RDONLY
+#if defined(O_DIRECTORY)
+			    | O_DIRECTORY
+#endif
+			    | O_CLOEXEC);
+			/*
+		 `	 * If we don't support O_DIRECTORY,
+			 * or open() has failed, we must stat()
+			 * to verify that we are opening a directory
+			 */
+#if defined(O_DIRECTORY)
 			if (fd == -1) {
-				/* If we cannot lstat, skip entry */
-				if (lstat(p->name, &st) != 0)
+				if (lstat(p->name, &st) != 0 ||
+				    !S_ISDIR(st.st_mode)) {
 					goto skip_fixup_entry;
-				/*
-				 * If we deal with a symbolic link, mark
-				 * it in the fixup mode to ensure no
-				 * modifications are made to its target.
-				 */
-				if (S_ISLNK(st.st_mode)) {
-					p->mode &= ~S_IFMT;
-					p->mode |= S_IFLNK;
 				}
 			}
+#else
+#if HAVE_FSTAT
+			if (fd > 0 && (
+			    fstat(fd, &st) != 0 || !S_ISDIR(st.st_mode))) {
+				goto skip_fixup_entry;
+			} else
+#endif
+			if (lstat(p->name, &st) != 0 ||
+			    !S_ISDIR(st.st_mode)) {
+				goto skip_fixup_entry;
+			}
+#endif
 		}
 		if (p->fixup & TODO_TIMES) {
 			set_times(a, fd, p->mode, p->name,
@@ -2504,14 +2530,13 @@ _archive_write_disk_close(struct archive *_a)
 		if (p->fixup & TODO_MODE_BASE) {
 #ifdef HAVE_FCHMOD
 			if (fd >= 0)
-				fchmod(fd, p->mode);
+				fchmod(fd, p->mode & 07777);
 			else
 #endif
 #ifdef HAVE_LCHMOD
-			lchmod(p->name, p->mode);
+			lchmod(p->name, p->mode & 07777);
 #else
-			if (!S_ISLNK(p->mode))
-				chmod(p->name, p->mode);
+			chmod(p->name, p->mode & 07777);
 #endif
 		}
 		if (p->fixup & TODO_ACLS)
@@ -2664,7 +2689,6 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
 	fe->next = a->fixup_list;
 	a->fixup_list = fe;
 	fe->fixup = 0;
-	fe->mode = 0;
 	fe->name = strdup(pathname);
 	return (fe);
 }
diff --git a/libarchive/test/test_write_disk_fixup.c b/libarchive/test/test_write_disk_fixup.c
index c399c984..b83b7307 100644
--- a/libarchive/test/test_write_disk_fixup.c
+++ b/libarchive/test/test_write_disk_fixup.c
@@ -47,26 +47,50 @@ DEFINE_TEST(test_write_disk_fixup)
 	/*
 	 * Create a file
 	 */
-	assertMakeFile("victim", 0600, "a");
+	assertMakeFile("file", 0600, "a");
+
+	/*
+	 * Create a directory
+	 */
+	assertMakeDir("dir", 0700);
 
 	/*
 	 * Create a directory and a symlink with the same name
 	 */
 
-	/* Directory: dir */
+	/* Directory: dir1 */
+        assert((ae = archive_entry_new()) != NULL);
+        archive_entry_copy_pathname(ae, "dir1/");
+        archive_entry_set_mode(ae, AE_IFDIR | 0555);
+	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
+	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+        archive_entry_free(ae);
+
+	/* Directory: dir2 */
         assert((ae = archive_entry_new()) != NULL);
-        archive_entry_copy_pathname(ae, "dir");
-        archive_entry_set_mode(ae, AE_IFDIR | 0606);
+        archive_entry_copy_pathname(ae, "dir2/");
+        archive_entry_set_mode(ae, AE_IFDIR | 0555);
 	assertEqualIntA(ad, 0, archive_write_header(ad, ae));
 	assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
         archive_entry_free(ae);
 
-	/* Symbolic Link: dir -> foo */
+	/* Symbolic Link: dir1 -> dir */
+	assert((ae = archive_entry_new()) != NULL);
+	archive_entry_copy_pathname(ae, "dir1");
+	archive_entry_set_mode(ae, AE_IFLNK | 0777);
+	archive_entry_set_size(ae, 0);
+	archive_entry_copy_symlink(ae, "dir");
+	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
+	if (r >= ARCHIVE_WARN)
+		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+	archive_entry_free(ae);
+
+	/* Symbolic Link: dir2 -> file */
 	assert((ae = archive_entry_new()) != NULL);
-	archive_entry_copy_pathname(ae, "dir");
+	archive_entry_copy_pathname(ae, "dir2");
 	archive_entry_set_mode(ae, AE_IFLNK | 0777);
 	archive_entry_set_size(ae, 0);
-	archive_entry_copy_symlink(ae, "victim");
+	archive_entry_copy_symlink(ae, "file");
 	assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
 	if (r >= ARCHIVE_WARN)
 		assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
@@ -75,6 +99,8 @@ DEFINE_TEST(test_write_disk_fixup)
 	assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
 
 	/* Test the entries on disk. */
-	assertIsSymlink("dir", "victim");
-	assertFileMode("victim", 0600);
+	assertIsSymlink("dir1", "dir");
+	assertIsSymlink("dir2", "file");
+	assertFileMode("dir", 0700);
+	assertFileMode("file", 0600);
 }
-- 
2.31.1

From ede459d2ebb879f5eedb6f7abea203be0b334230 Mon Sep 17 00:00:00 2001
From: Martin Matuska <martin@matuska.org>
Date: Wed, 17 Nov 2021 21:06:00 +0100
Subject: [PATCH] archive_write_disk_posix: fix writing fflags broken in
 8a1bd5c

The fixup list was erroneously assumed to be directories only.
Only in the case of critical file flags modification (e.g. SF_IMMUTABLE
on BSD systems), other file types (e.g. regular files or symbolic links)
may be added to the fixup list. We still need to verify that we are writing
to the correct file type, so compare the archive entry file type with
the file type of the file to be modified.

Fixes #1617
---
 libarchive/archive_write_disk_posix.c | 87 +++++++++++++++++++++++----
 1 file changed, 75 insertions(+), 12 deletions(-)

diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index aadc5871..7e57aac2 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -173,6 +173,7 @@ struct fixup_entry {
 	struct fixup_entry	*next;
 	struct archive_acl	 acl;
 	mode_t			 mode;
+	__LA_MODE_T		 filetype;
 	int64_t			 atime;
 	int64_t                  birthtime;
 	int64_t			 mtime;
@@ -357,6 +358,7 @@ struct archive_write_disk {
 
 #define HFS_BLOCKS(s)	((s) >> 12)
 
+static int	la_verify_filetype(mode_t, __LA_MODE_T);
 static void	fsobj_error(int *, struct archive_string *, int, const char *,
 		    const char *);
 static int	check_symlinks_fsobj(char *, int *, struct archive_string *,
@@ -464,6 +466,39 @@ la_opendirat(int fd, const char *path) {
 static ssize_t	_archive_write_disk_data_block(struct archive *, const void *,
 		    size_t, int64_t);
 
+static int
+la_verify_filetype(mode_t mode, __LA_MODE_T filetype) {
+	int ret = 0;
+
+	switch (filetype) {
+	case AE_IFREG:
+		ret = (S_ISREG(mode));
+		break;
+	case AE_IFDIR:
+		ret = (S_ISDIR(mode));
+		break;
+	case AE_IFLNK:
+		ret = (S_ISLNK(mode));
+		break;
+	case AE_IFSOCK:
+		ret = (S_ISSOCK(mode));
+		break;
+	case AE_IFCHR:
+		ret = (S_ISCHR(mode));
+		break;
+	case AE_IFBLK:
+		ret = (S_ISBLK(mode));
+		break;
+	case AE_IFIFO:
+		ret = (S_ISFIFO(mode));
+		break;
+	default:
+		break;
+	}
+
+	return (ret);
+}
+
 static int
 lazy_stat(struct archive_write_disk *a)
 {
@@ -822,6 +857,7 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
 		fe = current_fixup(a, archive_entry_pathname(entry));
 		if (fe == NULL)
 			return (ARCHIVE_FATAL);
+		fe->filetype = archive_entry_filetype(entry);
 		fe->fixup |= TODO_MODE_BASE;
 		fe->mode = a->mode;
 	}
@@ -832,6 +868,7 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
 		fe = current_fixup(a, archive_entry_pathname(entry));
 		if (fe == NULL)
 			return (ARCHIVE_FATAL);
+		fe->filetype = archive_entry_filetype(entry);
 		fe->mode = a->mode;
 		fe->fixup |= TODO_TIMES;
 		if (archive_entry_atime_is_set(entry)) {
@@ -865,6 +902,7 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
 		fe = current_fixup(a, archive_entry_pathname(entry));
 		if (fe == NULL)
 			return (ARCHIVE_FATAL);
+		fe->filetype = archive_entry_filetype(entry);
 		fe->fixup |= TODO_ACLS;
 		archive_acl_copy(&fe->acl, archive_entry_acl(entry));
 	}
@@ -877,6 +915,7 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
 			fe = current_fixup(a, archive_entry_pathname(entry));
 			if (fe == NULL)
 				return (ARCHIVE_FATAL);
+			fe->filetype = archive_entry_filetype(entry);
 			fe->mac_metadata = malloc(metadata_size);
 			if (fe->mac_metadata != NULL) {
 				memcpy(fe->mac_metadata, metadata,
@@ -891,6 +930,7 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
 		fe = current_fixup(a, archive_entry_pathname(entry));
 		if (fe == NULL)
 			return (ARCHIVE_FATAL);
+		fe->filetype = archive_entry_filetype(entry);
 		fe->fixup |= TODO_FFLAGS;
 		/* TODO: Complete this.. defer fflags from below. */
 	}
@@ -2463,7 +2503,7 @@ _archive_write_disk_close(struct archive *_a)
 	struct fixup_entry *next, *p;
 	struct stat st;
 	char *c;
-	int fd, ret;
+	int fd, ret, openflags;
 
 	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
 	    ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
@@ -2490,32 +2530,53 @@ _archive_write_disk_close(struct archive *_a)
 		if (p->fixup == 0)
 			goto skip_fixup_entry;
 		else {
-			fd = open(p->name, O_BINARY | O_NOFOLLOW | O_RDONLY
+			/*
+			 * We need to verify if the type of the file
+			 * we are going to open matches the file type
+			 * of the fixup entry.
+			 */
+			openflags = O_BINARY | O_NOFOLLOW | O_RDONLY
+			    | O_CLOEXEC;
 #if defined(O_DIRECTORY)
-			    | O_DIRECTORY
+			if (p->filetype == AE_IFDIR)
+				openflags |= O_DIRECTORY;
 #endif
-			    | O_CLOEXEC);
+			fd = open(p->name, openflags);
+
+#if defined(O_DIRECTORY)
 			/*
-		 `	 * If we don't support O_DIRECTORY,
-			 * or open() has failed, we must stat()
-			 * to verify that we are opening a directory
+			 * If we support O_DIRECTORY and open was
+			 * successful we can skip the file type check
+			 * for directories. For other file types
+			 * we need to verify via fstat() or lstat()
 			 */
-#if defined(O_DIRECTORY)
-			if (fd == -1) {
+			if (fd == -1 || p->filetype != AE_IFDIR) {
+#if HAVE_FSTAT
+				if (fd > 0 && (
+				    fstat(fd, &st) != 0 ||
+				    la_verify_filetype(st.st_mode,
+				    p->filetype) == 0)) {
+					goto skip_fixup_entry;
+				} else
+#endif
 				if (lstat(p->name, &st) != 0 ||
-				    !S_ISDIR(st.st_mode)) {
+				    la_verify_filetype(st.st_mode,
+				    p->filetype) == 0) {
 					goto skip_fixup_entry;
 				}
 			}
 #else
 #if HAVE_FSTAT
 			if (fd > 0 && (
-			    fstat(fd, &st) != 0 || !S_ISDIR(st.st_mode))) {
+			    fstat(fd, &st) != 0 ||
+			    la_verify_filetype(st.st_mode,
+			    p->filetype) == 0)) {
 				goto skip_fixup_entry;
 			} else
 #endif
 			if (lstat(p->name, &st) != 0 ||
-			    !S_ISDIR(st.st_mode)) {
+			    la_verify_filetype(st.st_mode,
+			    p->filetype) == 0) {
 				goto skip_fixup_entry;
 			}
 #endif
@@ -2689,6 +2750,7 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
 	fe->next = a->fixup_list;
 	a->fixup_list = fe;
 	fe->fixup = 0;
+	fe->filetype = 0;
 	fe->name = strdup(pathname);
 	return (fe);
 }
@@ -3811,6 +3873,7 @@ set_fflags(struct archive_write_disk *a)
 			le = current_fixup(a, a->name);
 			if (le == NULL)
 				return (ARCHIVE_FATAL);
+			le->filetype = archive_entry_filetype(a->entry);
 			le->fixup |= TODO_FFLAGS;
 			le->fflags_set = set;
 			/* Store the mode if it's not already there. */
-- 
2.31.1