Skip to content

Undefined behavior in the FrodoKEM reference implementation #36

Description

@protz

As it stands, the FrodoKEM source code contains several instances of undefined behavior. This makes it hard to reason about what the code may do, since the compiler can potentially do anything -- it may also turn into security issues.

The two chief issues are:

  • lack of alignment of some stack variables, which are then cast as as pointers of type uint32_t*. The C standard mandates that such pointers be aligned on a sizeof(uint32) == 4 byte boundary, but right now, these pointers are only aligned on a two-bytes boundary because they originate from a stack allocation of uint16_ts.
  • potentially violating strict aliasing by having two live pointers that are aliases, yet have different types, none of which is char*

See https://blog.regehr.org/archives/1520 for some background context about UB and why it undermines properties of programs (including security and correctness).

The first issue could potentially be fixed as follows:

diff --git a/FrodoKEM/src/frodo_macrify.c b/FrodoKEM/src/frodo_macrify.c
index 0acc623..3c1b08d 100644
--- a/FrodoKEM/src/frodo_macrify.c
+++ b/FrodoKEM/src/frodo_macrify.c
@@ -344,7 +344,7 @@ void frodo_key_encode(uint16_t *out, const uint16_t *in)
     for (i = 0; i < nwords; i++) {
         temp = 0;
         for(j = 0; j < PARAMS_EXTRACTED_BITS; j++)
-            temp |= ((uint64_t)((uint8_t*)in)[i*PARAMS_EXTRACTED_BITS + j]) << (8*j);
+            temp |= ((uint64_t)((const uint8_t*)in)[i*PARAMS_EXTRACTED_BITS + j]) << (8*j);
         for (j = 0; j < npieces_word; j++) {
             *pos = (uint16_t)((temp & mask) << (PARAMS_LOGQ - PARAMS_EXTRACTED_BITS));
             temp >>= PARAMS_EXTRACTED_BITS;
diff --git a/FrodoKEM/src/kem.c b/FrodoKEM/src/kem.c
index c3152a9..fb3b7af 100644
--- a/FrodoKEM/src/kem.c
+++ b/FrodoKEM/src/kem.c
@@ -23,8 +23,8 @@ int crypto_kem_keypair(unsigned char* pk, unsigned char* sk)
     uint8_t *sk_pk = &sk[CRYPTO_BYTES];
     uint8_t *sk_S = &sk[CRYPTO_BYTES + CRYPTO_PUBLICKEYBYTES];
     uint8_t *sk_pkh = &sk[CRYPTO_BYTES + CRYPTO_PUBLICKEYBYTES + 2*PARAMS_N*PARAMS_NBAR];
-    uint16_t B[PARAMS_N*PARAMS_NBAR] = {0};
-    uint16_t S[2*PARAMS_N*PARAMS_NBAR] = {0};                          // contains secret data
+    ALIGN_HEADER(32) uint16_t B[PARAMS_N*PARAMS_NBAR] ALIGN_FOOTER(32) = {0};
+    ALIGN_HEADER(32) uint16_t S[2*PARAMS_N*PARAMS_NBAR] ALIGN_FOOTER(32) = {0};                          // contains secret data
     uint16_t *E = (uint16_t *)&S[PARAMS_N*PARAMS_NBAR];                // contains secret data
     uint8_t randomness[CRYPTO_BYTES + BYTES_SEED_SE + BYTES_SEED_A];   // contains secret data via randomness_s and randomness_seedSE
     uint8_t *randomness_s = &randomness[0];                            // contains secret data
@@ -93,7 +93,7 @@ int crypto_kem_enc(unsigned char *ct, unsigned char *ss, const unsigned char *pk
     ALIGN_HEADER(32) uint16_t Sp[(2*PARAMS_N+PARAMS_NBAR)*PARAMS_NBAR] ALIGN_FOOTER(32) = {0};  // contains secret data
     uint16_t *Ep = (uint16_t *)&Sp[PARAMS_N*PARAMS_NBAR];              // contains secret data
     uint16_t *Epp = (uint16_t *)&Sp[2*PARAMS_N*PARAMS_NBAR];           // contains secret data
-    uint8_t G2in[BYTES_PKHASH + BYTES_MU + BYTES_SALT];                // contains secret data via mu
+    ALIGN_HEADER(32) uint8_t G2in[BYTES_PKHASH + BYTES_MU + BYTES_SALT] ALIGN_FOOTER(32);                // contains secret data via mu
     uint8_t *pkh = &G2in[0];
     uint8_t *mu = &G2in[BYTES_PKHASH];                                 // contains secret data
     uint8_t *salt = &G2in[BYTES_PKHASH + BYTES_MU];
@@ -184,7 +184,7 @@ int crypto_kem_dec(unsigned char *ss, const unsigned char *ct, const unsigned ch
     const uint8_t *sk_pkh = &sk[CRYPTO_BYTES + CRYPTO_PUBLICKEYBYTES + 2*PARAMS_N*PARAMS_NBAR];
     const uint8_t *pk_seedA = &sk_pk[0];
     const uint8_t *pk_b = &sk_pk[BYTES_SEED_A];
-    uint8_t G2in[BYTES_PKHASH + BYTES_MU + BYTES_SALT];                // contains secret data via muprime
+    ALIGN_HEADER(32) uint8_t G2in[BYTES_PKHASH + BYTES_MU + BYTES_SALT] ALIGN_FOOTER(32);                // contains secret data via muprime
     uint8_t *pkh = &G2in[0];
     uint8_t *muprime = &G2in[BYTES_PKHASH];                            // contains secret data
     uint8_t *G2in_salt = &G2in[BYTES_PKHASH + BYTES_MU];

However, this does not resolve the issue with strict aliasing.

The second issue can be fixed as follows:

diff --git a/FrodoKEM/src/frodo_macrify.c b/FrodoKEM/src/frodo_macrify.c
index 0acc623..3c1b08d 100644
--- a/FrodoKEM/src/frodo_macrify.c
+++ b/FrodoKEM/src/frodo_macrify.c
@@ -30,7 +30,7 @@ int frodo_mul_add_as_plus_e(uint16_t *out, const uint16_t *s, const uint16_t *e,
     ALIGN_HEADER(32) int16_t a_row[4*PARAMS_N] ALIGN_FOOTER(32) = {0};

     for (i = 0; i < (PARAMS_N*PARAMS_NBAR); i += 2) {
-        *((uint32_t*)&out[i]) = *((uint32_t*)&e[i]);
+        memcpy(&out[i], &e[i], 4);
     }

This also fixes the first issue as a side-effect. However, I wonder why the loop is necessary here -- could this simply be replaced by a memcpy?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions