Skip to content

Compiler-introduced timing leak in Kyber reference implementation #556

@antoonpurnal

Description

@antoonpurnal

The following code may produce a secret-dependent branch when compiled under Clang:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions