commit 8a1bd5c18e896f0411a991240ce0d772bb02c840
Author: Martin Matuska <martin@matuska.org>
Date:   Fri Aug 27 10:56:28 2021 +0200

    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

Index: libarchive-3.3.3/libarchive/archive_write_disk_posix.c
===================================================================
--- libarchive-3.3.3.orig/libarchive/archive_write_disk_posix.c
+++ libarchive-3.3.3/libarchive/archive_write_disk_posix.c
@@ -2319,7 +2319,9 @@ _archive_write_disk_close(struct archive
 {
 	struct archive_write_disk *a = (struct archive_write_disk *)_a;
 	struct fixup_entry *next, *p;
-	int ret;
+        struct stat st;
+        char *c;
+	int fd, ret;
 
 	archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
 	    ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
@@ -2330,34 +2332,92 @@ _archive_write_disk_close(struct archive
 	p = sort_dir_list(a->fixup_list);
 
 	while (p != NULL) {
-		a->pst = NULL; /* Mark stat cache as out-of-date. */
-		if (p->fixup & TODO_TIMES) {
-			set_times(a, -1, 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)
-			chmod(p->name, p->mode);
-		if (p->fixup & TODO_ACLS)
-			archive_write_disk_set_acls(&a->archive, -1, p->name,
-			    &p->acl, p->mode);
-		if (p->fixup & TODO_FFLAGS)
-			set_fflags_platform(a, -1, p->name,
-			    p->mode, p->fflags_set, 0);
-		if (p->fixup & TODO_MAC_METADATA)
-			set_mac_metadata(a, p->name, p->mac_metadata,
-					 p->mac_metadata_size);
-		next = p->next;
-		archive_acl_clear(&p->acl);
-		free(p->mac_metadata);
-		free(p->name);
-		free(p);
-		p = next;
-	}
-	a->fixup_list = NULL;
-	return (ret);
+                fd = -1;
+                a->pst = NULL; /* Mark stat cache as out-of-date. */
+
+                /* 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 (lstat(p->name, &st) != 0 ||
+                                    !S_ISDIR(st.st_mode)) {
+                                        goto skip_fixup_entry;
+                                }
+                        }
+#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,
+                            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) {
+#ifdef HAVE_FCHMOD
+                        if (fd >= 0)
+                                fchmod(fd, p->mode & 07777);
+                        else
+#endif
+#ifdef HAVE_LCHMOD
+                        lchmod(p->name, p->mode & 07777);
+#else
+                        chmod(p->name, p->mode & 07777);
+#endif
+                }
+                if (p->fixup & TODO_ACLS)
+                        archive_write_disk_set_acls(&a->archive, fd,
+                            p->name, &p->acl, p->mode);
+                if (p->fixup & TODO_FFLAGS)
+                        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,
+                                         p->mac_metadata_size);
+skip_fixup_entry:
+                next = p->next;
+                archive_acl_clear(&p->acl);
+                free(p->mac_metadata);
+                free(p->name);
+                if (fd >= 0)
+                        close(fd);
+                free(p);
+                p = next;
+        }
+        a->fixup_list = NULL;
+        return (ret);
 }
 
 static int
