Skip to content

Commit d4cf95c

Browse files
committed
archive_write: Fix crash on failure to convert WCS/UTF-8 pathname to MBS
If an entry pathname is set only by WCS or UTF-8, it may not have any MBS representation in the archive's hdrcharset. Do not crash or create an archive with an empty pathname. Furthermore, the entry pathname may not have any MBS representation in the current locale. Do not report a `(null)` pathname in the error message.
1 parent 84bda64 commit d4cf95c

9 files changed

Lines changed: 288 additions & 24 deletions

libarchive/archive_write_set_format_gnutar.c

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,17 @@ archive_write_gnutar_header(struct archive_write *a,
293293
} else
294294
sconv = gnutar->opt_sconv;
295295

296+
/* Sanity check. */
297+
if (archive_entry_pathname(entry) == NULL
298+
#if defined(_WIN32) && !defined(__CYGWIN__)
299+
&& archive_entry_pathname_w(entry) == NULL
300+
#endif
301+
) {
302+
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
303+
"Can't record entry in tar file without pathname");
304+
return ARCHIVE_FAILED;
305+
}
306+
296307
/* Only regular files (not hardlinks) have data. */
297308
if (archive_entry_hardlink(entry) != NULL ||
298309
archive_entry_symlink(entry) != NULL ||
@@ -385,17 +396,30 @@ archive_write_gnutar_header(struct archive_write *a,
385396
r = archive_entry_pathname_l(entry, &(gnutar->pathname),
386397
&(gnutar->pathname_length), sconv);
387398
if (r != 0) {
399+
const char* p_mbs;
388400
if (errno == ENOMEM) {
389401
archive_set_error(&a->archive, ENOMEM,
390402
"Can't allocate memory for pathname");
391403
ret = ARCHIVE_FATAL;
392404
goto exit_write_header;
393405
}
394-
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
395-
"Can't translate pathname '%s' to %s",
396-
archive_entry_pathname(entry),
397-
archive_string_conversion_charset_name(sconv));
398-
ret2 = ARCHIVE_WARN;
406+
p_mbs = archive_entry_pathname(entry);
407+
if (p_mbs) {
408+
/* We have a wrongly-encoded MBS pathname.
409+
* Warn and use it. */
410+
archive_set_error(&a->archive,
411+
ARCHIVE_ERRNO_FILE_FORMAT,
412+
"Can't translate pathname '%s' to %s", p_mbs,
413+
archive_string_conversion_charset_name(sconv));
414+
ret2 = ARCHIVE_WARN;
415+
} else {
416+
/* We have no MBS pathname. Fail. */
417+
archive_set_error(&a->archive,
418+
ARCHIVE_ERRNO_FILE_FORMAT,
419+
"Can't translate pathname to %s",
420+
archive_string_conversion_charset_name(sconv));
421+
return ARCHIVE_FAILED;
422+
}
399423
}
400424
r = archive_entry_uname_l(entry, &(gnutar->uname),
401425
&(gnutar->uname_length), sconv);

libarchive/archive_write_set_format_ustar.c

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,11 @@ archive_write_ustar_header(struct archive_write *a, struct archive_entry *entry)
254254
sconv = ustar->opt_sconv;
255255

256256
/* Sanity check. */
257+
if (archive_entry_pathname(entry) == NULL
257258
#if defined(_WIN32) && !defined(__CYGWIN__)
258-
if (archive_entry_pathname_w(entry) == NULL) {
259-
#else
260-
if (archive_entry_pathname(entry) == NULL) {
259+
&& archive_entry_pathname_w(entry) == NULL
261260
#endif
261+
) {
262262
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
263263
"Can't record entry in tar file without pathname");
264264
return (ARCHIVE_FAILED);
@@ -409,15 +409,28 @@ __archive_write_format_header_ustar(struct archive_write *a, char h[512],
409409
*/
410410
r = archive_entry_pathname_l(entry, &pp, &copy_length, sconv);
411411
if (r != 0) {
412+
const char* p_mbs;
412413
if (errno == ENOMEM) {
413414
archive_set_error(&a->archive, ENOMEM,
414415
"Can't allocate memory for Pathname");
415416
return (ARCHIVE_FATAL);
416417
}
417-
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
418-
"Can't translate pathname '%s' to %s",
419-
pp, archive_string_conversion_charset_name(sconv));
420-
ret = ARCHIVE_WARN;
418+
p_mbs = archive_entry_pathname(entry);
419+
if (p_mbs) {
420+
/* We have a wrongly-encoded MBS pathname.
421+
* Warn and use it. */
422+
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
423+
"Can't translate pathname '%s' to %s", p_mbs,
424+
archive_string_conversion_charset_name(sconv));
425+
ret = ARCHIVE_WARN;
426+
} else {
427+
/* We have no MBS pathname. Fail. */
428+
archive_set_error(&a->archive,
429+
ARCHIVE_ERRNO_FILE_FORMAT,
430+
"Can't translate pathname to %s",
431+
archive_string_conversion_charset_name(sconv));
432+
return ARCHIVE_FAILED;
433+
}
421434
}
422435
if (copy_length <= USTAR_name_size)
423436
memcpy(h + USTAR_name_offset, pp, copy_length);

libarchive/archive_write_set_format_v7tar.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,11 @@ archive_write_v7tar_header(struct archive_write *a, struct archive_entry *entry)
232232
sconv = v7tar->opt_sconv;
233233

234234
/* Sanity check. */
235-
if (archive_entry_pathname(entry) == NULL) {
235+
if (archive_entry_pathname(entry) == NULL
236+
#if defined(_WIN32) && !defined(__CYGWIN__)
237+
&& archive_entry_pathname_w(entry) == NULL
238+
#endif
239+
) {
236240
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
237241
"Can't record entry in tar file without pathname");
238242
return (ARCHIVE_FAILED);
@@ -382,15 +386,28 @@ format_header_v7tar(struct archive_write *a, char h[512],
382386
*/
383387
r = archive_entry_pathname_l(entry, &pp, &copy_length, sconv);
384388
if (r != 0) {
389+
const char* p_mbs;
385390
if (errno == ENOMEM) {
386391
archive_set_error(&a->archive, ENOMEM,
387392
"Can't allocate memory for Pathname");
388393
return (ARCHIVE_FATAL);
389394
}
390-
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
391-
"Can't translate pathname '%s' to %s",
392-
pp, archive_string_conversion_charset_name(sconv));
393-
ret = ARCHIVE_WARN;
395+
p_mbs = archive_entry_pathname(entry);
396+
if (p_mbs) {
397+
/* We have a wrongly-encoded MBS pathname.
398+
* Warn and use it. */
399+
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
400+
"Can't translate pathname '%s' to %s", p_mbs,
401+
archive_string_conversion_charset_name(sconv));
402+
ret = ARCHIVE_WARN;
403+
} else {
404+
/* We have no MBS pathname. Fail. */
405+
archive_set_error(&a->archive,
406+
ARCHIVE_ERRNO_FILE_FORMAT,
407+
"Can't translate pathname to %s",
408+
archive_string_conversion_charset_name(sconv));
409+
return ARCHIVE_FAILED;
410+
}
394411
}
395412
if (strict && copy_length < V7TAR_name_size)
396413
memcpy(h + V7TAR_name_offset, pp, copy_length);

libarchive/archive_write_set_format_zip.c

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,17 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry)
802802
int version_needed = 10;
803803
#define MIN_VERSION_NEEDED(x) do { if (version_needed < x) { version_needed = x; } } while (0)
804804

805+
/* Sanity check. */
806+
if (archive_entry_pathname(entry) == NULL
807+
#if defined(_WIN32) && !defined(__CYGWIN__)
808+
&& archive_entry_pathname_w(entry) == NULL
809+
#endif
810+
) {
811+
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
812+
"Can't record entry in zip file without pathname");
813+
return ARCHIVE_FAILED;
814+
}
815+
805816
/* Ignore types of entries that we don't support. */
806817
type = archive_entry_filetype(entry);
807818
if (type != AE_IFREG && type != AE_IFDIR && type != AE_IFLNK) {
@@ -882,22 +893,33 @@ archive_write_zip_header(struct archive_write *a, struct archive_entry *entry)
882893
return (ARCHIVE_FATAL);
883894
}
884895

