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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/Build/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,34 @@ else()
endif()
project(${PROJECT_NAME})

# SOURCE_DATE_EPOCH for the cpack-driven DEB pipeline.
# Precedence: -DSOURCE_DATE_EPOCH=N, env, git HEAD, fixed fallback.
# Re-exported to ENV so dpkg-deb/tar inherit it.
if(NOT DEFINED SOURCE_DATE_EPOCH)
if(DEFINED ENV{SOURCE_DATE_EPOCH})
set(SOURCE_DATE_EPOCH "$ENV{SOURCE_DATE_EPOCH}")
else()
execute_process(
COMMAND git -C "$ENV{SOURCEPATH}" log -1 --pretty=%ct
OUTPUT_VARIABLE _git_ct
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _git_rc)
if(_git_rc EQUAL 0 AND _git_ct)
set(SOURCE_DATE_EPOCH "${_git_ct}")
else()
set(SOURCE_DATE_EPOCH "1577836800")
endif()
endif()
endif()
if(NOT SOURCE_DATE_EPOCH MATCHES "^[0-9]+$")
MESSAGE(FATAL_ERROR "SOURCE_DATE_EPOCH must be a non-negative Unix timestamp")
endif()
message(STATUS "SOURCE_DATE_EPOCH = ${SOURCE_DATE_EPOCH}")
set(ENV{SOURCE_DATE_EPOCH} "${SOURCE_DATE_EPOCH}")
# Avoid nondeterministic ordering from cpack 3.18+ parallel compression.
set(CPACK_THREADS 1)

