From 01333e8c4dd7b5e2bb90cc773332613cf085ccf4 Mon Sep 17 00:00:00 2001 From: Martin Matuska Date: Mon, 5 Jan 2026 21:08:25 +0100 Subject: [PATCH] Update vendor/libarchive to 3.8.5 Important bugfixes: #2809 bsdtar: fix regression from 3.8.4 zero-length pattern issue bugfix Obtained from: libarchive Vendor commit: dd897a78c662a2c7a003e7ec158cea7909557bee --- .cirrus.yml | 20 ++- .github/workflows/ci.yml | 16 +- .github/workflows/cifuzz.yml | 2 +- .github/workflows/codeql.yml | 8 +- .github/workflows/scorecard.yml | 6 +- CMakeLists.txt | 16 +- NEWS | 2 + README.md | 4 +- build/version | 2 +- configure.ac | 4 +- contrib/android/Android.mk | 20 +++ contrib/android/config/android.h | 10 +- contrib/oss-fuzz/fuzz_helpers.h | 156 ++++++++++++++++++ contrib/oss-fuzz/libarchive_7zip_fuzzer.cc | 63 +++++++ contrib/oss-fuzz/libarchive_7zip_fuzzer.dict | 47 ++++++ .../oss-fuzz/libarchive_7zip_fuzzer.options | 10 ++ contrib/oss-fuzz/libarchive_ar_fuzzer.cc | 54 ++++++ contrib/oss-fuzz/libarchive_ar_fuzzer.dict | 20 +++ contrib/oss-fuzz/libarchive_cab_fuzzer.cc | 51 ++++++ contrib/oss-fuzz/libarchive_cab_fuzzer.dict | 23 +++ contrib/oss-fuzz/libarchive_cpio_fuzzer.cc | 58 +++++++ contrib/oss-fuzz/libarchive_cpio_fuzzer.dict | 25 +++ .../oss-fuzz/libarchive_encryption_fuzzer.cc | 101 ++++++++++++ .../libarchive_encryption_fuzzer.options | 10 ++ contrib/oss-fuzz/libarchive_entry_fuzzer.cc | 105 ++++++++++++ contrib/oss-fuzz/libarchive_filter_fuzzer.cc | 65 ++++++++ .../oss-fuzz/libarchive_filter_fuzzer.dict | 33 ++++ .../oss-fuzz/libarchive_filter_fuzzer.options | 10 ++ contrib/oss-fuzz/libarchive_fuzzer.cc | 17 +- contrib/oss-fuzz/libarchive_fuzzer.dict | 76 +++++++++ contrib/oss-fuzz/libarchive_fuzzer.options | 9 + contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc | 58 +++++++ .../oss-fuzz/libarchive_iso9660_fuzzer.dict | 36 ++++ .../libarchive_iso9660_fuzzer.options | 10 ++ contrib/oss-fuzz/libarchive_lha_fuzzer.cc | 54 ++++++ contrib/oss-fuzz/libarchive_lha_fuzzer.dict | 26 +++ contrib/oss-fuzz/libarchive_linkify_fuzzer.cc | 110 ++++++++++++ contrib/oss-fuzz/libarchive_match_fuzzer.cc | 96 +++++++++++ contrib/oss-fuzz/libarchive_mtree_fuzzer.cc | 61 +++++++ contrib/oss-fuzz/libarchive_mtree_fuzzer.dict | 47 ++++++ contrib/oss-fuzz/libarchive_rar5_fuzzer.cc | 61 +++++++ contrib/oss-fuzz/libarchive_rar5_fuzzer.dict | 37 +++++ contrib/oss-fuzz/libarchive_rar_fuzzer.cc | 56 +++++++ .../oss-fuzz/libarchive_read_disk_fuzzer.cc | 76 +++++++++ .../oss-fuzz/libarchive_roundtrip_fuzzer.cc | 110 ++++++++++++ .../libarchive_roundtrip_fuzzer.options | 3 + contrib/oss-fuzz/libarchive_seek_fuzzer.cc | 125 ++++++++++++++ .../oss-fuzz/libarchive_seek_fuzzer.options | 3 + contrib/oss-fuzz/libarchive_string_fuzzer.cc | 144 ++++++++++++++++ contrib/oss-fuzz/libarchive_tar_fuzzer.cc | 86 ++++++++++ contrib/oss-fuzz/libarchive_tar_fuzzer.dict | 51 ++++++ contrib/oss-fuzz/libarchive_warc_fuzzer.cc | 50 ++++++ contrib/oss-fuzz/libarchive_warc_fuzzer.dict | 34 ++++ .../oss-fuzz/libarchive_write_disk_fuzzer.cc | 125 ++++++++++++++ .../libarchive_write_disk_fuzzer.options | 3 + contrib/oss-fuzz/libarchive_write_fuzzer.cc | 132 +++++++++++++++ contrib/oss-fuzz/libarchive_xar_fuzzer.cc | 60 +++++++ contrib/oss-fuzz/libarchive_xar_fuzzer.dict | 44 +++++ .../oss-fuzz/libarchive_xar_fuzzer.options | 10 ++ contrib/oss-fuzz/libarchive_zip_fuzzer.cc | 68 ++++++++ contrib/oss-fuzz/libarchive_zip_fuzzer.dict | 43 +++++ contrib/oss-fuzz/oss-fuzz-build.sh | 137 +++++++++++++-- cpio/cpio.c | 2 +- libarchive/archive.h | 4 +- libarchive/archive_entry.h | 2 +- libarchive/archive_read_disk_windows.c | 2 +- libarchive/archive_read_support_filter_uu.c | 8 +- libarchive/archive_read_support_format_cab.c | 2 +- libarchive/archive_read_support_format_cpio.c | 6 +- libarchive/archive_read_support_format_lha.c | 2 +- .../archive_read_support_format_mtree.c | 12 +- libarchive/archive_string.c | 4 +- libarchive/archive_util.c | 2 +- libarchive/archive_windows.c | 5 +- libarchive/archive_write_open_fd.c | 6 +- libarchive/archive_write_open_file.c | 6 +- libarchive/archive_write_open_memory.c | 6 +- libarchive/archive_write_set_format_shar.c | 5 +- libarchive/archive_write_set_format_ustar.c | 2 +- libarchive/test/test_compat_lzip.c | 4 +- libarchive_fe/line_reader.c | 2 + tar/subst.c | 16 +- tar/write.c | 5 +- test_utils/test_main.c | 19 ++- unzip/bsdunzip.c | 4 +- unzip/la_queue.h | 2 +- 86 files changed, 3005 insertions(+), 122 deletions(-) create mode 100644 contrib/oss-fuzz/fuzz_helpers.h create mode 100644 contrib/oss-fuzz/libarchive_7zip_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_7zip_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_7zip_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_ar_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_ar_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_cab_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_cab_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_cpio_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_cpio_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_encryption_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_encryption_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_entry_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_filter_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_filter_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_filter_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_iso9660_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_iso9660_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_iso9660_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_lha_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_lha_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_linkify_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_match_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_mtree_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_mtree_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_rar5_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_rar5_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_rar_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_read_disk_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_roundtrip_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_roundtrip_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_seek_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_seek_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_string_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_tar_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_tar_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_warc_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_warc_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_write_disk_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_write_disk_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_write_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_xar_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_xar_fuzzer.dict create mode 100644 contrib/oss-fuzz/libarchive_xar_fuzzer.options create mode 100644 contrib/oss-fuzz/libarchive_zip_fuzzer.cc create mode 100644 contrib/oss-fuzz/libarchive_zip_fuzzer.dict 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 #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 +#include +#include +#include +#include + +#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(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(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(data_[pos_]) | + (static_cast(data_[pos_ + 1]) << 8); + pos_ += 2; + } + return val; + } + + uint32_t consume_u32() { + uint32_t val = 0; + if (pos_ + 4 <= size_) { + val = static_cast(data_[pos_]) | + (static_cast(data_[pos_ + 1]) << 8) | + (static_cast(data_[pos_ + 2]) << 16) | + (static_cast(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(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(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 +#include +#include + +#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 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 +#include +#include + +#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 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 +"!\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 +#include +#include + +#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 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 +#include +#include + +#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 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 +#include +#include +#include + +#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 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 +#include +#include +#include + +#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 +#include +#include + +#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 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 #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(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 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 +"!\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 +#include +#include + +#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 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 +#include +#include + +#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 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 +#include +#include +#include + +#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 +#include +#include + +#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 +#include +#include + +#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 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 +#include +#include + +#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 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 +#include +#include + +#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 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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 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 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 +#include +#include +#include + +#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(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(client_data); + la_int64_t new_pos; + + switch (whence) { + case SEEK_SET: + new_pos = offset; + break; + case SEEK_CUR: + new_pos = static_cast(buffer->pos) + offset; + break; + case SEEK_END: + new_pos = static_cast(buffer->len) + offset; + break; + default: + return ARCHIVE_FATAL; + } + + if (new_pos < 0) new_pos = 0; + if (new_pos > static_cast(buffer->len)) + new_pos = static_cast(buffer->len); + + buffer->pos = static_cast(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(client_data); + size_t avail = buffer->len - buffer->pos; + la_int64_t to_skip = (request > static_cast(avail)) + ? static_cast(avail) + : request; + buffer->pos += static_cast(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 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 +#include +#include +#include +#include + +#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(buf + 1); + size_t str_len = len - 1; + + // Ensure null termination for string operations + char *safe_str = static_cast(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(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 +#include +#include + +#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 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 +#include +#include + +#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 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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 *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(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 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 +#include +#include + +#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 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 +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" +"" + +# 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 +#include +#include + +#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 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 +FUZZ_DIR=$SRC/libarchive/contrib/oss-fuzz +TEST_DIR=$SRC/libarchive/libarchive/test -# 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 +# 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 #include /* 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] ...\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 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