1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2024 Mostyn Bramley-Moore <mostyn@antipode.se>
5 */
6
7 #include "test.h"
8
9 #include <stdlib.h>
10
11 #if defined(_WIN32) && !defined(__CYGWIN__)
12 #define UNLINK _unlink
13 #else
14 #include <unistd.h>
15 #define UNLINK unlink
16 #endif
17
18 /*
19 * Exercise security checks that should prevent writing absolute paths
20 * when extracting archives.
21 */
DEFINE_TEST(test_write_disk_secure_noabsolutepaths)22 DEFINE_TEST(test_write_disk_secure_noabsolutepaths)
23 {
24 struct archive *a, *ad;
25 struct archive_entry *ae;
26
27 char buff[10000];
28
29 size_t used;
30
31 // Create an archive_write object.
32 assert((a = archive_write_new()) != NULL);
33 assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_ustar(a));
34 assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
35 assertEqualIntA(a, ARCHIVE_OK, archive_write_open_memory(a, buff, sizeof(buff), &used));
36
37 // Create an absolute path for a test file inside testworkdir.
38 const char *entry_suffix = "/badfile";
39 size_t entry_suffix_length = strlen(entry_suffix);
40 size_t testworkdir_length = strlen(testworkdir);
41 size_t temp_absolute_file_name_length = testworkdir_length + entry_suffix_length;
42 char *temp_absolute_file_name = calloc(1, temp_absolute_file_name_length + 1); // +1 for null character.
43 assertEqualInt(snprintf(temp_absolute_file_name, temp_absolute_file_name_length + 1, "%s%s", testworkdir, entry_suffix),
44 temp_absolute_file_name_length);
45
46 // Convert to a unix-style path, so we can compare it to the entry
47 // path when reading back the archive.
48 for (char *p = temp_absolute_file_name; *p != '\0'; p++)
49 if (*p == '\\') *p = '/';
50
51 // Add a regular file entry with an absolute path.
52 assert((ae = archive_entry_new()) != NULL);
53 archive_entry_copy_pathname(ae, temp_absolute_file_name);
54 archive_entry_set_mode(ae, S_IFREG | 0777);
55 archive_entry_set_size(ae, 6);
56 assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
57 archive_entry_free(ae);
58 assertEqualInt(6, archive_write_data(a, "hello", 6));
59 assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
60 assertEqualInt(ARCHIVE_OK, archive_write_free(a));
61
62 // Now try to extract the data.
63 assert((a = archive_read_new()) != NULL);
64 assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
65 assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
66 assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
67
68 assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
69 assertEqualString(temp_absolute_file_name, archive_entry_pathname(ae));
70 assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
71 assertEqualInt(AE_IFREG | 0777, archive_entry_mode(ae));
72 assertEqualInt(6, archive_entry_size(ae));
73
74 // This should succeed.
75 assertEqualInt(ARCHIVE_OK, archive_read_extract(a, ae, 0));
76 UNLINK(temp_absolute_file_name);
77
78 // This should fail, since the archive entry has an absolute path.
79 assert(ARCHIVE_OK != archive_read_extract(a, ae, ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS));
80
81 // This should also fail.
82 assert((ad = archive_write_new()) != NULL);
83 assertEqualInt(ARCHIVE_OK, archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS));
84 assert(ARCHIVE_OK != archive_read_extract2(a, ae, ad));
85
86 assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
87 assertEqualInt(ARCHIVE_OK, archive_read_free(a));
88 }
89