Add static build and reproducibility compare gates
ober
b51e1bc025edf4c30e220f1125976a0db20fd554
--- a/Makefile +++ b/Makefile @@ -4,8 +4,10 @@ SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct) export SOURCE_DATE_EPOCH HOST_UNAME_S := $(shell uname -s) HOST_UNAME_M := $(shell uname -m) +STATIC ?= 0 +STATIC_ENABLED := $(filter 1 yes true on,$(STATIC)) CHEZ_BUILD_DIR ?= $(JERBOA_HOME)/build/chez -CHEZ_PREFIX ?= $(JERBOA_HOME)/.chez +CHEZ_PREFIX ?= $(if $(STATIC_ENABLED),$(JERBOA_HOME)/.chez-static,$(JERBOA_HOME)/.chez) SCHEME ?= $(CHEZ_PREFIX)/bin/scheme # Detect host machine type from the vendored configure script (e.g. tarm64osx @@ -26,6 +28,7 @@ CHEZ_HARDEN ?= --enable-harden endif CHEZ_INSTALL_FLAGS = \ + $(if $(STATIC_ENABLED),--static) \ $(CHEZ_HARDEN) \ --installprefix=$(CHEZ_PREFIX) \ --installbin=$(CHEZ_PREFIX)/bin \ @@ -57,7 +60,7 @@ TYPED_LLVMIR_PARITY_FN ?= sample_typed_llvmir_smoke::main LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi) TYPED_WRAPPER_DIR ?= build/typed/jerboa -.PHONY: help chez chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push +.PHONY: help chez static-supported-check chez-cross build binary binary-typed binary-typed-smoke binary-cross native-cross pure-audit typecheck typed-rust typed-llvmir typed-llvmir-check typed-llvmir-smoke typed-llvmir-parity typed-wrappers typed-build typed-wrapper-smoke typed-split-tree-smoke typed-test typed-clean test test-known-flaky test-reader test-core test-runtime test-try-debug test-stdlib test-ffi test-modules test-expanded test-contract test-ergo test-sqlite-robustness test-limits-primitives test-typed-core test-typed-parser test-typed-checker test-typed-rust test-typed-llvmir test-typed-wrappers test-pure-audit test-features test-wrappers test-phase4a test-phase4b test-phase4c test-phase4d test-phase4e test-phase4f test-phase5 test-phase5e test-phase6 test-phase7 test-phase8 test-functional test-repl test-security test-security-profile test-native test-gaps native clean-native audit audit-native security-audit clean security security-production security-profile security-hardware-smoke sbom reproducibility-report reproducibility-compare release-evidence fuzz fuzz-smoke fuzz-deep fuzz-reader-fuzz fuzz-json-fuzz fuzz-http2-fuzz fuzz-dns-fuzz fuzz-pregexp-fuzz fuzz-csv-fuzz fuzz-base64-fuzz fuzz-hex-fuzz fuzz-uri-fuzz fuzz-format-fuzz fuzz-router-fuzz fuzz-sandbox-fuzz test-rawstring test-regex test-rx test-peg test-regex-all check-docs check-docs-strict data-check docker-build docker-push .PHONY: check-cross-tools docker fuzz-websocket-fuzz jlsp jlsp-freebsd-amd64 \ jlsp-install jlsp-linux-amd64 jlsp-portable jmcp-freebsd-amd64 \ jmcp-freebsd-arm64 jmcp-linux-amd64 jmcp-linux-arm64 \ @@ -111,6 +114,7 @@ help: @echo " typed-test Run Typed Jerboa front-end tests" @echo " typed-clean Remove generated typed build artifacts" @echo " native Build Rust native library" + @echo " STATIC=1 Build native ELF/BSD Chez/binaries from .chez-static" @echo " clean Remove compiled .so and .wpo artifacts" @echo " clean-native Remove Rust build artifacts" @echo " audit Run production security/release gate checks" @@ -118,6 +122,7 @@ help: @echo " pure-audit Scan jerboa-* repos for non-pure runtime surfaces" @echo " sbom Write local SBOM/toolchain evidence under dist/sbom" @echo " reproducibility-report Compare two local multicall builds" + @echo " reproducibility-compare Compare two independent reproducibility reports" @echo " release-evidence Write release-readiness evidence under dist/release-evidence" @echo "" @echo "Cross-platform binaries (one static jerboa + jerbuild/jmcp/jlsp symlinks):" @@ -212,7 +217,15 @@ help: @echo " docker-build Build jerboa21/jerboa base image" @echo " docker-push Push base image to Docker Hub" -chez: $(SCHEME) +static-supported-check: + @if [ -n "$(STATIC_ENABLED)" ]; then \ + case "$(CHEZ_UNAME_S)" in \ + Linux|FreeBSD|OpenBSD|NetBSD) ;; \ + *) echo "ERROR: STATIC=1 native builds are supported only on ELF/BSD hosts; use the portable/cross Linux musl targets for static release artifacts." >&2; exit 1 ;; \ + esac; \ + fi + +chez: static-supported-check $(SCHEME) # Two-phase build: # 1. Configure with --pb and run `make bootquick XM=$(CHEZ_MACHINE_TYPE)` so @@ -242,7 +255,7 @@ build: chez BINARY_ENTRY ?= support/binary-entry.ss BINARY_OUTPUT ?= jerboa-bin binary: chez build - SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT) + SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) BINARY_STATIC=$(STATIC) support/build-binary.sh $(BINARY_ENTRY) $(BINARY_OUTPUT) # ── jerbuild standalone binary ─────────────────────────────────────────────── # Builds a self-contained ./jerbuild that bundles Chez + the Jerboa stdlib @@ -253,7 +266,7 @@ binary: chez build # ./jerbuild <src> <lib> # transpile .PHONY: jerbuild jerbuild-smoke jerbuild: chez build - SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) support/build-jerbuild.sh + SCHEME=$(SCHEME) JERBOA_CHEZ_PREFIX=$(CHEZ_PREFIX) JERBUILD_STATIC=$(STATIC) support/build-jerbuild.sh # End-to-end smoke test: with no JERBOA_HOME and only ./jerbuild, transpile a # small (export greet) module, then exec a script that imports + calls it. @@ -527,6 +540,11 @@ sbom: reproducibility-report: JERBOA_REPRO_DIR="$(REPRO_DIR)" support/reproducibility-report.sh +reproducibility-compare: + @test -n "$(REPRO_A)" || { echo "ERROR: set REPRO_A=path/to/first/reproducibility-report" >&2; exit 1; } + @test -n "$(REPRO_B)" || { echo "ERROR: set REPRO_B=path/to/second/reproducibility-report" >&2; exit 1; } + support/reproducibility-compare.sh "$(REPRO_A)" "$(REPRO_B)" + release-evidence: mkdir -p "$(EVIDENCE_DIR)" git rev-parse HEAD > "$(EVIDENCE_DIR)/git-commit.txt" @@ -778,7 +796,7 @@ mcp-test-binary: jmcp mcp-check # Native self-contained binary for the host (./jmcp). Embeds data/*.sexp. jmcp: chez build mcp-check @echo "=== Building native ./jmcp (host) ===" - @JERBOA_HOME=$(JERBOA_HOME) SCHEME=$(SCHEME) JMCP_OUTPUT=jmcp mcp/build-jmcp.sh + @JERBOA_HOME=$(JERBOA_HOME) SCHEME=$(SCHEME) JMCP_OUTPUT=jmcp JMCP_STATIC=$(STATIC) mcp/build-jmcp.sh # Cross-built binaries reuse jerboa's own cross prefixes (.chez-cross-<m>) and # xpatch (build/chez/xc-<m>/s/xpatch). The native host build (make jmcp) covers --- a/data/anti-patterns.sexp +++ b/data/anti-patterns.sexp @@ -1256,21 +1256,21 @@ "jerboa_run_tests")) (("advice" . - "Treat std sandbox modules as non-enforcing unless the scanner and runtime evidence prove otherwise. Either remove the dependency and state that kernel sandboxing is not claimed, or implement a verified native enforcement layer and add target-specific tests/evidence. Rerun jerboa_security_scan at medium severity or stricter.") + "Treat sandbox enforcement claims as platform-specific unless the scanner, docs, and runtime tests prove the exact path. Current std seccomp/Landlock modules use real Linux kernel enforcement, but static-musl/raw-libc fallback paths and unsupported kernels still need explicit evidence or degraded-status wording. Rerun jerboa_security_scan at medium severity or stricter.") ("avoid" . - "Do not import Jerboa std seccomp/Landlock stubs and document them as real production sandbox enforcement without proof that they enforce kernel policy on the target platform.") + "Do not document seccomp/Landlock as production enforcement for every target without proof that the selected module and build mode enforce kernel policy on that target.") ("id" . "std-security-stub-production-claim") ("kinds" "security" "ffi" "docs") ("pattern" . "std security (seccomp|landlock)|seccomp-.*install|landlock-.*install") ("severity" . "high") - ("tags" "seccomp" "landlock" "sandbox" "stubs" "production" + ("tags" "seccomp" "landlock" "sandbox" "kernel" "production" "scanner") ("title" . - "Do Not Claim Std Sandbox Stubs As Production Enforcement") + "Do Not Overclaim Sandbox Enforcement Across Targets") ("tools" "jerboa_security_scan" "rg" --- a/data/changelog.sexp +++ b/data/changelog.sexp @@ -2,7 +2,19 @@ . "Machine-readable changelog of Jerboa API drift. Consumers (LLM tooling, lints, jerboa_verify) use this to invalidate stale recommendations and to suggest migrations when a symbol is renamed or relocated.") ("entries" - (("added" "*sandbox-max-memory-size*" + (("added") + ("date" . "2026-06-25") + ("modules_added") + ("moved") + ("notes" + . + "Build/release metadata now exposes STATIC=1 as a guarded native static profile for Linux/FreeBSD/OpenBSD/NetBSD. The profile installs Chez under .chez-static, passes --static to the vendored Chez configure step, and propagates static link mode to binary, jerbuild, and jmcp. make reproducibility-compare now compares two independent reproducibility-report directories for cross-builder evidence. The stale seccomp-landlock-stub-production security rule was removed because current std security seccomp/Landlock modules use real kernel enforcement; static-musl/raw-libc caveats remain covered by sandbox-ffi-raw-libc-static-noop.") + ("removed" ("seccomp-landlock-stub-production")) + ("renamed") + ("tier_changes") + ("tools_added" "reproducibility-compare") + ("version" . "v0.2.3")) + (("added" "*sandbox-max-memory-size*" "sandbox-config-max-memory-size") ("date" . "2026-06-25") ("modules_added") --- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -6495,4 +6495,15 @@ "std-security-sandbox" "rlimit") ("title" . - "Cap run-safe Child Memory with max-memory-size"))) + "Cap run-safe Child Memory with max-memory-size")) + (("code" + . + "# Native static build on supported ELF/BSD hosts.\n# Uses .chez-static so the dynamic .chez install is not reused.\nmake chez STATIC=1\nmake binary STATIC=1\nmake jerbuild STATIC=1\nmake jmcp STATIC=1\n\n# Local two-pass reproducibility report.\nmake reproducibility-report\n\n# Compare reports from two independent builders.\nmake reproducibility-compare \\\n REPRO_A=dist/reproducibility \\\n REPRO_B=/path/to/other/reproducibility") ("id" . "native-static-and-repro-compare") ("imports") + ("notes" + . + "STATIC=1 is intentionally rejected for native macOS because Mach-O static system binaries are not a supported release target. Use jerboa-portable or Linux musl cross/release targets for static release artifacts. reproducibility-compare expects each input directory to be produced by make reproducibility-report and checks status=match, git commit, SOURCE_DATE_EPOCH, program image mode, binary/program/bundle hashes when present, symlink manifests, source manifests, and object-cache manifests.") + ("tags" "makefile" "static" "reproducibility" + "release-evidence" "chez" "jmcp") + ("title" + . + "Build Native Static Artifacts and Compare Reproducibility Reports"))) --- a/data/security-rules.sexp +++ b/data/security-rules.sexp @@ -394,18 +394,6 @@ ("title" . "FASL deserialization in privilege separation channel")) - (("id" . "seccomp-landlock-stub-production") - ("message" - . - "seccomp-install! and landlock-install! are currently stubs that record policy but do NOT enforce it. Using them creates a false sense of security.") - ("pattern" . "seccomp-install!|landlock-install!") - ("remediation" - . - "These functions now print a WARNING to stderr. Do not rely on them for actual sandboxing until real kernel enforcement is implemented. Use privsep + restricted environments for actual isolation.") - ("scope" . "scheme") ("severity" . "medium") - ("title" - . - "Seccomp/Landlock stub used in production without enforcement")) (("id" . "fork-without-child-limit") ("message" . --- a/docs/bundling-chez.md +++ b/docs/bundling-chez.md @@ -19,7 +19,7 @@ part of the normal build. This doc records how that works and what's left. | Cross-compilation | **Done** — `make chez-cross` for Linux/FreeBSD/macOS-x86 targets | | Docker | **Done** — image builds the same `vendor/ChezScheme` (glibc + a `--static` musl variant) | | `--static` musl | **Done** — `support/musl-chez-build*.sh`, Docker musl stage | -| `--static` everywhere | **Partial** — Linux musl and FreeBSD release paths are wired; a single native `STATIC=1` knob is still not generalized | +| Native `STATIC=1` | **Done for ELF/BSD** — `make ... STATIC=1` uses `.chez-static`, passes Chez `--static`, and links supported native binaries statically on Linux/FreeBSD/OpenBSD/NetBSD; macOS rejects the knob explicitly because Mach-O static system binaries are not a supported release target | | Licensing / attribution | **Done** — `LICENSE-CHEZ` + README + `--version`; all bundled deps permissive; lz4 reduced to BSD `lib/` + LICENSE (zero GPL) | | Distribution story | **Done** — `jerboa-portable`, `release-artifact(s)`, signing, SBOM, and release evidence targets exist | @@ -117,11 +117,15 @@ dirs; supports multiple independent instances. `make -C lz4/lib liblz4.a` (exit 0). That `lib/Makefile` edit is a local divergence to re-apply on any lz4 re-vendor. -2. **Generalize `--static` (partial).** Static, hermetic Chez is wired for - Linux/musl (`musl-chez-build*.sh`, Docker musl, `jerboa-linux-*`) and the - FreeBSD release path uses the system static toolchain. A single native knob - (e.g. `make chez STATIC=1`, including macOS behavior that does not depend on - `dlopen`) still does not exist. +2. **Generalize `--static` (done for supported native static targets).** + Static, hermetic Chez is wired for Linux/musl (`musl-chez-build*.sh`, + Docker musl, `jerboa-linux-*`) and the FreeBSD release path uses the system + static toolchain. Native `make ... STATIC=1` now installs Chez under + `.chez-static`, passes `--static` to the vendored Chez configure step, and + propagates the static link mode to `binary`, `jerbuild`, and `jmcp` on + Linux/FreeBSD/OpenBSD/NetBSD. macOS is intentionally out of scope for native + static system binaries; the build fails early there instead of silently + producing a dynamic Mach-O. 3. **Distribution story (done).** `make jerboa-portable` stages the supported target directories, `make release-artifact` packages one target, and --- a/docs/release-artifacts.md +++ b/docs/release-artifacts.md @@ -106,6 +106,12 @@ information. Release promotion requires the reproducibility report to record default program image kind is `compile-program`; WPO artifacts require separate opt-in evidence before they can be promoted. +To compare reports from two independent builders: + +```sh +make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility +``` + Before uploading production artifacts, sign them: ```sh --- a/docs/release-security.md +++ b/docs/release-security.md @@ -84,6 +84,16 @@ with `binary_status=match`, `program_image_status=match`, `bundle_status=match`, `link_status=match`, `source_manifest_status=match`, `object_cache_equivalence_status=match`, and `status=match`. +Independent-builder evidence is checked with: + +```bash +make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility +``` + +That target compares the two report statuses, git commit, `SOURCE_DATE_EPOCH`, +binary hash, program image hash when present, bundle hash when present, symlink +manifest, and tracked source manifest. It exits nonzero on mismatch. + Current local evidence records `binary_status=match`, `program_image_kind=compile-program`, `program_image_status=match`, `program_so_status=match`, `program_wpo_status=missing`, --- a/docs/safety-guide.md +++ b/docs/safety-guide.md @@ -775,12 +775,16 @@ produce a "multiple definitions" warning. This is cosmetic but means symbol resolution order could theoretically surprise you in edge cases. In practice, the safe version always wins because it is bound last. -### Reproducibility Evidence Is Local +### Reproducibility Evidence Needs Independent Comparison `make reproducibility-report` performs a local two-build comparison of the multicall binary, object cache, bundle, links, and source manifest, and exits -nonzero on mismatch. Cross-machine reproducibility still requires comparing -those reports from independent builders. +nonzero on mismatch. Cross-machine reproducibility is checked by comparing +reports from independent builders: + +```bash +make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other/reproducibility +``` --- --- a/mcp/build-jmcp.sh +++ b/mcp/build-jmcp.sh @@ -323,6 +323,12 @@ cat > "$builder" <<'SCHEME' [(and static? (string=? target-os "linux")) (format "~a -O2~a -static -Wl,--export-dynamic -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -Wl,--defsym=_dl_find_object=0 -lm -ldl -lpthread" ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)] + [(and static? (string=? target-os "freebsd")) + (format "~a -O2~a -static -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil" + ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)] + [(and static? (string=? target-os "macos")) + (error 'build-jmcp-binary + "JMCP_STATIC=1 is not supported for native macOS; use portable/cross Linux musl release artifacts")] [(string=? target-os "freebsd") (format "~a -O2~a -I~a -I~a -o ~a ~a ~a/libkernel.a ~a/libz.a ~a/liblz4.a~a -lm -lthr -lutil -lncurses" ccq harden-cflags supportq chezq outq mainq chezq chezq chezq harden-ldflags)] --- a/support/build-binary.sh +++ b/support/build-binary.sh @@ -24,6 +24,7 @@ SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}" JERBOA_CHEZ_PREFIX="${JERBOA_CHEZ_PREFIX:-$JERBOA_HOME/.chez}" BINARY_LIBDIRS="${BINARY_LIBDIRS:-${LIBDIRS:-$JERBOA_HOME/lib}}" BINARY_STATIC_ENV="${BINARY_STATIC_ENV:-}" +BINARY_STATIC="${BINARY_STATIC:-${STATIC:-}}" # ── Cross-compilation parameters (all optional; unset = native build) ──────── # TARGET_MACHINE Chez machine type to emit (e.g. ta6osx, ta6le). @@ -33,6 +34,13 @@ TARGET_MACHINE="${TARGET_MACHINE:-}" JERBOA_CROSS_PREFIX="${JERBOA_CROSS_PREFIX:-}" JERBOA_XPATCH="${JERBOA_XPATCH:-}" +flag_enabled() { + case "${1:-}" in + ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;; + *) return 0 ;; + esac +} + if [ -n "$TARGET_MACHINE" ]; then CROSS_BUILD=yes [ -n "$JERBOA_CROSS_PREFIX" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not" >&2; exit 1; } @@ -102,16 +110,30 @@ else fi CC="${CC:-$CC_DEFAULT}" +if flag_enabled "$BINARY_STATIC"; then + case "$TARGET_OS" in + Linux) + OS_LIBS="-lm -ldl -lpthread -static" + BINARY_STATIC_ENV=1 + ;; + FreeBSD|OpenBSD|NetBSD) + OS_LIBS="-lm -lpthread -static" + BINARY_STATIC_ENV=1 + ;; + Darwin) + echo "ERROR: BINARY_STATIC=1 is not supported for native macOS; use jerboa-portable/cross Linux musl for static release artifacts." >&2 + exit 1 + ;; + *) + echo "ERROR: BINARY_STATIC=1 is not supported for target OS '$TARGET_OS'." >&2 + exit 1 + ;; + esac +fi + # Allow caller to override link libs entirely (musl-static, etc.). OS_LIBS="${OS_LIBS_OVERRIDE:-$OS_LIBS}" -flag_enabled() { - case "${1:-}" in - ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;; - *) return 0 ;; - esac -} - target_hardening_cflags() { target_os="$1" machine="$2" --- a/support/build-jerbuild.sh +++ b/support/build-jerbuild.sh @@ -24,6 +24,7 @@ SCHEME="${SCHEME:-$JERBOA_HOME/.chez/bin/scheme}" JERBOA_CHEZ_PREFIX="${JERBOA_CHEZ_PREFIX:-$JERBOA_HOME/.chez}" CC="${CC:-cc}" OUTPUT="${OUTPUT:-jerbuild}" +JERBUILD_STATIC="${JERBUILD_STATIC:-${STATIC:-}}" # ── Cross-compilation parameters (all optional; unset = native build) ──────── # TARGET_MACHINE Chez machine type to emit (e.g. ta6osx, ta6le). @@ -33,6 +34,13 @@ TARGET_MACHINE="${TARGET_MACHINE:-}" JERBOA_CROSS_PREFIX="${JERBOA_CROSS_PREFIX:-}" JERBOA_XPATCH="${JERBOA_XPATCH:-}" +flag_enabled() { + case "${1:-}" in + ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;; + *) return 0 ;; + esac +} + if [ -n "$TARGET_MACHINE" ]; then CROSS_BUILD=yes [ -n "$JERBOA_CROSS_PREFIX" ] || { echo "ERROR: TARGET_MACHINE set but JERBOA_CROSS_PREFIX is not" >&2; exit 1; } @@ -86,12 +94,24 @@ else esac fi -flag_enabled() { - case "${1:-}" in - ""|0|false|False|FALSE|no|No|NO|off|Off|OFF) return 1 ;; - *) return 0 ;; +if flag_enabled "$JERBUILD_STATIC"; then + case "$TARGET_OS" in + Linux) + OS_LIBS="-lm -ldl -lpthread -static" + ;; + FreeBSD|OpenBSD|NetBSD) + OS_LIBS="-lm -lpthread -static" + ;; + Darwin) + echo "ERROR: JERBUILD_STATIC=1 is not supported for native macOS; use jerboa-portable/cross Linux musl for static release artifacts." >&2 + exit 1 + ;; + *) + echo "ERROR: JERBUILD_STATIC=1 is not supported for target OS '$TARGET_OS'." >&2 + exit 1 + ;; esac -} +fi target_hardening_cflags() { target_os="$1" new file mode 100755 --- /dev/null +++ b/support/reproducibility-compare.sh @@ -0,0 +1,168 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 2 ]; then + echo "Usage: $0 <repro-report-a> <repro-report-b>" >&2 + exit 1 +fi + +A=$1 +B=$2 +fail=0 + +note() { + printf '%s\n' "$*" +} + +mark_fail() { + note "ERROR: $*" + fail=1 +} + +require_file() { + if [ ! -f "$1" ]; then + mark_fail "missing required report file: $1" + return 1 + fi +} + +field_value() { + file=$1 + key=$2 + awk -F= -v key="$key" '$1 == key {print substr($0, length(key) + 2); found=1; exit} END {if (!found) exit 1}' "$file" +} + +compare_kv_field() { + rel=$1 + key=$2 + fa="$A/$rel" + fb="$B/$rel" + if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then + mark_fail "missing key/value file for $key: $rel" + return + fi + va=$(field_value "$fa" "$key" 2>/dev/null || true) + vb=$(field_value "$fb" "$key" 2>/dev/null || true) + if [ -z "$va" ] || [ -z "$vb" ]; then + mark_fail "missing field $key in $rel" + elif [ "$va" != "$vb" ]; then + mark_fail "$rel field mismatch for $key: '$va' != '$vb'" + else + note "$rel:$key=$va" + fi +} + +compare_field() { + key=$1 + va=$(field_value "$A/result.txt" "$key" 2>/dev/null || true) + vb=$(field_value "$B/result.txt" "$key" 2>/dev/null || true) + if [ -z "$va" ] || [ -z "$vb" ]; then + mark_fail "missing result field: $key" + elif [ "$va" != "$vb" ]; then + mark_fail "result field mismatch for $key: '$va' != '$vb'" + else + note "$key=$va" + fi +} + +first_hash() { + awk 'NF >= 1 {print $1; exit}' "$1" +} + +compare_hash_file() { + rel=$1 + fa="$A/$rel" + fb="$B/$rel" + if [ ! -f "$fa" ] && [ ! -f "$fb" ]; then + note "$rel=missing-in-both" + return + fi + if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then + mark_fail "hash file presence mismatch: $rel" + return + fi + ha=$(first_hash "$fa") + hb=$(first_hash "$fb") + if [ "$ha" != "$hb" ]; then + mark_fail "hash mismatch for $rel: $ha != $hb" + else + note "$rel=$ha" + fi +} + +compare_manifest() { + rel=$1 + fa="$A/$rel" + fb="$B/$rel" + if [ ! -f "$fa" ] && [ ! -f "$fb" ]; then + note "$rel=missing-in-both" + return + fi + if [ ! -f "$fa" ] || [ ! -f "$fb" ]; then + mark_fail "manifest presence mismatch: $rel" + return + fi + if cmp -s "$fa" "$fb"; then + note "$rel=match" + else + mark_fail "manifest mismatch: $rel" + fi +} + +require_file "$A/result.txt" || exit 1 +require_file "$B/result.txt" || exit 1 + +status_a=$(field_value "$A/result.txt" status 2>/dev/null || true) +status_b=$(field_value "$B/result.txt" status 2>/dev/null || true) +if [ "$status_a" != match ] || [ "$status_b" != match ]; then + mark_fail "both reports must have status=match before cross-report comparison" +fi + +compare_kv_field build-env.txt git_commit +compare_kv_field build-env.txt source_date_epoch + +for key in \ + program_image_kind \ + binary_status \ + program_image_status \ + bundle_status \ + link_status \ + source_manifest_status \ + object_cache_equivalence_status \ + measurement_mode +do + compare_field "$key" +done + +for rel in \ + first-jerboa.sha256 \ + second-jerboa.sha256 \ + first-program.so.sha256 \ + second-program.so.sha256 \ + first-program.wp.so.sha256 \ + second-program.wp.so.sha256 \ + first-bundle.sha256 \ + second-bundle.sha256 +do + compare_hash_file "$rel" +done + +for rel in \ + source-before.sha256 \ + source-after.sha256 \ + first-links.txt \ + second-links.txt \ + warm-object-cache.sha256 \ + warm-multicall-object-cache.sha256 \ + first-fresh-object-cache.sha256 \ + second-fresh-object-cache.sha256 +do + compare_manifest "$rel" +done + +if [ "$fail" -ne 0 ]; then + note "status=mismatch" + exit 1 +fi + +note "status=match"