Index: user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:29.bspatch =================================================================== --- user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:29.bspatch (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:29.bspatch (revision 307229) @@ -0,0 +1,325 @@ +--- usr.bin/bsdiff/bspatch/bspatch.c.orig ++++ usr.bin/bsdiff/bspatch/bspatch.c +@@ -27,56 +27,133 @@ + #include + __FBSDID("$FreeBSD$"); + ++#if defined(__FreeBSD__) ++#include ++#if __FreeBSD_version >= 1001511 ++#include ++#define HAVE_CAPSICUM ++#endif ++#endif ++ + #include +-#include ++#include ++#include ++#include ++#include ++#include ++#include + #include ++#include + #include +-#include + #include +-#include + + #ifndef O_BINARY + #define O_BINARY 0 + #endif ++#define HEADER_SIZE 32 ++ ++static char *newfile; ++static int dirfd = -1; ++ ++static void ++exit_cleanup(void) ++{ ++ ++ if (dirfd != -1 && newfile != NULL) ++ if (unlinkat(dirfd, newfile, 0)) ++ warn("unlinkat"); ++} + + static off_t offtin(u_char *buf) + { + off_t y; + +- y=buf[7]&0x7F; +- y=y*256;y+=buf[6]; +- y=y*256;y+=buf[5]; +- y=y*256;y+=buf[4]; +- y=y*256;y+=buf[3]; +- y=y*256;y+=buf[2]; +- y=y*256;y+=buf[1]; +- y=y*256;y+=buf[0]; ++ y = buf[7] & 0x7F; ++ y = y * 256; y += buf[6]; ++ y = y * 256; y += buf[5]; ++ y = y * 256; y += buf[4]; ++ y = y * 256; y += buf[3]; ++ y = y * 256; y += buf[2]; ++ y = y * 256; y += buf[1]; ++ y = y * 256; y += buf[0]; + +- if(buf[7]&0x80) y=-y; ++ if (buf[7] & 0x80) ++ y = -y; + +- return y; ++ return (y); + } + +-int main(int argc,char * argv[]) ++int main(int argc, char *argv[]) + { +- FILE * f, * cpf, * dpf, * epf; +- BZFILE * cpfbz2, * dpfbz2, * epfbz2; ++ FILE *f, *cpf, *dpf, *epf; ++ BZFILE *cpfbz2, *dpfbz2, *epfbz2; ++ char *directory, *namebuf; + int cbz2err, dbz2err, ebz2err; +- int fd; +- ssize_t oldsize,newsize; +- ssize_t bzctrllen,bzdatalen; +- u_char header[32],buf[8]; ++ int newfd, oldfd; ++ off_t oldsize, newsize; ++ off_t bzctrllen, bzdatalen; ++ u_char header[HEADER_SIZE], buf[8]; + u_char *old, *new; +- off_t oldpos,newpos; ++ off_t oldpos, newpos; + off_t ctrl[3]; +- off_t lenread; +- off_t i; ++ off_t i, lenread, offset; ++#ifdef HAVE_CAPSICUM ++ cap_rights_t rights_dir, rights_ro, rights_wr; ++#endif + + if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]); + + /* Open patch file */ + if ((f = fopen(argv[3], "rb")) == NULL) + err(1, "fopen(%s)", argv[3]); ++ /* Open patch file for control block */ ++ if ((cpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for diff block */ ++ if ((dpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for extra block */ ++ if ((epf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open oldfile */ ++ if ((oldfd = open(argv[1], O_RDONLY | O_BINARY, 0)) < 0) ++ err(1, "open(%s)", argv[1]); ++ /* open directory where we'll write newfile */ ++ if ((namebuf = strdup(argv[2])) == NULL || ++ (directory = dirname(namebuf)) == NULL || ++ (dirfd = open(directory, O_DIRECTORY)) < 0) ++ err(1, "open %s", argv[2]); ++ free(namebuf); ++ if ((newfile = basename(argv[2])) == NULL) ++ err(1, "basename"); ++ /* open newfile */ ++ if ((newfd = openat(dirfd, newfile, ++ O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) ++ err(1, "open(%s)", argv[2]); ++ atexit(exit_cleanup); ++ ++#ifdef HAVE_CAPSICUM ++ if (cap_enter() < 0) { ++ /* Failed to sandbox, fatal if CAPABILITY_MODE enabled */ ++ if (errno != ENOSYS) ++ err(1, "failed to enter security sandbox"); ++ } else { ++ /* Capsicum Available */ ++ cap_rights_init(&rights_ro, CAP_READ, CAP_FSTAT, CAP_SEEK); ++ cap_rights_init(&rights_wr, CAP_WRITE); ++ cap_rights_init(&rights_dir, CAP_UNLINKAT); ++ ++ if (cap_rights_limit(fileno(f), &rights_ro) < 0 || ++ cap_rights_limit(fileno(cpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(dpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(epf), &rights_ro) < 0 || ++ cap_rights_limit(oldfd, &rights_ro) < 0 || ++ cap_rights_limit(newfd, &rights_wr) < 0 || ++ cap_rights_limit(dirfd, &rights_dir) < 0) ++ err(1, "cap_rights_limit() failed, could not restrict" ++ " capabilities"); ++ } ++#endif + + /* + File format: +@@ -93,99 +170,99 @@ + */ + + /* Read header */ +- if (fread(header, 1, 32, f) < 32) { ++ if (fread(header, 1, HEADER_SIZE, f) < HEADER_SIZE) { + if (feof(f)) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + err(1, "fread(%s)", argv[3]); + } + + /* Check for appropriate magic */ + if (memcmp(header, "BSDIFF40", 8) != 0) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Read lengths from header */ +- bzctrllen=offtin(header+8); +- bzdatalen=offtin(header+16); +- newsize=offtin(header+24); +- if((bzctrllen<0) || (bzdatalen<0) || (newsize<0)) +- errx(1,"Corrupt patch\n"); ++ bzctrllen = offtin(header + 8); ++ bzdatalen = offtin(header + 16); ++ newsize = offtin(header + 24); ++ if (bzctrllen < 0 || bzctrllen > OFF_MAX - HEADER_SIZE || ++ bzdatalen < 0 || bzctrllen + HEADER_SIZE > OFF_MAX - bzdatalen || ++ newsize < 0 || newsize > SSIZE_MAX) ++ errx(1, "Corrupt patch"); + + /* Close patch file and re-open it via libbzip2 at the right places */ + if (fclose(f)) + err(1, "fclose(%s)", argv[3]); +- if ((cpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(cpf, 32, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)32); ++ offset = HEADER_SIZE; ++ if (fseeko(cpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); +- if ((dpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen)); ++ offset += bzctrllen; ++ if (fseeko(dpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); +- if ((epf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen + bzdatalen)); ++ offset += bzdatalen; ++ if (fseeko(epf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); + +- if(((fd=open(argv[1],O_RDONLY|O_BINARY,0))<0) || +- ((oldsize=lseek(fd,0,SEEK_END))==-1) || +- ((old=malloc(oldsize+1))==NULL) || +- (lseek(fd,0,SEEK_SET)!=0) || +- (read(fd,old,oldsize)!=oldsize) || +- (close(fd)==-1)) err(1,"%s",argv[1]); +- if((new=malloc(newsize+1))==NULL) err(1,NULL); ++ if ((oldsize = lseek(oldfd, 0, SEEK_END)) == -1 || ++ oldsize > SSIZE_MAX || ++ (old = malloc(oldsize)) == NULL || ++ lseek(oldfd, 0, SEEK_SET) != 0 || ++ read(oldfd, old, oldsize) != oldsize || ++ close(oldfd) == -1) ++ err(1, "%s", argv[1]); ++ if ((new = malloc(newsize)) == NULL) ++ err(1, NULL); + +- oldpos=0;newpos=0; +- while(newpos INT_MAX || ++ ctrl[1] < 0 || ctrl[1] > INT_MAX) ++ errx(1, "Corrupt patch"); + + /* Sanity-check */ +- if(newpos+ctrl[0]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[0] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read diff string */ + lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); + if ((lenread < ctrl[0]) || + ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Add old data to diff string */ +- for(i=0;i=0) && (oldpos+i= 0) && (oldpos + i < oldsize)) ++ new[newpos + i] += old[oldpos + i]; + + /* Adjust pointers */ +- newpos+=ctrl[0]; +- oldpos+=ctrl[0]; ++ newpos += ctrl[0]; ++ oldpos += ctrl[0]; + + /* Sanity-check */ +- if(newpos+ctrl[1]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[1] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read extra string */ + lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); + if ((lenread < ctrl[1]) || + ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Adjust pointers */ + newpos+=ctrl[1]; +@@ -200,12 +277,13 @@ + err(1, "fclose(%s)", argv[3]); + + /* Write the new file */ +- if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))<0) || +- (write(fd,new,newsize)!=newsize) || (close(fd)==-1)) +- err(1,"%s",argv[2]); ++ if (write(newfd, new, newsize) != newsize || close(newfd) == -1) ++ err(1, "%s", argv[2]); ++ /* Disable atexit cleanup */ ++ newfile = NULL; + + free(new); + free(old); + +- return 0; ++ return (0); + } Index: user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:30.portsnap =================================================================== --- user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:30.portsnap (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:30.portsnap (revision 307229) @@ -0,0 +1,49 @@ +--- usr.sbin/portsnap/portsnap/portsnap.sh.orig ++++ usr.sbin/portsnap/portsnap/portsnap.sh +@@ -646,7 +646,7 @@ + # Verify a list of files + fetch_snapshot_verify() { + while read F; do +- if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then ++ if [ "`gunzip -c < snap/${F}.gz | ${SHA256} -q`" != ${F} ]; then + echo "snapshot corrupt." + return 1 + fi +@@ -681,11 +681,18 @@ + cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1 + # Extract the index + rm -f INDEX.new +- gunzip -c snap/`look INDEX tINDEX.new | ++ gunzip -c < snap/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + # Verify the snapshot contents + cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1 ++ cut -f 2 -d '|' tINDEX.new INDEX.new | sort -u > files.expected ++ find snap -mindepth 1 | sed -E 's^snap/(.*)\.gz^\1^' | sort > files.snap ++ if ! cmp -s files.expected files.snap; then ++ echo "unexpected files in snapshot." ++ return 1 ++ fi ++ rm files.expected files.snap + echo "done." + + # Move files into their proper locations +@@ -777,7 +784,7 @@ + + # Extract the index + echo -n "Extracting index... " 1>${QUIETREDIR} +- gunzip -c files/`look INDEX tINDEX.new | ++ gunzip -c < files/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + +@@ -897,7 +904,7 @@ + echo -n "$1 not provided by portsnap server; " + echo "$2 not being generated." + else +- gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | ++ gunzip -c < "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | + cut -f 2 -d '|'`.gz" | + cat - ${LOCALDESC} | + ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2 Index: user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:31.libarchive =================================================================== --- user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:31.libarchive (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.1-RELEASE/40-SA-16:31.libarchive (revision 307229) @@ -0,0 +1,1270 @@ +--- contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c.orig ++++ contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c +@@ -409,9 +409,7 @@ + { + const char *accpath; + acl_t acl; +-#if HAVE_ACL_IS_TRIVIAL_NP + int r; +-#endif + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) +@@ -443,9 +441,13 @@ + } + #endif + if (acl != NULL) { +- translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); ++ r = translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); + acl_free(acl); +- return (ARCHIVE_OK); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate NFSv4 ACLs: %s", accpath); ++ } ++ return (r); + } + + /* Retrieve access ACL from file. */ +@@ -464,18 +466,29 @@ + else + acl = acl_get_file(accpath, ACL_TYPE_ACCESS); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate access ACLs: %s", accpath); ++ return (r); ++ } + } + + /* Only directories can have default ACLs. */ + if (S_ISDIR(archive_entry_mode(entry))) { + acl = acl_get_file(accpath, ACL_TYPE_DEFAULT); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate default ACLs: %s", ++ accpath); ++ return (r); ++ } + } + } + return (ARCHIVE_OK); +@@ -536,7 +549,11 @@ + // FreeBSD "brands" ACLs as POSIX.1e or NFSv4 + // Make sure the "brand" on this ACL is consistent + // with the default_entry_acl_type bits provided. +- acl_get_brand_np(acl, &brand); ++ if (acl_get_brand_np(acl, &brand) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to read ACL brand"); ++ return (ARCHIVE_WARN); ++ } + switch (brand) { + case ACL_BRAND_POSIX: + switch (default_entry_acl_type) { +@@ -544,30 +561,42 @@ + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for POSIX.1e ACL"); ++ return (ARCHIVE_WARN); + } + break; + case ACL_BRAND_NFS4: + if (default_entry_acl_type & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) { +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for NFSv4 ACL"); ++ return (ARCHIVE_WARN); + } + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL brand"); ++ return (ARCHIVE_WARN); + break; + } + + + s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get first ACL entry"); ++ return (ARCHIVE_WARN); ++ } + while (s == 1) { + ae_id = -1; + ae_name = NULL; + ae_perm = 0; + +- acl_get_tag_type(acl_entry, &acl_tag); ++ if (acl_get_tag_type(acl_entry, &acl_tag) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL tag type"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_tag) { + case ACL_USER: + ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry); +@@ -600,12 +629,17 @@ + continue; + } + +- // XXX acl type maps to allow/deny/audit/YYYY bits +- // XXX acl_get_entry_type_np on FreeBSD returns EINVAL for +- // non-NFSv4 ACLs ++ // XXX acl_type maps to allow/deny/audit/YYYY bits + entry_acl_type = default_entry_acl_type; +- r = acl_get_entry_type_np(acl_entry, &acl_type); +- if (r == 0) { ++ if (default_entry_acl_type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) { ++ /* ++ * acl_get_entry_type_np() falis with non-NFSv4 ACLs ++ */ ++ if (acl_get_entry_type_np(acl_entry, &acl_type) != 0) { ++ archive_set_error(&a->archive, errno, "Failed " ++ "to get ACL type from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_type) { + case ACL_ENTRY_TYPE_ALLOW: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALLOW; +@@ -619,28 +653,52 @@ + case ACL_ENTRY_TYPE_ALARM: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALARM; + break; ++ default: ++ archive_set_error(&a->archive, errno, ++ "Invalid NFSv4 ACL entry type"); ++ return (ARCHIVE_WARN); + } +- } +- +- /* +- * Libarchive stores "flag" (NFSv4 inheritance bits) +- * in the ae_perm bitmap. +- */ +- acl_get_flagset_np(acl_entry, &acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (acl_get_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit)) +- ae_perm |= acl_inherit_map[i].archive_inherit; + +- } ++ /* ++ * Libarchive stores "flag" (NFSv4 inheritance bits) ++ * in the ae_perm bitmap. ++ * ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get flagset from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ r = acl_get_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check flag in a NFSv4 " ++ "ACL flagset"); ++ return (ARCHIVE_WARN); ++ } else if (r) ++ ae_perm |= acl_inherit_map[i].archive_inherit; ++ } ++ } + +- acl_get_permset(acl_entry, &acl_permset); +- for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL permission set"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + /* + * acl_get_perm() is spelled differently on different + * platforms; see above. + */ +- if (ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm)) ++ r = ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check permission in an ACL permission set"); ++ return (ARCHIVE_WARN); ++ } else if (r) + ae_perm |= acl_perm_map[i].archive_perm; + } + +@@ -649,6 +707,11 @@ + ae_id, ae_name); + + s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get next ACL entry"); ++ return (ARCHIVE_WARN); ++ } + } + return (ARCHIVE_OK); + } +--- contrib/libarchive/libarchive/archive_read_support_format_tar.c.orig ++++ contrib/libarchive/libarchive/archive_read_support_format_tar.c +@@ -136,6 +136,7 @@ + int64_t entry_padding; + int64_t entry_bytes_unconsumed; + int64_t realsize; ++ int sparse_allowed; + struct sparse_block *sparse_list; + struct sparse_block *sparse_last; + int64_t sparse_offset; +@@ -1216,6 +1217,14 @@ + * sparse information in the extended area. + */ + /* FALLTHROUGH */ ++ case '0': ++ /* ++ * Enable sparse file "read" support only for regular ++ * files and explicit GNU sparse files. However, we ++ * don't allow non-standard file types to be sparse. ++ */ ++ tar->sparse_allowed = 1; ++ /* FALLTHROUGH */ + default: /* Regular file and non-standard types */ + /* + * Per POSIX: non-recognized types should always be +@@ -1675,6 +1684,14 @@ + #endif + switch (key[0]) { + case 'G': ++ /* Reject GNU.sparse.* headers on non-regular files. */ ++ if (strncmp(key, "GNU.sparse", 10) == 0 && ++ !tar->sparse_allowed) { ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Non-regular file cannot be sparse"); ++ return (ARCHIVE_FATAL); ++ } ++ + /* GNU "0.0" sparse pax format. */ + if (strcmp(key, "GNU.sparse.numblocks") == 0) { + tar->sparse_offset = -1; +--- contrib/libarchive/libarchive/archive_write_disk_acl.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_acl.c +@@ -131,6 +131,7 @@ + acl_entry_t acl_entry; + acl_permset_t acl_permset; + acl_flagset_t acl_flagset; ++ int r; + int ret; + int ae_type, ae_permset, ae_tag, ae_id; + uid_t ae_uid; +@@ -144,9 +145,19 @@ + if (entries == 0) + return (ARCHIVE_OK); + acl = acl_init(entries); ++ if (acl == (acl_t)NULL) { ++ archive_set_error(a, errno, ++ "Failed to initialize ACL working storage"); ++ return (ARCHIVE_FAILED); ++ } + while (archive_acl_next(a, abstract_acl, ae_requested_type, &ae_type, + &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) { +- acl_create_entry(&acl, &acl_entry); ++ if (acl_create_entry(&acl, &acl_entry) != 0) { ++ archive_set_error(a, errno, ++ "Failed to create a new ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + switch (ae_tag) { + case ARCHIVE_ENTRY_ACL_USER: +@@ -175,47 +186,95 @@ + acl_set_tag_type(acl_entry, ACL_EVERYONE); + break; + default: +- /* XXX */ +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL tag"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + ++ r = 0; + switch (ae_type) { + case ARCHIVE_ENTRY_ACL_TYPE_ALLOW: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); + break; + case ARCHIVE_ENTRY_ACL_TYPE_DENY: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); + break; + case ARCHIVE_ENTRY_ACL_TYPE_AUDIT: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ALARM: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ACCESS: + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + // These don't translate directly into the system ACL. + break; + default: +- // XXX error handling here. +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (r != 0) { ++ archive_set_error(a, errno, ++ "Failed to set ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + +- acl_get_permset(acl_entry, &acl_permset); +- acl_clear_perms(acl_permset); ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get ACL permission set"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_perms(acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear ACL permissions"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + if (ae_permset & acl_perm_map[i].archive_perm) +- acl_add_perm(acl_permset, +- acl_perm_map[i].platform_perm); ++ if (acl_add_perm(acl_permset, ++ acl_perm_map[i].platform_perm) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add ACL permission"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + } + + acl_get_flagset_np(acl_entry, &acl_flagset); +- acl_clear_flags_np(acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (ae_permset & acl_inherit_map[i].archive_inherit) +- acl_add_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit); ++ if (acl_type == ACL_TYPE_NFS4) { ++ /* ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get flagset from an NFSv4 ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_flags_np(acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear flags from an NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ if (ae_permset & acl_inherit_map[i].archive_inherit) { ++ if (acl_add_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add flag to NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ } ++ } + } + } + +@@ -243,6 +302,7 @@ + ret = ARCHIVE_WARN; + } + #endif ++exit_free: + acl_free(acl); + return (ret); + } +--- contrib/libarchive/libarchive/archive_write_disk_posix.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_posix.c +@@ -140,7 +140,17 @@ + #define O_BINARY 0 + #endif + #ifndef O_CLOEXEC +-#define O_CLOEXEC 0 ++#define O_CLOEXEC 0 ++#endif ++ ++/* Ignore non-int O_NOFOLLOW constant. */ ++/* gnulib's fcntl.h does this on AIX, but it seems practical everywhere */ ++#if defined O_NOFOLLOW && !(INT_MIN <= O_NOFOLLOW && O_NOFOLLOW <= INT_MAX) ++#undef O_NOFOLLOW ++#endif ++ ++#ifndef O_NOFOLLOW ++#define O_NOFOLLOW 0 + #endif + + struct fixup_entry { +@@ -326,12 +336,14 @@ + + #define HFS_BLOCKS(s) ((s) >> 12) + ++static int check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int check_symlinks(struct archive_write_disk *); + static int create_filesystem_object(struct archive_write_disk *); + static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); + #if defined(HAVE_FCHDIR) && defined(PATH_MAX) + static void edit_deep_directories(struct archive_write_disk *ad); + #endif ++static int cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int cleanup_pathname(struct archive_write_disk *); + static int create_dir(struct archive_write_disk *, char *); + static int create_parent_dir(struct archive_write_disk *, char *); +@@ -1791,7 +1803,7 @@ + char *tail = a->name; + + /* If path is short, avoid the open() below. */ +- if (strlen(tail) <= PATH_MAX) ++ if (strlen(tail) < PATH_MAX) + return; + + /* Try to record our starting dir. */ +@@ -1801,7 +1813,7 @@ + return; + + /* As long as the path is too long... */ +- while (strlen(tail) > PATH_MAX) { ++ while (strlen(tail) >= PATH_MAX) { + /* Locate a dir prefix shorter than PATH_MAX. */ + tail += PATH_MAX - 8; + while (tail > a->name && *tail != '/') +@@ -1996,6 +2008,10 @@ + const char *linkname; + mode_t final_mode, mode; + int r; ++ /* these for check_symlinks_fsobj */ ++ char *linkname_copy; /* non-const copy of linkname */ ++ struct archive_string error_string; ++ int error_number; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ +@@ -2004,6 +2020,27 @@ + #if !HAVE_LINK + return (EPERM); + #else ++ archive_string_init(&error_string); ++ linkname_copy = strdup(linkname); ++ if (linkname_copy == NULL) { ++ return (EPERM); ++ } ++ /* TODO: consider using the cleaned-up path as the link target? */ ++ r = cleanup_pathname_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ r = check_symlinks_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ free(linkname_copy); + r = link(linkname, a->name) ? errno : 0; + /* + * New cpio and pax formats allow hardlink entries +@@ -2022,7 +2059,7 @@ + a->deferred = 0; + } else if (r == 0 && a->filesize > 0) { + a->fd = open(a->name, +- O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC); ++ O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC | O_NOFOLLOW); + __archive_ensure_cloexec_flag(a->fd); + if (a->fd < 0) + r = errno; +@@ -2332,110 +2369,233 @@ + return (a->current_fixup); + } + +-/* TODO: Make this work. */ +-/* +- * TODO: The deep-directory support bypasses this; disable deep directory +- * support if we're doing symlink checks. +- */ + /* + * TODO: Someday, integrate this with the deep dir support; they both + * 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. ++ */ + static int +-check_symlinks(struct archive_write_disk *a) ++check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + #if !defined(HAVE_LSTAT) + /* Platform doesn't have lstat, so we can't look for symlinks. */ +- (void)a; /* UNUSED */ ++ (void)path; /* UNUSED */ ++ (void)error_number; /* UNUSED */ ++ (void)error_string; /* UNUSED */ ++ (void)flags; /* UNUSED */ + return (ARCHIVE_OK); + #else +- char *pn; ++ int res = ARCHIVE_OK; ++ char *tail; ++ char *head; ++ int last; + char c; + int r; + struct stat st; ++ int restore_pwd; ++ ++ /* Nothing to do here if name is empty */ ++ if(path[0] == '\0') ++ return (ARCHIVE_OK); + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. ++ * ++ * Walk the filename in chunks separated by '/'. For each segment: ++ * - if it doesn't exist, continue ++ * - if it's symlink, abort or remove it ++ * - if it's a directory and it's not the last chunk, cd into it ++ * As we go: ++ * head points to the current (relative) path ++ * tail points to the temporary \0 terminating the segment we're currently examining ++ * 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) ++ return (ARCHIVE_FATAL); ++ head = path; ++ tail = path; ++ last = 0; ++ /* TODO: reintroduce a safe cache here? */ ++ /* Skip the root directory if the path is absolute. */ ++ if(tail == path && tail[0] == '/') ++ ++tail; ++ /* Keep going until we've checked the entire name. ++ * head, tail, path all alias the same string, which is ++ * temporarily zeroed at tail, so be careful restoring the ++ * stashed (c=tail[0]) for error messages. ++ * Exiting the loop with break is okay; continue is not. + */ +- /* Whatever we checked last time doesn't need to be re-checked. */ +- pn = a->name; +- if (archive_strlen(&(a->path_safe)) > 0) { +- char *p = a->path_safe.s; +- while ((*pn != '\0') && (*p == *pn)) +- ++p, ++pn; +- } +- c = pn[0]; +- /* Keep going until we've checked the entire name. */ +- while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { ++ while (!last) { ++ /* Skip the separator we just consumed, plus any adjacent ones */ ++ while (*tail == '/') ++ ++tail; + /* Skip the next path element. */ +- while (*pn != '\0' && *pn != '/') +- ++pn; +- c = pn[0]; +- pn[0] = '\0'; ++ while (*tail != '\0' && *tail != '/') ++ ++tail; ++ /* is this the last path component? */ ++ last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0'); ++ /* temporarily truncate the string here */ ++ c = tail[0]; ++ tail[0] = '\0'; + /* Check that we haven't hit a symlink. */ +- r = lstat(a->name, &st); ++ r = lstat(head, &st); + if (r != 0) { ++ tail[0] = c; + /* We've hit a dir that doesn't exist; stop now. */ +- if (errno == ENOENT) ++ if (errno == ENOENT) { + break; ++ } else { ++ /* Treat any other error as fatal - best to be paranoid here ++ * Note: This effectively disables deep directory ++ * support when security checks are enabled. ++ * Otherwise, very long pathnames that trigger ++ * an error here could evade the sandbox. ++ * TODO: We could do better, but it would probably ++ * require merging the symlink checks with the ++ * deep-directory editing. */ ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not stat %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; ++ } ++ } else if (S_ISDIR(st.st_mode)) { ++ if (!last) { ++ if (chdir(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not chdir %s", ++ path); ++ res = (ARCHIVE_FATAL); ++ break; ++ } ++ /* Our view is now from inside this dir: */ ++ head = tail + 1; ++ } + } else if (S_ISLNK(st.st_mode)) { +- if (c == '\0') { ++ if (last) { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ +- if (unlink(a->name)) { +- archive_set_error(&a->archive, errno, +- "Could not remove symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head)) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not remove symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ +- if (!S_ISLNK(a->mode)) { +- archive_set_error(&a->archive, 0, +- "Removing symlink %s", +- a->name); ++ tail[0] = c; ++ /* FIXME: not sure how important this is to restore ++ if (!S_ISLNK(path)) { ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Removing symlink %s", ++ path); + } ++ */ + /* Symlink gone. No more problem! */ +- pn[0] = c; +- return (0); +- } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { ++ res = ARCHIVE_OK; ++ break; ++ } else if (flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ +- if (unlink(a->name) != 0) { +- archive_set_error(&a->archive, 0, +- "Cannot remove intervening symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot remove intervening symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; ++ tail[0] = c; + } else { +- archive_set_error(&a->archive, 0, +- "Cannot extract through symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot extract through symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } + } ++ /* be sure to always maintain this */ ++ tail[0] = c; ++ if (tail[0] != '\0') ++ tail++; /* Advance to the next segment. */ + } +- pn[0] = c; +- /* We've checked and/or cleaned the whole path, so remember it. */ +- archive_strcpy(&a->path_safe, a->name); +- return (ARCHIVE_OK); ++ /* Catches loop exits via break */ ++ tail[0] = c; ++#ifdef HAVE_FCHDIR ++ /* If we changed directory above, restore it here. */ ++ if (restore_pwd >= 0) { ++ r = fchdir(restore_pwd); ++ if (r != 0) { ++ if(error_number) *error_number = errno; ++ if(error_string) ++ archive_string_sprintf(error_string, ++ "chdir() failure"); ++ } ++ close(restore_pwd); ++ restore_pwd = -1; ++ if (r != 0) { ++ res = (ARCHIVE_FATAL); ++ } ++ } ++#endif ++ /* TODO: reintroduce a safe cache here? */ ++ return res; + #endif + } + ++/* ++ * Check a->name for symlinks, returning ARCHIVE_OK if its clean, otherwise ++ * calls archive_set_error and returns ARCHIVE_{FATAL,FAILED} ++ */ ++static int ++check_symlinks(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = check_symlinks_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ a->pst = NULL; /* to be safe */ ++ return rc; ++} ++ ++ + #if defined(__CYGWIN__) + /* + * 1. Convert a path separator from '\' to '/' . +@@ -2509,15 +2669,17 @@ + * is set) if the path is absolute. + */ + static int +-cleanup_pathname(struct archive_write_disk *a) ++cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + char *dest, *src; + char separator = '\0'; + +- dest = src = a->name; ++ dest = src = path; + if (*src == '\0') { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Invalid empty pathname"); ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Invalid empty pathname"); + return (ARCHIVE_FAILED); + } + +@@ -2526,9 +2688,11 @@ + #endif + /* Skip leading '/'. */ + if (*src == '/') { +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Path is absolute"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path is absolute"); + return (ARCHIVE_FAILED); + } + +@@ -2555,10 +2719,11 @@ + } else if (src[1] == '.') { + if (src[2] == '/' || src[2] == '\0') { + /* Conditionally warn about '..' */ +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { +- archive_set_error(&a->archive, +- ARCHIVE_ERRNO_MISC, +- "Path contains '..'"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path contains '..'"); + return (ARCHIVE_FAILED); + } + } +@@ -2589,7 +2754,7 @@ + * We've just copied zero or more path elements, not including the + * final '/'. + */ +- if (dest == a->name) { ++ if (dest == path) { + /* + * Nothing got copied. The path must have been something + * like '.' or '/' or './' or '/././././/./'. +@@ -2604,6 +2769,21 @@ + return (ARCHIVE_OK); + } + ++static int ++cleanup_pathname(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = cleanup_pathname_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ return rc; ++} ++ + /* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. +--- contrib/libarchive/libarchive/test/main.c.orig ++++ contrib/libarchive/libarchive/test/main.c +@@ -1396,6 +1396,31 @@ + return (0); + } + ++/* Verify mode of 'pathname'. */ ++int ++assertion_file_mode(const char *file, int line, const char *pathname, int expected_mode) ++{ ++ int mode; ++ int r; ++ ++ assertion_count(file, line); ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ failure_start(file, line, "assertFileMode not yet implemented for Windows"); ++#else ++ { ++ struct stat st; ++ r = lstat(pathname, &st); ++ mode = (int)(st.st_mode & 0777); ++ } ++ if (r == 0 && mode == expected_mode) ++ return (1); ++ failure_start(file, line, "File %s has mode %o, expected %o", ++ pathname, mode, expected_mode); ++#endif ++ failure_finish(NULL); ++ return (0); ++} ++ + /* Assert that 'pathname' is a dir. If mode >= 0, verify that too. */ + int + assertion_is_dir(const char *file, int line, const char *pathname, int mode) +--- contrib/libarchive/libarchive/test/test.h.orig ++++ contrib/libarchive/libarchive/test/test.h +@@ -176,6 +176,8 @@ + assertion_file_nlinks(__FILE__, __LINE__, pathname, nlinks) + #define assertFileSize(pathname, size) \ + assertion_file_size(__FILE__, __LINE__, pathname, size) ++#define assertFileMode(pathname, mode) \ ++ assertion_file_mode(__FILE__, __LINE__, pathname, mode) + #define assertTextFileContents(text, pathname) \ + assertion_text_file_contents(__FILE__, __LINE__, text, pathname) + #define assertFileContainsLinesAnyOrder(pathname, lines) \ +@@ -239,6 +241,7 @@ + int assertion_file_nlinks(const char *, int, const char *, int); + int assertion_file_not_exists(const char *, int, const char *); + int assertion_file_size(const char *, int, const char *, long); ++int assertion_file_mode(const char *, int, const char *, int); + int assertion_is_dir(const char *, int, const char *, int); + int assertion_is_hardlink(const char *, int, const char *, const char *); + int assertion_is_not_hardlink(const char *, int, const char *, const char *); +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure744.c +@@ -0,0 +1,95 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #744 describes a bug in the sandboxing code that ++ * causes very long pathnames to not get checked for symlinks. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure744) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ size_t buff_size = 8192; ++ char *buff = malloc(buff_size); ++ char *p = buff; ++ int n = 0; ++ int t; ++ ++ assert(buff != NULL); ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ while (p + 500 < buff + buff_size) { ++ memset(p, 'x', 100); ++ p += 100; ++ p[0] = '\0'; ++ ++ buff[0] = ((n / 1000) % 10) + '0'; ++ buff[1] = ((n / 100) % 10)+ '0'; ++ buff[2] = ((n / 10) % 10)+ '0'; ++ buff[3] = ((n / 1) % 10)+ '0'; ++ buff[4] = '_'; ++ ++n; ++ ++ /* Create a symlink pointing to the testworkdir */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFREG | 0777); ++ archive_entry_copy_symlink(ae, testworkdir); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ *p++ = '/'; ++ sprintf(p, "target%d", n); ++ ++ /* Try to create a file through the symlink, should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ ++ t = archive_write_header(a, ae); ++ archive_entry_free(ae); ++ failure("Attempt to create target%d via %d-character symlink should have failed", n, (int)strlen(buff)); ++ if(!assertEqualInt(ARCHIVE_FAILED, t)) { ++ break; ++ } ++ } ++ archive_free(a); ++ free(buff); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure745.c +@@ -0,0 +1,79 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #745 describes a bug in the sandboxing code that ++ * allows one to use a symlink to edit the permissions on a file or ++ * directory outside of the sandbox. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure745) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* The target dir: The one we're going to try to change permission on */ ++ assertMakeDir("target", 0700); ++ ++ /* The sandbox dir we're going to run inside of. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create a symlink pointing to the target directory */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Try to alter the target dir through the symlink; this should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Permission of target dir should not have changed. */ ++ assertFileMode("../target", 0700); ++ ++ assert(0 == archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure746.c +@@ -0,0 +1,129 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #746 describes a problem in which hardlink targets are ++ * not adequately checked and can be used to modify entries outside of ++ * the sandbox. ++ */ ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NODOTDOT disallows '..' in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746a) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NODOTDOT); ++ ++ /* Attempt to hardlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "../target/foo"); ++ assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualInt(ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++#endif ++} ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NOSYMLINK disallows symlinks in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746b) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* Create a symlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "symlink"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Attempt to hardlink to the target directory via the symlink. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "symlink/foo"); ++ assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++ ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- lib/libarchive/test/Makefile.orig ++++ lib/libarchive/test/Makefile +@@ -175,6 +175,9 @@ + test_write_disk_no_hfs_compression.c \ + test_write_disk_perms.c \ + test_write_disk_secure.c \ ++ test_write_disk_secure744.c \ ++ test_write_disk_secure745.c \ ++ test_write_disk_secure746.c \ + test_write_disk_sparse.c \ + test_write_disk_symlink.c \ + test_write_disk_times.c \ Index: user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:29.bspatch =================================================================== --- user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:29.bspatch (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:29.bspatch (revision 307229) @@ -0,0 +1,325 @@ +--- usr.bin/bsdiff/bspatch/bspatch.c.orig ++++ usr.bin/bsdiff/bspatch/bspatch.c +@@ -27,56 +27,133 @@ + #include + __FBSDID("$FreeBSD$"); + ++#if defined(__FreeBSD__) ++#include ++#if __FreeBSD_version >= 1001511 ++#include ++#define HAVE_CAPSICUM ++#endif ++#endif ++ + #include +-#include ++#include ++#include ++#include ++#include ++#include ++#include + #include ++#include + #include +-#include + #include +-#include + + #ifndef O_BINARY + #define O_BINARY 0 + #endif ++#define HEADER_SIZE 32 ++ ++static char *newfile; ++static int dirfd = -1; ++ ++static void ++exit_cleanup(void) ++{ ++ ++ if (dirfd != -1 && newfile != NULL) ++ if (unlinkat(dirfd, newfile, 0)) ++ warn("unlinkat"); ++} + + static off_t offtin(u_char *buf) + { + off_t y; + +- y=buf[7]&0x7F; +- y=y*256;y+=buf[6]; +- y=y*256;y+=buf[5]; +- y=y*256;y+=buf[4]; +- y=y*256;y+=buf[3]; +- y=y*256;y+=buf[2]; +- y=y*256;y+=buf[1]; +- y=y*256;y+=buf[0]; ++ y = buf[7] & 0x7F; ++ y = y * 256; y += buf[6]; ++ y = y * 256; y += buf[5]; ++ y = y * 256; y += buf[4]; ++ y = y * 256; y += buf[3]; ++ y = y * 256; y += buf[2]; ++ y = y * 256; y += buf[1]; ++ y = y * 256; y += buf[0]; + +- if(buf[7]&0x80) y=-y; ++ if (buf[7] & 0x80) ++ y = -y; + +- return y; ++ return (y); + } + +-int main(int argc,char * argv[]) ++int main(int argc, char *argv[]) + { +- FILE * f, * cpf, * dpf, * epf; +- BZFILE * cpfbz2, * dpfbz2, * epfbz2; ++ FILE *f, *cpf, *dpf, *epf; ++ BZFILE *cpfbz2, *dpfbz2, *epfbz2; ++ char *directory, *namebuf; + int cbz2err, dbz2err, ebz2err; +- int fd; +- ssize_t oldsize,newsize; +- ssize_t bzctrllen,bzdatalen; +- u_char header[32],buf[8]; ++ int newfd, oldfd; ++ off_t oldsize, newsize; ++ off_t bzctrllen, bzdatalen; ++ u_char header[HEADER_SIZE], buf[8]; + u_char *old, *new; +- off_t oldpos,newpos; ++ off_t oldpos, newpos; + off_t ctrl[3]; +- off_t lenread; +- off_t i; ++ off_t i, lenread, offset; ++#ifdef HAVE_CAPSICUM ++ cap_rights_t rights_dir, rights_ro, rights_wr; ++#endif + + if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]); + + /* Open patch file */ + if ((f = fopen(argv[3], "rb")) == NULL) + err(1, "fopen(%s)", argv[3]); ++ /* Open patch file for control block */ ++ if ((cpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for diff block */ ++ if ((dpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for extra block */ ++ if ((epf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open oldfile */ ++ if ((oldfd = open(argv[1], O_RDONLY | O_BINARY, 0)) < 0) ++ err(1, "open(%s)", argv[1]); ++ /* open directory where we'll write newfile */ ++ if ((namebuf = strdup(argv[2])) == NULL || ++ (directory = dirname(namebuf)) == NULL || ++ (dirfd = open(directory, O_DIRECTORY)) < 0) ++ err(1, "open %s", argv[2]); ++ free(namebuf); ++ if ((newfile = basename(argv[2])) == NULL) ++ err(1, "basename"); ++ /* open newfile */ ++ if ((newfd = openat(dirfd, newfile, ++ O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) ++ err(1, "open(%s)", argv[2]); ++ atexit(exit_cleanup); ++ ++#ifdef HAVE_CAPSICUM ++ if (cap_enter() < 0) { ++ /* Failed to sandbox, fatal if CAPABILITY_MODE enabled */ ++ if (errno != ENOSYS) ++ err(1, "failed to enter security sandbox"); ++ } else { ++ /* Capsicum Available */ ++ cap_rights_init(&rights_ro, CAP_READ, CAP_FSTAT, CAP_SEEK); ++ cap_rights_init(&rights_wr, CAP_WRITE); ++ cap_rights_init(&rights_dir, CAP_UNLINKAT); ++ ++ if (cap_rights_limit(fileno(f), &rights_ro) < 0 || ++ cap_rights_limit(fileno(cpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(dpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(epf), &rights_ro) < 0 || ++ cap_rights_limit(oldfd, &rights_ro) < 0 || ++ cap_rights_limit(newfd, &rights_wr) < 0 || ++ cap_rights_limit(dirfd, &rights_dir) < 0) ++ err(1, "cap_rights_limit() failed, could not restrict" ++ " capabilities"); ++ } ++#endif + + /* + File format: +@@ -93,99 +170,99 @@ + */ + + /* Read header */ +- if (fread(header, 1, 32, f) < 32) { ++ if (fread(header, 1, HEADER_SIZE, f) < HEADER_SIZE) { + if (feof(f)) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + err(1, "fread(%s)", argv[3]); + } + + /* Check for appropriate magic */ + if (memcmp(header, "BSDIFF40", 8) != 0) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Read lengths from header */ +- bzctrllen=offtin(header+8); +- bzdatalen=offtin(header+16); +- newsize=offtin(header+24); +- if((bzctrllen<0) || (bzdatalen<0) || (newsize<0)) +- errx(1,"Corrupt patch\n"); ++ bzctrllen = offtin(header + 8); ++ bzdatalen = offtin(header + 16); ++ newsize = offtin(header + 24); ++ if (bzctrllen < 0 || bzctrllen > OFF_MAX - HEADER_SIZE || ++ bzdatalen < 0 || bzctrllen + HEADER_SIZE > OFF_MAX - bzdatalen || ++ newsize < 0 || newsize > SSIZE_MAX) ++ errx(1, "Corrupt patch"); + + /* Close patch file and re-open it via libbzip2 at the right places */ + if (fclose(f)) + err(1, "fclose(%s)", argv[3]); +- if ((cpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(cpf, 32, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)32); ++ offset = HEADER_SIZE; ++ if (fseeko(cpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); +- if ((dpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen)); ++ offset += bzctrllen; ++ if (fseeko(dpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); +- if ((epf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen + bzdatalen)); ++ offset += bzdatalen; ++ if (fseeko(epf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); + +- if(((fd=open(argv[1],O_RDONLY|O_BINARY,0))<0) || +- ((oldsize=lseek(fd,0,SEEK_END))==-1) || +- ((old=malloc(oldsize+1))==NULL) || +- (lseek(fd,0,SEEK_SET)!=0) || +- (read(fd,old,oldsize)!=oldsize) || +- (close(fd)==-1)) err(1,"%s",argv[1]); +- if((new=malloc(newsize+1))==NULL) err(1,NULL); ++ if ((oldsize = lseek(oldfd, 0, SEEK_END)) == -1 || ++ oldsize > SSIZE_MAX || ++ (old = malloc(oldsize)) == NULL || ++ lseek(oldfd, 0, SEEK_SET) != 0 || ++ read(oldfd, old, oldsize) != oldsize || ++ close(oldfd) == -1) ++ err(1, "%s", argv[1]); ++ if ((new = malloc(newsize)) == NULL) ++ err(1, NULL); + +- oldpos=0;newpos=0; +- while(newpos INT_MAX || ++ ctrl[1] < 0 || ctrl[1] > INT_MAX) ++ errx(1, "Corrupt patch"); + + /* Sanity-check */ +- if(newpos+ctrl[0]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[0] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read diff string */ + lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); + if ((lenread < ctrl[0]) || + ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Add old data to diff string */ +- for(i=0;i=0) && (oldpos+i= 0) && (oldpos + i < oldsize)) ++ new[newpos + i] += old[oldpos + i]; + + /* Adjust pointers */ +- newpos+=ctrl[0]; +- oldpos+=ctrl[0]; ++ newpos += ctrl[0]; ++ oldpos += ctrl[0]; + + /* Sanity-check */ +- if(newpos+ctrl[1]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[1] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read extra string */ + lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); + if ((lenread < ctrl[1]) || + ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Adjust pointers */ + newpos+=ctrl[1]; +@@ -200,12 +277,13 @@ + err(1, "fclose(%s)", argv[3]); + + /* Write the new file */ +- if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))<0) || +- (write(fd,new,newsize)!=newsize) || (close(fd)==-1)) +- err(1,"%s",argv[2]); ++ if (write(newfd, new, newsize) != newsize || close(newfd) == -1) ++ err(1, "%s", argv[2]); ++ /* Disable atexit cleanup */ ++ newfile = NULL; + + free(new); + free(old); + +- return 0; ++ return (0); + } Index: user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:30.portsnap =================================================================== --- user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:30.portsnap (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:30.portsnap (revision 307229) @@ -0,0 +1,49 @@ +--- usr.sbin/portsnap/portsnap/portsnap.sh.orig ++++ usr.sbin/portsnap/portsnap/portsnap.sh +@@ -646,7 +646,7 @@ + # Verify a list of files + fetch_snapshot_verify() { + while read F; do +- if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then ++ if [ "`gunzip -c < snap/${F}.gz | ${SHA256} -q`" != ${F} ]; then + echo "snapshot corrupt." + return 1 + fi +@@ -681,11 +681,18 @@ + cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1 + # Extract the index + rm -f INDEX.new +- gunzip -c snap/`look INDEX tINDEX.new | ++ gunzip -c < snap/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + # Verify the snapshot contents + cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1 ++ cut -f 2 -d '|' tINDEX.new INDEX.new | sort -u > files.expected ++ find snap -mindepth 1 | sed -E 's^snap/(.*)\.gz^\1^' | sort > files.snap ++ if ! cmp -s files.expected files.snap; then ++ echo "unexpected files in snapshot." ++ return 1 ++ fi ++ rm files.expected files.snap + echo "done." + + # Move files into their proper locations +@@ -777,7 +784,7 @@ + + # Extract the index + echo -n "Extracting index... " 1>${QUIETREDIR} +- gunzip -c files/`look INDEX tINDEX.new | ++ gunzip -c < files/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + +@@ -897,7 +904,7 @@ + echo -n "$1 not provided by portsnap server; " + echo "$2 not being generated." + else +- gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | ++ gunzip -c < "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | + cut -f 2 -d '|'`.gz" | + cat - ${LOCALDESC} | + ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2 Index: user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:31.libarchive =================================================================== --- user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:31.libarchive (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.2-RELEASE/23-SA-16:31.libarchive (revision 307229) @@ -0,0 +1,1270 @@ +--- contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c.orig ++++ contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c +@@ -409,9 +409,7 @@ + { + const char *accpath; + acl_t acl; +-#if HAVE_ACL_IS_TRIVIAL_NP + int r; +-#endif + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) +@@ -443,9 +441,13 @@ + } + #endif + if (acl != NULL) { +- translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); ++ r = translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); + acl_free(acl); +- return (ARCHIVE_OK); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate NFSv4 ACLs: %s", accpath); ++ } ++ return (r); + } + + /* Retrieve access ACL from file. */ +@@ -464,18 +466,29 @@ + else + acl = acl_get_file(accpath, ACL_TYPE_ACCESS); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate access ACLs: %s", accpath); ++ return (r); ++ } + } + + /* Only directories can have default ACLs. */ + if (S_ISDIR(archive_entry_mode(entry))) { + acl = acl_get_file(accpath, ACL_TYPE_DEFAULT); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate default ACLs: %s", ++ accpath); ++ return (r); ++ } + } + } + return (ARCHIVE_OK); +@@ -536,7 +549,11 @@ + // FreeBSD "brands" ACLs as POSIX.1e or NFSv4 + // Make sure the "brand" on this ACL is consistent + // with the default_entry_acl_type bits provided. +- acl_get_brand_np(acl, &brand); ++ if (acl_get_brand_np(acl, &brand) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to read ACL brand"); ++ return (ARCHIVE_WARN); ++ } + switch (brand) { + case ACL_BRAND_POSIX: + switch (default_entry_acl_type) { +@@ -544,30 +561,42 @@ + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for POSIX.1e ACL"); ++ return (ARCHIVE_WARN); + } + break; + case ACL_BRAND_NFS4: + if (default_entry_acl_type & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) { +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for NFSv4 ACL"); ++ return (ARCHIVE_WARN); + } + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL brand"); ++ return (ARCHIVE_WARN); + break; + } + + + s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get first ACL entry"); ++ return (ARCHIVE_WARN); ++ } + while (s == 1) { + ae_id = -1; + ae_name = NULL; + ae_perm = 0; + +- acl_get_tag_type(acl_entry, &acl_tag); ++ if (acl_get_tag_type(acl_entry, &acl_tag) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL tag type"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_tag) { + case ACL_USER: + ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry); +@@ -600,12 +629,17 @@ + continue; + } + +- // XXX acl type maps to allow/deny/audit/YYYY bits +- // XXX acl_get_entry_type_np on FreeBSD returns EINVAL for +- // non-NFSv4 ACLs ++ // XXX acl_type maps to allow/deny/audit/YYYY bits + entry_acl_type = default_entry_acl_type; +- r = acl_get_entry_type_np(acl_entry, &acl_type); +- if (r == 0) { ++ if (default_entry_acl_type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) { ++ /* ++ * acl_get_entry_type_np() falis with non-NFSv4 ACLs ++ */ ++ if (acl_get_entry_type_np(acl_entry, &acl_type) != 0) { ++ archive_set_error(&a->archive, errno, "Failed " ++ "to get ACL type from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_type) { + case ACL_ENTRY_TYPE_ALLOW: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALLOW; +@@ -619,28 +653,52 @@ + case ACL_ENTRY_TYPE_ALARM: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALARM; + break; ++ default: ++ archive_set_error(&a->archive, errno, ++ "Invalid NFSv4 ACL entry type"); ++ return (ARCHIVE_WARN); + } +- } +- +- /* +- * Libarchive stores "flag" (NFSv4 inheritance bits) +- * in the ae_perm bitmap. +- */ +- acl_get_flagset_np(acl_entry, &acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (acl_get_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit)) +- ae_perm |= acl_inherit_map[i].archive_inherit; + +- } ++ /* ++ * Libarchive stores "flag" (NFSv4 inheritance bits) ++ * in the ae_perm bitmap. ++ * ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get flagset from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ r = acl_get_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check flag in a NFSv4 " ++ "ACL flagset"); ++ return (ARCHIVE_WARN); ++ } else if (r) ++ ae_perm |= acl_inherit_map[i].archive_inherit; ++ } ++ } + +- acl_get_permset(acl_entry, &acl_permset); +- for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL permission set"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + /* + * acl_get_perm() is spelled differently on different + * platforms; see above. + */ +- if (ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm)) ++ r = ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check permission in an ACL permission set"); ++ return (ARCHIVE_WARN); ++ } else if (r) + ae_perm |= acl_perm_map[i].archive_perm; + } + +@@ -649,6 +707,11 @@ + ae_id, ae_name); + + s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get next ACL entry"); ++ return (ARCHIVE_WARN); ++ } + } + return (ARCHIVE_OK); + } +--- contrib/libarchive/libarchive/archive_read_support_format_tar.c.orig ++++ contrib/libarchive/libarchive/archive_read_support_format_tar.c +@@ -136,6 +136,7 @@ + int64_t entry_padding; + int64_t entry_bytes_unconsumed; + int64_t realsize; ++ int sparse_allowed; + struct sparse_block *sparse_list; + struct sparse_block *sparse_last; + int64_t sparse_offset; +@@ -1226,6 +1227,14 @@ + * sparse information in the extended area. + */ + /* FALLTHROUGH */ ++ case '0': ++ /* ++ * Enable sparse file "read" support only for regular ++ * files and explicit GNU sparse files. However, we ++ * don't allow non-standard file types to be sparse. ++ */ ++ tar->sparse_allowed = 1; ++ /* FALLTHROUGH */ + default: /* Regular file and non-standard types */ + /* + * Per POSIX: non-recognized types should always be +@@ -1685,6 +1694,14 @@ + #endif + switch (key[0]) { + case 'G': ++ /* Reject GNU.sparse.* headers on non-regular files. */ ++ if (strncmp(key, "GNU.sparse", 10) == 0 && ++ !tar->sparse_allowed) { ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Non-regular file cannot be sparse"); ++ return (ARCHIVE_FATAL); ++ } ++ + /* GNU "0.0" sparse pax format. */ + if (strcmp(key, "GNU.sparse.numblocks") == 0) { + tar->sparse_offset = -1; +--- contrib/libarchive/libarchive/archive_write_disk_acl.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_acl.c +@@ -131,6 +131,7 @@ + acl_entry_t acl_entry; + acl_permset_t acl_permset; + acl_flagset_t acl_flagset; ++ int r; + int ret; + int ae_type, ae_permset, ae_tag, ae_id; + uid_t ae_uid; +@@ -144,9 +145,19 @@ + if (entries == 0) + return (ARCHIVE_OK); + acl = acl_init(entries); ++ if (acl == (acl_t)NULL) { ++ archive_set_error(a, errno, ++ "Failed to initialize ACL working storage"); ++ return (ARCHIVE_FAILED); ++ } + while (archive_acl_next(a, abstract_acl, ae_requested_type, &ae_type, + &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) { +- acl_create_entry(&acl, &acl_entry); ++ if (acl_create_entry(&acl, &acl_entry) != 0) { ++ archive_set_error(a, errno, ++ "Failed to create a new ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + switch (ae_tag) { + case ARCHIVE_ENTRY_ACL_USER: +@@ -175,47 +186,95 @@ + acl_set_tag_type(acl_entry, ACL_EVERYONE); + break; + default: +- /* XXX */ +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL tag"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + ++ r = 0; + switch (ae_type) { + case ARCHIVE_ENTRY_ACL_TYPE_ALLOW: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); + break; + case ARCHIVE_ENTRY_ACL_TYPE_DENY: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); + break; + case ARCHIVE_ENTRY_ACL_TYPE_AUDIT: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ALARM: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ACCESS: + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + // These don't translate directly into the system ACL. + break; + default: +- // XXX error handling here. +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (r != 0) { ++ archive_set_error(a, errno, ++ "Failed to set ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + +- acl_get_permset(acl_entry, &acl_permset); +- acl_clear_perms(acl_permset); ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get ACL permission set"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_perms(acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear ACL permissions"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + if (ae_permset & acl_perm_map[i].archive_perm) +- acl_add_perm(acl_permset, +- acl_perm_map[i].platform_perm); ++ if (acl_add_perm(acl_permset, ++ acl_perm_map[i].platform_perm) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add ACL permission"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + } + + acl_get_flagset_np(acl_entry, &acl_flagset); +- acl_clear_flags_np(acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (ae_permset & acl_inherit_map[i].archive_inherit) +- acl_add_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit); ++ if (acl_type == ACL_TYPE_NFS4) { ++ /* ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get flagset from an NFSv4 ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_flags_np(acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear flags from an NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ if (ae_permset & acl_inherit_map[i].archive_inherit) { ++ if (acl_add_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add flag to NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ } ++ } + } + } + +@@ -243,6 +302,7 @@ + ret = ARCHIVE_WARN; + } + #endif ++exit_free: + acl_free(acl); + return (ret); + } +--- contrib/libarchive/libarchive/archive_write_disk_posix.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_posix.c +@@ -140,7 +140,17 @@ + #define O_BINARY 0 + #endif + #ifndef O_CLOEXEC +-#define O_CLOEXEC 0 ++#define O_CLOEXEC 0 ++#endif ++ ++/* Ignore non-int O_NOFOLLOW constant. */ ++/* gnulib's fcntl.h does this on AIX, but it seems practical everywhere */ ++#if defined O_NOFOLLOW && !(INT_MIN <= O_NOFOLLOW && O_NOFOLLOW <= INT_MAX) ++#undef O_NOFOLLOW ++#endif ++ ++#ifndef O_NOFOLLOW ++#define O_NOFOLLOW 0 + #endif + + struct fixup_entry { +@@ -326,12 +336,14 @@ + + #define HFS_BLOCKS(s) ((s) >> 12) + ++static int check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int check_symlinks(struct archive_write_disk *); + static int create_filesystem_object(struct archive_write_disk *); + static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); + #if defined(HAVE_FCHDIR) && defined(PATH_MAX) + static void edit_deep_directories(struct archive_write_disk *ad); + #endif ++static int cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int cleanup_pathname(struct archive_write_disk *); + static int create_dir(struct archive_write_disk *, char *); + static int create_parent_dir(struct archive_write_disk *, char *); +@@ -1791,7 +1803,7 @@ + char *tail = a->name; + + /* If path is short, avoid the open() below. */ +- if (strlen(tail) <= PATH_MAX) ++ if (strlen(tail) < PATH_MAX) + return; + + /* Try to record our starting dir. */ +@@ -1801,7 +1813,7 @@ + return; + + /* As long as the path is too long... */ +- while (strlen(tail) > PATH_MAX) { ++ while (strlen(tail) >= PATH_MAX) { + /* Locate a dir prefix shorter than PATH_MAX. */ + tail += PATH_MAX - 8; + while (tail > a->name && *tail != '/') +@@ -1996,6 +2008,10 @@ + const char *linkname; + mode_t final_mode, mode; + int r; ++ /* these for check_symlinks_fsobj */ ++ char *linkname_copy; /* non-const copy of linkname */ ++ struct archive_string error_string; ++ int error_number; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ +@@ -2004,6 +2020,27 @@ + #if !HAVE_LINK + return (EPERM); + #else ++ archive_string_init(&error_string); ++ linkname_copy = strdup(linkname); ++ if (linkname_copy == NULL) { ++ return (EPERM); ++ } ++ /* TODO: consider using the cleaned-up path as the link target? */ ++ r = cleanup_pathname_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ r = check_symlinks_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ free(linkname_copy); + r = link(linkname, a->name) ? errno : 0; + /* + * New cpio and pax formats allow hardlink entries +@@ -2022,7 +2059,7 @@ + a->deferred = 0; + } else if (r == 0 && a->filesize > 0) { + a->fd = open(a->name, +- O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC); ++ O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC | O_NOFOLLOW); + __archive_ensure_cloexec_flag(a->fd); + if (a->fd < 0) + r = errno; +@@ -2332,110 +2369,233 @@ + return (a->current_fixup); + } + +-/* TODO: Make this work. */ +-/* +- * TODO: The deep-directory support bypasses this; disable deep directory +- * support if we're doing symlink checks. +- */ + /* + * TODO: Someday, integrate this with the deep dir support; they both + * 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. ++ */ + static int +-check_symlinks(struct archive_write_disk *a) ++check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + #if !defined(HAVE_LSTAT) + /* Platform doesn't have lstat, so we can't look for symlinks. */ +- (void)a; /* UNUSED */ ++ (void)path; /* UNUSED */ ++ (void)error_number; /* UNUSED */ ++ (void)error_string; /* UNUSED */ ++ (void)flags; /* UNUSED */ + return (ARCHIVE_OK); + #else +- char *pn; ++ int res = ARCHIVE_OK; ++ char *tail; ++ char *head; ++ int last; + char c; + int r; + struct stat st; ++ int restore_pwd; ++ ++ /* Nothing to do here if name is empty */ ++ if(path[0] == '\0') ++ return (ARCHIVE_OK); + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. ++ * ++ * Walk the filename in chunks separated by '/'. For each segment: ++ * - if it doesn't exist, continue ++ * - if it's symlink, abort or remove it ++ * - if it's a directory and it's not the last chunk, cd into it ++ * As we go: ++ * head points to the current (relative) path ++ * tail points to the temporary \0 terminating the segment we're currently examining ++ * 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) ++ return (ARCHIVE_FATAL); ++ head = path; ++ tail = path; ++ last = 0; ++ /* TODO: reintroduce a safe cache here? */ ++ /* Skip the root directory if the path is absolute. */ ++ if(tail == path && tail[0] == '/') ++ ++tail; ++ /* Keep going until we've checked the entire name. ++ * head, tail, path all alias the same string, which is ++ * temporarily zeroed at tail, so be careful restoring the ++ * stashed (c=tail[0]) for error messages. ++ * Exiting the loop with break is okay; continue is not. + */ +- /* Whatever we checked last time doesn't need to be re-checked. */ +- pn = a->name; +- if (archive_strlen(&(a->path_safe)) > 0) { +- char *p = a->path_safe.s; +- while ((*pn != '\0') && (*p == *pn)) +- ++p, ++pn; +- } +- c = pn[0]; +- /* Keep going until we've checked the entire name. */ +- while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { ++ while (!last) { ++ /* Skip the separator we just consumed, plus any adjacent ones */ ++ while (*tail == '/') ++ ++tail; + /* Skip the next path element. */ +- while (*pn != '\0' && *pn != '/') +- ++pn; +- c = pn[0]; +- pn[0] = '\0'; ++ while (*tail != '\0' && *tail != '/') ++ ++tail; ++ /* is this the last path component? */ ++ last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0'); ++ /* temporarily truncate the string here */ ++ c = tail[0]; ++ tail[0] = '\0'; + /* Check that we haven't hit a symlink. */ +- r = lstat(a->name, &st); ++ r = lstat(head, &st); + if (r != 0) { ++ tail[0] = c; + /* We've hit a dir that doesn't exist; stop now. */ +- if (errno == ENOENT) ++ if (errno == ENOENT) { + break; ++ } else { ++ /* Treat any other error as fatal - best to be paranoid here ++ * Note: This effectively disables deep directory ++ * support when security checks are enabled. ++ * Otherwise, very long pathnames that trigger ++ * an error here could evade the sandbox. ++ * TODO: We could do better, but it would probably ++ * require merging the symlink checks with the ++ * deep-directory editing. */ ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not stat %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; ++ } ++ } else if (S_ISDIR(st.st_mode)) { ++ if (!last) { ++ if (chdir(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not chdir %s", ++ path); ++ res = (ARCHIVE_FATAL); ++ break; ++ } ++ /* Our view is now from inside this dir: */ ++ head = tail + 1; ++ } + } else if (S_ISLNK(st.st_mode)) { +- if (c == '\0') { ++ if (last) { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ +- if (unlink(a->name)) { +- archive_set_error(&a->archive, errno, +- "Could not remove symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head)) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not remove symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ +- if (!S_ISLNK(a->mode)) { +- archive_set_error(&a->archive, 0, +- "Removing symlink %s", +- a->name); ++ tail[0] = c; ++ /* FIXME: not sure how important this is to restore ++ if (!S_ISLNK(path)) { ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Removing symlink %s", ++ path); + } ++ */ + /* Symlink gone. No more problem! */ +- pn[0] = c; +- return (0); +- } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { ++ res = ARCHIVE_OK; ++ break; ++ } else if (flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ +- if (unlink(a->name) != 0) { +- archive_set_error(&a->archive, 0, +- "Cannot remove intervening symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot remove intervening symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; ++ tail[0] = c; + } else { +- archive_set_error(&a->archive, 0, +- "Cannot extract through symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot extract through symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } + } ++ /* be sure to always maintain this */ ++ tail[0] = c; ++ if (tail[0] != '\0') ++ tail++; /* Advance to the next segment. */ + } +- pn[0] = c; +- /* We've checked and/or cleaned the whole path, so remember it. */ +- archive_strcpy(&a->path_safe, a->name); +- return (ARCHIVE_OK); ++ /* Catches loop exits via break */ ++ tail[0] = c; ++#ifdef HAVE_FCHDIR ++ /* If we changed directory above, restore it here. */ ++ if (restore_pwd >= 0) { ++ r = fchdir(restore_pwd); ++ if (r != 0) { ++ if(error_number) *error_number = errno; ++ if(error_string) ++ archive_string_sprintf(error_string, ++ "chdir() failure"); ++ } ++ close(restore_pwd); ++ restore_pwd = -1; ++ if (r != 0) { ++ res = (ARCHIVE_FATAL); ++ } ++ } ++#endif ++ /* TODO: reintroduce a safe cache here? */ ++ return res; + #endif + } + ++/* ++ * Check a->name for symlinks, returning ARCHIVE_OK if its clean, otherwise ++ * calls archive_set_error and returns ARCHIVE_{FATAL,FAILED} ++ */ ++static int ++check_symlinks(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = check_symlinks_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ a->pst = NULL; /* to be safe */ ++ return rc; ++} ++ ++ + #if defined(__CYGWIN__) + /* + * 1. Convert a path separator from '\' to '/' . +@@ -2509,15 +2669,17 @@ + * is set) if the path is absolute. + */ + static int +-cleanup_pathname(struct archive_write_disk *a) ++cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + char *dest, *src; + char separator = '\0'; + +- dest = src = a->name; ++ dest = src = path; + if (*src == '\0') { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Invalid empty pathname"); ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Invalid empty pathname"); + return (ARCHIVE_FAILED); + } + +@@ -2526,9 +2688,11 @@ + #endif + /* Skip leading '/'. */ + if (*src == '/') { +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Path is absolute"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path is absolute"); + return (ARCHIVE_FAILED); + } + +@@ -2555,10 +2719,11 @@ + } else if (src[1] == '.') { + if (src[2] == '/' || src[2] == '\0') { + /* Conditionally warn about '..' */ +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { +- archive_set_error(&a->archive, +- ARCHIVE_ERRNO_MISC, +- "Path contains '..'"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path contains '..'"); + return (ARCHIVE_FAILED); + } + } +@@ -2589,7 +2754,7 @@ + * We've just copied zero or more path elements, not including the + * final '/'. + */ +- if (dest == a->name) { ++ if (dest == path) { + /* + * Nothing got copied. The path must have been something + * like '.' or '/' or './' or '/././././/./'. +@@ -2604,6 +2769,21 @@ + return (ARCHIVE_OK); + } + ++static int ++cleanup_pathname(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = cleanup_pathname_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ return rc; ++} ++ + /* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. +--- contrib/libarchive/libarchive/test/main.c.orig ++++ contrib/libarchive/libarchive/test/main.c +@@ -1396,6 +1396,31 @@ + return (0); + } + ++/* Verify mode of 'pathname'. */ ++int ++assertion_file_mode(const char *file, int line, const char *pathname, int expected_mode) ++{ ++ int mode; ++ int r; ++ ++ assertion_count(file, line); ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ failure_start(file, line, "assertFileMode not yet implemented for Windows"); ++#else ++ { ++ struct stat st; ++ r = lstat(pathname, &st); ++ mode = (int)(st.st_mode & 0777); ++ } ++ if (r == 0 && mode == expected_mode) ++ return (1); ++ failure_start(file, line, "File %s has mode %o, expected %o", ++ pathname, mode, expected_mode); ++#endif ++ failure_finish(NULL); ++ return (0); ++} ++ + /* Assert that 'pathname' is a dir. If mode >= 0, verify that too. */ + int + assertion_is_dir(const char *file, int line, const char *pathname, int mode) +--- contrib/libarchive/libarchive/test/test.h.orig ++++ contrib/libarchive/libarchive/test/test.h +@@ -176,6 +176,8 @@ + assertion_file_nlinks(__FILE__, __LINE__, pathname, nlinks) + #define assertFileSize(pathname, size) \ + assertion_file_size(__FILE__, __LINE__, pathname, size) ++#define assertFileMode(pathname, mode) \ ++ assertion_file_mode(__FILE__, __LINE__, pathname, mode) + #define assertTextFileContents(text, pathname) \ + assertion_text_file_contents(__FILE__, __LINE__, text, pathname) + #define assertFileContainsLinesAnyOrder(pathname, lines) \ +@@ -239,6 +241,7 @@ + int assertion_file_nlinks(const char *, int, const char *, int); + int assertion_file_not_exists(const char *, int, const char *); + int assertion_file_size(const char *, int, const char *, long); ++int assertion_file_mode(const char *, int, const char *, int); + int assertion_is_dir(const char *, int, const char *, int); + int assertion_is_hardlink(const char *, int, const char *, const char *); + int assertion_is_not_hardlink(const char *, int, const char *, const char *); +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure744.c +@@ -0,0 +1,95 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #744 describes a bug in the sandboxing code that ++ * causes very long pathnames to not get checked for symlinks. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure744) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ size_t buff_size = 8192; ++ char *buff = malloc(buff_size); ++ char *p = buff; ++ int n = 0; ++ int t; ++ ++ assert(buff != NULL); ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ while (p + 500 < buff + buff_size) { ++ memset(p, 'x', 100); ++ p += 100; ++ p[0] = '\0'; ++ ++ buff[0] = ((n / 1000) % 10) + '0'; ++ buff[1] = ((n / 100) % 10)+ '0'; ++ buff[2] = ((n / 10) % 10)+ '0'; ++ buff[3] = ((n / 1) % 10)+ '0'; ++ buff[4] = '_'; ++ ++n; ++ ++ /* Create a symlink pointing to the testworkdir */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFREG | 0777); ++ archive_entry_copy_symlink(ae, testworkdir); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ *p++ = '/'; ++ sprintf(p, "target%d", n); ++ ++ /* Try to create a file through the symlink, should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ ++ t = archive_write_header(a, ae); ++ archive_entry_free(ae); ++ failure("Attempt to create target%d via %d-character symlink should have failed", n, (int)strlen(buff)); ++ if(!assertEqualInt(ARCHIVE_FAILED, t)) { ++ break; ++ } ++ } ++ archive_free(a); ++ free(buff); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure745.c +@@ -0,0 +1,79 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #745 describes a bug in the sandboxing code that ++ * allows one to use a symlink to edit the permissions on a file or ++ * directory outside of the sandbox. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure745) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* The target dir: The one we're going to try to change permission on */ ++ assertMakeDir("target", 0700); ++ ++ /* The sandbox dir we're going to run inside of. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create a symlink pointing to the target directory */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Try to alter the target dir through the symlink; this should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Permission of target dir should not have changed. */ ++ assertFileMode("../target", 0700); ++ ++ assert(0 == archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure746.c +@@ -0,0 +1,129 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #746 describes a problem in which hardlink targets are ++ * not adequately checked and can be used to modify entries outside of ++ * the sandbox. ++ */ ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NODOTDOT disallows '..' in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746a) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NODOTDOT); ++ ++ /* Attempt to hardlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "../target/foo"); ++ assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualInt(ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++#endif ++} ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NOSYMLINK disallows symlinks in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746b) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* Create a symlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "symlink"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Attempt to hardlink to the target directory via the symlink. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "symlink/foo"); ++ assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++ ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- lib/libarchive/test/Makefile.orig ++++ lib/libarchive/test/Makefile +@@ -176,6 +176,9 @@ + test_write_disk_no_hfs_compression.c \ + test_write_disk_perms.c \ + test_write_disk_secure.c \ ++ test_write_disk_secure744.c \ ++ test_write_disk_secure745.c \ ++ test_write_disk_secure746.c \ + test_write_disk_sparse.c \ + test_write_disk_symlink.c \ + test_write_disk_times.c \ Index: user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:29.bspatch =================================================================== --- user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:29.bspatch (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:29.bspatch (revision 307229) @@ -0,0 +1,325 @@ +--- usr.bin/bsdiff/bspatch/bspatch.c.orig ++++ usr.bin/bsdiff/bspatch/bspatch.c +@@ -27,56 +27,133 @@ + #include + __FBSDID("$FreeBSD$"); + ++#if defined(__FreeBSD__) ++#include ++#if __FreeBSD_version >= 1001511 ++#include ++#define HAVE_CAPSICUM ++#endif ++#endif ++ + #include +-#include ++#include ++#include ++#include ++#include ++#include ++#include + #include ++#include + #include +-#include + #include +-#include + + #ifndef O_BINARY + #define O_BINARY 0 + #endif ++#define HEADER_SIZE 32 ++ ++static char *newfile; ++static int dirfd = -1; ++ ++static void ++exit_cleanup(void) ++{ ++ ++ if (dirfd != -1 && newfile != NULL) ++ if (unlinkat(dirfd, newfile, 0)) ++ warn("unlinkat"); ++} + + static off_t offtin(u_char *buf) + { + off_t y; + +- y=buf[7]&0x7F; +- y=y*256;y+=buf[6]; +- y=y*256;y+=buf[5]; +- y=y*256;y+=buf[4]; +- y=y*256;y+=buf[3]; +- y=y*256;y+=buf[2]; +- y=y*256;y+=buf[1]; +- y=y*256;y+=buf[0]; ++ y = buf[7] & 0x7F; ++ y = y * 256; y += buf[6]; ++ y = y * 256; y += buf[5]; ++ y = y * 256; y += buf[4]; ++ y = y * 256; y += buf[3]; ++ y = y * 256; y += buf[2]; ++ y = y * 256; y += buf[1]; ++ y = y * 256; y += buf[0]; + +- if(buf[7]&0x80) y=-y; ++ if (buf[7] & 0x80) ++ y = -y; + +- return y; ++ return (y); + } + +-int main(int argc,char * argv[]) ++int main(int argc, char *argv[]) + { +- FILE * f, * cpf, * dpf, * epf; +- BZFILE * cpfbz2, * dpfbz2, * epfbz2; ++ FILE *f, *cpf, *dpf, *epf; ++ BZFILE *cpfbz2, *dpfbz2, *epfbz2; ++ char *directory, *namebuf; + int cbz2err, dbz2err, ebz2err; +- int fd; +- ssize_t oldsize,newsize; +- ssize_t bzctrllen,bzdatalen; +- u_char header[32],buf[8]; ++ int newfd, oldfd; ++ off_t oldsize, newsize; ++ off_t bzctrllen, bzdatalen; ++ u_char header[HEADER_SIZE], buf[8]; + u_char *old, *new; +- off_t oldpos,newpos; ++ off_t oldpos, newpos; + off_t ctrl[3]; +- off_t lenread; +- off_t i; ++ off_t i, lenread, offset; ++#ifdef HAVE_CAPSICUM ++ cap_rights_t rights_dir, rights_ro, rights_wr; ++#endif + + if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]); + + /* Open patch file */ + if ((f = fopen(argv[3], "rb")) == NULL) + err(1, "fopen(%s)", argv[3]); ++ /* Open patch file for control block */ ++ if ((cpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for diff block */ ++ if ((dpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for extra block */ ++ if ((epf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open oldfile */ ++ if ((oldfd = open(argv[1], O_RDONLY | O_BINARY, 0)) < 0) ++ err(1, "open(%s)", argv[1]); ++ /* open directory where we'll write newfile */ ++ if ((namebuf = strdup(argv[2])) == NULL || ++ (directory = dirname(namebuf)) == NULL || ++ (dirfd = open(directory, O_DIRECTORY)) < 0) ++ err(1, "open %s", argv[2]); ++ free(namebuf); ++ if ((newfile = basename(argv[2])) == NULL) ++ err(1, "basename"); ++ /* open newfile */ ++ if ((newfd = openat(dirfd, newfile, ++ O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) ++ err(1, "open(%s)", argv[2]); ++ atexit(exit_cleanup); ++ ++#ifdef HAVE_CAPSICUM ++ if (cap_enter() < 0) { ++ /* Failed to sandbox, fatal if CAPABILITY_MODE enabled */ ++ if (errno != ENOSYS) ++ err(1, "failed to enter security sandbox"); ++ } else { ++ /* Capsicum Available */ ++ cap_rights_init(&rights_ro, CAP_READ, CAP_FSTAT, CAP_SEEK); ++ cap_rights_init(&rights_wr, CAP_WRITE); ++ cap_rights_init(&rights_dir, CAP_UNLINKAT); ++ ++ if (cap_rights_limit(fileno(f), &rights_ro) < 0 || ++ cap_rights_limit(fileno(cpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(dpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(epf), &rights_ro) < 0 || ++ cap_rights_limit(oldfd, &rights_ro) < 0 || ++ cap_rights_limit(newfd, &rights_wr) < 0 || ++ cap_rights_limit(dirfd, &rights_dir) < 0) ++ err(1, "cap_rights_limit() failed, could not restrict" ++ " capabilities"); ++ } ++#endif + + /* + File format: +@@ -93,99 +170,99 @@ + */ + + /* Read header */ +- if (fread(header, 1, 32, f) < 32) { ++ if (fread(header, 1, HEADER_SIZE, f) < HEADER_SIZE) { + if (feof(f)) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + err(1, "fread(%s)", argv[3]); + } + + /* Check for appropriate magic */ + if (memcmp(header, "BSDIFF40", 8) != 0) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Read lengths from header */ +- bzctrllen=offtin(header+8); +- bzdatalen=offtin(header+16); +- newsize=offtin(header+24); +- if((bzctrllen<0) || (bzdatalen<0) || (newsize<0)) +- errx(1,"Corrupt patch\n"); ++ bzctrllen = offtin(header + 8); ++ bzdatalen = offtin(header + 16); ++ newsize = offtin(header + 24); ++ if (bzctrllen < 0 || bzctrllen > OFF_MAX - HEADER_SIZE || ++ bzdatalen < 0 || bzctrllen + HEADER_SIZE > OFF_MAX - bzdatalen || ++ newsize < 0 || newsize > SSIZE_MAX) ++ errx(1, "Corrupt patch"); + + /* Close patch file and re-open it via libbzip2 at the right places */ + if (fclose(f)) + err(1, "fclose(%s)", argv[3]); +- if ((cpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(cpf, 32, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)32); ++ offset = HEADER_SIZE; ++ if (fseeko(cpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); +- if ((dpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen)); ++ offset += bzctrllen; ++ if (fseeko(dpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); +- if ((epf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen + bzdatalen)); ++ offset += bzdatalen; ++ if (fseeko(epf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); + +- if(((fd=open(argv[1],O_RDONLY|O_BINARY,0))<0) || +- ((oldsize=lseek(fd,0,SEEK_END))==-1) || +- ((old=malloc(oldsize+1))==NULL) || +- (lseek(fd,0,SEEK_SET)!=0) || +- (read(fd,old,oldsize)!=oldsize) || +- (close(fd)==-1)) err(1,"%s",argv[1]); +- if((new=malloc(newsize+1))==NULL) err(1,NULL); ++ if ((oldsize = lseek(oldfd, 0, SEEK_END)) == -1 || ++ oldsize > SSIZE_MAX || ++ (old = malloc(oldsize)) == NULL || ++ lseek(oldfd, 0, SEEK_SET) != 0 || ++ read(oldfd, old, oldsize) != oldsize || ++ close(oldfd) == -1) ++ err(1, "%s", argv[1]); ++ if ((new = malloc(newsize)) == NULL) ++ err(1, NULL); + +- oldpos=0;newpos=0; +- while(newpos INT_MAX || ++ ctrl[1] < 0 || ctrl[1] > INT_MAX) ++ errx(1, "Corrupt patch"); + + /* Sanity-check */ +- if(newpos+ctrl[0]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[0] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read diff string */ + lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); + if ((lenread < ctrl[0]) || + ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Add old data to diff string */ +- for(i=0;i=0) && (oldpos+i= 0) && (oldpos + i < oldsize)) ++ new[newpos + i] += old[oldpos + i]; + + /* Adjust pointers */ +- newpos+=ctrl[0]; +- oldpos+=ctrl[0]; ++ newpos += ctrl[0]; ++ oldpos += ctrl[0]; + + /* Sanity-check */ +- if(newpos+ctrl[1]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[1] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read extra string */ + lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); + if ((lenread < ctrl[1]) || + ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Adjust pointers */ + newpos+=ctrl[1]; +@@ -200,12 +277,13 @@ + err(1, "fclose(%s)", argv[3]); + + /* Write the new file */ +- if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))<0) || +- (write(fd,new,newsize)!=newsize) || (close(fd)==-1)) +- err(1,"%s",argv[2]); ++ if (write(newfd, new, newsize) != newsize || close(newfd) == -1) ++ err(1, "%s", argv[2]); ++ /* Disable atexit cleanup */ ++ newfile = NULL; + + free(new); + free(old); + +- return 0; ++ return (0); + } Index: user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:30.portsnap =================================================================== --- user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:30.portsnap (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:30.portsnap (revision 307229) @@ -0,0 +1,49 @@ +--- usr.sbin/portsnap/portsnap/portsnap.sh.orig ++++ usr.sbin/portsnap/portsnap/portsnap.sh +@@ -646,7 +646,7 @@ + # Verify a list of files + fetch_snapshot_verify() { + while read F; do +- if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then ++ if [ "`gunzip -c < snap/${F}.gz | ${SHA256} -q`" != ${F} ]; then + echo "snapshot corrupt." + return 1 + fi +@@ -681,11 +681,18 @@ + cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1 + # Extract the index + rm -f INDEX.new +- gunzip -c snap/`look INDEX tINDEX.new | ++ gunzip -c < snap/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + # Verify the snapshot contents + cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1 ++ cut -f 2 -d '|' tINDEX.new INDEX.new | sort -u > files.expected ++ find snap -mindepth 1 | sed -E 's^snap/(.*)\.gz^\1^' | sort > files.snap ++ if ! cmp -s files.expected files.snap; then ++ echo "unexpected files in snapshot." ++ return 1 ++ fi ++ rm files.expected files.snap + echo "done." + + # Move files into their proper locations +@@ -777,7 +784,7 @@ + + # Extract the index + echo -n "Extracting index... " 1>${QUIETREDIR} +- gunzip -c files/`look INDEX tINDEX.new | ++ gunzip -c < files/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + +@@ -897,7 +904,7 @@ + echo -n "$1 not provided by portsnap server; " + echo "$2 not being generated." + else +- gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | ++ gunzip -c < "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | + cut -f 2 -d '|'`.gz" | + cat - ${LOCALDESC} | + ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2 Index: user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:31.libarchive =================================================================== --- user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:31.libarchive (nonexistent) +++ user/cperciva/freebsd-update-build/patches/10.3-RELEASE/10-SA-16:31.libarchive (revision 307229) @@ -0,0 +1,1270 @@ +--- contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c.orig ++++ contrib/libarchive/libarchive/archive_read_disk_entry_from_file.c +@@ -409,9 +409,7 @@ + { + const char *accpath; + acl_t acl; +-#if HAVE_ACL_IS_TRIVIAL_NP + int r; +-#endif + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) +@@ -443,9 +441,13 @@ + } + #endif + if (acl != NULL) { +- translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); ++ r = translate_acl(a, entry, acl, ARCHIVE_ENTRY_ACL_TYPE_NFS4); + acl_free(acl); +- return (ARCHIVE_OK); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate NFSv4 ACLs: %s", accpath); ++ } ++ return (r); + } + + /* Retrieve access ACL from file. */ +@@ -464,18 +466,29 @@ + else + acl = acl_get_file(accpath, ACL_TYPE_ACCESS); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate access ACLs: %s", accpath); ++ return (r); ++ } + } + + /* Only directories can have default ACLs. */ + if (S_ISDIR(archive_entry_mode(entry))) { + acl = acl_get_file(accpath, ACL_TYPE_DEFAULT); + if (acl != NULL) { +- translate_acl(a, entry, acl, ++ r = translate_acl(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + acl_free(acl); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, errno, ++ "Couldn't translate default ACLs: %s", ++ accpath); ++ return (r); ++ } + } + } + return (ARCHIVE_OK); +@@ -536,7 +549,11 @@ + // FreeBSD "brands" ACLs as POSIX.1e or NFSv4 + // Make sure the "brand" on this ACL is consistent + // with the default_entry_acl_type bits provided. +- acl_get_brand_np(acl, &brand); ++ if (acl_get_brand_np(acl, &brand) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to read ACL brand"); ++ return (ARCHIVE_WARN); ++ } + switch (brand) { + case ACL_BRAND_POSIX: + switch (default_entry_acl_type) { +@@ -544,30 +561,42 @@ + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for POSIX.1e ACL"); ++ return (ARCHIVE_WARN); + } + break; + case ACL_BRAND_NFS4: + if (default_entry_acl_type & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) { +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Invalid ACL entry type for NFSv4 ACL"); ++ return (ARCHIVE_WARN); + } + break; + default: +- // XXX set warning message? +- return ARCHIVE_FAILED; ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL brand"); ++ return (ARCHIVE_WARN); + break; + } + + + s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get first ACL entry"); ++ return (ARCHIVE_WARN); ++ } + while (s == 1) { + ae_id = -1; + ae_name = NULL; + ae_perm = 0; + +- acl_get_tag_type(acl_entry, &acl_tag); ++ if (acl_get_tag_type(acl_entry, &acl_tag) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL tag type"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_tag) { + case ACL_USER: + ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry); +@@ -600,12 +629,17 @@ + continue; + } + +- // XXX acl type maps to allow/deny/audit/YYYY bits +- // XXX acl_get_entry_type_np on FreeBSD returns EINVAL for +- // non-NFSv4 ACLs ++ // XXX acl_type maps to allow/deny/audit/YYYY bits + entry_acl_type = default_entry_acl_type; +- r = acl_get_entry_type_np(acl_entry, &acl_type); +- if (r == 0) { ++ if (default_entry_acl_type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) { ++ /* ++ * acl_get_entry_type_np() falis with non-NFSv4 ACLs ++ */ ++ if (acl_get_entry_type_np(acl_entry, &acl_type) != 0) { ++ archive_set_error(&a->archive, errno, "Failed " ++ "to get ACL type from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } + switch (acl_type) { + case ACL_ENTRY_TYPE_ALLOW: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALLOW; +@@ -619,28 +653,52 @@ + case ACL_ENTRY_TYPE_ALARM: + entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALARM; + break; ++ default: ++ archive_set_error(&a->archive, errno, ++ "Invalid NFSv4 ACL entry type"); ++ return (ARCHIVE_WARN); + } +- } +- +- /* +- * Libarchive stores "flag" (NFSv4 inheritance bits) +- * in the ae_perm bitmap. +- */ +- acl_get_flagset_np(acl_entry, &acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (acl_get_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit)) +- ae_perm |= acl_inherit_map[i].archive_inherit; + +- } ++ /* ++ * Libarchive stores "flag" (NFSv4 inheritance bits) ++ * in the ae_perm bitmap. ++ * ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get flagset from a NFSv4 ACL entry"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ r = acl_get_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check flag in a NFSv4 " ++ "ACL flagset"); ++ return (ARCHIVE_WARN); ++ } else if (r) ++ ae_perm |= acl_inherit_map[i].archive_inherit; ++ } ++ } + +- acl_get_permset(acl_entry, &acl_permset); +- for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get ACL permission set"); ++ return (ARCHIVE_WARN); ++ } ++ for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + /* + * acl_get_perm() is spelled differently on different + * platforms; see above. + */ +- if (ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm)) ++ r = ACL_GET_PERM(acl_permset, acl_perm_map[i].platform_perm); ++ if (r == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to check permission in an ACL permission set"); ++ return (ARCHIVE_WARN); ++ } else if (r) + ae_perm |= acl_perm_map[i].archive_perm; + } + +@@ -649,6 +707,11 @@ + ae_id, ae_name); + + s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); ++ if (s == -1) { ++ archive_set_error(&a->archive, errno, ++ "Failed to get next ACL entry"); ++ return (ARCHIVE_WARN); ++ } + } + return (ARCHIVE_OK); + } +--- contrib/libarchive/libarchive/archive_read_support_format_tar.c.orig ++++ contrib/libarchive/libarchive/archive_read_support_format_tar.c +@@ -136,6 +136,7 @@ + int64_t entry_padding; + int64_t entry_bytes_unconsumed; + int64_t realsize; ++ int sparse_allowed; + struct sparse_block *sparse_list; + struct sparse_block *sparse_last; + int64_t sparse_offset; +@@ -1226,6 +1227,14 @@ + * sparse information in the extended area. + */ + /* FALLTHROUGH */ ++ case '0': ++ /* ++ * Enable sparse file "read" support only for regular ++ * files and explicit GNU sparse files. However, we ++ * don't allow non-standard file types to be sparse. ++ */ ++ tar->sparse_allowed = 1; ++ /* FALLTHROUGH */ + default: /* Regular file and non-standard types */ + /* + * Per POSIX: non-recognized types should always be +@@ -1685,6 +1694,14 @@ + #endif + switch (key[0]) { + case 'G': ++ /* Reject GNU.sparse.* headers on non-regular files. */ ++ if (strncmp(key, "GNU.sparse", 10) == 0 && ++ !tar->sparse_allowed) { ++ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, ++ "Non-regular file cannot be sparse"); ++ return (ARCHIVE_FATAL); ++ } ++ + /* GNU "0.0" sparse pax format. */ + if (strcmp(key, "GNU.sparse.numblocks") == 0) { + tar->sparse_offset = -1; +--- contrib/libarchive/libarchive/archive_write_disk_acl.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_acl.c +@@ -131,6 +131,7 @@ + acl_entry_t acl_entry; + acl_permset_t acl_permset; + acl_flagset_t acl_flagset; ++ int r; + int ret; + int ae_type, ae_permset, ae_tag, ae_id; + uid_t ae_uid; +@@ -144,9 +145,19 @@ + if (entries == 0) + return (ARCHIVE_OK); + acl = acl_init(entries); ++ if (acl == (acl_t)NULL) { ++ archive_set_error(a, errno, ++ "Failed to initialize ACL working storage"); ++ return (ARCHIVE_FAILED); ++ } + while (archive_acl_next(a, abstract_acl, ae_requested_type, &ae_type, + &ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) { +- acl_create_entry(&acl, &acl_entry); ++ if (acl_create_entry(&acl, &acl_entry) != 0) { ++ archive_set_error(a, errno, ++ "Failed to create a new ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + switch (ae_tag) { + case ARCHIVE_ENTRY_ACL_USER: +@@ -175,47 +186,95 @@ + acl_set_tag_type(acl_entry, ACL_EVERYONE); + break; + default: +- /* XXX */ +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL tag"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + ++ r = 0; + switch (ae_type) { + case ARCHIVE_ENTRY_ACL_TYPE_ALLOW: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALLOW); + break; + case ARCHIVE_ENTRY_ACL_TYPE_DENY: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_DENY); + break; + case ARCHIVE_ENTRY_ACL_TYPE_AUDIT: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_AUDIT); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ALARM: +- acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); ++ r = acl_set_entry_type_np(acl_entry, ACL_ENTRY_TYPE_ALARM); + break; + case ARCHIVE_ENTRY_ACL_TYPE_ACCESS: + case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT: + // These don't translate directly into the system ACL. + break; + default: +- // XXX error handling here. +- break; ++ archive_set_error(a, ARCHIVE_ERRNO_MISC, ++ "Unknown ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (r != 0) { ++ archive_set_error(a, errno, ++ "Failed to set ACL entry type"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; + } + +- acl_get_permset(acl_entry, &acl_permset); +- acl_clear_perms(acl_permset); ++ if (acl_get_permset(acl_entry, &acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get ACL permission set"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_perms(acl_permset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear ACL permissions"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + + for (i = 0; i < (int)(sizeof(acl_perm_map) / sizeof(acl_perm_map[0])); ++i) { + if (ae_permset & acl_perm_map[i].archive_perm) +- acl_add_perm(acl_permset, +- acl_perm_map[i].platform_perm); ++ if (acl_add_perm(acl_permset, ++ acl_perm_map[i].platform_perm) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add ACL permission"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } + } + + acl_get_flagset_np(acl_entry, &acl_flagset); +- acl_clear_flags_np(acl_flagset); +- for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { +- if (ae_permset & acl_inherit_map[i].archive_inherit) +- acl_add_flag_np(acl_flagset, +- acl_inherit_map[i].platform_inherit); ++ if (acl_type == ACL_TYPE_NFS4) { ++ /* ++ * acl_get_flagset_np() fails with non-NFSv4 ACLs ++ */ ++ if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to get flagset from an NFSv4 ACL entry"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ if (acl_clear_flags_np(acl_flagset) != 0) { ++ archive_set_error(a, errno, ++ "Failed to clear flags from an NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ for (i = 0; i < (int)(sizeof(acl_inherit_map) / sizeof(acl_inherit_map[0])); ++i) { ++ if (ae_permset & acl_inherit_map[i].archive_inherit) { ++ if (acl_add_flag_np(acl_flagset, ++ acl_inherit_map[i].platform_inherit) != 0) { ++ archive_set_error(a, errno, ++ "Failed to add flag to NFSv4 ACL flagset"); ++ ret = ARCHIVE_FAILED; ++ goto exit_free; ++ } ++ } ++ } + } + } + +@@ -243,6 +302,7 @@ + ret = ARCHIVE_WARN; + } + #endif ++exit_free: + acl_free(acl); + return (ret); + } +--- contrib/libarchive/libarchive/archive_write_disk_posix.c.orig ++++ contrib/libarchive/libarchive/archive_write_disk_posix.c +@@ -140,7 +140,17 @@ + #define O_BINARY 0 + #endif + #ifndef O_CLOEXEC +-#define O_CLOEXEC 0 ++#define O_CLOEXEC 0 ++#endif ++ ++/* Ignore non-int O_NOFOLLOW constant. */ ++/* gnulib's fcntl.h does this on AIX, but it seems practical everywhere */ ++#if defined O_NOFOLLOW && !(INT_MIN <= O_NOFOLLOW && O_NOFOLLOW <= INT_MAX) ++#undef O_NOFOLLOW ++#endif ++ ++#ifndef O_NOFOLLOW ++#define O_NOFOLLOW 0 + #endif + + struct fixup_entry { +@@ -326,12 +336,14 @@ + + #define HFS_BLOCKS(s) ((s) >> 12) + ++static int check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int check_symlinks(struct archive_write_disk *); + static int create_filesystem_object(struct archive_write_disk *); + static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); + #if defined(HAVE_FCHDIR) && defined(PATH_MAX) + static void edit_deep_directories(struct archive_write_disk *ad); + #endif ++static int cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags); + static int cleanup_pathname(struct archive_write_disk *); + static int create_dir(struct archive_write_disk *, char *); + static int create_parent_dir(struct archive_write_disk *, char *); +@@ -1791,7 +1803,7 @@ + char *tail = a->name; + + /* If path is short, avoid the open() below. */ +- if (strlen(tail) <= PATH_MAX) ++ if (strlen(tail) < PATH_MAX) + return; + + /* Try to record our starting dir. */ +@@ -1801,7 +1813,7 @@ + return; + + /* As long as the path is too long... */ +- while (strlen(tail) > PATH_MAX) { ++ while (strlen(tail) >= PATH_MAX) { + /* Locate a dir prefix shorter than PATH_MAX. */ + tail += PATH_MAX - 8; + while (tail > a->name && *tail != '/') +@@ -1996,6 +2008,10 @@ + const char *linkname; + mode_t final_mode, mode; + int r; ++ /* these for check_symlinks_fsobj */ ++ char *linkname_copy; /* non-const copy of linkname */ ++ struct archive_string error_string; ++ int error_number; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ +@@ -2004,6 +2020,27 @@ + #if !HAVE_LINK + return (EPERM); + #else ++ archive_string_init(&error_string); ++ linkname_copy = strdup(linkname); ++ if (linkname_copy == NULL) { ++ return (EPERM); ++ } ++ /* TODO: consider using the cleaned-up path as the link target? */ ++ r = cleanup_pathname_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ r = check_symlinks_fsobj(linkname_copy, &error_number, &error_string, a->flags); ++ if (r != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ free(linkname_copy); ++ /* EPERM is more appropriate than error_number for our callers */ ++ return (EPERM); ++ } ++ free(linkname_copy); + r = link(linkname, a->name) ? errno : 0; + /* + * New cpio and pax formats allow hardlink entries +@@ -2022,7 +2059,7 @@ + a->deferred = 0; + } else if (r == 0 && a->filesize > 0) { + a->fd = open(a->name, +- O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC); ++ O_WRONLY | O_TRUNC | O_BINARY | O_CLOEXEC | O_NOFOLLOW); + __archive_ensure_cloexec_flag(a->fd); + if (a->fd < 0) + r = errno; +@@ -2332,110 +2369,233 @@ + return (a->current_fixup); + } + +-/* TODO: Make this work. */ +-/* +- * TODO: The deep-directory support bypasses this; disable deep directory +- * support if we're doing symlink checks. +- */ + /* + * TODO: Someday, integrate this with the deep dir support; they both + * 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. ++ */ + static int +-check_symlinks(struct archive_write_disk *a) ++check_symlinks_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + #if !defined(HAVE_LSTAT) + /* Platform doesn't have lstat, so we can't look for symlinks. */ +- (void)a; /* UNUSED */ ++ (void)path; /* UNUSED */ ++ (void)error_number; /* UNUSED */ ++ (void)error_string; /* UNUSED */ ++ (void)flags; /* UNUSED */ + return (ARCHIVE_OK); + #else +- char *pn; ++ int res = ARCHIVE_OK; ++ char *tail; ++ char *head; ++ int last; + char c; + int r; + struct stat st; ++ int restore_pwd; ++ ++ /* Nothing to do here if name is empty */ ++ if(path[0] == '\0') ++ return (ARCHIVE_OK); + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. ++ * ++ * Walk the filename in chunks separated by '/'. For each segment: ++ * - if it doesn't exist, continue ++ * - if it's symlink, abort or remove it ++ * - if it's a directory and it's not the last chunk, cd into it ++ * As we go: ++ * head points to the current (relative) path ++ * tail points to the temporary \0 terminating the segment we're currently examining ++ * 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) ++ return (ARCHIVE_FATAL); ++ head = path; ++ tail = path; ++ last = 0; ++ /* TODO: reintroduce a safe cache here? */ ++ /* Skip the root directory if the path is absolute. */ ++ if(tail == path && tail[0] == '/') ++ ++tail; ++ /* Keep going until we've checked the entire name. ++ * head, tail, path all alias the same string, which is ++ * temporarily zeroed at tail, so be careful restoring the ++ * stashed (c=tail[0]) for error messages. ++ * Exiting the loop with break is okay; continue is not. + */ +- /* Whatever we checked last time doesn't need to be re-checked. */ +- pn = a->name; +- if (archive_strlen(&(a->path_safe)) > 0) { +- char *p = a->path_safe.s; +- while ((*pn != '\0') && (*p == *pn)) +- ++p, ++pn; +- } +- c = pn[0]; +- /* Keep going until we've checked the entire name. */ +- while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { ++ while (!last) { ++ /* Skip the separator we just consumed, plus any adjacent ones */ ++ while (*tail == '/') ++ ++tail; + /* Skip the next path element. */ +- while (*pn != '\0' && *pn != '/') +- ++pn; +- c = pn[0]; +- pn[0] = '\0'; ++ while (*tail != '\0' && *tail != '/') ++ ++tail; ++ /* is this the last path component? */ ++ last = (tail[0] == '\0') || (tail[0] == '/' && tail[1] == '\0'); ++ /* temporarily truncate the string here */ ++ c = tail[0]; ++ tail[0] = '\0'; + /* Check that we haven't hit a symlink. */ +- r = lstat(a->name, &st); ++ r = lstat(head, &st); + if (r != 0) { ++ tail[0] = c; + /* We've hit a dir that doesn't exist; stop now. */ +- if (errno == ENOENT) ++ if (errno == ENOENT) { + break; ++ } else { ++ /* Treat any other error as fatal - best to be paranoid here ++ * Note: This effectively disables deep directory ++ * support when security checks are enabled. ++ * Otherwise, very long pathnames that trigger ++ * an error here could evade the sandbox. ++ * TODO: We could do better, but it would probably ++ * require merging the symlink checks with the ++ * deep-directory editing. */ ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not stat %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; ++ } ++ } else if (S_ISDIR(st.st_mode)) { ++ if (!last) { ++ if (chdir(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not chdir %s", ++ path); ++ res = (ARCHIVE_FATAL); ++ break; ++ } ++ /* Our view is now from inside this dir: */ ++ head = tail + 1; ++ } + } else if (S_ISLNK(st.st_mode)) { +- if (c == '\0') { ++ if (last) { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ +- if (unlink(a->name)) { +- archive_set_error(&a->archive, errno, +- "Could not remove symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head)) { ++ tail[0] = c; ++ if (error_number) *error_number = errno; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Could not remove symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ +- if (!S_ISLNK(a->mode)) { +- archive_set_error(&a->archive, 0, +- "Removing symlink %s", +- a->name); ++ tail[0] = c; ++ /* FIXME: not sure how important this is to restore ++ if (!S_ISLNK(path)) { ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Removing symlink %s", ++ path); + } ++ */ + /* Symlink gone. No more problem! */ +- pn[0] = c; +- return (0); +- } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { ++ res = ARCHIVE_OK; ++ break; ++ } else if (flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ +- if (unlink(a->name) != 0) { +- archive_set_error(&a->archive, 0, +- "Cannot remove intervening symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ if (unlink(head) != 0) { ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot remove intervening symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } +- a->pst = NULL; ++ tail[0] = c; + } else { +- archive_set_error(&a->archive, 0, +- "Cannot extract through symlink %s", +- a->name); +- pn[0] = c; +- return (ARCHIVE_FAILED); ++ tail[0] = c; ++ if (error_number) *error_number = 0; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Cannot extract through symlink %s", ++ path); ++ res = ARCHIVE_FAILED; ++ break; + } + } ++ /* be sure to always maintain this */ ++ tail[0] = c; ++ if (tail[0] != '\0') ++ tail++; /* Advance to the next segment. */ + } +- pn[0] = c; +- /* We've checked and/or cleaned the whole path, so remember it. */ +- archive_strcpy(&a->path_safe, a->name); +- return (ARCHIVE_OK); ++ /* Catches loop exits via break */ ++ tail[0] = c; ++#ifdef HAVE_FCHDIR ++ /* If we changed directory above, restore it here. */ ++ if (restore_pwd >= 0) { ++ r = fchdir(restore_pwd); ++ if (r != 0) { ++ if(error_number) *error_number = errno; ++ if(error_string) ++ archive_string_sprintf(error_string, ++ "chdir() failure"); ++ } ++ close(restore_pwd); ++ restore_pwd = -1; ++ if (r != 0) { ++ res = (ARCHIVE_FATAL); ++ } ++ } ++#endif ++ /* TODO: reintroduce a safe cache here? */ ++ return res; + #endif + } + ++/* ++ * Check a->name for symlinks, returning ARCHIVE_OK if its clean, otherwise ++ * calls archive_set_error and returns ARCHIVE_{FATAL,FAILED} ++ */ ++static int ++check_symlinks(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = check_symlinks_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ a->pst = NULL; /* to be safe */ ++ return rc; ++} ++ ++ + #if defined(__CYGWIN__) + /* + * 1. Convert a path separator from '\' to '/' . +@@ -2509,15 +2669,17 @@ + * is set) if the path is absolute. + */ + static int +-cleanup_pathname(struct archive_write_disk *a) ++cleanup_pathname_fsobj(char *path, int *error_number, struct archive_string *error_string, int flags) + { + char *dest, *src; + char separator = '\0'; + +- dest = src = a->name; ++ dest = src = path; + if (*src == '\0') { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Invalid empty pathname"); ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Invalid empty pathname"); + return (ARCHIVE_FAILED); + } + +@@ -2526,9 +2688,11 @@ + #endif + /* Skip leading '/'. */ + if (*src == '/') { +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { +- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, +- "Path is absolute"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path is absolute"); + return (ARCHIVE_FAILED); + } + +@@ -2555,10 +2719,11 @@ + } else if (src[1] == '.') { + if (src[2] == '/' || src[2] == '\0') { + /* Conditionally warn about '..' */ +- if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { +- archive_set_error(&a->archive, +- ARCHIVE_ERRNO_MISC, +- "Path contains '..'"); ++ if (flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { ++ if (error_number) *error_number = ARCHIVE_ERRNO_MISC; ++ if (error_string) ++ archive_string_sprintf(error_string, ++ "Path contains '..'"); + return (ARCHIVE_FAILED); + } + } +@@ -2589,7 +2754,7 @@ + * We've just copied zero or more path elements, not including the + * final '/'. + */ +- if (dest == a->name) { ++ if (dest == path) { + /* + * Nothing got copied. The path must have been something + * like '.' or '/' or './' or '/././././/./'. +@@ -2604,6 +2769,21 @@ + return (ARCHIVE_OK); + } + ++static int ++cleanup_pathname(struct archive_write_disk *a) ++{ ++ struct archive_string error_string; ++ int error_number; ++ int rc; ++ archive_string_init(&error_string); ++ rc = cleanup_pathname_fsobj(a->name, &error_number, &error_string, a->flags); ++ if (rc != ARCHIVE_OK) { ++ archive_set_error(&a->archive, error_number, "%s", error_string.s); ++ } ++ archive_string_free(&error_string); ++ return rc; ++} ++ + /* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. +--- contrib/libarchive/libarchive/test/main.c.orig ++++ contrib/libarchive/libarchive/test/main.c +@@ -1396,6 +1396,31 @@ + return (0); + } + ++/* Verify mode of 'pathname'. */ ++int ++assertion_file_mode(const char *file, int line, const char *pathname, int expected_mode) ++{ ++ int mode; ++ int r; ++ ++ assertion_count(file, line); ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ failure_start(file, line, "assertFileMode not yet implemented for Windows"); ++#else ++ { ++ struct stat st; ++ r = lstat(pathname, &st); ++ mode = (int)(st.st_mode & 0777); ++ } ++ if (r == 0 && mode == expected_mode) ++ return (1); ++ failure_start(file, line, "File %s has mode %o, expected %o", ++ pathname, mode, expected_mode); ++#endif ++ failure_finish(NULL); ++ return (0); ++} ++ + /* Assert that 'pathname' is a dir. If mode >= 0, verify that too. */ + int + assertion_is_dir(const char *file, int line, const char *pathname, int mode) +--- contrib/libarchive/libarchive/test/test.h.orig ++++ contrib/libarchive/libarchive/test/test.h +@@ -176,6 +176,8 @@ + assertion_file_nlinks(__FILE__, __LINE__, pathname, nlinks) + #define assertFileSize(pathname, size) \ + assertion_file_size(__FILE__, __LINE__, pathname, size) ++#define assertFileMode(pathname, mode) \ ++ assertion_file_mode(__FILE__, __LINE__, pathname, mode) + #define assertTextFileContents(text, pathname) \ + assertion_text_file_contents(__FILE__, __LINE__, text, pathname) + #define assertFileContainsLinesAnyOrder(pathname, lines) \ +@@ -239,6 +241,7 @@ + int assertion_file_nlinks(const char *, int, const char *, int); + int assertion_file_not_exists(const char *, int, const char *); + int assertion_file_size(const char *, int, const char *, long); ++int assertion_file_mode(const char *, int, const char *, int); + int assertion_is_dir(const char *, int, const char *, int); + int assertion_is_hardlink(const char *, int, const char *, const char *); + int assertion_is_not_hardlink(const char *, int, const char *, const char *); +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure744.c +@@ -0,0 +1,95 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #744 describes a bug in the sandboxing code that ++ * causes very long pathnames to not get checked for symlinks. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure744) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ size_t buff_size = 8192; ++ char *buff = malloc(buff_size); ++ char *p = buff; ++ int n = 0; ++ int t; ++ ++ assert(buff != NULL); ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ while (p + 500 < buff + buff_size) { ++ memset(p, 'x', 100); ++ p += 100; ++ p[0] = '\0'; ++ ++ buff[0] = ((n / 1000) % 10) + '0'; ++ buff[1] = ((n / 100) % 10)+ '0'; ++ buff[2] = ((n / 10) % 10)+ '0'; ++ buff[3] = ((n / 1) % 10)+ '0'; ++ buff[4] = '_'; ++ ++n; ++ ++ /* Create a symlink pointing to the testworkdir */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFREG | 0777); ++ archive_entry_copy_symlink(ae, testworkdir); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ *p++ = '/'; ++ sprintf(p, "target%d", n); ++ ++ /* Try to create a file through the symlink, should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, buff); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ ++ t = archive_write_header(a, ae); ++ archive_entry_free(ae); ++ failure("Attempt to create target%d via %d-character symlink should have failed", n, (int)strlen(buff)); ++ if(!assertEqualInt(ARCHIVE_FAILED, t)) { ++ break; ++ } ++ } ++ archive_free(a); ++ free(buff); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure745.c +@@ -0,0 +1,79 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #745 describes a bug in the sandboxing code that ++ * allows one to use a symlink to edit the permissions on a file or ++ * directory outside of the sandbox. ++ */ ++ ++DEFINE_TEST(test_write_disk_secure745) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* The target dir: The one we're going to try to change permission on */ ++ assertMakeDir("target", 0700); ++ ++ /* The sandbox dir we're going to run inside of. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create a symlink pointing to the target directory */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Try to alter the target dir through the symlink; this should fail. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "sym"); ++ archive_entry_set_mode(ae, S_IFDIR | 0777); ++ assert(0 == archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Permission of target dir should not have changed. */ ++ assertFileMode("../target", 0700); ++ ++ assert(0 == archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- /dev/null ++++ contrib/libarchive/libarchive/test/test_write_disk_secure746.c +@@ -0,0 +1,129 @@ ++/*- ++ * Copyright (c) 2003-2007,2016 Tim Kientzle ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR ++ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ++ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ++ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ++ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ++ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ++ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ++ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++#include "test.h" ++__FBSDID("$FreeBSD$"); ++ ++#define UMASK 022 ++ ++/* ++ * Github Issue #746 describes a problem in which hardlink targets are ++ * not adequately checked and can be used to modify entries outside of ++ * the sandbox. ++ */ ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NODOTDOT disallows '..' in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746a) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NODOTDOT); ++ ++ /* Attempt to hardlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "../target/foo"); ++ assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualInt(ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++#endif ++} ++ ++/* ++ * Verify that ARCHIVE_EXTRACT_SECURE_NOSYMLINK disallows symlinks in hardlink ++ * targets. ++ */ ++DEFINE_TEST(test_write_disk_secure746b) ++{ ++#if defined(_WIN32) && !defined(__CYGWIN__) ++ skipping("archive_write_disk security checks not supported on Windows"); ++#else ++ struct archive *a; ++ struct archive_entry *ae; ++ ++ /* Start with a known umask. */ ++ assertUmask(UMASK); ++ ++ /* The target directory we're going to try to affect. */ ++ assertMakeDir("target", 0700); ++ assertMakeFile("target/foo", 0700, "unmodified"); ++ ++ /* The sandbox dir we're going to work within. */ ++ assertMakeDir("sandbox", 0700); ++ assertChdir("sandbox"); ++ ++ /* Create an archive_write_disk object. */ ++ assert((a = archive_write_disk_new()) != NULL); ++ archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_SYMLINKS); ++ ++ /* Create a symlink to the target directory. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "symlink"); ++ archive_entry_set_mode(ae, AE_IFLNK | 0777); ++ archive_entry_copy_symlink(ae, "../target"); ++ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae)); ++ archive_entry_free(ae); ++ ++ /* Attempt to hardlink to the target directory via the symlink. */ ++ assert((ae = archive_entry_new()) != NULL); ++ archive_entry_copy_pathname(ae, "bar"); ++ archive_entry_set_mode(ae, AE_IFREG | 0777); ++ archive_entry_set_size(ae, 8); ++ archive_entry_copy_hardlink(ae, "symlink/foo"); ++ assertEqualIntA(a, ARCHIVE_FAILED, archive_write_header(a, ae)); ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_data(a, "modified", 8)); ++ archive_entry_free(ae); ++ ++ /* Verify that target file contents are unchanged. */ ++ assertTextFileContents("unmodified", "../target/foo"); ++ ++ assertEqualIntA(a, ARCHIVE_FATAL, archive_write_close(a)); ++ archive_write_free(a); ++#endif ++} +--- lib/libarchive/tests/Makefile.orig ++++ lib/libarchive/tests/Makefile +@@ -177,6 +177,9 @@ + test_write_disk_no_hfs_compression.c \ + test_write_disk_perms.c \ + test_write_disk_secure.c \ ++ test_write_disk_secure744.c \ ++ test_write_disk_secure745.c \ ++ test_write_disk_secure746.c \ + test_write_disk_sparse.c \ + test_write_disk_symlink.c \ + test_write_disk_times.c \ Index: user/cperciva/freebsd-update-build/patches/11.0-RELEASE/0-EN-16:99.newvers =================================================================== --- user/cperciva/freebsd-update-build/patches/11.0-RELEASE/0-EN-16:99.newvers (nonexistent) +++ user/cperciva/freebsd-update-build/patches/11.0-RELEASE/0-EN-16:99.newvers (revision 307229) @@ -0,0 +1,13 @@ +Index: sys/conf/newvers.sh +=================================================================== +--- sys/conf/newvers.sh (revision 306799) ++++ sys/conf/newvers.sh (working copy) +@@ -32,7 +32,7 @@ + + TYPE="FreeBSD" + REVISION="11.0" +-BRANCH="RELEASE-p1" ++BRANCH="RELEASE" + if [ -n "${BRANCH_OVERRIDE}" ]; then + BRANCH=${BRANCH_OVERRIDE} + fi Index: user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:28.bind =================================================================== --- user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:28.bind (nonexistent) +++ user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:28.bind (revision 307229) @@ -0,0 +1,87 @@ +--- contrib/bind9/lib/dns/message.c.orig ++++ contrib/bind9/lib/dns/message.c +@@ -1736,7 +1736,7 @@ + if (r.length < DNS_MESSAGE_HEADERLEN) + return (ISC_R_NOSPACE); + +- if (r.length < msg->reserved) ++ if (r.length - DNS_MESSAGE_HEADERLEN < msg->reserved) + return (ISC_R_NOSPACE); + + /* +@@ -1863,8 +1863,29 @@ + + return (ISC_TRUE); + } ++#endif + +-#endif ++static isc_result_t ++renderset(dns_rdataset_t *rdataset, dns_name_t *owner_name, ++ dns_compress_t *cctx, isc_buffer_t *target, ++ unsigned int reserved, unsigned int options, unsigned int *countp) ++{ ++ isc_result_t result; ++ ++ /* ++ * Shrink the space in the buffer by the reserved amount. ++ */ ++ if (target->length - target->used < reserved) ++ return (ISC_R_NOSPACE); ++ ++ target->length -= reserved; ++ result = dns_rdataset_towire(rdataset, owner_name, ++ cctx, target, options, countp); ++ target->length += reserved; ++ ++ return (result); ++} ++ + isc_result_t + dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid, + unsigned int options) +@@ -1907,6 +1928,8 @@ + /* + * Shrink the space in the buffer by the reserved amount. + */ ++ if (msg->buffer->length - msg->buffer->used < msg->reserved) ++ return (ISC_R_NOSPACE); + msg->buffer->length -= msg->reserved; + + total = 0; +@@ -2183,9 +2206,8 @@ + * Render. + */ + count = 0; +- result = dns_rdataset_towire(msg->opt, dns_rootname, +- msg->cctx, msg->buffer, 0, +- &count); ++ result = renderset(msg->opt, dns_rootname, msg->cctx, ++ msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); +@@ -2201,9 +2223,8 @@ + if (result != ISC_R_SUCCESS) + return (result); + count = 0; +- result = dns_rdataset_towire(msg->tsig, msg->tsigname, +- msg->cctx, msg->buffer, 0, +- &count); ++ result = renderset(msg->tsig, msg->tsigname, msg->cctx, ++ msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); +@@ -2224,9 +2245,8 @@ + * the owner name of a SIG(0) is irrelevant, and will not + * be set in a message being rendered. + */ +- result = dns_rdataset_towire(msg->sig0, dns_rootname, +- msg->cctx, msg->buffer, 0, +- &count); ++ result = renderset(msg->sig0, dns_rootname, msg->cctx, ++ msg->buffer, msg->reserved, 0, &count); + msg->counts[DNS_SECTION_ADDITIONAL] += count; + if (result != ISC_R_SUCCESS) + return (result); Index: user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:29.bspatch =================================================================== --- user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:29.bspatch (nonexistent) +++ user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:29.bspatch (revision 307229) @@ -0,0 +1,325 @@ +--- usr.bin/bsdiff/bspatch/bspatch.c.orig ++++ usr.bin/bsdiff/bspatch/bspatch.c +@@ -27,56 +27,133 @@ + #include + __FBSDID("$FreeBSD$"); + ++#if defined(__FreeBSD__) ++#include ++#if __FreeBSD_version >= 1001511 ++#include ++#define HAVE_CAPSICUM ++#endif ++#endif ++ + #include +-#include ++#include ++#include ++#include ++#include ++#include ++#include + #include ++#include + #include +-#include + #include +-#include + + #ifndef O_BINARY + #define O_BINARY 0 + #endif ++#define HEADER_SIZE 32 ++ ++static char *newfile; ++static int dirfd = -1; ++ ++static void ++exit_cleanup(void) ++{ ++ ++ if (dirfd != -1 && newfile != NULL) ++ if (unlinkat(dirfd, newfile, 0)) ++ warn("unlinkat"); ++} + + static off_t offtin(u_char *buf) + { + off_t y; + +- y=buf[7]&0x7F; +- y=y*256;y+=buf[6]; +- y=y*256;y+=buf[5]; +- y=y*256;y+=buf[4]; +- y=y*256;y+=buf[3]; +- y=y*256;y+=buf[2]; +- y=y*256;y+=buf[1]; +- y=y*256;y+=buf[0]; ++ y = buf[7] & 0x7F; ++ y = y * 256; y += buf[6]; ++ y = y * 256; y += buf[5]; ++ y = y * 256; y += buf[4]; ++ y = y * 256; y += buf[3]; ++ y = y * 256; y += buf[2]; ++ y = y * 256; y += buf[1]; ++ y = y * 256; y += buf[0]; + +- if(buf[7]&0x80) y=-y; ++ if (buf[7] & 0x80) ++ y = -y; + +- return y; ++ return (y); + } + +-int main(int argc,char * argv[]) ++int main(int argc, char *argv[]) + { +- FILE * f, * cpf, * dpf, * epf; +- BZFILE * cpfbz2, * dpfbz2, * epfbz2; ++ FILE *f, *cpf, *dpf, *epf; ++ BZFILE *cpfbz2, *dpfbz2, *epfbz2; ++ char *directory, *namebuf; + int cbz2err, dbz2err, ebz2err; +- int fd; +- ssize_t oldsize,newsize; +- ssize_t bzctrllen,bzdatalen; +- u_char header[32],buf[8]; ++ int newfd, oldfd; ++ off_t oldsize, newsize; ++ off_t bzctrllen, bzdatalen; ++ u_char header[HEADER_SIZE], buf[8]; + u_char *old, *new; +- off_t oldpos,newpos; ++ off_t oldpos, newpos; + off_t ctrl[3]; +- off_t lenread; +- off_t i; ++ off_t i, lenread, offset; ++#ifdef HAVE_CAPSICUM ++ cap_rights_t rights_dir, rights_ro, rights_wr; ++#endif + + if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]); + + /* Open patch file */ + if ((f = fopen(argv[3], "rb")) == NULL) + err(1, "fopen(%s)", argv[3]); ++ /* Open patch file for control block */ ++ if ((cpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for diff block */ ++ if ((dpf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open patch file for extra block */ ++ if ((epf = fopen(argv[3], "rb")) == NULL) ++ err(1, "fopen(%s)", argv[3]); ++ /* open oldfile */ ++ if ((oldfd = open(argv[1], O_RDONLY | O_BINARY, 0)) < 0) ++ err(1, "open(%s)", argv[1]); ++ /* open directory where we'll write newfile */ ++ if ((namebuf = strdup(argv[2])) == NULL || ++ (directory = dirname(namebuf)) == NULL || ++ (dirfd = open(directory, O_DIRECTORY)) < 0) ++ err(1, "open %s", argv[2]); ++ free(namebuf); ++ if ((newfile = basename(argv[2])) == NULL) ++ err(1, "basename"); ++ /* open newfile */ ++ if ((newfd = openat(dirfd, newfile, ++ O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0666)) < 0) ++ err(1, "open(%s)", argv[2]); ++ atexit(exit_cleanup); ++ ++#ifdef HAVE_CAPSICUM ++ if (cap_enter() < 0) { ++ /* Failed to sandbox, fatal if CAPABILITY_MODE enabled */ ++ if (errno != ENOSYS) ++ err(1, "failed to enter security sandbox"); ++ } else { ++ /* Capsicum Available */ ++ cap_rights_init(&rights_ro, CAP_READ, CAP_FSTAT, CAP_SEEK); ++ cap_rights_init(&rights_wr, CAP_WRITE); ++ cap_rights_init(&rights_dir, CAP_UNLINKAT); ++ ++ if (cap_rights_limit(fileno(f), &rights_ro) < 0 || ++ cap_rights_limit(fileno(cpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(dpf), &rights_ro) < 0 || ++ cap_rights_limit(fileno(epf), &rights_ro) < 0 || ++ cap_rights_limit(oldfd, &rights_ro) < 0 || ++ cap_rights_limit(newfd, &rights_wr) < 0 || ++ cap_rights_limit(dirfd, &rights_dir) < 0) ++ err(1, "cap_rights_limit() failed, could not restrict" ++ " capabilities"); ++ } ++#endif + + /* + File format: +@@ -93,99 +170,99 @@ + */ + + /* Read header */ +- if (fread(header, 1, 32, f) < 32) { ++ if (fread(header, 1, HEADER_SIZE, f) < HEADER_SIZE) { + if (feof(f)) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + err(1, "fread(%s)", argv[3]); + } + + /* Check for appropriate magic */ + if (memcmp(header, "BSDIFF40", 8) != 0) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Read lengths from header */ +- bzctrllen=offtin(header+8); +- bzdatalen=offtin(header+16); +- newsize=offtin(header+24); +- if((bzctrllen<0) || (bzdatalen<0) || (newsize<0)) +- errx(1,"Corrupt patch\n"); ++ bzctrllen = offtin(header + 8); ++ bzdatalen = offtin(header + 16); ++ newsize = offtin(header + 24); ++ if (bzctrllen < 0 || bzctrllen > OFF_MAX - HEADER_SIZE || ++ bzdatalen < 0 || bzctrllen + HEADER_SIZE > OFF_MAX - bzdatalen || ++ newsize < 0 || newsize > SSIZE_MAX) ++ errx(1, "Corrupt patch"); + + /* Close patch file and re-open it via libbzip2 at the right places */ + if (fclose(f)) + err(1, "fclose(%s)", argv[3]); +- if ((cpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(cpf, 32, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)32); ++ offset = HEADER_SIZE; ++ if (fseeko(cpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err); +- if ((dpf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(dpf, 32 + bzctrllen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen)); ++ offset += bzctrllen; ++ if (fseeko(dpf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err); +- if ((epf = fopen(argv[3], "rb")) == NULL) +- err(1, "fopen(%s)", argv[3]); +- if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET)) +- err(1, "fseeko(%s, %lld)", argv[3], +- (long long)(32 + bzctrllen + bzdatalen)); ++ offset += bzdatalen; ++ if (fseeko(epf, offset, SEEK_SET)) ++ err(1, "fseeko(%s, %jd)", argv[3], (intmax_t)offset); + if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL) + errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err); + +- if(((fd=open(argv[1],O_RDONLY|O_BINARY,0))<0) || +- ((oldsize=lseek(fd,0,SEEK_END))==-1) || +- ((old=malloc(oldsize+1))==NULL) || +- (lseek(fd,0,SEEK_SET)!=0) || +- (read(fd,old,oldsize)!=oldsize) || +- (close(fd)==-1)) err(1,"%s",argv[1]); +- if((new=malloc(newsize+1))==NULL) err(1,NULL); ++ if ((oldsize = lseek(oldfd, 0, SEEK_END)) == -1 || ++ oldsize > SSIZE_MAX || ++ (old = malloc(oldsize)) == NULL || ++ lseek(oldfd, 0, SEEK_SET) != 0 || ++ read(oldfd, old, oldsize) != oldsize || ++ close(oldfd) == -1) ++ err(1, "%s", argv[1]); ++ if ((new = malloc(newsize)) == NULL) ++ err(1, NULL); + +- oldpos=0;newpos=0; +- while(newpos INT_MAX || ++ ctrl[1] < 0 || ctrl[1] > INT_MAX) ++ errx(1, "Corrupt patch"); + + /* Sanity-check */ +- if(newpos+ctrl[0]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[0] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read diff string */ + lenread = BZ2_bzRead(&dbz2err, dpfbz2, new + newpos, ctrl[0]); + if ((lenread < ctrl[0]) || + ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Add old data to diff string */ +- for(i=0;i=0) && (oldpos+i= 0) && (oldpos + i < oldsize)) ++ new[newpos + i] += old[oldpos + i]; + + /* Adjust pointers */ +- newpos+=ctrl[0]; +- oldpos+=ctrl[0]; ++ newpos += ctrl[0]; ++ oldpos += ctrl[0]; + + /* Sanity-check */ +- if(newpos+ctrl[1]>newsize) +- errx(1,"Corrupt patch\n"); ++ if (newpos + ctrl[1] > newsize) ++ errx(1, "Corrupt patch"); + + /* Read extra string */ + lenread = BZ2_bzRead(&ebz2err, epfbz2, new + newpos, ctrl[1]); + if ((lenread < ctrl[1]) || + ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END))) +- errx(1, "Corrupt patch\n"); ++ errx(1, "Corrupt patch"); + + /* Adjust pointers */ + newpos+=ctrl[1]; +@@ -200,12 +277,13 @@ + err(1, "fclose(%s)", argv[3]); + + /* Write the new file */ +- if(((fd=open(argv[2],O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,0666))<0) || +- (write(fd,new,newsize)!=newsize) || (close(fd)==-1)) +- err(1,"%s",argv[2]); ++ if (write(newfd, new, newsize) != newsize || close(newfd) == -1) ++ err(1, "%s", argv[2]); ++ /* Disable atexit cleanup */ ++ newfile = NULL; + + free(new); + free(old); + +- return 0; ++ return (0); + } Index: user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:30.portsnap =================================================================== --- user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:30.portsnap (nonexistent) +++ user/cperciva/freebsd-update-build/patches/9.3-RELEASE/48-SA-16:30.portsnap (revision 307229) @@ -0,0 +1,49 @@ +--- usr.sbin/portsnap/portsnap/portsnap.sh.orig ++++ usr.sbin/portsnap/portsnap/portsnap.sh +@@ -609,7 +609,7 @@ + # Verify a list of files + fetch_snapshot_verify() { + while read F; do +- if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then ++ if [ "`gunzip -c < snap/${F}.gz | ${SHA256} -q`" != ${F} ]; then + echo "snapshot corrupt." + return 1 + fi +@@ -644,11 +644,18 @@ + cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1 + # Extract the index + rm -f INDEX.new +- gunzip -c snap/`look INDEX tINDEX.new | ++ gunzip -c < snap/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + # Verify the snapshot contents + cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1 ++ cut -f 2 -d '|' tINDEX.new INDEX.new | sort -u > files.expected ++ find snap -mindepth 1 | sed -E 's^snap/(.*)\.gz^\1^' | sort > files.snap ++ if ! cmp -s files.expected files.snap; then ++ echo "unexpected files in snapshot." ++ return 1 ++ fi ++ rm files.expected files.snap + echo "done." + + # Move files into their proper locations +@@ -737,7 +744,7 @@ + echo "done." + + # Extract the index +- gunzip -c files/`look INDEX tINDEX.new | ++ gunzip -c < files/`look INDEX tINDEX.new | + cut -f 2 -d '|'`.gz > INDEX.new + fetch_index_sanity || return 1 + +@@ -842,7 +849,7 @@ + echo -n "$1 not provided by portsnap server; " + echo "$2 not being generated." + else +- gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | ++ gunzip -c < "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX | + cut -f 2 -d '|'`.gz" | + cat - ${LOCALDESC} | + ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2