From b920a38dc0a87f5884444d4731a8b887b5e16018 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 24 Jul 2017 17:20:13 +0200 Subject: tar: postpone creation of symlinks with "suspicious" targets. Closes 8411 function old new delta data_extract_all 968 1038 +70 tar_main 952 986 +34 scan_tree 258 262 +4 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 108/0) Total: 108 bytes Signed-off-by: Denys Vlasenko --- archival/libarchive/data_extract_all.c | 42 +++++++++++++++++++++++++++++----- archival/tar.c | 37 +++++++++++++++--------------- archival/tar_symlink_attack | 16 +++++++++++++ 3 files changed, 71 insertions(+), 24 deletions(-) create mode 100755 archival/tar_symlink_attack (limited to 'archival') diff --git a/archival/libarchive/data_extract_all.c b/archival/libarchive/data_extract_all.c index 1830ffb8d..1ce927c2f 100644 --- a/archival/libarchive/data_extract_all.c +++ b/archival/libarchive/data_extract_all.c @@ -128,10 +128,11 @@ 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", + bb_perror_msg("can't create %slink '%s' to '%s'", + "hard", dst_name, - hard_link); + hard_link + ); } /* Hardlinks have no separate mode/ownership, skip chown/chmod */ goto ret; @@ -178,15 +179,44 @@ 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?) + + /* To avoid a directory traversal attack via symlinks, + * for certain link targets postpone creation of symlinks. + * + * For example, consider a .tar created via: + * $ tar cvf bug.tar anything.txt + * $ ln -s /tmp symlink + * $ tar --append -f bug.tar symlink + * $ rm symlink + * $ mkdir symlink + * $ tar --append -f bug.tar symlink/evil.py + * + * This will result in an archive that contains: + * $ tar --list -f bug.tar + * anything.txt + * symlink [-> /tmp] + * symlink/evil.py + * + * 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", + bb_perror_msg("can't create %slink '%s' to '%s'", + "sym", dst_name, - file_header->link_target); + file_header->link_target + ); } break; case S_IFSOCK: diff --git a/archival/tar.c b/archival/tar.c index 0fc574dfd..280ded4e1 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -22,24 +22,6 @@ * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ -/* TODO: security with -C DESTDIR option can be enhanced. - * Consider tar file created via: - * $ tar cvf bug.tar anything.txt - * $ ln -s /tmp symlink - * $ tar --append -f bug.tar symlink - * $ rm symlink - * $ mkdir symlink - * $ tar --append -f bug.tar symlink/evil.py - * - * This will result in an archive which contains: - * $ tar --list -f bug.tar - * anything.txt - * symlink - * symlink/evil.py - * - * Untarring it puts evil.py in '/tmp' even if the -C DESTDIR is given. - * This doesn't feel right, and IIRC GNU tar doesn't do that. - */ //config:config TAR //config: bool "tar (40 kb)" @@ -296,6 +278,23 @@ 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) { @@ -1252,6 +1251,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 */ + 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) diff --git a/archival/tar_symlink_attack b/archival/tar_symlink_attack new file mode 100755 index 000000000..35455f200 --- /dev/null +++ b/archival/tar_symlink_attack @@ -0,0 +1,16 @@ +#!/bin/sh +# Makes "symlink attack" tarball (needs GNU tar for --append) + +true >anything.txt +tar cvf tar_symlink_attack.tar anything.txt +rm anything.txt + +ln -s /tmp symlink +tar --append -f tar_symlink_attack.tar symlink +rm symlink + +mkdir symlink +echo BUG >symlink/bb_test_evilfile +tar --append -f tar_symlink_attack.tar symlink/bb_test_evilfile +rm symlink/bb_test_evilfile +rmdir symlink -- cgit v1.2.3