-
Notifications
You must be signed in to change notification settings - Fork 177
Compiler-introduced timing leak in Kyber reference implementation #556
Description
The following code may produce a secret-dependent branch when compiled under Clang:
PQClean/crypto_kem/kyber512/clean/poly.c
Lines 116 to 126 in f2996f2
| void PQCLEAN_KYBER512_CLEAN_poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES]) { | |
| size_t i, j; | |
| int16_t mask; | |
| for (i = 0; i < KYBER_N / 8; i++) { | |
| for (j = 0; j < 8; j++) { | |
| mask = -(int16_t)((msg[i] >> j) & 1); | |
| r->coeffs[8 * i + j] = mask & ((KYBER_Q + 1) / 2); | |
| } | |
| } | |
| } |
The function poly_frommsg produces a polynomial based on the bits of m during both encapsulation and decapsulation. Despite the source-level mitigations in poly_frommsg, the latest generations of Clang recognize that the code essentially performs a bit test and produces a secret-dependent branch for several compiler options.
Here are a few compiler options (Clang 15-16-17-18 on x86) which produce a branch (Godbolt link):
-Os-O1-O2 -fno-vectorize-O3 -fno-vectorize
Note that plain -O2 and -O3 (i.e., without fno-vectorize) produce branch and vectorized code side by side in the binary. In practice, the vectorized instructions appear to be selected for execution at runtime.
The timing leak can be used to implement a plaintext-checking oracle.
Thanks to the help of Peter Schwabe, the pqcrystals/kyber upstream already has a fix available for this issue: pq-crystals/kyber@9b8d306