CVE-2021-23177: Improper Link Resolution Before File Access ('Link Following') (CWE-59)
value: An improper link resolution flaw while extracting an archive can lead to changing the access control list (ACL) of the target of the link. An attacker may provide a malicious archive to a victim user, who would trigger this flaw when trying to extract the archive. A local attacker may use this flaw to change the ACL of a file on the system and gain more privileges.
CWE description: The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.
CVSS v3 base score: 7.8
Published: 2022-08-23T16:15Z
Repository: https://github.com/libarchive/libarchive
Language: C
Files changed: libarchive/archive_disk_acl_freebsd.c, libarchive/archive_disk_acl_linux.c, libarchive/archive_disk_acl_sunos.c
Commit date: 2021-08-22 03:50:30 +0200
Commit message: Fix handling of symbolic link ACLs

On Linux ACLs on symbolic links are not supported.
We must avoid calling acl_set_file() on symbolic links as their
targets are modified instead.

While here, do not try to set default ACLs on non-directories.

Fixes #1565

Diff: diff --git a/libarchive/archive_disk_acl_freebsd.c b/libarchive/archive_disk_acl_freebsd.c
index aba41e5dab..ed4e7a7896 100644
--- a/libarchive/archive_disk_acl_freebsd.c
+++ b/libarchive/archive_disk_acl_freebsd.c
@@ -317,11 +317,11 @@ translate_acl(struct archive_read_disk *a,
 	return (ARCHIVE_OK);
 }
 
 static int
 set_acl(struct archive *a, int fd, const char *name,
-    struct archive_acl *abstract_acl,
+    struct archive_acl *abstract_acl, __LA_MODE_T mode,
     int ae_requested_type, const char *tname)
 {
 	int		 acl_type = 0;
 	acl_t		 acl;
 	acl_entry_t	 acl_entry;
@@ -362,10 +362,17 @@ set_acl(struct archive *a, int fd, const char *name,
 		errno = ENOENT;
 		archive_set_error(a, errno, "Unsupported ACL type");
 		return (ARCHIVE_FAILED);
 	}
 
+	if (acl_type == ACL_TYPE_DEFAULT && !S_ISDIR(mode)) {
+		errno = EINVAL;
+		archive_set_error(a, errno,
+		    "Cannot set default ACL on non-directory");
+		return (ARCHIVE_WARN);
+	}
+
 	acl = acl_init(entries);
 	if (acl == (acl_t)NULL) {
 		archive_set_error(a, errno,
 		    "Failed to initialize ACL working storage");
 		return (ARCHIVE_FAILED);
@@ -540,11 +547,14 @@ set_acl(struct archive *a, int fd, const char *name,
 	}
 #if HAVE_ACL_SET_LINK_NP
 	else if (acl_set_link_np(name, acl_type, acl) != 0)
 #else
 	/* FreeBSD older than 8.0 */
-	else if (acl_set_file(name, acl_type, acl) != 0)
+	else if (S_ISLNK(mode)) {
+	    /* acl_set_file() follows symbolic links, skip */
+	    ret = ARCHIVE_OK;
+	} else if (acl_set_file(name, acl_type, acl) != 0)
 #endif
 	{
 		if (errno == EOPNOTSUPP) {
 			/* Filesystem doesn't support ACLs */
 			ret = ARCHIVE_OK;
@@ -675,27 +685,27 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
 
 	if ((archive_acl_types(abstract_acl)
 	    & ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) != 0) {
 		if ((archive_acl_types(abstract_acl)
 		    & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
-			ret = set_acl(a, fd, name, abstract_acl,
+			ret = set_acl(a, fd, name, abstract_acl, mode,
 			    ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
 			if (ret != ARCHIVE_OK)
 				return (ret);
 		}
 		if ((archive_acl_types(abstract_acl)
 		    & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0)
-			ret = set_acl(a, fd, name, abstract_acl,
+			ret = set_acl(a, fd, name, abstract_acl, mode,
 			    ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
 
 		/* Simultaneous POSIX.1e and NFSv4 is not supported */
 		return (ret);
 	}
 #if ARCHIVE_ACL_FREEBSD_NFS4
 	else if ((archive_acl_types(abstract_acl) &
 	    ARCHIVE_ENTRY_ACL_TYPE_NFS4) != 0) {
-		ret = set_acl(a, fd, name, abstract_acl,
+		ret = set_acl(a, fd, name, abstract_acl, mode,
 		    ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
 	}
 #endif
 	return (ret);
 }
diff --git a/libarchive/archive_disk_acl_linux.c b/libarchive/archive_disk_acl_linux.c
index 3928f3d6fa..31d270535c 100644
--- a/libarchive/archive_disk_acl_linux.c
+++ b/libarchive/archive_disk_acl_linux.c
@@ -341,10 +341,15 @@ set_richacl(struct archive *a, int fd, const char *name,
 		errno = ENOENT;
 		archive_set_error(a, errno, "Unsupported ACL type");
 		return (ARCHIVE_FAILED);
 	}
 
+	if (S_ISLNK(mode)) {
+		/* Linux does not support RichACLs on symbolic links */
+		return (ARCHIVE_OK);
+	}
+
 	richacl = richacl_alloc(entries);
 	if (richacl == NULL) {
 		archive_set_error(a, errno,
 			"Failed to initialize RichACL working storage");
 		return (ARCHIVE_FAILED);
@@ -453,11 +458,11 @@ exit_free:
 #endif /* ARCHIVE_ACL_RICHACL */
 
 #if ARCHIVE_ACL_LIBACL
 static int
 set_acl(struct archive *a, int fd, const char *name,
-    struct archive_acl *abstract_acl,
+    struct archive_acl *abstract_acl, __LA_MODE_T mode,
     int ae_requested_type, const char *tname)
 {
 	int		 acl_type = 0;
 	int		 ae_type, ae_permset, ae_tag, ae_id;
 	uid_t		 ae_uid;
@@ -486,10 +491,22 @@ set_acl(struct archive *a, int fd, const char *name,
 		errno = ENOENT;
 		archive_set_error(a, errno, "Unsupported ACL type");
 		return (ARCHIVE_FAILED);
 	}
 
+	if (S_ISLNK(mode)) {
+		/* Linux does not support ACLs on symbolic links */
+		return (ARCHIVE_OK);
+	}
+
+	if (acl_type == ACL_TYPE_DEFAULT && !S_ISDIR(mode)) {
+		errno = EINVAL;
+		archive_set_error(a, errno,
+		    "Cannot set default ACL on non-directory");
+		return (ARCHIVE_WARN);
+	}
+
 	acl = acl_init(entries);
 	if (acl == (acl_t)NULL) {
 		archive_set_error(a, errno,
 		    "Failed to initialize ACL working storage");
 		return (ARCHIVE_FAILED);
@@ -725,18 +742,18 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
 #if ARCHIVE_ACL_LIBACL
 	if ((archive_acl_types(abstract_acl)
 	    & ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) != 0) {
 		if ((archive_acl_types(abstract_acl)
 		    & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
-			ret = set_acl(a, fd, name, abstract_acl,
+			ret = set_acl(a, fd, name, abstract_acl, mode,
 			    ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
 			if (ret != ARCHIVE_OK)
 				return (ret);
 		}
 		if ((archive_acl_types(abstract_acl)
 		    & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0)
-			ret = set_acl(a, fd, name, abstract_acl,
+			ret = set_acl(a, fd, name, abstract_acl, mode,
 			    ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
 	}
 #endif	/* ARCHIVE_ACL_LIBACL */
 	return (ret);
 }
diff --git a/libarchive/archive_disk_acl_sunos.c b/libarchive/archive_disk_acl_sunos.c
index b0f5dfad9b..0ef3ad52ee 100644
--- a/libarchive/archive_disk_acl_sunos.c
+++ b/libarchive/archive_disk_acl_sunos.c
@@ -441,11 +441,11 @@ translate_acl(struct archive_read_disk *a,
 	return (ARCHIVE_OK);
 }
 
 static int
 set_acl(struct archive *a, int fd, const char *name,
-    struct archive_acl *abstract_acl,
+    struct archive_acl *abstract_acl, __LA_MODE_T mode,
     int ae_requested_type, const char *tname)
 {
 	aclent_t	 *aclent;
 #if ARCHIVE_ACL_SUNOS_NFS4
 	ace_t		 *ace;
@@ -465,11 +465,10 @@ set_acl(struct archive *a, int fd, const char *name,
 	ret = ARCHIVE_OK;
 	entries = archive_acl_reset(abstract_acl, ae_requested_type);
 	if (entries == 0)
 		return (ARCHIVE_OK);
 
-
 	switch (ae_requested_type) {
 	case ARCHIVE_ENTRY_ACL_TYPE_POSIX1E:
 		cmd = SETACL;
 		aclp = malloc(entries * sizeof(aclent_t));
 		break;
@@ -490,10 +489,16 @@ set_acl(struct archive *a, int fd, const char *name,
 		archive_set_error(a, errno,
 		    "Can't allocate memory for acl buffer");
 		return (ARCHIVE_FAILED);
 	}
 
+        if (S_ISLNK(mode)) {
+                /* Skip ACLs on symbolic links */
+		ret = ARCHIVE_OK;
+		goto exit_free;
+        }
+
 	e = 0;
 
 	while (archive_acl_next(a, abstract_acl, ae_requested_type, &ae_type,
 		   &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) {
 		aclent = NULL;
@@ -799,20 +804,20 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
 	(void)mode;	/* UNUSED */
 
 	if ((archive_acl_types(abstract_acl)
 	    & ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) != 0) {
 		/* Solaris writes POSIX.1e access and default ACLs together */
-		ret = set_acl(a, fd, name, abstract_acl,
+		ret = set_acl(a, fd, name, abstract_acl, mode,
 		    ARCHIVE_ENTRY_ACL_TYPE_POSIX1E, "posix1e");
 
 		/* Simultaneous POSIX.1e and NFSv4 is not supported */
 		return (ret);
 	}
 #if ARCHIVE_ACL_SUNOS_NFS4
 	else if ((archive_acl_types(abstract_acl) &
 	    ARCHIVE_ENTRY_ACL_TYPE_NFS4) != 0) {
-		ret = set_acl(a, fd, name, abstract_acl,
+		ret = set_acl(a, fd, name, abstract_acl, mode,
 		    ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
 	}
 #endif
 	return (ret);
 }

Vulnerable code:     struct archive_acl *abstract_acl,
	else if (acl_set_file(name, acl_type, acl) != 0)
			ret = set_acl(a, fd, name, abstract_acl,
			ret = set_acl(a, fd, name, abstract_acl,
		ret = set_acl(a, fd, name, abstract_acl,
    struct archive_acl *abstract_acl,
			ret = set_acl(a, fd, name, abstract_acl,
			ret = set_acl(a, fd, name, abstract_acl,
    struct archive_acl *abstract_acl,

		ret = set_acl(a, fd, name, abstract_acl,
		ret = set_acl(a, fd, name, abstract_acl,
Fixed code:     struct archive_acl *abstract_acl, __LA_MODE_T mode,
	if (acl_type == ACL_TYPE_DEFAULT && !S_ISDIR(mode)) {
		errno = EINVAL;
		archive_set_error(a, errno,
		    "Cannot set default ACL on non-directory");
		return (ARCHIVE_WARN);
	}

	else if (S_ISLNK(mode)) {
	    /* acl_set_file() follows symbolic links, skip */
	    ret = ARCHIVE_OK;
	} else if (acl_set_file(name, acl_type, acl) != 0)
			ret = set_acl(a, fd, name, abstract_acl, mode,
			ret = set_acl(a, fd, name, abstract_acl, mode,
		ret = set_acl(a, fd, name, abstract_acl, mode,
	if (S_ISLNK(mode)) {
		/* Linux does not support RichACLs on symbolic links */
		return (ARCHIVE_OK);
	}

    struct archive_acl *abstract_acl, __LA_MODE_T mode,
	if (S_ISLNK(mode)) {
		/* Linux does not support ACLs on symbolic links */
		return (ARCHIVE_OK);
	}

	if (acl_type == ACL_TYPE_DEFAULT && !S_ISDIR(mode)) {
		errno = EINVAL;
		archive_set_error(a, errno,
		    "Cannot set default ACL on non-directory");
		return (ARCHIVE_WARN);
	}

			ret = set_acl(a, fd, name, abstract_acl, mode,
			ret = set_acl(a, fd, name, abstract_acl, mode,
    struct archive_acl *abstract_acl, __LA_MODE_T mode,
        if (S_ISLNK(mode)) {
                /* Skip ACLs on symbolic links */
		ret = ARCHIVE_OK;
		goto exit_free;
        }

		ret = set_acl(a, fd, name, abstract_acl, mode,
		ret = set_acl(a, fd, name, abstract_acl, mode,