tar,unzip: postpone creation of symlinks with "suspicious" targets
No binary (build failed)
—
(log yok)
| @@ -107,9 +107,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| } | |
| } | |
| else if (existing_sb.st_mtime >= file_header->mtime) { | |
| - | if (!(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| - | && !S_ISDIR(file_header->mode) |
| - | ) { |
| + | if (!S_ISDIR(file_header->mode)) { |
| bb_error_msg("%s not created: newer or " | |
| "same age file exists", dst_name); | |
| } | |
| @@ -125,7 +123,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| /* Handle hard links separately */ | |
| if (hard_link) { | |
| res = link(hard_link, dst_name); | |
| - | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { |
| + | if (res != 0) { |
| /* shared message */ | |
| bb_perror_msg("can't create %slink '%s' to '%s'", | |
| "hard", dst_name, hard_link | |
| @@ -165,10 +163,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| } | |
| case S_IFDIR: | |
| res = mkdir(dst_name, file_header->mode); | |
| - | if ((res == -1) |
| + | if ((res != 0) |
| && (errno != EISDIR) /* btw, Linux doesn't return this */ | |
| && (errno != EEXIST) | |
| - | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| ) { | |
| bb_perror_msg("can't make dir %s", dst_name); | |
| } | |
| @@ -198,27 +195,16 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |
| * | |
| * Untarring bug.tar would otherwise place evil.py in '/tmp'. | |
| */ | |
| - | 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 |
| - | ); |
| - | } |
| - | } |
| + | create_or_remember_symlink(&archive_handle->symlink_placeholders, |
| + | file_header->link_target, |
| + | dst_name); |
| break; | |
| case S_IFSOCK: | |
| case S_IFBLK: | |
| case S_IFCHR: | |
| case S_IFIFO: | |
| res = mknod(dst_name, file_header->mode, file_header->device); | |
| - | if ((res == -1) |
| - | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| - | ) { |
| + | if (res != 0) { |
| bb_perror_msg("can't create node %s", dst_name); | |
| } | |
| break; | |
| @@ -5,44 +5,37 @@ | |
| #include "libbb.h" | |
| #include "bb_archive.h" | |
| - | int FAST_FUNC unsafe_symlink_target(const char *target) |
| + | void FAST_FUNC create_or_remember_symlink(llist_t **symlink_placeholders, |
| + | const char *target, |
| + | const char *linkname) |
| { | |
| - | 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" |
| + | if (target[0] == '/' || strstr(target, "..")) { |
| + | llist_add_to(symlink_placeholders, |
| + | xasprintf("%s%c%s", linkname, '\0', target) |
| + | ); |
| + | return; |
| + | } |
| + | if (symlink(target, linkname) != 0) { |
| + | /* shared message */ |
| + | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
| + | "sym", linkname, target |
| ); | |
| - | /* Prevent further messages */ |
| - | setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0); |
| - | return 1; /* "UNSAFE!" */ |
| } | |
| + | } |
| - | dot = target; |
| - | for (;;) { |
| - | dot = strchr(dot, '.'); |
| - | if (!dot) |
| - | return 0; /* safe target */ |
| + | void FAST_FUNC create_symlinks_from_list(llist_t *list) |
| + | { |
| + | while (list) { |
| + | char *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; |
| + | 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 |
| + | ); |
| } | |
| - | /* NB: it can even be trailing ".", should only add 1 */ |
| - | dot += 1; |
| + | list = list->link; |
| } | |
| } | |
| @@ -1244,6 +1244,8 @@ 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 */ | |
| + | create_symlinks_from_list(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) | |
| @@ -345,7 +345,9 @@ static void unzip_create_leading_dirs(const char *fn) | |
| } | |
| #if ENABLE_FEATURE_UNZIP_CDF | |
| - | static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) |
| + | static void unzip_extract_symlink(llist_t **symlink_placeholders, |
| + | zip_header_t *zip, |
| + | const char *dst_fn) |
| { | |
| char *target; | |
| @@ -370,15 +372,9 @@ 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)) { |
| - | /* shared message */ |
| - | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
| - | "sym", dst_fn, target |
| - | ); |
| - | } |
| - | } |
| + | create_or_remember_symlink(symlink_placeholders, |
| + | target, |
| + | dst_fn); |
| free(target); | |
| } | |
| #endif | |
| @@ -490,6 +486,9 @@ int unzip_main(int argc, char **argv) | |
| llist_t *zaccept = NULL; | |
| llist_t *zreject = NULL; | |
| char *base_dir = NULL; | |
| + | #if ENABLE_FEATURE_UNZIP_CDF |
| + | llist_t *symlink_placeholders = NULL; |
| + | #endif |
| int i; | |
| char key_buf[80]; /* must match size used by my_fgets80 */ | |
| @@ -954,7 +953,7 @@ int unzip_main(int argc, char **argv) | |
| #if ENABLE_FEATURE_UNZIP_CDF | |
| if (S_ISLNK(file_mode)) { | |
| if (dst_fd != STDOUT_FILENO) /* not -p? */ | |
| - | unzip_extract_symlink(&zip, dst_fn); |
| + | unzip_extract_symlink(&symlink_placeholders, &zip, dst_fn); |
| } else | |
| #endif | |
| { | |
| @@ -990,6 +989,10 @@ int unzip_main(int argc, char **argv) | |
| total_entries++; | |
| } | |
| + | #if ENABLE_FEATURE_UNZIP_CDF |
| + | create_symlinks_from_list(symlink_placeholders); |
| + | #endif |
| + | |
| if ((opts & OPT_l) && quiet <= 1) { | |
| if (!verbose) { | |
| // " Length Date Time Name\n" | |
| @@ -64,6 +64,9 @@ 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 *); | |
| @@ -119,15 +122,14 @@ typedef struct archive_handle_t { | |
| #define ARCHIVE_RESTORE_DATE (1 << 0) | |
| #define ARCHIVE_CREATE_LEADING_DIRS (1 << 1) | |
| #define ARCHIVE_UNLINK_OLD (1 << 2) | |
| - | #define ARCHIVE_EXTRACT_QUIET (1 << 3) |
| - | #define ARCHIVE_EXTRACT_NEWER (1 << 4) |
| - | #define ARCHIVE_DONT_RESTORE_OWNER (1 << 5) |
| - | #define ARCHIVE_DONT_RESTORE_PERM (1 << 6) |
| - | #define ARCHIVE_NUMERIC_OWNER (1 << 7) |
| - | #define ARCHIVE_O_TRUNC (1 << 8) |
| - | #define ARCHIVE_REMEMBER_NAMES (1 << 9) |
| + | #define ARCHIVE_EXTRACT_NEWER (1 << 3) |
| + | #define ARCHIVE_DONT_RESTORE_OWNER (1 << 4) |
| + | #define ARCHIVE_DONT_RESTORE_PERM (1 << 5) |
| + | #define ARCHIVE_NUMERIC_OWNER (1 << 6) |
| + | #define ARCHIVE_O_TRUNC (1 << 7) |
| + | #define ARCHIVE_REMEMBER_NAMES (1 << 8) |
| #if ENABLE_RPM | |
| - | #define ARCHIVE_REPLACE_VIA_RENAME (1 << 10) |
| + | #define ARCHIVE_REPLACE_VIA_RENAME (1 << 9) |
| #endif | |
| @@ -197,7 +199,10 @@ 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 create_or_remember_symlink(llist_t **symlink_placeholders, |
| + | const char *target, |
| + | const char *linkname) FAST_FUNC; |
| + | void create_symlinks_from_list(llist_t *list) 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; | |
| @@ -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: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| + | tar: can't create symlink 'passwd' to '/tmp/passwd' |
| 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: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| + | tar: can't create symlink 'passwd' to '/tmp/passwd' |
| 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 | |
| - | 0 |
| + | tar: can't create symlink 'symlink' to '/tmp' |
| + | 1 |
| ls: /tmp/bb_test_evilfile: No such file or directory | |
| ls: bb_test_evilfile: No such file or directory | |
| symlink/bb_test_evilfile | |