Skip to content

Commit 5e646b8

Browse files
committed
Fix extracting hardlinks to symlinks
On platforms that support the linkat(2) function we can safely write hardlinks to symlinks as linkat(2) does not follow symlinks by default. Fixes libarchive#1044
1 parent c6f17a0 commit 5e646b8

6 files changed

Lines changed: 86 additions & 6 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,7 @@ CHECK_FUNCTION_EXISTS_GLIBC(lchflags HAVE_LCHFLAGS)
13521352
CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD)
13531353
CHECK_FUNCTION_EXISTS_GLIBC(lchown HAVE_LCHOWN)
13541354
CHECK_FUNCTION_EXISTS_GLIBC(link HAVE_LINK)
1355+
CHECK_FUNCTION_EXISTS_GLIBC(linkat HAVE_LINKAT)
13551356
CHECK_FUNCTION_EXISTS_GLIBC(localtime_r HAVE_LOCALTIME_R)
13561357
CHECK_FUNCTION_EXISTS_GLIBC(lstat HAVE_LSTAT)
13571358
CHECK_FUNCTION_EXISTS_GLIBC(lutimes HAVE_LUTIMES)

build/cmake/config.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,9 @@ typedef uint64_t uintmax_t;
744744
/* Define to 1 if you have the `link' function. */
745745
#cmakedefine HAVE_LINK 1
746746

747+
/* Define to 1 if you have the `linkat' function. */
748+
#cmakedefine HAVE_LINKAT 1
749+
747750
/* Define to 1 if you have the <linux/fiemap.h> header file. */
748751
#cmakedefine HAVE_LINUX_FIEMAP_H 1
749752

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ AC_CHECK_FUNCS([fstat fstatat fstatfs fstatvfs ftruncate])
656656
AC_CHECK_FUNCS([futimens futimes futimesat])
657657
AC_CHECK_FUNCS([geteuid getpid getgrgid_r getgrnam_r])
658658
AC_CHECK_FUNCS([getpwnam_r getpwuid_r getvfsbyname gmtime_r])
659-
AC_CHECK_FUNCS([lchflags lchmod lchown link localtime_r lstat lutimes])
659+
AC_CHECK_FUNCS([lchflags lchmod lchown link linkat localtime_r lstat lutimes])
660660
AC_CHECK_FUNCS([mbrtowc memmove memset])
661661
AC_CHECK_FUNCS([mkdir mkfifo mknod mkstemp])
662662
AC_CHECK_FUNCS([nl_langinfo openat pipe poll posix_spawnp readlink readlinkat])