# - Check whether 'Tcdefs.h' and 'License.txt' exist
if(NOT EXISTS "$ENV{SOURCEPATH}/Common/Tcdefs.h")
MESSAGE(FATAL_ERROR "Tcdefs.h does not exist.")
Expand Down Expand Up @@ -252,6 +280,19 @@ if ( ( PLATFORM STREQUAL "Debian" ) OR ( PLATFORM STREQUAL "Ubuntu" ) )
set( DEBIAN_PRERM ${CMAKE_CURRENT_BINARY_DIR}/Packaging/debian-control/prerm)

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 --
Expand Down Expand Up @@ -353,6 +394,7 @@ elseif ( ( PLATFORM STREQUAL "CentOS" ) OR ( PLATFORM STREQUAL "openSUSE" ) OR (
set( CPACK_RPM_PACKAGE_GROUP "Applications/System" ) # mandatory, https://fedoraproject.org/wiki/RPMGroups
set( CPACK_RPM_PACKAGE_VENDOR ${CPACK_PACKAGE_VENDOR} ) # mandatory
set( CPACK_RPM_PACKAGE_AUTOREQ "no" ) # disable automatic shared libraries dependency detection (most of the time buggy)

if (VC_WITH_FUSE3)
set(VC_RPM_FUSE_PACKAGE "fuse3")
else ()
Expand Down
15 changes: 13 additions & 2 deletions src/Build/Include/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,18 @@ TR_SED_BIN := tr '\n' ' ' | tr -s ' ' ',' | sed -e 's/^,//g' -e 's/,$$/n/' | tr
-include $(OBJS:.o=.d) $(OBJSEX:.oo=.d) $(OBJSNOOPT:.o0=.d) $(OBJSHANI:.oshani=.d) $(OBJAESNI:.oaesni=.d) $(OBJSSSE41:.osse41=.d) $(OBJSSSSE3:.ossse3=.d) $(OBJSAVX2:.oavx2=.d) $(OBJARMV8CRYPTO:.oarmv8crypto=.d)


# Deterministic static library: the 'D' modifier zeroes member mtime/uid/gid
# and 'ranlib -D' writes a deterministic index. Both are probed functionally
# (running them on a throwaway archive) rather than by parsing --help, whose
# wording varies between binutils versions. Very old binutils that lack the
# feature simply falls back to a normal, still-correct archive.
# Probe also covers BSD ar / macOS libtool ar (neither supports -D): both
# variables come out empty there and the original ar/ranlib calls are used.
AR_DETERMINISTIC := $(shell t=$$(mktemp); rm -f $$t.a; $(AR) Drc $$t.a $$t >/dev/null 2>&1 && echo D; rm -f $$t $$t.a)
RANLIB_DETERMINISTIC := $(shell t=$$(mktemp); rm -f $$t.a; $(AR) rc $$t.a $$t >/dev/null 2>&1; $(RANLIB) -D $$t.a >/dev/null 2>&1 && echo -D; rm -f $$t $$t.a)

$(NAME).a: $(OBJS) $(OBJSEX) $(OBJSNOOPT) $(OBJSHANI) $(OBJAESNI) $(OBJSSSE41) $(OBJSSSSE3) $(OBJSAVX2) $(OBJARMV8CRYPTO)
@echo Updating library $@
$(AR) $(AFLAGS) -rc $@ $(OBJS) $(OBJSEX) $(OBJSNOOPT) $(OBJSHANI) $(OBJAESNI) $(OBJSSSE41) $(OBJSSSSE3) $(OBJSAVX2) $(OBJARMV8CRYPTO)
$(RANLIB) $@
rm -f $@
$(AR) $(AFLAGS) $(AR_DETERMINISTIC)rc $@ $(OBJS) $(OBJSEX) $(OBJSNOOPT) $(OBJSHANI) $(OBJAESNI) $(OBJSSSE41) $(OBJSSSSE3) $(OBJSAVX2) $(OBJARMV8CRYPTO)
$(RANLIB) $(RANLIB_DETERMINISTIC) $@
108 changes: 108 additions & 0 deletions src/Build/Tools/cmake_repro_clamp_mtimes.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) 2026 VeraCrypt
# Governed by the Apache License 2.0.
#
# 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.
#
# Safety: only a package staging tree may be modified, never a live host
# tree. Two recognised staging conventions:
# 1. CPack: it installs into its private
# <build>/_CPack_Packages/.../<pkg>${CPACK_PACKAGING_INSTALL_PREFIX}
# directory, exposed here as CMAKE_INSTALL_PREFIX. We require the
# path to contain a "_CPack_Packages" component.
# 2. "make DESTDIR=<dir> install" style: $ENV{DESTDIR} is a non-live
# staging root and $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} is clamped.
# 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()
endif()

set(_destdir "$ENV{DESTDIR}")
set(_prefix "${CMAKE_INSTALL_PREFIX}")

if(NOT _destdir STREQUAL "")
get_filename_component(_destdir_abs "${_destdir}" ABSOLUTE)
foreach(_live_destdir "/" "/usr" "/usr/local" "/opt")
if(_destdir_abs STREQUAL "${_live_destdir}")
message(FATAL_ERROR "Reproducible build: refusing to clamp live "
"DESTDIR '${_destdir}'")
endif()
endforeach()
set(_staging "${_destdir}${_prefix}")
elseif(_prefix MATCHES "/_CPack_Packages/")
set(_staging "${_prefix}")
else()
message(STATUS "Reproducible build: not a package staging install "
"(DESTDIR empty and prefix '${_prefix}' is not a CPack "
"staging tree); skipping mtime/mode clamp")
return()
endif()
get_filename_component(_staging "${_staging}" ABSOLUTE)

if(NOT IS_DIRECTORY "${_staging}")
message(STATUS "Reproducible build: staging root '${_staging}' absent, "
"skipping mtime/mode clamp")
return()
endif()

# SOURCE_DATE_EPOCH is baked in by configure_file-style substitution from
# the parent CMakeLists (see install(SCRIPT) call site).
set(_epoch "@SOURCE_DATE_EPOCH@")
if(NOT _epoch MATCHES "^[0-9]+$")
message(FATAL_ERROR "Reproducible build: SOURCE_DATE_EPOCH must be a "
"non-negative Unix timestamp")
endif()

# Probe GNU touch on a private temp file, not /dev/null: /dev/null is
# root-owned, so probing it fails for normal users and rewrites the
# device node's mtime as root (the bug the review flagged).
execute_process(
COMMAND mktemp
OUTPUT_VARIABLE _probe
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _mktemp_rc)
if(NOT _mktemp_rc EQUAL 0)
message(STATUS "Reproducible build: mktemp failed, skipping mtime clamp")
return()
endif()
execute_process(
COMMAND touch --no-dereference --date=@0 "${_probe}"
RESULT_VARIABLE _touch_rc
OUTPUT_QUIET ERROR_QUIET)
file(REMOVE "${_probe}")
if(NOT _touch_rc EQUAL 0)
message(STATUS "Reproducible build: GNU touch unavailable, "
"skipping mtime clamp")
return()
endif()

# Normalise permission bits first: the make-side prepare creates staging
# dirs with "mkdir -p" (umask-dependent) and CPack's tar records those
# modes verbatim via USE_SOURCE_PERMISSIONS, so umask 027 -> 0750 vs
# umask 022 -> 0755 breaks reproducibility. Match the legacy tar's
# --mode=go-w,a+rX: dirs/exes 0755, regular files 0644.
execute_process(
COMMAND find "${_staging}" -type d -exec chmod 0755 {} +
RESULT_VARIABLE _cd_rc OUTPUT_QUIET ERROR_QUIET)
execute_process(
COMMAND find "${_staging}" -type f -perm -u+x -exec chmod 0755 {} +
RESULT_VARIABLE _cx_rc OUTPUT_QUIET ERROR_QUIET)
execute_process(
COMMAND find "${_staging}" -type f -not -perm -u+x -exec chmod 0644 {} +
RESULT_VARIABLE _cf_rc OUTPUT_QUIET ERROR_QUIET)

# Clamp mtimes last so this is the final metadata write.
execute_process(
COMMAND find "${_staging}" -exec
touch --no-dereference --date=@${_epoch} {} +
RESULT_VARIABLE _find_rc
OUTPUT_QUIET ERROR_QUIET)

if(_find_rc EQUAL 0 AND _cd_rc EQUAL 0 AND _cx_rc EQUAL 0 AND _cf_rc EQUAL 0)
message(STATUS "Reproducible build: clamped mtimes and modes under ${_staging}")
else()
message(WARNING "Reproducible build: mtime/mode clamp incomplete under ${_staging}")
endif()
55 changes: 55 additions & 0 deletions src/Build/Tools/makeself_repro_finalize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
#
# Copyright (c) 2026 VeraCrypt
# Governed by the Apache License 2.0.
#
# Zero the gzip mtime in a makeself archive and refresh its integrity
# fields. makeself runs `gzip -c9 < tmpfile' which writes tmpfile's
# mtime into the gzip header (gzip ignores SOURCE_DATE_EPOCH for
# redirected stdin), so the installer is otherwise not reproducible.
#
# After editing the payload the recorded checksums are refreshed:
# - CRCsum is set to "0000000000". Makeself stores a POSIX cksum(1)
# value there, not a zlib CRC-32 (the two differ); an all-zero
# CRCsum makes its extractor skip the redundant CRC check.
# - MD5 is recomputed, which the extractor still verifies.
#
# Usage: makeself_repro_finalize.py <archive>

import hashlib
import re
import sys


def finalize(path):
with open(path, "rb") as f:
raw = bytearray(f.read())
text = raw.decode("latin1", errors="replace")
# Locate payload start by line count, mirroring makeself's own extractor.
m = re.search(r'^skip="(\d+)"', text, re.MULTILINE)
if not m:
sys.exit(f"{path}: no skip= line in makeself header")
skip = int(m.group(1))
header_text = "\n".join(text.split("\n")[:skip]) + "\n"
offset = len(header_text.encode("latin1"))
if bytes(raw[offset:offset + 3]) != b"\x1f\x8b\x08":
sys.exit(f"{path}: no gzip magic at payload offset {offset}")
# gzip header mtime: 4-byte LE uint at offset+4 (RFC 1952 section 2.3.1).
raw[offset + 4:offset + 8] = b"\x00\x00\x00\x00"
payload = bytes(raw[offset:])
new_md5 = hashlib.md5(payload).hexdigest()
# CRCsum -> all zeros (extractor then skips the CRC check); MD5 -> fresh.
new_header = re.sub(r'CRCsum="[^"]*"', 'CRCsum="0000000000"', header_text)
new_header = re.sub(r'MD5="[0-9a-fA-F]+"', f'MD5="{new_md5}"', new_header)
new_bytes = new_header.encode("latin1")
# Line count must stay the same so makeself's "skip=" remains accurate.
if new_bytes.count(b"\n") != skip:
sys.exit(f"{path}: header line count changed during rewrite")
with open(path, "wb") as f:
f.write(new_bytes + payload)


if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit("Usage: makeself_repro_finalize.py <archive>")
finalize(sys.argv[1])
24 changes: 22 additions & 2 deletions src/Build/build_cmake_deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@
# Errors should cause script to exit
set -e

# Deterministic umask: dpkg-deb records the mode of the temporary ar
# members (debian-binary, control.tar.gz, data.tar.gz) it creates, so a
# caller umask of 027 yields 0640 where 022 yields 0644 and the .deb is
# not reproducible. Pin it for the whole packaging run.
umask 022

# Compute and export SOURCE_DATE_EPOCH so cmake/cpack inherit it (they get
# an empty env from this shell otherwise). Precedence: caller, git HEAD,
# fallback constant matching src/Makefile and CMakeLists.txt.
if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then
SOURCE_DATE_EPOCH=$(git -C "$(dirname "$0")/../.." log -1 --pretty=%ct 2>/dev/null || echo 1577836800)
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

# Absolute path to this script
export SCRIPT=$(readlink -f "$0")
# Absolute path this script is in
Expand Down Expand Up @@ -158,8 +178,8 @@ rm -rf $PARENTDIR/VeraCrypt_Packaging
mkdir -p $PARENTDIR/VeraCrypt_Packaging/GUI
mkdir -p $PARENTDIR/VeraCrypt_Packaging/Console

cmake -H$SCRIPTPATH -B$PARENTDIR/VeraCrypt_Packaging/GUI -DVERACRYPT_BUILD_DIR="$PARENTDIR/VeraCrypt_Setup/GUI" -DNOGUI=FALSE $FUSE3_CMAKE_FLAG || 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
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
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
cpack --config $PARENTDIR/VeraCrypt_Packaging/Console/CPackConfig.cmake || exit 1
3 changes: 3 additions & 0 deletions src/Build/build_cmake_opensuse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
# Errors should cause script to exit
set -e

# Keep staged RPM payload permissions independent of the caller's umask.
umask 022

# Absolute path to this script
export SCRIPT=$(readlink -f "$0")
# Absolute path this script is in
Expand Down
3 changes: 3 additions & 0 deletions src/Build/build_cmake_rpm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
# Errors should cause script to exit
set -e

# Keep staged RPM payload permissions independent of the caller's umask.
umask 022

# Absolute path to this script
export SCRIPT=$(readlink -f "$0")
# Absolute path this script is in
Expand Down
Loading
Loading