Blame SOURCES/libarchive-3.1.2-CVE-2016-5418.patch

58251f
diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
58251f
index bbd50a6..66fc0f5 100644
58251f
--- a/libarchive/archive_write_disk_posix.c
58251f
+++ b/libarchive/archive_write_disk_posix.c
58251f
@@ -326,6 +326,7 @@ struct archive_write_disk {
58251f
 
58251f
 #define HFS_BLOCKS(s)	((s) >> 12)
58251f
 
58251f
+static int check_path_for_symlinks(char *path, int *error_number, struct archive_string *error_string, int flags);
58251f
 static int	check_symlinks(struct archive_write_disk *);
58251f
 static int	create_filesystem_object(struct archive_write_disk *);
58251f
 static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname);
58251f
@@ -1791,7 +1792,7 @@ edit_deep_directories(struct archive_write_disk *a)
58251f
 	char *tail = a->name;
58251f
 
58251f
 	/* If path is short, avoid the open() below. */
58251f
-	if (strlen(tail) <= PATH_MAX)
58251f
+	if (strlen(tail) < PATH_MAX)
58251f
 		return;
58251f
 
58251f
 	/* Try to record our starting dir. */
58251f
@@ -1801,7 +1802,7 @@ edit_deep_directories(struct archive_write_disk *a)
58251f
 		return;
58251f
 
58251f
 	/* As long as the path is too long... */
