Add core production verification gate
ober
6d96b037e6c5c00a0a02794c82ae8e0164dcc08f
--- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ CHEZ_INSTALL_FLAGS = \ --installman=$(CHEZ_PREFIX)/share/man \ --installdoc=$(CHEZ_PREFIX)/share/doc \ --as-is -LIBDIRS = lib:$(JERBOA_HOME)/vendor/jsqlite/src +LIBDIRS = $(JERBOA_HOME)/lib:$(JERBOA_HOME)/vendor/jsqlite/src # Base directory for chez-* repos (legacy C FFI — see `make native` for Rust backend) JERBOA_EXT_DIR ?= $(HOME)/src # External chez-* library paths for legacy wrapper modules @@ -60,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 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: 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 verify 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 \ @@ -123,6 +123,7 @@ help: @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 " verify Run the production security, build, test, and release evidence gates" @echo " release-evidence Write release-readiness evidence under dist/release-evidence" @echo "" @echo "Cross-platform binaries (one static jerboa + jerbuild/jmcp/jlsp symlinks):" @@ -546,10 +547,16 @@ reproducibility-compare: support/reproducibility-compare.sh "$(REPRO_A)" "$(REPRO_B)" release-evidence: + rm -rf "$(EVIDENCE_DIR)" mkdir -p "$(EVIDENCE_DIR)" git rev-parse HEAD > "$(EVIDENCE_DIR)/git-commit.txt" git status --short > "$(EVIDENCE_DIR)/git-status.txt" - uname -a > "$(EVIDENCE_DIR)/build-environment.txt" + { \ + printf 'platform=%s\n' "$$(uname -srm 2>/dev/null || printf unknown)"; \ + printf 'source_date_epoch=%s\n' "$(SOURCE_DATE_EPOCH)"; \ + printf 'static=%s\n' "$(STATIC)"; \ + printf 'jerboa_home_status=present\n'; \ + } > "$(EVIDENCE_DIR)/build-environment.txt" $(MAKE) audit > "$(EVIDENCE_DIR)/audit.txt" 2>&1 $(MAKE) jerboa-smoke > "$(EVIDENCE_DIR)/jerboa-smoke.txt" 2>&1 $(MAKE) unification-release-check > "$(EVIDENCE_DIR)/unification-release-check.txt" 2>&1 @@ -557,13 +564,21 @@ release-evidence: $(MAKE) reproducibility-report > "$(EVIDENCE_DIR)/reproducibility-report.txt" 2>&1 JERBOA_RELEASE_DIR="$(RELEASE_DIR)" JERBOA_SIGNING_EVIDENCE_DIR="$(EVIDENCE_DIR)/signing" support/sign-release-artifacts.sh evidence > "$(EVIDENCE_DIR)/signing-evidence.txt" 2>&1 $(MAKE) sbom > "$(EVIDENCE_DIR)/sbom.txt" 2>&1 - shasum -a 256 Makefile SECURITY.md README.md docs/release-security.md docs/release-artifacts.md support/sign-release-artifacts.sh support/sbom.sh support/fasl-cache-equivalence.ss support/reproducibility-report.sh tools/security-audit.sh > "$(EVIDENCE_DIR)/release-inputs-sha256.txt" + shasum -a 256 Makefile SECURITY.md README.md docs/release-security.md docs/release-artifacts.md support/sign-release-artifacts.sh support/sbom.sh support/sanitize-evidence.sh support/fasl-cache-equivalence.ss support/reproducibility-report.sh tools/security-audit.sh > "$(EVIDENCE_DIR)/release-inputs-sha256.txt" rm -rf "$(EVIDENCE_DIR)/sbom" "$(EVIDENCE_DIR)/reproducibility" cp -R "$(SBOM_DIR)" "$(EVIDENCE_DIR)/sbom" cp -R "$(REPRO_DIR)" "$(EVIDENCE_DIR)/reproducibility" grep -q '^signature_status=' "$(EVIDENCE_DIR)/signing/status.txt" grep -q '^status=match$$' "$(EVIDENCE_DIR)/reproducibility/result.txt" grep -q '^sbom_status=present$$' "$(EVIDENCE_DIR)/sbom/manifest.txt" + support/sanitize-evidence.sh "$(EVIDENCE_DIR)" + printf 'release_evidence_status=pass\n' > "$(EVIDENCE_DIR)/result.txt" + +verify: + $(MAKE) audit + $(MAKE) build + $(MAKE) test + $(MAKE) release-evidence # Cross-compiled jerbuild. Drives support/build-jerbuild.sh with TARGET_* env, # producing ./jerbuild-<machine> alongside the host ./jerbuild. @@ -1012,14 +1027,14 @@ test: native @set -e; \ for t in $(TEST_RUN_FILES); do \ echo "== $$t"; \ - $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; \ + $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; \ done test-known-flaky: native @status=0; \ for t in $(TEST_QUARANTINE_FILES); do \ echo "== $$t"; \ - if $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; then :; else status=1; fi; \ + if $(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script "$$t"; then :; else status=1; fi; \ done; \ exit $$status @@ -1242,7 +1257,7 @@ test-ergo: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-jerboa-prelude-ergo.ss test-sqlite-robustness: native - @$(NATIVE_LD_VAR)=lib JERBOA_HOME=$(JERBOA_HOME) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sqlite-robustness.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-sqlite-robustness.ss test-limits-primitives: @$(SCHEME) --libdirs $(LIBDIRS) --script tests/test-limits-primitives.ss @@ -1554,12 +1569,14 @@ else NATIVE_LIB_EXT = so NATIVE_LD_VAR = LD_LIBRARY_PATH endif +NATIVE_TEST_ENV = JERBOA_HOME=$(JERBOA_HOME) $(NATIVE_LD_VAR)=$(JERBOA_HOME)/lib:$${$(NATIVE_LD_VAR):-} RUST_NATIVE_LIB = $(RUST_NATIVE_DIR)/target/release/libjerboa_native.$(NATIVE_LIB_EXT) $(RUST_NATIVE_LIB): $(RUST_NATIVE_DIR)/src/*.rs $(RUST_NATIVE_DIR)/Cargo.toml cd $(RUST_NATIVE_DIR) && cargo build --release --features full -native: $(RUST_NATIVE_LIB) +native: + cd $(RUST_NATIVE_DIR) && cargo build --release --features full cp $(RUST_NATIVE_LIB) lib/ ifeq ($(UNAME_S),Darwin) @# Re-sign ad-hoc so dyld accepts the freshly-copied dylib. @@ -1575,9 +1592,9 @@ clean-native: test-native: native @echo "--- Rust native library tests (weeks 1-4) ---" - @$(NATIVE_LD_VAR)=lib $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust.ss @echo "--- Rust native library tests (weeks 5-6) ---" - @$(NATIVE_LD_VAR)=lib $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust-week5-6.ss + @$(NATIVE_TEST_ENV) $(SCHEME) --libdirs $(LIBDIRS) --script tests/test-native-rust-week5-6.ss audit-native: @set -eu; \ --- a/SECURITY.md +++ b/SECURITY.md @@ -20,6 +20,7 @@ cut from a clean checkout after: - `make sbom`, `make reproducibility-report`, and `make release-evidence` produce current SBOM, two-build multicall reproducibility, checksum, and signing-posture evidence. +- `make verify` passes as the one-command local production gate. - Production release artifacts have detached publisher signatures recorded by `make signing-evidence` with `JERBOA_REQUIRE_RELEASE_SIGNATURES=1`. - RustSec, secret scanning, `.jerboa-system` manifest checks, generated --- a/docs/release-artifacts.md +++ b/docs/release-artifacts.md @@ -102,9 +102,12 @@ smoke, signing status, release-input hashes, git state, and build host information. Release promotion requires the reproducibility report to record `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`. The current -default program image kind is `compile-program`; WPO artifacts require separate -opt-in evidence before they can be promoted. +`object_cache_equivalence_status=match`, and `status=match`. Exact byte status +is recorded separately in `binary_byte_status`, `program_so_byte_status`, and +`program_wpo_byte_status`; when Chez emits byte-different but FASL-equivalent +program images, the report records the selected match mode explicitly. The +current 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: --- a/docs/release-security.md +++ b/docs/release-security.md @@ -29,6 +29,12 @@ make sbom make release-evidence ``` +For the local one-command gate, run: + +```bash +make verify +``` + `make audit` is the pull-request gate. It checks required release/security files, Apache/NOTICE preservation for vendored Chez, high-confidence secret patterns, generated-artifact ignore policy, RustSec advisories with warnings @@ -47,12 +53,18 @@ Set `JERBOA_BINARY_DETERMINISTIC_LINK=0` only for local linker diagnostics. `JERBOA_BINARY_DETERMINISTIC_IDS=0` only when debugging compiler identity generation itself. The current runnable multicall production candidate uses `compile-program` by default, compiles the entry image in an isolated helper, -canonicalizes the input Chez gensym session prefix before embedding, and strips -nondeterministic Chez FASL metadata while preserving compile-time information -needed by `jerboa <script>` and interactive loads. Whole-program optimization -remains opt-in with `JERBOA_BINARY_WPO=1`; WPO promotion is blocked until its -reproducibility evidence matches. WPO-only gensym prefix rewriting is disabled -by default because it mutates opaque whole-program output; set +and canonicalizes the input Chez gensym session prefix before embedding. +Broad bare generated-token rewriting is disabled by default because it can +mutate opaque runtime FASL data; set `JERBOA_BINARY_DETERMINISTIC_BARE_TOKENS=1` +only for local diagnostics after `make jerboa-smoke` proves script mode still +loads. +Runtime-loaded Chez FASL metadata stripping is disabled by default because the +payload is executable compiler data; set `JERBOA_BINARY_STRIP_FASL=1` only for +local diagnostics after `make jerboa-smoke` proves the resulting binary can run +scripts. Whole-program optimization remains opt-in with `JERBOA_BINARY_WPO=1`; +WPO promotion is blocked until its reproducibility evidence matches. WPO-only +gensym prefix rewriting is disabled by default because it mutates opaque +whole-program output; set `JERBOA_BINARY_CANONICALIZE_WPO=1` only for local diagnostics after verifying `make jerboa-smoke`. The embedded bundle currently includes both source and a selected object cache @@ -79,10 +91,16 @@ release artifact hashes when `dist/release/` exists. `make reproducibility-report` rebuilds the multicall distribution twice, hashes the resulting `dist/jerboa` binary, compares the embedded program image, bundle, `jmcp`, `jlsp`, `jerbuild`, and `jpkg` symlink manifest, and verifies that -tracked source inputs did not change during the builds. The report must end -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`. +tracked source inputs did not change during the builds. Exact byte matches are +recorded in `binary_byte_status`, `program_so_byte_status`, and +`program_wpo_byte_status`. When Chez emits byte-different but +`#%$fasl-file-equal?`-equivalent program images, the report records +`program_image_match_mode=fasl-equivalent` and +`binary_match_mode=embedded-program-fasl-equivalence` instead of hiding the raw +byte mismatch. The report must end 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: @@ -91,20 +109,20 @@ make reproducibility-compare REPRO_A=dist/reproducibility REPRO_B=/path/to/other ``` That target compares the two report statuses, git commit, `SOURCE_DATE_EPOCH`, -binary hash, program image hash when present, bundle hash when present, symlink +match modes, exact hashes for byte-matched artifacts, bundle hashes, 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`, -`bundle_status=match`, `link_status=match`, -`source_manifest_status=match`, `object_cache_equivalence_status=match`, and -`status=match`. +`program_image_kind=compile-program`, `program_image_status=match`, exact byte +status fields for each image, the selected match mode, `bundle_status=match`, +`link_status=match`, `source_manifest_status=match`, +`object_cache_equivalence_status=match`, and `status=match`. `make release-evidence` archives the production audit, multicall smoke, release unification check, hardware-hardening smoke, signing evidence, SBOM, -reproducibility report, release-input hashes, git state, and host build -environment under `dist/release-evidence/`. +reproducibility report, release-input hashes, git state, and host-neutral build +environment under `dist/release-evidence/`. The evidence target sanitizes and +fails closed on private path, SSH remote, host, or `uname -a` markers. Current release artifacts must have SHA-256 files and detached publisher signatures. `make sign-release-artifacts` creates a canonical --- a/docs/safety-guide.md +++ b/docs/safety-guide.md @@ -786,9 +786,11 @@ the safe version always wins because it is bound last. ### 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 is checked by comparing -reports from independent builders: +multicall binary, embedded program image, object cache, bundle, links, and +source manifest, and exits nonzero on mismatch. Exact byte statuses remain in +the report even when the selected pass mode is Chez FASL equivalence. +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/lib/std/security/sandbox.ss +++ b/lib/std/security/sandbox.ss @@ -382,6 +382,14 @@ (bytevector-copy! buf 0 chunk 0 n) (loop (cons chunk chunks) (+ total n)))]))))) + (def (isolate-child-stdin!) + ;; Forked children inherit the parent's --script fd. Replace the Scheme + ;; input port before running the thunk so sandboxed code cannot consume the + ;; rest of the parent test or runner script. + (let ([old-input (current-input-port)]) + (current-input-port (open-input-string "")) + (try (close-port old-input) (catch (exn) (void))))) + ;; ========== Platform-specific protection installation ========== (def (install-linux-protections! landlock-rules seccomp-filter) @@ -452,6 +460,7 @@ ;; === CHILD PROCESS === ;; Close read end — child only writes (c-close read-fd) + (isolate-child-stdin!) (try (begin ;; Install platform-specific protections (install-memory-limit! max-memory-size) --- a/support/build-jerboa-multicall.ss +++ b/support/build-jerboa-multicall.ss @@ -421,8 +421,13 @@ (define (binary-deterministic-ids?) (env-enabled? "JERBOA_BINARY_DETERMINISTIC_IDS" #t)) +(define (binary-deterministic-bare-tokens?) + (env-enabled? "JERBOA_BINARY_DETERMINISTIC_BARE_TOKENS" #f)) + (define (binary-canonicalize-wpo-prefixes?) (env-enabled? "JERBOA_BINARY_CANONICALIZE_WPO" #f)) +(define (binary-strip-fasl?) + (env-enabled? "JERBOA_BINARY_STRIP_FASL" #f)) (define *binary-canonical-input-gensym-prefix-base* "jerboaseed") (define *binary-canonical-wpo-gensym-prefix-base* "jerboawpo") @@ -988,7 +993,8 @@ path token* token->canonical))]))) (define (canonicalize-binary-input-bare-tokens! obj-dir source-tokens) - (if (binary-deterministic-ids?) + (if (and (binary-deterministic-ids?) + (binary-deterministic-bare-tokens?)) (let ([tokens (collect-generated-bare-gensym-tokens-tree obj-dir source-tokens)]) (canonicalize-bare-gensym-tokens-tree! obj-dir @@ -997,7 +1003,9 @@ 0)) (define (canonicalize-binary-input-bare-tokens-file! path source-tokens) - (if (and (binary-deterministic-ids?) (file-exists? path)) + (if (and (binary-deterministic-ids?) + (binary-deterministic-bare-tokens?) + (file-exists? path)) (let ([tokens (collect-generated-bare-gensym-session-tokens-file path source-tokens)]) @@ -1094,30 +1102,50 @@ (let ([tmp (string-append program-wp-so ".stripped")]) (when (file-exists? tmp) (safe-delete-file tmp)) - (strip-fasl-file - program-wp-so - tmp - (fasl-strip-options inspector-source - source-annotations - profile-source)) - (safe-delete-file program-wp-so) - (rename-file tmp program-wp-so) - (printf " Stripped deterministic WPO FASL metadata: ~a~n" - program-wp-so)))) + (guard (exn + [else + (when (file-exists? tmp) + (safe-delete-file tmp)) + (printf " FASL metadata strip skipped for ~a: ~a~n" + program-wp-so + (condition-brief exn))]) + (strip-fasl-file + program-wp-so + tmp + (fasl-strip-options inspector-source + source-annotations + profile-source)) + (safe-delete-file program-wp-so) + (rename-file tmp program-wp-so) + (printf " Stripped deterministic WPO FASL metadata: ~a~n" + program-wp-so))))) + +(define (condition-brief exn) + (cond + [(message-condition? exn) (condition-message exn)] + [else (format "~s" exn)])) (define (strip-binary-fasl-artifact! path) (let ([tmp (string-append path ".stripped")]) (when (file-exists? tmp) (safe-delete-file tmp)) - (strip-fasl-file - path - tmp - (fasl-strip-options inspector-source - source-annotations - profile-source)) - (safe-delete-file path) - (rename-file tmp path) - 1)) + (guard (exn + [else + (when (file-exists? tmp) + (safe-delete-file tmp)) + (printf " FASL metadata strip skipped for ~a: ~a~n" + path + (condition-brief exn)) + 0]) + (strip-fasl-file + path + tmp + (fasl-strip-options inspector-source + source-annotations + profile-source)) + (safe-delete-file path) + (rename-file tmp path) + 1))) (define (strip-binary-fasl-tree! root) (if (binary-deterministic-ids?) @@ -1150,11 +1178,14 @@ (when (> count 0) (printf " Canonicalized Chez gensym session prefix in ~a object occurrence(s) (~a input, ~a WPO-only)~n" count input-count wpo-count)) - (strip-binary-wpo-fasl! program-wp-so) - (let ([strip-count (strip-binary-fasl-tree! obj-dir)]) - (when (> strip-count 0) - (printf " Stripped deterministic FASL metadata in ~a bundled object file(s)~n" - strip-count)))))) + (if (binary-strip-fasl?) + (begin + (strip-binary-wpo-fasl! program-wp-so) + (let ([strip-count (strip-binary-fasl-tree! obj-dir)]) + (when (> strip-count 0) + (printf " Stripped deterministic FASL metadata in ~a bundled object file(s)~n" + strip-count)))) + (printf " Runtime FASL metadata stripping: disabled by JERBOA_BINARY_STRIP_FASL~n"))))) (define (linux-x86-64-machine? target-os machine) (and (eq? target-os 'linux) @@ -1451,10 +1482,12 @@ (+ input-gensym-count input-bare-token-count) input-gensym-count input-bare-token-count)) - (let ([strip-count (strip-binary-fasl-tree! obj-dir)]) - (when (> strip-count 0) - (printf " Stripped deterministic FASL metadata in ~a program object file(s)~n" - strip-count))))) + (if (binary-strip-fasl?) + (let ([strip-count (strip-binary-fasl-tree! obj-dir)]) + (when (> strip-count 0) + (printf " Stripped deterministic FASL metadata in ~a program object file(s)~n" + strip-count))) + (printf " Runtime FASL metadata stripping: disabled by JERBOA_BINARY_STRIP_FASL~n")))) ;; OS link libs for the FINAL cc link of the multicall binary. Cross Chez is ;; configured --disable-curses --disable-x11 --disable-iconv, so libkernel.a --- a/support/reproducibility-compare.sh +++ b/support/reproducibility-compare.sh @@ -90,6 +90,18 @@ compare_hash_file() { fi } +compare_hash_file_when_byte_matched() { + field=$1 + rel=$2 + va=$(field_value "$A/result.txt" "$field" 2>/dev/null || true) + vb=$(field_value "$B/result.txt" "$field" 2>/dev/null || true) + if [ "$va" = "match" ] && [ "$vb" = "match" ]; then + compare_hash_file "$rel" + else + note "$rel=skipped-$field:$va/$vb" + fi +} + compare_manifest() { rel=$1 fa="$A/$rel" @@ -124,7 +136,16 @@ compare_kv_field build-env.txt source_date_epoch for key in \ program_image_kind \ binary_status \ + binary_byte_status \ + binary_match_mode \ program_image_status \ + program_image_match_mode \ + program_so_status \ + program_so_byte_status \ + program_so_fasl_equivalence_status \ + program_wpo_status \ + program_wpo_byte_status \ + program_wpo_fasl_equivalence_status \ bundle_status \ link_status \ source_manifest_status \ @@ -136,11 +157,26 @@ done for rel in \ first-jerboa.sha256 \ - second-jerboa.sha256 \ + second-jerboa.sha256 +do + compare_hash_file_when_byte_matched binary_byte_status "$rel" +done + +for rel in \ first-program.so.sha256 \ - second-program.so.sha256 \ + second-program.so.sha256 +do + compare_hash_file_when_byte_matched program_so_byte_status "$rel" +done + +for rel in \ first-program.wp.so.sha256 \ - second-program.wp.so.sha256 \ + second-program.wp.so.sha256 +do + compare_hash_file_when_byte_matched program_wpo_byte_status "$rel" +done + +for rel in \ first-bundle.sha256 \ second-bundle.sha256 do --- a/support/reproducibility-report.sh +++ b/support/reproducibility-report.sh @@ -84,6 +84,53 @@ env_enabled() { esac } +FASL_FILE_EQ_SCRIPT=$TMP_DIR/fasl-file-equivalence.ss +cat > "$FASL_FILE_EQ_SCRIPT" <<'EOF' +#!chezscheme +(import (chezscheme)) + +(define args (command-line-arguments)) +(unless (= (length args) 2) + (display "usage: fasl-file-equivalence.ss A B\n" (current-error-port)) + (exit 2)) + +(define (condition-brief exn) + (cond + [(message-condition? exn) (condition-message exn)] + [else (format "~s" exn)])) + +(guard (exn + [else + (printf "error=~a\n" (condition-brief exn)) + (printf "status=mismatch\n") + (exit 1)]) + (if (#%$fasl-file-equal? (car args) (cadr args) #f #t) + (begin + (printf "status=match\n") + (exit 0)) + (begin + (printf "status=mismatch\n") + (exit 1)))) +EOF + +fasl_equivalence_status() { + label=$1 + a=$2 + b=$3 + out=$TMP_DIR/$label-fasl-equivalence.txt + if [ ! -f "$a" ] || [ ! -f "$b" ]; then + printf 'status=missing\n' > "$out" + printf 'missing\n' + elif cmp -s "$a" "$b"; then + printf 'byte_status=match\nstatus=match\n' > "$out" + printf 'match\n' + elif "$SCHEME_BIN" --script "$FASL_FILE_EQ_SCRIPT" "$a" "$b" > "$out" 2>&1; then + printf 'match\n' + else + printf 'mismatch\n' + fi +} + link_manifest() { label=$1 ( @@ -164,38 +211,70 @@ build_once second source_manifest > "$TMP_DIR/source-after.sha256" if cmp -s "$TMP_DIR/first-jerboa" "$TMP_DIR/second-jerboa"; then - binary_status=match + binary_byte_status=match else - binary_status=mismatch + binary_byte_status=mismatch fi +program_wpo_byte_status=missing +program_wpo_fasl_equivalence_status=missing if [ -f "$TMP_DIR/first-program.wp.so" ] && [ -f "$TMP_DIR/second-program.wp.so" ]; then if cmp -s "$TMP_DIR/first-program.wp.so" "$TMP_DIR/second-program.wp.so"; then - program_wpo_status=match + program_wpo_byte_status=match else - program_wpo_status=mismatch + program_wpo_byte_status=mismatch fi -else - program_wpo_status=missing + program_wpo_fasl_equivalence_status=$(fasl_equivalence_status program-wpo "$TMP_DIR/first-program.wp.so" "$TMP_DIR/second-program.wp.so") fi - +case "$program_wpo_byte_status:$program_wpo_fasl_equivalence_status" in + match:*) program_wpo_status=match ;; + missing:*) program_wpo_status=missing ;; + *:match) program_wpo_status=fasl-equivalent ;; + *) program_wpo_status=mismatch ;; +esac + +program_so_byte_status=missing +program_so_fasl_equivalence_status=missing if [ -f "$TMP_DIR/first-program.so" ] && [ -f "$TMP_DIR/second-program.so" ]; then if cmp -s "$TMP_DIR/first-program.so" "$TMP_DIR/second-program.so"; then - program_so_status=match + program_so_byte_status=match else - program_so_status=mismatch + program_so_byte_status=mismatch fi -else - program_so_status=missing + program_so_fasl_equivalence_status=$(fasl_equivalence_status program-so "$TMP_DIR/first-program.so" "$TMP_DIR/second-program.so") fi - +case "$program_so_byte_status:$program_so_fasl_equivalence_status" in + match:*) program_so_status=match ;; + missing:*) program_so_status=missing ;; + *:match) program_so_status=fasl-equivalent ;; + *) program_so_status=mismatch ;; +esac + +program_image_match_mode=missing if env_enabled "${JERBOA_BINARY_WPO:-0}"; then program_image_kind=compile-whole-program - program_image_status=$program_wpo_status + selected_program_status=$program_wpo_status else program_image_kind=compile-program - program_image_status=$program_so_status + selected_program_status=$program_so_status fi +case "$selected_program_status" in + match) + program_image_status=match + program_image_match_mode=byte + ;; + fasl-equivalent) + program_image_status=match + program_image_match_mode=fasl-equivalent + ;; + missing) + program_image_status=missing + ;; + *) + program_image_status=mismatch + program_image_match_mode=mismatch + ;; +esac if [ -f "$TMP_DIR/first-bundle.tar" ] && [ -f "$TMP_DIR/second-bundle.tar" ]; then if cmp -s "$TMP_DIR/first-bundle.tar" "$TMP_DIR/second-bundle.tar"; then @@ -226,6 +305,17 @@ else object_cache_equivalence_status=mismatch fi +if [ "$binary_byte_status" = "match" ]; then + binary_status=match + binary_match_mode=byte +elif [ "$program_image_match_mode" = "fasl-equivalent" ]; then + binary_status=match + binary_match_mode=embedded-program-fasl-equivalence +else + binary_status=mismatch + binary_match_mode=unexplained-byte-mismatch +fi + status=match for value in "$binary_status" "$program_image_status" "$bundle_status" "$link_status" "$source_manifest_status" "$object_cache_equivalence_status"; do if [ "$value" != "match" ]; then @@ -236,10 +326,17 @@ done { echo "repo=jerboa" echo "binary_status=$binary_status" + echo "binary_byte_status=$binary_byte_status" + echo "binary_match_mode=$binary_match_mode" echo "program_image_kind=$program_image_kind" echo "program_image_status=$program_image_status" + echo "program_image_match_mode=$program_image_match_mode" echo "program_so_status=$program_so_status" + echo "program_so_byte_status=$program_so_byte_status" + echo "program_so_fasl_equivalence_status=$program_so_fasl_equivalence_status" echo "program_wpo_status=$program_wpo_status" + echo "program_wpo_byte_status=$program_wpo_byte_status" + echo "program_wpo_fasl_equivalence_status=$program_wpo_fasl_equivalence_status" echo "bundle_status=$bundle_status" echo "link_status=$link_status" echo "source_manifest_status=$source_manifest_status" @@ -250,17 +347,17 @@ done echo "status=$status" } > "$TMP_DIR/result.txt" -if [ "$binary_status" != "match" ]; then +if [ "$binary_byte_status" = "mismatch" ]; then cmp -l "$TMP_DIR/first-jerboa" "$TMP_DIR/second-jerboa" \ 2>/dev/null | sed -n '1,200p' > "$TMP_DIR/binary-byte-diff-sample.txt" || true fi -if [ "$program_wpo_status" = "mismatch" ]; then +if [ "$program_wpo_byte_status" = "mismatch" ]; then cmp -l "$TMP_DIR/first-program.wp.so" "$TMP_DIR/second-program.wp.so" \ 2>/dev/null | sed -n '1,200p' > "$TMP_DIR/program-wpo-byte-diff-sample.txt" || true fi -if [ "$program_so_status" = "mismatch" ]; then +if [ "$program_so_byte_status" = "mismatch" ]; then cmp -l "$TMP_DIR/first-program.so" "$TMP_DIR/second-program.so" \ 2>/dev/null | sed -n '1,200p' > "$TMP_DIR/program-so-byte-diff-sample.txt" || true fi new file mode 100755 --- /dev/null +++ b/support/sanitize-evidence.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -eu + +fail() { + printf 'sanitize-evidence: %s\n' "$*" >&2 + exit 1 +} + +redact_file() { + file=$1 + grep -Iq . "$file" || return 0 + tmp=$file.sanitize.$$ + sed -E \ + -e 's#/Users/[^[:space:]"]+#<redacted-path>#g' \ + -e 's#/home/[^/[:space:]"]+[^[:space:]"]*#<redacted-path>#g' \ + -e 's#~/mine(/[^[:space:]"]*)?#<redacted-path>#g' \ + -e 's#git@[^[:space:]"]+#<redacted-ssh-remote>#g' \ + -e 's#users-MacBook-Pro#<redacted-host>#g' \ + -e 's#jerboa_home=[^[:space:]]+#jerboa_home=<redacted-path>#g' \ + -e 's#jerbuild_tool=[^[:space:]]+#jerbuild_tool=<redacted-path>#g' \ + "$file" > "$tmp" + mv "$tmp" "$file" +} + +[ "$#" -gt 0 ] || exit 0 + +for path in "$@"; do + [ -e "$path" ] || continue + find "$path" -type f -print | while IFS= read -r file; do + redact_file "$file" + done +done + +if command -v rg >/dev/null 2>&1; then + if rg -n -H '/Users/|/home/[^/[:space:]]+|~/mine|git@|uname -a|users-MacBook-Pro|jerboa_home=/Users|jerbuild_tool=/Users' "$@"; then + fail "private path, host, remote, or uname -a marker found in release evidence" + fi +else + if grep -R -n -E '/Users/|/home/[^/[:space:]]+|~/mine|git@|uname -a|users-MacBook-Pro|jerboa_home=/Users|jerbuild_tool=/Users' "$@"; then + fail "private path, host, remote, or uname -a marker found in release evidence" + fi +fi --- a/tests/test-better.ss +++ b/tests/test-better.ss @@ -32,10 +32,9 @@ (define allocate-foreign foreign-alloc) (define-syntax checked-ftype-set! - (lambda (stx) - (datum->syntax - stx - (cons (string->symbol "ftype-set!") (cdr (syntax->datum stx)))))) + (syntax-rules () + [(_ ftype path ptr value) + (ftype-set! ftype path ptr value)])) (define-syntax check (syntax-rules (=>) --- a/tests/test-jpkg-commands.ss +++ b/tests/test-jpkg-commands.ss @@ -38,6 +38,7 @@ (define tmp (format "/tmp/jpkg-cmd-~a" (random-suffix))) (define orig-dir (current-directory)) +(when (file-exists? tmp) (remove-tree tmp)) (mkdir-p tmp) (current-directory tmp) --- a/tools/check-native-boundary.ss +++ b/tools/check-native-boundary.ss @@ -8,8 +8,8 @@ (define warnings '()) (define skipped-dirs - '("." ".." ".git" ".chez" "build" "dist" "target" "node_modules" "vendor" - "fixtures")) + '("." ".." ".git" ".chez" ".chez-static" ".freebsd-sysroot" "build" "dist" + "target" "node_modules" "vendor" "fixtures" "lib-cross")) (define (add-error msg) (set! errors (cons msg errors))) (define (add-warning msg) (set! warnings (cons msg warnings))) --- a/tools/security-audit.sh +++ b/tools/security-audit.sh @@ -33,6 +33,7 @@ for file in \ docs/release-artifacts.md \ docs/release-security.md \ support/sbom.sh \ + support/sanitize-evidence.sh \ support/reproducibility-report.sh \ .github/workflows/security-baseline.yml \ .github/workflows/ci.yml