diff options
Diffstat (limited to 'archivers/libarchive/files/libarchive/archive_write_disk_posix.c')
-rw-r--r-- | archivers/libarchive/files/libarchive/archive_write_disk_posix.c | 360 |
1 files changed, 256 insertions, 104 deletions
diff --git a/archivers/libarchive/files/libarchive/archive_write_disk_posix.c b/archivers/libarchive/files/libarchive/archive_write_disk_posix.c index 3fd5f579851..b1a0bb3843a 100644 --- a/archivers/libarchive/files/libarchive/archive_write_disk_posix.c +++ b/archivers/libarchive/files/libarchive/archive_write_disk_posix.c @@ -165,6 +165,10 @@ __FBSDID("$FreeBSD$"); #define O_NOFOLLOW 0 #endif +#ifndef AT_FDCWD +#define AT_FDCWD -100 +#endif + struct fixup_entry { struct fixup_entry *next; struct archive_acl acl; @@ -348,6 +352,8 @@ struct archive_write_disk { #define HFS_BLOCKS(s) ((s) >> 12) + +static int la_opendirat(int, const char *); static void fsobj_error(int *, struct archive_string *, int, const char *, const char *); static int check_symlinks_fsobj(char *, int *, struct archive_string *, @@ -401,6 +407,37 @@ static ssize_t _archive_write_disk_data_block(struct archive *, const void *, size_t, int64_t); static int +la_opendirat(int fd, const char *path) { + const int flags = O_CLOEXEC +#if defined(O_BINARY) + | O_BINARY +#endif +#if defined(O_DIRECTORY) + | O_DIRECTORY +#endif +#if defined(O_PATH) + | O_PATH +#elif defined(O_SEARCH) + | O_SEARCH +#elif defined(O_EXEC) + | O_EXEC +#else + | O_RDONLY +#endif + ; + +#if !defined(HAVE_OPENAT) + if (fd != AT_FDCWD) { + errno = ENOTSUP; + return (-1); + } else + return (open(fd, path, flags)); +#else + return (openat(fd, path, flags)); +#endif +} + +static int lazy_stat(struct archive_write_disk *a) { if (a->pst != NULL) { @@ -1705,6 +1742,20 @@ _archive_write_disk_finish_entry(struct archive *_a) } /* + * HYPOTHESIS: + * If we're not root, we won't be setting any security + * attributes that may be wiped by the set_mode() routine + * below. We also can't set xattr on non-owner-writable files, + * which may be the state after set_mode(). Perform + * set_xattrs() first based on these constraints. + */ + if (a->user_uid != 0 && + (a->todo & TODO_XATTR)) { + int r2 = set_xattrs(a); + if (r2 < ret) ret = r2; + } + + /* * set_mode must precede ACLs on systems such as Solaris and * FreeBSD where setting the mode implicitly clears extended ACLs */ @@ -1717,8 +1768,10 @@ _archive_write_disk_finish_entry(struct archive *_a) * Security-related extended attributes (such as * security.capability on Linux) have to be restored last, * since they're implicitly removed by other file changes. + * We do this last only when root. */ - if (a->todo & TODO_XATTR) { + if (a->user_uid == 0 && + (a->todo & TODO_XATTR)) { int r2 = set_xattrs(a); if (r2 < ret) ret = r2; } @@ -1775,10 +1828,8 @@ finish_metadata: a->fd = -1; } /* If there's an entry, we can release it now. */ - if (a->entry) { - archive_entry_free(a->entry); - a->entry = NULL; - } + archive_entry_free(a->entry); + a->entry = NULL; a->archive.state = ARCHIVE_STATE_HEADER; return (ret); } @@ -1895,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 = la_opendirat(AT_FDCWD, "."); __archive_ensure_cloexec_flag(a->restore_pwd); if (a->restore_pwd < 0) return; @@ -2018,7 +2069,7 @@ restore_entry(struct archive_write_disk *a) * follow the symlink if we're creating a dir. */ if (S_ISDIR(a->mode)) - r = stat(a->name, &a->st); + r = la_stat(a->name, &a->st); /* * If it's not a dir (or it's a broken symlink), * then don't follow it. @@ -2184,7 +2235,7 @@ create_filesystem_object(struct archive_write_disk *a) #ifdef HAVE_LSTAT r = lstat(a->name, &st); #else - r = stat(a->name, &st); + r = la_stat(a->name, &st); #endif if (r != 0) r = errno; @@ -2223,6 +2274,15 @@ create_filesystem_object(struct archive_write_disk *a) */ mode = final_mode & 0777 & ~a->user_umask; + /* + * Always create writable such that [f]setxattr() works if we're not + * root. + */ + if (a->user_uid != 0 && + a->todo & (TODO_HFS_COMPRESSION | TODO_XATTR)) { + mode |= 0200; + } + switch (a->mode & AE_IFMT) { default: /* POSIX requires that we fall through here. */ @@ -2319,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, @@ -2330,21 +2390,33 @@ _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->fixup & + (TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) { + 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, 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 (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, -1, p->name, - &p->acl, p->mode); + archive_write_disk_set_acls(&a->archive, fd, + p->name, &p->acl, p->mode); if (p->fixup & TODO_FFLAGS) - set_fflags_platform(a, -1, 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, @@ -2353,6 +2425,8 @@ _archive_write_disk_close(struct archive *_a) archive_acl_clear(&p->acl); free(p->mac_metadata); free(p->name); + if (fd >= 0) + close(fd); free(p); p = next; } @@ -2373,8 +2447,7 @@ _archive_write_disk_free(struct archive *_a) ret = _archive_write_disk_close(&a->archive); archive_write_disk_set_group_lookup(&a->archive, NULL, NULL, NULL); archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL); - if (a->entry) - archive_entry_free(a->entry); + archive_entry_free(a->entry); archive_string_free(&a->_name_data); archive_string_free(&a->archive.error_string); archive_string_free(&a->path_safe); @@ -2518,8 +2591,6 @@ fsobj_error(int *a_eno, struct archive_string *a_estr, * scan the path and both can be optimized by comparing against other * recent paths. */ -/* TODO: Extend this to support symlinks on Windows Vista and later. */ - /* * Checks the given path to see if any elements along it are symlinks. Returns * ARCHIVE_OK if there are none, otherwise puts an error in errmsg. @@ -2528,7 +2599,8 @@ static int check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, int flags) { -#if !defined(HAVE_LSTAT) +#if !defined(HAVE_LSTAT) && \ + !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT)) /* Platform doesn't have lstat, so we can't look for symlinks. */ (void)path; /* UNUSED */ (void)error_number; /* UNUSED */ @@ -2543,7 +2615,10 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, char c; int r; struct stat st; - int restore_pwd; + int chdir_fd; +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + int fd; +#endif /* Nothing to do here if name is empty */ if(path[0] == '\0') @@ -2564,10 +2639,13 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, * c holds what used to be in *tail * last is 1 if this is the last tail */ - restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC); - __archive_ensure_cloexec_flag(restore_pwd); - if (restore_pwd < 0) + chdir_fd = la_opendirat(AT_FDCWD, "."); + __archive_ensure_cloexec_flag(chdir_fd); + if (chdir_fd < 0) { + fsobj_error(a_eno, a_estr, errno, + "Could not open ", path); return (ARCHIVE_FATAL); + } head = path; tail = path; last = 0; @@ -2596,7 +2674,11 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, c = tail[0]; tail[0] = '\0'; /* Check that we haven't hit a symlink. */ +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + r = fstatat(chdir_fd, head, &st, AT_SYMLINK_NOFOLLOW); +#else r = lstat(head, &st); +#endif if (r != 0) { tail[0] = c; /* We've hit a dir that doesn't exist; stop now. */ @@ -2622,7 +2704,19 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, } } else if (S_ISDIR(st.st_mode)) { if (!last) { - if (chdir(head) != 0) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + fd = la_opendirat(chdir_fd, head); + if (fd < 0) + r = -1; + else { + r = 0; + close(chdir_fd); + chdir_fd = fd; + } +#else + r = chdir(head); +#endif + if (r != 0) { tail[0] = c; fsobj_error(a_eno, a_estr, errno, "Could not chdir ", path); @@ -2639,7 +2733,12 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, * so we can overwrite it with the * item being extracted. */ - if (unlink(head)) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + r = unlinkat(chdir_fd, head, 0); +#else + r = unlink(head); +#endif + if (r != 0) { tail[0] = c; fsobj_error(a_eno, a_estr, errno, "Could not remove symlink ", @@ -2669,7 +2768,12 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, break; } else if (flags & ARCHIVE_EXTRACT_UNLINK) { /* User asked us to remove problems. */ - if (unlink(head) != 0) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + r = unlinkat(chdir_fd, head, 0); +#else + r = unlink(head); +#endif + if (r != 0) { tail[0] = c; fsobj_error(a_eno, a_estr, 0, "Cannot remove intervening " @@ -2687,7 +2791,11 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, * This is needed to extract hardlinks over * symlinks. */ - r = stat(head, &st); +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + r = fstatat(chdir_fd, head, &st, 0); +#else + r = la_stat(head, &st); +#endif if (r != 0) { tail[0] = c; if (errno == ENOENT) { @@ -2700,7 +2808,19 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, break; } } else if (S_ISDIR(st.st_mode)) { - if (chdir(head) != 0) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + fd = la_opendirat(chdir_fd, head); + if (fd < 0) + r = -1; + else { + r = 0; + close(chdir_fd); + chdir_fd = fd; + } +#else + r = chdir(head); +#endif + if (r != 0) { tail[0] = c; fsobj_error(a_eno, a_estr, errno, @@ -2736,16 +2856,21 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr, } /* Catches loop exits via break */ tail[0] = c; -#ifdef HAVE_FCHDIR +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT) + /* If we operate with openat(), fstatat() and unlinkat() there was + * no chdir(), so just close the fd */ + if (chdir_fd >= 0) + close(chdir_fd); +#elif HAVE_FCHDIR /* If we changed directory above, restore it here. */ - if (restore_pwd >= 0) { - r = fchdir(restore_pwd); + if (chdir_fd >= 0) { + r = fchdir(chdir_fd); if (r != 0) { fsobj_error(a_eno, a_estr, errno, "chdir() failure", ""); } - close(restore_pwd); - restore_pwd = -1; + close(chdir_fd); + chdir_fd = -1; if (r != 0) { res = (ARCHIVE_FATAL); } @@ -3027,7 +3152,7 @@ create_dir(struct archive_write_disk *a, char *path) * here loses the ability to extract through symlinks. Also note * that this should not use the a->st cache. */ - if (stat(path, &st) == 0) { + if (la_stat(path, &st) == 0) { if (S_ISDIR(st.st_mode)) return (ARCHIVE_OK); if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { @@ -3085,7 +3210,7 @@ create_dir(struct archive_write_disk *a, char *path) * don't add it to the fixup list here, as it's already been * added. */ - if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + if (la_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return (ARCHIVE_OK); archive_set_error(&a->archive, errno, "Failed to create dir '%s'", @@ -3106,12 +3231,14 @@ create_dir(struct archive_write_disk *a, char *path) static int set_ownership(struct archive_write_disk *a) { -#ifndef __CYGWIN__ -/* unfortunately, on win32 there is no 'root' user with uid 0, - so we just have to try the chown and see if it works */ - - /* If we know we can't change it, don't bother trying. */ - if (a->user_uid != 0 && a->user_uid != a->uid) { +#if !defined(__CYGWIN__) && !defined(__linux__) +/* + * On Linux, a process may have the CAP_CHOWN capability. + * On Windows there is no 'root' user with uid 0. + * Elsewhere we can skip calling chown if we are not root and the desired + * user id does not match the current user. + */ + if (a->user_uid != 0 && a->user_uid != a->uid) { archive_set_error(&a->archive, errno, "Can't set UID=%jd", (intmax_t)a->uid); return (ARCHIVE_WARN); @@ -3360,6 +3487,7 @@ static int set_mode(struct archive_write_disk *a, int mode) { int r = ARCHIVE_OK; + int r2; mode &= 07777; /* Strip off file type bits. */ if (a->todo & TODO_SGID_CHECK) { @@ -3416,16 +3544,26 @@ set_mode(struct archive_write_disk *a, int mode) } if (S_ISLNK(a->mode)) { -#ifdef HAVE_LCHMOD /* - * If this is a symlink, use lchmod(). If the + * If this is a symlink, use fchmod() or lchmod(). If the * platform doesn't support lchmod(), just skip it. A * platform that doesn't provide a way to set * permissions on symlinks probably ignores * permissions on symlinks, so a failure here has no * impact. */ - if (lchmod(a->name, mode) != 0) { +#ifdef HAVE_FCHMOD + if (a->fd > 0) + r2 = fchmod(a->fd, mode); + else +#endif +#ifdef HAVE_LCHMOD + r2 = lchmod(a->name, mode); +#else + /* We don't have lchmod() here and a fd is not given */ + r2 = 0; +#endif + if (r2 != 0) { switch (errno) { case ENOTSUP: case ENOSYS: @@ -3444,7 +3582,6 @@ set_mode(struct archive_write_disk *a, int mode) r = ARCHIVE_WARN; } } -#endif } else if (!S_ISDIR(a->mode)) { /* * If it's not a symlink and not a dir, then use @@ -3453,21 +3590,19 @@ set_mode(struct archive_write_disk *a, int mode) * post-extract fixup, which is handled elsewhere. */ #ifdef HAVE_FCHMOD - if (a->fd >= 0) { - if (fchmod(a->fd, mode) != 0) { - archive_set_error(&a->archive, errno, - "Can't set permissions to 0%o", (int)mode); - r = ARCHIVE_WARN; - } - } else + if (a->fd >= 0) + r2 = fchmod(a->fd, mode); + else #endif - /* If this platform lacks fchmod(), then - * we'll just use chmod(). */ - if (chmod(a->name, mode) != 0) { - archive_set_error(&a->archive, errno, - "Can't set permissions to 0%o", (int)mode); - r = ARCHIVE_WARN; - } + /* If this platform lacks fchmod(), then + * we'll just use chmod(). */ + r2 = chmod(a->name, mode); + + if (r2 != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } } return (r); } @@ -3478,9 +3613,7 @@ set_fflags(struct archive_write_disk *a) struct fixup_entry *le; unsigned long set, clear; int r; - int critical_flags; mode_t mode = archive_entry_mode(a->entry); - /* * Make 'critical_flags' hold all file flags that can't be * immediately restored. For example, on BSD systems, @@ -3496,33 +3629,33 @@ set_fflags(struct archive_write_disk *a) * other programs that might try to muck with files as they're * being restored. */ - /* Hopefully, the compiler will optimize this mess into a constant. */ - critical_flags = 0; + const int critical_flags = 0 #ifdef SF_IMMUTABLE - critical_flags |= SF_IMMUTABLE; + | SF_IMMUTABLE #endif #ifdef UF_IMMUTABLE - critical_flags |= UF_IMMUTABLE; + | UF_IMMUTABLE #endif #ifdef SF_APPEND - critical_flags |= SF_APPEND; + | SF_APPEND #endif #ifdef UF_APPEND - critical_flags |= UF_APPEND; + | UF_APPEND #endif #if defined(FS_APPEND_FL) - critical_flags |= FS_APPEND_FL; + | FS_APPEND_FL #elif defined(EXT2_APPEND_FL) - critical_flags |= EXT2_APPEND_FL; + | EXT2_APPEND_FL #endif #if defined(FS_IMMUTABLE_FL) - critical_flags |= FS_IMMUTABLE_FL; + | FS_IMMUTABLE_FL #elif defined(EXT2_IMMUTABLE_FL) - critical_flags |= EXT2_IMMUTABLE_FL; + | EXT2_IMMUTABLE_FL #endif #ifdef FS_JOURNAL_DATA_FL - critical_flags |= FS_JOURNAL_DATA_FL; + | FS_JOURNAL_DATA_FL #endif + ; if (a->todo & TODO_FFLAGS) { archive_entry_fflags(a->entry, &set, &clear); @@ -3553,29 +3686,27 @@ set_fflags(struct archive_write_disk *a) static int clear_nochange_fflags(struct archive_write_disk *a) { - int nochange_flags; mode_t mode = archive_entry_mode(a->entry); - - /* Hopefully, the compiler will optimize this mess into a constant. */ - nochange_flags = 0; + const int nochange_flags = 0 #ifdef SF_IMMUTABLE - nochange_flags |= SF_IMMUTABLE; + | SF_IMMUTABLE #endif #ifdef UF_IMMUTABLE - nochange_flags |= UF_IMMUTABLE; + | UF_IMMUTABLE #endif #ifdef SF_APPEND - nochange_flags |= SF_APPEND; + | SF_APPEND #endif #ifdef UF_APPEND - nochange_flags |= UF_APPEND; + | UF_APPEND #endif #ifdef EXT2_APPEND_FL - nochange_flags |= EXT2_APPEND_FL; + | EXT2_APPEND_FL #endif #ifdef EXT2_IMMUTABLE_FL - nochange_flags |= EXT2_IMMUTABLE_FL; + | EXT2_IMMUTABLE_FL #endif + ; return (set_fflags_platform(a, a->fd, a->name, mode, 0, nochange_flags)); @@ -3591,8 +3722,22 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, mode_t mode, unsigned long set, unsigned long clear) { int r; - + const int sf_mask = 0 +#ifdef SF_APPEND + | SF_APPEND +#endif +#ifdef SF_ARCHIVED + | SF_ARCHIVED +#endif +#ifdef SF_IMMUTABLE + | SF_IMMUTABLE +#endif +#ifdef SF_NOUNLINK + | SF_NOUNLINK +#endif + ; (void)mode; /* UNUSED */ + if (set == 0 && clear == 0) return (ARCHIVE_OK); @@ -3607,6 +3752,12 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, a->st.st_flags &= ~clear; a->st.st_flags |= set; + + /* Only super-user may change SF_* flags */ + + if (a->user_uid != 0) + a->st.st_flags &= ~sf_mask; + #ifdef HAVE_FCHFLAGS /* If platform has fchflags() and we were given an fd, use it. */ if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0) @@ -3648,22 +3799,6 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, int ret; int myfd = fd; int newflags, oldflags; - int sf_mask = 0; - - if (set == 0 && clear == 0) - return (ARCHIVE_OK); - /* Only regular files and dirs can have flags. */ - if (!S_ISREG(mode) && !S_ISDIR(mode)) - return (ARCHIVE_OK); - - /* If we weren't given an fd, open it ourselves. */ - if (myfd < 0) { - myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC); - __archive_ensure_cloexec_flag(myfd); - } - if (myfd < 0) - return (ARCHIVE_OK); - /* * Linux has no define for the flags that are only settable by * the root user. This code may seem a little complex, but @@ -3671,19 +3806,36 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, * defines. (?) The code below degrades reasonably gracefully * if sf_mask is incomplete. */ + const int sf_mask = 0 #if defined(FS_IMMUTABLE_FL) - sf_mask |= FS_IMMUTABLE_FL; + | FS_IMMUTABLE_FL #elif defined(EXT2_IMMUTABLE_FL) - sf_mask |= EXT2_IMMUTABLE_FL; + | EXT2_IMMUTABLE_FL #endif #if defined(FS_APPEND_FL) - sf_mask |= FS_APPEND_FL; + | FS_APPEND_FL #elif defined(EXT2_APPEND_FL) - sf_mask |= EXT2_APPEND_FL; + | EXT2_APPEND_FL #endif #if defined(FS_JOURNAL_DATA_FL) - sf_mask |= FS_JOURNAL_DATA_FL; + | FS_JOURNAL_DATA_FL #endif + ; + + if (set == 0 && clear == 0) + return (ARCHIVE_OK); + /* Only regular files and dirs can have flags. */ + if (!S_ISREG(mode) && !S_ISDIR(mode)) + return (ARCHIVE_OK); + + /* If we weren't given an fd, open it ourselves. */ + if (myfd < 0) { + myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC); + __archive_ensure_cloexec_flag(myfd); + } + if (myfd < 0) + return (ARCHIVE_OK); + /* * XXX As above, this would be way simpler if we didn't have * to read the current flags from disk. XXX |