]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add loop check to the directory traversals.
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Fri, 14 Jan 2011 12:08:30 +0000 (07:08 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Fri, 14 Jan 2011 12:08:30 +0000 (07:08 -0500)
SVN-Revision: 2886

libarchive/archive_read_disk_posix.c
libarchive/archive_read_disk_windows.c
libarchive/test/test_read_disk_directory_traversals.c

index fbf4e51a8850302afd65bcf292bf3f5460200f2d..ef3bf55d0a8f468ad934f9fcb6f4864a1db4ae22 100644 (file)
@@ -118,8 +118,8 @@ struct tree_entry {
        struct tree_entry       *parent;
        struct archive_string    name;
        size_t                   dirname_length;
-       dev_t                    dev;
-       ino_t                    ino;
+       int64_t                  dev;
+       int64_t                  ino;
        int                      flags;
        int                      filesystem_id;
        /* How to return back to the parent of a symlink. */
@@ -214,7 +214,7 @@ static struct tree *tree_open(const char *, int);
 static struct tree *tree_reopen(struct tree *, const char *);
 static void tree_close(struct tree *);
 static void tree_free(struct tree *);
-static void tree_push(struct tree *, const char *, int);
+static void tree_push(struct tree *, const char *, int, int64_t, int64_t);
 static int tree_enter_initial_dir(struct tree *);
 static int tree_enter_working_dir(struct tree *);
 
@@ -283,6 +283,7 @@ static int tree_current_is_dir(struct tree *);
 static int update_filesystem(struct archive_read_disk *a,
                    int64_t dev);
 static int setup_current_filesystem(struct archive_read_disk *);
+static int tree_target_is_same_as_parent(struct tree *, const struct stat *);
 
 static int     _archive_read_free(struct archive *);
 static int     _archive_read_close(struct archive *);
@@ -729,7 +730,7 @@ _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
                a->follow_symlinks = 1;
                /* 'L': Archive symlinks as targets, if we can. */
                st = tree_current_stat(t);
-               if (st != NULL)
+               if (st != NULL && !tree_target_is_same_as_parent(t, st))
                        break;
                /* If stat fails, we have a broken symlink;
                 * in that case, don't follow the link. */
@@ -854,10 +855,12 @@ archive_read_disk_descend(struct archive *_a)
        }
 
        if (tree_current_is_physical_dir(t)) {
-               tree_push(t, t->basename, t->current_filesystem_id);
+               tree_push(t, t->basename, t->current_filesystem_id,
+                   t->lst.st_dev, t->lst.st_ino);
                t->stack->flags |= isDir;
        } else if (tree_current_is_dir(t)) {
-               tree_push(t, t->basename, t->current_filesystem_id);
+               tree_push(t, t->basename, t->current_filesystem_id,
+                   t->st.st_dev, t->st.st_ino);
                t->stack->flags |= isDirLink;
        }
        t->descend = 0;
@@ -1179,7 +1182,8 @@ setup_current_filesystem(struct archive_read_disk *a)
  * Add a directory path to the current stack.
  */
 static void
-tree_push(struct tree *t, const char *path, int filesystem_id)
+tree_push(struct tree *t, const char *path, int filesystem_id,
+    int64_t dev, int64_t ino)
 {
        struct tree_entry *te;
 
@@ -1195,6 +1199,8 @@ tree_push(struct tree *t, const char *path, int filesystem_id)
        archive_strcpy(&te->name, path);
        te->flags = needsDescent | needsOpen | needsAscent;
        te->filesystem_id = filesystem_id;
+       te->dev = dev;
+       te->ino = ino;
        te->dirname_length = t->dirname_length;
 }
 
@@ -1255,7 +1261,7 @@ tree_reopen(struct tree *t, const char *path)
        archive_string_empty(&t->path);
 
        /* First item is set up a lot like a symlink traversal. */
-       tree_push(t, path, 0);
+       tree_push(t, path, 0, 0, 0);
        t->stack->flags = needsFirstVisit;
        t->maxOpenCount = t->openCount = 1;
        t->initial_dir_fd = open(".", O_RDONLY);
@@ -1592,6 +1598,21 @@ tree_current_is_physical_dir(struct tree *t)
        return (S_ISDIR(st->st_mode));
 }
 
