libarchive: do not extract unsafe symlinks unless $EXTRACT_UNSAFE_SYMLINKS=1
No binary (build failed)
—
(log yok)
| @@ -11,6 +11,8 @@ COMMON_FILES:= \ | |
| data_skip.o \ | |
| data_extract_all.o \ | |
| data_extract_to_stdout.o \ | |
| + | \ |
| + | unsafe_symlink_target.o \ |
| \ | |
| filter_accept_all.o \ | |
| filter_accept_list.o \ | |
| @@ -129,9 +129,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { | |
| /* shared message */ | |
| bb_perror_msg("can't create %slink '%s' to '%s'", | |
| - | "hard", |
| - | dst_name, |
| - | hard_link |
| + | "hard", dst_name, hard_link |
| ); | |
| } | |
| /* Hardlinks have no separate mode/ownership, skip chown/chmod */ | |
| @@ -181,7 +179,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) | |
| /* To avoid a directory traversal attack via symlinks, | |
| - | * for certain link targets postpone creation of symlinks. |
| + | * do not restore symlinks with ".." components |
| + | * or symlinks starting with "/", unless a magic |
| + | * envvar is set. |
| * | |
| * For example, consider a .tar created via: | |
| * $ tar cvf bug.tar anything.txt | |
| @@ -199,24 +199,17 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| * | |
| * Untarring bug.tar would otherwise place evil.py in '/tmp'. | |
| */ | |
| - | if (file_header->link_target[0] == '/' |
| - | || strstr(file_header->link_target, "..") |
| - | ) { |
| - | llist_add_to(&archive_handle->symlink_placeholders, |
| - | xasprintf("%s%c%s", file_header->name, '\0', file_header->link_target) |
| - | ); |
| - | break; |
| - | } |
| - | res = symlink(file_header->link_target, dst_name); |
| - | if (res != 0 |
| - | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| - | ) { |
| - | /* shared message */ |
| - | bb_perror_msg("can't create %slink '%s' to '%s'", |
| - | "sym", |
| - | dst_name, |
| - | file_header->link_target |
| - | ); |
| + | if (!unsafe_symlink_target(file_header->link_target)) { |
| + | res = symlink(file_header->link_target, dst_name); |
| + | if (res != 0 |
| + | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| + | ) { |
| + | /* shared message */ |
| + | bb_perror_msg("can't create %slink '%s' to '%s'", |
| + | "sym", |
| + | dst_name, file_header->link_target |
| + | ); |
| + | } |
| } | |
| break; | |
| case S_IFSOCK: | |
| @@ -0,0 +1,48 @@ | |
| + | /* 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" |
| + | |
| + | int FAST_FUNC unsafe_symlink_target(const char *target) |
| + | { |
| + | const char *dot; |
| + | |
| + | if (target[0] == '/') { |
| + | const char *var; |
| + | unsafe: |
| + | var = getenv("EXTRACT_UNSAFE_SYMLINKS"); |
| + | if (var) { |
| + | if (LONE_CHAR(var, '1')) |
| + | return 0; /* pretend it's safe */ |
| + | return 1; /* "UNSAFE!" */ |
| + | } |
| + | bb_error_msg("skipping unsafe symlink to '%s' in archive," |
| + | " set %s=1 to extract", |
| + | target, |
| + | "EXTRACT_UNSAFE_SYMLINKS" |
| + | ); |
| + | /* Prevent further messages */ |
| + | setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0); |
| + | return 1; /* "UNSAFE!" */ |
| + | } |
| + | |
| + | dot = target; |
| + | for (;;) { |
| + | dot = strchr(dot, '.'); |
| + | if (!dot) |
| + | return 0; /* safe target */ |
| + | |
| + | /* Is it a path component starting with ".."? */ |
| + | if ((dot[1] == '.') |
| + | && (dot == target || dot[-1] == '/') |
| + | /* Is it exactly ".."? */ |
| + | && (dot[2] == '/' || dot[2] == '\0') |
| + | ) { |
| + | goto unsafe; |
| + | } |
| + | /* NB: it can even be trailing ".", should only add 1 */ |
| + | dot += 1; |
| + | } |
| + | } |
| @@ -278,23 +278,6 @@ static void chksum_and_xwrite(int fd, struct tar_header_t* hp) | |
| xwrite(fd, hp, sizeof(*hp)); | |
| } | |
| - | static void replace_symlink_placeholders(llist_t *list) |
| - | { |
| - | while (list) { |
| - | char *target; |
| - | |
| - | target = list->data + strlen(list->data) + 1; |
| - | if (symlink(target, list->data)) { |
| - | /* shared message */ |
| - | bb_error_msg_and_die("can't create %slink '%s' to '%s'", |
| - | "sym", |
| - | list->data, target |
| - | ); |
| - | } |
| - | list = list->link; |
| - | } |
| - | } |
| - | |
| #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS | |
| static void writeLongname(int fd, int type, const char *name, int dir) | |
| { | |
| @@ -1255,8 +1238,6 @@ int tar_main(int argc UNUSED_PARAM, char **argv) | |
| while (get_header_tar(tar_handle) == EXIT_SUCCESS) | |
| bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ | |
| - | replace_symlink_placeholders(tar_handle->symlink_placeholders); |
| - | |
| /* Check that every file that should have been extracted was */ | |
| while (tar_handle->accept) { | |
| if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) | |
| @@ -368,9 +368,15 @@ static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) | |
| target[xstate.mem_output_size] = '\0'; | |
| #endif | |
| } | |
| + | if (!unsafe_symlink_target(target)) { |
| //TODO: libbb candidate | |
| - | if (symlink(target, dst_fn)) |
| - | bb_perror_msg_and_die("can't create symlink '%s'", dst_fn); |
| + | if (symlink(target, dst_fn)) { |
| + | /* shared message */ |
| + | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
| + | "sym", dst_fn, target |
| + | ); |
| + | } |
| + | } |
| free(target); | |
| } | |
| #endif | |
| @@ -31,9 +31,8 @@ int link_main(int argc UNUSED_PARAM, char **argv) | |
| argv += optind; | |
| if (link(argv[0], argv[1]) != 0) { | |
| /* shared message */ | |
| - | bb_perror_msg_and_die("can't create %slink " |
| - | "'%s' to '%s'", "hard", |
| - | argv[1], argv[0] |
| + | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
| + | "hard", argv[1], argv[0] |
| ); | |
| } | |
| return EXIT_SUCCESS; | |
| @@ -64,9 +64,6 @@ typedef struct archive_handle_t { | |
| /* Currently processed file's header */ | |
| file_header_t *file_header; | |
| - | /* List of symlink placeholders */ |
| - | llist_t *symlink_placeholders; |
| - | |
| /* Process the header component, e.g. tar -t */ | |
| void FAST_FUNC (*action_header)(const file_header_t *); | |
| @@ -200,6 +197,7 @@ 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; | |
| + | int unsafe_symlink_target(const char *target) FAST_FUNC; |
| void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; | |
| const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; | |
| @@ -371,7 +371,10 @@ int FAST_FUNC copy_file(const char *source, const char *dest, int flags) | |
| int r = symlink(lpath, dest); | |
| free(lpath); | |
| if (r < 0) { | |
| - | bb_perror_msg("can't create symlink '%s'", dest); |
| + | /* shared message */ |
| + | bb_perror_msg("can't create %slink '%s' to '%s'", |
| + | "sym", dest, lpath |
| + | ); |
| return -1; | |
| } | |
| if (flags & FILEUTILS_PRESERVE_STATUS) | |
| @@ -279,7 +279,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |
| testing "tar does not extract into symlinks" "\ | |
| >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | |
| " "\ | |
| - | tar: can't create symlink 'passwd' to '/tmp/passwd' |
| + | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| 0 | |
| " \ | |
| "" "\ | |
| @@ -299,7 +299,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |
| testing "tar -k does not extract into symlinks" "\ | |
| >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | |
| " "\ | |
| - | tar: can't create symlink 'passwd' to '/tmp/passwd' |
| + | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| 0 | |
| " \ | |
| "" "\ | |
| @@ -324,11 +324,11 @@ rm -rf etc usr | |
| ' "\ | |
| etc/ssl/certs/3b2716e5.0 | |
| etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | |
| + | tar: skipping unsafe symlink to '/usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| etc/ssl/certs/f80cc7f6.0 | |
| usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt | |
| 0 | |
| etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | |
| - | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem -> /usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt |
| etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | |
| " \ | |
| "" "" | |
| @@ -346,9 +346,9 @@ ls symlink/bb_test_evilfile | |
| ' "\ | |
| anything.txt | |
| symlink | |
| + | tar: skipping unsafe symlink to '/tmp' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| symlink/bb_test_evilfile | |
| - | tar: can't create symlink 'symlink' to '/tmp' |
| - | 1 |
| + | 0 |
| ls: /tmp/bb_test_evilfile: No such file or directory | |
| ls: bb_test_evilfile: No such file or directory | |
| symlink/bb_test_evilfile | |