CVE-2024-37880 (published 2024-06-10T02:15Z)

The Kyber reference implementation before 9b8d306, when compiled by LLVM Clang through 18.x with some common optimization options, has a timing side channel that allows attackers to recover an ML-KEM 512 secret key in minutes. This occurs because poly_frommsg in poly.c does not prevent Clang from emitting a vulnerable secret-dependent branch.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-203
cwe_name: Observable Discrepancy
description: The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not.
extended_description: Discrepancies can take many forms, and variations may be detectable in timing, control flow, communications such as replies or requests, or general behavior. These discrepancies can reveal information about the product's operation or internal state to an unauthorized actor. In some cases, discrepancies can be used by attackers to form a side channel.
         

Fix information:
repo_url: https://github.com/pq-crystals/kyber
commit_details: repo_url: https://github.com/pq-crystals/kyber
author: Peter Schwabe
author_date: 2024-06-03 09:00:59+02:00
msg: Fixed secret-dependent branch in poly_frommsg introduced by recent versions of clang with some flags (Thanks to Antoon Purnal for pointing this out!)
repository: repo_url: https://github.com/pq-crystals/kyber
repo_name: pq-crystals/kyber
repo_language: C
owner: pq-crystals
file_changes: filename: poly.c
change_type: ModificationType.MODIFY
diff: @@ -5,6 +5,7 @@
 #include "reduce.h"
 #include "cbd.h"
 #include "symmetric.h"
+#include "verify.h"
 
 /*************************************************
 * Name:        poly_compress
@@ -166,7 +167,6 @@ void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES])
 void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES])
 {
   unsigned int i,j;
-  int16_t mask;
 
 #if (KYBER_INDCPA_MSGBYTES != KYBER_N/8)
 #error "KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!"
@@ -174,8 +174,8 @@ void poly_frommsg(poly *r, const uint8_t msg[KYBER_INDCPA_MSGBYTES])
 
   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);
+      r->coeffs[8*i+j] = 0;
+      cmov_int16(r->coeffs+8*i+j, ((KYBER_Q+1)/2), (msg[i] >> j)&1);
     }
   }
 }

programming_language: C
filename: verify.c
change_type: ModificationType.MODIFY
diff: @@ -55,3 +55,10 @@ void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b)
   for(i=0;i<len;i++)
     r[i] ^= b & (r[i] ^ x[i]);
 }
+
+
+void cmov_int16(int16_t *r, int16_t v, uint16_t b)
+{
+  b = -b;
+  *r ^= b & ((*r) ^ v);
+}

programming_language: C
filename: verify.h
change_type: ModificationType.MODIFY
diff: @@ -11,4 +11,7 @@ int verify(const uint8_t *a, const uint8_t *b, size_t len);
 #define cmov KYBER_NAMESPACE(cmov)
 void cmov(uint8_t *r, const uint8_t *x, size_t len, uint8_t b);
 
+#define cmov_int16 KYBER_NAMESPACE(cmov_int16)
+void cmov_int16(int16_t *r, int16_t v, uint16_t b);
+
 #endif

programming_language: C