tar: strip unsafe hardlink components - GNU tar does the same
No binary (build failed)
gcc -Wp,-MD,applets/.applet_tables.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -o applets/applet_tables applets/applet_tables.c
(log yok)
| @@ -66,8 +66,8 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| } | |
| #endif | |
| #if ENABLE_FEATURE_PATH_TRAVERSAL_PROTECTION | |
| - | /* Strip leading "/" and up to last "/../" path component */ |
| - | dst_name = (char *)strip_unsafe_prefix(dst_name); |
| + | /* Skip leading "/" and past last ".." path component */ |
| + | dst_name = (char *)skip_unsafe_prefix(dst_name); |
| #endif | |
| // ^^^ This may be a problem if some applets do need to extract absolute names. | |
| // (Probably will need to invent ARCHIVE_ALLOW_UNSAFE_NAME flag). | |
| @@ -185,8 +185,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| /* To avoid a directory traversal attack via symlinks, | |
| * do not restore symlinks with ".." components | |
| - | * or symlinks starting with "/", unless a magic |
| - | * envvar is set. |
| + | * or symlinks starting with "/" |
| * | |
| * For example, consider a .tar created via: | |
| * $ tar cvf bug.tar anything.txt | |
| @@ -454,8 +454,15 @@ char FAST_FUNC get_header_tar(archive_handle_t *archive_handle) | |
| #endif | |
| /* Everything up to and including last ".." component is stripped */ | |
| - | overlapping_strcpy(file_header->name, strip_unsafe_prefix(file_header->name)); |
| - | //TODO: do the same for file_header->link_target? |
| + | strip_unsafe_prefix(file_header->name); |
| + | if (file_header->link_target) { |
| + | /* GNU tar 1.34 examples: |
| + | * tar: Removing leading '/' from hard link targets |
| + | * tar: Removing leading '../' from hard link targets |
| + | * tar: Removing leading 'etc/../' from hard link targets |
| + | */ |
| + | strip_unsafe_prefix(file_header->link_target); |
| + | } |
| /* Strip trailing '/' in directories */ | |
| /* Must be done after mode is set as '/' is used to check if it's a directory */ | |
| @@ -5,11 +5,11 @@ | |
| #include "libbb.h" | |
| #include "bb_archive.h" | |
| - | const char* FAST_FUNC strip_unsafe_prefix(const char *str) |
| + | const char* FAST_FUNC skip_unsafe_prefix(const char *str) |
| { | |
| const char *cp = str; | |
| while (1) { | |
| - | char *cp2; |
| + | const char *cp2; |
| if (*cp == '/') { | |
| cp++; | |
| continue; | |
| @@ -22,10 +22,25 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str) | |
| cp += 3; | |
| continue; | |
| } | |
| - | cp2 = strstr(cp, "/../"); |
| + | cp2 = cp; |
| + | find_dotdot: |
| + | cp2 = strstr(cp2, "/.."); |
| if (!cp2) | |
| - | break; |
| - | cp = cp2 + 4; |
| + | break; /* No (more) malicious components */ |
| + | |
| + | /* We found "/..something" */ |
| + | cp2 += 3; |
| + | if (*cp2 != '/') { |
| + | if (*cp2 == '\0') { |
| + | /* Trailing "/..": malicious, return "" */ |
| + | /* (causes harmless errors trying to create or hardlink a file named "") */ |
| + | return cp2; |
| + | } |
| + | /* "/..name" is not malicious, look for next "/.." */ |
| + | goto find_dotdot; |
| + | } |
| + | /* Found "/../": malicious, advance past it */ |
| + | cp = cp2 + 1; |
| } | |
| if (cp != str) { | |
| static smallint warned = 0; | |
| @@ -37,3 +52,8 @@ const char* FAST_FUNC strip_unsafe_prefix(const char *str) | |
| } | |
| return cp; | |
| } | |
| + | |
| + | void FAST_FUNC strip_unsafe_prefix(char *str) |
| + | { |
| + | overlapping_strcpy(str, skip_unsafe_prefix(str)); |
| + | } |
| @@ -36,6 +36,7 @@ void FAST_FUNC create_links_from_list(llist_t *list) | |
| *list->data ? "hard" : "sym", | |
| list->data + 1, target | |
| ); | |
| + | /* Note: GNU tar 1.34 errors out only _after_ all links are (attempted to be) created */ |
| } | |
| list = list->link; | |
| } | |
| @@ -475,7 +475,7 @@ static int FAST_FUNC writeFileToTarball(struct recursive_state *state, | |
| DBG("writeFileToTarball('%s')", fileName); | |
| /* Strip leading '/' and such (must be before memorizing hardlink's name) */ | |
| - | header_name = strip_unsafe_prefix(fileName); |
| + | header_name = skip_unsafe_prefix(fileName); |
| if (header_name[0] == '\0') | |
| return TRUE; | |
| @@ -860,7 +860,7 @@ int unzip_main(int argc, char **argv) | |
| /* Guard against "/abspath", "/../" and similar attacks */ | |
| // NB: UnZip 6.00 has option -: to disable this | |
| - | overlapping_strcpy(dst_fn, strip_unsafe_prefix(dst_fn)); |
| + | strip_unsafe_prefix(dst_fn); |
| /* Filter zip entries */ | |
| if (find_list_entry(zreject, dst_fn) | |
| @@ -202,7 +202,8 @@ char get_header_tar_xz(archive_handle_t *archive_handle) FAST_FUNC; | |
| void seek_by_jump(int fd, off_t amount) FAST_FUNC; | |
| void seek_by_read(int fd, off_t amount) FAST_FUNC; | |
| - | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; |
| + | const char *skip_unsafe_prefix(const char *str) FAST_FUNC; |
| + | void strip_unsafe_prefix(char *str) FAST_FUNC; |
| void create_or_remember_link(llist_t **link_placeholders, | |
| const char *target, | |
| const char *linkname, | |
| @@ -213,7 +213,8 @@ int main(int argc, char **argv) | |
| write_and_die(1, "Status: 429 Too Many Requests\r\n" | |
| "Content-Type: text/plain\r\n" | |
| "Retry-After: 60\r\n" | |
| - | "Connection: close\r\n\r\n" |
| + | "Connection: close\r\n" |
| + | "\r\n" |
| "Too many concurrent requests\n" | |
| ); | |
| return 0; | |
| @@ -223,7 +224,9 @@ exec: | |
| execv(argv[0], argv); | |
| full_write2(2, "can't execute", argv[0]); | |
| write_and_die(1, "Status: 500 Internal Server Error\r\n" | |
| - | "Content-Type: text/plain\r\n\r\n" |
| - | "Failed to execute binary\n"); |
| + | "Content-Type: text/plain\r\n" |
| + | "\r\n" |
| + | "Can't execute binary\n" |
| + | ); |
| return 1; | |
| - | } |
| + | } |