Skip to content

Commit c83948e

Browse files
committed
fix(os): make mkosi builds reproducible
1 parent cc482dd commit c83948e

11 files changed

Lines changed: 111 additions & 23 deletions

os/mkosi/build.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ROOT=$(cd "$SELF/../.." && pwd)
77
source "$SELF/versions.env"
88
action=${1:-image}
99
BUILD_DIR=${2:-$SELF/build}
10+
BUILD_DIR=$(realpath -m "$BUILD_DIR")
1011
case "$action" in
1112
image|repro-check|lint) ;;
1213
*) echo "Usage: $0 {image|repro-check|lint} [build-dir]" >&2; exit 2 ;;
@@ -66,6 +67,9 @@ EOF
6667
mkosi --directory "$SELF" --force --extra-tree="$stage" "${devargs[@]}" \
6768
--format=directory --output-directory="$work" --output=rootfs \
6869
--compress-output=no --bootable=no build
70+
# ldconfig's binary auxiliary cache records traversal-dependent ordering.
71+
# /var is volatile at runtime, so ship no host-generated cache.
72+
rm -f "$tree/var/cache/ldconfig/aux-cache"
6973
mkdir -p "$tree/usr/lib/modules"
7074
cp -a "$kstage/usr/lib/modules/." "$tree/usr/lib/modules/"
7175
"$SELF/tests/check-parity.py" "$SELF/parity.json" "$tree" "$kstage" "$flavor"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Prefix-map arguments contain the intentionally different clean-build path.
2+
# They must affect compilation without becoming runtime version metadata.
3+
diff --git a/Makefile b/Makefile
4+
index 1a5115c..87c501c 100644
5+
--- a/Makefile
6+
+++ b/Makefile
7+
@@ -203,7 +203,7 @@ DEPENDENCIES := $(BIN_OBJS:%.o=%.d) $(LIB_OBJS:%.lo=%.d)
8+
$(BUILD_DEFS):
9+
@printf '#define BUILD_DATE "%s"\n' '$(strip $(DATE))' >$(BUILD_DEFS)
10+
@printf '#define BUILD_COMPILER "%s " __VERSION__\n' '$(notdir $(COMPILER))' >>$(BUILD_DEFS)
11+
- @printf '#define BUILD_FLAGS "%s"\n' '$(strip $(CPPFLAGS) $(CFLAGS) $(LDFLAGS))' >>$(BUILD_DEFS)
12+
+ @printf '#define BUILD_FLAGS "%s"\n' '$(strip $(filter-out -ffile-prefix-map=% -fmacro-prefix-map=%,$(CPPFLAGS) $(CFLAGS) $(LDFLAGS)))' >>$(BUILD_DEFS)
13+
@printf '#define BUILD_REVISION "%s"\n' '$(strip $(REVISION))' >>$(BUILD_DEFS)
14+
@printf '#define BUILD_PLATFORM "%s"\n' '$(strip $(PLATFORM))' >>$(BUILD_DEFS)
15+

os/mkosi/scripts/build-container-stack.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ SELF="$ROOT/os/mkosi"
77
source "$SELF/versions.env"
88
BUILD_DIR=${1:?build directory required}
99
STAGE=${2:?rootfs staging tree required}
10+
BUILD_DIR=$(realpath -m "$BUILD_DIR")
11+
STAGE=$(realpath -m "$STAGE")
1012
mkdir -p "$BUILD_DIR/downloads" "$STAGE/usr/bin"
1113

