From 1a1aa17038c521599f9a2501f3c06b1b01416a64 Mon Sep 17 00:00:00 2001 From: LoadingALIAS Date: Thu, 20 Aug 2026 13:41:25 -0400 Subject: [PATCH] ci: restore weekly assurance gates Narrow Miri and test-only reachability so warning-denying builds no longer compile unused CRC, dispatch, tuning, Keccak, XXH3, or Linux ML-KEM assembly helpers. Keep the CT API inventory on the pinned stable compiler, discover emitted evidence across stable and nightly Cargo layouts, and record the six audited public API additions for every release-evidence target. Document CtDecision's redacted Debug output and cover the stable rustdoc JSON environment in the CT validation regression. --- ct.toml | 24 ++++++++++----------- docs/constant-time.md | 7 +++--- scripts/ct/artifacts.sh | 8 +++---- scripts/ct/evidence_validation_test.py | 30 ++++++++++++++++++++++++++ scripts/ct/validate.py | 7 +++++- src/auth/mlkem/portable/aarch64.rs | 8 +++---- src/checksum/common/tests.rs | 6 ++++++ src/checksum/crc64/portable.rs | 4 ++-- src/hashes/crypto/keccak.rs | 2 +- src/hashes/fast/xxh3/kernels.rs | 4 ++-- src/platform/detect/arch/aarch64.rs | 17 +++++++++++++++ 11 files changed, 88 insertions(+), 29 deletions(-) diff --git a/ct.toml b/ct.toml index cf2cb14a..854f2c81 100644 --- a/ct.toml +++ b/ct.toml @@ -2737,8 +2737,8 @@ name = "x86_64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2230 -compiler_api_sha256 = "671da8ccb1855d450418dc6fc3e79c9b9a2d50c518f98761d8c0fda6abb7c3b3" +compiler_api_item_count = 2236 +compiler_api_sha256 = "ec37f397970ac2d9bce6c5188c48c0e1dfbd9b1d321674bce13395acdd6781f8" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -2749,8 +2749,8 @@ name = "aarch64-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2224 -compiler_api_sha256 = "2bc45c4c1aa47db7c9786ca5aae449cfc7e4370899841c96efbaeb7b67a909ae" +compiler_api_item_count = 2230 +compiler_api_sha256 = "2f64f9277c6acbb0510f9b6bb2753b6adcf862f6e8f2945831c5b7607d0a9240" claim = "ct-intended" physical_timing = "required" binsec = "required" @@ -2809,8 +2809,8 @@ name = "aarch64-apple-darwin" group = "macos" backend = "llvm" linker = "apple-ld-unpinned" -compiler_api_item_count = 2224 -compiler_api_sha256 = "2bc45c4c1aa47db7c9786ca5aae449cfc7e4370899841c96efbaeb7b67a909ae" +compiler_api_item_count = 2230 +compiler_api_sha256 = "2f64f9277c6acbb0510f9b6bb2753b6adcf862f6e8f2945831c5b7607d0a9240" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2834,8 +2834,8 @@ name = "s390x-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "e54c2a119d506935fda2279f7d7f9f75506ce86fe5b0b6fc72fd08a7e8090883" +compiler_api_item_count = 2229 +compiler_api_sha256 = "8c5e606f4792a7a5052da0e00093fbaa01f56f2223264b7df67bc3ada774d4d5" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2847,8 +2847,8 @@ name = "powerpc64le-unknown-linux-gnu" group = "ibm" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "e85c77d60d67a4648efd50cda101b8248026eb57d9008ceaae2511e319b502d9" +compiler_api_item_count = 2229 +compiler_api_sha256 = "24448b0c3dc339211793edc2999bcb77b57956ddde4c1d1111247c52067b9c9f" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" @@ -2860,8 +2860,8 @@ name = "riscv64gc-unknown-linux-gnu" group = "linux" backend = "llvm" linker = "platform-default-unpinned" -compiler_api_item_count = 2223 -compiler_api_sha256 = "119e6aef49beac534d7155cba8b15a7774c7a4fb6990c9e304b07998844a0858" +compiler_api_item_count = 2229 +compiler_api_sha256 = "3d1b7cfa425696e8d060773d2f2778119ce27b8aac3f69daaef1c24ddfb6e677" claim = "ct-intended" physical_timing = "required" binsec = "unsupported" diff --git a/docs/constant-time.md b/docs/constant-time.md index b3503a5e..ec70e11d 100644 --- a/docs/constant-time.md +++ b/docs/constant-time.md @@ -87,9 +87,10 @@ opaque authentication success/failure result may leak. Secret-bearing fixed-size keys, shared secrets, authentication tags, keypairs, and keyed outputs do not implement `PartialEq` or `Eq`. Their inherent `ct_eq` methods return `CtDecision`, an opaque, non-`Copy` value with no public -constructor, formatting, equality, or implicit boolean conversion. Decisions -can be composed with bitwise `&`, `|`, and `!`; the consuming `declassify()` -method is the only public route to a branchable equality bit. +constructor, equality, or implicit boolean conversion. Its `Debug` output is +the fixed redacted string `CtDecision(..)` and does not expose the decision. +Decisions can be composed with bitwise `&`, `|`, and `!`; the consuming +`declassify()` method is the only public route to a branchable equality bit. Verification APIs keep that boundary inside the primitive and return one opaque `Result`. Public keys, nonces, signatures, and ciphertext containers are public diff --git a/scripts/ct/artifacts.sh b/scripts/ct/artifacts.sh index b3beff05..4fefea37 100755 --- a/scripts/ct/artifacts.sh +++ b/scripts/ct/artifacts.sh @@ -114,7 +114,7 @@ fi OUT_DIR="$ROOT/target/ct/$TARGET/$PROFILE" ARTIFACT_DIR="$OUT_DIR/artifacts" BUILD_TARGET_DIR="$ROOT/target/ct-build/$TARGET/$PROFILE" -DEPS_DIR="$BUILD_TARGET_DIR/$TARGET/$PROFILE/deps" +EMIT_ROOT="$BUILD_TARGET_DIR/$TARGET/$PROFILE" rm -rf "$OUT_DIR" rm -rf "$BUILD_TARGET_DIR" mkdir -p "$ARTIFACT_DIR" @@ -138,7 +138,7 @@ LIB_EMITTED=() while IFS= read -r artifact; do LIB_EMITTED+=("$artifact") done < <( - find "$DEPS_DIR" -maxdepth 1 -type f \ + find "$EMIT_ROOT" -type f \ \( -name 'rscrypto_ct_harness*.ll' \ -o -name 'rscrypto_ct_harness*.s' \ -o -name 'rscrypto_ct_harness*.o' \ @@ -147,7 +147,7 @@ done < <( ) if [[ ${#LIB_EMITTED[@]} -eq 0 ]]; then - echo "no CT harness emitted artifacts found in $DEPS_DIR" >&2 + echo "no CT harness emitted artifacts found under $EMIT_ROOT" >&2 exit 1 fi if [[ ${#LIB_EMITTED[@]} -ne 3 ]]; then @@ -201,7 +201,7 @@ BIN_EMITTED=() while IFS= read -r artifact; do BIN_EMITTED+=("$artifact") done < <( - find "$DEPS_DIR" -maxdepth 1 -type f \ + find "$EMIT_ROOT" -type f \ \( -name 'rscrypto_ct_evidence*.ll' \ -o -name 'rscrypto_ct_evidence*.s' \ -o -name 'rscrypto_ct_evidence*.o' \ diff --git a/scripts/ct/evidence_validation_test.py b/scripts/ct/evidence_validation_test.py index 1fe86bad..da32508c 100644 --- a/scripts/ct/evidence_validation_test.py +++ b/scripts/ct/evidence_validation_test.py @@ -142,6 +142,36 @@ def main() -> None: configure_target_environment("powerpc64le-unknown-linux-gnu", unrelated_environment) assert unrelated_environment == {} + captured_rustdoc: dict[str, object] = {} + original_run = manifest_validation.subprocess.run + + def capture_rustdoc(command, **kwargs): + captured_rustdoc["command"] = command + captured_rustdoc["env"] = kwargs.get("env") + return subprocess.CompletedProcess(command, 1, "", "rustdoc unavailable") + + manifest_validation.subprocess.run = capture_rustdoc + try: + with tempfile.TemporaryDirectory() as temporary: + inventory_errors: list[str] = [] + assert ( + manifest_validation.compiler_public_api_snapshot( + Path(temporary), + "aarch64-apple-darwin", + ("rscrypto::auth",), + inventory_errors, + ) + is None + ) + assert inventory_errors == ["compiler public-API inventory failed: rustdoc unavailable"] + finally: + manifest_validation.subprocess.run = original_run + + rustdoc_env = captured_rustdoc["env"] + assert isinstance(rustdoc_env, dict) + assert rustdoc_env["RUSTC_BOOTSTRAP"] == "rscrypto" + assert captured_rustdoc["command"][-4:] == ["-Z", "unstable-options", "--output-format", "json"] + commit = "a" * 40 validate_exact_candidate("1.2.3", commit, "1.2.3", commit) expect_failure(lambda: validate_exact_candidate("1.2.3", commit, "1.2.4", commit)) diff --git a/scripts/ct/validate.py b/scripts/ct/validate.py index 1ca854a8..6a0b3946 100755 --- a/scripts/ct/validate.py +++ b/scripts/ct/validate.py @@ -6,6 +6,7 @@ import argparse import hashlib import json +import os import re import subprocess import sys @@ -242,7 +243,11 @@ def compiler_public_api_snapshot( "--output-format", "json", ] - completed = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False) + rustdoc_env = os.environ.copy() + # rustdoc JSON is unstable even when the selected release compiler supports it. Scope the escape hatch to + # rscrypto so the inventory describes the exact compiler used by this CT evidence lane. + rustdoc_env["RUSTC_BOOTSTRAP"] = "rscrypto" + completed = subprocess.run(command, cwd=root, capture_output=True, text=True, check=False, env=rustdoc_env) if completed.returncode != 0: detail = completed.stderr.strip().splitlines() suffix = f": {detail[-1]}" if detail else "" diff --git a/src/auth/mlkem/portable/aarch64.rs b/src/auth/mlkem/portable/aarch64.rs index 3e7f76e8..26763db5 100644 --- a/src/auth/mlkem/portable/aarch64.rs +++ b/src/auth/mlkem/portable/aarch64.rs @@ -62,9 +62,9 @@ unsafe extern "C" { input2: *const u8, caps: *const usize, ) -> u64; - #[cfg(any(test, feature = "diag"))] + #[cfg(test)] fn rscrypto_mlkem_rej_uniform_3blocks_aarch64_linux(out: *mut u16, input: *const u8) -> usize; - #[cfg(any(test, feature = "diag"))] + #[cfg(test)] fn rscrypto_mlkem_basemul_accumulate_aarch64_linux( acc: *mut u16, a: *const u16, @@ -187,7 +187,7 @@ pub(super) unsafe fn sample_ntt_rej_uniform_triple_block_bounded_asm( unpack_triple_counts(packed) } -#[cfg(all(any(test, feature = "diag"), target_os = "linux"))] +#[cfg(all(test, target_os = "linux"))] #[inline] pub(super) unsafe fn sample_ntt_rej_uniform_3blocks_asm(out: *mut u16, input: *const u8) -> usize { // SAFETY: Linux aarch64 three-block SampleNTT rejection parser call because: @@ -200,7 +200,7 @@ pub(super) unsafe fn sample_ntt_rej_uniform_3blocks_asm(out: *mut u16, input: *c } #[inline] -#[cfg(any(test, feature = "diag", target_os = "macos"))] +#[cfg(any(test, target_os = "macos"))] /// # Safety /// /// The active platform must provide the matching ML-KEM AArch64 assembly diff --git a/src/checksum/common/tests.rs b/src/checksum/common/tests.rs index 06c86df8..40cb73fd 100644 --- a/src/checksum/common/tests.rs +++ b/src/checksum/common/tests.rs @@ -33,6 +33,7 @@ where /// Test that combine produces crc(A || B) from crc(A) and crc(B). /// /// This is the fundamental combine property that enables parallel CRC computation. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_property(data: &[u8], split: usize) { let split = split.checked_rem(data.len()).unwrap_or(0); @@ -63,6 +64,7 @@ where } /// Test combine with empty second part (identity case). + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_empty_suffix(data: &[u8]) { let crc_data = C::checksum(data); @@ -73,6 +75,7 @@ where } /// Test combine with empty first part. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_combine_empty_prefix(data: &[u8]) { let crc_empty = C::checksum(&[]); @@ -98,6 +101,7 @@ where } /// Test streaming with byte-at-a-time updates. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_byte_at_a_time(data: &[u8]) { let oneshot = C::checksum(data); @@ -112,6 +116,7 @@ where } /// Test streaming across a specific chunk size boundary. + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_chunked(data: &[u8], chunk_size: usize) { if chunk_size == 0 { @@ -167,6 +172,7 @@ where /// 1. Streaming first half /// 2. Computing second half separately /// 3. Combining the results + #[cfg(not(miri))] #[inline] pub(in crate::checksum) fn test_streaming_and_combine(data: &[u8]) { if data.is_empty() { diff --git a/src/checksum/crc64/portable.rs b/src/checksum/crc64/portable.rs index d2ccccc9..34fe9cba 100644 --- a/src/checksum/crc64/portable.rs +++ b/src/checksum/crc64/portable.rs @@ -10,14 +10,14 @@ use crate::checksum::common::portable; /// CRC-64-XZ slice-by-8 computation. #[inline] -#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))] +#[cfg(all(test, not(miri), any(target_arch = "x86_64", target_arch = "aarch64")))] pub(super) fn crc64_slice8_xz(crc: u64, data: &[u8]) -> u64 { crc64_slice8(crc, data, &kernel_tables::XZ_TABLES_8) } /// CRC-64-NVME slice-by-8 computation. #[inline] -#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))] +#[cfg(all(test, not(miri), any(target_arch = "x86_64", target_arch = "aarch64")))] pub(super) fn crc64_slice8_nvme(crc: u64, data: &[u8]) -> u64 { crc64_slice8(crc, data, &kernel_tables::NVME_TABLES_8) } diff --git a/src/hashes/crypto/keccak.rs b/src/hashes/crypto/keccak.rs index 74dc4ea6..bac2aa97 100644 --- a/src/hashes/crypto/keccak.rs +++ b/src/hashes/crypto/keccak.rs @@ -434,7 +434,7 @@ fn read_unaligned_block_lane(block: &[u8; RATE], lane: usize) /// /// Since `RATE` is a const generic, `RATE / 8` is compile-time known and LLVM /// eliminates all `if lane < lanes` branches — the result is straight-line code. -#[cfg(target_arch = "aarch64")] +#[cfg(all(target_arch = "aarch64", not(miri)))] #[inline] fn keccakf_absorb_portable(state: &mut [u64; 25], block: &[u8; RATE]) { debug_assert_eq!(RATE % 8, 0); diff --git a/src/hashes/fast/xxh3/kernels.rs b/src/hashes/fast/xxh3/kernels.rs index f47c06f1..30ecca12 100644 --- a/src/hashes/fast/xxh3/kernels.rs +++ b/src/hashes/fast/xxh3/kernels.rs @@ -43,7 +43,7 @@ impl Xxh3KernelId { /// Long-path-only entry for 64-bit hash (>240B, no ≤240B length checks). #[cfg(any( - test, + all(test, not(miri)), not(any( all(target_arch = "x86_64", any(target_feature = "avx512f", target_feature = "avx2")), all(target_arch = "aarch64", target_feature = "neon"), @@ -69,7 +69,7 @@ pub(crate) fn hash64_long_fn(id: Xxh3KernelId) -> fn(&[u8], u64) -> u64 { /// Long-path-only entry for 128-bit hash (>240B, no ≤240B length checks). #[cfg(any( - test, + all(test, not(miri)), not(any( all(target_arch = "x86_64", any(target_feature = "avx512f", target_feature = "avx2")), all(target_arch = "aarch64", target_feature = "neon"), diff --git a/src/platform/detect/arch/aarch64.rs b/src/platform/detect/arch/aarch64.rs index 7bd534bb..f8bae080 100644 --- a/src/platform/detect/arch/aarch64.rs +++ b/src/platform/detect/arch/aarch64.rs @@ -379,6 +379,7 @@ fn runtime_aarch64() -> Caps { /// Apple Silicon chip generation. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(test, feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64"), @@ -401,6 +402,7 @@ enum AppleSiliconGen { /// Microarchitecture family used for aarch64 kernel-table selection. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64") @@ -426,6 +428,7 @@ pub(crate) enum Aarch64TuneFamily { /// /// Returns `None` for unknown/future chips or A-series (pre-M1) processors. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(test, feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64"), @@ -508,6 +511,7 @@ fn detect_apple_silicon_gen() -> Option { /// Detect the aarch64 microarchitecture family used by runtime table selection. #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(feature = "crc16", feature = "crc24", feature = "crc32", feature = "crc64") @@ -612,6 +616,7 @@ fn detect_apple_sme_features() -> Caps { // MIDR_EL1 Detection (Linux aarch64) #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -619,6 +624,7 @@ fn detect_apple_sme_features() -> Caps { ))] const MIDR_IMPLEMENTER_SHIFT: u32 = 24; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -627,6 +633,7 @@ const MIDR_IMPLEMENTER_SHIFT: u32 = 24; const MIDR_PARTNUM_SHIFT: u32 = 4; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -634,6 +641,7 @@ const MIDR_PARTNUM_SHIFT: u32 = 4; ))] const ARM_CPU_IMP_ARM: u32 = 0x41; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -641,6 +649,7 @@ const ARM_CPU_IMP_ARM: u32 = 0x41; ))] const ARM_CPU_PART_NEOVERSE_N1: u32 = 0xD0C; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -648,6 +657,7 @@ const ARM_CPU_PART_NEOVERSE_N1: u32 = 0xD0C; ))] const ARM_CPU_PART_NEOVERSE_V1: u32 = 0xD40; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -656,6 +666,7 @@ const ARM_CPU_PART_NEOVERSE_V1: u32 = 0xD40; const ARM_CPU_PART_NEOVERSE_V2: u32 = 0xD4F; #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -667,6 +678,7 @@ fn midr_implementer(midr: u32) -> u32 { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -678,6 +690,7 @@ fn midr_partnum(midr: u32) -> u32 { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -696,6 +709,7 @@ fn parse_u32_auto_radix(value: &str) -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -719,6 +733,7 @@ fn read_linux_midr_sysfs() -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -751,6 +766,7 @@ fn read_linux_midr_cpuinfo() -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"), @@ -771,6 +787,7 @@ fn map_linux_midr_to_tune_family(midr: u32) -> Option { } #[cfg(all( + not(miri), target_arch = "aarch64", feature = "std", any(target_os = "linux", target_os = "android"),