unzip: prevent attacks via malicious filenames
No binary (build failed)
—
(log yok)
| @@ -30,6 +30,7 @@ COMMON_FILES:= \ | |
| DPKG_FILES:= \ | |
| unpack_ar_archive.o \ | |
| filter_accept_list_reassign.o \ | |
| + | unsafe_prefix.o \ |
| get_header_ar.o \ | |
| get_header_tar.o \ | |
| get_header_tar_gz.o \ | |
| @@ -44,7 +45,7 @@ lib-$(CONFIG_DPKG_DEB) += $(DPKG_FILES) | |
| lib-$(CONFIG_AR) += get_header_ar.o unpack_ar_archive.o | |
| lib-$(CONFIG_CPIO) += get_header_cpio.o | |
| - | lib-$(CONFIG_TAR) += get_header_tar.o |
| + | lib-$(CONFIG_TAR) += get_header_tar.o unsafe_prefix.o |
| lib-$(CONFIG_FEATURE_TAR_TO_COMMAND) += data_extract_to_command.o | |
| lib-$(CONFIG_LZOP) += lzo1x_1.o lzo1x_1o.o lzo1x_d.o | |
| lib-$(CONFIG_LZOP_COMPR_HIGH) += lzo1x_9x.o | |
| @@ -53,7 +54,7 @@ lib-$(CONFIG_UNLZMA) += open_transformer.o decompress_unlzma. | |
| lib-$(CONFIG_UNXZ) += open_transformer.o decompress_unxz.o | |
| lib-$(CONFIG_GUNZIP) += open_transformer.o decompress_gunzip.o | |
| lib-$(CONFIG_UNCOMPRESS) += open_transformer.o decompress_uncompress.o | |
| - | lib-$(CONFIG_UNZIP) += open_transformer.o decompress_gunzip.o |
| + | lib-$(CONFIG_UNZIP) += open_transformer.o decompress_gunzip.o unsafe_prefix.o |
| lib-$(CONFIG_RPM2CPIO) += open_transformer.o decompress_gunzip.o get_header_cpio.o | |
| lib-$(CONFIG_RPM) += open_transformer.o decompress_gunzip.o get_header_cpio.o | |
| @@ -17,36 +17,6 @@ | |
| typedef uint32_t aliased_uint32_t FIX_ALIASING; | |
| typedef off_t aliased_off_t FIX_ALIASING; | |
| - | |
| - | const char* FAST_FUNC strip_unsafe_prefix(const char *str) |
| - | { |
| - | const char *cp = str; |
| - | while (1) { |
| - | char *cp2; |
| - | if (*cp == '/') { |
| - | cp++; |
| - | continue; |
| - | } |
| - | if (strncmp(cp, "/../"+1, 3) == 0) { |
| - | cp += 3; |
| - | continue; |
| - | } |
| - | cp2 = strstr(cp, "/../"); |
| - | if (!cp2) |
| - | break; |
| - | cp = cp2 + 4; |
| - | } |
| - | if (cp != str) { |
| - | static smallint warned = 0; |
| - | if (!warned) { |
| - | warned = 1; |
| - | bb_error_msg("removing leading '%.*s' from member names", |
| - | (int)(cp - str), str); |
| - | } |
| - | } |
| - | return cp; |
| - | } |
| - | |
| /* NB: _DESTROYS_ str[len] character! */ | |
| static unsigned long long getOctal(char *str, int len) | |
| { | |
| @@ -0,0 +1,36 @@ | |
| + | /* vi: set sw=4 ts=4: */ |
| + | /* |
| + | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| + | */ |
| + | |
| + | #include "libbb.h" |
| + | #include "bb_archive.h" |
| + | |
| + | const char* FAST_FUNC strip_unsafe_prefix(const char *str) |
| + | { |
| + | const char *cp = str; |
| + | while (1) { |
| + | char *cp2; |
| + | if (*cp == '/') { |
| + | cp++; |
| + | continue; |
| + | } |
| + | if (strncmp(cp, "/../"+1, 3) == 0) { |
| + | cp += 3; |
| + | continue; |
| + | } |
| + | cp2 = strstr(cp, "/../"); |
| + | if (!cp2) |
| + | break; |
| + | cp = cp2 + 4; |
| + | } |
| + | if (cp != str) { |
| + | static smallint warned = 0; |
| + | if (!warned) { |
| + | warned = 1; |
| + | bb_error_msg("removing leading '%.*s' from member names", |
| + | (int)(cp - str), str); |
| + | } |
| + | } |
| + | return cp; |
| + | } |
| @@ -596,14 +596,18 @@ int unzip_main(int argc, char **argv) | |
| /* Skip extra header bytes */ | |
| unzip_skip(zip_header.formatted.extra_len); | |
| + | /* Guard against "/abspath", "/../" and similar attacks */ |
| + | overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn)); |
| + | |
| /* Filter zip entries */ | |
| if (find_list_entry(zreject, dst_fn) | |
| || (zaccept && !find_list_entry(zaccept, dst_fn)) | |
| ) { /* Skip entry */ | |
| i = 'n'; | |
| - | } else { /* Extract entry */ |
| - | if (listing) { /* List entry */ |
| + | } else { |
| + | if (listing) { |
| + | /* List entry */ |
| unsigned dostime = zip_header.formatted.modtime | (zip_header.formatted.moddate << 16); | |
| if (!verbose) { | |
| // " Length Date Time Name\n" | |
| @@ -639,9 +643,11 @@ int unzip_main(int argc, char **argv) | |
| total_size += zip_header.formatted.cmpsize; | |
| } | |
| i = 'n'; | |
| - | } else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */ |
| + | } else if (dst_fd == STDOUT_FILENO) { |
| + | /* Extracting to STDOUT */ |
| i = -1; | |
| - | } else if (last_char_is(dst_fn, '/')) { /* Extract directory */ |
| + | } else if (last_char_is(dst_fn, '/')) { |
| + | /* Extract directory */ |
| if (stat(dst_fn, &stat_buf) == -1) { | |
| if (errno != ENOENT) { | |
| bb_perror_msg_and_die("can't stat '%s'", dst_fn); | |
| @@ -655,22 +661,27 @@ int unzip_main(int argc, char **argv) | |
| } | |
| } else { | |
| if (!S_ISDIR(stat_buf.st_mode)) { | |
| - | bb_error_msg_and_die("'%s' exists but is not directory", dst_fn); |
| + | bb_error_msg_and_die("'%s' exists but is not a %s", |
| + | dst_fn, "directory"); |
| } | |
| } | |
| i = 'n'; | |
| - | } else { /* Extract file */ |
| + | } else { |
| + | /* Extract file */ |
| check_file: | |
| - | if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */ |
| + | if (stat(dst_fn, &stat_buf) == -1) { |
| + | /* File does not exist */ |
| if (errno != ENOENT) { | |
| bb_perror_msg_and_die("can't stat '%s'", dst_fn); | |
| } | |
| i = 'y'; | |
| - | } else { /* File already exists */ |
| + | } else { |
| + | /* File already exists */ |
| if (overwrite == O_NEVER) { | |
| i = 'n'; | |
| - | } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */ |
| + | } else if (S_ISREG(stat_buf.st_mode)) { |
| + | /* File is regular file */ |
| if (overwrite == O_ALWAYS) { | |
| i = 'y'; | |
| } else { | |
| @@ -678,8 +689,10 @@ int unzip_main(int argc, char **argv) | |
| my_fgets80(key_buf); | |
| i = key_buf[0]; | |
| } | |
| - | } else { /* File is not regular file */ |
| - | bb_error_msg_and_die("'%s' exists but is not regular file", dst_fn); |
| + | } else { |
| + | /* File is not regular file */ |
| + | bb_error_msg_and_die("'%s' exists but is not a %s", |
| + | dst_fn, "regular file"); |
| } | |
| } | |
| } | |