aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Matuska <mm@FreeBSD.org>2026-01-05 20:08:25 +0000
committerMartin Matuska <mm@FreeBSD.org>2026-01-05 20:10:21 +0000
commit01333e8c4dd7b5e2bb90cc773332613cf085ccf4 (patch)
treed3695e6c5576083074ed3d96865707633a9d6028
parentbf3359d92128e09a91c702f7da132d329ffda123 (diff)
Update vendor/libarchive to 3.8.5vendor/libarchive
Important bugfixes: #2809 bsdtar: fix regression from 3.8.4 zero-length pattern issue bugfix Obtained from: libarchive Vendor commit: dd897a78c662a2c7a003e7ec158cea7909557bee
-rw-r--r--.cirrus.yml20
-rw-r--r--.github/workflows/ci.yml16
-rw-r--r--.github/workflows/cifuzz.yml2
-rw-r--r--.github/workflows/codeql.yml8
-rw-r--r--.github/workflows/scorecard.yml6
-rw-r--r--CMakeLists.txt16
-rw-r--r--NEWS2
-rw-r--r--README.md4
-rw-r--r--build/version2
-rw-r--r--configure.ac4
-rw-r--r--contrib/android/Android.mk20
-rw-r--r--contrib/android/config/android.h10
-rw-r--r--contrib/oss-fuzz/fuzz_helpers.h156
-rw-r--r--contrib/oss-fuzz/libarchive_7zip_fuzzer.cc63
-rw-r--r--contrib/oss-fuzz/libarchive_7zip_fuzzer.dict47
-rw-r--r--contrib/oss-fuzz/libarchive_7zip_fuzzer.options10
-rw-r--r--contrib/oss-fuzz/libarchive_ar_fuzzer.cc54
-rw-r--r--contrib/oss-fuzz/libarchive_ar_fuzzer.dict20
-rw-r--r--contrib/oss-fuzz/libarchive_cab_fuzzer.cc51
-rw-r--r--contrib/oss-fuzz/libarchive_cab_fuzzer.dict23
-rw-r--r--contrib/oss-fuzz/libarchive_cpio_fuzzer.cc58
-rw-r--r--contrib/oss-fuzz/libarchive_cpio_fuzzer.dict25
-rw-r--r--contrib/oss-fuzz/libarchive_encryption_fuzzer.cc101
-rw-r--r--contrib/oss-fuzz/libarchive_encryption_fuzzer.options10
-rw-r--r--contrib/oss-fuzz/libarchive_entry_fuzzer.cc105
-rw-r--r--contrib/oss-fuzz/libarchive_filter_fuzzer.cc65
-rw-r--r--contrib/oss-fuzz/libarchive_filter_fuzzer.dict33
-rw-r--r--contrib/oss-fuzz/libarchive_filter_fuzzer.options10
-rw-r--r--contrib/oss-fuzz/libarchive_fuzzer.cc17
-rw-r--r--contrib/oss-fuzz/libarchive_fuzzer.dict76
-rw-r--r--contrib/oss-fuzz/libarchive_fuzzer.options9
-rw-r--r--contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc58
-rw-r--r--contrib/oss-fuzz/libarchive_iso9660_fuzzer.dict36
-rw-r--r--contrib/oss-fuzz/libarchive_iso9660_fuzzer.options10
-rw-r--r--contrib/oss-fuzz/libarchive_lha_fuzzer.cc54
-rw-r--r--contrib/oss-fuzz/libarchive_lha_fuzzer.dict26
-rw-r--r--contrib/oss-fuzz/libarchive_linkify_fuzzer.cc110
-rw-r--r--contrib/oss-fuzz/libarchive_match_fuzzer.cc96
-rw-r--r--contrib/oss-fuzz/libarchive_mtree_fuzzer.cc61
-rw-r--r--contrib/oss-fuzz/libarchive_mtree_fuzzer.dict47
-rw-r--r--contrib/oss-fuzz/libarchive_rar5_fuzzer.cc61
-rw-r--r--contrib/oss-fuzz/libarchive_rar5_fuzzer.dict37
-rw-r--r--contrib/oss-fuzz/libarchive_rar_fuzzer.cc56
-rw-r--r--contrib/oss-fuzz/libarchive_read_disk_fuzzer.cc76
-rw-r--r--contrib/oss-fuzz/libarchive_roundtrip_fuzzer.cc110
-rw-r--r--contrib/oss-fuzz/libarchive_roundtrip_fuzzer.options3
-rw-r--r--contrib/oss-fuzz/libarchive_seek_fuzzer.cc125
-rw-r--r--contrib/oss-fuzz/libarchive_seek_fuzzer.options3
-rw-r--r--contrib/oss-fuzz/libarchive_string_fuzzer.cc144
-rw-r--r--contrib/oss-fuzz/libarchive_tar_fuzzer.cc86
-rw-r--r--contrib/oss-fuzz/libarchive_tar_fuzzer.dict51
-rw-r--r--contrib/oss-fuzz/libarchive_warc_fuzzer.cc50
-rw-r--r--contrib/oss-fuzz/libarchive_warc_fuzzer.dict34
-rw-r--r--contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc125
-rw-r--r--contrib/oss-fuzz/libarchive_write_disk_fuzzer.options3
-rw-r--r--contrib/oss-fuzz/libarchive_write_fuzzer.cc132
-rw-r--r--contrib/oss-fuzz/libarchive_xar_fuzzer.cc60
-rw-r--r--contrib/oss-fuzz/libarchive_xar_fuzzer.dict44
-rw-r--r--contrib/oss-fuzz/libarchive_xar_fuzzer.options10
-rw-r--r--contrib/oss-fuzz/libarchive_zip_fuzzer.cc68
-rw-r--r--contrib/oss-fuzz/libarchive_zip_fuzzer.dict43
-rwxr-xr-xcontrib/oss-fuzz/oss-fuzz-build.sh139
-rw-r--r--cpio/cpio.c2
-rw-r--r--libarchive/archive.h4
-rw-r--r--libarchive/archive_entry.h2
-rw-r--r--libarchive/archive_read_disk_windows.c2
-rw-r--r--libarchive/archive_read_support_filter_uu.c8
-rw-r--r--libarchive/archive_read_support_format_cab.c2
-rw-r--r--libarchive/archive_read_support_format_cpio.c6
-rw-r--r--libarchive/archive_read_support_format_lha.c2
-rw-r--r--libarchive/archive_read_support_format_mtree.c12
-rw-r--r--libarchive/archive_string.c4
-rw-r--r--libarchive/archive_util.c2
-rw-r--r--libarchive/archive_windows.c5
-rw-r--r--libarchive/archive_write_open_fd.c6
-rw-r--r--libarchive/archive_write_open_file.c6
-rw-r--r--libarchive/archive_write_open_memory.c6
-rw-r--r--libarchive/archive_write_set_format_shar.c5
-rw-r--r--libarchive/archive_write_set_format_ustar.c2
-rw-r--r--libarchive/test/test_compat_lzip.c4
-rw-r--r--libarchive_fe/line_reader.c2
-rw-r--r--tar/subst.c16
-rw-r--r--tar/write.c5
-rw-r--r--test_utils/test_main.c19
-rw-r--r--unzip/bsdunzip.c4
-rw-r--r--unzip/la_queue.h2
86 files changed, 3006 insertions, 123 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index d5c6d65b5120..523d9cb61fac 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -1,3 +1,4 @@
+---
env:
CIRRUS_CLONE_DEPTH: 1
ARCH: amd64
@@ -9,10 +10,21 @@ FreeBSD_task:
env:
BS: cmake
matrix:
- freebsd_instance:
- image_family: freebsd-14-3
- freebsd_instance:
- image_family: freebsd-13-5
+ - name: 15.0-STABLE (UFS)
+ freebsd_instance:
+ image_family: freebsd-15-0-amd64-ufs-snap
+ - name: 15.0-RELEASE (UFS)
+ freebsd_instance:
+ image_family: freebsd-15-0-amd64-ufs
+ - name: 15.0-RELEASE (ZFS)
+ freebsd_instance:
+ image_family: freebsd-15-0-amd64-zfs
+ - name: 14.3-RELEASE
+ freebsd_instance:
+ image_family: freebsd-14-3
+ - name: 13.5-RELEASE
+ freebsd_instance:
+ image_family: freebsd-13-5
prepare_script:
- ./build/ci/cirrus_ci/ci.sh prepare
configure_script:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 80adc6ae74bf..f95e1ad0a69c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
matrix:
bs: [autotools, cmake]
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install dependencies
run: ./build/ci/github_actions/install-macos-dependencies.sh
- name: Autogen
@@ -47,7 +47,7 @@ jobs:
run: ./build/ci/build.sh -a artifact
env:
BS: ${{ matrix.bs }}
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: libarchive-macos-${{ matrix.bs }}-${{ github.sha }}
path: libarchive.tar.xz
@@ -59,7 +59,7 @@ jobs:
bs: [autotools, cmake]
crypto: [mbedtls, nettle, openssl]
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Update apt cache
run: sudo apt-get update
- name: Install dependencies
@@ -93,14 +93,14 @@ jobs:
run: ./build/ci/build.sh -a artifact
env:
BS: ${{ matrix.bs }}
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: libarchive-ubuntu-${{ matrix.bs }}-${{ matrix.crypto }}-${{ github.sha }}
path: libarchive.tar.xz
Ubuntu-distcheck:
runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Update package definitions
run: sudo apt-get update
- name: Install dependencies
@@ -115,7 +115,7 @@ jobs:
SKIP_OPEN_FD_ERR_TEST: 1
- name: Dist-Artifact
run: ./build/ci/build.sh -a dist-artifact
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: libarchive-${{ github.sha }}
path: libarchive-dist.tar
@@ -127,7 +127,7 @@ jobs:
matrix:
be: [mingw-gcc, msvc]
steps:
- - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install mingw
if: ${{ matrix.be=='mingw-gcc' }}
run: choco install mingw
@@ -163,7 +163,7 @@ jobs:
shell: cmd
env:
BE: ${{ matrix.be }}
- - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: libarchive-windows-${{ matrix.be }}-${{ github.sha }}
path: libarchive.zip
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
index d647091bb2e1..2a0d88398f43 100644
--- a/.github/workflows/cifuzz.yml
+++ b/.github/workflows/cifuzz.yml
@@ -21,7 +21,7 @@ jobs:
fuzz-seconds: 600
dry-run: false
- name: Upload Crash
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 139d47d5a9a2..38e3bc9cc432 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -26,18 +26,18 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Initialize CodeQL
- uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
+ uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
- uses: github/codeql-action/autobuild@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
+ uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
+ uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 4038954a21ae..5e25858e3e42 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -29,7 +29,7 @@ jobs:
steps:
- name: "Checkout code"
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
@@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: SARIF file
path: results.sarif
@@ -60,6 +60,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6
+ uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: results.sarif
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b15be8754567..3ab6291332fb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -139,7 +139,12 @@ IF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR
# either of the following two, yet neither is supported as of 3.0.2
# - check_linker_flag - does not exist
# - try_compile - does not support linker flags
- IF(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ IF(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip")
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-dead_strip")
+ ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS")
+ # SunOS linker doesn't support --gc-sections
+ ELSE()
# Place the functions and data into separate sections, allowing the linker
# to garbage collect the unused ones.
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
@@ -148,10 +153,7 @@ IF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR
# Printing the discarded section is "too much", so enable on demand.
#SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -Wl,--print-gc-sections")
#SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} -Wl,--print-gc-sections")
- ELSE()
- SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip")
- SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-dead_strip")
- ENDIF(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ ENDIF()
ENDIF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR
CMAKE_C_COMPILER_ID MATCHES "^Clang$" AND NOT MSVC)
IF (CMAKE_C_COMPILER_ID MATCHES "^XL$")
@@ -1085,8 +1087,8 @@ MACRO(CHECK_ICONV LIB TRY_ICONV_CONST)
CMAKE_C_COMPILER_ID MATCHES "^Clang$")
#
# During checking iconv proto type, we should use -Werror to avoid the
- # success of iconv detection with a warning which success is a miss
- # detection. So this needs for all build mode(even it's a release mode).
+ # success of iconv detection with a warning, which would be a false
+ # positive. So this is needed for all build modes, even in release mode.
#
SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror")
ENDIF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR
diff --git a/NEWS b/NEWS
index fbbb65440b39..be14de445b57 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,5 @@
+Jan 05, 2026: libarchive 3.8.5 released
+
Dec 01, 2025: libarchive 3.8.4 released
Nov 17, 2025: libarchive 3.8.3 released
diff --git a/README.md b/README.md
index 0d63357292ec..e9691f1b710b 100644
--- a/README.md
+++ b/README.md
@@ -191,7 +191,7 @@ questions we are asked about libarchive:
libraries. This also reduces the size of statically-linked
binaries in environments where that matters.
-* The library is generally _thread safe_ depending on the platform:
+* The library is generally _thread-safe_ depending on the platform:
it does not define any global variables of its own. However, some
platforms do not provide fully thread-safe versions of key C library
functions. On those platforms, libarchive will use the non-thread-safe
@@ -214,7 +214,7 @@ questions we are asked about libarchive:
multiple threads. Of course, those modules are completely
optional and you can use the rest of libarchive without them.
-* The library is _not_ thread aware, however. It does no locking
+* The library is _not_ thread-aware, however. It does no locking
or thread management of any kind. If you create a libarchive
object and need to access it from multiple threads, you will
need to provide your own locking.
diff --git a/build/version b/build/version
index 79b0dfb7a030..3f78540c6288 100644
--- a/build/version
+++ b/build/version
@@ -1 +1 @@
-3008004
+3008005
diff --git a/configure.ac b/configure.ac
index 6a55359c0ae7..b60b886e62d5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,8 +4,8 @@ dnl First, define all of the version numbers up front.
dnl In particular, this allows the version macro to be used in AC_INIT
dnl These first two version numbers are updated automatically on each release.
-m4_define([LIBARCHIVE_VERSION_S],[3.8.4])
-m4_define([LIBARCHIVE_VERSION_N],[3008004])
+m4_define([LIBARCHIVE_VERSION_S],[3.8.5])
+m4_define([LIBARCHIVE_VERSION_N],[3008005])
dnl bsdtar and bsdcpio versioning tracks libarchive
m4_define([BSDTAR_VERSION_S],LIBARCHIVE_VERSION_S())
diff --git a/contrib/android/Android.mk b/contrib/android/Android.mk
index 2e9d9a399ad8..20e46a699643 100644
--- a/contrib/android/Android.mk
+++ b/contrib/android/Android.mk
@@ -26,6 +26,8 @@ LOCAL_PATH := $(subst /contrib/android,,$(call my-dir))
libarchive_target_config := contrib/android/config/android.h
libarchive_src_files := libarchive/archive_acl.c \
+ libarchive/archive_blake2s_ref.c \
+ libarchive/archive_blake2sp_ref.c \
libarchive/archive_check_magic.c \
libarchive/archive_cmdline.c \
libarchive/archive_cryptor.c \
@@ -44,6 +46,7 @@ libarchive_src_files := libarchive/archive_acl.c \
libarchive/archive_parse_date.c \
libarchive/archive_pathmatch.c \
libarchive/archive_ppmd7.c \
+ libarchive/archive_ppmd8.c \
libarchive/archive_random.c \
libarchive/archive_rb.c \
libarchive/archive_read.c \
@@ -86,6 +89,7 @@ libarchive_src_files := libarchive/archive_acl.c \
libarchive/archive_read_support_format_lha.c \
libarchive/archive_read_support_format_mtree.c \
libarchive/archive_read_support_format_rar.c \
+ libarchive/archive_read_support_format_rar5.c \
libarchive/archive_read_support_format_raw.c \
libarchive/archive_read_support_format_tar.c \
libarchive/archive_read_support_format_warc.c \
@@ -93,6 +97,7 @@ libarchive_src_files := libarchive/archive_acl.c \
libarchive/archive_read_support_format_zip.c \
libarchive/archive_string.c \
libarchive/archive_string_sprintf.c \
+ libarchive/archive_time.c \
libarchive/archive_util.c \
libarchive/archive_version_details.c \
libarchive/archive_virtual.c \
@@ -123,7 +128,9 @@ libarchive_src_files := libarchive/archive_acl.c \
libarchive/archive_write_set_format_ar.c \
libarchive/archive_write_set_format_by_name.c \
libarchive/archive_write_set_format_cpio.c \
+ libarchive/archive_write_set_format_cpio_binary.c \
libarchive/archive_write_set_format_cpio_newc.c \
+ libarchive/archive_write_set_format_cpio_odc.c \
libarchive/archive_write_set_format_iso9660.c \
libarchive/archive_write_set_format_mtree.c \
libarchive/archive_write_set_format_pax.c \
@@ -305,4 +312,17 @@ LOCAL_SRC_FILES := $(bsdcat_src_files)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libarchive $(LOCAL_PATH)/libarchive_fe $(LOCAL_PATH)/contrib/android/include
include $(BUILD_EXECUTABLE)
+include $(CLEAR_VARS)
+LOCAL_MODULE := bsdtar-recovery
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_STEM := bsdtar
+LOCAL_CFLAGS := -DBSDTAR_VERSION_STRING=ARCHIVE_VERSION_ONLY_STRING -DPLATFORM_CONFIG_H=\"$(libarchive_target_config)\"
+LOCAL_STATIC_LIBRARIES := libarchive libarchive_fe libz
+LOCAL_SRC_FILES := $(bsdtar_src_files)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/libarchive $(LOCAL_PATH)/libarchive_fe $(LOCAL_PATH)/contrib/android/include
+LOCAL_MODULE_CLASS := RECOVERY_EXECUTABLES
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(BUILD_EXECUTABLE)
+
endif
diff --git a/contrib/android/config/android.h b/contrib/android/config/android.h
index 0a273be235b7..3fd6806d5f1c 100644
--- a/contrib/android/config/android.h
+++ b/contrib/android/config/android.h
@@ -26,6 +26,8 @@
#ifndef ARCHIVE_PLATFORM_H_ANDROID_INCLUDED
#define ARCHIVE_PLATFORM_H_ANDROID_INCLUDED
+#define __LIBARCHIVE_CONFIG_H_INCLUDED 1
+
#include <android/api-level.h>
#ifdef __ANDROID_API__
#if __ANDROID_API__ > 20
@@ -40,8 +42,8 @@
#define HAVE_CHOWN 1
#define HAVE_CHROOT 1
-#define HAVE_CLOSEFROM 1
-#define HAVE_CLOSE_RANGE 1
+#define HAVE_CLOSEFROM 0
+#define HAVE_CLOSE_RANGE 0
#define HAVE_CTIME_R 1
#define HAVE_CTYPE_H 1
#define HAVE_DECL_EXTATTR_NAMESPACE_USER 0
@@ -55,6 +57,8 @@
#define HAVE_DECL_UINTMAX_MAX 1
#define HAVE_DECL_UINT32_MAX 1
#define HAVE_DECL_UINT64_MAX 1
+#define HAVE_DECL_INT32_MAX 1
+#define HAVE_DECL_INT32_MIN 1
#define HAVE_DIRENT_H 1
#define HAVE_DIRFD 1
#define HAVE_DLFCN_H 1
@@ -137,7 +141,7 @@
#define HAVE_STRING_H 1
#define HAVE_STRRCHR 1
#define HAVE_STRUCT_STAT_ST_BLKSIZE 1
-#define HAVE_STRUCT_STAT_ST_MTIME_NSEC 1
+#define HAVE_STRUCT_STAT_ST_MTIME_NSEC 0
#define HAVE_STRUCT_TM_TM_GMTOFF 1
#define HAVE_SYMLINK 1
#define HAVE_SYS_CDEFS_H 1
diff --git a/contrib/oss-fuzz/fuzz_helpers.h b/contrib/oss-fuzz/fuzz_helpers.h
new file mode 100644
index 000000000000..bbdfdaf3a4fd
--- /dev/null
+++ b/contrib/oss-fuzz/fuzz_helpers.h
@@ -0,0 +1,156 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBARCHIVE_FUZZ_HELPERS_H_
+#define LIBARCHIVE_FUZZ_HELPERS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <ftw.h>
+#include <unistd.h>
+
+#include "archive.h"
+
+// Default maximum input size for fuzzers
+static constexpr size_t kDefaultMaxInputSize = 256 * 1024; // 256KB
+
+// Buffer structure for archive reading callbacks
+struct Buffer {
+ const uint8_t* data;
+ size_t size;
+ size_t pos;
+};
+
+// Archive read callback function
+static la_ssize_t reader_callback(struct archive* a, void* client_data,
+ const void** buffer) {
+ (void)a;
+ Buffer* buf = static_cast<Buffer*>(client_data);
+
+ if (buf->pos >= buf->size) {
+ return 0; // EOF
+ }
+
+ *buffer = buf->data + buf->pos;
+ size_t remaining = buf->size - buf->pos;
+ buf->pos = buf->size; // Consume all remaining data
+ return static_cast<la_ssize_t>(remaining);
+}
+
+// Helper class for consuming fuzz data in structured ways
+class DataConsumer {
+ public:
+ DataConsumer(const uint8_t* data, size_t size)
+ : data_(data), size_(size), pos_(0) {}
+
+ bool empty() const { return pos_ >= size_; }
+ size_t remaining() const { return size_ - pos_; }
+
+ uint8_t consume_byte() {
+ if (pos_ >= size_) return 0;
+ return data_[pos_++];
+ }
+
+ uint16_t consume_u16() {
+ uint16_t val = 0;
+ if (pos_ + 2 <= size_) {
+ val = static_cast<uint16_t>(data_[pos_]) |
+ (static_cast<uint16_t>(data_[pos_ + 1]) << 8);
+ pos_ += 2;
+ }
+ return val;
+ }
+
+ uint32_t consume_u32() {
+ uint32_t val = 0;
+ if (pos_ + 4 <= size_) {
+ val = static_cast<uint32_t>(data_[pos_]) |
+ (static_cast<uint32_t>(data_[pos_ + 1]) << 8) |
+ (static_cast<uint32_t>(data_[pos_ + 2]) << 16) |
+ (static_cast<uint32_t>(data_[pos_ + 3]) << 24);
+ pos_ += 4;
+ }
+ return val;
+ }
+
+ int64_t consume_i64() {
+ int64_t val = 0;
+ if (pos_ + 8 <= size_) {
+ for (int i = 0; i < 8; i++) {
+ val |= static_cast<int64_t>(data_[pos_ + i]) << (8 * i);
+ }
+ pos_ += 8;
+ }
+ return val;
+ }
+
+ // Consume a null-terminated string up to max_len characters
+ // Returns pointer to internal buffer (valid until next consume_string call)
+ const char* consume_string(size_t max_len) {
+ if (max_len > sizeof(string_buf_) - 1) {
+ max_len = sizeof(string_buf_) - 1;
+ }
+ size_t avail = size_ - pos_;
+ size_t len = (avail < max_len) ? avail : max_len;
+ size_t actual_len = 0;
+
+ while (actual_len < len && pos_ < size_) {
+ char c = static_cast<char>(data_[pos_++]);
+ if (c == '\0') break;
+ string_buf_[actual_len++] = c;
+ }
+ string_buf_[actual_len] = '\0';
+ return string_buf_;
+ }
+
+ // Consume raw bytes into a buffer
+ size_t consume_bytes(void* out, size_t len) {
+ size_t avail = size_ - pos_;
+ size_t to_copy = (avail < len) ? avail : len;
+ if (to_copy > 0) {
+ memcpy(out, data_ + pos_, to_copy);
+ pos_ += to_copy;
+ }
+ return to_copy;
+ }
+
+ // Get remaining data as a buffer
+ const uint8_t* remaining_data() const {
+ return data_ + pos_;
+ }
+
+ private:
+ const uint8_t* data_;
+ size_t size_;
+ size_t pos_;
+ char string_buf_[512];
+};
+
+// Callback for nftw to remove files/directories
+static int remove_callback(const char* fpath, const struct stat* sb,
+ int typeflag, struct FTW* ftwbuf) {
+ (void)sb;
+ (void)typeflag;
+ (void)ftwbuf;
+ return remove(fpath);
+}
+
+// Recursively remove a directory tree (safer than system("rm -rf ..."))
+static int remove_directory_tree(const char* path) {
+ // nftw with FTW_DEPTH processes directory contents before the directory itself
+ return nftw(path, remove_callback, 64, FTW_DEPTH | FTW_PHYS);
+}
+
+#endif // LIBARCHIVE_FUZZ_HELPERS_H_
diff --git a/contrib/oss-fuzz/libarchive_7zip_fuzzer.cc b/contrib/oss-fuzz/libarchive_7zip_fuzzer.cc
new file mode 100644
index 000000000000..74b3b2ad98d0
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_7zip_fuzzer.cc
@@ -0,0 +1,63 @@
+/*
+ * 7-Zip format specific fuzzer for libarchive
+ * Targets 7-Zip parsing and decompression code paths
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024; // 512KB
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable 7-Zip format specifically
+ archive_read_support_format_7zip(a);
+ // Enable all filters for 7z internal compression
+ archive_read_support_filter_all(a);
+
+ // Set passphrase for encrypted archives
+ archive_read_add_passphrase(a, "password");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ // Exercise entry metadata access
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_is_encrypted(entry);
+ archive_entry_is_data_encrypted(entry);
+ archive_entry_is_metadata_encrypted(entry);
+
+ // Read data
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_7zip_fuzzer.dict b/contrib/oss-fuzz/libarchive_7zip_fuzzer.dict
new file mode 100644
index 000000000000..3bee01a3bf72
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_7zip_fuzzer.dict
@@ -0,0 +1,47 @@
+# 7-Zip format dictionary
+# Magic bytes
+"7z\xbc\xaf\x27\x1c"
+"\x37\x7a\xbc\xaf\x27\x1c"
+
+# Common property IDs
+"\x00"
+"\x01"
+"\x02"
+"\x03"
+"\x04"
+"\x05"
+"\x06"
+"\x07"
+"\x08"
+"\x09"
+"\x0a"
+"\x0b"
+"\x0c"
+"\x0d"
+"\x0e"
+"\x0f"
+"\x10"
+"\x11"
+"\x17"
+"\x19"
+"\x21"
+"\x23"
+"\x24"
+"\x25"
+
+# Compression method IDs
+"\x00\x00"
+"\x00\x03"
+"\x00\x04"
+"\x00\x06"
+"\x01\x01"
+"\x03\x01\x01"
+"\x04\x01\x08"
+"\x04\x02\x02"
+"\x21\x01"
+"\x30\x01\x01"
+
+# Encryption
+"\x06\xf1\x07\x01"
+"Password"
+"password"
diff --git a/contrib/oss-fuzz/libarchive_7zip_fuzzer.options b/contrib/oss-fuzz/libarchive_7zip_fuzzer.options
new file mode 100644
index 000000000000..d2d9f0ed27d2
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_7zip_fuzzer.options
@@ -0,0 +1,10 @@
+[libfuzzer]
+max_len = 524288
+timeout = 60
+rss_limit_mb = 2048
+
+[honggfuzz]
+timeout = 60
+
+[afl]
+timeout = 60
diff --git a/contrib/oss-fuzz/libarchive_ar_fuzzer.cc b/contrib/oss-fuzz/libarchive_ar_fuzzer.cc
new file mode 100644
index 000000000000..3ad88084d440
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_ar_fuzzer.cc
@@ -0,0 +1,54 @@
+/*
+ * AR (Unix archive) format fuzzer for libarchive
+ * Tests BSD and GNU ar formats
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_ar(a);
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_ar_fuzzer.dict b/contrib/oss-fuzz/libarchive_ar_fuzzer.dict
new file mode 100644
index 000000000000..4f2e3db1b3ac
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_ar_fuzzer.dict
@@ -0,0 +1,20 @@
+# AR format dictionary
+
+# AR magic
+"!<arch>\x0a"
+
+# File header terminator
+"\x60\x0a"
+
+# Special entries
+"/"
+"//"
+"/SYM64/"
+
+# Common permissions
+"100644 "
+"100755 "
+
+# UID/GID fields
+"0 "
+"1000 "
diff --git a/contrib/oss-fuzz/libarchive_cab_fuzzer.cc b/contrib/oss-fuzz/libarchive_cab_fuzzer.cc
new file mode 100644
index 000000000000..2b7086005131
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_cab_fuzzer.cc
@@ -0,0 +1,51 @@
+/*
+ * CAB (Microsoft Cabinet) format fuzzer for libarchive
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_cab(a);
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_cab_fuzzer.dict b/contrib/oss-fuzz/libarchive_cab_fuzzer.dict
new file mode 100644
index 000000000000..76e1d3bcd7e9
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_cab_fuzzer.dict
@@ -0,0 +1,23 @@
+# CAB (Microsoft Cabinet) format dictionary
+
+# CAB signature
+"MSCF"
+"\x4d\x53\x43\x46"
+
+# Version
+"\x03\x01"
+
+# Compression types
+"\x00\x00"
+"\x01\x00"
+"\x02\x00"
+"\x03\x00"
+
+# Folder count patterns
+"\x01\x00"
+"\x02\x00"
+
+# Header flags
+"\x00\x00"
+"\x01\x00"
+"\x04\x00"
diff --git a/contrib/oss-fuzz/libarchive_cpio_fuzzer.cc b/contrib/oss-fuzz/libarchive_cpio_fuzzer.cc
new file mode 100644
index 000000000000..acbca31c7433
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_cpio_fuzzer.cc
@@ -0,0 +1,58 @@
+/*
+ * CPIO format fuzzer for libarchive
+ * Tests all CPIO variants: binary, odc, newc, crc
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_cpio(a);
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+ archive_entry_ino(entry);
+ archive_entry_nlink(entry);
+ archive_entry_rdev(entry);
+ archive_entry_hardlink(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_cpio_fuzzer.dict b/contrib/oss-fuzz/libarchive_cpio_fuzzer.dict
new file mode 100644
index 000000000000..b7ceeee1c39b
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_cpio_fuzzer.dict
@@ -0,0 +1,25 @@
+# CPIO format dictionary
+
+# Binary magic (little endian)
+"\xc7\x71"
+
+# Binary magic (big endian)
+"\x71\xc7"
+
+# ASCII odc magic
+"070707"
+
+# ASCII newc magic
+"070701"
+
+# ASCII crc magic
+"070702"
+
+# Common trailer
+"TRAILER!!!"
+
+# Common field patterns
+"00000000"
+"00000001"
+"000001ed"
+"000003e8"
diff --git a/contrib/oss-fuzz/libarchive_encryption_fuzzer.cc b/contrib/oss-fuzz/libarchive_encryption_fuzzer.cc
new file mode 100644
index 000000000000..402265cb5e07
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_encryption_fuzzer.cc
@@ -0,0 +1,101 @@
+/*
+ * Encrypted archive fuzzer for libarchive
+ * Tests password/passphrase handling across formats
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+// Passphrase callback for testing
+static const char *test_passphrases[] = {
+ "password",
+ "test",
+ "123456",
+ "",
+ "secret",
+ NULL
+};
+
+static int passphrase_idx = 0;
+
+static const char* passphrase_callback(struct archive *a, void *client_data) {
+ (void)a;
+ (void)client_data;
+ const char *pass = test_passphrases[passphrase_idx];
+ if (pass != NULL) {
+ passphrase_idx++;
+ }
+ return pass;
+}
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ // Reset passphrase index
+ passphrase_idx = 0;
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable all formats that support encryption
+ archive_read_support_format_zip(a);
+ archive_read_support_format_7zip(a);
+ archive_read_support_format_rar(a);
+ archive_read_support_format_rar5(a);
+ archive_read_support_filter_all(a);
+
+ // Set up passphrase callback
+ archive_read_set_passphrase_callback(a, NULL, passphrase_callback);
+
+ // Also add some static passphrases
+ archive_read_add_passphrase(a, "password");
+ archive_read_add_passphrase(a, "test123");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+ int entry_count = 0;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK && entry_count < 100) {
+ archive_entry_pathname(entry);
+
+ // Check encryption status
+ int is_encrypted = archive_entry_is_encrypted(entry);
+ int is_data_encrypted = archive_entry_is_data_encrypted(entry);
+ int is_meta_encrypted = archive_entry_is_metadata_encrypted(entry);
+ (void)is_encrypted;
+ (void)is_data_encrypted;
+ (void)is_meta_encrypted;
+
+ // Check if archive has encrypted entries
+ archive_read_has_encrypted_entries(a);
+
+ // Try to read data (may fail due to wrong password)
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+
+ entry_count++;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_encryption_fuzzer.options b/contrib/oss-fuzz/libarchive_encryption_fuzzer.options
new file mode 100644
index 000000000000..d2d9f0ed27d2
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_encryption_fuzzer.options
@@ -0,0 +1,10 @@
+[libfuzzer]
+max_len = 524288
+timeout = 60
+rss_limit_mb = 2048
+
+[honggfuzz]
+timeout = 60
+
+[afl]
+timeout = 60
diff --git a/contrib/oss-fuzz/libarchive_entry_fuzzer.cc b/contrib/oss-fuzz/libarchive_entry_fuzzer.cc
new file mode 100644
index 000000000000..d8aa8b51ecf4
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_entry_fuzzer.cc
@@ -0,0 +1,105 @@
+/*
+ * Archive entry fuzzer for libarchive
+ * Targets archive_entry_* functions including ACL, linkify, and metadata
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 64 * 1024; // 64KB
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+
+ struct archive_entry *entry = archive_entry_new();
+ if (entry == NULL) {
+ return 0;
+ }
+
+ // Set basic entry properties
+ archive_entry_set_pathname(entry, consumer.consume_string(256));
+ archive_entry_set_size(entry, consumer.consume_i64());
+ archive_entry_set_mode(entry, consumer.consume_u32());
+ archive_entry_set_uid(entry, consumer.consume_u32());
+ archive_entry_set_gid(entry, consumer.consume_u32());
+ archive_entry_set_mtime(entry, consumer.consume_i64(), 0);
+ archive_entry_set_atime(entry, consumer.consume_i64(), 0);
+ archive_entry_set_ctime(entry, consumer.consume_i64(), 0);
+ archive_entry_set_birthtime(entry, consumer.consume_i64(), 0);
+
+ // Set various string fields
+ archive_entry_set_uname(entry, consumer.consume_string(64));
+ archive_entry_set_gname(entry, consumer.consume_string(64));
+ archive_entry_set_symlink(entry, consumer.consume_string(256));
+ archive_entry_set_hardlink(entry, consumer.consume_string(256));
+
+ // Exercise ACL functions (low coverage targets)
+ int acl_type = consumer.consume_byte() & 0x0F;
+ int acl_permset = consumer.consume_u32();
+ int acl_tag = consumer.consume_byte() & 0x0F;
+ int acl_qual = consumer.consume_u32();
+ const char *acl_name = consumer.consume_string(64);
+
+ archive_entry_acl_add_entry(entry, acl_type, acl_permset, acl_tag, acl_qual, acl_name);
+
+ // Add more ACL entries based on remaining data
+ while (!consumer.empty() && consumer.remaining() > 10) {
+ acl_type = consumer.consume_byte() & 0x0F;
+ acl_permset = consumer.consume_u32();
+ acl_tag = consumer.consume_byte() & 0x0F;
+ acl_qual = consumer.consume_u32();
+ acl_name = consumer.consume_string(32);
+ archive_entry_acl_add_entry(entry, acl_type, acl_permset, acl_tag, acl_qual, acl_name);
+ }
+
+ // Exercise ACL text conversion functions (archive_acl_to_text_* are uncovered)
+ ssize_t text_len;
+ char *acl_text = archive_entry_acl_to_text(entry, &text_len, ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ if (acl_text) {
+ // Parse the text back
+ archive_entry_acl_from_text(entry, acl_text, ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ free(acl_text);
+ }
+
+ acl_text = archive_entry_acl_to_text(entry, &text_len, ARCHIVE_ENTRY_ACL_TYPE_DEFAULT);
+ if (acl_text) {
+ free(acl_text);
+ }
+
+ acl_text = archive_entry_acl_to_text(entry, &text_len, ARCHIVE_ENTRY_ACL_TYPE_NFS4);
+ if (acl_text) {
+ free(acl_text);
+ }
+
+ // Exercise wide character versions
+ wchar_t *acl_text_w = archive_entry_acl_to_text_w(entry, &text_len, ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ if (acl_text_w) {
+ free(acl_text_w);
+ }
+
+ // Get pathname variants
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_pathname_utf8(entry);
+
+ // Clone the entry
+ struct archive_entry *entry2 = archive_entry_clone(entry);
+ if (entry2) {
+ archive_entry_free(entry2);
+ }
+
+ // Clear and reuse
+ archive_entry_clear(entry);
+
+ archive_entry_free(entry);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_filter_fuzzer.cc b/contrib/oss-fuzz/libarchive_filter_fuzzer.cc
new file mode 100644
index 000000000000..cfd1807ecfd4
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_filter_fuzzer.cc
@@ -0,0 +1,65 @@
+/*
+ * Compression filter fuzzer for libarchive
+ * Tests decompression of gzip, bzip2, xz, lzma, zstd, lz4, etc.
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 256 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable raw format (just decompress, no archive format)
+ archive_read_support_format_raw(a);
+
+ // Enable all compression filters
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(8192, 0);
+ struct archive_entry *entry;
+
+ if (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ // Get filter info
+ int filter_count = archive_filter_count(a);
+ for (int i = 0; i < filter_count; i++) {
+ archive_filter_name(a, i);
+ archive_filter_code(a, i);
+ archive_filter_bytes(a, i);
+ }
+
+ // Read all decompressed data
+ ssize_t total = 0;
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0) {
+ total += r;
+ // Limit total decompressed size to prevent zip bombs
+ if (total > 10 * 1024 * 1024) {
+ break;
+ }
+ }
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_filter_fuzzer.dict b/contrib/oss-fuzz/libarchive_filter_fuzzer.dict
new file mode 100644
index 000000000000..2f780c9f4d0f
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_filter_fuzzer.dict
@@ -0,0 +1,33 @@
+# Compression filter dictionary
+
+# GZIP magic
+"\x1f\x8b"
+"\x1f\x8b\x08"
+
+# BZIP2 magic
+"BZh"
+"BZ0"
+
+# XZ magic
+"\xfd7zXZ\x00"
+
+# LZMA magic
+"\x5d\x00\x00"
+
+# ZSTD magic
+"\x28\xb5\x2f\xfd"
+
+# LZ4 magic
+"\x04\x22\x4d\x18"
+
+# Compress (.Z) magic
+"\x1f\x9d"
+
+# LZIP magic
+"LZIP"
+
+# LRZIP magic
+"LRZI"
+
+# LZO magic
+"\x89LZO\x00\x0d\x0a\x1a\x0a"
diff --git a/contrib/oss-fuzz/libarchive_filter_fuzzer.options b/contrib/oss-fuzz/libarchive_filter_fuzzer.options
new file mode 100644
index 000000000000..5a0374b3167d
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_filter_fuzzer.options
@@ -0,0 +1,10 @@
+[libfuzzer]
+max_len = 262144
+timeout = 30
+rss_limit_mb = 2048
+
+[honggfuzz]
+timeout = 30
+
+[afl]
+timeout = 30
diff --git a/contrib/oss-fuzz/libarchive_fuzzer.cc b/contrib/oss-fuzz/libarchive_fuzzer.cc
index bc7f865b69c5..09a9b1cc698f 100644
--- a/contrib/oss-fuzz/libarchive_fuzzer.cc
+++ b/contrib/oss-fuzz/libarchive_fuzzer.cc
@@ -3,20 +3,7 @@
#include <vector>
#include "archive.h"
-
-struct Buffer {
- const uint8_t *buf;
- size_t len;
-};
-
-ssize_t reader_callback(struct archive *a, void *client_data,
- const void **block) {
- Buffer *buffer = reinterpret_cast<Buffer *>(client_data);
- *block = buffer->buf;
- ssize_t len = buffer->len;
- buffer->len = 0;
- return len;
-}
+#include "fuzz_helpers.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
int ret;
@@ -26,7 +13,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
- Buffer buffer = {buf, len};
+ Buffer buffer = {buf, len, 0};
archive_read_open(a, &buffer, NULL, reader_callback, NULL);
std::vector<uint8_t> data_buffer(getpagesize(), 0);
diff --git a/contrib/oss-fuzz/libarchive_fuzzer.dict b/contrib/oss-fuzz/libarchive_fuzzer.dict
new file mode 100644
index 000000000000..390b68567ad5
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_fuzzer.dict
@@ -0,0 +1,76 @@
+# General libarchive dictionary covering multiple formats
+
+# TAR magic
+"ustar"
+"ustar\x00"
+"ustar \x00"
+"\x00\x00"
+
+# ZIP magic
+"PK\x03\x04"
+"PK\x05\x06"
+"PK\x01\x02"
+"PK\x07\x08"
+
+# 7z magic
+"7z\xbc\xaf\x27\x1c"
+
+# RAR magic
+"Rar!\x1a\x07\x00"
+"Rar!\x1a\x07\x01\x00"
+
+# XAR magic
+"xar!"
+
+# CPIO magic
+"\xc7\x71"
+"070701"
+"070702"
+"070707"
+
+# CAB magic
+"MSCF"
+
+# LHA magic
+"-lh"
+"-lz"
+
+# AR magic
+"!<arch>\x0a"
+
+# ISO9660 magic
+"CD001"
+
+# GZIP magic
+"\x1f\x8b"
+
+# BZIP2 magic
+"BZ"
+"BZh"
+
+# XZ magic
+"\xfd7zXZ\x00"
+
+# LZMA magic
+"\x5d\x00\x00"
+
+# ZSTD magic
+"\x28\xb5\x2f\xfd"
+
+# LZ4 magic
+"\x04\x22\x4d\x18"
+
+# Common paths
+"/"
+"./"
+"../"
+"./test"
+"test.txt"
+"test/"
+
+# Common attributes
+"\x00\x00\x00\x00"
+"\xff\xff\xff\xff"
+
+# Passphrase
+"password"
diff --git a/contrib/oss-fuzz/libarchive_fuzzer.options b/contrib/oss-fuzz/libarchive_fuzzer.options
new file mode 100644
index 000000000000..7b1139e29326
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_fuzzer.options
@@ -0,0 +1,9 @@
+[libfuzzer]
+max_len = 524288
+timeout = 30
+
+[honggfuzz]
+timeout = 30
+
+[afl]
+timeout = 30
diff --git a/contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc
new file mode 100644
index 000000000000..6cdaff23a09c
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc
@@ -0,0 +1,58 @@
+/*
+ * ISO9660 format fuzzer for libarchive
+ * Tests ISO, Joliet, and Rock Ridge extensions
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 1024 * 1024; // 1MB for ISO images
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_iso9660(a);
+ archive_read_support_filter_all(a);
+
+ // Set options to test various ISO extensions
+ archive_read_set_options(a, "iso9660:joliet,iso9660:rockridge");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_symlink(entry);
+ archive_entry_hardlink(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_iso9660_fuzzer.dict b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.dict
new file mode 100644
index 000000000000..6dac3eaa52ff
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.dict
@@ -0,0 +1,36 @@
+# ISO9660 format dictionary
+
+# Volume descriptor type
+"\x00"
+"\x01"
+"\x02"
+"\xff"
+
+# Standard identifier
+"CD001"
+
+# Volume descriptor version
+"\x01"
+
+# Joliet escape sequences
+"%/@"
+"%/C"
+"%/E"
+
+# Rock Ridge signatures
+"SP"
+"RR"
+"CE"
+"PX"
+"PN"
+"SL"
+"NM"
+"CL"
+"PL"
+"RE"
+"TF"
+"SF"
+
+# System use
+"ER"
+"ES"
diff --git a/contrib/oss-fuzz/libarchive_iso9660_fuzzer.options b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.options
new file mode 100644
index 000000000000..f04ee45fd24b
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_iso9660_fuzzer.options
@@ -0,0 +1,10 @@
+[libfuzzer]
+max_len = 1048576
+timeout = 60
+rss_limit_mb = 2048
+
+[honggfuzz]
+timeout = 60
+
+[afl]
+timeout = 60
diff --git a/contrib/oss-fuzz/libarchive_lha_fuzzer.cc b/contrib/oss-fuzz/libarchive_lha_fuzzer.cc
new file mode 100644
index 000000000000..3957d3ed3d38
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_lha_fuzzer.cc
@@ -0,0 +1,54 @@
+/*
+ * LHA/LZH format fuzzer for libarchive
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_lha(a);
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_lha_fuzzer.dict b/contrib/oss-fuzz/libarchive_lha_fuzzer.dict
new file mode 100644
index 000000000000..38ca18406442
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_lha_fuzzer.dict
@@ -0,0 +1,26 @@
+# LHA/LZH format dictionary
+
+# Compression methods
+"-lh0-"
+"-lh1-"
+"-lh2-"
+"-lh3-"
+"-lh4-"
+"-lh5-"
+"-lh6-"
+"-lh7-"
+"-lhd-"
+"-lzs-"
+"-lz4-"
+"-lz5-"
+
+# OS type
+"\x00"
+"\x4d"
+"\x55"
+
+# Header levels
+"\x00"
+"\x01"
+"\x02"
+"\x03"
diff --git a/contrib/oss-fuzz/libarchive_linkify_fuzzer.cc b/contrib/oss-fuzz/libarchive_linkify_fuzzer.cc
new file mode 100644
index 000000000000..64c22dca959c
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_linkify_fuzzer.cc
@@ -0,0 +1,110 @@
+/*
+ * Archive entry link resolver fuzzer for libarchive
+ * Targets archive_entry_linkify (complexity: 775, zero coverage)
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 64 * 1024; // 64KB
+
+// Simple data consumer
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+
+ // Create a link resolver
+ struct archive_entry_linkresolver *resolver = archive_entry_linkresolver_new();
+ if (resolver == NULL) {
+ return 0;
+ }
+
+ // Set the format strategy based on input
+ uint8_t strategy = consumer.consume_byte() % 5;
+ int format;
+ switch (strategy) {
+ case 0: format = ARCHIVE_FORMAT_TAR_GNUTAR; break;
+ case 1: format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; break;
+ case 2: format = ARCHIVE_FORMAT_CPIO_POSIX; break;
+ case 3: format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; break;
+ default: format = ARCHIVE_FORMAT_TAR_USTAR; break;
+ }
+ archive_entry_linkresolver_set_strategy(resolver, format);
+
+ // Create multiple entries to test linkify with hardlinks
+ struct archive_entry *entries[32];
+ int num_entries = 0;
+
+ while (!consumer.empty() && num_entries < 32 && consumer.remaining() > 20) {
+ struct archive_entry *entry = archive_entry_new();
+ if (entry == NULL) break;
+
+ // Set pathname
+ archive_entry_set_pathname(entry, consumer.consume_string(64));
+
+ // Set inode and device for hardlink detection
+ archive_entry_set_ino(entry, consumer.consume_i64());
+ archive_entry_set_dev(entry, consumer.consume_u32());
+ archive_entry_set_nlink(entry, (consumer.consume_byte() % 5) + 1);
+
+ // Set mode (regular file or directory)
+ uint8_t ftype = consumer.consume_byte() % 2;
+ mode_t mode = ftype ? (S_IFDIR | 0755) : (S_IFREG | 0644);
+ archive_entry_set_mode(entry, mode);
+
+ archive_entry_set_size(entry, consumer.consume_i64() & 0xFFFF);
+ archive_entry_set_uid(entry, consumer.consume_u32() & 0xFFFF);
+ archive_entry_set_gid(entry, consumer.consume_u32() & 0xFFFF);
+
+ entries[num_entries++] = entry;
+ }
+
+ // Now run all entries through the linkresolver
+ for (int i = 0; i < num_entries; i++) {
+ struct archive_entry *entry = entries[i];
+ struct archive_entry *spare = NULL;
+
+ // This is the main function we want to fuzz (zero coverage)
+ archive_entry_linkify(resolver, &entry, &spare);
+
+ // entry and spare may be modified by linkify
+ // We still need to free the original entries we allocated
+ if (spare != NULL) {
+ archive_entry_free(spare);
+ }
+ }
+
+ // Free remaining entries from the resolver
+ struct archive_entry *entry = NULL;
+ struct archive_entry *spare = NULL;
+ while (1) {
+ archive_entry_linkify(resolver, &entry, &spare);
+ if (entry == NULL)
+ break;
+ archive_entry_free(entry);
+ entry = NULL;
+ if (spare != NULL) {
+ archive_entry_free(spare);
+ spare = NULL;
+ }
+ }
+
+ // Free all our created entries
+ for (int i = 0; i < num_entries; i++) {
+ if (entries[i] != NULL) {
+ archive_entry_free(entries[i]);
+ }
+ }
+
+ archive_entry_linkresolver_free(resolver);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_match_fuzzer.cc b/contrib/oss-fuzz/libarchive_match_fuzzer.cc
new file mode 100644
index 000000000000..a0f8ba7dae84
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_match_fuzzer.cc
@@ -0,0 +1,96 @@
+/*
+ * Archive match fuzzer for libarchive
+ * Tests pattern matching, time matching, and owner matching
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 32 * 1024;
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+
+ struct archive *match = archive_match_new();
+ if (match == NULL) {
+ return 0;
+ }
+
+ // Add various match patterns
+ while (!consumer.empty() && consumer.remaining() > 5) {
+ uint8_t match_type = consumer.consume_byte() % 6;
+
+ switch (match_type) {
+ case 0: {
+ // Pattern exclusion
+ const char *pattern = consumer.consume_string(64);
+ archive_match_exclude_pattern(match, pattern);
+ break;
+ }
+ case 1: {
+ // Pattern inclusion
+ const char *pattern = consumer.consume_string(64);
+ archive_match_include_pattern(match, pattern);
+ break;
+ }
+ case 2: {
+ // Time comparison (newer than)
+ int64_t sec = consumer.consume_i64();
+ int64_t nsec = consumer.consume_i64() % 1000000000;
+ archive_match_include_time(match, ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_NEWER,
+ sec, nsec);
+ break;
+ }
+ case 3: {
+ // Time comparison (older than)
+ int64_t sec = consumer.consume_i64();
+ int64_t nsec = consumer.consume_i64() % 1000000000;
+ archive_match_include_time(match, ARCHIVE_MATCH_MTIME | ARCHIVE_MATCH_OLDER,
+ sec, nsec);
+ break;
+ }
+ case 4: {
+ // UID inclusion
+ int64_t uid = consumer.consume_i64() & 0xFFFF;
+ archive_match_include_uid(match, uid);
+ break;
+ }
+ case 5: {
+ // GID inclusion
+ int64_t gid = consumer.consume_i64() & 0xFFFF;
+ archive_match_include_gid(match, gid);
+ break;
+ }
+ }
+ }
+
+ // Create a test entry and check if it matches
+ struct archive_entry *entry = archive_entry_new();
+ if (entry) {
+ archive_entry_set_pathname(entry, "test/file.txt");
+ archive_entry_set_mtime(entry, 1234567890, 0);
+ archive_entry_set_uid(entry, 1000);
+ archive_entry_set_gid(entry, 1000);
+ archive_entry_set_mode(entry, 0644 | 0100000); // Regular file
+
+ // Test matching
+ archive_match_path_excluded(match, entry);
+ archive_match_time_excluded(match, entry);
+ archive_match_owner_excluded(match, entry);
+ archive_match_excluded(match, entry);
+
+ archive_entry_free(entry);
+ }
+
+ archive_match_free(match);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_mtree_fuzzer.cc b/contrib/oss-fuzz/libarchive_mtree_fuzzer.cc
new file mode 100644
index 000000000000..e0110d4e1bc4
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_mtree_fuzzer.cc
@@ -0,0 +1,61 @@
+/*
+ * MTREE format fuzzer for libarchive
+ * Tests mtree manifest parsing
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 256 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_mtree(a);
+ archive_read_support_filter_all(a);
+
+ // Enable checkfs option to test more code paths
+ archive_read_set_options(a, "mtree:checkfs");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+ archive_entry_uname(entry);
+ archive_entry_gname(entry);
+ archive_entry_symlink(entry);
+ archive_entry_fflags_text(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_mtree_fuzzer.dict b/contrib/oss-fuzz/libarchive_mtree_fuzzer.dict
new file mode 100644
index 000000000000..7241ea5d2242
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_mtree_fuzzer.dict
@@ -0,0 +1,47 @@
+# MTREE format dictionary
+
+# Keywords
+"/set"
+"/unset"
+".."
+
+# File types
+"type=file"
+"type=dir"
+"type=link"
+"type=block"
+"type=char"
+"type=fifo"
+"type=socket"
+
+# Attributes
+"mode="
+"uid="
+"gid="
+"uname="
+"gname="
+"size="
+"time="
+"link="
+"cksum="
+"md5="
+"md5digest="
+"sha1="
+"sha1digest="
+"sha256="
+"sha256digest="
+"sha384="
+"sha384digest="
+"sha512="
+"sha512digest="
+"rmd160="
+"rmd160digest="
+"flags="
+"nlink="
+"inode="
+"device="
+"resdevice="
+"contents="
+"optional"
+"ignore"
+"nochange"
diff --git a/contrib/oss-fuzz/libarchive_rar5_fuzzer.cc b/contrib/oss-fuzz/libarchive_rar5_fuzzer.cc
new file mode 100644
index 000000000000..2a7b97f87e7a
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_rar5_fuzzer.cc
@@ -0,0 +1,61 @@
+/*
+ * RAR5 format specific fuzzer for libarchive
+ * Targets RAR5 parsing code paths
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024; // 512KB
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable RAR5 format specifically
+ archive_read_support_format_rar5(a);
+ // Enable common filters
+ archive_read_support_filter_all(a);
+
+ // Set passphrase for encrypted archives
+ archive_read_add_passphrase(a, "password");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ // Exercise entry metadata access
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_is_encrypted(entry);
+
+ // Read data
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_rar5_fuzzer.dict b/contrib/oss-fuzz/libarchive_rar5_fuzzer.dict
new file mode 100644
index 000000000000..f1e4311570f8
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_rar5_fuzzer.dict
@@ -0,0 +1,37 @@
+# RAR5 format dictionary
+# Magic bytes (RAR5 signature)
+"Rar!\x1a\x07\x01\x00"
+"\x52\x61\x72\x21\x1a\x07\x01\x00"
+
+# Common header types
+"\x01"
+"\x02"
+"\x03"
+"\x04"
+"\x05"
+
+# Common flags
+"\x00\x00"
+"\x01\x00"
+"\x02\x00"
+"\x04\x00"
+
+# Compression methods
+"\x00"
+"\x01"
+"\x02"
+"\x03"
+"\x04"
+"\x05"
+
+# File attributes
+"\x20\x00\x00\x00"
+"\x10\x00\x00\x00"
+
+# Encryption marker
+"\x80"
+"password"
+"Password"
+
+# End of archive
+"\x1d\x77\x56\x51\x03\x05\x04\x00"
diff --git a/contrib/oss-fuzz/libarchive_rar_fuzzer.cc b/contrib/oss-fuzz/libarchive_rar_fuzzer.cc
new file mode 100644
index 000000000000..dd94181bcc06
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_rar_fuzzer.cc
@@ -0,0 +1,56 @@
+/*
+ * RAR v4 format fuzzer for libarchive
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_rar(a);
+ archive_read_support_filter_all(a);
+
+ // Add passphrase for encrypted RARs
+ archive_read_add_passphrase(a, "password");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_is_encrypted(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_read_disk_fuzzer.cc b/contrib/oss-fuzz/libarchive_read_disk_fuzzer.cc
new file mode 100644
index 000000000000..91ccea15fde6
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_read_disk_fuzzer.cc
@@ -0,0 +1,76 @@
+/*
+ * Archive read disk fuzzer for libarchive
+ * Tests filesystem traversal and entry creation from paths
+ * Security-critical: path traversal, symlink handling
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 16 * 1024;
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+
+ struct archive *a = archive_read_disk_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Configure disk reader behavior
+ uint8_t flags = consumer.consume_byte();
+ if (flags & 0x01) {
+ archive_read_disk_set_symlink_logical(a);
+ } else if (flags & 0x02) {
+ archive_read_disk_set_symlink_physical(a);
+ } else {
+ archive_read_disk_set_symlink_hybrid(a);
+ }
+
+ archive_read_disk_set_standard_lookup(a);
+
+ // Set behavior flags
+ int behavior = 0;
+ if (flags & 0x04) behavior |= ARCHIVE_READDISK_RESTORE_ATIME;
+ if (flags & 0x08) behavior |= ARCHIVE_READDISK_HONOR_NODUMP;
+ if (flags & 0x10) behavior |= ARCHIVE_READDISK_NO_TRAVERSE_MOUNTS;
+ archive_read_disk_set_behavior(a, behavior);
+
+ // Create an entry and test entry_from_file with various paths
+ struct archive_entry *entry = archive_entry_new();
+ if (entry) {
+ // Test with /tmp (safe, always exists)
+ archive_entry_copy_pathname(entry, "/tmp");
+ archive_read_disk_entry_from_file(a, entry, -1, NULL);
+
+ // Get entry info
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+
+ // Test name lookups
+ archive_read_disk_gname(a, 0);
+ archive_read_disk_uname(a, 0);
+ archive_read_disk_gname(a, 1000);
+ archive_read_disk_uname(a, 1000);
+
+ archive_entry_free(entry);
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.cc b/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.cc
new file mode 100644
index 000000000000..15d4434bc4eb
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.cc
@@ -0,0 +1,110 @@
+/*
+ * Archive roundtrip fuzzer for libarchive
+ * Writes an archive then reads it back - tests write/read consistency
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 64 * 1024;
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len < 10 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+ std::vector<uint8_t> archive_data;
+ archive_data.reserve(len * 2);
+
+ // Phase 1: Write an archive
+ struct archive *writer = archive_write_new();
+ if (writer == NULL) {
+ return 0;
+ }
+
+ // Select format
+ uint8_t format = consumer.consume_byte() % 5;
+ switch (format) {
+ case 0: archive_write_set_format_pax_restricted(writer); break;
+ case 1: archive_write_set_format_ustar(writer); break;
+ case 2: archive_write_set_format_cpio_newc(writer); break;
+ case 3: archive_write_set_format_zip(writer); break;
+ default: archive_write_set_format_gnutar(writer); break;
+ }
+
+ archive_write_add_filter_none(writer);
+
+ // Open to memory
+ size_t used = 0;
+ archive_data.resize(len * 4);
+ if (archive_write_open_memory(writer, archive_data.data(), archive_data.size(), &used) != ARCHIVE_OK) {
+ archive_write_free(writer);
+ return 0;
+ }
+
+ // Write entries
+ int entry_count = 0;
+ while (!consumer.empty() && entry_count < 5 && consumer.remaining() > 10) {
+ struct archive_entry *entry = archive_entry_new();
+ if (entry == NULL) break;
+
+ archive_entry_set_pathname(entry, consumer.consume_string(32));
+ archive_entry_set_mode(entry, S_IFREG | 0644);
+ archive_entry_set_uid(entry, consumer.consume_u32() & 0xFFFF);
+ archive_entry_set_gid(entry, consumer.consume_u32() & 0xFFFF);
+
+ uint8_t data_buf[256];
+ size_t data_len = consumer.consume_bytes(data_buf, 256);
+ archive_entry_set_size(entry, data_len);
+
+ if (archive_write_header(writer, entry) == ARCHIVE_OK && data_len > 0) {
+ archive_write_data(writer, data_buf, data_len);
+ }
+
+ archive_entry_free(entry);
+ entry_count++;
+ }
+
+ archive_write_close(writer);
+ archive_write_free(writer);
+
+ if (used == 0) {
+ return 0;
+ }
+
+ // Phase 2: Read the archive back
+ struct archive *reader = archive_read_new();
+ if (reader == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_all(reader);
+ archive_read_support_filter_all(reader);
+
+ if (archive_read_open_memory(reader, archive_data.data(), used) != ARCHIVE_OK) {
+ archive_read_free(reader);
+ return 0;
+ }
+
+ std::vector<uint8_t> read_buffer(4096, 0);
+ struct archive_entry *entry;
+ while (archive_read_next_header(reader, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(reader, read_buffer.data(), read_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(reader);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.options b/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.options
new file mode 100644
index 000000000000..1489609db06e
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_roundtrip_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+timeout = 30
diff --git a/contrib/oss-fuzz/libarchive_seek_fuzzer.cc b/contrib/oss-fuzz/libarchive_seek_fuzzer.cc
new file mode 100644
index 000000000000..11e958030701
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_seek_fuzzer.cc
@@ -0,0 +1,125 @@
+/*
+ * Archive seek/read fuzzer for libarchive
+ * Tests seeking within archives and reading at random positions
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 256 * 1024;
+
+struct SeekableBuffer {
+ const uint8_t *buf;
+ size_t len;
+ size_t pos;
+};
+
+static ssize_t seek_read_callback(struct archive *a, void *client_data,
+ const void **block) {
+ (void)a;
+ SeekableBuffer *buffer = reinterpret_cast<SeekableBuffer *>(client_data);
+ if (buffer->pos >= buffer->len) {
+ *block = NULL;
+ return 0;
+ }
+ *block = buffer->buf + buffer->pos;
+ size_t avail = buffer->len - buffer->pos;
+ size_t to_read = (avail > 4096) ? 4096 : avail;
+ buffer->pos += to_read;
+ return to_read;
+}
+
+static la_int64_t seek_callback(struct archive *a, void *client_data,
+ la_int64_t offset, int whence) {
+ (void)a;
+ SeekableBuffer *buffer = reinterpret_cast<SeekableBuffer *>(client_data);
+ la_int64_t new_pos;
+
+ switch (whence) {
+ case SEEK_SET:
+ new_pos = offset;
+ break;
+ case SEEK_CUR:
+ new_pos = static_cast<la_int64_t>(buffer->pos) + offset;
+ break;
+ case SEEK_END:
+ new_pos = static_cast<la_int64_t>(buffer->len) + offset;
+ break;
+ default:
+ return ARCHIVE_FATAL;
+ }
+
+ if (new_pos < 0) new_pos = 0;
+ if (new_pos > static_cast<la_int64_t>(buffer->len))
+ new_pos = static_cast<la_int64_t>(buffer->len);
+
+ buffer->pos = static_cast<size_t>(new_pos);
+ return new_pos;
+}
+
+static la_int64_t skip_callback(struct archive *a, void *client_data,
+ la_int64_t request) {
+ (void)a;
+ SeekableBuffer *buffer = reinterpret_cast<SeekableBuffer *>(client_data);
+ size_t avail = buffer->len - buffer->pos;
+ la_int64_t to_skip = (request > static_cast<la_int64_t>(avail))
+ ? static_cast<la_int64_t>(avail)
+ : request;
+ buffer->pos += static_cast<size_t>(to_skip);
+ return to_skip;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable formats that benefit from seeking
+ archive_read_support_format_zip_seekable(a);
+ archive_read_support_format_7zip(a);
+ archive_read_support_format_rar(a);
+ archive_read_support_format_rar5(a);
+ archive_read_support_format_iso9660(a);
+ archive_read_support_filter_all(a);
+
+ SeekableBuffer buffer = {buf, len, 0};
+
+ archive_read_set_read_callback(a, seek_read_callback);
+ archive_read_set_seek_callback(a, seek_callback);
+ archive_read_set_skip_callback(a, skip_callback);
+ archive_read_set_callback_data(a, &buffer);
+
+ if (archive_read_open1(a) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+ int entry_count = 0;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK && entry_count < 50) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+
+ // Read data which may trigger seeks
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+
+ entry_count++;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_seek_fuzzer.options b/contrib/oss-fuzz/libarchive_seek_fuzzer.options
new file mode 100644
index 000000000000..4821a7059b59
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_seek_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 262144
+timeout = 30
diff --git a/contrib/oss-fuzz/libarchive_string_fuzzer.cc b/contrib/oss-fuzz/libarchive_string_fuzzer.cc
new file mode 100644
index 000000000000..24731da1c680
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_string_fuzzer.cc
@@ -0,0 +1,144 @@
+/*
+ * Archive string/encoding conversion fuzzer for libarchive
+ * Tests character encoding conversions which are often vulnerability sources
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 32 * 1024;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive_entry *entry = archive_entry_new();
+ if (entry == NULL) {
+ return 0;
+ }
+
+ // Reserve some bytes for control
+ if (len < 4) {
+ archive_entry_free(entry);
+ return 0;
+ }
+
+ uint8_t test_type = buf[0];
+ const char *str = reinterpret_cast<const char*>(buf + 1);
+ size_t str_len = len - 1;
+
+ // Ensure null termination for string operations
+ char *safe_str = static_cast<char*>(malloc(str_len + 1));
+ if (safe_str == NULL) {
+ archive_entry_free(entry);
+ return 0;
+ }
+ memcpy(safe_str, str, str_len);
+ safe_str[str_len] = '\0';
+
+ // Test various string functions based on type
+ switch (test_type % 10) {
+ case 0:
+ // Pathname conversions
+ archive_entry_set_pathname(entry, safe_str);
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_pathname_utf8(entry);
+ break;
+
+ case 1:
+ // Symlink conversions
+ archive_entry_set_symlink(entry, safe_str);
+ archive_entry_symlink(entry);
+ archive_entry_symlink_w(entry);
+ archive_entry_symlink_utf8(entry);
+ break;
+
+ case 2:
+ // Hardlink conversions
+ archive_entry_set_hardlink(entry, safe_str);
+ archive_entry_hardlink(entry);
+ archive_entry_hardlink_w(entry);
+ archive_entry_hardlink_utf8(entry);
+ break;
+
+ case 3:
+ // Username conversions
+ archive_entry_set_uname(entry, safe_str);
+ archive_entry_uname(entry);
+ archive_entry_uname_w(entry);
+ archive_entry_uname_utf8(entry);
+ break;
+
+ case 4:
+ // Group name conversions
+ archive_entry_set_gname(entry, safe_str);
+ archive_entry_gname(entry);
+ archive_entry_gname_w(entry);
+ archive_entry_gname_utf8(entry);
+ break;
+
+ case 5:
+ // Copy functions
+ archive_entry_copy_pathname(entry, safe_str);
+ archive_entry_copy_symlink(entry, safe_str);
+ archive_entry_copy_hardlink(entry, safe_str);
+ break;
+
+ case 6:
+ // UTF-8 specific
+ archive_entry_update_pathname_utf8(entry, safe_str);
+ archive_entry_update_symlink_utf8(entry, safe_str);
+ archive_entry_update_hardlink_utf8(entry, safe_str);
+ break;
+
+ case 7:
+ // Fflags text
+ archive_entry_copy_fflags_text(entry, safe_str);
+ archive_entry_fflags_text(entry);
+ break;
+
+ case 8:
+ // ACL text parsing
+ archive_entry_acl_from_text(entry, safe_str, ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ archive_entry_acl_from_text(entry, safe_str, ARCHIVE_ENTRY_ACL_TYPE_DEFAULT);
+ archive_entry_acl_from_text(entry, safe_str, ARCHIVE_ENTRY_ACL_TYPE_NFS4);
+ break;
+
+ case 9: {
+ // Wide character operations
+ size_t wlen = str_len;
+ wchar_t *wstr = static_cast<wchar_t*>(malloc((wlen + 1) * sizeof(wchar_t)));
+ if (wstr) {
+ mbstowcs(wstr, safe_str, wlen);
+ wstr[wlen] = L'\0';
+
+ archive_entry_copy_pathname_w(entry, wstr);
+ archive_entry_pathname_w(entry);
+
+ archive_entry_copy_symlink_w(entry, wstr);
+ archive_entry_symlink_w(entry);
+
+ free(wstr);
+ }
+ break;
+ }
+ }
+
+ // Clone and compare
+ struct archive_entry *entry2 = archive_entry_clone(entry);
+ if (entry2) {
+ archive_entry_free(entry2);
+ }
+
+ free(safe_str);
+ archive_entry_free(entry);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_tar_fuzzer.cc b/contrib/oss-fuzz/libarchive_tar_fuzzer.cc
new file mode 100644
index 000000000000..ef4ce5fe339f
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_tar_fuzzer.cc
@@ -0,0 +1,86 @@
+/*
+ * TAR format fuzzer for libarchive
+ * Tests all TAR variants: ustar, pax, gnutar, v7, oldgnu
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_tar(a);
+ archive_read_support_format_gnutar(a);
+ archive_read_support_filter_all(a);
+
+ // Enable various TAR options
+ archive_read_set_options(a, "tar:read_concatenated_archives,tar:mac-ext");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ // Exercise all metadata accessors
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_atime(entry);
+ archive_entry_ctime(entry);
+ archive_entry_mode(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+ archive_entry_uname(entry);
+ archive_entry_gname(entry);
+ archive_entry_symlink(entry);
+ archive_entry_hardlink(entry);
+ archive_entry_rdev(entry);
+ archive_entry_devmajor(entry);
+ archive_entry_devminor(entry);
+
+ // Test sparse file handling
+ archive_entry_sparse_reset(entry);
+ int64_t offset, length;
+ while (archive_entry_sparse_next(entry, &offset, &length) == ARCHIVE_OK) {
+ (void)offset;
+ (void)length;
+ }
+
+ // Test xattr handling
+ archive_entry_xattr_reset(entry);
+ const char *name;
+ const void *value;
+ size_t size;
+ while (archive_entry_xattr_next(entry, &name, &value, &size) == ARCHIVE_OK) {
+ (void)name;
+ (void)value;
+ (void)size;
+ }
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_tar_fuzzer.dict b/contrib/oss-fuzz/libarchive_tar_fuzzer.dict
new file mode 100644
index 000000000000..954d54b59c25
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_tar_fuzzer.dict
@@ -0,0 +1,51 @@
+# TAR format dictionary
+
+# USTAR magic
+"ustar"
+"ustar\x00"
+"ustar \x00"
+
+# GNU tar magic
+"GNUtar "
+"GNUtar\x00"
+
+# Common header field values
+"00000000000"
+"0000644"
+"0000755"
+"0000777"
+
+# Type flags
+"0"
+"1"
+"2"
+"3"
+"4"
+"5"
+"6"
+"7"
+"g"
+"x"
+"L"
+"K"
+
+# PAX keywords
+"path="
+"linkpath="
+"uname="
+"gname="
+"uid="
+"gid="
+"size="
+"mtime="
+"atime="
+"ctime="
+"SCHILY.xattr."
+"LIBARCHIVE.xattr."
+
+# Sparse headers
+"GNU.sparse.major="
+"GNU.sparse.minor="
+"GNU.sparse.name="
+"GNU.sparse.realsize="
+"GNU.sparse.map="
diff --git a/contrib/oss-fuzz/libarchive_warc_fuzzer.cc b/contrib/oss-fuzz/libarchive_warc_fuzzer.cc
new file mode 100644
index 000000000000..2ae7a167f68a
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_warc_fuzzer.cc
@@ -0,0 +1,50 @@
+/*
+ * WARC (Web Archive) format fuzzer for libarchive
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_warc(a);
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_warc_fuzzer.dict b/contrib/oss-fuzz/libarchive_warc_fuzzer.dict
new file mode 100644
index 000000000000..ca1d08adf22e
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_warc_fuzzer.dict
@@ -0,0 +1,34 @@
+# WARC format dictionary
+
+# Version
+"WARC/1.0"
+"WARC/1.1"
+"WARC/0.17"
+"WARC/0.18"
+
+# Record types
+"warcinfo"
+"response"
+"resource"
+"request"
+"metadata"
+"revisit"
+"conversion"
+"continuation"
+
+# Headers
+"WARC-Type:"
+"WARC-Record-ID:"
+"WARC-Date:"
+"WARC-Target-URI:"
+"Content-Length:"
+"Content-Type:"
+"WARC-Block-Digest:"
+"WARC-Payload-Digest:"
+"WARC-Concurrent-To:"
+"WARC-Refers-To:"
+
+# Content types
+"application/warc-fields"
+"application/http;msgtype=request"
+"application/http;msgtype=response"
diff --git a/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc
new file mode 100644
index 000000000000..056bd1639550
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc
@@ -0,0 +1,125 @@
+/*
+ * Archive write disk fuzzer for libarchive
+ * Tests extraction to filesystem
+ * Security-critical: path traversal, permission handling, symlink attacks
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 64 * 1024;
+
+static char g_temp_dir[256] = {0};
+
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ (void)argc;
+ (void)argv;
+ // Create a temporary directory for extraction
+ snprintf(g_temp_dir, sizeof(g_temp_dir), "/tmp/fuzz_extract_XXXXXX");
+ if (mkdtemp(g_temp_dir) == NULL) {
+ g_temp_dir[0] = '\0';
+ }
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ if (g_temp_dir[0] == '\0') {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+
+ struct archive *disk = archive_write_disk_new();
+ if (disk == NULL) {
+ return 0;
+ }
+
+ // Configure write disk options
+ uint8_t opt_flags = consumer.consume_byte();
+ int flags = 0;
+ if (opt_flags & 0x01) flags |= ARCHIVE_EXTRACT_TIME;
+ if (opt_flags & 0x02) flags |= ARCHIVE_EXTRACT_PERM;
+ if (opt_flags & 0x04) flags |= ARCHIVE_EXTRACT_ACL;
+ 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;
+
+ archive_write_disk_set_options(disk, flags);
+ archive_write_disk_set_standard_lookup(disk);
+
+ // Create entries to extract
+ int entry_count = 0;
+ while (!consumer.empty() && entry_count < 5 && consumer.remaining() > 20) {
+ 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);
+
+ uint8_t ftype = consumer.consume_byte() % 3;
+ mode_t mode;
+ switch (ftype) {
+ case 0: mode = S_IFREG | 0644; break;
+ case 1: mode = S_IFDIR | 0755; break;
+ default: mode = S_IFREG | 0644; break;
+ }
+ archive_entry_set_mode(entry, mode);
+
+ archive_entry_set_uid(entry, 1000);
+ archive_entry_set_gid(entry, 1000);
+ archive_entry_set_mtime(entry, consumer.consume_i64(), 0);
+
+ // 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);
+ }
+ }
+ archive_write_finish_entry(disk);
+ }
+
+ archive_entry_free(entry);
+ entry_count++;
+ }
+
+ archive_write_close(disk);
+ archive_write_free(disk);
+
+ // Clean up extracted files using nftw (safer than system())
+ remove_directory_tree(g_temp_dir);
+ // Recreate the temp directory for next iteration
+ mkdir(g_temp_dir, 0700);
+
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_write_disk_fuzzer.options b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.options
new file mode 100644
index 000000000000..1489609db06e
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_write_disk_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 65536
+timeout = 30
diff --git a/contrib/oss-fuzz/libarchive_write_fuzzer.cc b/contrib/oss-fuzz/libarchive_write_fuzzer.cc
new file mode 100644
index 000000000000..012c5a4f5596
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_write_fuzzer.cc
@@ -0,0 +1,132 @@
+/*
+ * Archive write fuzzer for libarchive
+ * Tests archive creation and writing code paths
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 64 * 1024; // 64KB
+
+// Simple data consumer
+
+// Memory write callback
+static std::vector<uint8_t> *g_output = nullptr;
+
+static ssize_t write_callback(struct archive *a, void *client_data, const void *buffer, size_t length) {
+ (void)a;
+ (void)client_data;
+ if (g_output && length > 0) {
+ const uint8_t *buf = static_cast<const uint8_t*>(buffer);
+ g_output->insert(g_output->end(), buf, buf + length);
+ }
+ return length;
+}
+
+static int close_callback(struct archive *a, void *client_data) {
+ (void)a;
+ (void)client_data;
+ return ARCHIVE_OK;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ DataConsumer consumer(buf, len);
+ std::vector<uint8_t> output;
+ g_output = &output;
+
+ struct archive *a = archive_write_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Select format based on input
+ uint8_t format_choice = consumer.consume_byte() % 8;
+ switch (format_choice) {
+ case 0: archive_write_set_format_pax_restricted(a); break;
+ case 1: archive_write_set_format_gnutar(a); break;
+ case 2: archive_write_set_format_ustar(a); break;
+ case 3: archive_write_set_format_cpio_newc(a); break;
+ case 4: archive_write_set_format_zip(a); break;
+ case 5: archive_write_set_format_7zip(a); break;
+ case 6: archive_write_set_format_xar(a); break;
+ default: archive_write_set_format_pax(a); break;
+ }
+
+ // Select compression based on input
+ uint8_t filter_choice = consumer.consume_byte() % 6;
+ switch (filter_choice) {
+ case 0: archive_write_add_filter_gzip(a); break;
+ case 1: archive_write_add_filter_bzip2(a); break;
+ case 2: archive_write_add_filter_xz(a); break;
+ case 3: archive_write_add_filter_zstd(a); break;
+ case 4: archive_write_add_filter_none(a); break;
+ default: archive_write_add_filter_none(a); break;
+ }
+
+ // Open for writing to memory
+ if (archive_write_open(a, NULL, NULL, write_callback, close_callback) != ARCHIVE_OK) {
+ archive_write_free(a);
+ g_output = nullptr;
+ return 0;
+ }
+
+ // Create entries based on remaining input
+ int entry_count = 0;
+ while (!consumer.empty() && entry_count < 10 && consumer.remaining() > 20) {
+ struct archive_entry *entry = archive_entry_new();
+ if (entry == NULL) break;
+
+ // Set entry properties
+ archive_entry_set_pathname(entry, consumer.consume_string(64));
+
+ 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);
+
+ archive_entry_set_uid(entry, consumer.consume_u32() & 0xFFFF);
+ archive_entry_set_gid(entry, consumer.consume_u32() & 0xFFFF);
+ archive_entry_set_mtime(entry, consumer.consume_i64(), 0);
+
+ // For regular files, write some data
+ if (S_ISREG(mode)) {
+ uint8_t data_buf[1024];
+ size_t data_len = consumer.consume_bytes(data_buf, 1024);
+ archive_entry_set_size(entry, data_len);
+
+ if (archive_write_header(a, entry) == ARCHIVE_OK && data_len > 0) {
+ archive_write_data(a, data_buf, data_len);
+ }
+ } else if (S_ISLNK(mode)) {
+ archive_entry_set_symlink(entry, consumer.consume_string(64));
+ archive_entry_set_size(entry, 0);
+ archive_write_header(a, entry);
+ } else {
+ archive_entry_set_size(entry, 0);
+ archive_write_header(a, entry);
+ }
+
+ archive_entry_free(entry);
+ entry_count++;
+ }
+
+ archive_write_close(a);
+ archive_write_free(a);
+ g_output = nullptr;
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_xar_fuzzer.cc b/contrib/oss-fuzz/libarchive_xar_fuzzer.cc
new file mode 100644
index 000000000000..be889643140d
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_xar_fuzzer.cc
@@ -0,0 +1,60 @@
+/*
+ * XAR format specific fuzzer for libarchive
+ * Targets xar_read_header and XAR parsing code paths
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024; // 512KB
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ // Enable XAR format specifically
+ archive_read_support_format_xar(a);
+ // Enable common filters
+ archive_read_support_filter_all(a);
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ // Exercise entry metadata access
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_filetype(entry);
+ archive_entry_uid(entry);
+ archive_entry_gid(entry);
+
+ // Read data
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_xar_fuzzer.dict b/contrib/oss-fuzz/libarchive_xar_fuzzer.dict
new file mode 100644
index 000000000000..1e5d935442ae
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_xar_fuzzer.dict
@@ -0,0 +1,44 @@
+# XAR format dictionary
+# Magic bytes
+"xar!"
+"\x78\x61\x72\x21"
+
+# XML elements commonly in XAR
+"<xar>"
+"</xar>"
+"<toc>"
+"</toc>"
+"<file>"
+"</file>"
+"<name>"
+"</name>"
+"<data>"
+"</data>"
+"<encoding>"
+"</encoding>"
+"<archived-checksum>"
+"<extracted-checksum>"
+"<offset>"
+"<length>"
+"<size>"
+"<mode>"
+"<uid>"
+"<gid>"
+"<user>"
+"<group>"
+"<type>"
+"<mtime>"
+"<atime>"
+"<ctime>"
+
+# Compression types
+"application/octet-stream"
+"application/x-gzip"
+"application/x-bzip2"
+"application/x-lzma"
+
+# Checksum types
+"sha1"
+"md5"
+"sha256"
+"sha512"
diff --git a/contrib/oss-fuzz/libarchive_xar_fuzzer.options b/contrib/oss-fuzz/libarchive_xar_fuzzer.options
new file mode 100644
index 000000000000..d2d9f0ed27d2
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_xar_fuzzer.options
@@ -0,0 +1,10 @@
+[libfuzzer]
+max_len = 524288
+timeout = 60
+rss_limit_mb = 2048
+
+[honggfuzz]
+timeout = 60
+
+[afl]
+timeout = 60
diff --git a/contrib/oss-fuzz/libarchive_zip_fuzzer.cc b/contrib/oss-fuzz/libarchive_zip_fuzzer.cc
new file mode 100644
index 000000000000..15ce1f4855ae
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_zip_fuzzer.cc
@@ -0,0 +1,68 @@
+/*
+ * ZIP format fuzzer for libarchive
+ * Tests ZIP with various compression methods and encryption
+ */
+#include <stddef.h>
+#include <stdint.h>
+#include <vector>
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "fuzz_helpers.h"
+
+static constexpr size_t kMaxInputSize = 512 * 1024;
+
+
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
+ if (len == 0 || len > kMaxInputSize) {
+ return 0;
+ }
+
+ struct archive *a = archive_read_new();
+ if (a == NULL) {
+ return 0;
+ }
+
+ archive_read_support_format_zip(a);
+ archive_read_support_filter_all(a);
+
+ // Add passphrase for encrypted ZIPs
+ archive_read_add_passphrase(a, "password");
+ archive_read_add_passphrase(a, "test");
+ archive_read_add_passphrase(a, "");
+
+ // Enable ZIP options
+ archive_read_set_options(a, "zip:ignorecrc32");
+
+ Buffer buffer = {buf, len, 0};
+ if (archive_read_open(a, &buffer, NULL, reader_callback, NULL) != ARCHIVE_OK) {
+ archive_read_free(a);
+ return 0;
+ }
+
+ std::vector<uint8_t> data_buffer(4096, 0);
+ struct archive_entry *entry;
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+ archive_entry_pathname(entry);
+ archive_entry_pathname_w(entry);
+ archive_entry_size(entry);
+ archive_entry_mtime(entry);
+ archive_entry_mode(entry);
+ archive_entry_is_encrypted(entry);
+ archive_entry_is_data_encrypted(entry);
+ archive_entry_is_metadata_encrypted(entry);
+
+ // Check compression name
+ archive_format_name(a);
+ archive_filter_name(a, 0);
+
+ ssize_t r;
+ while ((r = archive_read_data(a, data_buffer.data(), data_buffer.size())) > 0)
+ ;
+ }
+
+ archive_read_free(a);
+ return 0;
+}
diff --git a/contrib/oss-fuzz/libarchive_zip_fuzzer.dict b/contrib/oss-fuzz/libarchive_zip_fuzzer.dict
new file mode 100644
index 000000000000..185c6a3fa97d
--- /dev/null
+++ b/contrib/oss-fuzz/libarchive_zip_fuzzer.dict
@@ -0,0 +1,43 @@
+# ZIP format dictionary
+
+# Signatures
+"PK\x03\x04"
+"PK\x01\x02"
+"PK\x05\x06"
+"PK\x06\x06"
+"PK\x06\x07"
+"PK\x07\x08"
+
+# Version needed
+"\x14\x00"
+"\x0a\x00"
+"\x2d\x00"
+"\x3f\x00"
+
+# Compression methods
+"\x00\x00"
+"\x08\x00"
+"\x09\x00"
+"\x0c\x00"
+"\x0e\x00"
+"\x5f\x00"
+
+# General purpose flags
+"\x00\x00"
+"\x01\x00"
+"\x08\x00"
+"\x09\x00"
+
+# Extra field IDs
+"\x01\x00"
+"\x07\x00"
+"\x09\x00"
+"\x0a\x00"
+"\x15\x00"
+"\x17\x00"
+"\x55\x54"
+"\x75\x78"
+
+# Encryption
+"\x01\x99"
+"\x02\x99"
diff --git a/contrib/oss-fuzz/oss-fuzz-build.sh b/contrib/oss-fuzz/oss-fuzz-build.sh
index 83d8470b13f3..16850fe38eff 100755
--- a/contrib/oss-fuzz/oss-fuzz-build.sh
+++ b/contrib/oss-fuzz/oss-fuzz-build.sh
@@ -1,16 +1,131 @@
-# build the project
+#!/bin/bash -eu
+
+# Build the project
./build/autogen.sh
./configure
make -j$(nproc) all
-# build seed
-cp $SRC/libarchive/contrib/oss-fuzz/corpus.zip\
- $OUT/libarchive_fuzzer_seed_corpus.zip
-
-# build fuzzer(s)
-$CXX $CXXFLAGS -Ilibarchive \
- $SRC/libarchive/contrib/oss-fuzz/libarchive_fuzzer.cc \
- -o $OUT/libarchive_fuzzer $LIB_FUZZING_ENGINE \
- .libs/libarchive.a -Wl,-Bstatic -lbz2 -llzo2 \
- -lxml2 -llzma -lz -lcrypto -llz4 -licuuc \
- -licudata -Wl,-Bdynamic
+FUZZ_DIR=$SRC/libarchive/contrib/oss-fuzz
+TEST_DIR=$SRC/libarchive/libarchive/test
+
+# Common libraries for linking
+LIBS=".libs/libarchive.a -Wl,-Bstatic -lbz2 -llzo2 -lxml2 -llzma -lz -lcrypto -llz4 -licuuc -licudata -Wl,-Bdynamic"
+
+# Function to build a fuzzer
+build_fuzzer() {
+ local name=$1
+ local source=$2
+ echo "Building fuzzer: $name"
+ $CXX $CXXFLAGS -Ilibarchive \
+ "$source" \
+ -o "$OUT/$name" $LIB_FUZZING_ENGINE $LIBS
+}
+
+# Build all format-specific fuzzers
+FUZZERS=(
+ "libarchive_fuzzer"
+ "libarchive_tar_fuzzer"
+ "libarchive_zip_fuzzer"
+ "libarchive_7zip_fuzzer"
+ "libarchive_rar_fuzzer"
+ "libarchive_rar5_fuzzer"
+ "libarchive_xar_fuzzer"
+ "libarchive_cab_fuzzer"
+ "libarchive_lha_fuzzer"
+ "libarchive_iso9660_fuzzer"
+ "libarchive_cpio_fuzzer"
+ "libarchive_warc_fuzzer"
+ "libarchive_mtree_fuzzer"
+ "libarchive_ar_fuzzer"
+ "libarchive_filter_fuzzer"
+ "libarchive_entry_fuzzer"
+ "libarchive_write_fuzzer"
+ "libarchive_linkify_fuzzer"
+ "libarchive_match_fuzzer"
+ "libarchive_encryption_fuzzer"
+ "libarchive_read_disk_fuzzer"
+ "libarchive_write_disk_fuzzer"
+ "libarchive_seek_fuzzer"
+ "libarchive_string_fuzzer"
+ "libarchive_roundtrip_fuzzer"
+)
+
+for fuzzer in "${FUZZERS[@]}"; do
+ if [ -f "$FUZZ_DIR/${fuzzer}.cc" ]; then
+ build_fuzzer "$fuzzer" "$FUZZ_DIR/${fuzzer}.cc"
+ fi
+done
+
+# Copy dictionaries and options
+cp "$FUZZ_DIR"/*.dict "$OUT/" 2>/dev/null || true
+cp "$FUZZ_DIR"/*.options "$OUT/" 2>/dev/null || true
+
+# Build seed corpora
+echo "Building seed corpora..."
+
+# Main fuzzer corpus (existing)
+cp "$FUZZ_DIR/corpus.zip" "$OUT/libarchive_fuzzer_seed_corpus.zip"
+
+# Function to create corpus from test files
+create_corpus() {
+ local name=$1
+ local pattern=$2
+ local dir="/tmp/${name}_corpus"
+
+ mkdir -p "$dir"
+ for f in $TEST_DIR/$pattern; do
+ if [ -f "$f" ]; then
+ base=$(basename "$f" .uu)
+ uudecode -o "$dir/$base" "$f" 2>/dev/null || true
+ fi
+ done
+
+ if [ "$(ls -A $dir 2>/dev/null)" ]; then
+ zip -j "$OUT/${name}_seed_corpus.zip" "$dir"/* 2>/dev/null || true
+ echo "Created corpus for $name with $(ls $dir | wc -l) files"
+ fi
+ rm -rf "$dir"
+}
+
+# Create format-specific corpora
+create_corpus "libarchive_tar_fuzzer" "test_compat_*tar*.uu"
+create_corpus "libarchive_zip_fuzzer" "test_*zip*.uu"
+create_corpus "libarchive_7zip_fuzzer" "test_read_format_7zip*.uu"
+create_corpus "libarchive_rar_fuzzer" "test_read_format_rar_*.uu"
+create_corpus "libarchive_rar5_fuzzer" "test_read_format_rar5*.uu"
+create_corpus "libarchive_xar_fuzzer" "test_read_format_xar*.uu"
+create_corpus "libarchive_cab_fuzzer" "test_read_format_cab*.uu"
+create_corpus "libarchive_lha_fuzzer" "test_read_format_lha*.uu"
+create_corpus "libarchive_iso9660_fuzzer" "test_read_format_iso*.uu"
+create_corpus "libarchive_cpio_fuzzer" "test_compat_cpio*.uu"
+create_corpus "libarchive_warc_fuzzer" "test_read_format_warc*.uu"
+create_corpus "libarchive_mtree_fuzzer" "test_read_format_mtree*.uu"
+create_corpus "libarchive_ar_fuzzer" "test_read_format_ar*.uu"
+
+# Filter corpus - use compressed test files
+mkdir -p /tmp/filter_corpus
+for f in $TEST_DIR/*.gz.uu $TEST_DIR/*.bz2.uu $TEST_DIR/*.xz.uu $TEST_DIR/*.lz4.uu $TEST_DIR/*.zst.uu $TEST_DIR/*.Z.uu; do
+ if [ -f "$f" ]; then
+ base=$(basename "$f" .uu)
+ uudecode -o "/tmp/filter_corpus/$base" "$f" 2>/dev/null || true
+ fi
+done
+if [ "$(ls -A /tmp/filter_corpus 2>/dev/null)" ]; then
+ zip -j "$OUT/libarchive_filter_fuzzer_seed_corpus.zip" /tmp/filter_corpus/* 2>/dev/null || true
+fi
+rm -rf /tmp/filter_corpus
+
+# Encryption corpus - encrypted archives
+mkdir -p /tmp/encryption_corpus
+for f in $TEST_DIR/*encrypt*.uu $TEST_DIR/*password*.uu; do
+ if [ -f "$f" ]; then
+ base=$(basename "$f" .uu)
+ uudecode -o "/tmp/encryption_corpus/$base" "$f" 2>/dev/null || true
+ fi
+done
+if [ "$(ls -A /tmp/encryption_corpus 2>/dev/null)" ]; then
+ zip -j "$OUT/libarchive_encryption_fuzzer_seed_corpus.zip" /tmp/encryption_corpus/* 2>/dev/null || true
+fi
+rm -rf /tmp/encryption_corpus
+
+echo "Build complete! Built ${#FUZZERS[@]} fuzzers."
diff --git a/cpio/cpio.c b/cpio/cpio.c
index 262db510568b..77eefe809f37 100644
--- a/cpio/cpio.c
+++ b/cpio/cpio.c
@@ -725,7 +725,7 @@ file_to_archive(struct cpio *cpio, const char *srcpath)
if (cpio->uid_override >= 0)
archive_entry_set_uid(entry, cpio->uid_override);
- if (cpio->gname_override != NULL)
+ if (cpio->uname_override != NULL)
archive_entry_set_uname(entry, cpio->uname_override);
if (cpio->gid_override >= 0)
archive_entry_set_gid(entry, cpio->gid_override);
diff --git a/libarchive/archive.h b/libarchive/archive.h
index 0eda822ae6bf..a9d34beb4f5a 100644
--- a/libarchive/archive.h
+++ b/libarchive/archive.h
@@ -34,7 +34,7 @@
* assert that ARCHIVE_VERSION_NUMBER >= 2012108.
*/
/* Note: Compiler will complain if this does not match archive_entry.h! */
-#define ARCHIVE_VERSION_NUMBER 3008004
+#define ARCHIVE_VERSION_NUMBER 3008005
#include <sys/stat.h>
#include <stddef.h> /* for wchar_t */
@@ -177,7 +177,7 @@ __LA_DECL int archive_version_number(void);
/*
* Textual name/version of the library, useful for version displays.
*/
-#define ARCHIVE_VERSION_ONLY_STRING "3.8.4"
+#define ARCHIVE_VERSION_ONLY_STRING "3.8.5"
#define ARCHIVE_VERSION_STRING "libarchive " ARCHIVE_VERSION_ONLY_STRING
__LA_DECL const char * archive_version_string(void);
diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h
index 74466f394c4a..b43435692c27 100644
--- a/libarchive/archive_entry.h
+++ b/libarchive/archive_entry.h
@@ -28,7 +28,7 @@
#define ARCHIVE_ENTRY_H_INCLUDED
/* Note: Compiler will complain if this does not match archive.h! */
-#define ARCHIVE_VERSION_NUMBER 3008004
+#define ARCHIVE_VERSION_NUMBER 3008005
/*
* Note: archive_entry.h is for use outside of libarchive; the
diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c
index 17557a891039..117ca505fadd 100644
--- a/libarchive/archive_read_disk_windows.c
+++ b/libarchive/archive_read_disk_windows.c
@@ -1468,7 +1468,7 @@ update_current_filesystem(struct archive_read_disk *a, int64_t dev)
return (ARCHIVE_FATAL);
}
t->filesystem_table = (struct filesystem *)p;
- t->allocated_filesystem = (int)s;
+ t->allocated_filesystem = s;
}
t->current_filesystem_id = fid;
t->current_filesystem = &(t->filesystem_table[fid]);
diff --git a/libarchive/archive_read_support_filter_uu.c b/libarchive/archive_read_support_filter_uu.c
index d722fe343657..acb8feb79b2d 100644
--- a/libarchive/archive_read_support_filter_uu.c
+++ b/libarchive/archive_read_support_filter_uu.c
@@ -232,8 +232,8 @@ bid_get_line(struct archive_read_filter *filter,
size_t nbytes_req = (*ravail+1023) & ~1023U;
ssize_t tested;
- /* Increase reading bytes if it is not enough to at least
- * new two lines. */
+ /* Increase reading bytes if it is not enough for at least
+ * two new lines. */
if (nbytes_req < (size_t)*ravail + 160)
nbytes_req <<= 1;
@@ -411,7 +411,7 @@ ensure_in_buff_size(struct archive_read_filter *self,
/*
* Calculate a new buffer size for in_buff.
- * Increase its value until it has enough size we need.
+ * Increase its value until it is enough for our needs.
*/
newsize = uudecode->in_allocated;
do {
@@ -494,7 +494,7 @@ read_more:
}
/*
* If there is remaining data which is saved by
- * previous calling, use it first.
+ * a previous call, use it first.
*/
if (ensure_in_buff_size(self, uudecode,
avail_in + uudecode->in_cnt) != ARCHIVE_OK)
diff --git a/libarchive/archive_read_support_format_cab.c b/libarchive/archive_read_support_format_cab.c
index a96f7d313951..63755ef9e579 100644
--- a/libarchive/archive_read_support_format_cab.c
+++ b/libarchive/archive_read_support_format_cab.c
@@ -2813,7 +2813,7 @@ lzx_decode_blocks(struct lzx_stream *strm, int last)
lzx_br_bits(&bre, mt_max_bits));
lzx_br_consume(&bre, mt_bitlen[c]);
}
- if (c > UCHAR_MAX)
+ if ((unsigned int)c > UCHAR_MAX)
break;
/*
* 'c' is exactly literal code.
diff --git a/libarchive/archive_read_support_format_cpio.c b/libarchive/archive_read_support_format_cpio.c
index 74f3549d159e..526096b39f75 100644
--- a/libarchive/archive_read_support_format_cpio.c
+++ b/libarchive/archive_read_support_format_cpio.c
@@ -825,9 +825,9 @@ header_odc(struct archive_read *a, struct cpio *cpio,
}
/*
- * NOTE: if a filename suffix is ".z", it is the file gziped by afio.
- * it would be nice that we can show uncompressed file size and we can
- * uncompressed file contents automatically, unfortunately we have nothing
+ * NOTE: if a filename suffix is ".z", it is a file gzipped by afio.
+ * it would be nice if we could show uncompressed file size and
+ * uncompress file contents automatically, unfortunately we have nothing
* to get a uncompressed file size while reading each header. It means
* we also cannot uncompress file contents under our framework.
*/
diff --git a/libarchive/archive_read_support_format_lha.c b/libarchive/archive_read_support_format_lha.c
index abf8b8799636..cf6a147abda6 100644
--- a/libarchive/archive_read_support_format_lha.c
+++ b/libarchive/archive_read_support_format_lha.c
@@ -2374,7 +2374,7 @@ lzh_decode_blocks(struct lzh_stream *strm, int last)
lzh_br_consume(&bre, lt_bitlen[c]);
}
blocks_avail--;
- if (c > UCHAR_MAX)
+ if ((unsigned int)c > UCHAR_MAX)
/* Current block is a match data. */
break;
/*
diff --git a/libarchive/archive_read_support_format_mtree.c b/libarchive/archive_read_support_format_mtree.c
index 96d2c71f4c4f..10c07b05d965 100644
--- a/libarchive/archive_read_support_format_mtree.c
+++ b/libarchive/archive_read_support_format_mtree.c
@@ -392,8 +392,8 @@ next_line(struct archive_read *a,
if (len >= MAX_LINE_LEN)
return (-1);
- /* Increase reading bytes if it is not enough to at least
- * new two lines. */
+ /* Increase reading bytes if it is not enough for at least
+ * two new lines. */
if (nbytes_req < (size_t)*ravail + 160)
nbytes_req <<= 1;
@@ -568,8 +568,8 @@ bid_keyword_list(const char *p, ssize_t len, int unset, int last_is_path)
--len;
value = 1;
}
- /* A keyword should have a its value unless
- * "/unset" operation. */
+ /* A keyword should have a value unless this is
+ * an "/unset" operation. */
if (!unset && value == 0)
return (-1);
}
@@ -752,7 +752,7 @@ detect_form(struct archive_read *a, int *is_form_d)
} else if (form_D == 1) {
if (!last_is_path && keywords > 0)
/* This this is not `form D'
- * and We cannot accept mixed
+ * and we cannot accept mixed
* format. */
break;
}
@@ -805,7 +805,7 @@ detect_form(struct archive_read *a, int *is_form_d)
* to read the entire mtree file into memory up front.
*
* The parsing is done in two steps. First, it is decided if a line
- * changes the global defaults and if it is, processed accordingly.
+ * changes the global defaults and if it does, it is processed accordingly.
* Otherwise, the options of the line are merged with the current
* global options.
*/
diff --git a/libarchive/archive_string.c b/libarchive/archive_string.c
index 740308b6e4e3..a776dc85c688 100644
--- a/libarchive/archive_string.c
+++ b/libarchive/archive_string.c
@@ -3573,7 +3573,7 @@ win_strncat_from_utf16(struct archive_string *as, const void *_p, size_t bytes,
if (sc->to_cp == CP_C_LOCALE) {
/*
- * "C" locale special process.
+ * "C" locale special processing.
*/
u16 = _p;
ll = 0;
@@ -3690,7 +3690,7 @@ win_strncat_to_utf16(struct archive_string *as16, const void *_p,
avail = as16->buffer_length - 2;
if (sc->from_cp == CP_C_LOCALE) {
/*
- * "C" locale special process.
+ * "C" locale special processing.
*/
count = 0;
while (count < length && *s) {
diff --git a/libarchive/archive_util.c b/libarchive/archive_util.c
index d048bbc94650..0d1de1ef2091 100644
--- a/libarchive/archive_util.c
+++ b/libarchive/archive_util.c
@@ -456,7 +456,7 @@ __archive_issetugid(void)
return (-1);
if (ruid != euid || ruid != suid)
return (1);
- if (getresgid(&ruid, &egid, &sgid) != 0)
+ if (getresgid(&rgid, &egid, &sgid) != 0)
return (-1);
if (rgid != egid || rgid != sgid)
return (1);
diff --git a/libarchive/archive_windows.c b/libarchive/archive_windows.c
index e55f995c7702..3fbea6c5b6dd 100644
--- a/libarchive/archive_windows.c
+++ b/libarchive/archive_windows.c
@@ -253,7 +253,7 @@ la_CreateFile(const char *path, DWORD dwDesiredAccess, DWORD dwShareMode,
createExParams.dwSize = sizeof(createExParams);
createExParams.dwFileAttributes = dwFlagsAndAttributes & 0xFFFF;
createExParams.dwFileFlags = dwFlagsAndAttributes & 0xFFF00000;
- createExParams.dwSecurityQosFlags = dwFlagsAndAttributes & 0x000F00000;
+ createExParams.dwSecurityQosFlags = dwFlagsAndAttributes & 0x000F0000;
createExParams.lpSecurityAttributes = lpSecurityAttributes;
createExParams.hTemplateFile = hTemplateFile;
handle = CreateFile2(wpath, dwDesiredAccess, dwShareMode,
@@ -747,7 +747,8 @@ __la_seek_fstat(int fd, la_seek_stat_t *st)
int ret;
ret = __hstat((HANDLE)_get_osfhandle(fd), &u);
- copy_seek_stat(st, &u);
+ if (ret >= 0)
+ copy_seek_stat(st, &u);
return (ret);
}
diff --git a/libarchive/archive_write_open_fd.c b/libarchive/archive_write_open_fd.c
index ba034ed92f8a..a795552020df 100644
--- a/libarchive/archive_write_open_fd.c
+++ b/libarchive/archive_write_open_fd.c
@@ -135,11 +135,7 @@ file_write(struct archive *a, void *client_data, const void *buff, size_t length
static int
file_free(struct archive *a, void *client_data)
{
- struct write_fd_data *mine = (struct write_fd_data *)client_data;
-
(void)a; /* UNUSED */
- if (mine == NULL)
- return (ARCHIVE_OK);
- free(mine);
+ free(client_data);
return (ARCHIVE_OK);
}
diff --git a/libarchive/archive_write_open_file.c b/libarchive/archive_write_open_file.c
index 0b310f3da83b..6271b368d07f 100644
--- a/libarchive/archive_write_open_file.c
+++ b/libarchive/archive_write_open_file.c
@@ -96,11 +96,7 @@ file_write(struct archive *a, void *client_data, const void *buff, size_t length
static int
file_free(struct archive *a, void *client_data)
{
- struct write_FILE_data *mine = client_data;
-
(void)a; /* UNUSED */
- if (mine == NULL)
- return (ARCHIVE_OK);
- free(mine);
+ free(client_data);
return (ARCHIVE_OK);
}
diff --git a/libarchive/archive_write_open_memory.c b/libarchive/archive_write_open_memory.c
index e31650447279..3b929ac7f60d 100644
--- a/libarchive/archive_write_open_memory.c
+++ b/libarchive/archive_write_open_memory.c
@@ -104,11 +104,7 @@ memory_write(struct archive *a, void *client_data, const void *buff, size_t leng
static int
memory_write_free(struct archive *a, void *client_data)
{
- struct write_memory_data *mine;
(void)a; /* UNUSED */
- mine = client_data;
- if (mine == NULL)
- return (ARCHIVE_OK);
- free(mine);
+ free(client_data);
return (ARCHIVE_OK);
}
diff --git a/libarchive/archive_write_set_format_shar.c b/libarchive/archive_write_set_format_shar.c
index be9f78ce96cd..f6f28debdfa9 100644
--- a/libarchive/archive_write_set_format_shar.c
+++ b/libarchive/archive_write_set_format_shar.c
@@ -144,7 +144,9 @@ archive_write_set_format_shar_dump(struct archive *_a)
struct archive_write *a = (struct archive_write *)_a;
struct shar *shar;
- archive_write_set_format_shar(&a->archive);
+ int ret = archive_write_set_format_shar(&a->archive);
+ if (ret != ARCHIVE_OK)
+ return ret;
shar = (struct shar *)a->format_data;
shar->dump = 1;
a->format_write_data = archive_write_shar_data_uuencode;
@@ -240,6 +242,7 @@ archive_write_shar_header(struct archive_write *a, struct archive_entry *entry)
shar_quote(&shar->work, p, 1);
archive_strcat(&shar->work,
" > /dev/null 2>&1\n");
+ free(shar->last_dir);
shar->last_dir = p;
}
} else {
diff --git a/libarchive/archive_write_set_format_ustar.c b/libarchive/archive_write_set_format_ustar.c
index 09b71fe6672a..4084eb455968 100644
--- a/libarchive/archive_write_set_format_ustar.c
+++ b/libarchive/archive_write_set_format_ustar.c
@@ -539,7 +539,7 @@ __archive_write_format_header_ustar(struct archive_write *a, char h[512],
ret = ARCHIVE_WARN;
}
if (copy_length > 0) {
- if (strlen(p) > USTAR_gname_size) {
+ if (copy_length > USTAR_gname_size) {
if (tartype != 'x') {
archive_set_error(&a->archive,
ARCHIVE_ERRNO_MISC, "Group name too long");
diff --git a/libarchive/test/test_compat_lzip.c b/libarchive/test/test_compat_lzip.c
index 50920eefb1cf..cd246b943b22 100644
--- a/libarchive/test/test_compat_lzip.c
+++ b/libarchive/test/test_compat_lzip.c
@@ -47,7 +47,7 @@ echo "f3" > $dir/d1/f3
rm -r $dir
}
#
-# Make a lzip file from split tar file.
+# Make a lzip file from the split tar file.
#
name=test_compat_lzip_1
dir="$name`date +%Y%m%d%H%M%S`.$USER"
@@ -75,7 +75,7 @@ exit 0
*/
/*
- * Verify our ability to read sample files compatibly with lzip.
+ * Verify our ability to read the sample files compatibly with lzip.
*
* In particular:
* * lzip will read multiple lzip streams, concatenating the output
diff --git a/libarchive_fe/line_reader.c b/libarchive_fe/line_reader.c
index 0af9db53c0a2..a4bc84b7f233 100644
--- a/libarchive_fe/line_reader.c
+++ b/libarchive_fe/line_reader.c
@@ -64,6 +64,8 @@ lafe_line_reader(const char *pathname, int nullSeparator)
lr->nullSeparator = nullSeparator;
lr->pathname = strdup(pathname);
+ if (lr->pathname == NULL)
+ lafe_errc(1, ENOMEM, "Can't open %s", pathname);
if (strcmp(pathname, "-") == 0)
lr->f = stdin;
diff --git a/tar/subst.c b/tar/subst.c
index a466f65358a5..53497ad0d1a3 100644
--- a/tar/subst.c
+++ b/tar/subst.c
@@ -237,7 +237,7 @@ apply_substitution(struct bsdtar *bsdtar, const char *name, char **result,
char isEnd = 0;
do {
- isEnd = *name == '\0';
+ isEnd = *name == '\0';
if (regexec(&rule->re, name, 10, matches, 0))
break;
@@ -293,13 +293,13 @@ apply_substitution(struct bsdtar *bsdtar, const char *name, char **result,
realloc_strcat(result, rule->result + j);
if (matches[0].rm_eo > 0) {
- name += matches[0].rm_eo;
- } else {
- // We skip a character because the match is 0-length
- // so we need to add it to the output
- realloc_strncat(result, name, 1);
- name += 1;
- }
+ name += matches[0].rm_eo;
+ } else if (!isEnd) {
+ // We skip a character because the match is 0-length
+ // so we need to add it to the output
+ realloc_strncat(result, name, 1);
+ name += 1;
+ }
} while (rule->global && !isEnd); // Testing one step after because sed et al. run 0-length patterns a last time on the empty string at the end
}
diff --git a/tar/write.c b/tar/write.c
index 9e6c97b580b7..b39a397707ba 100644
--- a/tar/write.c
+++ b/tar/write.c
@@ -163,7 +163,7 @@ set_writer_options(struct bsdtar *bsdtar, struct archive *a)
* a format or filters which are not added to
* the archive write object. */
memcpy(p, IGNORE_WRONG_MODULE_NAME, module_len);
- memcpy(p, writer_options, opt_len);
+ memcpy(p + module_len, writer_options, opt_len);
r = archive_write_set_options(a, p);
free(p);
if (r < ARCHIVE_WARN)
@@ -190,13 +190,12 @@ set_reader_options(struct bsdtar *bsdtar, struct archive *a)
char *p;
/* Set default write options. */
if ((p = malloc(module_len + opt_len)) == NULL)
- if (p == NULL)
lafe_errc(1, errno, "Out of memory");
/* Prepend magic code to ignore options for
* a format or filters which are not added to
* the archive write object. */
memcpy(p, IGNORE_WRONG_MODULE_NAME, module_len);
- memcpy(p, reader_options, opt_len);
+ memcpy(p + module_len, reader_options, opt_len);
r = archive_read_set_options(a, p);
free(p);
if (r < ARCHIVE_WARN)
diff --git a/test_utils/test_main.c b/test_utils/test_main.c
index f31678166ad0..dbd3fcf60e9a 100644
--- a/test_utils/test_main.c
+++ b/test_utils/test_main.c
@@ -3681,11 +3681,19 @@ test_run(int i, const char *tmpdir)
*/
static void
-usage(const char *program)
+list_tests(void)
{
static const int limit = nitems(tests);
int i;
+ for (i = 0; i < limit; i++)
+ printf(" %d: %s\n", i, tests[i].name);
+}
+
+static void
+usage(const char *program)
+{
+
printf("Usage: %s [options] <test> <test> ...\n", program);
printf("Default is to run all tests.\n");
printf("Otherwise, specify the numbers of the tests you wish to run.\n");
@@ -3693,6 +3701,8 @@ usage(const char *program)
printf(" -d Dump core after any failure, for debugging.\n");
printf(" -k Keep all temp files.\n");
printf(" Default: temp files for successful tests deleted.\n");
+ printf(" -l List available tests and exit, ignoring all other.\n");
+ printf(" options and arguments.\n");
#ifdef PROGRAM
printf(" -p <path> Path to executable to be tested.\n");
printf(" Default: path taken from " ENVBASE " environment variable.\n");
@@ -3704,8 +3714,7 @@ usage(const char *program)
printf(" -u Keep running specified tests until one fails.\n");
printf(" -v Verbose.\n");
printf("Available tests:\n");
- for (i = 0; i < limit; i++)
- printf(" %d: %s\n", i, tests[i].name);
+ list_tests();
exit(1);
}
@@ -4079,6 +4088,10 @@ main(int argc, char **argv)
case 'k':
keep_temp_files = 1;
break;
+ case 'l':
+ list_tests();
+ exit(0);
+ break;
case 'p':
#ifdef PROGRAM
testprogfile = option_arg;
diff --git a/unzip/bsdunzip.c b/unzip/bsdunzip.c
index 1b520e841690..14bd418f169c 100644
--- a/unzip/bsdunzip.c
+++ b/unzip/bsdunzip.c
@@ -654,11 +654,11 @@ recheck:
#elif HAVE_STRUCT_STAT_ST_MTIME_N
sb.st_mtime > mtime.tv_sec ||
(sb.st_mtime == mtime.tv_sec &&
- sb.st_mtime_n => mtime.tv_nsec)
+ sb.st_mtime_n >= mtime.tv_nsec)
#elif HAVE_STRUCT_STAT_ST_MTIME_USEC
sb.st_mtime > mtime.tv_sec ||
(sb.st_mtime == mtime.tv_sec &&
- sb.st_mtime_usec => mtime.tv_nsec / 1000)
+ sb.st_mtime_usec >= mtime.tv_nsec / 1000)
#else
sb.st_mtime > mtime.tv_sec
#endif
diff --git a/unzip/la_queue.h b/unzip/la_queue.h
index 917526531b2a..bb305f5bd8ce 100644
--- a/unzip/la_queue.h
+++ b/unzip/la_queue.h
@@ -85,7 +85,7 @@
* _SWAP + + + +
*/
#ifdef QUEUE_MACRO_DEBUG
-#warn Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
+#warning Use QUEUE_MACRO_DEBUG_TRACE and/or QUEUE_MACRO_DEBUG_TRASH
#define QUEUE_MACRO_DEBUG_TRACE
#define QUEUE_MACRO_DEBUG_TRASH
#endif