+/*
+ * Test whether the same file has been in the tree as its parent.
+ */
+static int
+tree_target_is_same_as_parent(struct tree *t, const struct stat *st)
+{
+       struct tree_entry *te;
+
+       for (te = t->current->parent; te != NULL; te = te->parent) {
+               if (te->dev == st->st_dev && te->ino == st->st_ino)
+                       return (1);
+       }
+       return (0);
+}
+
 /*
  * Return the access path for the entry just returned from tree_next().
  */
index acf092be2f301df6e39a99d35e77b31fa12956fc..f8fe52f75379a92891de374d39a2ec7bac5610cc 100644 (file)
@@ -115,9 +115,10 @@ struct tree_entry {
        size_t                   full_path_dir_length;
        struct archive_wstring   name;
        size_t                   dirname_length;
-       dev_t                    dev;
-       ino_t                    ino;
+       int64_t                  dev;
+       int64_t                  ino;
        int                      flags;
+       int                      filesystem_id;
 };
 
 struct filesystem {
@@ -189,7 +190,12 @@ struct tree {
        int                      allocated_filesytem;
 };
 
-#define dev_no(st)     st->dwVolumeSerialNumber
+#define bhfi_dev(bhfi) ((bhfi)->dwVolumeSerialNumber)
+/* Treat FileIndex as i-node. We should remove a sequence number
+ * which is high-16-bits of nFileIndexHigh. */
+#define bhfi_ino(bhfi) \
+       ((((int64_t)((bhfi)->nFileIndexHigh & 0x0000FFFFUL)) << 32) \
+    + (bhfi)->nFileIndexLow)
 
 /* Definitions for tree.flags bitmap. */
 #define        hasStat         16 /* The st entry is valid. */
@@ -211,7 +217,7 @@ static struct tree *tree_open(const char *, int);
 static struct tree *tree_reopen(struct tree *, const char *);
 static void tree_close(struct tree *);
 static void tree_free(struct tree *);
-static void tree_push(struct tree *, const wchar_t *);
+static void tree_push(struct tree *, const wchar_t *, int, int64_t, int64_t);
 
 /*
  * tree_next() returns Zero if there is no next entry, non-zero if
@@ -276,6 +282,8 @@ static int tree_current_is_dir(struct tree *);
 static int update_filesystem(struct archive_read_disk *a,
                    int64_t dev);
 static int setup_current_filesystem(struct archive_read_disk *);
+static int tree_target_is_same_as_parent(struct tree *,
+                   const BY_HANDLE_FILE_INFORMATION *);
 
 static int     _archive_read_free(struct archive *);
 static int     _archive_read_close(struct archive *);
@@ -705,7 +713,7 @@ _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
                a->follow_symlinks = 1;
                /* 'L': Archive symlinks as targets, if we can. */
                st = tree_current_stat(t);
-               if (st != NULL)
+               if (st != NULL && !tree_target_is_same_as_parent(t, st))
                        break;
                /* If stat fails, we have a broken symlink;
                 * in that case, don't follow the link. */
@@ -721,7 +729,7 @@ _archive_read_next_header2(struct archive *_a, struct archive_entry *entry)
                break;
        }
 
