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