From: Michihiro NAKAJIMA Date: Fri, 14 Jan 2011 12:08:30 +0000 (-0500) Subject: Add loop check to the directory traversals. X-Git-Tag: v3.0.0a~764 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=76cc921453296fe9f74c8bd414fa762c92506851;p=thirdparty%2Flibarchive.git Add loop check to the directory traversals. SVN-Revision: 2886 --- diff --git a/libarchive/archive_read_disk_posix.c b/libarchive/archive_read_disk_posix.c index fbf4e51a8..ef3bf55d0 100644 --- a/libarchive/archive_read_disk_posix.c +++ b/libarchive/archive_read_disk_posix.c @@ -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(). */ diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c index acf092be2..f8fe52f75 100644 --- a/libarchive/archive_read_disk_windows.c +++ b/libarchive/archive_read_disk_windows.c @@ -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(). */ diff --git a/libarchive/test/test_read_disk_directory_traversals.c b/libarchive/test/test_read_disk_directory_traversals.c index cccc8d7c5..906e5e669 100644 --- a/libarchive/test/test_read_disk_directory_traversals.c +++ b/libarchive/test/test_read_disk_directory_traversals.c @@ -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(); }