tar,unzip: postpone creation of symlinks with "suspicious" targets
[oweals/busybox.git] / archival / libarchive / data_extract_all.c
index 1830ffb8d0f786be7abe4ac4636fde932c87d6a5..8fa69ffafc1cfc4186659705a16c6f6f49b23a92 100644 (file)
@@ -2,7 +2,6 @@
 /*
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
-
 #include "libbb.h"
 #include "bb_archive.h"
 
@@ -108,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);
                        }
@@ -126,12 +123,11 @@ 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);
+                       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;
@@ -167,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);
                }
@@ -178,25 +173,38 @@ 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);
-               }
+
+               /* To avoid a directory traversal attack via 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
+                *  $ 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'.
+                */
+               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;