libarchive/test/test_write_disk_no_hfs_compression.c \
libarchive/test/test_write_disk_perms.c \
libarchive/test/test_write_disk_secure.c \
+ libarchive/test/test_write_disk_secure_noabsolutepaths.c \
libarchive/test/test_write_disk_secure744.c \
libarchive/test/test_write_disk_secure745.c \
libarchive/test/test_write_disk_secure746.c \
cpio/test/test_basic.c \
cpio/test/test_cmdline.c \
cpio/test/test_extract_cpio_Z.c \
+ cpio/test/test_extract_cpio_absolute_paths.c \
cpio/test/test_extract_cpio_bz2.c \
cpio/test/test_extract_cpio_grz.c \
cpio/test/test_extract_cpio_gz.c \
test_basic.c
test_cmdline.c
test_extract_cpio_Z.c
+ test_extract_cpio_absolute_paths.c
test_extract_cpio_bz2.c
test_extract_cpio_grz.c
test_extract_cpio_gz.c
--- /dev/null
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Mostyn Bramley-Moore <mostyn@antipode.se>
+ */
+
+#include "test.h"
+
+#include <stdlib.h>
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+
+#include <fileapi.h>
+
+#define UNLINK _unlink
+
+#else
+
+#define UNLINK unlink
+
+#endif
+
+DEFINE_TEST(test_extract_cpio_absolute_paths)
+{
+ int r;
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ char temp_dir[MAX_PATH + 1];
+ assert(GetTempPathA(MAX_PATH + 1, temp_dir) != 0);
+
+ char temp_file_name[MAX_PATH + 1];
+ char temp_absolute_file_name[MAX_PATH + 1];
+ assert(GetTempFileNameA(temp_dir, "abs", 0, temp_file_name) != 0);
+
+ assert(_fullpath(temp_absolute_file_name, temp_file_name, MAX_PATH) != NULL);
+#else
+ char temp_absolute_file_name[] = "/tmp/cpio-noabs.testXXXXXX";
+ mkstemp(temp_absolute_file_name);
+#endif
+
+ UNLINK(temp_absolute_file_name);
+
+ const char *sample_data = "test file from test_extract_cpio_absolute_paths";
+
+ assertMakeFile("filelist", 0644, temp_absolute_file_name);
+ assertMakeFile(temp_absolute_file_name, 0644, sample_data);
+
+ // Create an archive with the absolute path.
+ r = systemf("%s -o < filelist > archive.cpio 2> stderr1.txt", testprog);
+ UNLINK("filelist");
+ assertEqualInt(r, 0);
+
+ // Ensure that the temp file does not exist.
+ UNLINK(temp_absolute_file_name);
+
+ // We should refuse to create the absolute path without --insecure.
+ r = systemf("%s -i < archive.cpio 2> stderr2.txt", testprog);
+ //assert(r != 0); // Should this command fail?
+ assertFileNotExists(temp_absolute_file_name);
+ UNLINK(temp_absolute_file_name); // Cleanup just in case.
+
+ // But if we specify --insecure then the absolute path should be created.
+ r = systemf("%s -i --insecure < archive.cpio 2> stderr3.txt", testprog);
+ assert(r == 0);
+ assertFileExists(temp_absolute_file_name);
+ UNLINK(temp_absolute_file_name);
+}
int64_t
archive_write_disk_gid(struct archive *_a, const char *name, la_int64_t id)
{
- struct archive_write_disk *a = (struct archive_write_disk *)_a;
- archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
- ARCHIVE_STATE_ANY, "archive_write_disk_gid");
- if (a->lookup_gid)
- return (a->lookup_gid)(a->lookup_gid_data, name, id);
- return (id);
+ struct archive_write_disk *a = (struct archive_write_disk *)_a;
+ archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
+ ARCHIVE_STATE_ANY, "archive_write_disk_gid");
+ if (a->lookup_gid)
+ return (a->lookup_gid)(a->lookup_gid_data, name, id);
+ return (id);
}
int64_t
archive_write_disk_uid(struct archive *_a, const char *name, la_int64_t id)
{
- struct archive_write_disk *a = (struct archive_write_disk *)_a;
- archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
- ARCHIVE_STATE_ANY, "archive_write_disk_uid");
- if (a->lookup_uid)
- return (a->lookup_uid)(a->lookup_uid_data, name, id);
- return (id);
+ struct archive_write_disk *a = (struct archive_write_disk *)_a;
+ archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
+ ARCHIVE_STATE_ANY, "archive_write_disk_uid");
+ if (a->lookup_uid)
+ return (a->lookup_uid)(a->lookup_uid_data, name, id);
+ return (id);
}
/*
* Canonicalize the pathname. In particular, this strips duplicate
* '\' characters, '.' elements, and trailing '\'. It also raises an
* error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is
- * set) any '..' in the path.
+ * set) any '..' in the path or (if ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS)
+ * if the path is absolute.
*/
static int
cleanup_pathname(struct archive_write_disk *a, wchar_t *name)
{
wchar_t *dest, *src, *p, *top;
wchar_t separator = L'\0';
+ BOOL absolute_path = 0;
p = name;
if (*p == L'\0') {
if (p[0] == L'\\' && p[1] == L'\\' &&
(p[2] == L'.' || p[2] == L'?') && p[3] == L'\\')
{
+ absolute_path = 1;
+
/* A path begin with "\\?\UNC\" */
if (p[2] == L'?' &&
(p[4] == L'U' || p[4] == L'u') &&
return (ARCHIVE_FAILED);
} else
p += 4;
- /* Network drive path like "\\<server-name>\<share-name>\file" */
- } else if (p[0] == L'\\' && p[1] == L'\\') {
- p += 2;
+ /* Network drive path like "\\<server-name>\<share-name>\file" */
+ } else if (p[0] == L'\\' && p[1] == L'\\') {
+ absolute_path = 1;
+ p += 2;
}
/* Skip leading drive letter from archives created
"Path is a drive name");
return (ARCHIVE_FAILED);
}
+
+ absolute_path = 1;
+
if (p[2] == L'\\')
p += 2;
}
+ if (absolute_path && (a->flags & ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS)) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Path is absolute");
+ return (ARCHIVE_FAILED);
+ }
+
top = dest = src = p;
/* Rewrite the path name if its character is a unusable. */
for (; *p != L'\0'; p++) {
return (ARCHIVE_OK);
return (set_fflags_platform(a->name, set, clear));
- }
+ }
return (ARCHIVE_OK);
}
test_write_disk_no_hfs_compression.c
test_write_disk_perms.c
test_write_disk_secure.c
+ test_write_disk_secure_noabsolutepaths.c
test_write_disk_secure744.c
test_write_disk_secure745.c
test_write_disk_secure746.c
--- /dev/null
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Mostyn Bramley-Moore <mostyn@antipode.se>
+ */
+
+#include "test.h"
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+
+#include <fileapi.h>
+
+#define UNLINK _unlink
+
+#else
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define UNLINK unlink
+
+#endif
+
+/*
+ * Exercise security checks that should prevent writing absolute paths
+ * when extracting archives.
+ */
+DEFINE_TEST(test_write_disk_secure_noabsolutepaths)
+{
+ struct archive *a, *ad;
+ struct archive_entry *ae;
+
+ char buff[10000];
+
+ size_t used;
+
+ // Create an archive_write object.
+ assert((a = archive_write_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_ustar(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_add_filter_none(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_open_memory(a, buff, sizeof(buff), &used));
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ char temp_dir[MAX_PATH + 1];
+ assert(GetTempPathA(MAX_PATH + 1, temp_dir) != 0);
+
+ char temp_file_name[MAX_PATH + 1];
+ char temp_absolute_file_name[MAX_PATH + 1];
+ assert(GetTempFileNameA(temp_dir, "abs", 0, temp_file_name) != 0);
+
+ assert(_fullpath(temp_absolute_file_name, temp_file_name, MAX_PATH) != NULL);
+
+ // Convert to a unix-style path.
+ for (char *p = temp_absolute_file_name; *p != '\0'; p++)
+ if (*p == '\\') *p = '/';
+
+#else
+ char temp_absolute_file_name[] = "/tmp/noabs.testXXXXXX";
+ mkstemp(temp_absolute_file_name);
+#endif
+
+ // Ensure that the target file does not exist.
+ UNLINK(temp_absolute_file_name);
+
+ // Add a regular file entry with an absolute path.
+ assert((ae = archive_entry_new()) != NULL);
+ archive_entry_copy_pathname(ae, temp_absolute_file_name);
+ archive_entry_set_mode(ae, S_IFREG | 0777);
+ archive_entry_set_size(ae, 6);
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
+ archive_entry_free(ae);
+ assertEqualInt(6, archive_write_data(a, "hello", 6));
+ assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
+ assertEqualInt(ARCHIVE_OK, archive_write_free(a));
+
+ // Now try to extract the data.
+ assert((a = archive_read_new()) != NULL);
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a));
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_open_memory(a, buff, used));
+
+ assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+ assertEqualString(temp_absolute_file_name, archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
+ assertEqualInt(AE_IFREG | 0777, archive_entry_mode(ae));
+ assertEqualInt(6, archive_entry_size(ae));
+
+ // This should succeed.
+ assertEqualInt(ARCHIVE_OK, archive_read_extract(a, ae, 0));
+ UNLINK(temp_absolute_file_name);
+
+ // This should fail, since the archive entry has an absolute path.
+ assert(ARCHIVE_OK != archive_read_extract(a, ae, ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS));
+
+ // This should also fail.
+ assert((ad = archive_write_new()) != NULL);
+ assertEqualInt(ARCHIVE_OK, archive_write_disk_set_options(a, ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS));
+ assert(ARCHIVE_OK != archive_read_extract2(a, ae, ad));
+
+ assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
+ assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+}