885-
if (sconv != NULL) {
896+
{
886897
const char *p;
887898
size_t len;
888899

889900
if (archive_entry_pathname_l(zip->entry, &p, &len, sconv) != 0) {
901+
const char* p_mbs;
890902
if (errno == ENOMEM) {
891903
archive_set_error(&a->archive, ENOMEM,
892904
"Can't allocate memory for Pathname");
893905
return (ARCHIVE_FATAL);
894906
}
895-
archive_set_error(&a->archive,
896-
ARCHIVE_ERRNO_FILE_FORMAT,
897-
"Can't translate Pathname '%s' to %s",
898-
archive_entry_pathname(zip->entry),
899-
archive_string_conversion_charset_name(sconv));
900-
ret2 = ARCHIVE_WARN;
907+
p_mbs = archive_entry_pathname(zip->entry);
908+
if (p_mbs) {
909+
/* We have a wrongly-encoded MBS pathname. Warn and use it. */
910+
archive_set_error(&a->archive,
911+
ARCHIVE_ERRNO_FILE_FORMAT,
912+
"Can't translate pathname '%s' to %s", p_mbs,
913+
archive_string_conversion_charset_name(sconv));
914+
ret2 = ARCHIVE_WARN;
915+
} else {
916+
/* We have no MBS pathname. Fail. */
917+
archive_set_error(&a->archive,
918+
ARCHIVE_ERRNO_FILE_FORMAT,
919+
"Can't translate pathname to %s",
920+
archive_string_conversion_charset_name(sconv));
921+
return ARCHIVE_FAILED;
922+
}
901923
}
902924
if (len > 0)
903925
archive_entry_set_pathname(zip->entry, p);

libarchive/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ IF(ENABLE_TEST)
227227
test_tar_large.c
228228
test_ustar_filename_encoding.c
229229
test_ustar_filenames.c
230+
test_v7tar_filename_encoding.c
230231
test_warn_missing_hardlink_target.c
231232
test_write_disk.c
232233
test_write_disk_appledouble.c

libarchive/test/test_gnutar_filename_encoding.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,3 +491,43 @@ DEFINE_TEST(test_gnutar_filename_encoding_UTF16_win)
491491
assertEqualMem(buff + 157, "\xE8\xA1\xA8.txt", 7);
492492
#endif
493493
}
494+
495+
DEFINE_TEST(test_gnutar_filename_encoding_fail_UTF16_win)
496+
{
497+
#if !defined(_WIN32) || defined(__CYGWIN__)
498+
skipping("This test is meant to verify unicode string handling"
499+
" on Windows with UTF-16 names");
500+
return;
501+
#else
502+
struct archive *a;
503+
struct archive_entry *entry;
504+
char buff[4096];
505+
size_t used;
506+
507+
/* Test the C locale by not calling setlocale. */
508+
509+
a = archive_write_new();
510+
assertEqualInt(ARCHIVE_OK, archive_write_set_format_gnutar(a));
511+
if (archive_write_set_options(a, "hdrcharset=CP437") != ARCHIVE_OK) {
512+
skipping("This system cannot convert character-set"
513+
" from UTF-16 to CP437.");
514+
archive_write_free(a);
515+
return;
516+
}
517+
assertEqualInt(ARCHIVE_OK,
518+
archive_write_open_memory(a, buff, sizeof(buff), &used));
519+
520+
entry = archive_entry_new2(a);
521+
/* Set the filename using a UTF-16 string */
522+
archive_entry_copy_pathname_w(entry, L"\u8868.txt");
523+
archive_entry_set_filetype(entry, AE_IFREG);
524+
archive_entry_set_size(entry, 0);
525+
assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, entry));
526+
/* The pathname cannot even be represented in the current locale
527+
for inclusion in the error message. */
528+
assertEqualString("Can't translate pathname to CP437",
529+
archive_error_string(a));
530+
archive_entry_free(entry);
531+
assertEqualInt(ARCHIVE_OK, archive_write_free(a));
532+
#endif
533+
}

