diff --git a/misc/AMDSEV/README.md b/misc/AMDSEV/README.md index 1e17bf731..db7883b86 100644 --- a/misc/AMDSEV/README.md +++ b/misc/AMDSEV/README.md @@ -233,12 +233,46 @@ Use `ovmf-metadata` to inspect the OVMF firmware's SEV metadata sections: ## Reproducible Builds -Set `SOURCE_DATE_EPOCH` for deterministic output: +`SOURCE_DATE_EPOCH` is required for deterministic builds: ```sh -SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) ./misc/AMDSEV/build.sh +export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) +./misc/AMDSEV/build.sh +``` + +For stronger package source determinism, pin apt to a snapshot: + +```sh +export APT_SNAPSHOT_URL="http://snapshot.ubuntu.com/ubuntu/20250115T000000Z/" +export APT_SNAPSHOT_SUITE="noble" +export APT_SNAPSHOT_COMPONENTS="main" ``` +If building in a containerized pipeline, set the image digest for provenance tracking: + +```sh +export BUILD_CONTAINER_IMAGE_DIGEST="sha256:" +``` + +Run a built-in reproducibility check (double build + hash compare): + +```sh +export KATANA_STRICT_REPRO=1 # optional: requires vendored cargo deps for strict katana reproducibility +./misc/AMDSEV/build.sh --katana /path/to/katana --repro-check +``` + +You can also compare two build output directories directly: + +```sh +./misc/AMDSEV/verify-build.sh --compare /path/to/build-a /path/to/build-b +``` + +Each build writes deterministic provenance metadata to: +- `build-info.txt` +- `materials.lock` + +See [`REPRODUCIBILITY.md`](./REPRODUCIBILITY.md) for the full policy. + ## Troubleshooting ### `SEV: guest firmware hashes table area is invalid (base=0x0 size=0x0)` diff --git a/misc/AMDSEV/REPRODUCIBILITY.md b/misc/AMDSEV/REPRODUCIBILITY.md new file mode 100644 index 000000000..8f1925d8e --- /dev/null +++ b/misc/AMDSEV/REPRODUCIBILITY.md @@ -0,0 +1,43 @@ +# AMD SEV-SNP Build Reproducibility Policy + +## Scope + +This policy targets byte-identical outputs for the following artifacts when built with the same: +- source tree revision +- `build-config` pins +- `SOURCE_DATE_EPOCH` +- toolchain/runtime environment + +Artifacts: +- `OVMF.fd` +- `vmlinuz` +- `initrd.img` +- `katana` + +## Required Inputs + +- `SOURCE_DATE_EPOCH` must be explicitly set and fixed. +- `OVMF_COMMIT` must be pinned. +- Package versions and SHA256 values in `build-config` must remain pinned. +- `BUILD_CONTAINER_IMAGE_DIGEST` should be set when using a containerized CI pipeline. +- For katana, prefer passing a prebuilt pinned binary via `--katana`. If auto-building, set `KATANA_STRICT_REPRO=1` with vendored dependencies. + +## Stronger Package Source Determinism + +To avoid host apt source drift, set: +- `APT_SNAPSHOT_URL` +- `APT_SNAPSHOT_SUITE` +- `APT_SNAPSHOT_COMPONENTS` + +If unset, build scripts use host apt sources and reproducibility guarantees are weaker. + +## Validation + +- Use `./misc/AMDSEV/build.sh --repro-check` to run a double-build and hash comparison. +- Use `./misc/AMDSEV/verify-build.sh --compare DIR_A DIR_B` for explicit directory comparisons. + +## Provenance Files + +Each build emits: +- `build-info.txt` with pinned inputs and output checksums +- `materials.lock` with immutable input and artifact hashes diff --git a/misc/AMDSEV/build-config b/misc/AMDSEV/build-config index feba66845..11eba29c0 100644 --- a/misc/AMDSEV/build-config +++ b/misc/AMDSEV/build-config @@ -20,3 +20,13 @@ BUSYBOX_PKG_SHA256="944b2728f53ceb3916cec2c962873c9951e612408099601751db2a0a5d81 # Kernel modules extra (Ubuntu package, for initrd SEV-SNP support) KERNEL_MODULES_EXTRA_PKG_VERSION="6.8.0-90.91" KERNEL_MODULES_EXTRA_PKG_SHA256="c17bd76779ce68a076dc1ef9b1669947f35f1868f6736dbd0a8a7ccacf7571f3" + +# Optional apt snapshot source for stronger reproducibility guarantees. +# When set, build scripts use this source instead of host /etc/apt/sources.list. +# Example: +# APT_SNAPSHOT_URL="http://snapshot.ubuntu.com/ubuntu/20250115T000000Z/" +# APT_SNAPSHOT_SUITE="noble" +# APT_SNAPSHOT_COMPONENTS="main" +APT_SNAPSHOT_URL="" +APT_SNAPSHOT_SUITE="noble" +APT_SNAPSHOT_COMPONENTS="main" diff --git a/misc/AMDSEV/build-initrd.sh b/misc/AMDSEV/build-initrd.sh index 5abf1ac04..c4e99c678 100755 --- a/misc/AMDSEV/build-initrd.sh +++ b/misc/AMDSEV/build-initrd.sh @@ -20,12 +20,26 @@ # KERNEL_MODULES_EXTRA_PKG_VERSION REQUIRED. Exact apt package version. # KERNEL_MODULES_EXTRA_PKG_SHA256 REQUIRED. SHA256 checksum of the modules .deb package. # +# Optional environment (stronger package source determinism): +# APT_SNAPSHOT_URL +# APT_SNAPSHOT_SUITE +# APT_SNAPSHOT_COMPONENTS +# # ============================================================================== set -euo pipefail +# Environment normalization for reproducibility. +export TZ=UTC +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 +umask 022 + REQUIRED_APPLETS=(sh mount umount sleep kill cat mkdir ln mknod ip insmod poweroff sync) SYMLINK_APPLETS=(sh mount umount mkdir mknod switch_root ip insmod sleep kill cat ln poweroff sync) +APT_SNAPSHOT_DIR="" +APT_SOURCE_MODE="host" +declare -a APT_OPTS=() usage() { echo "Usage: $0 KATANA_BINARY OUTPUT_INITRD [KERNEL_VERSION]" @@ -44,6 +58,9 @@ usage() { echo " BUSYBOX_PKG_SHA256 SHA256 checksum of the busybox .deb package" echo " KERNEL_MODULES_EXTRA_PKG_VERSION Exact apt package version for linux-modules-extra" echo " KERNEL_MODULES_EXTRA_PKG_SHA256 SHA256 checksum of the linux-modules-extra .deb" + echo " APT_SNAPSHOT_URL Optional apt snapshot URL for deterministic package resolution" + echo " APT_SNAPSHOT_SUITE Snapshot suite when APT_SNAPSHOT_URL is set" + echo " APT_SNAPSHOT_COMPONENTS Snapshot components when APT_SNAPSHOT_URL is set" echo "" echo "EXAMPLES:" echo " export SOURCE_DATE_EPOCH=\$(date +%s)" @@ -79,6 +96,47 @@ die() { exit 1 } +run_cmd() { + echo "$*" + "$@" || die "$*" +} + +setup_apt_snapshot() { + : "${APT_SNAPSHOT_URL:?APT_SNAPSHOT_URL is required when snapshot mode is enabled}" + : "${APT_SNAPSHOT_SUITE:?APT_SNAPSHOT_SUITE is required when snapshot mode is enabled}" + + local components="${APT_SNAPSHOT_COMPONENTS:-main}" + local sources_list + + APT_SNAPSHOT_DIR="$(mktemp -d)" + mkdir -p "$APT_SNAPSHOT_DIR/lists/partial" "$APT_SNAPSHOT_DIR/cache/partial" + + sources_list="$APT_SNAPSHOT_DIR/sources.list" + printf "deb [check-valid-until=no] %s %s %s\n" \ + "$APT_SNAPSHOT_URL" "$APT_SNAPSHOT_SUITE" "$components" > "$sources_list" + + APT_OPTS=( + -o "Dir::Etc::sourcelist=$sources_list" + -o "Dir::Etc::sourceparts=-" + -o "Dir::State::Lists=$APT_SNAPSHOT_DIR/lists" + -o "Dir::Cache::archives=$APT_SNAPSHOT_DIR/cache" + -o "Dir::State::status=/var/lib/dpkg/status" + -o "APT::Get::List-Cleanup=0" + ) + + log_info "Using apt snapshot source" + log_info " URL: $APT_SNAPSHOT_URL" + log_info " Suite: $APT_SNAPSHOT_SUITE" + log_info " Components: $components" + run_cmd apt-get "${APT_OPTS[@]}" update + APT_SOURCE_MODE="snapshot" +} + +apt_download() { + local package="$1" + run_cmd apt-get "${APT_OPTS[@]}" download "$package" +} + # Show help if requested or insufficient arguments if [[ $# -lt 2 ]] || [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then usage @@ -89,20 +147,17 @@ OUTPUT_INITRD="$2" KERNEL_VERSION="${3:-${KERNEL_VERSION:?KERNEL_VERSION must be set or passed as third argument}}" OUTPUT_DIR="$(dirname "$OUTPUT_INITRD")" -log_section "Building Initrd" -echo "Configuration:" -echo " Katana binary: $KATANA_BINARY" -echo " Output initrd: $OUTPUT_INITRD" -echo " Kernel version: $KERNEL_VERSION" -echo " SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH:-}" -echo "" -echo "Package versions:" -echo " busybox-static: ${BUSYBOX_PKG_VERSION:-}" -echo " linux-modules-extra: ${KERNEL_MODULES_EXTRA_PKG_VERSION:-}" - if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then die "SOURCE_DATE_EPOCH must be set for reproducible builds" fi +if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then + die "SOURCE_DATE_EPOCH must be an integer unix timestamp" +fi + +: "${BUSYBOX_PKG_VERSION:?BUSYBOX_PKG_VERSION not set - required for reproducible builds}" +: "${BUSYBOX_PKG_SHA256:?BUSYBOX_PKG_SHA256 not set - required for reproducible builds}" +: "${KERNEL_MODULES_EXTRA_PKG_VERSION:?KERNEL_MODULES_EXTRA_PKG_VERSION not set - required for reproducible builds}" +: "${KERNEL_MODULES_EXTRA_PKG_SHA256:?KERNEL_MODULES_EXTRA_PKG_SHA256 not set - required for reproducible builds}" if [[ ! -f "$KATANA_BINARY" ]]; then die "Katana binary not found: $KATANA_BINARY" @@ -124,12 +179,33 @@ for tool in "${REQUIRED_TOOLS[@]}"; do done log_ok "Preflight validation complete" +if [[ -n "${APT_SNAPSHOT_URL:-}" ]]; then + setup_apt_snapshot +else + log_warn "APT_SNAPSHOT_URL not set, using host apt sources (weaker reproducibility)" +fi + +log_section "Building Initrd" +echo "Configuration:" +echo " Katana binary: $KATANA_BINARY" +echo " Output initrd: $OUTPUT_INITRD" +echo " Kernel version: $KERNEL_VERSION" +echo " SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH}" +echo " APT source mode: ${APT_SOURCE_MODE}" +echo "" +echo "Package versions:" +echo " busybox-static: ${BUSYBOX_PKG_VERSION}" +echo " linux-modules-extra: ${KERNEL_MODULES_EXTRA_PKG_VERSION}" + WORK_DIR="$(mktemp -d)" cleanup() { local exit_code=$? - if [[ -d "$WORK_DIR" ]]; then + if [[ -n "${WORK_DIR:-}" ]] && [[ -d "$WORK_DIR" ]]; then rm -rf "$WORK_DIR" fi + if [[ -n "${APT_SNAPSHOT_DIR:-}" ]] && [[ -d "$APT_SNAPSHOT_DIR" ]]; then + rm -rf "$APT_SNAPSHOT_DIR" + fi exit "$exit_code" } trap cleanup EXIT INT TERM @@ -146,22 +222,16 @@ mkdir -p "$PACKAGES_DIR" pushd "$PACKAGES_DIR" >/dev/null -: "${BUSYBOX_PKG_VERSION:?BUSYBOX_PKG_VERSION not set - required for reproducible builds}" -: "${KERNEL_MODULES_EXTRA_PKG_VERSION:?KERNEL_MODULES_EXTRA_PKG_VERSION not set - required for reproducible builds}" - log_info "Downloading busybox-static=${BUSYBOX_PKG_VERSION}" -apt-get download "busybox-static=${BUSYBOX_PKG_VERSION}" +apt_download "busybox-static=${BUSYBOX_PKG_VERSION}" log_info "Downloading linux-modules-extra-${KERNEL_VERSION}-generic=${KERNEL_MODULES_EXTRA_PKG_VERSION}" -apt-get download "linux-modules-extra-${KERNEL_VERSION}-generic=${KERNEL_MODULES_EXTRA_PKG_VERSION}" +apt_download "linux-modules-extra-${KERNEL_VERSION}-generic=${KERNEL_MODULES_EXTRA_PKG_VERSION}" echo "" echo "Downloaded packages:" ls -lh *.deb -: "${BUSYBOX_PKG_SHA256:?BUSYBOX_PKG_SHA256 not set - required for reproducible builds}" -: "${KERNEL_MODULES_EXTRA_PKG_SHA256:?KERNEL_MODULES_EXTRA_PKG_SHA256 not set - required for reproducible builds}" - log_info "Verifying busybox-static checksum" ACTUAL_SHA256="$(sha256sum busybox-static_*.deb | awk '{print $1}')" if [[ "$ACTUAL_SHA256" != "$BUSYBOX_PKG_SHA256" ]]; then @@ -187,10 +257,10 @@ EXTRACTED_DIR="$WORK_DIR/extracted" mkdir -p "$EXTRACTED_DIR" log_info "Extracting busybox-static" -dpkg-deb -x "$PACKAGES_DIR"/busybox-static_*.deb "$EXTRACTED_DIR" +run_cmd dpkg-deb -x "$PACKAGES_DIR"/busybox-static_*.deb "$EXTRACTED_DIR" log_info "Extracting linux-modules-extra" -dpkg-deb -x "$PACKAGES_DIR"/linux-modules-extra-*.deb "$EXTRACTED_DIR" +run_cmd dpkg-deb -x "$PACKAGES_DIR"/linux-modules-extra-*.deb "$EXTRACTED_DIR" log_ok "Packages extracted" # ============================================================================== @@ -585,6 +655,7 @@ echo "==========================================" echo "[OK] Initrd created successfully!" echo "==========================================" echo "Output file: $OUTPUT_INITRD" +echo "APT mode: $APT_SOURCE_MODE" echo "Size: $(du -h "$OUTPUT_INITRD" | cut -f1)" echo "SHA256: $(sha256sum "$OUTPUT_INITRD" | cut -d' ' -f1)" echo "==========================================" diff --git a/misc/AMDSEV/build-kernel.sh b/misc/AMDSEV/build-kernel.sh index 6404b54dd..c262c2063 100755 --- a/misc/AMDSEV/build-kernel.sh +++ b/misc/AMDSEV/build-kernel.sh @@ -10,6 +10,12 @@ # # Environment (required): # KERNEL_VERSION Kernel version to download (e.g., 6.8.0-90) +# SOURCE_DATE_EPOCH Unix timestamp for reproducible output metadata +# +# Environment (optional for stronger reproducibility): +# APT_SNAPSHOT_URL Snapshot apt base URL +# APT_SNAPSHOT_SUITE Snapshot suite (e.g., noble) +# APT_SNAPSHOT_COMPONENTS Snapshot components (e.g., "main") # # ============================================================================== @@ -19,10 +25,13 @@ set -euo pipefail export TZ=UTC export LANG=C.UTF-8 export LC_ALL=C.UTF-8 +umask 022 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +APT_SNAPSHOT_DIR="" +declare -a APT_OPTS=() +APT_SOURCE_MODE="host" -function usage() { +usage() { echo "Usage: $0 OUTPUT_DIR" echo "" echo "Download and extract Ubuntu kernel for TEE." @@ -31,7 +40,11 @@ function usage() { echo " OUTPUT_DIR Directory to store vmlinuz" echo "" echo "ENVIRONMENT VARIABLES (or source build-config):" - echo " KERNEL_VERSION Kernel version to download (e.g., 6.8.0-90)" + echo " KERNEL_VERSION Kernel version to download (e.g., 6.8.0-90)" + echo " SOURCE_DATE_EPOCH Unix timestamp for reproducible output metadata" + echo " APT_SNAPSHOT_URL Optional snapshot apt URL for deterministic package resolution" + echo " APT_SNAPSHOT_SUITE Snapshot suite (default from build-config)" + echo " APT_SNAPSHOT_COMPONENTS Snapshot components (default from build-config)" echo "" echo "EXAMPLES:" echo " source build-config && $0 ./output" @@ -39,12 +52,61 @@ function usage() { exit 1 } +die() { + echo "ERROR: $*" >&2 + exit 1 +} + run_cmd() { echo "$*" - eval "$*" || { - echo "ERROR: $*" - exit 1 - } + "$@" || die "$*" +} + +setup_apt_snapshot() { + : "${APT_SNAPSHOT_URL:?APT_SNAPSHOT_URL is required when snapshot mode is enabled}" + : "${APT_SNAPSHOT_SUITE:?APT_SNAPSHOT_SUITE is required when snapshot mode is enabled}" + + local components="${APT_SNAPSHOT_COMPONENTS:-main}" + local sources_list + + APT_SNAPSHOT_DIR="$(mktemp -d)" + mkdir -p "$APT_SNAPSHOT_DIR/lists/partial" "$APT_SNAPSHOT_DIR/cache/partial" + + sources_list="$APT_SNAPSHOT_DIR/sources.list" + printf "deb [check-valid-until=no] %s %s %s\n" \ + "$APT_SNAPSHOT_URL" "$APT_SNAPSHOT_SUITE" "$components" > "$sources_list" + + APT_OPTS=( + -o "Dir::Etc::sourcelist=$sources_list" + -o "Dir::Etc::sourceparts=-" + -o "Dir::State::Lists=$APT_SNAPSHOT_DIR/lists" + -o "Dir::Cache::archives=$APT_SNAPSHOT_DIR/cache" + -o "Dir::State::status=/var/lib/dpkg/status" + -o "APT::Get::List-Cleanup=0" + ) + + echo "Using apt snapshot source:" + echo " URL: $APT_SNAPSHOT_URL" + echo " Suite: $APT_SNAPSHOT_SUITE" + echo " Components: $components" + run_cmd apt-get "${APT_OPTS[@]}" update + APT_SOURCE_MODE="snapshot" +} + +apt_download() { + local package="$1" + run_cmd apt-get "${APT_OPTS[@]}" download "$package" +} + +cleanup() { + local exit_code=$? + if [[ -n "${WORK_DIR:-}" ]] && [[ -d "$WORK_DIR" ]]; then + rm -rf "$WORK_DIR" + fi + if [[ -n "$APT_SNAPSHOT_DIR" ]] && [[ -d "$APT_SNAPSHOT_DIR" ]]; then + rm -rf "$APT_SNAPSHOT_DIR" + fi + exit "$exit_code" } if [[ $# -lt 1 ]] || [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then @@ -52,9 +114,12 @@ if [[ $# -lt 1 ]] || [[ "${1:-}" == "-h" ]] || [[ "${1:-}" == "--help" ]]; then fi DEST="$1" - -# Validate required environment variables KERNEL_VER="${KERNEL_VERSION:?KERNEL_VERSION not set - source build-config first}" +: "${KERNEL_PKG_SHA256:?KERNEL_PKG_SHA256 not set - required for reproducible builds}" +: "${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH not set - required for reproducible builds}" +if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then + die "SOURCE_DATE_EPOCH must be a unix timestamp integer" +fi echo "==========================================" echo "Building Kernel" @@ -62,53 +127,43 @@ echo "==========================================" echo "Configuration:" echo " Output dir: $DEST" echo " Kernel version: $KERNEL_VER" +echo " SOURCE_DATE_EPOCH: $SOURCE_DATE_EPOCH" +echo " APT source: ${APT_SNAPSHOT_URL:-}" echo "==========================================" echo "" -# Create temporary working directory -WORK_DIR=$(mktemp -d) - -cleanup() { - local exit_code=$? - if [[ -d "$WORK_DIR" ]]; then - rm -rf "$WORK_DIR" - fi - exit $exit_code -} +if [[ -n "${APT_SNAPSHOT_URL:-}" ]]; then + setup_apt_snapshot +else + echo "WARNING: APT_SNAPSHOT_URL not set, using host apt sources (weaker reproducibility)" +fi +WORK_DIR="$(mktemp -d)" trap cleanup EXIT INT TERM echo "Working directory: $WORK_DIR" pushd "$WORK_DIR" >/dev/null - # Download kernel package - run_cmd apt-get download linux-image-unsigned-${KERNEL_VER}-generic + apt_download "linux-image-unsigned-${KERNEL_VER}-generic" echo "" echo "Downloaded packages:" ls -lh *.deb - # Require checksum verification for reproducibility - : "${KERNEL_PKG_SHA256:?KERNEL_PKG_SHA256 not set - required for reproducible builds}" - echo "" echo "Verifying package checksum..." - ACTUAL_SHA256=$(sha256sum linux-image-unsigned-*.deb | awk '{print $1}') + ACTUAL_SHA256="$(sha256sum linux-image-unsigned-*.deb | awk '{print $1}')" if [[ "$ACTUAL_SHA256" != "$KERNEL_PKG_SHA256" ]]; then - echo "ERROR: Package checksum mismatch!" - echo " Expected: $KERNEL_PKG_SHA256" - echo " Actual: $ACTUAL_SHA256" - exit 1 + die "Package checksum mismatch (expected $KERNEL_PKG_SHA256, got $ACTUAL_SHA256)" fi echo "[OK] Package checksum verified: $ACTUAL_SHA256" - # Extract kernel mkdir -p extracted run_cmd dpkg-deb -x linux-image-unsigned-*.deb extracted/ - # Copy to output mkdir -p "$DEST" run_cmd cp extracted/boot/vmlinuz-* "$DEST/vmlinuz" + touch -d "@${SOURCE_DATE_EPOCH}" "$DEST/vmlinuz" popd >/dev/null echo "" @@ -117,5 +172,6 @@ echo "[OK] Kernel build complete" echo "==========================================" echo "Output: $DEST/vmlinuz" echo "Version: ${KERNEL_VER}" +echo "APT mode: ${APT_SOURCE_MODE}" echo "SHA256: $(sha256sum "$DEST/vmlinuz" | awk '{print $1}')" echo "==========================================" diff --git a/misc/AMDSEV/build-ovmf.sh b/misc/AMDSEV/build-ovmf.sh index 324ef986b..4e171ce5b 100755 --- a/misc/AMDSEV/build-ovmf.sh +++ b/misc/AMDSEV/build-ovmf.sh @@ -11,6 +11,8 @@ # Environment (required): # OVMF_GIT_URL Git URL for OVMF repository # OVMF_BRANCH Git branch to build +# OVMF_COMMIT Exact commit to build +# SOURCE_DATE_EPOCH Unix timestamp used for reproducibility # # ============================================================================== @@ -20,6 +22,7 @@ set -euo pipefail export TZ=UTC export LANG=C.UTF-8 export LC_ALL=C.UTF-8 +umask 022 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -34,6 +37,8 @@ function usage() { echo "ENVIRONMENT VARIABLES (or source build-config):" echo " OVMF_GIT_URL Git URL for OVMF repository" echo " OVMF_BRANCH Git branch to build" + echo " OVMF_COMMIT Exact commit to build (required for reproducibility)" + echo " SOURCE_DATE_EPOCH Unix timestamp for reproducible builds" echo "" echo "EXAMPLES:" echo " source build-config && $0 ./output" @@ -57,6 +62,12 @@ DEST="$1" # Validate required environment variables : "${OVMF_GIT_URL:?OVMF_GIT_URL not set - source build-config first}" : "${OVMF_BRANCH:?OVMF_BRANCH not set - source build-config first}" +: "${OVMF_COMMIT:?OVMF_COMMIT not set - required for reproducible builds}" +: "${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH not set - required for reproducible builds}" +if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then + echo "ERROR: SOURCE_DATE_EPOCH must be a unix timestamp integer" + exit 1 +fi echo "==========================================" echo "Building OVMF" @@ -65,7 +76,8 @@ echo "Configuration:" echo " Output dir: $DEST" echo " Git URL: $OVMF_GIT_URL" echo " Branch: $OVMF_BRANCH" -echo " Commit: ${OVMF_COMMIT:-}" +echo " Commit: ${OVMF_COMMIT}" +echo " SOURCE_DATE_EPOCH: $SOURCE_DATE_EPOCH" echo "==========================================" echo "" @@ -101,24 +113,24 @@ fi # Build OVMF pushd "$OVMF_DIR" >/dev/null run_cmd git fetch current - if [[ -n "${OVMF_COMMIT:-}" ]]; then - # Checkout exact commit for reproducibility - run_cmd git checkout "${OVMF_COMMIT}" - echo "Checked out pinned commit: $OVMF_COMMIT" - else - echo "WARNING: OVMF_COMMIT not set - build may not be reproducible" - run_cmd git checkout current/${OVMF_BRANCH} - fi + # Checkout exact commit for reproducibility. + run_cmd git checkout "${OVMF_COMMIT}" + echo "Checked out pinned commit: $OVMF_COMMIT" # Verify commit after checkout ACTUAL_COMMIT=$(git rev-parse HEAD) - if [[ -n "${OVMF_COMMIT:-}" ]] && [[ "$ACTUAL_COMMIT" != "$OVMF_COMMIT" ]]; then + if [[ "$ACTUAL_COMMIT" != "$OVMF_COMMIT" ]]; then echo "ERROR: Commit mismatch after checkout" echo " Expected: $OVMF_COMMIT" echo " Actual: $ACTUAL_COMMIT" exit 1 fi run_cmd git submodule update --init --recursive + if git submodule status --recursive | grep -Eq '^[+-]'; then + echo "ERROR: OVMF submodule state is not pinned/clean" + git submodule status --recursive + exit 1 + fi run_cmd touch OvmfPkg/AmdSev/Grub/grub.efi # https://github.com/AMDESE/ovmf/issues/6#issuecomment-2843109558 run_cmd make -C BaseTools clean run_cmd make -C BaseTools -j $(getconf _NPROCESSORS_ONLN) @@ -130,8 +142,9 @@ pushd "$OVMF_DIR" >/dev/null mkdir -p "$DEST" run_cmd cp -f Build/AmdSev/DEBUG_$GCCVERS/FV/OVMF.fd $DEST + run_cmd touch -d "@${SOURCE_DATE_EPOCH}" "$DEST/OVMF.fd" - COMMIT=$(git log --format="%h" -1 HEAD) + COMMIT=$(git rev-parse HEAD) echo "$COMMIT" > "${SCRIPT_DIR}/source-commit.ovmf" popd >/dev/null diff --git a/misc/AMDSEV/build.sh b/misc/AMDSEV/build.sh index 7f218f00b..313dad68d 100755 --- a/misc/AMDSEV/build.sh +++ b/misc/AMDSEV/build.sh @@ -6,65 +6,90 @@ # Usage: # ./misc/AMDSEV/build.sh # ./misc/AMDSEV/build.sh --katana /path/to/katana -# ./misc/AMDSEV/build.sh ovmf kernel +# ./misc/AMDSEV/build.sh --repro-check ovmf kernel initrd # +set -euo pipefail + +# Environment normalization for reproducibility. +export TZ=UTC +export LANG=C.UTF-8 +export LC_ALL=C.UTF-8 +umask 022 + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -. ${SCRIPT_DIR}/build-config +# shellcheck source=/dev/null +. "${SCRIPT_DIR}/build-config" -# Export variables for child scripts +# Export variables for child scripts. export OVMF_GIT_URL OVMF_BRANCH OVMF_COMMIT KERNEL_VERSION export KERNEL_PKG_SHA256 BUSYBOX_PKG_SHA256 KERNEL_MODULES_EXTRA_PKG_SHA256 export BUSYBOX_PKG_VERSION KERNEL_MODULES_EXTRA_PKG_VERSION +export APT_SNAPSHOT_URL APT_SNAPSHOT_SUITE APT_SNAPSHOT_COMPONENTS +export BUILD_CONTAINER_IMAGE_DIGEST +export KATANA_STRICT_REPRO -# Set SOURCE_DATE_EPOCH if not already set (for reproducible builds) -export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}" - -# Reproducibility validation -echo "" -if [[ -z "${OVMF_COMMIT:-}" ]]; then - echo "WARNING: OVMF_COMMIT not set - OVMF build may not be reproducible" -fi -if [[ -z "${SOURCE_DATE_EPOCH:-}" ]] || [[ "$SOURCE_DATE_EPOCH" == "$(date +%s)" ]]; then - echo "NOTE: SOURCE_DATE_EPOCH defaulting to current time" - echo " For reproducible builds: export SOURCE_DATE_EPOCH=\$(git log -1 --format=%ct)" -fi -echo "" - -function usage() -{ +usage() { echo "Usage: $0 [OPTIONS] [COMPONENTS]" echo "" echo "OPTIONS:" echo " --install PATH Installation path (default: ${SCRIPT_DIR}/output/qemu)" echo " --katana PATH Path to katana binary (optional, will build if not provided)" + echo " --repro-check Build twice and fail if output hashes differ" echo " -h|--help Usage information" echo "" echo "COMPONENTS (if none specified, builds all):" echo " ovmf Build OVMF firmware" echo " kernel Build kernel" echo " initrd Build initrd (builds katana if --katana not provided)" + exit 1 +} +die() { + echo "ERROR: $*" >&2 exit 1 } +require_source_date_epoch() { + if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then + die "SOURCE_DATE_EPOCH must be set for reproducible builds (e.g. export SOURCE_DATE_EPOCH=\$(git log -1 --format=%ct))" + fi + if ! [[ "$SOURCE_DATE_EPOCH" =~ ^[0-9]+$ ]]; then + die "SOURCE_DATE_EPOCH must be a unix timestamp integer" + fi +} + +tool_version() { + local cmd="$1" + if command -v "$cmd" >/dev/null 2>&1; then + "$cmd" --version 2>/dev/null | head -n 1 | tr -s ' ' + else + echo "${cmd}:not-installed" + fi +} + INSTALL_DIR="${SCRIPT_DIR}/output/qemu" KATANA_BINARY="" BUILD_OVMF=0 BUILD_KERNEL=0 BUILD_INITRD=0 +REPRO_CHECK=0 -while [ -n "$1" ]; do +while [[ $# -gt 0 ]]; do case "$1" in --install) - [ -z "$2" ] && usage + [[ -z "${2:-}" ]] && usage INSTALL_DIR="$2" - shift; shift + shift 2 ;; --katana) - [ -z "$2" ] && usage + [[ -z "${2:-}" ]] && usage KATANA_BINARY="$2" - shift; shift + shift 2 + ;; + --repro-check) + REPRO_CHECK=1 + shift ;; -h|--help) usage @@ -82,30 +107,38 @@ while [ -n "$1" ]; do shift ;; -*|--*) - echo "Unsupported option: [$1]" - usage + die "Unsupported option: [$1]" ;; *) - echo "Unsupported argument: [$1]" - usage + die "Unsupported argument: [$1]" ;; esac done -# If no components specified, build all -if [ $BUILD_OVMF -eq 0 ] && [ $BUILD_KERNEL -eq 0 ] && [ $BUILD_INITRD -eq 0 ]; then +# If no components specified, build all. +if [[ $BUILD_OVMF -eq 0 && $BUILD_KERNEL -eq 0 && $BUILD_INITRD -eq 0 ]]; then BUILD_OVMF=1 BUILD_KERNEL=1 BUILD_INITRD=1 fi -# Build katana if needed for initrd and not provided -if [ $BUILD_INITRD -eq 1 ] && [ -z "$KATANA_BINARY" ]; then +require_source_date_epoch + +echo "" +if [[ -z "${OVMF_COMMIT:-}" ]]; then + die "OVMF_COMMIT must be pinned in build-config" +fi +if [[ -z "${APT_SNAPSHOT_URL:-}" ]]; then + echo "WARNING: APT_SNAPSHOT_URL is not set; package resolution depends on host apt sources" + echo " Set APT_SNAPSHOT_URL/APT_SNAPSHOT_SUITE/APT_SNAPSHOT_COMPONENTS for stronger reproducibility" +fi +echo "" + +# Build katana if needed for initrd and not provided. +if [[ $BUILD_INITRD -eq 1 && -z "$KATANA_BINARY" ]]; then echo "No --katana provided." - if [ ! -t 0 ]; then - echo "ERROR: Cannot prompt without an interactive terminal." - echo "Pass --katana /path/to/katana to use a pre-built binary." - exit 1 + if [[ ! -t 0 ]]; then + die "Cannot prompt without an interactive terminal. Pass --katana /path/to/katana to use a pre-built binary." fi read -r -p "Build katana from source with musl now? [y/N] " CONFIRM_BUILD_KATANA @@ -114,144 +147,172 @@ if [ $BUILD_INITRD -eq 1 ] && [ -z "$KATANA_BINARY" ]; then echo "Building katana with musl..." ;; *) - echo "Aborting. Provide --katana /path/to/katana to use a pre-built binary." - exit 1 + die "Aborting. Provide --katana /path/to/katana to use a pre-built binary." ;; - esac + esac - PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" - "${PROJECT_ROOT}/scripts/build-musl.sh" - if [ $? -ne 0 ]; then - echo "Katana build failed" - exit 1 - fi - KATANA_BINARY="${PROJECT_ROOT}/target/x86_64-unknown-linux-musl/performance/katana" - if [ ! -f "$KATANA_BINARY" ]; then - echo "ERROR: Katana binary not found at $KATANA_BINARY" - exit 1 + PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + if [[ "${KATANA_STRICT_REPRO:-0}" == "1" ]]; then + "${PROJECT_ROOT}/scripts/build-musl.sh" --strict + else + echo "WARNING: Building katana without --strict dependency vendoring." + echo " Set KATANA_STRICT_REPRO=1 (and vendor deps) for stronger reproducibility." + "${PROJECT_ROOT}/scripts/build-musl.sh" + fi + KATANA_BINARY="${PROJECT_ROOT}/target/x86_64-unknown-linux-musl/performance/katana" + [[ -f "$KATANA_BINARY" ]] || die "Katana binary not found at $KATANA_BINARY" + echo "Using built katana: $KATANA_BINARY" fi - echo "Using built katana: $KATANA_BINARY" + +if [[ -n "$KATANA_BINARY" ]]; then + KATANA_BINARY="$(readlink -e "$KATANA_BINARY")" fi -mkdir -p $INSTALL_DIR -IDIR=$INSTALL_DIR -INSTALL_DIR=$(readlink -e $INSTALL_DIR) -[ -n "$INSTALL_DIR" -a -d "$INSTALL_DIR" ] || { - echo "Installation directory [$IDIR] does not exist, exiting" - exit 1 -} +mkdir -p "$INSTALL_DIR" +IDIR="$INSTALL_DIR" +INSTALL_DIR="$(readlink -e "$INSTALL_DIR")" +[[ -n "$INSTALL_DIR" && -d "$INSTALL_DIR" ]] || die "Installation directory [$IDIR] does not exist" -if [ $BUILD_OVMF -eq 1 ]; then +if [[ $BUILD_OVMF -eq 1 ]]; then "${SCRIPT_DIR}/build-ovmf.sh" "$INSTALL_DIR" - if [ $? -ne 0 ]; then - echo "OVMF build failed: $?" - exit 1 - fi fi -if [ $BUILD_KERNEL -eq 1 ]; then +if [[ $BUILD_KERNEL -eq 1 ]]; then "${SCRIPT_DIR}/build-kernel.sh" "$INSTALL_DIR" - if [ $? -ne 0 ]; then - echo "Kernel build failed: $?" - exit 1 - fi fi -if [ $BUILD_INITRD -eq 1 ]; then +if [[ $BUILD_INITRD -eq 1 ]]; then "${SCRIPT_DIR}/build-initrd.sh" "$KATANA_BINARY" "$INSTALL_DIR/initrd.img" - if [ $? -ne 0 ]; then - echo "Initrd build failed: $?" - exit 1 - fi - # Copy katana binary to output directory cp "$KATANA_BINARY" "$INSTALL_DIR/katana" echo "Copied katana binary to $INSTALL_DIR/katana" fi -# ============================================================================== -# Generate build-info.txt (merge with existing if present) -# ============================================================================== BUILD_INFO="$INSTALL_DIR/build-info.txt" +MATERIALS_LOCK="$INSTALL_DIR/materials.lock" + +INFO_OVMF_COMMIT="$OVMF_COMMIT" +[[ -f "${SCRIPT_DIR}/source-commit.ovmf" ]] && INFO_OVMF_COMMIT="$(cat "${SCRIPT_DIR}/source-commit.ovmf")" -# Initialize variables with defaults (empty) -INFO_OVMF_GIT_URL="" -INFO_OVMF_BRANCH="" -INFO_OVMF_COMMIT="" -INFO_KERNEL_VERSION="" -INFO_KERNEL_PKG_SHA256="" -INFO_BUSYBOX_PKG_SHA256="" -INFO_KERNEL_MODULES_EXTRA_PKG_SHA256="" -INFO_KATANA_BINARY_SHA256="" INFO_OVMF_SHA256="" INFO_KERNEL_SHA256="" INFO_INITRD_SHA256="" +INFO_KATANA_BINARY_SHA256="" -# Load existing values if build-info.txt exists -if [ -f "$BUILD_INFO" ]; then - while IFS='=' read -r key value; do - # Skip comments and empty lines - [[ "$key" =~ ^#.*$ || -z "$key" ]] && continue - case "$key" in - OVMF_GIT_URL) INFO_OVMF_GIT_URL="$value" ;; - OVMF_BRANCH) INFO_OVMF_BRANCH="$value" ;; - OVMF_COMMIT) INFO_OVMF_COMMIT="$value" ;; - KERNEL_VERSION) INFO_KERNEL_VERSION="$value" ;; - KERNEL_PKG_SHA256) INFO_KERNEL_PKG_SHA256="$value" ;; - BUSYBOX_PKG_SHA256) INFO_BUSYBOX_PKG_SHA256="$value" ;; - KERNEL_MODULES_EXTRA_PKG_SHA256) INFO_KERNEL_MODULES_EXTRA_PKG_SHA256="$value" ;; - KATANA_BINARY_SHA256) INFO_KATANA_BINARY_SHA256="$value" ;; - OVMF_SHA256) INFO_OVMF_SHA256="$value" ;; - KERNEL_SHA256) INFO_KERNEL_SHA256="$value" ;; - INITRD_SHA256) INFO_INITRD_SHA256="$value" ;; - esac - done < "$BUILD_INFO" -fi - -# Update values for components that were built -if [ $BUILD_OVMF -eq 1 ]; then - INFO_OVMF_GIT_URL="$OVMF_GIT_URL" - INFO_OVMF_BRANCH="$OVMF_BRANCH" - [ -f "${SCRIPT_DIR}/source-commit.ovmf" ] && INFO_OVMF_COMMIT="$(cat "${SCRIPT_DIR}/source-commit.ovmf")" - [ -f "$INSTALL_DIR/OVMF.fd" ] && INFO_OVMF_SHA256="$(sha256sum "$INSTALL_DIR/OVMF.fd" | awk '{print $1}')" +[[ -f "$INSTALL_DIR/OVMF.fd" ]] && INFO_OVMF_SHA256="$(sha256sum "$INSTALL_DIR/OVMF.fd" | awk '{print $1}')" +[[ -f "$INSTALL_DIR/vmlinuz" ]] && INFO_KERNEL_SHA256="$(sha256sum "$INSTALL_DIR/vmlinuz" | awk '{print $1}')" +[[ -f "$INSTALL_DIR/initrd.img" ]] && INFO_INITRD_SHA256="$(sha256sum "$INSTALL_DIR/initrd.img" | awk '{print $1}')" +if [[ -f "$INSTALL_DIR/katana" ]]; then + INFO_KATANA_BINARY_SHA256="$(sha256sum "$INSTALL_DIR/katana" | awk '{print $1}')" +elif [[ -n "$KATANA_BINARY" && -f "$KATANA_BINARY" ]]; then + INFO_KATANA_BINARY_SHA256="$(sha256sum "$KATANA_BINARY" | awk '{print $1}')" fi -if [ $BUILD_KERNEL -eq 1 ]; then - INFO_KERNEL_VERSION="$KERNEL_VERSION" - INFO_KERNEL_PKG_SHA256="$KERNEL_PKG_SHA256" - [ -f "$INSTALL_DIR/vmlinuz" ] && INFO_KERNEL_SHA256="$(sha256sum "$INSTALL_DIR/vmlinuz" | awk '{print $1}')" -fi +TOOLCHAIN_ID="bash=${BASH_VERSION};$(tool_version gcc);$(tool_version ld);$(tool_version cpio);$(tool_version gzip);$(tool_version dpkg-deb);$(tool_version apt-get)" -if [ $BUILD_INITRD -eq 1 ]; then - INFO_BUSYBOX_PKG_SHA256="$BUSYBOX_PKG_SHA256" - INFO_KERNEL_MODULES_EXTRA_PKG_SHA256="$KERNEL_MODULES_EXTRA_PKG_SHA256" - [ -n "$KATANA_BINARY" ] && [ -f "$KATANA_BINARY" ] && INFO_KATANA_BINARY_SHA256="$(sha256sum "$KATANA_BINARY" | awk '{print $1}')" - [ -f "$INSTALL_DIR/initrd.img" ] && INFO_INITRD_SHA256="$(sha256sum "$INSTALL_DIR/initrd.img" | awk '{print $1}')" -fi +INPUT_MANIFEST_SHA256="$({ + echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" + echo "OVMF_GIT_URL=${OVMF_GIT_URL}" + echo "OVMF_BRANCH=${OVMF_BRANCH}" + echo "OVMF_COMMIT=${INFO_OVMF_COMMIT}" + echo "KERNEL_VERSION=${KERNEL_VERSION}" + echo "KERNEL_PKG_SHA256=${KERNEL_PKG_SHA256}" + echo "BUSYBOX_PKG_VERSION=${BUSYBOX_PKG_VERSION}" + echo "BUSYBOX_PKG_SHA256=${BUSYBOX_PKG_SHA256}" + echo "KERNEL_MODULES_EXTRA_PKG_VERSION=${KERNEL_MODULES_EXTRA_PKG_VERSION}" + echo "KERNEL_MODULES_EXTRA_PKG_SHA256=${KERNEL_MODULES_EXTRA_PKG_SHA256}" + echo "APT_SNAPSHOT_URL=${APT_SNAPSHOT_URL:-}" + echo "APT_SNAPSHOT_SUITE=${APT_SNAPSHOT_SUITE:-}" + echo "APT_SNAPSHOT_COMPONENTS=${APT_SNAPSHOT_COMPONENTS:-}" + echo "BUILD_CONTAINER_IMAGE_DIGEST=${BUILD_CONTAINER_IMAGE_DIGEST:-}" + echo "KATANA_STRICT_REPRO=${KATANA_STRICT_REPRO:-0}" +} | sha256sum | awk '{print $1}')" -# Write build-info.txt with all values { echo "# TEE Build Information" - echo "# Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "" echo "# Reproducibility" - echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" + echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" + echo "INPUT_MANIFEST_SHA256=${INPUT_MANIFEST_SHA256}" + echo "TOOLCHAIN_ID=${TOOLCHAIN_ID}" + echo "BUILD_CONTAINER_IMAGE_DIGEST=${BUILD_CONTAINER_IMAGE_DIGEST:-}" echo "" echo "# Dependencies" - [ -n "$INFO_OVMF_GIT_URL" ] && echo "OVMF_GIT_URL=$INFO_OVMF_GIT_URL" - [ -n "$INFO_OVMF_BRANCH" ] && echo "OVMF_BRANCH=$INFO_OVMF_BRANCH" - [ -n "$INFO_OVMF_COMMIT" ] && echo "OVMF_COMMIT=$INFO_OVMF_COMMIT" - [ -n "$INFO_KERNEL_VERSION" ] && echo "KERNEL_VERSION=$INFO_KERNEL_VERSION" - [ -n "$INFO_KERNEL_PKG_SHA256" ] && echo "KERNEL_PKG_SHA256=$INFO_KERNEL_PKG_SHA256" - [ -n "$INFO_BUSYBOX_PKG_SHA256" ] && echo "BUSYBOX_PKG_SHA256=$INFO_BUSYBOX_PKG_SHA256" - [ -n "$INFO_KERNEL_MODULES_EXTRA_PKG_SHA256" ] && echo "KERNEL_MODULES_EXTRA_PKG_SHA256=$INFO_KERNEL_MODULES_EXTRA_PKG_SHA256" - [ -n "$INFO_KATANA_BINARY_SHA256" ] && echo "KATANA_BINARY_SHA256=$INFO_KATANA_BINARY_SHA256" + echo "OVMF_GIT_URL=${OVMF_GIT_URL}" + echo "OVMF_BRANCH=${OVMF_BRANCH}" + echo "OVMF_COMMIT=${INFO_OVMF_COMMIT}" + echo "KERNEL_VERSION=${KERNEL_VERSION}" + echo "KERNEL_PKG_SHA256=${KERNEL_PKG_SHA256}" + echo "BUSYBOX_PKG_VERSION=${BUSYBOX_PKG_VERSION}" + echo "BUSYBOX_PKG_SHA256=${BUSYBOX_PKG_SHA256}" + echo "KERNEL_MODULES_EXTRA_PKG_VERSION=${KERNEL_MODULES_EXTRA_PKG_VERSION}" + echo "KERNEL_MODULES_EXTRA_PKG_SHA256=${KERNEL_MODULES_EXTRA_PKG_SHA256}" + echo "APT_SNAPSHOT_URL=${APT_SNAPSHOT_URL:-}" + echo "APT_SNAPSHOT_SUITE=${APT_SNAPSHOT_SUITE:-}" + echo "APT_SNAPSHOT_COMPONENTS=${APT_SNAPSHOT_COMPONENTS:-}" + echo "BUILD_CONTAINER_IMAGE_DIGEST=${BUILD_CONTAINER_IMAGE_DIGEST:-}" + echo "KATANA_STRICT_REPRO=${KATANA_STRICT_REPRO:-0}" + [[ -n "$INFO_KATANA_BINARY_SHA256" ]] && echo "KATANA_BINARY_SHA256=${INFO_KATANA_BINARY_SHA256}" echo "" echo "# Output Checksums (SHA256)" - [ -n "$INFO_OVMF_SHA256" ] && echo "OVMF_SHA256=$INFO_OVMF_SHA256" - [ -n "$INFO_KERNEL_SHA256" ] && echo "KERNEL_SHA256=$INFO_KERNEL_SHA256" - [ -n "$INFO_INITRD_SHA256" ] && echo "INITRD_SHA256=$INFO_INITRD_SHA256" + [[ -n "$INFO_OVMF_SHA256" ]] && echo "OVMF_SHA256=${INFO_OVMF_SHA256}" + [[ -n "$INFO_KERNEL_SHA256" ]] && echo "KERNEL_SHA256=${INFO_KERNEL_SHA256}" + [[ -n "$INFO_INITRD_SHA256" ]] && echo "INITRD_SHA256=${INFO_INITRD_SHA256}" } > "$BUILD_INFO" +{ + echo "# Immutable Build Materials" + echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" + echo "INPUT_MANIFEST_SHA256=${INPUT_MANIFEST_SHA256}" + echo "OVMF_GIT_URL=${OVMF_GIT_URL}" + echo "OVMF_BRANCH=${OVMF_BRANCH}" + echo "OVMF_COMMIT=${INFO_OVMF_COMMIT}" + echo "KERNEL_VERSION=${KERNEL_VERSION}" + echo "KERNEL_PKG_SHA256=${KERNEL_PKG_SHA256}" + echo "BUSYBOX_PKG_VERSION=${BUSYBOX_PKG_VERSION}" + echo "BUSYBOX_PKG_SHA256=${BUSYBOX_PKG_SHA256}" + echo "KERNEL_MODULES_EXTRA_PKG_VERSION=${KERNEL_MODULES_EXTRA_PKG_VERSION}" + echo "KERNEL_MODULES_EXTRA_PKG_SHA256=${KERNEL_MODULES_EXTRA_PKG_SHA256}" + echo "APT_SNAPSHOT_URL=${APT_SNAPSHOT_URL:-}" + echo "APT_SNAPSHOT_SUITE=${APT_SNAPSHOT_SUITE:-}" + echo "APT_SNAPSHOT_COMPONENTS=${APT_SNAPSHOT_COMPONENTS:-}" + echo "BUILD_CONTAINER_IMAGE_DIGEST=${BUILD_CONTAINER_IMAGE_DIGEST:-}" + echo "KATANA_STRICT_REPRO=${KATANA_STRICT_REPRO:-0}" + [[ -n "$INFO_OVMF_SHA256" ]] && echo "ARTIFACT_OVMF_SHA256=${INFO_OVMF_SHA256}" + [[ -n "$INFO_KERNEL_SHA256" ]] && echo "ARTIFACT_KERNEL_SHA256=${INFO_KERNEL_SHA256}" + [[ -n "$INFO_INITRD_SHA256" ]] && echo "ARTIFACT_INITRD_SHA256=${INFO_INITRD_SHA256}" + [[ -n "$INFO_KATANA_BINARY_SHA256" ]] && echo "ARTIFACT_KATANA_SHA256=${INFO_KATANA_BINARY_SHA256}" +} > "$MATERIALS_LOCK" + +touch -d "@${SOURCE_DATE_EPOCH}" "$BUILD_INFO" "$MATERIALS_LOCK" + +if [[ $REPRO_CHECK -eq 1 && -z "${REPRO_CHECK_INTERNAL:-}" ]]; then + COMPARE_DIR="$(mktemp -d)" + cleanup_compare() { + if [[ -d "$COMPARE_DIR" ]]; then + rm -rf "$COMPARE_DIR" + fi + } + trap cleanup_compare EXIT INT TERM + + echo "" + echo "==========================================" + echo "Reproducibility check" + echo "==========================================" + echo "Second build output: $COMPARE_DIR" + + REBUILD_ARGS=(--install "$COMPARE_DIR") + [[ -n "$KATANA_BINARY" ]] && REBUILD_ARGS+=(--katana "$KATANA_BINARY") + [[ $BUILD_OVMF -eq 1 ]] && REBUILD_ARGS+=(ovmf) + [[ $BUILD_KERNEL -eq 1 ]] && REBUILD_ARGS+=(kernel) + [[ $BUILD_INITRD -eq 1 ]] && REBUILD_ARGS+=(initrd) + + REPRO_CHECK_INTERNAL=1 SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH" \ + "${SCRIPT_DIR}/build.sh" "${REBUILD_ARGS[@]}" + + "${SCRIPT_DIR}/verify-build.sh" --compare "$INSTALL_DIR" "$COMPARE_DIR" + echo "[OK] Reproducibility check passed" +fi + echo "" echo "==========================================" echo "Build complete" diff --git a/misc/AMDSEV/verify-build.sh b/misc/AMDSEV/verify-build.sh index f55a58232..6b46741c2 100755 --- a/misc/AMDSEV/verify-build.sh +++ b/misc/AMDSEV/verify-build.sh @@ -3,61 +3,124 @@ # VERIFY-BUILD.SH - Verify reproducibility of TEE builds # ============================================================================== # -# Computes and displays checksums for all TEE build artifacts. -# Run this after building to verify reproducibility. +# Computes and displays checksums for TEE build artifacts. # # Usage: # ./verify-build.sh [OUTPUT_DIR] +# ./verify-build.sh --compare OUTPUT_DIR_A OUTPUT_DIR_B # # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -OUTPUT_DIR="${1:-${SCRIPT_DIR}/output/qemu}" +ARTIFACTS=(OVMF.fd vmlinuz initrd.img katana build-info.txt materials.lock) -if [[ ! -d "$OUTPUT_DIR" ]]; then - echo "ERROR: Output directory not found: $OUTPUT_DIR" - echo "" +usage() { echo "Usage: $0 [OUTPUT_DIR]" - echo " Default: ${SCRIPT_DIR}/output/qemu" + echo " $0 --compare OUTPUT_DIR_A OUTPUT_DIR_B" + echo "" + echo "Default OUTPUT_DIR: ${SCRIPT_DIR}/output/qemu" exit 1 -fi +} -echo "==========================================" -echo "TEE Build Verification" -echo "==========================================" -echo "Output directory: $OUTPUT_DIR" -echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" -echo "" - -echo "Artifact Checksums (SHA256):" -echo "-------------------------------------------" - -for file in OVMF.fd vmlinuz initrd.img katana; do - if [[ -f "$OUTPUT_DIR/$file" ]]; then - CHECKSUM=$(sha256sum "$OUTPUT_DIR/$file" | awk '{print $1}') - SIZE=$(du -h "$OUTPUT_DIR/$file" | awk '{print $1}') - printf "%-12s %s (%s)\n" "$file:" "$CHECKSUM" "$SIZE" +checksum_file() { + local file="$1" + if [[ -f "$file" ]]; then + sha256sum "$file" | awk '{print $1}' else - printf "%-12s \n" "$file:" + echo "" fi -done +} -echo "" -echo "-------------------------------------------" +print_report() { + local output_dir="$1" -if [[ -f "$OUTPUT_DIR/build-info.txt" ]]; then + [[ -d "$output_dir" ]] || { + echo "ERROR: Output directory not found: $output_dir" >&2 + exit 1 + } + + echo "==========================================" + echo "TEE Build Verification" + echo "==========================================" + echo "Output directory: $output_dir" echo "" - echo "Build Configuration:" - echo "-------------------------------------------" - grep -E "^(SOURCE_DATE_EPOCH|OVMF_COMMIT|KERNEL_VERSION)=" "$OUTPUT_DIR/build-info.txt" 2>/dev/null || true + + echo "Artifact Checksums (SHA256):" echo "-------------------------------------------" + for file in "${ARTIFACTS[@]}"; do + local path="$output_dir/$file" + if [[ -f "$path" ]]; then + local checksum + local size + checksum="$(sha256sum "$path" | awk '{print $1}')" + size="$(du -h "$path" | awk '{print $1}')" + printf "%-16s %s (%s)\n" "$file:" "$checksum" "$size" + else + printf "%-16s \n" "$file:" + fi + done + + if [[ -f "$output_dir/build-info.txt" ]]; then + echo "" + echo "Build Configuration:" + echo "-------------------------------------------" + grep -E "^(SOURCE_DATE_EPOCH|INPUT_MANIFEST_SHA256|OVMF_COMMIT|KERNEL_VERSION)=" "$output_dir/build-info.txt" || true + echo "-------------------------------------------" + fi + + echo "" +} + +compare_reports() { + local dir_a="$1" + local dir_b="$2" + local failed=0 + + [[ -d "$dir_a" ]] || { echo "ERROR: Output directory not found: $dir_a" >&2; exit 1; } + [[ -d "$dir_b" ]] || { echo "ERROR: Output directory not found: $dir_b" >&2; exit 1; } + + echo "==========================================" + echo "TEE Build Reproducibility Compare" + echo "==========================================" + echo "A: $dir_a" + echo "B: $dir_b" + echo "" + + for file in "${ARTIFACTS[@]}"; do + local checksum_a checksum_b + checksum_a="$(checksum_file "$dir_a/$file")" + checksum_b="$(checksum_file "$dir_b/$file")" + + if [[ "$checksum_a" == "$checksum_b" ]]; then + printf "[OK] %-16s %s\n" "$file" "$checksum_a" + else + printf "[FAIL] %-16s A=%s B=%s\n" "$file" "$checksum_a" "$checksum_b" + failed=1 + fi + done + + if [[ $failed -ne 0 ]]; then + echo "" + echo "Reproducibility check failed." + exit 1 + fi + + echo "" + echo "Reproducibility check passed." +} + +if [[ $# -eq 0 ]]; then + print_report "${SCRIPT_DIR}/output/qemu" + exit 0 +fi + +if [[ "$1" == "--compare" ]]; then + [[ $# -eq 3 ]] || usage + compare_reports "$2" "$3" + exit 0 fi -echo "" -echo "To verify reproducibility:" -echo " 1. Save this output: $0 > build1.txt" -echo " 2. Clean and rebuild with same SOURCE_DATE_EPOCH" -echo " 3. Compare: diff build1.txt build2.txt" -echo "" +[[ $# -eq 1 ]] || usage +print_report "$1"