libarchive/archive_write_disk_posix.c

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ static int la_mktemp(struct archive_write_disk *);
360360
static void fsobj_error(int *, struct archive_string *, int, const char *,
361361
const char *);
362362
static int check_symlinks_fsobj(char *, int *, struct archive_string *,
363-
int);
363+
int, int);
364364
static int check_symlinks(struct archive_write_disk *);
365365
static int create_filesystem_object(struct archive_write_disk *);
366366
static struct fixup_entry *current_fixup(struct archive_write_disk *,
@@ -2263,7 +2263,7 @@ create_filesystem_object(struct archive_write_disk *a)
22632263
return (EPERM);
22642264
}
22652265
r = check_symlinks_fsobj(linkname_copy, &error_number,
2266-
&error_string, a->flags);
2266+
&error_string, a->flags, 1);
22672267
if (r != ARCHIVE_OK) {
22682268
archive_set_error(&a->archive, error_number, "%s",
22692269
error_string.s);
@@ -2284,7 +2284,12 @@ create_filesystem_object(struct archive_write_disk *a)
22842284
*/
22852285
if (a->flags & ARCHIVE_EXTRACT_SAFE_WRITES)
22862286
unlink(a->name);
2287+
#ifdef HAVE_LINKAT
2288+
r = linkat(AT_FDCWD, linkname, AT_FDCWD, a->name,
2289+
0) ? errno : 0;
2290+
#else
22872291
r = link(linkname, a->name) ? errno : 0;
2292+
#endif
22882293
/*
22892294
* New cpio and pax formats allow hardlink entries
22902295
* to carry data, so we may have to open the file
@@ -2675,7 +2680,7 @@ fsobj_error(int *a_eno, struct archive_string *a_estr,
26752680
*/
26762681
static int
26772682
check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
2678-
int flags)
2683+
int flags, int extracting_hardlink)
26792684
{
26802685
#if !defined(HAVE_LSTAT) && \
26812686
!(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_UNLINKAT))
@@ -2684,6 +2689,7 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
26842689
(void)error_number; /* UNUSED */
26852690
(void)error_string; /* UNUSED */
26862691
(void)flags; /* UNUSED */
2692+
(void)extracting_hardlink; /* UNUSED */
26872693
return (ARCHIVE_OK);
26882694
#else
26892695
int res = ARCHIVE_OK;
@@ -2805,6 +2811,28 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
28052811
head = tail + 1;
28062812
}
28072813
} else if (S_ISLNK(st.st_mode)) {
2814+
if (last && extracting_hardlink) {
2815+
#ifdef HAVE_LINKAT
2816+
/*
2817+
* Hardlinks to symlinks are safe to write
2818+
* if linkat() is supported as it does not
2819+
* follow symlinks.
2820+
*/
2821+
res = ARCHIVE_OK;
2822+
#else
2823+
/*
2824+
* We return ARCHIVE_FAILED here as we are
2825+
* not able to safely write hardlinks
2826+
* to symlinks.
2827+
*/
2828+
tail[0] = c;
2829+
fsobj_error(a_eno, a_estr, errno,
2830+
"Cannot write hardlink to symlink ",
2831+
path);
2832+
res = ARCHIVE_FAILED;
2833+
#endif
2834+
break;
2835+
} else
28082836
if (last) {
28092837
/*
28102838
* Last element is symlink; remove it
@@ -2971,7 +2999,7 @@ check_symlinks(struct archive_write_disk *a)
29712999
int rc;
29723000
archive_string_init(&error_string);
29733001
rc = check_symlinks_fsobj(a->name, &error_number, &error_string,
2974-
a->flags);
3002+
a->flags, 0);
29753003
if (rc != ARCHIVE_OK) {
29763004
archive_set_error(&a->archive, error_number, "%s",
29773005
error_string.s);

libarchive/config_freebsd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
#define HAVE_LIBZ 1
139139
#define HAVE_LIMITS_H 1
140140
#define HAVE_LINK 1
141+
#define HAVE_LINKAT 1
141142
#define HAVE_LOCALE_H 1
142143
#define HAVE_LOCALTIME_R 1
143144
#define HAVE_LONG_LONG_INT 1

libarchive/test/test_write_disk_hardlink.c

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ DEFINE_TEST(test_write_disk_hardlink)
4949
static const char data[]="abcdefghijklmnopqrstuvwxyz";
5050
struct archive *ad;
5151
struct archive_entry *ae;
52+
#ifdef HAVE_LINKAT
53+
int can_symlink;
54+
#endif
5255
int r;
5356

5457
/* Force the umask to something predictable. */
@@ -147,7 +150,7 @@ DEFINE_TEST(test_write_disk_hardlink)
147150
archive_entry_free(ae);
148151

149152
/*
150-
* Finally, try a new-cpio-like approach, where the initial
153+
* Third, try a new-cpio-like approach, where the initial
151154
* regular file is empty and the hardlink has the data.
152155
*/
153156

@@ -174,6 +177,41 @@ DEFINE_TEST(test_write_disk_hardlink)
174177
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
175178
}
176179
archive_entry_free(ae);
180+
181+
#ifdef HAVE_LINKAT
182+
/* Finally, try creating a hard link to a dangling symlink */
183+
can_symlink = canSymlink();
184+
if (can_symlink) {
185+
/* Symbolic link: link5a -> foo */
186+
assert((ae = archive_entry_new()) != NULL);
187+
archive_entry_copy_pathname(ae, "link5a");
188+
archive_entry_set_mode(ae, AE_IFLNK | 0642);
189+
archive_entry_unset_size(ae);
190+
archive_entry_copy_symlink(ae, "foo");
191+
assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
192+
if (r >= ARCHIVE_WARN) {
193+
assertEqualInt(ARCHIVE_WARN,
194+
archive_write_data(ad, data, sizeof(data)));
195+
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
196+
}
197+
archive_entry_free(ae);
198+
199+
200+
/* Link. Size of zero means this doesn't carry data. */
201+
assert((ae = archive_entry_new()) != NULL);
202+
archive_entry_copy_pathname(ae, "link5b");
203+
archive_entry_set_mode(ae, S_IFREG | 0642);
204+
archive_entry_set_size(ae, 0);
205+
archive_entry_copy_hardlink(ae, "link5a");
206+
assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
207+
if (r >= ARCHIVE_WARN) {
208+
assertEqualInt(ARCHIVE_WARN,
209+
archive_write_data(ad, data, sizeof(data)));
210+
assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
211+
}
212+
archive_entry_free(ae);
213+
}
214+
#endif
177215
assertEqualInt(0, archive_write_free(ad));
178216

179217
/* Test the entries on disk. */
@@ -211,5 +249,14 @@ DEFINE_TEST(test_write_disk_hardlink)
211249
assertFileNLinks("link4a", 2);
212250
assertFileSize("link4a", sizeof(data));
213251
assertIsHardlink("link4a", "link4b");
252+
253+
#ifdef HAVE_LINKAT
254+
if (can_symlink) {
255+
/* Test #5 */
256+
assertIsSymlink("link5a", "foo", 0);
257+
assertFileNLinks("link5a", 2);
258+
assertIsHardlink("link5a", "link5b");
259+
}
260+
#endif
214261
#endif
215262
}

0 commit comments

Comments
 (0)