58251f
-	while (strlen(tail) > PATH_MAX) {
58251f
+	while (strlen(tail) >= PATH_MAX) {
58251f
 		/* Locate a dir prefix shorter than PATH_MAX. */
58251f
 		tail += PATH_MAX - 8;
58251f
 		while (tail > a->name && *tail != '/')
58251f
@@ -1996,6 +1997,10 @@ create_filesystem_object(struct archive_write_disk *a)
58251f
 	const char *linkname;
58251f
 	mode_t final_mode, mode;
58251f
 	int r;
58251f
+	/* these for check_path_for_symlinks */
58251f
+	char *linkname_copy;	/* non-const copy of linkname */
58251f
+	struct archive_string error_string;
58251f
+	int error_number;
58251f
 
58251f
 	/* We identify hard/symlinks according to the link names. */
58251f
 	/* Since link(2) and symlink(2) don't handle modes, we're done here. */
58251f
@@ -2004,6 +2009,18 @@ create_filesystem_object(struct archive_write_disk *a)
58251f
 #if !HAVE_LINK
58251f
 		return (EPERM);
58251f
 #else
58251f
+		archive_string_init(&error_string);
58251f
+		linkname_copy = strdup(linkname);
58251f
+		if (linkname_copy == NULL) {
58251f
+		    return (EPERM);
58251f
+		}
58251f
+		r = check_path_for_symlinks(linkname_copy, &error_number, &error_string, a->flags);
58251f
+		free(linkname_copy);
58251f
+		if (r != ARCHIVE_OK) {
58251f
+			archive_set_error(&a->archive, error_number, "%s", error_string.s);
58251f
+			/* EPERM is more appropriate than error_number for our callers */
58251f
+			return (EPERM);
58251f
+		}
58251f
 		r = link(linkname, a->name) ? errno : 0;
58251f
 		/*
58251f
 		 * New cpio and pax formats allow hardlink entries
58251f
@@ -2343,99 +2360,214 @@ current_fixup(struct archive_write_disk *a, const char *pathname)
58251f
  * recent paths.
58251f
  */
58251f
 /* TODO: Extend this to support symlinks on Windows Vista and later. */
58251f
-static int
58251f
-check_symlinks(struct archive_write_disk *a)
58251f
+
58251f
+/*
58251f
+ * Checks the given path to see if any elements along it are symlinks.  Returns
58251f
+ * ARCHIVE_OK if there are none, otherwise puts an error in errmsg.
58251f
+ */
58251f
+static int check_path_for_symlinks(char *path, int *error_number, struct archive_string *error_string, int flags)
58251f
 {
58251f
 #if !defined(HAVE_LSTAT)
58251f
 	/* Platform doesn't have lstat, so we can't look for symlinks. */
58251f
-	(void)a; /* UNUSED */
58251f
+	(void)path; /* UNUSED */
58251f
+	(void)error_number; /* UNUSED */
58251f
+	(void)error_string; /* UNUSED */
58251f
+	(void)flags; /* UNUSED */
58251f
 	return (ARCHIVE_OK);
58251f
 #else
58251f
-	char *pn;
58251f
+	int res = ARCHIVE_OK;
58251f
+	char *tail;
58251f
+	char *head;
58251f
+	int last;
58251f
 	char c;
58251f
 	int r;
58251f
 	struct stat st;
58251f
+	int restore_pwd;
58251f
+
58251f
+	/* Nothing to do here if name is empty */
58251f
+	if(path[0] == '\0')
58251f
+	    return (ARCHIVE_OK);
58251f
 
58251f
 	/*
58251f
 	 * Guard against symlink tricks.  Reject any archive entry whose
58251f
 	 * destination would be altered by a symlink.
58251f
+	 *
58251f
+	 * Walk the filename in chunks separated by '/'.  For each segment:
58251f
+	 *  - if it doesn't exist, continue
58251f
+	 *  - if it's symlink, abort or remove it
58251f
+	 *  - if it's a directory and it's not the last chunk, cd into it
58251f
+	 * As we go:
58251f
+	 *  head points to the current (relative) path
58251f
+	 *  tail points to the temporary \0 terminating the segment we're currently examining
58251f
+	 *  c holds what used to be in *tail
58251f
+	 *  last is 1 if this is the last tail
58251f
 	 */
58251f
-	/* Whatever we checked last time doesn't need to be re-checked. */
58251f
-	pn = a->name;
58251f
-	if (archive_strlen(&(a->path_safe)) > 0) {
58251f
-		char *p = a->path_safe.s;
58251f
-		while ((*pn != '\0') && (*p == *pn))
58251f
-			++p, ++pn;
58251f
-	}
58251f
-	c = pn[0];
58251f
-	/* Keep going until we've checked the entire name. */
58251f
-	while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) {
58251f
+	restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC);
58251f
+	__archive_ensure_cloexec_flag(restore_pwd);
58251f
+	if (restore_pwd < 0)
58251f
+		return (ARCHIVE_FATAL);
58251f
+	head = path;
58251f
+	tail = path;
58251f
+	last = 0;
58251f
+
58251f
+	/* TODO: reintroduce a safe cache here? */
58251f
+
58251f
+	/* Keep going until we've checked the entire name.
58251f
+	 * head, tail, path all alias the same string, which is
58251f
+	 * temporarily zeroed at tail, so be careful restoring the
58251f
+	 * stashed (c=tail[0]) for error messages.
58251f
+	 * Exiting the loop with break is okay; continue is not.
58251f
+	 */
58251f
+	while (!last) {
58251f
+		/* Skip the separator we just consumed, plus any adjacent ones */
58251f
+		while (*tail == '/')
58251f
+		    ++tail;
58251f
 		/* Skip the next path element. */
58251f
-		while (*pn != '\0' && *pn != '/')
58251f
-			++pn;
58251f
-		c = pn[0];
58251f
-		pn[0] = '\0';
58251f
+		while (*tail != '\0' && *tail != '/')
58251f
+			++tail;
58251f
+		/* is this the last path component? */
58251f
+		last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0');
58251f
+		/* temporarily truncate the string here */
58251f
+		c = tail[0];
58251f
+		tail[0] = '\0';
58251f
 		/* Check that we haven't hit a symlink. */
58251f
-		r = lstat(a->name, &st);
58251f
+		r = lstat(head, &st);
58251f
 		if (r != 0) {
58251f
+			tail[0] = c;
58251f
 			/* We've hit a dir that doesn't exist; stop now. */
58251f
 			if (errno == ENOENT)
58251f
 				break;
58251f
+			/* Treat any other error as fatal - best to be paranoid here */
58251f
+			if(error_number) *error_number = errno;
58251f
+			if(error_string)
58251f
+				archive_string_sprintf(error_string,
58251f
+					"Could not stat %s",
58251f
+					path);
58251f
+			res = (ARCHIVE_FATAL);
58251f
+			break;
58251f
+		} else if (S_ISDIR(st.st_mode)) {
58251f
+			if (!last) {
58251f
+				if (chdir(head) != 0) {
58251f
+					tail[0] = c;
58251f
+					if(error_number) *error_number = errno;
58251f
+					if(error_string)
58251f
+						archive_string_sprintf(error_string,
58251f
+							"Could not chdir %s",
58251f
+							path);
58251f
+					res = (ARCHIVE_FATAL);
58251f
+					break;
58251f
+				}
58251f
+				/* Our view is now from inside this dir: */
58251f
+				head = tail + 1;
58251f
+			}
58251f
 		} else if (S_ISLNK(st.st_mode)) {
58251f
-			if (c == '\0') {
58251f
+			if (last) {
58251f
 				/*
58251f
 				 * Last element is symlink; remove it
58251f
 				 * so we can overwrite it with the
58251f
 				 * item being extracted.
58251f
 				 */
58251f
-				if (unlink(a->name)) {
58251f
-					archive_set_error(&a->archive, errno,
58251f
-					    "Could not remove symlink %s",
58251f
-					    a->name);
58251f
-					pn[0] = c;
58251f
-					return (ARCHIVE_FAILED);
58251f
+				if (unlink(head)) {
58251f
+					tail[0] = c;
58251f
+					if(error_number) *error_number = errno;
58251f
+					if(error_string)
58251f
+						archive_string_sprintf(error_string,
58251f
+							"Could not remove symlink %s",
58251f
+							path);
58251f
+					res = (ARCHIVE_FAILED);
58251f
+					break;
58251f
 				}
58251f
-				a->pst = NULL;
58251f
 				/*
58251f
 				 * Even if we did remove it, a warning
58251f
 				 * is in order.  The warning is silly,
58251f
 				 * though, if we're just replacing one
58251f
 				 * symlink with another symlink.
58251f
 				 */
58251f
+				tail[0] = c;
58251f
+				/* FIXME:  not sure how important this is to restore
58251f
 				if (!S_ISLNK(a->mode)) {
58251f
-					archive_set_error(&a->archive, 0,
58251f
-					    "Removing symlink %s",
58251f
-					    a->name);
58251f
+					if(error_number) *error_number = 0;
58251f
+					if(error_string)
58251f
+						archive_string_sprintf(error_string,
58251f
+							"Removing symlink %s",
58251f
+							path);
58251f
 				}
58251f
+				*/
58251f
 				/* Symlink gone.  No more problem! */
58251f
-				pn[0] = c;
58251f
-				return (0);
58251f
-			} else if (a->flags & ARCHIVE_EXTRACT_UNLINK) {
58251f
+				res = (ARCHIVE_OK);
58251f
+				break;
58251f
+			} else if (flags & ARCHIVE_EXTRACT_UNLINK) {
58251f
 				/* User asked us to remove problems. */
58251f
-				if (unlink(a->name) != 0) {
58251f
-					archive_set_error(&a->archive, 0,
58251f
-					    "Cannot remove intervening symlink %s",
58251f
-					    a->name);
58251f
-					pn[0] = c;
58251f
-					return (ARCHIVE_FAILED);
58251f
+				if (unlink(head) != 0) {
58251f
+					tail[0] = c;
58251f
+					if(error_number) *error_number = 0;
58251f
+					if(error_string)
58251f
+						archive_string_sprintf(error_string,
58251f
+							"Cannot remove intervening symlink %s",
58251f
+							path);
58251f
+					res = (ARCHIVE_FAILED);
58251f
+					break;
58251f
 				}
58251f
-				a->pst = NULL;
58251f
 			} else {
58251f
-				archive_set_error(&a->archive, 0,
58251f
-				    "Cannot extract through symlink %s",
58251f
-				    a->name);
58251f
-				pn[0] = c;
58251f
-				return (ARCHIVE_FAILED);
58251f
+				tail[0] = c;
58251f
+				if(error_number) *error_number = 0;
58251f
+				if(error_string)
58251f
+					archive_string_sprintf(error_string,
58251f
+						"Cannot extract through symlink %s",
58251f
+						path);
58251f
+				res = (ARCHIVE_FAILED);
58251f
+				break;
58251f
 			}
58251f
 		}
58251f
+		/* be sure to always maintain this */
58251f
+		tail[0] = c;
58251f
 	}
58251f
-	pn[0] = c;
58251f
-	/* We've checked and/or cleaned the whole path, so remember it. */
58251f
-	archive_strcpy(&a->path_safe, a->name);
58251f
-	return (ARCHIVE_OK);
58251f
+	/* Catches loop exits via break */
58251f
+	tail[0] = c;
58251f
+#ifdef HAVE_FCHDIR
58251f
+	/* If we changed directory above, restore it here. */
58251f
+	if (restore_pwd >= 0) {
58251f
+		r = fchdir(restore_pwd);
58251f
+		if (r != 0) {
58251f
+			if(error_number) *error_number = 0;
58251f
+			if(error_string)
58251f
+				archive_string_sprintf(error_string,
58251f
+					"Cannot extract through symlink %s",
58251f
+					path);
58251f
+		}
58251f
+		close(restore_pwd);
58251f
+		restore_pwd = -1;
58251f
+		if (r != 0) {
58251f
+			res = (ARCHIVE_FATAL);
58251f
+		}
58251f
+	}
58251f
+#endif
58251f
+	/* TODO: reintroduce a safe cache here? */
58251f
+	return res;
58251f
 #endif
58251f
 }
58251f
 
58251f
+/*
58251f
+ * Check a->name for symlinks, returning ARCHIVE_OK if its clean, otherwise
58251f
+ * calls archive_set_error and returns ARCHIVE_{FATAL,FAILED}
58251f
+ */
58251f
+static int
58251f
+check_symlinks(struct archive_write_disk *a)
58251f
+{
58251f
+	struct archive_string error_string;
58251f
+	int error_number;
58251f
+	int rc;
58251f
+	archive_string_init(&error_string);
58251f
+	rc = check_path_for_symlinks(a->name, &error_number, &error_string, a->flags);
58251f
+	if (rc != ARCHIVE_OK) {
58251f
+		archive_set_error(&a->archive, error_number, "%s", error_string.s);
58251f
+	}
58251f
+	archive_string_free(&error_string);
58251f
+	a->pst = NULL;	/* to be safe */
58251f
+	return rc;
58251f
+}
58251f
+
58251f
+
58251f
 #if defined(__CYGWIN__)
58251f
 /*
58251f
  * 1. Convert a path separator from '\' to '/' .