busybox-1.27.2: Fix CVE-2011-5325 [No upstream tracking] -- https://bugs.busybox.net/show_bug.cgi?id=8411 libarchive: do not extract unsafe symlinks Prevent unsafe links extracting unless env variable $EXTRACT_UNSAFE_SYMLINKS=1 is not set. Untarring file with -C DESTDIR parameter could be extracted with unwanted symlinks. This doesn't feel right, and IIRC GNU tar doesn't do that. Include necessary changes from previous commits. Upstream-Status: Backport [https://git.busybox.net/busybox/commit/?id=bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7] CVE: CVE-2011-5325 bug: 8411 Signed-off-by: Radovan Scasny Signed-off-by: Andrej Valek diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src index 942e755..e1a8a75 100644 --- a/archival/libarchive/Kbuild.src +++ b/archival/libarchive/Kbuild.src @@ -12,6 +12,8 @@ COMMON_FILES:= \ data_extract_all.o \ data_extract_to_stdout.o \ \ + unsafe_symlink_target.o \ +\ filter_accept_all.o \ filter_accept_list.o \ filter_accept_reject_list.o \ diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index 1830ffb..b828b65 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c @@ -128,10 +128,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) res = link(hard_link, dst_name); 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); + bb_perror_msg("can't create %slink '%s' to '%s'", + "hard", dst_name, hard_link + ); } /* Hardlinks have no separate mode/ownership, skip chown/chmod */ goto ret; @@ -178,15 +177,17 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) case S_IFLNK: /* Symlink */ //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) - 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: diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c new file mode 100644 index 0000000..ee46e28 --- /dev/null +++ b/archival/libarchive/unsafe_symlink_target.c @@ -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; + } +} \ No newline at end of file diff --git a/archival/unzip.c b/archival/unzip.c index 9037262..270e261 100644 --- a/archival/unzip.c +++ b/archival/unzip.c @@ -335,6 +335,44 @@ static void unzip_create_leading_dirs(const char *fn) free(name); } +static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) +{ + char *target; + + if (zip->fmt.ucmpsize > 0xfff) /* no funny business please */ + bb_error_msg_and_die("bad archive"); + + if (zip->fmt.method == 0) { + /* Method 0 - stored (not compressed) */ + target = xzalloc(zip->fmt.ucmpsize + 1); + xread(zip_fd, target, zip->fmt.ucmpsize); + } else { +#if 1 + bb_error_msg_and_die("compressed symlink is not supported"); +#else + transformer_state_t xstate; + init_transformer_state(&xstate); + xstate.mem_output_size_max = zip->fmt.ucmpsize; + /* ...unpack... */ + if (!xstate.mem_output_buf) + WTF(); + target = xstate.mem_output_buf; + target = xrealloc(target, xstate.mem_output_size + 1); + 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 + ); + } + } + free(target); +} + static void unzip_extract(zip_header_t *zip, int dst_fd) { transformer_state_t xstate; @@ -813,7 +851,7 @@ int unzip_main(int argc, char **argv) } check_file: /* Extract file */ - if (stat(dst_fn, &stat_buf) == -1) { + if (lstat(dst_fn, &stat_buf) == -1) { /* File does not exist */ if (errno != ENOENT) { bb_perror_msg_and_die("can't stat '%s'", dst_fn); @@ -834,6 +872,7 @@ int unzip_main(int argc, char **argv) goto do_open_and_extract; printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn); my_fgets80(key_buf); +//TODO: redo lstat + ISREG check! user input could have taken a long time! switch (key_buf[0]) { case 'A': @@ -842,7 +881,8 @@ int unzip_main(int argc, char **argv) do_open_and_extract: unzip_create_leading_dirs(dst_fn); #if ENABLE_FEATURE_UNZIP_CDF - dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode); + if (!S_ISLNK(file_mode)) + dst_fd = xopen3(dst_fn, O_WRONLY | O_CREAT | O_TRUNC, file_mode); #else dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC); #endif @@ -852,10 +892,18 @@ int unzip_main(int argc, char **argv) ? " extracting: %s\n" : */ " inflating: %s\n", dst_fn); } - unzip_extract(&zip, dst_fd); - if (dst_fd != STDOUT_FILENO) { - /* closing STDOUT is potentially bad for future business */ - close(dst_fd); +#if ENABLE_FEATURE_UNZIP_CDF + if (S_ISLNK(file_mode)) { + if (dst_fd != STDOUT_FILENO) /* no -p */ + unzip_extract_symlink(&zip, dst_fn); + } else +#endif + { + unzip_extract(&zip, dst_fd); + if (dst_fd != STDOUT_FILENO) { + /* closing STDOUT is potentially bad for future business */ + close(dst_fd); + }; } break; diff --git a/coreutils/link.c b/coreutils/link.c index ac3ef85..aab249d 100644 --- a/coreutils/link.c +++ b/coreutils/link.c @@ -32,9 +32,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; diff --git a/include/bb_archive.h b/include/bb_archive.h index 2b9c5f0..1e4da3c 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h @@ -196,6 +196,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; diff --git a/libbb/copy_file.c b/libbb/copy_file.c index 23c0f83..be90066 100644 --- a/libbb/copy_file.c +++ b/libbb/copy_file.c @@ -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) diff --git a/testsuite/tar.tests b/testsuite/tar.tests index 9f7ce15..b7cd74c 100755 --- a/testsuite/tar.tests +++ b/testsuite/tar.tests @@ -10,9 +10,6 @@ unset LC_COLLATE unset LC_ALL umask 022 -rm -rf tar.tempdir 2>/dev/null -mkdir tar.tempdir && cd tar.tempdir || exit 1 - # testing "test name" "script" "expected result" "file input" "stdin" testing "Empty file is not a tarball" '\ @@ -53,6 +50,7 @@ dd if=/dev/zero bs=512 count=20 2>/dev/null | tar xvf - 2>&1; echo $? "" "" SKIP= +mkdir tar.tempdir && cd tar.tempdir || exit 1 # "tar cf test.tar input input_dir/ input_hard1 input_hard2 input_hard1 input_dir/ input": # GNU tar 1.26 records as hardlinks: # input_hard2 -> input_hard1 @@ -64,7 +62,6 @@ SKIP= # We also don't use "hrw-r--r--" notation for hardlinks in "tar tv" listing. optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES testing "tar hardlinks and repeated files" '\ -rm -rf input_* test.tar 2>/dev/null >input_hard1 ln input_hard1 input_hard2 mkdir input_dir @@ -95,10 +92,11 @@ drwxr-xr-x input_dir " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES testing "tar hardlinks mode" '\ -rm -rf input_* test.tar 2>/dev/null >input_hard1 chmod 741 input_hard1 ln input_hard1 input_hard2 @@ -128,10 +126,11 @@ Ok: 0 " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 optional FEATURE_TAR_CREATE FEATURE_LS_SORTFILES testing "tar symlinks mode" '\ -rm -rf input_* test.tar 2>/dev/null >input_file chmod 741 input_file ln -s input_file input_soft @@ -159,10 +158,11 @@ lrwxrwxrwx input_file " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 optional FEATURE_TAR_CREATE FEATURE_TAR_LONG_OPTIONS testing "tar --overwrite" "\ -rm -rf input_* test.tar 2>/dev/null ln input input_hard tar cf test.tar input_hard echo WRONG >input @@ -174,12 +174,13 @@ Ok " \ "Ok\n" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 test x"$SKIP_KNOWN_BUGS" = x"" && { # Needs to be run under non-root for meaningful test optional FEATURE_TAR_CREATE testing "tar writing into read-only dir" '\ -rm -rf input_* test.tar 2>/dev/null mkdir input_dir >input_dir/input_file chmod 550 input_dir @@ -201,7 +202,9 @@ dr-xr-x--- input_dir "" "" SKIP= } +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 # Had a bug where on extract autodetect first "switched off" -z # and then failed to recognize .tgz extension optional FEATURE_TAR_CREATE FEATURE_SEAMLESS_GZ GUNZIP @@ -217,7 +220,9 @@ Ok " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 # Do we detect XZ-compressed data (even w/o .tar.xz or txz extension)? # (the uuencoded hello_world.txz contains one empty file named "hello_world") optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_XZ @@ -236,7 +241,9 @@ AAAEWVo= ==== " SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 # On extract, everything up to and including last ".." component is stripped optional FEATURE_TAR_CREATE testing "tar strips /../ on extract" "\ @@ -255,7 +262,9 @@ Ok " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 # attack.tar.bz2 has symlink pointing to a system file # followed by a regular file with the same name # containing "root::0:0::/root:/bin/sh": @@ -270,6 +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 0 " \ "" "\ @@ -281,12 +291,15 @@ l4/V8LDoe90yiWJhOJvIypgEfxdyRThQkBVn/bI= ==== " SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null + +mkdir tar.tempdir && cd tar.tempdir || exit 1 # And same with -k 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 open 'passwd': File exists +tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract 0 " \ "" "\ @@ -298,7 +311,9 @@ l4/V8LDoe90yiWJhOJvIypgEfxdyRThQkBVn/bI= ==== " SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null +mkdir tar.tempdir && cd tar.tempdir || exit 1 optional UNICODE_SUPPORT FEATURE_TAR_GNU_EXTENSIONS FEATURE_SEAMLESS_BZ2 FEATURE_TAR_AUTODETECT testing "Pax-encoded UTF8 names and symlinks" '\ tar xvf ../tar.utf8.tar.bz2 2>&1; echo $? @@ -309,17 +324,45 @@ 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 " \ "" "" SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null - -cd .. && rm -rf tar.tempdir || exit 1 +mkdir tar.tempdir && cd tar.tempdir || exit 1 +optional UUDECODE FEATURE_SEAMLESS_BZ2 FEATURE_TAR_AUTODETECT +testing "Symlink attack: create symlink and then write through it" '\ +exec 2>&1 +uudecode -o input && tar xvf input; echo $? +ls /tmp/bb_test_evilfile +ls bb_test_evilfile +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 +ls: /tmp/bb_test_evilfile: No such file or directory +ls: bb_test_evilfile: No such file or directory +symlink/bb_test_evilfile +" \ +"" "\ +begin-base64 644 tar_symlink_attack.tar.bz2 +QlpoOTFBWSZTWZgs7bQAALT/hMmQAFBAAf+AEMAGJPPv32AAAIAIMAC5thlR +omAjAmCMADQT1BqNE0AEwAAjAEwElTKeo9NTR6h6gaeoA0DQNLVdwZZ5iNTk +AQwCAV6S00QFJYhrlfFkVCEDEGtgNVqYrI0uK3ggnt30gqk4e1TTQm5QIAKa +SJqzRGSFLMmOloHSAcvLiFxxRiQtQZF+qPxbo173ZDISOAoNoPN4PQPhBhKS +n8fYaKlioCTzL2oXYczyUUIP4u5IpwoSEwWdtoA= +==== +" +SKIP= +cd .. || exit 1; rm -rf tar.tempdir 2>/dev/null exit $FAILCOUNT