Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions UPDATING
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Updating Information for MidnightBSD users.

20260722:
libarchive 3.8.8: fixes CVE-2026-14164, a double-free in the RAR5
reader (archive_read_support_format_rar5.c) that could crash
applications parsing a crafted RAR5 archive. Rebuild world and
restart applications that use libarchive.

20260713:
pam_xdg(8) is now also enabled in the system PAM policy, so console
logins via login(1) get XDG_RUNTIME_DIR. sshd and su declare their
Expand Down
2 changes: 2 additions & 0 deletions contrib/libarchive/NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Jun 23, 2026: libarchive 3.8.8 released

Apr 13, 2026: libarchive 3.8.7 released

Mar 10, 2026: libarchive 3.8.6 released
Expand Down
19 changes: 13 additions & 6 deletions contrib/libarchive/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Security Policy

If you have discovered a security vulnerability in this project, please report it
privately. **Do not disclose it as a public issue.** This gives us time to work with you
to fix the issue before public exposure, reducing the chance that the exploit will be
used before a patch is released.
If you have discovered a security vulnerability in this project,
please report it privately.
**Do not disclose it as a public issue.**
This gives us time to work with you to fix the issue before public exposure,
reducing the chance that the exploit will be used before a patch is released.

You may submit the report in the following ways:

Expand All @@ -15,5 +16,11 @@ Please provide the following information in your report:
- A description of the vulnerability and its impact
- How to reproduce the issue

This project is maintained by volunteers on a reasonable-effort basis. As such, we ask
that you give us 90 days to work on a fix before public exposure.
While we welcome all reports, we can act most quickly if the report
includes a complete libarchive test case and a proposed fix.
See [doc/WRITING_TESTS.md](doc/WRITING_TESTS.md) for guidance on creating a reproduction test case.
You can provide the test case and proposed fix as separate patch files
prepared with `git format-patch` or `git diff`.

This project is maintained by volunteers on a reasonable-effort basis.
We ask that you give us 90 days to work on a fix before public exposure.
41 changes: 41 additions & 0 deletions contrib/libarchive/build/ci/github_actions/freebsd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/sh
set -eu

if [ $# != 1 ]
then
echo "Usage: $0 prepare | test"
exit 1
fi

if [ "$1" = "prepare" ]
then
set -x -e
env ASSUME_ALWAYS_YES=yes pkg bootstrap -f
sed -i.bak -e 's,pkg+http://pkg.FreeBSD.org/\${ABI}/quarterly,pkg+http://pkg.FreeBSD.org/\${ABI}/latest,' /etc/pkg/FreeBSD.conf
pkg update
mount -u -o acls /
mkdir /tmp_acl_nfsv4
MD=`mdconfig -a -t swap -s 128M`
newfs /dev/$MD
tunefs -N enable /dev/$MD
mount /dev/$MD /tmp_acl_nfsv4
chmod 1777 /tmp_acl_nfsv4
pkg install -y autoconf automake cmake gmake libiconv libtool pkgconf expat libxml2 liblz4 zstd
elif [ "$1" = "test" ]
then
set -e
echo "Additional NFSv4 ACL tests"
CURDIR=`pwd`
if [ "${BS}" = "cmake" ]
then
BIN_SUBDIR="bin"
else
BIN_SUBDIR=.
fi
BUILDDIR="${CURDIR}/build_ci/${BS}"
cd "$BUILDDIR"
TMPDIR=/tmp_acl_nfsv4 ${BIN_SUBDIR}/libarchive_test -r "${CURDIR}/libarchive/test" -v test_acl_platform_nfs4
else
echo "Usage: $0 prepare | test"
exit 1
fi
15 changes: 15 additions & 0 deletions contrib/libarchive/build/ci/github_actions/vcpkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"dependencies": [
"zlib",
"bzip2",
"liblzma",
"zstd"
],
"vcpkg-configuration": {
"default-registry": {
"kind": "git",
"repository": "https://github.com/microsoft/vcpkg",
"baseline": "44819aa2a6c10e56065e2b0330e7d6c89d1d2574"
}
}
}
13 changes: 13 additions & 0 deletions contrib/libarchive/build/cmake/discover_tests.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generated by CMake at configure time -- do not edit.
# Scans test source files for DEFINE_TEST() declarations and writes list.h.
set(_sources @SOURCES@)
set(_output "@OUTPUT@")
file(WRITE "${_output}" "")
foreach(_src ${_sources})
if(_src MATCHES "test_[^/]+\\.c$")
file(STRINGS "${_src}" _lines REGEX "^DEFINE_TEST")
foreach(_line ${_lines})
file(APPEND "${_output}" "${_line}\n")
endforeach()
endif()
endforeach()
200 changes: 174 additions & 26 deletions contrib/libarchive/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>

