diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18fdd018..2aae0784 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,8 @@ env: DEBIAN_FRONTEND: noninteractive UPX_CMAKE_BUILD_FLAGS: --verbose UPX_CMAKE_CONFIG_FLAGS: -Wdev --warn-uninitialized - # 2024-01-24 - ZIG_DIST_VERSION: 0.12.0-dev.2334+aef1da163 + # 2024-01-25 + ZIG_DIST_VERSION: 0.12.0-dev.2341+92211135f jobs: job-rebuild-and-verify-stubs: @@ -78,7 +78,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install -y g++-multilib g++-mingw-w64-i686 g++-mingw-w64-x86-64 + sudo apt-get install -y g++-multilib g++-mingw-w64-i686 g++-mingw-w64-x86-64 valgrind # make sure that we use posix-threads (pthread/winpthreads) and NOT win32-threads for f in i686-w64-mingw32-g++ i686-w64-mingw32-gcc x86_64-w64-mingw32-g++ x86_64-w64-mingw32-gcc; do if test -f /usr/bin/$f-posix; then sudo update-alternatives --set $f /usr/bin/$f-posix; fi @@ -123,8 +123,8 @@ jobs: path: tmp/artifact - name: 'Run install tests' run: | - (cd build/extra/gcc/release && DESTDIR=$PWD/Install-with-cmake cmake --install .) - (cd build/extra/gcc/release && DESTDIR=$PWD/Install-with-make make install) + (cd build/extra/gcc/release && DESTDIR="$PWD/Install with cmake" cmake --install .) + (cd build/extra/gcc/release && DESTDIR="$PWD/Install with make" make install) - name: 'Run ctest tests' run: | make -C build/extra/gcc/debug test @@ -147,8 +147,29 @@ jobs: run: | env -C build/extra/gcc-m32/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh env -C build/extra/gcc-m32/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + - name: 'Mimic ctest tests with Valgrind' + if: false # TODO later: valgrind problem/bug + run: | + if command -v valgrind >/dev/null; then + export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet" + env -C build/extra/gcc/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/extra/gcc/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/extra/clang/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/extra/clang/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + fi + - name: 'Mimic ctest tests 32-bit with Valgrind' + if: ${{ matrix.use_m32 && false }} # TODO later: valgrind problem/bug + run: | + if command -v valgrind >/dev/null; then + export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet" + env -C build/extra/gcc-m32/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/extra/gcc-m32/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + fi - name: 'Run file system test suite' run: | + if command -v valgrind >/dev/null; then + export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet" + fi env -C build/extra/gcc/release bash "$PWD"/misc/testsuite/test_symlinks.sh - name: 'Run test suite build/extra/gcc/release' run: | @@ -220,8 +241,8 @@ jobs: path: tmp/artifact - name: 'Run install tests' run: | - (cd build/extra/clang/release && DESTDIR=$PWD/Install-with-cmake cmake --install .) - (cd build/extra/clang/release && DESTDIR=$PWD/Install-with-make make install) + (cd build/extra/clang/release && DESTDIR="$PWD/Install with cmake" cmake --install .) + (cd build/extra/clang/release && DESTDIR="$PWD/Install with make" make install) - name: 'Run ctest tests' if: ${{ !contains(matrix.os, 'macos-13') }} # FIXME: UPX on macos-13 is broken => disable self-test for now run: | @@ -345,7 +366,7 @@ jobs: set RUN_CL=cl ${{ matrix.cl_machine_flags }} -MT set RUN_LIB=link -lib ${{ matrix.link_machine_flags }} @rem UPX only uses the very basic Windows API - set DEFS=-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0400 + set DEFS=-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0501 set BDIR=%H%\build\%C%\%B% git rev-parse --short=12 HEAD > %BDIR%\upx\.GITREV.txt @REM ===== build bzip2 ===== @@ -415,31 +436,31 @@ jobs: needs: [ job-rebuild-and-verify-stubs ] name: ${{ format('zigcc {0} {1}', matrix.zig_target, matrix.zig_pic) }} runs-on: ubuntu-latest - container: 'alpine:3.19' # older versions such as alpine:3.12 also work; no-container also works + container: 'alpine:3.19' strategy: fail-fast: false matrix: include: # only build a few targets => more targets are tested in the Weekly CI - - { zig_target: aarch64-linux-musl } - # { zig_target: aarch64-linux-musl, zig_pic: -fPIE } + - { zig_target: aarch64-linux-musl, qemu: qemu-aarch64 } + # { zig_target: aarch64-linux-musl, qemu: qemu-aarch64, zig_pic: -fPIE } # { zig_target: aarch64-macos-none } - { zig_target: aarch64-macos.11.0-none } # { zig_target: aarch64-macos.12.0-none } # { zig_target: aarch64-macos.13.0-none } - { zig_target: aarch64-windows-gnu } - - { zig_target: arm-linux-musleabihf } - # { zig_target: arm-linux-musleabihf, zig_pic: -fPIE } - - { zig_target: i386-linux-musl } - # { zig_target: i386-linux-musl, zig_pic: -fPIE } + - { zig_target: arm-linux-musleabihf, qemu: qemu-arm } + # { zig_target: arm-linux-musleabihf, qemu: qemu-arm, zig_pic: -fPIE } + - { zig_target: i386-linux-musl, qemu: qemu-i386 } + # { zig_target: i386-linux-musl, qemu: qemu-i386, zig_pic: -fPIE } - { zig_target: i386-windows-gnu } - # { zig_target: mips-linux-musl } - # { zig_target: mipsel-linux-musl } - # { zig_target: powerpc-linux-musl } - # { zig_target: powerpc64-linux-musl } - # { zig_target: powerpc64le-linux-musl } - - { zig_target: x86_64-linux-musl } - # { zig_target: x86_64-linux-musl, zig_pic: -fPIE } + - { zig_target: mips-linux-musl } + - { zig_target: mipsel-linux-musl } + - { zig_target: powerpc-linux-musl, qemu: qemu-ppc } + - { zig_target: powerpc64-linux-musl, qemu: qemu-ppc64 } + - { zig_target: powerpc64le-linux-musl, qemu: qemu-ppc64le } + - { zig_target: x86_64-linux-musl, qemu: qemu-x86_64 } + # { zig_target: x86_64-linux-musl, qemu: qemu-x86_64, zig_pic: -fPIE } # { zig_target: x86_64-macos-none } - { zig_target: x86_64-macos.11.0-none } # { zig_target: x86_64-macos.12.0-none } @@ -523,5 +544,31 @@ jobs: - name: 'Run install tests' if: ${{ contains(matrix.zig_target, '-linux') }} run: | - (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR=$PWD/Install-with-cmake cmake --install .) - (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR=$PWD/Install-with-make make install) + (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR="$PWD/Install with cmake" cmake --install .) + (cd build/zig/${ZIG_TARGET}${ZIG_PIC}/release && DESTDIR="$PWD/Install with make" make install) + - name: 'Run ctest tests' + if: ${{ matrix.zig_target == 'i386-linux-musl' || matrix.zig_target == 'x86_64-linux-musl' }} + run: | + make -C build/zig/${ZIG_TARGET}${ZIG_PIC}/debug test + make -C build/zig/${ZIG_TARGET}${ZIG_PIC}/release test + - name: 'Mimic ctest tests' + if: ${{ matrix.zig_target == 'i386-linux-musl' || matrix.zig_target == 'x86_64-linux-musl' }} + run: | + apk add coreutils + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + - name: ${{ format('Mimic ctest tests with QEMU {0}', matrix.qemu) }} + if: ${{ matrix.qemu }} + run: | + qemu="${{ matrix.qemu }}" + apk add coreutils $qemu + export upx_exe_runner="$qemu" + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/release bash "$PWD"/misc/testsuite/mimic_ctest.sh + - name: 'Mimic ctest tests with Valgrind' + if: ${{ matrix.zig_target == 'x86_64-linux-musl' && false }} # TODO later: valgrind problem/bug + run: | + apk add coreutils valgrind + export upx_exe_runner="valgrind --leak-check=no --error-exitcode=1 --quiet" + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/debug bash "$PWD"/misc/testsuite/mimic_ctest.sh + env -C build/zig/${ZIG_TARGET}${ZIG_PIC}/release bash "$PWD"/misc/testsuite/mimic_ctest.sh diff --git a/misc/make/Makefile-extra.mk b/misc/make/Makefile-extra.mk index 0da4126a..005f2434 100644 --- a/misc/make/Makefile-extra.mk +++ b/misc/make/Makefile-extra.mk @@ -143,17 +143,19 @@ build/extra/cross-linux-gnu-arm-eabihf/%: CMAKE_CROSSCOMPILING_EMULATOR ?= qemu- # cross compiler: Windows x86 win32 MinGW (i386) build/extra/cross-windows-mingw32/debug: PHONY; $(call run_config_and_build,$@,Debug) build/extra/cross-windows-mingw32/release: PHONY; $(call run_config_and_build,$@,Release) -build/extra/cross-windows-mingw32/%: export CC = i686-w64-mingw32-gcc -static -D_WIN32_WINNT=0x0400 -build/extra/cross-windows-mingw32/%: export CXX = i686-w64-mingw32-g++ -static -D_WIN32_WINNT=0x0400 +build/extra/cross-windows-mingw32/%: export CC = i686-w64-mingw32-gcc -static -D_WIN32_WINNT=0x0501 +build/extra/cross-windows-mingw32/%: export CXX = i686-w64-mingw32-g++ -static -D_WIN32_WINNT=0x0501 build/extra/cross-windows-mingw32/%: CMAKE_SYSTEM_NAME ?= Windows +build/extra/cross-windows-mingw32/%: CMAKE_SYSTEM_PROCESSOR ?= X86 build/extra/cross-windows-mingw32/%: CMAKE_CROSSCOMPILING_EMULATOR ?= wine # cross compiler: Windows x64 win64 MinGW (amd64) build/extra/cross-windows-mingw64/debug: PHONY; $(call run_config_and_build,$@,Debug) build/extra/cross-windows-mingw64/release: PHONY; $(call run_config_and_build,$@,Release) -build/extra/cross-windows-mingw64/%: export CC = x86_64-w64-mingw32-gcc -static -D_WIN32_WINNT=0x0400 -build/extra/cross-windows-mingw64/%: export CXX = x86_64-w64-mingw32-g++ -static -D_WIN32_WINNT=0x0400 +build/extra/cross-windows-mingw64/%: export CC = x86_64-w64-mingw32-gcc -static -D_WIN32_WINNT=0x0501 +build/extra/cross-windows-mingw64/%: export CXX = x86_64-w64-mingw32-g++ -static -D_WIN32_WINNT=0x0501 build/extra/cross-windows-mingw64/%: CMAKE_SYSTEM_NAME ?= Windows +build/extra/cross-windows-mingw64/%: CMAKE_SYSTEM_PROCESSOR ?= AMD64 build/extra/cross-windows-mingw64/%: CMAKE_CROSSCOMPILING_EMULATOR ?= wine64 # cross compiler: macOS arm64 (aarch64) @@ -162,6 +164,7 @@ build/extra/cross-darwin-arm64/release: PHONY; $(call run_config_and_build,$@,Re build/extra/cross-darwin-arm64/%: export CC = clang -target arm64-apple-darwin build/extra/cross-darwin-arm64/%: export CXX = clang++ -target arm64-apple-darwin build/extra/cross-darwin-arm64/%: CMAKE_SYSTEM_NAME ?= Darwin +build/extra/cross-darwin-arm64/%: CMAKE_SYSTEM_PROCESSOR ?= arm64 # cross compiler: macOS x86_64 (amd64) build/extra/cross-darwin-x86_64/debug: PHONY; $(call run_config_and_build,$@,Debug) @@ -169,6 +172,7 @@ build/extra/cross-darwin-x86_64/release: PHONY; $(call run_config_and_build,$@,R build/extra/cross-darwin-x86_64/%: export CC = clang -target x86_64-apple-darwin build/extra/cross-darwin-x86_64/%: export CXX = clang++ -target x86_64-apple-darwin build/extra/cross-darwin-x86_64/%: CMAKE_SYSTEM_NAME ?= Darwin +build/extra/cross-darwin-x86_64/%: CMAKE_SYSTEM_PROCESSOR ?= x86_64 #*********************************************************************** # C/C++ static analyzers diff --git a/src/check/dt_check.cpp b/src/check/dt_check.cpp index 174b042b..4f57eba5 100644 --- a/src/check/dt_check.cpp +++ b/src/check/dt_check.cpp @@ -603,7 +603,7 @@ TEST_CASE("ptr_invalidate_and_poison") { ptr_invalidate_and_poison(ip); assert(ip != nullptr); (void) ip; - double *dp; + double *dp; // not initialized ptr_invalidate_and_poison(dp); assert(dp != nullptr); (void) dp; diff --git a/src/check/dt_cxxlib.cpp b/src/check/dt_cxxlib.cpp index 775ebc6d..463c1788 100644 --- a/src/check/dt_cxxlib.cpp +++ b/src/check/dt_cxxlib.cpp @@ -27,6 +27,22 @@ // lots of tests (and probably quite a number of redundant tests) // modern compilers will optimize away much of this code +// libc++ hardenining +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ + 0 >= 18) +#if DEBUG +#define _LIBCPP_HARDENING_MODE _LIBCPP_HARDENING_MODE_DEBUG +#else +#define _LIBCPP_HARDENING_MODE _LIBCPP_HARDENING_MODE_EXTENSIVE +#endif +#endif +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ + 0 < 18) +#if DEBUG +#define _LIBCPP_ENABLE_ASSERTIONS 1 +#endif +#endif + +#include "../headers.h" +#include #include "../conf.h" /************************************************************************* @@ -103,6 +119,23 @@ ACC_COMPILE_TIME_ASSERT_HEADER(!compile_time::string_gt("abc", "abz")) ACC_COMPILE_TIME_ASSERT_HEADER(!compile_time::string_ge("abc", "abz")) ACC_COMPILE_TIME_ASSERT_HEADER(compile_time::string_le("abc", "abz")) +/************************************************************************* +// +**************************************************************************/ + +TEST_CASE("libc++") { + constexpr size_t N = 16; + std::vector v(N); + CHECK(v.end() - v.begin() == N); + CHECK(&v[0] == &(*(v.begin()))); + // CHECK(&v[0] + N == &(*(v.end()))); // TODO later: is this legal?? +#if defined(_LIBCPP_HARDENING_MODE_DEBUG) && \ + (_LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG) + CHECK_THROWS((void) &v[N]); +#endif + UNUSED(v); +} + /************************************************************************* // UPX_CXX_DISABLE_xxx **************************************************************************/ diff --git a/src/check/dt_impl.cpp b/src/check/dt_impl.cpp index b2e127dd..f16b45c8 100644 --- a/src/check/dt_impl.cpp +++ b/src/check/dt_impl.cpp @@ -28,6 +28,20 @@ // doctest support code implementation **************************************************************************/ +// libc++ hardenining +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ + 0 >= 18) +#if DEBUG +#define _LIBCPP_HARDENING_MODE _LIBCPP_HARDENING_MODE_DEBUG +#else +#define _LIBCPP_HARDENING_MODE _LIBCPP_HARDENING_MODE_EXTENSIVE +#endif +#endif +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ + 0 < 18) +#if DEBUG +#define _LIBCPP_ENABLE_ASSERTIONS 1 +#endif +#endif + #if defined(__has_include) #if __has_include() #include // for __GLIBC__ diff --git a/src/check/dt_xspan.cpp b/src/check/dt_xspan.cpp index fbc4ed16..998ff0cb 100644 --- a/src/check/dt_xspan.cpp +++ b/src/check/dt_xspan.cpp @@ -241,6 +241,33 @@ TEST_CASE("basic xspan usage") { } } +TEST_CASE("xspan array access") { + constexpr size_t N = 16; + char buf[N]; + memset(buf, 0, sizeof(buf)); + XSPAN_0_VAR(char, c0, buf, sizeof(buf)); + XSPAN_P_VAR(char, cp, buf, sizeof(buf)); + XSPAN_S_VAR(char, cs, buf, sizeof(buf)); + for (size_t i = 0; i != N; ++i) + c0[i] += 1; + for (size_t i = 0; i != N; ++i) + cp[i] += 1; + for (size_t i = 0; i != N; ++i) + cs[i] += 1; + for (auto ptr = c0; ptr != c0 + N; ++ptr) + *ptr += 1; + for (auto ptr = c0 + 0; ptr < c0 + N; ++ptr) + *ptr += 1; + for (auto ptr = cp; ptr != cp + N; ++ptr) + *ptr += 1; + for (auto ptr = cp + 0; ptr < cp + N; ++ptr) + *ptr += 1; + for (auto ptr = cs; ptr != cs + N; ++ptr) + *ptr += 1; + for (auto ptr = cs + 0; ptr < cs + N; ++ptr) + *ptr += 1; +} + /************************************************************************* // **************************************************************************/ diff --git a/src/conf.h b/src/conf.h index 415c412d..a178be84 100644 --- a/src/conf.h +++ b/src/conf.h @@ -209,6 +209,8 @@ typedef upx_int64_t upx_off_t; #undef dos #undef large #undef linux +#undef PAGE_MASK +#undef PAGE_SIZE #undef small #undef SP #undef SS @@ -297,9 +299,6 @@ typedef upx_int64_t upx_off_t; #define index upx_renamed_index #define outp upx_renamed_outp -#undef PAGE_MASK -#undef PAGE_SIZE - #if !defined(O_BINARY) || (O_BINARY + 0 == 0) #if (ACC_OS_CYGWIN || ACC_OS_DOS16 || ACC_OS_DOS32 || ACC_OS_EMX || ACC_OS_OS2 || ACC_OS_OS216 || \ ACC_OS_WIN16 || ACC_OS_WIN32 || ACC_OS_WIN64) diff --git a/src/except.cpp b/src/except.cpp index f24ade25..ba04d749 100644 --- a/src/except.cpp +++ b/src/except.cpp @@ -31,7 +31,7 @@ // **************************************************************************/ -/*static*/ upx_std_atomic(size_t) Throwable::debug_counter; +/*static*/ Throwable::Stats Throwable::stats; Throwable::Throwable(const char *m, int e, bool w) noexcept : super(), msg(nullptr), @@ -41,10 +41,10 @@ Throwable::Throwable(const char *m, int e, bool w) noexcept : super(), msg = strdup(m); assert_noexcept(msg != nullptr); } -#if 0 - fprintf(stderr, "construct exception: %s %zu\n", msg, debug_counter); - debug_counter += 1; -#endif + NO_fprintf(stderr, "construct exception: %zu %zu %s\n", stats.counter_current, + stats.counter_total, (const char *) msg); + stats.counter_current += 1; + stats.counter_total += 1; } Throwable::Throwable(const Throwable &other) noexcept : super(other), @@ -55,17 +55,16 @@ Throwable::Throwable(const Throwable &other) noexcept : super(other), msg = strdup(other.msg); assert_noexcept(msg != nullptr); } -#if 0 - fprintf(stderr, "copy exception: %s %zu\n", msg, debug_counter); - debug_counter += 1; -#endif + NO_fprintf(stderr, "copy construct exception: %zu %zu %s\n", stats.counter_current, + stats.counter_total, (const char *) msg); + stats.counter_current += 1; + stats.counter_total += 1; } Throwable::~Throwable() noexcept { -#if 0 - debug_counter -= 1; - fprintf(stderr, "destruct exception: %s %zu\n", msg, debug_counter); -#endif + stats.counter_current -= 1; + NO_fprintf(stderr, "destruct exception: %zu %zu %s\n", stats.counter_current, + stats.counter_total, (const char *) msg); upx::owner_free(msg); } diff --git a/src/except.h b/src/except.h index a2716169..8e5d391e 100644 --- a/src/except.h +++ b/src/except.h @@ -61,7 +61,12 @@ private: UPX_CXX_DISABLE_ADDRESS(Throwable) private: - static upx_std_atomic(size_t) debug_counter; // for debugging + // static debug stats + struct Stats { + upx_std_atomic(size_t) counter_total; + upx_std_atomic(size_t) counter_current; + }; + static Stats stats; }; // Exceptions can/should be caught diff --git a/src/lefile.cpp b/src/lefile.cpp index c3d0a4c1..756af009 100644 --- a/src/lefile.cpp +++ b/src/lefile.cpp @@ -144,8 +144,8 @@ unsigned LeFile::getImageSize() const { } void LeFile::readImage() { - soimage = pages * mps; - if (!soimage) // late detection, but protect against .alloc(0) + soimage = mem_size(mps, pages); // assert size + if (!soimage) // late detection, but protect against .alloc(0) throwCantPack("no soimage"); mb_iimage.alloc(soimage); mb_iimage.clear(); @@ -157,7 +157,7 @@ void LeFile::readImage() { fif->seek(ih.data_pages_offset + exe_offset + (ipm_entries[ic].m * 0x100 + ipm_entries[ic].l - 1) * mps, SEEK_SET); - auto bytes = ic != pages - 1 ? mps : ih.bytes_on_last_page; + unsigned bytes = ic != pages - 1 ? mps : ih.bytes_on_last_page; fif->readx(iimage + jc, bytes); } jc += mps; @@ -184,7 +184,7 @@ void LeFile::writeNonResidentNames() { } bool LeFile::readFileHeader() { -#define H(x) get_le16(header + 2 * (x)) +#define H(x) get_le16(header + (2 * (x))) byte header[0x40]; le_offset = exe_offset = 0; int ic; @@ -208,7 +208,7 @@ bool LeFile::readFileHeader() { } else if (memcmp(header, "BW", 2) == 0) // used in dos4gw.exe le_offset += H(2) * 512 + H(1); else if (memcmp(header, "LE", 2) == 0) - break; + break; // success else if (memcmp(header, "PMW1", 4) == 0) throwCantPack("already packed with PMWLITE"); else @@ -219,10 +219,11 @@ bool LeFile::readFileHeader() { fif->seek(le_offset, SEEK_SET); fif->readx(&ih, sizeof(ih)); if (mps < 512 || mps > 2097152 || (mps & (mps - 1)) != 0) - throwCantPack("file header invalid page size"); + throwCantPack("LE file header invalid page size %u", (unsigned) mps); if (ih.bytes_on_last_page > mps || pages == 0) - throwCantPack("bad file header"); - (void) mem_size(mps, pages); // assert size + throwCantPack("bad LE file header"); + if (!mem_size_valid(mps, pages) || exe_offset > le_offset) + throwCantPack("bad LE file header"); return true; #undef H } diff --git a/src/lefile.h b/src/lefile.h index 1e00a103..9923d193 100644 --- a/src/lefile.h +++ b/src/lefile.h @@ -223,10 +223,7 @@ protected: private: // disable copy and move - LeFile(const LeFile &) DELETED_FUNCTION; - LeFile &operator=(const LeFile &) DELETED_FUNCTION; - LeFile(LeFile &&) noexcept DELETED_FUNCTION; - LeFile &operator=(LeFile &&) noexcept DELETED_FUNCTION; + UPX_CXX_DISABLE_COPY_MOVE(LeFile) }; /* vim:set ts=4 sw=4 et: */ diff --git a/src/linker.h b/src/linker.h index 9ec64374..2af5756b 100644 --- a/src/linker.h +++ b/src/linker.h @@ -142,7 +142,7 @@ struct ElfLinker::Relocation : private noncopyable { unsigned offset = 0; const char *type = nullptr; const Symbol *value = nullptr; - upx_uint64_t add; // used in .rela relocations + upx_uint64_t add = 0; // used in .rela relocations explicit Relocation(const Section *s, unsigned o, const char *t, const Symbol *v, upx_uint64_t a); diff --git a/src/msg.cpp b/src/msg.cpp index f4e8757e..bbd997e4 100644 --- a/src/msg.cpp +++ b/src/msg.cpp @@ -82,10 +82,10 @@ static void pr_error(const char *iname, const char *msg, bool is_warning) noexce bool c = acc_isatty(STDERR_FILENO) ? 1 : 0; int fg = con_fg(stderr, FG_BRTRED); - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s: ", progname); + upx_safe_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s: ", progname); pr_print(c, buf); //(void)con_fg(stderr,FG_RED); - snprintf(buf, sizeof(buf), "%s: ", iname); + upx_safe_snprintf(buf, sizeof(buf), "%s: ", iname); pr_print(c, buf); //(void)con_fg(stderr,FG_BRTRED); pr_print(c, msg); @@ -102,13 +102,13 @@ void printErr(const char *iname, const Throwable &e) noexcept { char buf[1024]; size_t l; - snprintf(buf, sizeof(buf), "%s", prettyName(typeid(e).name())); + upx_safe_snprintf(buf, sizeof(buf), "%s", prettyName(typeid(e).name())); l = strlen(buf); if (l < sizeof(buf) && e.getMsg()) - snprintf(buf + l, sizeof(buf) - l, ": %s", e.getMsg()); + upx_safe_snprintf(buf + l, sizeof(buf) - l, ": %s", e.getMsg()); l = strlen(buf); if (l < sizeof(buf) && e.getErrno()) { - snprintf(buf + l, sizeof(buf) - l, ": %s", strerror(e.getErrno())); + upx_safe_snprintf(buf + l, sizeof(buf) - l, ": %s", strerror(e.getErrno())); #if 1 // some compilers (e.g. Borland C++) put a trailing '\n' // into the strerror() result diff --git a/src/options.cpp b/src/options.cpp index 6b852b71..52c427d6 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -39,7 +39,7 @@ std::mutex opt_lock_mutex; **************************************************************************/ void Options::reset() noexcept { -#define opt ERROR_DO_NOT_USE_opt // protect against using the wrong variable +#define opt ERROR_DO_NOT_USE_opt // self-protect against using the wrong variable Options *const o = this; mem_clear(o); o->crp.reset(); @@ -90,7 +90,7 @@ void Options::reset() noexcept { **************************************************************************/ TEST_CASE("Options::reset") { -#define opt ERROR_DO_NOT_USE_opt // protect against using the wrong variable +#define opt ERROR_DO_NOT_USE_opt // self-protect against using the wrong variable COMPILE_TIME_ASSERT(std::is_standard_layout::value) COMPILE_TIME_ASSERT(std::is_nothrow_default_constructible::value) COMPILE_TIME_ASSERT(std::is_trivially_copyable::value) diff --git a/src/p_com.h b/src/p_com.h index 8913c6ac..c671f26c 100644 --- a/src/p_com.h +++ b/src/p_com.h @@ -31,7 +31,7 @@ // dos/com **************************************************************************/ -class PackCom : public Packer { +class PackCom /*not_final*/ : public Packer { typedef Packer super; public: diff --git a/src/p_elf.h b/src/p_elf.h index 3793f5b0..9bcfb000 100644 --- a/src/p_elf.h +++ b/src/p_elf.h @@ -178,9 +178,9 @@ packed_struct(Sym) { #define WANT_SYM_ENUM 1 #include "p_elf_enum.h" - static unsigned int get_st_bind(unsigned x) { return 0xf & (x >> 4); } - static unsigned int get_st_type(unsigned x) { return 0xf & x; } - static unsigned char make_st_info(unsigned bind, unsigned type) { + static constexpr unsigned get_st_bind(unsigned x) noexcept { return 0xf & (x >> 4); } + static constexpr unsigned get_st_type(unsigned x) noexcept { return 0xf & x; } + static constexpr unsigned char make_st_info(unsigned bind, unsigned type) noexcept { return (unsigned char) (((bind << 4) + (0xf & type)) & 0xff); } }; @@ -252,9 +252,9 @@ packed_struct(Sym) { #define WANT_SYM_ENUM 1 #include "p_elf_enum.h" - static unsigned int get_st_bind(unsigned x) { return 0xf & (x >> 4); } - static unsigned int get_st_type(unsigned x) { return 0xf & x; } - static unsigned char make_st_info(unsigned bind, unsigned type) { + static constexpr unsigned get_st_bind(unsigned x) noexcept { return 0xf & (x >> 4); } + static constexpr unsigned get_st_type(unsigned x) noexcept { return 0xf & x; } + static constexpr unsigned char make_st_info(unsigned bind, unsigned type) noexcept { return (unsigned char) (((bind << 4) + (0xf & type)) & 0xff); } }; diff --git a/src/packer.h b/src/packer.h index 05c0d87b..a02ef29c 100644 --- a/src/packer.h +++ b/src/packer.h @@ -36,7 +36,7 @@ class UiPacker; class Filter; /************************************************************************* -// PackerBase: purely abstract minimal base class for all packers +// PackerBase: abstract minimal base class for all packers // // clients: PackMaster, UiPacker **************************************************************************/ @@ -80,7 +80,7 @@ protected: InputFile *const fi; // reference union { // unnamed union const upx_int64_t file_size; // must get set by constructor - const upx_uint64_t file_size_u; // (explicitly unsigned to avoid casts) + const upx_uint64_t file_size_u; // (explicitly unsigned to avoid -Wsign-compare casts) }; PackHeader ph; // must be filled by canUnpack(); also used by UiPacker }; diff --git a/src/packer_c.cpp b/src/packer_c.cpp index 8cd05233..4e94f1e8 100644 --- a/src/packer_c.cpp +++ b/src/packer_c.cpp @@ -219,7 +219,7 @@ const char *Packer::getDecompressorSections() const { "LZMA_ELF00,LZMA_DEC20,LZMA_DEC30"; // clang-format on - unsigned const method = ph_forced_method(ph.method); + const unsigned method = ph_forced_method(ph.method); if (method == M_NRV2B_LE32) return opt->small ? nrv2b_le32_small : nrv2b_le32_fast; if (method == M_NRV2D_LE32) diff --git a/src/packhead.cpp b/src/packhead.cpp index 1ec09574..1939bb88 100644 --- a/src/packhead.cpp +++ b/src/packhead.cpp @@ -49,7 +49,7 @@ void PackHeader::reset() noexcept { // extremely simple checksum for the header itself (since version 10) **************************************************************************/ -static byte get_packheader_checksum(SPAN_S(const byte) buf, int blen) { +static upx_uint8_t get_packheader_checksum(SPAN_S(const byte) buf, int blen) { assert(blen >= 4); assert(get_le32(buf) == UPX_MAGIC_LE32); buf += 4; @@ -58,7 +58,7 @@ static byte get_packheader_checksum(SPAN_S(const byte) buf, int blen) { while (blen-- > 0) c += *buf++; c %= 251; - return (byte) c; + return (upx_uint8_t) c; } /************************************************************************* @@ -108,7 +108,7 @@ void PackHeader::putPackHeader(SPAN_S(byte) p) const { } int size = 0; - int old_chksum = 0; + upx_uint8_t old_chksum = 0; // the new variable length header if (format < 128) { // little endian diff --git a/src/pefile.cpp b/src/pefile.cpp index a1f385da..cd7af70e 100644 --- a/src/pefile.cpp +++ b/src/pefile.cpp @@ -2948,7 +2948,7 @@ void PeFile::unpack0(OutputFile *fo, const ht &ih, ht &oh, ord_mask_t ord_mask, if (iobjs > 2) { // read the noncompressed section - unsigned const size = isection[2].size; + const unsigned size = isection[2].size; ibuf.dealloc(); ibuf.alloc(size + 1); fi->seek(isection[2].rawdataptr, SEEK_SET); diff --git a/src/util/cxxlib.h b/src/util/cxxlib.h index de5c6afe..c4043942 100644 --- a/src/util/cxxlib.h +++ b/src/util/cxxlib.h @@ -360,6 +360,7 @@ private: pointer ptr; reference operator[](std::ptrdiff_t) noexcept DELETED_FUNCTION; const_reference operator[](std::ptrdiff_t) const noexcept DELETED_FUNCTION; + UPX_CXX_DISABLE_ADDRESS(OwningPointer) // UPX convention UPX_CXX_DISABLE_NEW_DELETE_NO_VIRTUAL(OwningPointer) // UPX convention }; // must overload mem_clear() diff --git a/src/util/membuffer.cpp b/src/util/membuffer.cpp index cf90a310..b712abd6 100644 --- a/src/util/membuffer.cpp +++ b/src/util/membuffer.cpp @@ -90,7 +90,7 @@ void *MemBuffer::subref_impl(const char *errfmt, size_t skip, size_t take) { // printf is using unsigned formatting if (!errfmt || !errfmt[0]) errfmt = "bad subref %#x %#x"; - snprintf(buf, sizeof(buf), errfmt, (unsigned) skip, (unsigned) take); + upx_safe_snprintf(buf, sizeof(buf), errfmt, (unsigned) skip, (unsigned) take); throwCantPack(buf); } return ptr + skip; @@ -189,7 +189,7 @@ void MemBuffer::alloc(upx_uint64_t bytes) { set_ne32(p + size_in_bytes + 0, MAGIC2(p)); set_ne32(p + size_in_bytes + 4, stats.global_alloc_counter); } - ptr = (pointer) (void *) p; + ptr = upx::ptr_static_cast(p); #if !defined(__SANITIZE_MEMORY__) && DEBUG memset(ptr, 0xfb, size_in_bytes); (void) VALGRIND_MAKE_MEM_UNDEFINED(ptr, size_in_bytes); @@ -247,26 +247,33 @@ void MemBuffer::dealloc() noexcept { **************************************************************************/ TEST_CASE("MemBuffer core") { + constexpr size_t N = 64; MemBuffer mb; CHECK_THROWS(mb.checkState()); CHECK_THROWS(mb.alloc(0x30000000 + 1)); CHECK(raw_bytes(mb, 0) == nullptr); CHECK_THROWS(raw_bytes(mb, 1)); - mb.alloc(64); + mb.alloc(N); mb.checkState(); - CHECK(raw_bytes(mb, 64) != nullptr); - CHECK(raw_bytes(mb, 64) == mb.getVoidPtr()); - CHECK_THROWS(raw_bytes(mb, 65)); - CHECK_NOTHROW(mb + 64); - CHECK_THROWS(mb + 65); + CHECK(mb.begin() == mb.cbegin()); + CHECK(mb.end() == mb.cend()); + CHECK(mb.begin() == &mb[0]); + CHECK(mb.end() == &mb[0] + N); + CHECK(mb.cbegin() == &mb[0]); + CHECK(mb.cend() == &mb[0] + N); + CHECK(raw_bytes(mb, N) != nullptr); + CHECK(raw_bytes(mb, N) == mb.getVoidPtr()); + CHECK_THROWS(raw_bytes(mb, N + 1)); + CHECK_NOTHROW(mb + N); + CHECK_THROWS(mb + (N + 1)); #if ALLOW_INT_PLUS_MEMBUFFER - CHECK_NOTHROW(64 + mb); - CHECK_THROWS(65 + mb); + CHECK_NOTHROW(N + mb); + CHECK_THROWS((N + 1) + mb); #endif - CHECK_NOTHROW(mb.subref("", 0, 64)); - CHECK_NOTHROW(mb.subref("", 64, 0)); - CHECK_THROWS(mb.subref("", 1, 64)); - CHECK_THROWS(mb.subref("", 64, 1)); + CHECK_NOTHROW(mb.subref("", 0, N)); + CHECK_NOTHROW(mb.subref("", N, 0)); + CHECK_THROWS(mb.subref("", 1, N)); + CHECK_THROWS(mb.subref("", N, 1)); if (use_simple_mcheck()) { byte *p = raw_bytes(mb, 0); unsigned magic1 = get_ne32(p - 4); @@ -314,6 +321,26 @@ TEST_CASE("MemBuffer unused") { CHECK(mb.raw_size_in_bytes() == 0); } +TEST_CASE("MemBuffer array access") { + constexpr size_t N = 16; + MemBuffer mb(N); + mb.clear(); + for (size_t i = 0; i != N; ++i) + mb[i] += 1; + for (byte *ptr = mb; ptr != mb + N; ++ptr) + *ptr += 1; + for (byte *ptr = mb + 0; ptr < mb + N; ++ptr) + *ptr += 1; + for (byte *ptr = &mb[0]; ptr != mb.end(); ++ptr) + *ptr += 1; + for (byte *ptr = mb.begin(); ptr < mb.end(); ++ptr) + *ptr += 1; + for (size_t i = 0; i != N; ++i) + assert(mb[i] == 5); + CHECK_NOTHROW((void) &mb[N - 1]); + CHECK_THROWS((void) &mb[N]); // NOT legal for containers like std::vector or MemBuffer +} + TEST_CASE("MemBuffer::getSizeForCompression") { CHECK_THROWS(MemBuffer::getSizeForCompression(0)); CHECK_THROWS(MemBuffer::getSizeForDecompression(0)); diff --git a/src/util/membuffer.h b/src/util/membuffer.h index 8d3b8b2a..ef0074ff 100644 --- a/src/util/membuffer.h +++ b/src/util/membuffer.h @@ -42,6 +42,11 @@ public: typedef typename std::add_lvalue_reference::type reference; typedef typename std::add_pointer::type pointer; typedef unsigned size_type; // limited by UPX_RSIZE_MAX + typedef pointer iterator; + typedef typename std::add_pointer::type const_iterator; +protected: + static constexpr size_t element_size = sizeof(element_type); + static_assert(element_size >= 1 && element_size <= UPX_RSIZE_MAX_MEM); protected: pointer ptr; @@ -57,9 +62,9 @@ public: // array access reference operator[](ptrdiff_t i) const may_throw { - // TODO: &array[SIZE] == array + SIZE, this is legal; but element access is not - if very_unlikely (i < 0 || mem_size(sizeof(element_type), i) > size_in_bytes) - throwCantPack("MemBuffer invalid index %td (%u bytes)", i, size_in_bytes); + // NOTE: &array[SIZE] is *not* legal for containers like std::vector and MemBuffer ! + if very_unlikely (i < 0 || mem_size(element_size, i) + element_size > size_in_bytes) + throwCantPack("MemBuffer invalid array index %td (%u bytes)", i, size_in_bytes); return ptr[i]; } // dereference @@ -67,11 +72,33 @@ public: // arrow operator pointer operator->() const DELETED_FUNCTION; + iterator begin() const may_throw { + if very_unlikely (ptr == nullptr) + throwCantPack("MemBuffer begin() unexpected NULL ptr"); + return ptr; + } + const_iterator cbegin() const may_throw { + if very_unlikely (ptr == nullptr) + throwCantPack("MemBuffer cbegin() unexpected NULL ptr"); + return ptr; + } + iterator end() const may_throw { + if very_unlikely (ptr == nullptr) + throwCantPack("MemBuffer end() unexpected NULL ptr"); + return ptr + size_in_bytes / element_size; + } + const_iterator cend() const may_throw { + if very_unlikely (ptr == nullptr) + throwCantPack("MemBuffer cend() unexpected NULL ptr"); + return ptr + size_in_bytes / element_size; + } + // membuffer + n -> pointer template - typename std::enable_if::value, pointer>::type operator+(U n) const { - size_t bytes = mem_size(sizeof(T), n); // check mem_size - return raw_bytes(bytes) + n; // and check bytes + typename std::enable_if::value, pointer>::type operator+(U n) const + may_throw { + size_t bytes = mem_size(element_size, n); // check mem_size + return raw_bytes(bytes) + n; // and check bytes } private: // membuffer - n -> pointer; not allowed - use raw_bytes() if needed @@ -83,7 +110,7 @@ public: // raw access pointer raw_ptr() const noexcept { return ptr; } size_type raw_size_in_bytes() const noexcept { return size_in_bytes; } - pointer raw_bytes(size_t bytes) const { + pointer raw_bytes(size_t bytes) const may_throw { if (bytes > 0) { if very_unlikely (ptr == nullptr) throwCantPack("MemBuffer raw_bytes unexpected NULL ptr"); @@ -161,39 +188,40 @@ inline typename MemBufferBase::pointer raw_index_bytes(const MemBufferBase class MemBuffer final : public MemBufferBase { public: explicit inline MemBuffer() noexcept : MemBufferBase() {} - explicit MemBuffer(upx_uint64_t bytes); + explicit MemBuffer(upx_uint64_t bytes) may_throw; ~MemBuffer() noexcept; - static unsigned getSizeForCompression(unsigned uncompressed_size, unsigned extra = 0); - static unsigned getSizeForDecompression(unsigned uncompressed_size, unsigned extra = 0); + static unsigned getSizeForCompression(unsigned uncompressed_size, unsigned extra = 0) may_throw; + static unsigned getSizeForDecompression(unsigned uncompressed_size, unsigned extra = 0) + may_throw; - void alloc(upx_uint64_t bytes); - void allocForCompression(unsigned uncompressed_size, unsigned extra = 0); - void allocForDecompression(unsigned uncompressed_size, unsigned extra = 0); + void alloc(upx_uint64_t bytes) may_throw; + void allocForCompression(unsigned uncompressed_size, unsigned extra = 0) may_throw; + void allocForDecompression(unsigned uncompressed_size, unsigned extra = 0) may_throw; void dealloc() noexcept; - void checkState() const; - unsigned getSize() const noexcept { return size_in_bytes; } + void checkState() const may_throw; // explicit conversion void *getVoidPtr() noexcept { return (void *) ptr; } const void *getVoidPtr() const noexcept { return (const void *) ptr; } + unsigned getSize() const noexcept { return size_in_bytes; } // util - void fill(unsigned off, unsigned len, int value); - forceinline void clear(unsigned off, unsigned len) { fill(off, len, 0); } - forceinline void clear() { fill(0, size_in_bytes, 0); } + noinline void fill(unsigned off, unsigned len, int value) may_throw; + forceinline void clear(unsigned off, unsigned len) may_throw { fill(off, len, 0); } + forceinline void clear() may_throw { fill(0, size_in_bytes, 0); } // If the entire range [skip, skip+take) is inside the buffer, // then return &ptr[skip]; else throwCantPack(sprintf(errfmt, skip, take)). // This is similar to BoundedPtr, except only checks once. // skip == offset, take == size_in_bytes - forceinline pointer subref(const char *errfmt, size_t skip, size_t take) { + forceinline pointer subref(const char *errfmt, size_t skip, size_t take) may_throw { return (pointer) subref_impl(errfmt, skip, take); } private: - void *subref_impl(const char *errfmt, size_t skip, size_t take); + void *subref_impl(const char *errfmt, size_t skip, size_t take) may_throw; // static debug stats struct Stats { diff --git a/src/util/util.cpp b/src/util/util.cpp index 507195ac..914f1592 100644 --- a/src/util/util.cpp +++ b/src/util/util.cpp @@ -39,8 +39,8 @@ #include "../conf.h" /************************************************************************* -// assert sane memory buffer sizes to protect against integer overflows -// and malicious header fields +// upx_rsize_t and mem_size: assert sane memory buffer sizes to protect +// against integer overflows and malicious header fields // see C 11 standard, Annex K **************************************************************************/ @@ -253,7 +253,7 @@ TEST_CASE("ptr_check_no_overlap 3") { // stdlib **************************************************************************/ -void *upx_calloc(size_t n, size_t element_size) { +void *upx_calloc(size_t n, size_t element_size) may_throw { size_t bytes = mem_size(element_size, n); // assert size void *p = malloc(bytes); if (p != nullptr) @@ -262,7 +262,7 @@ void *upx_calloc(size_t n, size_t element_size) { } // simple unoptimized memswap() -void upx_memswap(void *a, void *b, size_t n) { +void upx_memswap(void *a, void *b, size_t n) noexcept { if (a != b && n != 0) { byte *x = (byte *) a; byte *y = (byte *) b; @@ -277,7 +277,7 @@ void upx_memswap(void *a, void *b, size_t n) { } // much better memswap(), optimized for our use case in sort functions below -static void memswap_no_overlap(byte *a, byte *b, size_t n) { +static void memswap_no_overlap(byte *a, byte *b, size_t n) noexcept { #if defined(__clang__) && __clang_major__ < 15 // work around a clang < 15 ICE (Internal Compiler Error) // @COMPILER_BUG @CLANG_BUG diff --git a/src/util/util.h b/src/util/util.h index 7b944a7a..31c6089a 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -28,8 +28,8 @@ #pragma once /************************************************************************* -// assert sane memory buffer sizes to protect against integer overflows -// and malicious header fields +// upx_rsize_t and mem_size: assert sane memory buffer sizes to protect +// against integer overflows and malicious header fields // see C 11 standard, Annex K **************************************************************************/ @@ -73,7 +73,7 @@ T *NewArray(upx_uint64_t n) may_throw { COMPILE_TIME_ASSERT(std::is_standard_layout::value) COMPILE_TIME_ASSERT(std::is_trivially_copyable::value) COMPILE_TIME_ASSERT(std::is_trivially_default_constructible::value) - size_t bytes = mem_size(sizeof(T), n); // assert size + upx_rsize_t bytes = mem_size(sizeof(T), n); // assert size T *array = new T[size_t(n)]; #if !defined(__SANITIZE_MEMORY__) if (array != nullptr && bytes > 0) { @@ -145,7 +145,7 @@ inline void ptr_invalidate_and_poison(T *(&ptr)) noexcept { void *upx_calloc(size_t n, size_t element_size) may_throw; -void upx_memswap(void *a, void *b, size_t n); +void upx_memswap(void *a, void *b, size_t n) noexcept; typedef int(__acc_cdecl_qsort *upx_compare_func_t)(const void *, const void *); typedef void (*upx_sort_func_t)(void *array, size_t n, size_t element_size, upx_compare_func_t); diff --git a/src/util/xspan_impl.h b/src/util/xspan_impl.h index be02051e..4bb5f581 100644 --- a/src/util/xspan_impl.h +++ b/src/util/xspan_impl.h @@ -54,11 +54,11 @@ void xspan_check_range(const void *ptr, const void *base, ptrdiff_t size_in_byte // help constructor to distinguish between number of elements and bytes struct XSpanCount final { explicit forceinline_constexpr XSpanCount(size_t n) noexcept : count(n) {} - size_t count; // public + const size_t count; // public }; struct XSpanSizeInBytes final { explicit forceinline_constexpr XSpanSizeInBytes(size_t bytes) noexcept : size_in_bytes(bytes) {} - size_t size_in_bytes; // public + const size_t size_in_bytes; // public }; template