From 13afc8cbde82b559f15f7d70c3d55a89f7425d01 Mon Sep 17 00:00:00 2001 From: Duncan Horn <40036384+dunhor@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:23:13 -0700 Subject: [PATCH] Update archive_entry_link_resolver to copy the "wide" pathname for hardlinks on Windows (#2225) On Windows, if you are using `archive_entry_link_resolver` and give it an entry that links to past entry whose pathname was set using a "wide" string that cannot be represented by the current locale (i.e. WCS -> MBS conversion fails), this code will crash due to a null pointer read. This updates to use the `_w` function instead on Windows. Note: this is a partial cherry-pick from https://github.com/libarchive/libarchive/pull/2095, which I'm going to go through and break into smaller pieces in hopes of getting some things in while discussion of other things can continue. --- libarchive/archive_entry_link_resolver.c | 15 ++++++++ libarchive/test/test_link_resolver.c | 45 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/libarchive/archive_entry_link_resolver.c b/libarchive/archive_entry_link_resolver.c index 6c6173430..c2fd6895f 100644 --- a/libarchive/archive_entry_link_resolver.c +++ b/libarchive/archive_entry_link_resolver.c @@ -201,16 +201,26 @@ archive_entry_linkify(struct archive_entry_linkresolver *res, le = find_entry(res, *e); if (le != NULL) { archive_entry_unset_size(*e); +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif } else insert_entry(res, *e); return; case ARCHIVE_ENTRY_LINKIFY_LIKE_MTREE: le = find_entry(res, *e); if (le != NULL) { +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif } else insert_entry(res, *e); return; @@ -229,8 +239,13 @@ archive_entry_linkify(struct archive_entry_linkresolver *res, le->entry = t; /* Make the old entry into a hardlink. */ archive_entry_unset_size(*e); +#if defined(_WIN32) && !defined(__CYGWIN__) + archive_entry_copy_hardlink_w(*e, + archive_entry_pathname_w(le->canonical)); +#else archive_entry_copy_hardlink(*e, archive_entry_pathname(le->canonical)); +#endif /* If we ran out of links, return the * final entry as well. */ if (le->links == 0) { diff --git a/libarchive/test/test_link_resolver.c b/libarchive/test/test_link_resolver.c index 5bea9a463..6c6230c4d 100644 --- a/libarchive/test/test_link_resolver.c +++ b/libarchive/test/test_link_resolver.c @@ -202,3 +202,48 @@ DEFINE_TEST(test_link_resolver) test_linkify_old_cpio(); test_linkify_new_cpio(); } + +DEFINE_TEST(test_link_resolver_unicode_win) +{ +#if !defined(_WIN32) || defined(__CYGWIN__) + skipping("This test is meant to verify unicode string handling" + " on Windows with UTF-16 names"); + return; +#else + struct archive_entry *entry, *e2; + struct archive_entry_linkresolver *resolver; + + /* Initialize the resolver. */ + assert(NULL != (resolver = archive_entry_linkresolver_new())); + archive_entry_linkresolver_set_strategy(resolver, + ARCHIVE_FORMAT_TAR_USTAR); + + /* Create an entry with a unicode filename and 2 links. */ + assert(NULL != (entry = archive_entry_new())); + archive_entry_copy_pathname_w(entry, L"\u4f60\u597d.txt"); + archive_entry_set_ino(entry, 1); + archive_entry_set_dev(entry, 2); + archive_entry_set_nlink(entry, 2); + archive_entry_set_size(entry, 10); + archive_entry_linkify(resolver, &entry, &e2); + + /* Shouldn't be altered, since it wasn't seen before. */ + assert(e2 == NULL); + assertEqualWString(L"\u4f60\u597d.txt", archive_entry_pathname_w(entry)); + assertEqualWString(NULL, archive_entry_hardlink_w(entry)); + assertEqualInt(10, archive_entry_size(entry)); + + /* Link to the same file contents, but a new unicode name. */ + archive_entry_copy_pathname_w(entry, L"\u518d\u89c1.txt"); + archive_entry_linkify(resolver, &entry, &e2); + + /* Size & link path should have changed. */ + assert(e2 == NULL); + assertEqualWString(L"\u518d\u89c1.txt", archive_entry_pathname_w(entry)); + assertEqualWString(L"\u4f60\u597d.txt", archive_entry_hardlink_w(entry)); + assertEqualInt(0, archive_entry_size(entry)); + + archive_entry_free(entry); + archive_entry_linkresolver_free(resolver); +#endif +} -- 2.47.2