diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-10 11:52:42 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-10 11:52:42 +0200 |
| commit | bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7 (patch) | |
| tree | 72672bb0c187b93f1fba99012cf0c4e716214298 | |
| parent | 0cf64c8b5d86d603903397bfce87dea5a862caec (diff) | |
| download | busybox-w32-bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7.tar.gz busybox-w32-bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7.tar.bz2 busybox-w32-bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7.zip | |
libarchive: do not extract unsafe symlinks unless $EXTRACT_UNSAFE_SYMLINKS=1
function old new delta
unsafe_symlink_target - 147 +147
unzip_main 2711 2732 +21
copy_file 1657 1678 +21
tar_main 999 971 -28
data_extract_all 1038 984 -54
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 2/2 up/down: 189/-82) Total: 107 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
| -rw-r--r-- | archival/libarchive/Kbuild.src | 2 | ||||
| -rw-r--r-- | archival/libarchive/data_extract_all.c | 37 | ||||
| -rw-r--r-- | archival/libarchive/unsafe_symlink_target.c | 48 | ||||
| -rw-r--r-- | archival/tar.c | 19 | ||||
| -rw-r--r-- | archival/unzip.c | 10 | ||||
| -rw-r--r-- | coreutils/link.c | 5 | ||||
| -rw-r--r-- | include/bb_archive.h | 4 | ||||
| -rw-r--r-- | libbb/copy_file.c | 5 | ||||
| -rwxr-xr-x | testsuite/tar.tests | 10 |
9 files changed, 85 insertions, 55 deletions
diff --git a/archival/libarchive/Kbuild.src b/archival/libarchive/Kbuild.src index 942e755b9..e1a8a7529 100644 --- a/archival/libarchive/Kbuild.src +++ b/archival/libarchive/Kbuild.src | |||
| @@ -12,6 +12,8 @@ COMMON_FILES:= \ | |||
| 12 | data_extract_all.o \ | 12 | data_extract_all.o \ |
| 13 | data_extract_to_stdout.o \ | 13 | data_extract_to_stdout.o \ |
| 14 | \ | 14 | \ |
| 15 | unsafe_symlink_target.o \ | ||
| 16 | \ | ||
| 15 | filter_accept_all.o \ | 17 | filter_accept_all.o \ |
| 16 | filter_accept_list.o \ | 18 | filter_accept_list.o \ |
| 17 | filter_accept_reject_list.o \ | 19 | filter_accept_reject_list.o \ |
diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index 1ce927c2f..e658444e0 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c | |||
| @@ -129,9 +129,7 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
| 129 | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { | 129 | if (res != 0 && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET)) { |
| 130 | /* shared message */ | 130 | /* shared message */ |
| 131 | bb_perror_msg("can't create %slink '%s' to '%s'", | 131 | bb_perror_msg("can't create %slink '%s' to '%s'", |
| 132 | "hard", | 132 | "hard", dst_name, hard_link |
| 133 | dst_name, | ||
| 134 | hard_link | ||
| 135 | ); | 133 | ); |
| 136 | } | 134 | } |
| 137 | /* Hardlinks have no separate mode/ownership, skip chown/chmod */ | 135 | /* Hardlinks have no separate mode/ownership, skip chown/chmod */ |
| @@ -181,7 +179,9 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
| 181 | //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) | 179 | //TODO: what if file_header->link_target == NULL (say, corrupted tarball?) |
| 182 | 180 | ||
| 183 | /* To avoid a directory traversal attack via symlinks, | 181 | /* To avoid a directory traversal attack via symlinks, |
| 184 | * for certain link targets postpone creation of symlinks. | 182 | * do not restore symlinks with ".." components |
| 183 | * or symlinks starting with "/", unless a magic | ||
| 184 | * envvar is set. | ||
| 185 | * | 185 | * |
| 186 | * For example, consider a .tar created via: | 186 | * For example, consider a .tar created via: |
| 187 | * $ tar cvf bug.tar anything.txt | 187 | * $ tar cvf bug.tar anything.txt |
| @@ -199,24 +199,17 @@ void FAST_FUNC data_extract_all(archive_handle_t *archive_handle) | |||
| 199 | * | 199 | * |
| 200 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. | 200 | * Untarring bug.tar would otherwise place evil.py in '/tmp'. |
| 201 | */ | 201 | */ |
| 202 | if (file_header->link_target[0] == '/' | 202 | if (!unsafe_symlink_target(file_header->link_target)) { |
| 203 | || strstr(file_header->link_target, "..") | 203 | res = symlink(file_header->link_target, dst_name); |
| 204 | ) { | 204 | if (res != 0 |
| 205 | llist_add_to(&archive_handle->symlink_placeholders, | 205 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) |
| 206 | xasprintf("%s%c%s", file_header->name, '\0', file_header->link_target) | 206 | ) { |
| 207 | ); | 207 | /* shared message */ |
| 208 | break; | 208 | bb_perror_msg("can't create %slink '%s' to '%s'", |
| 209 | } | 209 | "sym", |
| 210 | res = symlink(file_header->link_target, dst_name); | 210 | dst_name, file_header->link_target |
| 211 | if (res != 0 | 211 | ); |
| 212 | && !(archive_handle->ah_flags & ARCHIVE_EXTRACT_QUIET) | 212 | } |
| 213 | ) { | ||
| 214 | /* shared message */ | ||
| 215 | bb_perror_msg("can't create %slink '%s' to '%s'", | ||
| 216 | "sym", | ||
| 217 | dst_name, | ||
| 218 | file_header->link_target | ||
| 219 | ); | ||
| 220 | } | 213 | } |
| 221 | break; | 214 | break; |
| 222 | case S_IFSOCK: | 215 | case S_IFSOCK: |
diff --git a/archival/libarchive/unsafe_symlink_target.c b/archival/libarchive/unsafe_symlink_target.c new file mode 100644 index 000000000..441ba8b24 --- /dev/null +++ b/archival/libarchive/unsafe_symlink_target.c | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | /* vi: set sw=4 ts=4: */ | ||
| 2 | /* | ||
| 3 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. | ||
| 4 | */ | ||
| 5 | #include "libbb.h" | ||
| 6 | #include "bb_archive.h" | ||
| 7 | |||
| 8 | int FAST_FUNC unsafe_symlink_target(const char *target) | ||
| 9 | { | ||
| 10 | const char *dot; | ||
| 11 | |||
| 12 | if (target[0] == '/') { | ||
| 13 | const char *var; | ||
| 14 | unsafe: | ||
| 15 | var = getenv("EXTRACT_UNSAFE_SYMLINKS"); | ||
| 16 | if (var) { | ||
| 17 | if (LONE_CHAR(var, '1')) | ||
| 18 | return 0; /* pretend it's safe */ | ||
| 19 | return 1; /* "UNSAFE!" */ | ||
| 20 | } | ||
| 21 | bb_error_msg("skipping unsafe symlink to '%s' in archive," | ||
| 22 | " set %s=1 to extract", | ||
| 23 | target, | ||
| 24 | "EXTRACT_UNSAFE_SYMLINKS" | ||
| 25 | ); | ||
| 26 | /* Prevent further messages */ | ||
| 27 | setenv("EXTRACT_UNSAFE_SYMLINKS", "0", 0); | ||
| 28 | return 1; /* "UNSAFE!" */ | ||
| 29 | } | ||
| 30 | |||
| 31 | dot = target; | ||
| 32 | for (;;) { | ||
| 33 | dot = strchr(dot, '.'); | ||
| 34 | if (!dot) | ||
| 35 | return 0; /* safe target */ | ||
| 36 | |||
| 37 | /* Is it a path component starting with ".."? */ | ||
| 38 | if ((dot[1] == '.') | ||
| 39 | && (dot == target || dot[-1] == '/') | ||
| 40 | /* Is it exactly ".."? */ | ||
| 41 | && (dot[2] == '/' || dot[2] == '\0') | ||
| 42 | ) { | ||
| 43 | goto unsafe; | ||
| 44 | } | ||
| 45 | /* NB: it can even be trailing ".", should only add 1 */ | ||
| 46 | dot += 1; | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/archival/tar.c b/archival/tar.c index 73c14ca81..9a5bcc7fe 100644 --- a/archival/tar.c +++ b/archival/tar.c | |||
| @@ -278,23 +278,6 @@ static void chksum_and_xwrite(int fd, struct tar_header_t* hp) | |||
| 278 | xwrite(fd, hp, sizeof(*hp)); | 278 | xwrite(fd, hp, sizeof(*hp)); |
| 279 | } | 279 | } |
| 280 | 280 | ||
| 281 | static void replace_symlink_placeholders(llist_t *list) | ||
| 282 | { | ||
| 283 | while (list) { | ||
| 284 | char *target; | ||
| 285 | |||
| 286 | target = list->data + strlen(list->data) + 1; | ||
| 287 | if (symlink(target, list->data)) { | ||
| 288 | /* shared message */ | ||
| 289 | bb_error_msg_and_die("can't create %slink '%s' to '%s'", | ||
| 290 | "sym", | ||
| 291 | list->data, target | ||
| 292 | ); | ||
| 293 | } | ||
| 294 | list = list->link; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS | 281 | #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS |
| 299 | static void writeLongname(int fd, int type, const char *name, int dir) | 282 | static void writeLongname(int fd, int type, const char *name, int dir) |
| 300 | { | 283 | { |
| @@ -1255,8 +1238,6 @@ int tar_main(int argc UNUSED_PARAM, char **argv) | |||
| 1255 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) | 1238 | while (get_header_tar(tar_handle) == EXIT_SUCCESS) |
| 1256 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ | 1239 | bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ |
| 1257 | 1240 | ||
| 1258 | replace_symlink_placeholders(tar_handle->symlink_placeholders); | ||
| 1259 | |||
| 1260 | /* Check that every file that should have been extracted was */ | 1241 | /* Check that every file that should have been extracted was */ |
| 1261 | while (tar_handle->accept) { | 1242 | while (tar_handle->accept) { |
| 1262 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) | 1243 | if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) |
diff --git a/archival/unzip.c b/archival/unzip.c index 8ed9ae7d5..604166063 100644 --- a/archival/unzip.c +++ b/archival/unzip.c | |||
| @@ -368,9 +368,15 @@ static void unzip_extract_symlink(zip_header_t *zip, const char *dst_fn) | |||
| 368 | target[xstate.mem_output_size] = '\0'; | 368 | target[xstate.mem_output_size] = '\0'; |
| 369 | #endif | 369 | #endif |
| 370 | } | 370 | } |
| 371 | if (!unsafe_symlink_target(target)) { | ||
| 371 | //TODO: libbb candidate | 372 | //TODO: libbb candidate |
| 372 | if (symlink(target, dst_fn)) | 373 | if (symlink(target, dst_fn)) { |
| 373 | bb_perror_msg_and_die("can't create symlink '%s'", dst_fn); | 374 | /* shared message */ |
| 375 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", | ||
| 376 | "sym", dst_fn, target | ||
| 377 | ); | ||
| 378 | } | ||
| 379 | } | ||
| 374 | free(target); | 380 | free(target); |
| 375 | } | 381 | } |
| 376 | #endif | 382 | #endif |
diff --git a/coreutils/link.c b/coreutils/link.c index 81808b778..d8d583b7b 100644 --- a/coreutils/link.c +++ b/coreutils/link.c | |||
| @@ -31,9 +31,8 @@ int link_main(int argc UNUSED_PARAM, char **argv) | |||
| 31 | argv += optind; | 31 | argv += optind; |
| 32 | if (link(argv[0], argv[1]) != 0) { | 32 | if (link(argv[0], argv[1]) != 0) { |
| 33 | /* shared message */ | 33 | /* shared message */ |
| 34 | bb_perror_msg_and_die("can't create %slink " | 34 | bb_perror_msg_and_die("can't create %slink '%s' to '%s'", |
| 35 | "'%s' to '%s'", "hard", | 35 | "hard", argv[1], argv[0] |
| 36 | argv[1], argv[0] | ||
| 37 | ); | 36 | ); |
| 38 | } | 37 | } |
| 39 | return EXIT_SUCCESS; | 38 | return EXIT_SUCCESS; |
diff --git a/include/bb_archive.h b/include/bb_archive.h index d3762415f..d3a02cf18 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h | |||
| @@ -64,9 +64,6 @@ typedef struct archive_handle_t { | |||
| 64 | /* Currently processed file's header */ | 64 | /* Currently processed file's header */ |
| 65 | file_header_t *file_header; | 65 | file_header_t *file_header; |
| 66 | 66 | ||
| 67 | /* List of symlink placeholders */ | ||
| 68 | llist_t *symlink_placeholders; | ||
| 69 | |||
| 70 | /* Process the header component, e.g. tar -t */ | 67 | /* Process the header component, e.g. tar -t */ |
| 71 | void FAST_FUNC (*action_header)(const file_header_t *); | 68 | void FAST_FUNC (*action_header)(const file_header_t *); |
| 72 | 69 | ||
| @@ -200,6 +197,7 @@ void seek_by_jump(int fd, off_t amount) FAST_FUNC; | |||
| 200 | void seek_by_read(int fd, off_t amount) FAST_FUNC; | 197 | void seek_by_read(int fd, off_t amount) FAST_FUNC; |
| 201 | 198 | ||
| 202 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; | 199 | const char *strip_unsafe_prefix(const char *str) FAST_FUNC; |
| 200 | int unsafe_symlink_target(const char *target) FAST_FUNC; | ||
| 203 | 201 | ||
| 204 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; | 202 | void data_align(archive_handle_t *archive_handle, unsigned boundary) FAST_FUNC; |
| 205 | const llist_t *find_list_entry(const llist_t *list, const char *filename) FAST_FUNC; | 203 | 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 23c0f8320..be9006631 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) | |||
| 371 | int r = symlink(lpath, dest); | 371 | int r = symlink(lpath, dest); |
| 372 | free(lpath); | 372 | free(lpath); |
| 373 | if (r < 0) { | 373 | if (r < 0) { |
| 374 | bb_perror_msg("can't create symlink '%s'", dest); | 374 | /* shared message */ |
| 375 | bb_perror_msg("can't create %slink '%s' to '%s'", | ||
| 376 | "sym", dest, lpath | ||
| 377 | ); | ||
| 375 | return -1; | 378 | return -1; |
| 376 | } | 379 | } |
| 377 | if (flags & FILEUTILS_PRESERVE_STATUS) | 380 | if (flags & FILEUTILS_PRESERVE_STATUS) |
diff --git a/testsuite/tar.tests b/testsuite/tar.tests index 1675b07b1..b7cd74ca5 100755 --- a/testsuite/tar.tests +++ b/testsuite/tar.tests | |||
| @@ -279,7 +279,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |||
| 279 | testing "tar does not extract into symlinks" "\ | 279 | testing "tar does not extract into symlinks" "\ |
| 280 | >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | 280 | >>/tmp/passwd && uudecode -o input && tar xf input 2>&1 && rm passwd; cat /tmp/passwd; echo \$? |
| 281 | " "\ | 281 | " "\ |
| 282 | tar: can't create symlink 'passwd' to '/tmp/passwd' | 282 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| 283 | 0 | 283 | 0 |
| 284 | " \ | 284 | " \ |
| 285 | "" "\ | 285 | "" "\ |
| @@ -299,7 +299,7 @@ optional UUDECODE FEATURE_TAR_AUTODETECT FEATURE_SEAMLESS_BZ2 | |||
| 299 | testing "tar -k does not extract into symlinks" "\ | 299 | testing "tar -k does not extract into symlinks" "\ |
| 300 | >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$? | 300 | >>/tmp/passwd && uudecode -o input && tar xf input -k 2>&1 && rm passwd; cat /tmp/passwd; echo \$? |
| 301 | " "\ | 301 | " "\ |
| 302 | tar: can't create symlink 'passwd' to '/tmp/passwd' | 302 | tar: skipping unsafe symlink to '/tmp/passwd' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract |
| 303 | 0 | 303 | 0 |
| 304 | " \ | 304 | " \ |
| 305 | "" "\ | 305 | "" "\ |
| @@ -324,11 +324,11 @@ rm -rf etc usr | |||
| 324 | ' "\ | 324 | ' "\ |
| 325 | etc/ssl/certs/3b2716e5.0 | 325 | etc/ssl/certs/3b2716e5.0 |
| 326 | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 326 | etc/ssl/certs/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
| 327 | 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 | ||
| 327 | etc/ssl/certs/f80cc7f6.0 | 328 | etc/ssl/certs/f80cc7f6.0 |
| 328 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt | 329 | usr/share/ca-certificates/mozilla/EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.crt |
| 329 | 0 | 330 | 0 |
| 330 | etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 331 | etc/ssl/certs/3b2716e5.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
| 331 | 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 | ||
| 332 | etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem | 332 | etc/ssl/certs/f80cc7f6.0 -> EBG_Elektronik_Sertifika_Hizmet_Sağlayıcısı.pem |
| 333 | " \ | 333 | " \ |
| 334 | "" "" | 334 | "" "" |
| @@ -346,9 +346,9 @@ ls symlink/bb_test_evilfile | |||
| 346 | ' "\ | 346 | ' "\ |
| 347 | anything.txt | 347 | anything.txt |
| 348 | symlink | 348 | symlink |
| 349 | tar: skipping unsafe symlink to '/tmp' in archive, set EXTRACT_UNSAFE_SYMLINKS=1 to extract | ||
| 349 | symlink/bb_test_evilfile | 350 | symlink/bb_test_evilfile |
| 350 | tar: can't create symlink 'symlink' to '/tmp' | 351 | 0 |
| 351 | 1 | ||
| 352 | ls: /tmp/bb_test_evilfile: No such file or directory | 352 | ls: /tmp/bb_test_evilfile: No such file or directory |
| 353 | ls: bb_test_evilfile: No such file or directory | 353 | ls: bb_test_evilfile: No such file or directory |
| 354 | symlink/bb_test_evilfile | 354 | symlink/bb_test_evilfile |