libarchive/test/test_ustar_filename_encoding.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,43 @@ DEFINE_TEST(test_ustar_filename_encoding_UTF16_win)
492492
assertEqualMem(buff + 157, "\xE8\xA1\xA8.txt", 7);
493493
#endif
494494
}
495+
496+
DEFINE_TEST(test_ustar_filename_encoding_fail_UTF16_win)
497+
{
498+
#if !defined(_WIN32) || defined(__CYGWIN__)
499+
skipping("This test is meant to verify unicode string handling"
500+
" on Windows with UTF-16 names");
501+
return;
502+
#else
503+
struct archive *a;
504+
struct archive_entry *entry;
505+
char buff[4096];
506+
size_t used;
507+
508+
/* Test the C locale by not calling setlocale. */
509+
510+
a = archive_write_new();
511+
assertEqualInt(ARCHIVE_OK, archive_write_set_format_ustar(a));
512+
if (archive_write_set_options(a, "hdrcharset=CP437") != ARCHIVE_OK) {
513+
skipping("This system cannot convert character-set"
514+
" from UTF-16 to CP437.");
515+
archive_write_free(a);
516+
return;
517+
}
518+
assertEqualInt(ARCHIVE_OK,
519+
archive_write_open_memory(a, buff, sizeof(buff), &used));
520+
521+
entry = archive_entry_new2(a);
522+
/* Set the filename using a UTF-16 string */
523+
archive_entry_copy_pathname_w(entry, L"\u8868.txt");
524+
archive_entry_set_filetype(entry, AE_IFREG);
525+
archive_entry_set_size(entry, 0);
526+
assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, entry));
527+
/* The pathname cannot even be represented in the current locale
528+
for inclusion in the error message. */
529+
assertEqualString("Can't translate pathname to CP437",
530+
archive_error_string(a));
531+
archive_entry_free(entry);
532+
assertEqualInt(ARCHIVE_OK, archive_write_free(a));
533+
#endif
534+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*-
2+
* Copyright (c) 2003-2025 Tim Kientzle
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
* 1. Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* 2. Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15+
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17+
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19+
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23+
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
#include "test.h"
26+
27+
#include <locale.h>
28+
29+
DEFINE_TEST(test_v7tar_filename_encoding_fail_UTF16_win)
30+
{
31+
#if !defined(_WIN32) || defined(__CYGWIN__)
32+
skipping("This test is meant to verify unicode string handling"
33+
" on Windows with UTF-16 names");
34+
return;
35+
#else
36+
struct archive *a;
37+
struct archive_entry *entry;
38+
char buff[4096];
39+
size_t used;
40+
41+
/* Test the C locale by not calling setlocale. */
42+
43+
a = archive_write_new();
44+
assertEqualInt(ARCHIVE_OK, archive_write_set_format_v7tar(a));
45+
if (archive_write_set_options(a, "hdrcharset=CP437") != ARCHIVE_OK) {
46+
skipping("This system cannot convert character-set"
47+
" from UTF-16 to CP437.");
48+
archive_write_free(a);
49+
return;
50+
}
51+
assertEqualInt(ARCHIVE_OK,
52+
archive_write_open_memory(a, buff, sizeof(buff), &used));
53+
54+
entry = archive_entry_new2(a);
55+
/* Set the filename using a UTF-16 string */
56+
archive_entry_copy_pathname_w(entry, L"\u8868.txt");
57+
archive_entry_set_filetype(entry, AE_IFREG);
58+
archive_entry_set_size(entry, 0);
59+
assertEqualInt(ARCHIVE_FAILED, archive_write_header(a, entry));
60+
/* The pathname cannot even be represented in the current locale
61+
for inclusion in the error message. */
62+
assertEqualString("Can't translate pathname to CP437",
63+
archive_error_string(a));
64+
archive_entry_free(entry);
65+
assertEqualInt(ARCHIVE_OK, archive_write_free(a));
66+
#endif
67+
}

0 commit comments

Comments
 (0)