Repository navigation
feat: release-assembly check and decaps timing test for the WP-8 gate (#16) - #40
Closed
Zikithezikit wants to merge 5 commits into
Closed
Zikithezikit wants to merge 5 commits into
Zikithezikit wants to merge 5 commits into
Conversation
scripts/asm_check.py builds the library as an rlib for x86_64, aarch64, i686 and armv7, disassembles it with llvm-objdump and follows the call relocations from the decapsulation entry points of ML-KEM and of the legacy Kyber code. Every function reachable from them is checked for division instructions and division helpers, memcmp-like calls and computed jumps. Conditional branches are classified as loop, panic guard or other; "other" branches must be in scripts/asm_check_allowlist.json with a per-target ceiling and a reason, so a changed count forces a new review. Nothing under src/ changes: rustc puts each function in its own section and the closure walk does not depend on which helpers get inlined. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
scripts/timing_decaps.py times decapsulation of valid, one-bit-flipped and random ciphertexts for a fixed key through the private extension functions, for ML-KEM and for the legacy Kyber classes. It interleaves the classes in a seeded random order, computes Welch's t on raw and cropped timings, and flags a pair only when |t| exceeds the threshold overall and in both halves of the run with the same sign. It prints the smallest difference it could have detected, and --inject-ns adds a known delay as a positive control. It is a measurement tool and is not part of cargo test, pytest or scripts/check.sh: it is slow and depends on the machine. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The job pins rustc 1.95.0, the toolchain the allowlist was reviewed with, so a new stable release cannot turn it red on its own; a bump comes with a new run of scripts/asm_check.py. It stays out of scripts/check.sh and is not a required check. The timing test remains a manual step. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tool versions, targets, what scripts/asm_check.py and scripts/timing_decaps.py checked and found, what they did not check, two proposed findings (SEC-001 for the legacy code, SEC-002 informational), the dated source check, and the commands to rerun them. The valgrind variant is documented as not run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the compiler-level half of the WP-8 security gate (issue #16). It adds two tools, a CI job for one of them, and the results in SECURITY.md. Nothing under src/ or python/pykyber/ is touched, and there are no dependency changes. Role here was audit and tooling, so findings are reported and not fixed.
Commits
release assembly checkjobWhat the assembly script does
It builds the library as an rlib (
cargo rustc --locked --release --lib --crate-type rlib --target T) for x86_64, aarch64, i686 and armv7, disassembles it with the llvm-objdump from the toolchain and starts at the decaps entry points (ML-KEMmlkem::kem::decaps, legacycrypto_kem_dec,_512,_1024). rustc puts every function in its own section, so no #[inline(never)] and no probe crate is needed. Because release builds inlinepoly_from_msg, the compress routines,verifyandcmovinto their callers, it follows call relocations and checks every function reachable from the roots (39 functions for ML-KEM and 47 for legacy on every target), and reports which of the named leaf functions exist as symbols of their own. An rlib needs no linker, so the cross targets build here.Per function it looks for division (div, idiv, udiv, sdiv, division helpers such as __udivdi3 and __aeabi_idiv, and the divide-by-zero panic), memcmp-like calls, and computed jumps. A tool cannot tell a secret branch from a public one, so conditional branches are classified: backward branches are loops, branches into a block that ends in a panic call are bounds guards, and the rest are "other". Every "other" branch has to be covered by a ceiling per function and target in the allowlist, with a reason. I wrote the ceilings by reading the compare that feeds each branch (
--show-other). More branches than the ceiling is a finding, so a compiler or source change forces a new review.Timing test
scripts/timing_decaps.py times decaps for one fixed key on valid ciphertexts, ciphertexts with one flipped bit and random ciphertexts, through the private extension functions, for ML-KEM and legacy Kyber. 100 000 timings per class, seed 20261030, interleaved in seeded random order, Welch t on raw and cropped timings, flag only if |t| > 5.0 overall and in both halves with the same sign. It is a measurement tool, not a correctness test, so it is outside cargo test, pytest and scripts/check.sh (it is slow and machine dependent); how to run it is in SECURITY.md. The asm check runs in CI on a pinned rustc 1.95.0 and is not a required check and not in check.sh. The timing test is a manual step before a release, because shared runners are too noisy for it.
Findings (AGENTS.md format)
No finding in the ML-KEM code. For the done-criterion "ML-KEM passes": the tools found nothing in ML-KEM under the limits below, which is not the same as a guarantee.
Verified (how)
python scripts/asm_check.pyon a clean clone of this branch: exit 0, no findings, 0 division instructions or helper calls on all four targets in both closures, 0 computed jumps, 1 bcmp site per ML-KEM decaps on armv7 (allowlisted). Output is pasted below.compress.rsis reported as div/udiv plus the divide-by-zero panic on x86_64 and aarch64, and replacing the select inreject_on_mismatchbyif black_box(diff) != 0raises the decaps branch count above its ceiling. A plainif diff != 0produced no finding because LLVM turned it back into a select.callq *(%rip)with GOTPCREL relocations, which my first version did not follow, so the x86_64 and i686 closures were far too small (4 and 8 functions) and the first "no findings" there was not meaningful. All four targets now give 39/47 functions, and I reviewed the new x86_64 and i686 branch listings before updating the allowlist.--sets 768 -n 100000 --cpu 2on a clean clone (i7-7700, CPython 3.14.4, powersave governor): no flag for ML-KEM-768 or Kyber-768, largest |t| 2.00. Full run for 512/768/1024 on the working tree: no flag, largest |t| 2.59. Detectable difference at |t| = 5, from the observed variance, was 300 to 920 ns on uncropped timings and as low as 16 ns on the 80th-percentile crop in the quietest runs. Positive control: 100 ns and 200 ns injected into the non-valid classes were flagged for both families at 768, on the cropped tests (uncropped at 100 ns reached only |t| of about 4).git rebase --exec 'bash scripts/check.sh' origin/dev-v2, every commit. Note thatcargo +stableis 1.95.0 on this machine, so the 1.99.1 run is the "recent stable" check.Believed, not verified
Not done
Out of scope, noted only
Sources checked, 2026-10-30
PR checklist
git rebase --exec)Real output of the assembly check (clean clone):
Real output of the timing test (clean clone, 768):
Generated with Claude Code