Skip to content

Commit 8a1bd5c

Browse files
committed
Fix following symlinks when processing the fixup list
The previous fix in b41daec was incomplete. Fixup entries are given the original path without calling cleanup_pathname(). To make sure we don't follow a symlink, we must strip trailing slashes from the path. The fixup entries are always directories. Make sure we try to modify only directories by providing O_DIRECTORY to open() (if supported) and if it fails to check directory via lstat(). Fixes libarchive#1566
1 parent b9f4955 commit 8a1bd5c

2 files changed

Lines changed: 78 additions & 28 deletions

File tree

libarchive/archive_write_disk_posix.c

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,6 +2462,7 @@ _archive_write_disk_close(struct archive *_a)
24622462
struct archive_write_disk *a = (struct archive_write_disk *)_a;
24632463
struct fixup_entry *next, *p;
24642464
struct stat st;
2465+
char *c;
24652466
int fd, ret;
24662467

24672468
archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
@@ -2475,24 +2476,49 @@ _archive_write_disk_close(struct archive *_a)
24752476
while (p != NULL) {
24762477
fd = -1;
24772478
a->pst = NULL; /* Mark stat cache as out-of-date. */
2478-
if (p->fixup &
2479-
(TODO_TIMES | TODO_MODE_BASE | TODO_ACLS | TODO_FFLAGS)) {
2480-
fd = open(p->name,
2481-
O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
2479+
2480+
/* We must strip trailing slashes from the path to avoid
2481+
dereferencing symbolic links to directories */
2482+
c = p->name;
2483+
while (*c != '\0')
2484+
c++;
2485+
while (c != p->name && *(c - 1) == '/') {
2486+
c--;
2487+
*c = '\0';
2488+
}
2489+
2490+
if (p->fixup == 0)
2491+
goto skip_fixup_entry;
2492+
else {
2493+
fd = open(p->name, O_BINARY | O_NOFOLLOW | O_RDONLY
2494+
#if defined(O_DIRECTORY)
2495+
| O_DIRECTORY
2496+
#endif
2497+
| O_CLOEXEC);
2498+
/*
2499+
` * If we don't support O_DIRECTORY,
2500+
* or open() has failed, we must stat()
2501+
* to verify that we are opening a directory
2502+
*/
2503+
#if defined(O_DIRECTORY)
24822504
if (fd == -1) {
2483-
/* If we cannot lstat, skip entry */
2484-
if (lstat(p->name, &st) != 0)
2505+
if (lstat(p->name, &st) != 0 ||
2506+
!S_ISDIR(st.st_mode)) {
24852507
goto skip_fixup_entry;
2486-
/*
2487-
* If we deal with a symbolic link, mark
2488-
* it in the fixup mode to ensure no
2489-
* modifications are made to its target.
2490-
*/
2491-
if (S_ISLNK(st.st_mode)) {
2492-
p->mode &= ~S_IFMT;
2493-
p->mode |= S_IFLNK;
24942508
}
24952509
}
2510+
#else
2511+
#if HAVE_FSTAT
2512+
if (fd > 0 && (
2513+
fstat(fd, &st) != 0 || !S_ISDIR(st.st_mode))) {
2514+
goto skip_fixup_entry;
2515+
} else
2516+
#endif
2517+
if (lstat(p->name, &st) != 0 ||
2518+
!S_ISDIR(st.st_mode)) {
2519+
goto skip_fixup_entry;
2520+
}
2521+
#endif
24962522
}
24972523
if (p->fixup & TODO_TIMES) {
24982524
set_times(a, fd, p->mode, p->name,
@@ -2504,14 +2530,13 @@ _archive_write_disk_close(struct archive *_a)
25042530
if (p->fixup & TODO_MODE_BASE) {
25052531
#ifdef HAVE_FCHMOD
25062532
if (fd >= 0)
2507-
fchmod(fd, p->mode);
2533+
fchmod(fd, p->mode & 07777);
25082534
else
25092535
#endif
25102536
#ifdef HAVE_LCHMOD
2511-
lchmod(p->name, p->mode);
2537+
lchmod(p->name, p->mode & 07777);
25122538
#else
2513-
if (!S_ISLNK(p->mode))
2514-
chmod(p->name, p->mode);
2539+
chmod(p->name, p->mode & 07777);
25152540
#endif
25162541
}
25172542
if (p->fixup & TODO_ACLS)
@@ -2664,7 +2689,6 @@ new_fixup(struct archive_write_disk *a, const char *pathname)
26642689
fe->next = a->fixup_list;
26652690
a->fixup_list = fe;
26662691
fe->fixup = 0;
2667-
fe->mode = 0;
26682692
fe->name = strdup(pathname);
26692693
return (fe);
26702694
}

libarchive/test/test_write_disk_fixup.c

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,50 @@ DEFINE_TEST(test_write_disk_fixup)
4747
/*
4848
* Create a file
4949
*/
50-
assertMakeFile("victim", 0600, "a");
50+
assertMakeFile("file", 0600, "a");
51+
52+
/*
53+
* Create a directory
54+
*/
55+
assertMakeDir("dir", 0700);
5156

5257
/*
5358
* Create a directory and a symlink with the same name
5459
*/
5560

56-
/* Directory: dir */
61+
/* Directory: dir1 */
62+
assert((ae = archive_entry_new()) != NULL);
63+
archive_entry_copy_pathname(ae, "dir1/");
64+
archive_entry_set_mode(ae, AE_IFDIR | 0555);
65+
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
66+
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
67+
archive_entry_free(ae);
68+
69+
/* Directory: dir2 */
5770
assert((ae = archive_entry_new()) != NULL);
58-
archive_entry_copy_pathname(ae, "dir");
59-
archive_entry_set_mode(ae, AE_IFDIR | 0606);
71+
archive_entry_copy_pathname(ae, "dir2/");
72+
archive_entry_set_mode(ae, AE_IFDIR | 0555);
6073
assertEqualIntA(ad, 0, archive_write_header(ad, ae));
6174
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
6275
archive_entry_free(ae);
6376

64-
/* Symbolic Link: dir -> foo */
77+
/* Symbolic Link: dir1 -> dir */
78+
assert((ae = archive_entry_new()) != NULL);
79+
archive_entry_copy_pathname(ae, "dir1");
80+
archive_entry_set_mode(ae, AE_IFLNK | 0777);
81+
archive_entry_set_size(ae, 0);
82+
archive_entry_copy_symlink(ae, "dir");
83+
assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
84+
if (r >= ARCHIVE_WARN)
85+
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
86+
archive_entry_free(ae);
87+
88+
/* Symbolic Link: dir2 -> file */
6589
assert((ae = archive_entry_new()) != NULL);
66-
archive_entry_copy_pathname(ae, "dir");
90+
archive_entry_copy_pathname(ae, "dir2");
6791
archive_entry_set_mode(ae, AE_IFLNK | 0777);
6892
archive_entry_set_size(ae, 0);
69-
archive_entry_copy_symlink(ae, "victim");
93+
archive_entry_copy_symlink(ae, "file");
7094
assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
7195
if (r >= ARCHIVE_WARN)
7296
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
@@ -75,7 +99,9 @@ DEFINE_TEST(test_write_disk_fixup)
7599
assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
76100

77101
/* Test the entries on disk. */
78-
assertIsSymlink("dir", "victim", 0);
79-
assertFileMode("victim", 0600);
102+
assertIsSymlink("dir1", "dir", 0);
103+
assertIsSymlink("dir2", "file", 0);
104+
assertFileMode("dir", 0700);
105+
assertFileMode("file", 0600);
80106
#endif
81107
}

0 commit comments

Comments
 (0)