-       if (update_filesystem(a, dev_no(lst)) != ARCHIVE_OK) {
+       if (update_filesystem(a, bhfi_dev(lst)) != ARCHIVE_OK) {
                a->archive.state = ARCHIVE_STATE_FATAL;
                return (ARCHIVE_FATAL);
        }
@@ -856,10 +864,12 @@ archive_read_disk_descend(struct archive *_a)
        }
 
        if (tree_current_is_physical_dir(t)) {
-               tree_push(t, t->basename);
+               tree_push(t, t->basename, t->current_filesystem_id,
+                   bhfi_dev(&(t->lst)), bhfi_ino(&(t->lst)));
                t->stack->flags |= isDir;
        } else if (tree_current_is_dir(t)) {
-               tree_push(t, t->basename);
+               tree_push(t, t->basename, t->current_filesystem_id,
+                   bhfi_dev(&(t->st)), bhfi_ino(&(t->st)));
                t->stack->flags |= isDirLink;
        }
        t->descend = 0;
@@ -1045,7 +1055,8 @@ setup_current_filesystem(struct archive_read_disk *a)
  * Add a directory path to the current stack.
  */
 static void
-tree_push(struct tree *t, const wchar_t *path)
+tree_push(struct tree *t, const wchar_t *path, int filesystem_id,
+    int64_t dev, int64_t ino)
 {
        struct tree_entry *te;
 
@@ -1059,6 +1070,9 @@ tree_push(struct tree *t, const wchar_t *path)
        archive_string_init(&te->name);
        archive_wstrcpy(&te->name, path);
        te->flags = needsDescent | needsOpen | needsAscent;
+       te->filesystem_id = filesystem_id;
+       te->dev = dev;
+       te->ino = ino;
        te->dirname_length = t->dirname_length;
        te->full_path_dir_length = t->full_path_dir_length;
 }
@@ -1262,7 +1276,7 @@ tree_reopen(struct tree *t, const char *path)
                        t->full_path_dir_length = archive_strlen(&t->full_path);
                }
        }
-       tree_push(t, base);
+       tree_push(t, base, 0, 0, 0);
        free(pathname);
        t->stack->flags = needsFirstVisit | isDirLink | needsAscent;
        return (t);
