1270 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			1270 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| --- 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			\
 |