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. */
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 *);
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 *);
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. */
}
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;
* 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;
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;
}
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);
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().
*/
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 {
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. */
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
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 *);
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. */
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);
}
}
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;
* 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;
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;
}
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);
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
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);
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().
*/
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. */
test_symlink_hybrid();
/* Test logcal mode; follow all symlinks. */
test_symlink_logical();
+ /* Test logcal mode; prevent loop in symlinks. */
+ test_symlink_logical_loop();
}