@@ -1468,12 +1482,8 @@ tree_archive_entry_copy_bhfi(struct archive_entry *entry, struct tree *t,
        archive_entry_set_mtime(entry, secs, nsecs);
        fileTimeToUtc(&bhfi->ftCreationTime, &secs, &nsecs);
        archive_entry_set_birthtime(entry, secs, nsecs);
-       archive_entry_set_dev(entry, bhfi->dwVolumeSerialNumber);
-       /* Set FileIndex as i-node. We should remove a sequence number
-        * which is high-16-bits of nFileIndexHigh. */
-       archive_entry_set_ino64(entry,
-           (((int64_t)(bhfi->nFileIndexHigh & 0x0000FFFFUL)) << 32)
-           + bhfi->nFileIndexLow);
+       archive_entry_set_dev(entry, bhfi_dev(bhfi));
+       archive_entry_set_ino64(entry, bhfi_ino(bhfi));
        if (bhfi->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                archive_entry_set_nlink(entry, bhfi->nNumberOfLinks + 1);
        else
@@ -1481,7 +1491,6 @@ tree_archive_entry_copy_bhfi(struct archive_entry *entry, struct tree *t,
        archive_entry_set_size(entry,
            (((int64_t)bhfi->nFileSizeHigh) << 32)
            + bhfi->nFileSizeLow);
-       archive_entry_set_dev(entry, bhfi->dwVolumeSerialNumber);
        archive_entry_set_uid(entry, 0);
        archive_entry_set_gid(entry, 0);
        archive_entry_set_rdev(entry, 0);
@@ -1612,6 +1621,24 @@ tree_current_is_physical_link(struct tree *t)
        return (0);
 }
 
+/*
+ * Test whether the same file has been in the tree as its parent.
+ */
+static int
+tree_target_is_same_as_parent(struct tree *t,
+    const BY_HANDLE_FILE_INFORMATION *st)
+{
+       struct tree_entry *te;
+       int64_t dev = bhfi_dev(st);
+       int64_t ino = bhfi_ino(st);
+
+       for (te = t->current->parent; te != NULL; te = te->parent) {
+               if (te->dev == dev && te->ino == ino)
+                       return (1);
+       }
+       return (0);
+}
+
 /*
  * Return the access path for the entry just returned from tree_next().
  */
index cccc8d7c5ed5971efbf7d086e53ddcb142f8cf9e..906e5e66927fa2d47b7e641c0fd0d98f0c81f6d7 100644 (file)
@@ -675,6 +675,85 @@ test_symlink_logical()
        archive_entry_free(ae);
 }
 
+static void
+test_symlink_logical_loop()
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       const void *p;
+       size_t size;
+       int64_t offset;
+       int file_count;
+
+       if (!canSymlink()) {
+               skipping("Can't test symlinks on this filesystem");
+               return;
+       }
+
+       /*
+        * Create a sample archive.
+        */
+       assertMakeDir("l2", 0755);
+       assertChdir("l2");
+       assertMakeDir("d1", 0755);
+       assertMakeDir("d1/d2", 0755);
+       assertMakeDir("d1/d2/d3", 0755);
+       assertMakeDir("d2", 0755);
+       assertMakeFile("d2/file1", 0644, "d2/file1");
+       assertMakeSymlink("d1/d2/ld1", "../../d1");
+       assertMakeSymlink("d1/d2/ld2", "../../d2");
+       assertChdir("..");
+
+       assert((ae = archive_entry_new()) != NULL);
+       assert((a = archive_read_disk_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_set_symlink_logical(a));
+
+       /*
+        * Specified file is a symbolic link file.
+        */
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_disk_open(a, "l2/d1"));
+       file_count = 6;
+
+       while (file_count--) {
+               assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header2(a, ae));
+               if (strcmp(archive_entry_pathname(ae), "l2/d1") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFDIR);
+               } else if (strcmp(archive_entry_pathname(ae), "l2/d1/d2") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFDIR);
+               } else if (strcmp(archive_entry_pathname(ae), "l2/d1/d2/d3") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFDIR);
+               } else if (strcmp(archive_entry_pathname(ae), "l2/d1/d2/ld1") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFLNK);
+               } else if (strcmp(archive_entry_pathname(ae), "l2/d1/d2/ld2") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFDIR);
+               } else if (strcmp(archive_entry_pathname(ae),
+                   "l2/d1/d2/ld2/file1") == 0) {
+                       assertEqualInt(archive_entry_filetype(ae), AE_IFREG);
+                       assertEqualInt(archive_entry_size(ae), 8);
+                       assertEqualIntA(a, ARCHIVE_OK,
+                           archive_read_data_block(a, &p, &size, &offset));
+                       assertEqualInt((int)size, 8);
+                       assertEqualInt((int)offset, 0);
+                       assertEqualInt(memcmp(p, "d2/file1", 8), 0);
+                       assertEqualInt(ARCHIVE_EOF,
+                           archive_read_data_block(a, &p, &size, &offset));
+                       assertEqualInt((int)size, 0);
+                       assertEqualInt((int)offset, 8);
+               }
+               if (archive_entry_filetype(ae) == AE_IFDIR) {
+                       /* Descend into the current object */
+                       assertEqualIntA(a, ARCHIVE_OK,
+                           archive_read_disk_descend(a));
+               }
+       }
+       /* There is no entry. */
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header2(a, ae));
+       /* Destroy the disk object. */
+       assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+       archive_entry_free(ae);
+}
+
 DEFINE_TEST(test_read_disk_directory_traversals)
 {
        /* Basic test. */
@@ -683,4 +762,6 @@ DEFINE_TEST(test_read_disk_directory_traversals)
        test_symlink_hybrid();
        /* Test logcal mode; follow all symlinks. */
        test_symlink_logical();
+       /* Test logcal mode; prevent loop in symlinks. */ 
+       test_symlink_logical_loop();
 }