1214
checkout() {
@@ -17,6 +19,7 @@ checkout() {
1719
git -C "$dir" fetch -q --depth=1 origin "$rev"
1820
git -C "$dir" checkout -q --detach FETCH_HEAD
1921
git -C "$dir" reset -q --hard "$rev"
22+
git -C "$dir" clean -qfdx
2023
}
2124
fetch_sha256() {
2225
local url=$1 sha=$2 output=$3
@@ -26,8 +29,17 @@ fetch_sha256() {
2629

2730
lnc="$BUILD_DIR/libnvidia-container"
2831
checkout https://github.com/NVIDIA/libnvidia-container.git "$LIBNVIDIA_CONTAINER_REVISION" "$lnc"
32+
patch -d "$lnc" -p1 --fuzz=0 < \
33+
"$SELF/patches/libnvidia-container/0001-omit-prefix-map-from-build-flags.patch"
34+
export CFLAGS="${CFLAGS:-} -O2 -g0 -ffile-prefix-map=$BUILD_DIR=/usr/src/container-stack -fmacro-prefix-map=$BUILD_DIR=/usr/src/container-stack"
35+
export LDFLAGS="${LDFLAGS:-} -Wl,--build-id=none"
36+
export CGO_CFLAGS="$CFLAGS"
37+
export GOFLAGS="${GOFLAGS:-} -trimpath -buildvcs=false"
2938
make -C "$lnc" -j"${JOBS:-$(nproc)}" LIB_VERSION=1.18.1 \
3039
WITH_NVCGO=yes WITH_LIBELF=yes WITH_TIRPC=yes all
40+
# Upstream adds a CRC of its unshipped split-debug file. The debug file is not
41+
# installed, and its CRC varies with the clean build directory.
42+
objcopy --remove-section=.gnu_debuglink "$lnc/libnvidia-container.so.1.18.1"
3143
install -m0755 "$lnc/nvidia-container-cli" "$STAGE/usr/bin/"
3244
install -Dm0755 "$lnc/libnvidia-container.so.1.18.1" "$STAGE/usr/lib/x86_64-linux-gnu/libnvidia-container.so.1.18.1"
3345
ln -sfn libnvidia-container.so.1.18.1 "$STAGE/usr/lib/x86_64-linux-gnu/libnvidia-container.so.1"

os/mkosi/scripts/build-kernel.sh

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,37 @@ MKOSI_DIR="$ROOT/os/mkosi"
77
source "$MKOSI_DIR/versions.env"
88
BUILD_DIR=${1:?build directory required}
99
STAGING=${2:?staging tree required}
10+
BUILD_DIR=$(realpath -m "$BUILD_DIR")
11+
STAGING=$(realpath -m "$STAGING")
1012
JOBS=${JOBS:-$(nproc)}
1113
export KBUILD_BUILD_TIMESTAMP="@${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH required}"
1214
export KBUILD_BUILD_USER=dstack KBUILD_BUILD_HOST=reproducible
1315
export KBUILD_BUILD_VERSION=1
1416
export KCONFIG_NOTIMESTAMP=1
17+
# Neither the source nor output directory may leak into DWARF/BTF or module
18+
# metadata: repro-check deliberately builds in two differently named trees.
19+
kernel_map="-fdebug-prefix-map=$BUILD_DIR=/usr/src/linux -fmacro-prefix-map=$BUILD_DIR=/usr/src/linux"
20+
export KCFLAGS="${KCFLAGS:-} $kernel_map"
21+
export KAFLAGS="${KAFLAGS:-} $kernel_map"
22+
export KCPPFLAGS="${KCPPFLAGS:-} $kernel_map"
1523

1624
mkdir -p "$BUILD_DIR/downloads" "$BUILD_DIR/kernel-build" "$STAGING"
25+
# pahole 1.25 produces a different BTF type order when its encoder runs with
26+
# Kbuild's parallel job count. Keep compilation parallel, but serialize BTF
27+
# encoding so vmlinux and bzImage are byte-for-byte reproducible.
28+
pahole_wrapper="$BUILD_DIR/pahole-reproducible"
29+
cat > "$pahole_wrapper" <<'EOF'
30+
#!/bin/bash
31+
set -euo pipefail
32+
args=()
33+
for arg; do
34+
# dwarves 1.25's parallel and optimized-function encoders both depend on
35+
# unstable traversal order. Neither is needed for BTF correctness.
36+
[[ $arg == -j* || $arg == --btf_gen_optimized ]] || args+=("$arg")
37+
done
38+
exec pahole -j1 "${args[@]}"
39+
EOF
40+
chmod 0755 "$pahole_wrapper"
1741
tarball="$BUILD_DIR/downloads/linux-$KERNEL_VERSION.tar.xz"
1842
if [[ ! -f $tarball ]]; then
1943
curl --fail --location --retry 3 -o "$tarball.tmp" \
@@ -32,14 +56,14 @@ for patch in \
3256
patch -d "$src" -p1 --fuzz=0 < "$patch"
3357
done
3458

35-
make -C "$src" O="$BUILD_DIR/kernel-build" x86_64_defconfig
59+
make -C "$src" O="$BUILD_DIR/kernel-build" PAHOLE="$pahole_wrapper" x86_64_defconfig
3660
"$src/scripts/kconfig/merge_config.sh" -m -O "$BUILD_DIR/kernel-build" \
3761
"$BUILD_DIR/kernel-build/.config" "$MKOSI_DIR/kernel.config"
38-
make -C "$src" O="$BUILD_DIR/kernel-build" olddefconfig
62+
make -C "$src" O="$BUILD_DIR/kernel-build" PAHOLE="$pahole_wrapper" olddefconfig
3963
"$MKOSI_DIR/scripts/check-kernel-config.sh" "$BUILD_DIR/kernel-build/.config"
40-
make -C "$src" O="$BUILD_DIR/kernel-build" -j"$JOBS" bzImage modules
64+
make -C "$src" O="$BUILD_DIR/kernel-build" PAHOLE="$pahole_wrapper" -j"$JOBS" bzImage modules
4165
make -C "$src" O="$BUILD_DIR/kernel-build" \
42-
INSTALL_MOD_PATH="$STAGING" modules_install
66+
PAHOLE="$pahole_wrapper" INSTALL_MOD_PATH="$STAGING" modules_install
4367
# Debian is usr-merged: /lib is a symlink. Normalize modules into /usr/lib so
4468
# ExtraTrees never tries to replace that symlink with a directory.
4569
if [[ -d $STAGING/lib/modules ]]; then

os/mkosi/scripts/build-nvattest.sh

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,41 @@ SELF="$ROOT/os/mkosi"
77
source "$SELF/versions.env"
88
BUILD_DIR=${1:?build directory required}
99
STAGE=${2:?rootfs staging tree required}
10-
src="$BUILD_DIR/attestation-sdk"
11-
build="$BUILD_DIR/build"
10+
BUILD_DIR=$(realpath -m "$BUILD_DIR")
11+
STAGE=$(realpath -m "$STAGE")
12+
checkout="$BUILD_DIR/attestation-sdk"
13+
# Cargo includes the literal RUSTFLAGS in crate disambiguators. A prefix-map
14+
# containing work-a/work-b would therefore change Rust symbol hashes even
15+
# though the mapped paths are equal. Serialize this component in a stable,
16+
# clean pathname and keep the per-build checkout as the verified input.
17+
canonical=/var/tmp/dstack-mkosi-nvattest
18+
exec 9>"$canonical.lock"
19+
flock 9
20+
src="$canonical/attestation-sdk"
21+
build="$canonical/build"
1222

13-
if [[ ! -d $src/.git ]]; then
23+
if [[ ! -d $checkout/.git ]]; then
1424
mkdir -p "$BUILD_DIR"
15-
git init -q "$src"
16-
git -C "$src" remote add origin https://github.com/NVIDIA/attestation-sdk.git
25+
git init -q "$checkout"
26+
git -C "$checkout" remote add origin https://github.com/NVIDIA/attestation-sdk.git
1727
fi
18-
git -C "$src" fetch -q --depth=1 origin "$NVATTEST_REVISION"
19-
git -C "$src" checkout -q --detach FETCH_HEAD
20-
git -C "$src" reset -q --hard "$NVATTEST_REVISION"
28+
git -C "$checkout" fetch -q --depth=1 origin "$NVATTEST_REVISION"
29+
git -C "$checkout" checkout -q --detach FETCH_HEAD
30+
git -C "$checkout" reset -q --hard "$NVATTEST_REVISION"
31+
git -C "$checkout" clean -qfdx
32+
rm -rf "$canonical"
33+
mkdir -p "$canonical"
34+
cp -a "$checkout" "$src"
2135
patch -d "$src" -p1 --fuzz=0 < \
2236
"$ROOT/os/yocto/layers/meta-nvidia/recipes-graphics/nvattest/files/0001-validate-ocsp-response-freshness.patch"
2337
patch -d "$src" -p1 --fuzz=0 < \
2438
"$SELF/patches/nvattest/0001-pin-fetchcontent-inputs.patch"
2539

2640
rm -rf "$build"
41+
export CFLAGS="${CFLAGS:-} -O2 -g0 -ffile-prefix-map=$canonical=/usr/src/nvattest -fmacro-prefix-map=$canonical=/usr/src/nvattest"
42+
export CXXFLAGS="${CXXFLAGS:-} -O2 -g0 -ffile-prefix-map=$canonical=/usr/src/nvattest -fmacro-prefix-map=$canonical=/usr/src/nvattest"
43+
export CARGO_INCREMENTAL=0
44+
export RUSTFLAGS="--remap-path-prefix=$canonical=/usr/src/nvattest -C strip=debuginfo"
2745
cmake -S "$src/nv-attestation-cli" -B "$build" -G Ninja \
2846
-DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \
2947
-DNVAT_BUILD_TESTS=OFF -DNVAT_BUILD_SAMPLES=OFF \
@@ -32,8 +50,6 @@ cmake -S "$src/nv-attestation-cli" -B "$build" -G Ninja \
3250
install -m0644 \
3351
"$ROOT/os/yocto/layers/meta-nvidia/recipes-graphics/nvattest/files/regorus-ffi-Cargo.lock" \
3452
"$build/_deps/regorus-src/bindings/ffi/Cargo.lock"
35-
export CARGO_INCREMENTAL=0
36-
export RUSTFLAGS="--remap-path-prefix=$BUILD_DIR=/usr/src/nvattest -C strip=debuginfo"
3753
cmake --build "$build" -j"${JOBS:-$(nproc)}"
3854
cmp "$ROOT/os/yocto/layers/meta-nvidia/recipes-graphics/nvattest/files/regorus-ffi-Cargo.lock" \
3955
"$build/_deps/regorus-src/bindings/ffi/Cargo.lock"

os/mkosi/scripts/build-nvidia.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ rm -rf "$src"
3232
chmod +x "$run"
3333
"$run" -x --target "$src" >/dev/null
3434
patch -d "$src" -p1 --fuzz=0 < "$SELF/patches/nvidia/0001-linux-7.1-drop-legacy-of-gpio-api.patch"
35+
module_map="-fdebug-prefix-map=$BUILD_DIR=/usr/src/nvidia -fmacro-prefix-map=$BUILD_DIR=/usr/src/nvidia -fdebug-prefix-map=$KERNEL_SRC=/usr/src/linux -fmacro-prefix-map=$KERNEL_SRC=/usr/src/linux -fdebug-prefix-map=$KERNEL_BUILD=/usr/src/linux-build -fmacro-prefix-map=$KERNEL_BUILD=/usr/src/linux-build"
3536
# Linux 7.1's gen-btf.sh cannot execute its space-containing PAHOLE wrapper
3637
# for external modules (the awk program is passed with literal quotes). Keep
3738
# BTF enabled for the kernel/in-tree modules, but omit it for NVIDIA's modules.
3839
make -C "$src/kernel-open" -j"${JOBS:-$(nproc)}" \
3940
SYSSRC="$KERNEL_SRC" SYSOUT="$KERNEL_BUILD" \
40-
CONFIG_DEBUG_INFO_BTF_MODULES= modules
41+
KCFLAGS="$module_map" CONFIG_DEBUG_INFO_BTF_MODULES= modules
4142
make -C "$src/kernel-open" SYSSRC="$KERNEL_SRC" SYSOUT="$KERNEL_BUILD" \
42-
CONFIG_DEBUG_INFO_BTF_MODULES= INSTALL_MOD_PATH="$KERNEL_STAGE" modules_install
43+
KCFLAGS="$module_map" CONFIG_DEBUG_INFO_BTF_MODULES= INSTALL_MOD_PATH="$KERNEL_STAGE" modules_install
4344
if [[ -d $KERNEL_STAGE/lib/modules ]]; then
4445
mkdir -p "$KERNEL_STAGE/usr/lib/modules"
4546
cp -a "$KERNEL_STAGE/lib/modules/." "$KERNEL_STAGE/usr/lib/modules/"

os/mkosi/scripts/build-ovmf.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ set -euo pipefail
66
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)
77
BUILD_DIR=${1:?build directory required}
88
OUT=${2:?output file required}
9+
BUILD_DIR=$(realpath -m "$BUILD_DIR")
10+
OUT=$(realpath -m "$OUT")
911
REV=fbe0805b2091393406952e84724188f8c1941837
1012
src="$BUILD_DIR/edk2"
1113
if [[ ! -d $src/.git ]]; then

os/mkosi/scripts/build-sysbox.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ SELF="$ROOT/os/mkosi"
77
source "$SELF/versions.env"
88
B=${1:?build directory required}
99
STAGE=${2:?rootfs staging tree required}
10+
B=$(realpath -m "$B")
11+
STAGE=$(realpath -m "$STAGE")
1012
checkout() {
1113
local url=$1 rev=$2 dir=$3
1214
if [[ ! -d $dir/.git ]]; then git init -q "$dir"; git -C "$dir" remote add origin "$url"; fi

os/mkosi/scripts/build-zfs.sh

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ KERNEL_STAGE=$(realpath -m "${5:?kernel staging tree required}")
1313
src="$B/zfs"
1414
export CFLAGS="${CFLAGS:-} -O2 -g0 -ffile-prefix-map=$B=/usr/src/zfs"
1515
export CXXFLAGS="${CXXFLAGS:-} -O2 -g0 -ffile-prefix-map=$B=/usr/src/zfs"
16-
export KCFLAGS="${KCFLAGS:-} -fmacro-prefix-map=$B=/usr/src/zfs"
16+
export KCFLAGS="${KCFLAGS:-} -fdebug-prefix-map=$B=/usr/src/zfs -fmacro-prefix-map=$B=/usr/src/zfs -fdebug-prefix-map=$KERNEL_SRC=/usr/src/linux -fmacro-prefix-map=$KERNEL_SRC=/usr/src/linux -fdebug-prefix-map=$KERNEL_BUILD=/usr/src/linux-build -fmacro-prefix-map=$KERNEL_BUILD=/usr/src/linux-build"
17+
export KAFLAGS="${KAFLAGS:-} $KCFLAGS"
18+
export KCPPFLAGS="${KCPPFLAGS:-} $KCFLAGS"
1719
if [[ ! -d $src/.git ]]; then git init -q "$src"; git -C "$src" remote add origin https://github.com/openzfs/zfs.git; fi
1820
git -C "$src" fetch -q --depth=1 origin "$ZFS_REVISION"
1921
git -C "$src" checkout -q --detach FETCH_HEAD
@@ -27,8 +29,11 @@ mkdir -p "$B/build"
2729
--with-linux-obj="$KERNEL_BUILD" --with-config=all --enable-systemd \
2830
--disable-pyzfs --without-dracutdir --with-udevdir=/usr/lib/udev \
2931
--with-systemdunitdir=/usr/lib/systemd/system)
30-
make -C "$B/build" -j"${JOBS:-$(nproc)}"
31-
make -C "$B/build" DESTDIR="$ROOT_STAGE" install
32+
# Linux 7.1 drives pahole 1.25's nondeterministic optimized encoder for
33+
# external modules. Kernel/in-tree BTF remains enabled; omit only ZFS module
34+
# BTF, as is also required for NVIDIA's external modules on this toolchain.
35+
make -C "$B/build" -j"${JOBS:-$(nproc)}" CONFIG_DEBUG_INFO_BTF_MODULES=
36+
make -C "$B/build" CONFIG_DEBUG_INFO_BTF_MODULES= DESTDIR="$ROOT_STAGE" install
3237
# ZFS installs kernel modules below DESTDIR/lib; merge them into usr-merge.
3338
if [[ -d $ROOT_STAGE/lib/modules ]]; then
3439
mkdir -p "$KERNEL_STAGE/usr/lib/modules"

os/mkosi/scripts/make-release-artifacts.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ install -m0644 "$kernel" "$OUT/files/bzImage"
1818

1919
# Combined squashfs + dm-verity tree, matching Yocto's separate-hash=0 output.
2020
rootfs="$OUT/files/rootfs.squashfs.verity"
21+
# mksquashfs -noappend overwrites its filesystem but does not reliably remove
22+
# a longer dm-verity tail left by a previous invocation.
23+
truncate -s 0 "$rootfs"
2124
env -u SOURCE_DATE_EPOCH mksquashfs "$TREE" "$rootfs" -noappend -all-root -no-progress \
2225
-comp zstd -mkfs-time "$SOURCE_DATE_EPOCH" -all-time "$SOURCE_DATE_EPOCH" >/dev/null
2326
data_size=$(stat -c %s "$rootfs")
2427
data_size=$(( (data_size + 4095) / 4096 * 4096 ))
2528
truncate -s "$data_size" "$rootfs"
2629
verity_output=$(veritysetup format "$rootfs" "$rootfs" \
2730
--hash-offset="$data_size" --data-block-size=4096 --hash-block-size=4096 \
31+
--uuid=00000000-0000-0000-0000-000000000000 \
2832
--salt="$(printf '%064x' 0)")
2933
root_hash=$(awk '/Root hash:/ {print $3}' <<<"$verity_output")
3034
[[ $root_hash =~ ^[0-9a-f]{64}$ ]] || { echo "failed to obtain verity root hash" >&2; exit 1; }

0 commit comments

Comments
 (0)