diff --git a/README.md b/README.md index be87b3f5..c7b04077 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ reproduce official release artifacts from a git checkout, set VeraCrypt sources tracked in another git checkout are treated the same way and use that checkout's HEAD timestamp. +Both the generated `.deb` and `.rpm` packages are reproducible, including on older rpm (e.g. CentOS/RHEL 7) that lacks the `SOURCE_DATE_EPOCH`/`_buildhost` build macros. + By default, a universal executable supporting both graphical and text user interface (through the switch --text) is built. On Linux, a console-only executable, which requires no GUI library, can be diff --git a/src/Build/CMakeLists.txt b/src/Build/CMakeLists.txt index 11d14112..118190b0 100644 --- a/src/Build/CMakeLists.txt +++ b/src/Build/CMakeLists.txt @@ -304,6 +304,18 @@ set( CPACK_PACKAGE_CHECKSUM SHA256 ) set( CPACK_PACKAGE_RELOCATABLE "OFF") # Disable package relocation (especially for rpm) set( CPACK_PACKAGE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Packaging ) +# Reproducible CPack payloads: clamp the just-installed staging tree's +# mtimes and modes so DEB/RPM payloads are independent of wall-clock time +# and the build umask. Placed AFTER install(DIRECTORY) so it runs against a +# populated tree (install rules execute in declaration order). The script +# acts only on a real package staging root and refuses a live prefix; see the +# script header for the staging-root detection rules. +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/Tools/cmake_repro_clamp_mtimes.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_repro_clamp_mtimes.cmake" + @ONLY) +install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/cmake_repro_clamp_mtimes.cmake") + if ( ( PLATFORM STREQUAL "Debian" ) OR ( PLATFORM STREQUAL "Ubuntu" ) ) # Debian control script(s) @@ -315,18 +327,6 @@ if ( ( PLATFORM STREQUAL "Debian" ) OR ( PLATFORM STREQUAL "Ubuntu" ) ) set( CPACK_GENERATOR "DEB" ) # mandatory - # Reproducible DEB: clamp the just-installed staging tree's mtimes - # and modes so the payload is independent of wall-clock time and - # the build umask. Placed AFTER install(DIRECTORY) so it runs against a - # populated tree (install rules execute in declaration order). The script - # acts only on a real package staging root and refuses a live prefix; - # see the script header for the staging-root detection rules. - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/Tools/cmake_repro_clamp_mtimes.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_repro_clamp_mtimes.cmake" - @ONLY) - install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/cmake_repro_clamp_mtimes.cmake") - set( CPACK_DEBIAN_PACKAGE_NAME ${CPACK_PACKAGE_NAME} ) # mandatory set( CPACK_DEBIAN_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}.deb ) # mandatory # -- Use a distro-specific version string to avoid repository conflicts -- @@ -424,6 +424,30 @@ elseif ( ( PLATFORM STREQUAL "CentOS" ) OR ( PLATFORM STREQUAL "openSUSE" ) OR ( set( RPM_PRERM ${CMAKE_CURRENT_BINARY_DIR}/Packaging/rpm-control/prerm.sh) set( CPACK_GENERATOR "RPM" ) # mandatory + # Reproducible RPM: CPackRPM does not consume SOURCE_DATE_EPOCH on its own. + # use_source_date_epoch_as_buildtime pins BuildTime to the SOURCE_DATE_EPOCH + # environment variable (exported by the wrappers and CPACK_PROJECT_CONFIG_FILE), + # _buildhost removes host identity from the header, and the mtime macros cover + # rpm versions with built-in policy support. source_date_epoch_from_changelog + # is disabled so CPack's placeholder changelog date cannot hijack the epoch. + # The install(SCRIPT) clamp above also normalises the CPack staging tree before + # rpmbuild sees it. Old rpm still runs brp-strip after %install, which rewrites + # executable mtimes, so append one final mtime clamp to the default brp chain. + set(_vc_rpm_repro_defines +"%define source_date_epoch_from_changelog 0 +%define use_source_date_epoch_as_buildtime 1 +%define _buildhost reproducible +%define clamp_mtime_to_source_date_epoch 1 +%define build_mtime_policy clamp_to_source_date_epoch +%global _vc_default_os_install_post %{__os_install_post} +%define __os_install_post %{_vc_default_os_install_post} \\ + find $RPM_BUILD_ROOT -exec touch --no-dereference --date=@${SOURCE_DATE_EPOCH} {} + \\ +%{nil}") + if(CPACK_RPM_SPEC_MORE_DEFINE) + set(CPACK_RPM_SPEC_MORE_DEFINE "${CPACK_RPM_SPEC_MORE_DEFINE}\n${_vc_rpm_repro_defines}") + else() + set(CPACK_RPM_SPEC_MORE_DEFINE "${_vc_rpm_repro_defines}") + endif() set( CPACK_RPM_PACKAGE_SUMMARY ${CPACK_PACKAGE_DESCRIPTION_SUMMARY} ) # mandatory set( CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION} ) # mandatory set( CPACK_RPM_PACKAGE_NAME ${CPACK_PACKAGE_NAME} ) # mandatory diff --git a/src/Build/Tools/cmake_repro_clamp_mtimes.cmake.in b/src/Build/Tools/cmake_repro_clamp_mtimes.cmake.in index 33d93740..273c6261 100644 --- a/src/Build/Tools/cmake_repro_clamp_mtimes.cmake.in +++ b/src/Build/Tools/cmake_repro_clamp_mtimes.cmake.in @@ -3,7 +3,7 @@ # # Run at install time by install(SCRIPT ...) AFTER all install(DIRECTORY) # rules, so the staging tree is fully populated. Clamps every file's mtime -# and permission bits so the CPack DEB payload is reproducible. +# and permission bits so the CPack DEB/RPM payload is reproducible. # # Safety: only a package staging tree may be modified, never a live host # tree. Two recognised staging conventions: @@ -16,8 +16,14 @@ # Anything else (bare "cmake --install" into /usr or /usr/local) is # refused so root cannot rewrite mtimes/modes outside a package build. -if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") - return() +if(DEFINED CMAKE_HOST_SYSTEM_NAME) + if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + return() + endif() +elseif(DEFINED CMAKE_SYSTEM_NAME) + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + return() + endif() endif() set(_destdir "$ENV{DESTDIR}") diff --git a/src/Build/Tools/repro_buildstamp.c b/src/Build/Tools/repro_buildstamp.c new file mode 100644 index 00000000..130dd523 --- /dev/null +++ b/src/Build/Tools/repro_buildstamp.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026 VeraCrypt + * Governed by the Apache License 2.0. + * + * Reproducible-build helper: a tiny libc-interposition shim that is + * LD_PRELOAD'ed only around the "cpack -G RPM" step by the RPM packaging + * wrappers. It pins the two RPM header fields that rpmbuild otherwise + * stamps from the wall clock and the build host: + * + * RPMTAG_BUILDTIME <- SOURCE_DATE_EPOCH (via time()) + * RPMTAG_BUILDHOST <- "reproducible" (via gethostname()/uname()) + * + * Modern rpm (>= 4.14 for buildtime, >= 4.18 for buildhost) handles these + * through its own macros, which CMakeLists.txt already sets; on those + * versions the shim merely produces the same values. The shim exists so + * the same reproducibility holds on old rpm (CentOS/RHEL <= 7, rpm < 4.14) + * that has no such macros, because it works at the libc level regardless of + * rpm's age. Payload file mtimes/modes are handled separately and + * version-independently by the install(SCRIPT) staging clamp. + * + * Safety notes: + * - Only time() is overridden for the clock; clock_gettime()/monotonic time + * are left untouched so nothing that waits on elapsed time can hang. + * - uname() is NOT faked wholesale: the real uname() is called first and + * only nodename is overwritten, so sysname/release/machine stay correct + * and rpm's architecture/platform detection is unaffected. + * - The shim must load cleanly; a load failure would make ld.so print to + * stderr, which rpm's check-buildroot brp script captures and treats as a + * fatal "buildroot leaked into files" error. The wrappers therefore only + * enable the shim after verifying it builds and loads without output. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#ifndef VC_REPRO_BUILDHOST +#define VC_REPRO_BUILDHOST "reproducible" +#endif + +static time_t vc_fixed_epoch(int *have_epoch) +{ + const char *e = getenv("SOURCE_DATE_EPOCH"); + if (e && *e) { + *have_epoch = 1; + return (time_t) strtoll(e, NULL, 10); + } + *have_epoch = 0; + return (time_t) 0; +} + +time_t time(time_t *t) +{ + int have_epoch; + time_t v = vc_fixed_epoch(&have_epoch); + + /* Without SOURCE_DATE_EPOCH there is nothing to pin to, so defer to the + * real time() rather than stamping the epoch (1970): a no-op shim is far + * safer than a frozen 1970 clock for anything that runs under the + * preload. The wrappers always export SOURCE_DATE_EPOCH, so this path is + * only a defensive fallback. */ + if (!have_epoch) { + static time_t (*real_time)(time_t *) = NULL; + if (!real_time) + real_time = (time_t (*)(time_t *)) dlsym(RTLD_NEXT, "time"); + if (real_time) + return real_time(t); + } + + if (t) + *t = v; + return v; +} + +int gethostname(char *name, size_t len) +{ + if (name && len) { + strncpy(name, VC_REPRO_BUILDHOST, len); + name[len - 1] = '\0'; + } + return 0; +} + +int uname(struct utsname *buf) +{ + static int (*real_uname)(struct utsname *) = NULL; + int rc; + + if (!real_uname) + real_uname = (int (*)(struct utsname *)) dlsym(RTLD_NEXT, "uname"); + rc = real_uname ? real_uname(buf) : -1; + if (rc == 0 && buf) { + strncpy(buf->nodename, VC_REPRO_BUILDHOST, sizeof(buf->nodename) - 1); + buf->nodename[sizeof(buf->nodename) - 1] = '\0'; + } + return rc; +} diff --git a/src/Build/build_cmake_opensuse.sh b/src/Build/build_cmake_opensuse.sh old mode 100644 new mode 100755 index 6db94d35..9ae4ba3c --- a/src/Build/build_cmake_opensuse.sh +++ b/src/Build/build_cmake_opensuse.sh @@ -21,6 +21,22 @@ export SOURCEPATH=$(readlink -f "$SCRIPTPATH/..") # Directory where the VeraCrypt has been checked out export PARENTDIR=$(readlink -f "$SCRIPTPATH/../../..") +# Compute and export SOURCE_DATE_EPOCH so make/cmake/cpack/rpmbuild inherit +# the same value. Precedence: caller, git HEAD, Common/Tcdefs.h release date. +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + SOURCE_DATE_EPOCH=$(sh "$SOURCEPATH/Build/Tools/source_date_epoch.sh" "$SOURCEPATH") || { + echo "Error: SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date" >&2 + exit 1 + } +fi +case "$SOURCE_DATE_EPOCH" in + ''|*[!0-9]*) + echo "Error: SOURCE_DATE_EPOCH must be a non-negative Unix timestamp" >&2 + exit 1 + ;; +esac +export SOURCE_DATE_EPOCH + # Check the condition of wxBuildConsole and wxWidgets-3.2.5 in the original PARENTDIR if [ -d "$PARENTDIR/wxBuildConsole" ]; then echo "Using existing PARENTDIR: $PARENTDIR, wxBuildConsole is present." @@ -129,8 +145,42 @@ rm -rf $PARENTDIR/VeraCrypt_Packaging mkdir -p $PARENTDIR/VeraCrypt_Packaging/GUI mkdir -p $PARENTDIR/VeraCrypt_Packaging/Console +# Pin the RPM header BuildTime/BuildHost on rpm versions that predate the +# SOURCE_DATE_EPOCH/_buildhost macros (CentOS/RHEL <= 7, rpm < 4.14). A tiny +# libc-interposition shim is LD_PRELOAD'ed onto cpack's rpmbuild child; modern +# rpm sets the same values through its own macros (see Build/CMakeLists.txt), so +# the shim only matters where those macros do not exist. Payload mtimes/modes +# are handled separately by the install(SCRIPT) staging clamp. +# +# The shim is strictly optional: build it and use it only if it compiles and +# loads without emitting anything on stderr, otherwise package without it. A +# shim that failed to load would make ld.so print to stderr, which rpm's +# check-buildroot brp script captures and turns into a fatal %install error, so +# we never enable a shim we have not verified. +RPM_REPRO_SHIM="" +_shim_src="$SOURCEPATH/Build/Tools/repro_buildstamp.c" +_shim_so="$PARENTDIR/VeraCrypt_Packaging/repro_buildstamp.so" +_cc="${CC:-cc}" +if command -v "$_cc" >/dev/null 2>&1 && + "$_cc" -shared -fPIC -O2 -o "$_shim_so" "$_shim_src" -ldl 2>/dev/null && + [ -z "$(LD_PRELOAD="$_shim_so" /bin/sh -c : 2>&1)" ]; then + RPM_REPRO_SHIM="$_shim_so" + echo "Reproducible RPM: build-stamp shim enabled ($_shim_so)" +else + echo "Reproducible RPM: build-stamp shim unavailable; header BuildTime/BuildHost rely on rpm macros (rpm >= 4.14/4.18)" >&2 +fi + +# Run cpack with the build-stamp shim preloaded when it is available. +run_cpack() { + if [ -n "$RPM_REPRO_SHIM" ]; then + LD_PRELOAD="$RPM_REPRO_SHIM" cpack "$@" + else + cpack "$@" + fi +} + # wxWidgets was built using native GTK version -cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/GUI -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/GUI" -DNOGUI=FALSE $FUSE3_CMAKE_FLAG || exit 1 -cpack --config $PARENTDIR/VeraCrypt_Packaging/GUI/CPackConfig.cmake || exit 1 -cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/Console -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/Console" -DNOGUI=TRUE $FUSE3_CMAKE_FLAG || exit 1 -cpack --config $PARENTDIR/VeraCrypt_Packaging/Console/CPackConfig.cmake|| exit 1 +cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/GUI -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/GUI" -DNOGUI=FALSE -DSOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH $FUSE3_CMAKE_FLAG || exit 1 +run_cpack --config $PARENTDIR/VeraCrypt_Packaging/GUI/CPackConfig.cmake || exit 1 +cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/Console -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/Console" -DNOGUI=TRUE -DSOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH $FUSE3_CMAKE_FLAG || exit 1 +run_cpack --config $PARENTDIR/VeraCrypt_Packaging/Console/CPackConfig.cmake || exit 1 diff --git a/src/Build/build_cmake_rpm.sh b/src/Build/build_cmake_rpm.sh old mode 100644 new mode 100755 index d40fb79a..3ffa78f7 --- a/src/Build/build_cmake_rpm.sh +++ b/src/Build/build_cmake_rpm.sh @@ -21,6 +21,22 @@ export SOURCEPATH=$(readlink -f "$SCRIPTPATH/..") # Directory where the VeraCrypt has been checked out export PARENTDIR=$(readlink -f "$SCRIPTPATH/../../..") +# Compute and export SOURCE_DATE_EPOCH so make/cmake/cpack/rpmbuild inherit +# the same value. Precedence: caller, git HEAD, Common/Tcdefs.h release date. +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + SOURCE_DATE_EPOCH=$(sh "$SOURCEPATH/Build/Tools/source_date_epoch.sh" "$SOURCEPATH") || { + echo "Error: SOURCE_DATE_EPOCH must be set, derivable from git, or derivable from Common/Tcdefs.h release date" >&2 + exit 1 + } +fi +case "$SOURCE_DATE_EPOCH" in + ''|*[!0-9]*) + echo "Error: SOURCE_DATE_EPOCH must be a non-negative Unix timestamp" >&2 + exit 1 + ;; +esac +export SOURCE_DATE_EPOCH + # Check the condition of wxBuildConsole and wxWidgets-3.2.5 in the original PARENTDIR if [ -d "$PARENTDIR/wxBuildConsole" ]; then echo "Using existing PARENTDIR: $PARENTDIR, wxBuildConsole is present." @@ -130,8 +146,42 @@ rm -rf $PARENTDIR/VeraCrypt_Packaging mkdir -p $PARENTDIR/VeraCrypt_Packaging/GUI mkdir -p $PARENTDIR/VeraCrypt_Packaging/Console +# Pin the RPM header BuildTime/BuildHost on rpm versions that predate the +# SOURCE_DATE_EPOCH/_buildhost macros (CentOS/RHEL <= 7, rpm < 4.14). A tiny +# libc-interposition shim is LD_PRELOAD'ed onto cpack's rpmbuild child; modern +# rpm sets the same values through its own macros (see Build/CMakeLists.txt), so +# the shim only matters where those macros do not exist. Payload mtimes/modes +# are handled separately by the install(SCRIPT) staging clamp. +# +# The shim is strictly optional: build it and use it only if it compiles and +# loads without emitting anything on stderr, otherwise package without it. A +# shim that failed to load would make ld.so print to stderr, which rpm's +# check-buildroot brp script captures and turns into a fatal %install error, so +# we never enable a shim we have not verified. +RPM_REPRO_SHIM="" +_shim_src="$SOURCEPATH/Build/Tools/repro_buildstamp.c" +_shim_so="$PARENTDIR/VeraCrypt_Packaging/repro_buildstamp.so" +_cc="${CC:-cc}" +if command -v "$_cc" >/dev/null 2>&1 && + "$_cc" -shared -fPIC -O2 -o "$_shim_so" "$_shim_src" -ldl 2>/dev/null && + [ -z "$(LD_PRELOAD="$_shim_so" /bin/sh -c : 2>&1)" ]; then + RPM_REPRO_SHIM="$_shim_so" + echo "Reproducible RPM: build-stamp shim enabled ($_shim_so)" +else + echo "Reproducible RPM: build-stamp shim unavailable; header BuildTime/BuildHost rely on rpm macros (rpm >= 4.14/4.18)" >&2 +fi + +# Run cpack with the build-stamp shim preloaded when it is available. +run_cpack() { + if [ -n "$RPM_REPRO_SHIM" ]; then + LD_PRELOAD="$RPM_REPRO_SHIM" cpack "$@" + else + cpack "$@" + fi +} + # wxWidgets was built using native GTK version -cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/GUI -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/GUI" -DNOGUI=FALSE $FUSE3_CMAKE_FLAG || exit 1 -cpack --config $PARENTDIR/VeraCrypt_Packaging/GUI/CPackConfig.cmake || exit 1 -cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/Console -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/Console" -DNOGUI=TRUE $FUSE3_CMAKE_FLAG || exit 1 -cpack --config $PARENTDIR/VeraCrypt_Packaging/Console/CPackConfig.cmake || exit 1 +cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/GUI -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/GUI" -DNOGUI=FALSE -DSOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH $FUSE3_CMAKE_FLAG || exit 1 +run_cpack --config $PARENTDIR/VeraCrypt_Packaging/GUI/CPackConfig.cmake || exit 1 +cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/Console -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/Console" -DNOGUI=TRUE -DSOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH $FUSE3_CMAKE_FLAG || exit 1 +run_cpack --config $PARENTDIR/VeraCrypt_Packaging/Console/CPackConfig.cmake || exit 1 diff --git a/src/Main/Main.make b/src/Main/Main.make index 2747f63b..92a64658 100755 --- a/src/Main/Main.make +++ b/src/Main/Main.make @@ -459,12 +459,12 @@ endif # Normalise modification times of every staged file. cp preserves the # checkout-time mtimes of the source tree, which would otherwise leak # into the tar/makeself archives and break reproducibility. - # Only run when GNU touch supports the option set. Keep AppImage - # outside this narrowed reproducibility scope: appimagetool is not - # verified here, so do not pre-clamp veracrypt.AppDir for that target. + # Only run when GNU touch supports the option set. AppImage staging is + # clamped too; appimagetool honours SOURCE_DATE_EPOCH for its SquashFS + # metadata, and the AppDir input should not leak checkout/build mtimes. ifeq "$(TOUCH_REPRODUCIBLE)" "yes" _appdir="$(BASE_DIR)/Setup/Linux/veracrypt.AppDir"; \ - if [ -n "$(filter appimage,$(MAKECMDGOALS))" ] || [ ! -d "$$_appdir" ]; then \ + if [ ! -d "$$_appdir" ]; then \ _appdir=""; \ fi; \ find $(BASE_DIR)/Setup/Linux/usr $$_appdir \ @@ -587,9 +587,6 @@ appimage: prepare wget --quiet -O "$${_appimagetool_executable_path}" "$${_appimagetool_url}"; \ chmod +x "$${_appimagetool_executable_path}"; \ echo "Creating AppImage $${_final_appimage_path}..."; \ - if [ "$(VC_SOURCE_DATE_EPOCH_AUTO)" = "1" ]; then \ - unset SOURCE_DATE_EPOCH; \ - fi; \ ARCH="$${_final_appimage_arch_suffix}" "$${_appimagetool_executable_path}" "$(BASE_DIR)/Setup/Linux/veracrypt.AppDir" "$${_final_appimage_path}"; \ echo "AppImage created: $${_final_appimage_path}"; \ echo "Cleaning up appimagetool..."; \