Repository navigation
Conversation
The AVX2 cmov built its blend mask with _mm256_set1_epi64x(b as i64), so for b == 1 every byte's high bit is 0 and _mm256_blendv_epi8 copies nothing. cmov is the Fujisaki-Okamoto implicit-rejection step in decapsulation (called with len == 32, entirely through the vector loop), so on an invalid ciphertext the rejection value was never applied, turning decapsulation into a chosen- ciphertext plaintext-checking oracle and enabling static key recovery. Fix: negate b once to 0x00/0xFF and broadcast per byte with _mm256_set1_epi8, matching the pq-crystals reference; drop the now-redundant scalar-tail negation.
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.
Problem
The AVX2
cmovinsrc/avx2/verify.rsbuilds its blend mask with_mm256_set1_epi64x(b as i64). Forb == 1each 64-bit lane is0x00000000_00000001, so every byte's high bit is 0 and_mm256_blendv_epi8copies nothing. The vector loop is a no-op for anyb.That
cmovis the Fujisaki-Okamoto implicit-rejection step incrypto_kem_dec(called withlen == KYBER_SYMBYTES == 32, entirely through the vector loop, no scalar tail). On an invalid ciphertext the rejection secret is never applied, so the returned shared secret depends on the decrypted plaintext of the attacker-chosen ciphertext. That is a chosen-ciphertext plaintext-checking oracle and allows recovery of a reused static secret key. Verified end to end on ML-KEM-768 (4,272 decapsulation queries, full key recovered). Only the opt-inavx2feature is affected; the reference backend is correct.Fix
Negate
bonce to0x00/0xFFand broadcast per byte with_mm256_set1_epi8, matching the pq-crystals reference AVX2 code. The scalar tail then reuses the already-negatedb, so its previousb = b.wrapping_neg()is removed to avoid a double negation.Note: the crate appears unmaintained (last release 0.7.1, August 2023). Filing this fix for the public record and as the upstream reference for a RustSec advisory.