Expand All @@ -17,10 +18,134 @@
static constexpr size_t kMaxInputSize = 64 * 1024;

static char g_temp_dir[256] = {0};
static char g_start_dir[512] = {0};
static char g_extract_dir[512] = {0};
static char g_outside_dir[512] = {0};
static char g_sentinel_path[512] = {0};

static int write_file(const char* path, const char* data) {
FILE* f = fopen(path, "wb");
size_t len = strlen(data);
size_t written;
if (f == NULL) {
return -1;
}
written = fwrite(data, 1, len, f);
if (fclose(f) != 0) {
return -1;
}
return written == len ? 0 : -1;
}

static int sentinel_is_unchanged() {
char buf[32];
FILE* f = fopen(g_sentinel_path, "rb");
if (f == NULL) {
return 0;
}
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
fclose(f);
buf[n] = '\0';
return strcmp(buf, "outside-original") == 0;
}

static int reset_temp_tree() {
if (g_start_dir[0] != '\0') {
if (chdir(g_start_dir) != 0) {
return -1;
}
}
remove_directory_tree(g_temp_dir);
if (mkdir(g_temp_dir, 0700) != 0) {
return -1;
}

snprintf(g_extract_dir, sizeof(g_extract_dir), "%s/extract", g_temp_dir);
snprintf(g_outside_dir, sizeof(g_outside_dir), "%s/outside", g_temp_dir);
snprintf(g_sentinel_path, sizeof(g_sentinel_path), "%s/outside/sentinel",
g_temp_dir);

if (mkdir(g_extract_dir, 0700) != 0 || mkdir(g_outside_dir, 0700) != 0) {
return -1;
}

char existing[512];
char existing_dir[512];
char nested_existing[512];
snprintf(existing, sizeof(existing), "%s/extract/existing", g_temp_dir);
snprintf(existing_dir, sizeof(existing_dir), "%s/extract/dir", g_temp_dir);
snprintf(nested_existing, sizeof(nested_existing),
"%s/extract/dir/existing", g_temp_dir);
if (mkdir(existing_dir, 0700) != 0) {
return -1;
}

return write_file(g_sentinel_path, "outside-original") ||
write_file(existing, "inside") ||
write_file(nested_existing, "inside");
}

static void consume_path(DataConsumer* consumer, char* out, size_t out_size) {
static const char* const kPaths[] = {
"file",
"dir/file",
"dir/./file",
"dir//file",
"link",
"link/file",
"link/./file",
"link//file",
"../outside/sentinel",
"../outside/new-file",
"./../outside/sentinel",
"dir/../file",
"hardlink",
"hardlink-with-data",
"symlink",
"existing",
"dir/existing",
};
uint8_t selector = consumer->consume_byte();
if ((selector & 0x80) != 0) {
snprintf(out, out_size, "%s/extract/absolute-%u", g_temp_dir, selector);
} else if ((selector & 0x40) != 0) {
snprintf(out, out_size, "%s/outside/sentinel", g_temp_dir);
} else {
snprintf(out, out_size, "%s",
kPaths[selector % (sizeof(kPaths) / sizeof(kPaths[0]))]);
}
}

static void consume_linkname(DataConsumer* consumer, char* out,
size_t out_size) {
static const char* const kTargets[] = {
"existing",
"dir/existing",
"link/sentinel",
"link/./sentinel",
"link//sentinel",
"../outside/sentinel",
"./../outside/sentinel",
"dir/../existing",
"missing-target",
};
uint8_t selector = consumer->consume_byte();
if ((selector & 0x80) != 0) {
snprintf(out, out_size, "%s/outside/sentinel", g_temp_dir);
} else if ((selector & 0x40) != 0) {
snprintf(out, out_size, "%s/extract/existing", g_temp_dir);
} else {
snprintf(out, out_size, "%s",
kTargets[selector % (sizeof(kTargets) / sizeof(kTargets[0]))]);
}
}

extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
(void)argc;
(void)argv;
if (getcwd(g_start_dir, sizeof(g_start_dir)) == NULL) {
g_start_dir[0] = '\0';
}
// Create a temporary directory for extraction
snprintf(g_temp_dir, sizeof(g_temp_dir), "/tmp/fuzz_extract_XXXXXX");
if (mkdtemp(g_temp_dir) == NULL) {
Expand All @@ -38,10 +163,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
return 0;
}

if (reset_temp_tree() != 0 || chdir(g_extract_dir) != 0) {
return 0;
}

DataConsumer consumer(buf, len);

struct archive *disk = archive_write_disk_new();
if (disk == NULL) {
if (g_start_dir[0] != '\0') {
if (chdir(g_start_dir) != 0) {
return 0;
}
}
return 0;
}

Expand All @@ -54,8 +188,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
if (opt_flags & 0x08) flags |= ARCHIVE_EXTRACT_FFLAGS;
if (opt_flags & 0x10) flags |= ARCHIVE_EXTRACT_OWNER;
if (opt_flags & 0x20) flags |= ARCHIVE_EXTRACT_XATTR;
if (opt_flags & 0x40) flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
if (opt_flags & 0x80) flags |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
if (opt_flags & 0x40) flags |= ARCHIVE_EXTRACT_UNLINK;
if (opt_flags & 0x80) flags |= ARCHIVE_EXTRACT_SAFE_WRITES;

flags |= ARCHIVE_EXTRACT_SECURE_SYMLINKS |
ARCHIVE_EXTRACT_SECURE_NODOTDOT |
ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;

archive_write_disk_set_options(disk, flags);
archive_write_disk_set_standard_lookup(disk);
Expand All @@ -66,45 +204,45 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
struct archive_entry *entry = archive_entry_new();
if (entry == NULL) break;

// Build a safe path within our temp directory
char safe_path[512];
const char *name = consumer.consume_string(32);
snprintf(safe_path, sizeof(safe_path), "%s/%s", g_temp_dir, name);

// Sanitize path to prevent traversal
char *p = safe_path;
while (*p) {
if (p[0] == '.' && p[1] == '.') {
p[0] = '_';
p[1] = '_';
}
p++;
}

archive_entry_set_pathname(entry, safe_path);
char pathname[512];
char linkname[512];
consume_path(&consumer, pathname, sizeof(pathname));
archive_entry_set_pathname(entry, pathname);

uint8_t ftype = consumer.consume_byte() % 3;
uint8_t ftype = consumer.consume_byte() % 4;
mode_t mode;
switch (ftype) {
case 0: mode = S_IFREG | 0644; break;
case 1: mode = S_IFDIR | 0755; break;
case 2: mode = S_IFLNK | 0777; break;
default: mode = S_IFREG | 0644; break;
}
archive_entry_set_mode(entry, mode);

if (S_ISLNK(mode)) {
consume_linkname(&consumer, linkname, sizeof(linkname));
archive_entry_set_symlink(entry, linkname);
archive_entry_set_size(entry, 0);
} else if (ftype == 3) {
consume_linkname(&consumer, linkname, sizeof(linkname));
archive_entry_set_hardlink(entry, linkname);
}

archive_entry_set_uid(entry, 1000);
archive_entry_set_gid(entry, 1000);
archive_entry_set_mtime(entry, consumer.consume_i64(), 0);

uint8_t data_buf[256];
size_t data_len = 0;
if (S_ISREG(mode)) {
data_len = consumer.consume_bytes(data_buf, 256);
archive_entry_set_size(entry, data_len);
}

// Write the entry header
if (archive_write_header(disk, entry) == ARCHIVE_OK) {
if (S_ISREG(mode)) {
uint8_t data_buf[256];
size_t data_len = consumer.consume_bytes(data_buf, 256);
archive_entry_set_size(entry, data_len);
if (data_len > 0) {
archive_write_data(disk, data_buf, data_len);
}
if (S_ISREG(mode) && data_len > 0) {
archive_write_data(disk, data_buf, data_len);
}
archive_write_finish_entry(disk);
}
Expand All @@ -116,6 +254,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
archive_write_close(disk);
archive_write_free(disk);

if (!sentinel_is_unchanged()) {
abort();
}

if (g_start_dir[0] != '\0') {
if (chdir(g_start_dir) != 0) {
abort();
}
}

// Clean up extracted files using nftw (safer than system())
remove_directory_tree(g_temp_dir);
// Recreate the temp directory for next iteration
Expand Down
Loading
Loading