From cf8e67ffc8a2227b63fc6d3d1569b0214f160f54 Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Sun, 2 Nov 2014 17:11:20 -0800 Subject: [PATCH] Fix handling of leading slashes for hardlinks --- Makefile.am | 2 + tar/test/CMakeLists.txt | 1 + tar/test/test_leading_slash.c | 41 +++++++ tar/test/test_leading_slash.tar.uu | 60 ++++++++++ tar/util.c | 186 ++++++++++++++++++----------- 5 files changed, 217 insertions(+), 73 deletions(-) create mode 100644 tar/test/test_leading_slash.c create mode 100644 tar/test/test_leading_slash.tar.uu diff --git a/Makefile.am b/Makefile.am index 464d1b27c..9b9990f4b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -889,6 +889,7 @@ bsdtar_test_SOURCES= \ tar/test/test_extract_tar_xz.c \ tar/test/test_format_newc.c \ tar/test/test_help.c \ + tar/test/test_leading_slash.c \ tar/test/test_option_C_upper.c \ tar/test/test_option_H_upper.c \ tar/test/test_option_L_upper.c \ @@ -958,6 +959,7 @@ bsdtar_test_EXTRA_DIST= \ tar/test/test_extract.tar.lzma.uu \ tar/test/test_extract.tar.lzo.uu \ tar/test/test_extract.tar.xz.uu \ + tar/test/test_leading_slash.tar.uu \ tar/test/test_option_keep_newer_files.tar.Z.uu \ tar/test/test_option_passphrase.zip.uu \ tar/test/test_option_s.tar.Z.uu \ diff --git a/tar/test/CMakeLists.txt b/tar/test/CMakeLists.txt index ade72ea7c..9648e4892 100644 --- a/tar/test/CMakeLists.txt +++ b/tar/test/CMakeLists.txt @@ -24,6 +24,7 @@ IF(ENABLE_TAR AND ENABLE_TEST) test_extract_tar_xz.c test_format_newc.c test_help.c + test_leading_slash.c test_option_C_upper.c test_option_H_upper.c test_option_L_upper.c diff --git a/tar/test/test_leading_slash.c b/tar/test/test_leading_slash.c new file mode 100644 index 000000000..350aa4a78 --- /dev/null +++ b/tar/test/test_leading_slash.c @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2003-2014 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" +__FBSDID("$FreeBSD$"); + +DEFINE_TEST(test_leading_slash) +{ + const char *reffile = "test_leading_slash.tar"; + + extract_reference_file(reffile); + assertEqualInt(0, systemf("%s -xf %s >test.out 2>test.err", testprog, reffile)); + assertFileExists("foo/file"); + assertTextFileContents("foo\x0a", "foo/file"); + assertTextFileContents("foo\x0a", "foo/hardlink"); + assertIsHardlink("foo/file", "foo/hardlink"); + assertEmptyFile("test.out"); + assertTextFileContents("bsdtar: Removing leading '/' from member names\x0a", "test.err"); +} + diff --git a/tar/test/test_leading_slash.tar.uu b/tar/test/test_leading_slash.tar.uu new file mode 100644 index 000000000..eeb27c3bb --- /dev/null +++ b/tar/test/test_leading_slash.tar.uu @@ -0,0 +1,60 @@ +begin 644 test_leading_slash.tar +M+V9O;R]F:6QE```````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````#`P,#8T-"``,#`P,#`P(``P,#`P,#`@`#`P,#`P,#`P,#`T +M(#$R-#$V,S4U-34V(#`Q,C8V-P`@,``````````````````````````````` +M```````````````````````````````````````````````````````````` +M``````````````````````````````````````````!Uwarned_lead_slash) { + lafe_warnc(0, + "Removing leading '%c' from member names", + c[0]); + bsdtar->warned_lead_slash = 1; + } +} + +static void +warn_strip_drive_letter(struct bsdtar *bsdtar) +{ + if (!bsdtar->warned_lead_slash) { + lafe_warnc(0, + "Removing leading drive letter from " + "member names"); + bsdtar->warned_lead_slash = 1; + } +} + +/* + * Convert absolute path to non-absolute path by skipping leading + * absolute path prefixes. + */ +static const char* +strip_absolute_path(struct bsdtar *bsdtar, const char *p) +{ + const char *rp; + + /* Remove leading "//./" or "//?/" or "//?/UNC/" + * (absolute path prefixes used by Windows API) */ + if ((p[0] == '/' || p[0] == '\\') && + (p[1] == '/' || p[1] == '\\') && + (p[2] == '.' || p[2] == '?') && + (p[3] == '/' || p[3] == '\\')) + { + if (p[2] == '?' && + (p[4] == 'U' || p[4] == 'u') && + (p[5] == 'N' || p[5] == 'n') && + (p[6] == 'C' || p[6] == 'c') && + (p[7] == '/' || p[7] == '\\')) + p += 8; + else + p += 4; + warn_strip_drive_letter(bsdtar); + } + + /* Remove multiple leading slashes and Windows drive letters. */ + do { + rp = p; + if (((p[0] >= 'a' && p[0] <= 'z') || + (p[0] >= 'A' && p[0] <= 'Z')) && + p[1] == ':') { + p += 2; + warn_strip_drive_letter(bsdtar); + } + + /* Remove leading "/../", "/./", "//", etc. */ + while (p[0] == '/' || p[0] == '\\') { + if (p[1] == '.' && + p[2] == '.' && + (p[3] == '/' || p[3] == '\\')) { + p += 3; /* Remove "/..", leave "/" for next pass. */ + } else if (p[1] == '.' && + (p[2] == '/' || p[2] == '\\')) { + p += 2; /* Remove "/.", leave "/" for next pass. */ + } else + p += 1; /* Remove "/". */ + warn_strip_leading_char(bsdtar, rp); + } + } while (rp != p); + + return (p); +} + /* * Handle --strip-components and any future path-rewriting options. * Returns non-zero if the pathname should not be extracted. * + * Note: The rewrites are applied uniformly to pathnames and hardlink + * names but not to symlink bodies. This is deliberate: Symlink + * bodies are not necessarily filenames. Even when they are, they + * need to be interpreted relative to the directory containing them, + * so simple rewrites like this are rarely appropriate. + * * TODO: Support pax-style regex path rewrites. */ int edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) { const char *name = archive_entry_pathname(entry); + const char *original_name = name; + const char *hardlinkname = archive_entry_hardlink(entry); + const char *original_hardlinkname = hardlinkname; #if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) char *subst_name; int r; + /* Apply user-specified substitution to pathname. */ r = apply_substitution(bsdtar, name, &subst_name, 0, 0); if (r == -1) { lafe_warnc(0, "Invalid substitution, skipping entry"); @@ -400,10 +487,12 @@ edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) } else free(subst_name); name = archive_entry_pathname(entry); + original_name = name; } - if (archive_entry_hardlink(entry)) { - r = apply_substitution(bsdtar, archive_entry_hardlink(entry), &subst_name, 0, 1); + /* Apply user-specified substitution to hardlink target. */ + if (hardlinkname != NULL) { + r = apply_substitution(bsdtar, hardlinkname, &subst_name, 0, 1); if (r == -1) { lafe_warnc(0, "Invalid substitution, skipping entry"); return 1; @@ -412,7 +501,11 @@ edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) archive_entry_copy_hardlink(entry, subst_name); free(subst_name); } + hardlinkname = archive_entry_hardlink(entry); + original_hardlinkname = hardlinkname; } + + /* Apply user-specified substitution to symlink body. */ if (archive_entry_symlink(entry) != NULL) { r = apply_substitution(bsdtar, archive_entry_symlink(entry), &subst_name, 1, 0); if (r == -1) { @@ -428,94 +521,41 @@ edit_pathname(struct bsdtar *bsdtar, struct archive_entry *entry) /* Strip leading dir names as per --strip-components option. */ if (bsdtar->strip_components > 0) { - const char *linkname = archive_entry_hardlink(entry); - name = strip_components(name, bsdtar->strip_components); if (name == NULL) return (1); - if (linkname != NULL) { - linkname = strip_components(linkname, + if (hardlinkname != NULL) { + hardlinkname = strip_components(hardlinkname, bsdtar->strip_components); - if (linkname == NULL) + if (hardlinkname == NULL) return (1); - archive_entry_copy_hardlink(entry, linkname); } } - /* By default, don't write or restore absolute pathnames. */ if (!bsdtar->option_absolute_paths) { - const char *rp, *p = name; - int slashonly = 1; - - /* Remove leading "//./" or "//?/" or "//?/UNC/" - * (absolute path prefixes used by Windows API) */ - if ((p[0] == '/' || p[0] == '\\') && - (p[1] == '/' || p[1] == '\\') && - (p[2] == '.' || p[2] == '?') && - (p[3] == '/' || p[3] == '\\')) - { - if (p[2] == '?' && - (p[4] == 'U' || p[4] == 'u') && - (p[5] == 'N' || p[5] == 'n') && - (p[6] == 'C' || p[6] == 'c') && - (p[7] == '/' || p[7] == '\\')) - p += 8; - else - p += 4; - slashonly = 0; - } - do { - rp = p; - /* Remove leading drive letter from archives created - * on Windows. */ - if (((p[0] >= 'a' && p[0] <= 'z') || - (p[0] >= 'A' && p[0] <= 'Z')) && - p[1] == ':') { - p += 2; - slashonly = 0; - } - /* Remove leading "/../", "//", etc. */ - while (p[0] == '/' || p[0] == '\\') { - if (p[1] == '.' && p[2] == '.' && - (p[3] == '/' || p[3] == '\\')) { - p += 3; /* Remove "/..", leave "/" - * for next pass. */ - slashonly = 0; - } else - p += 1; /* Remove "/". */ - } - } while (rp != p); - - if (p != name && !bsdtar->warned_lead_slash) { - /* Generate a warning the first time this happens. */ - if (slashonly) - lafe_warnc(0, - "Removing leading '%c' from member names", - name[0]); - else - lafe_warnc(0, - "Removing leading drive letter from " - "member names"); - bsdtar->warned_lead_slash = 1; - } - - /* Special case: Stripping everything yields ".". */ - if (*p == '\0') + /* By default, don't write or restore absolute pathnames. */ + name = strip_absolute_path(bsdtar, name); + if (*name == '\0') name = "."; - else - name = p; + + if (hardlinkname != NULL) { + hardlinkname = strip_absolute_path(bsdtar, hardlinkname); + if (*hardlinkname == '\0') + return (1); + } } else { /* Strip redundant leading '/' characters. */ while (name[0] == '/' && name[1] == '/') name++; } - /* Safely replace name in archive_entry. */ - if (name != archive_entry_pathname(entry)) { - char *q = strdup(name); - archive_entry_copy_pathname(entry, q); - free(q); + /* Replace name in archive_entry. */ + if (name != original_name) { + archive_entry_copy_pathname(entry, name); + } + if (hardlinkname != original_hardlinkname) { + archive_entry_copy_hardlink(entry, hardlinkname); } return (0); } -- 2.47.2