Skip to content

Commit 200f1da

Browse files
committed
Destruct intermediate buffers on the stack
FIPS-203, Section 3.3 demands the destruction of intermediate values before returning to the caller. This commit implements this. A new function `ct_zeroize()` is introduced which is used to wipe intermediate stack buffers prior to function return. (NB: The constant-time'ness is not relevant here, but conceptually the function is still best-placed in verify.h alongside other functions which, when naively implemented, would be prone to harmful compiler optimization -- ct_zeroize fits this category). By default, ct_zeroize() is implemented via SecureZeroMemory on Windows, and a plain memset + compiler barrier otherwise. If neither makes sense, compilation fails. Using memset_s would be preferred, but there is no portable way of detecting whether it is available. If users need to register a custom ct_zeroize, they can do so by defining MLK_CT_ZEROIZE_NATIVE and defining ct_zeroize_native, similar to how the arithmetic and FIPS-202 backends work. This includes the case where the use does not wish to do stack zeroization, or has an entirely separate (and more robust) method for it, such as compiler instrumentation. In this case, `ct_zeroize_native()` can be set to a no-op. Signed-off-by: Hanno Becker <[email protected]>
1 parent 5cde557 commit 200f1da

File tree

24 files changed

+253
-43
lines changed

24 files changed

+253
-43
lines changed

dev/x86_64/src/basemul.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#if defined(MLK_ARITH_BACKEND_X86_64_DEFAULT) && \
99
!defined(MLK_MULTILEVEL_BUILD_NO_SHARED)
1010

11+
#include "../../../verify.h"
1112
#include "arith_native_x86_64.h"
1213
#include "consts.h"
1314

@@ -57,6 +58,9 @@ void polyvec_basemul_acc_montgomery_cached_avx2(unsigned k, int16_t r[MLKEM_N],
5758
poly_basemul_montgomery_avx2(t, &a[i * MLKEM_N], &b[i * MLKEM_N]);
5859
poly_add_avx2(r, r, t);
5960
}
61+
62+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
63+
ct_zeroize(t, sizeof(t));
6064
}
6165

6266
#else /* defined(MLK_ARITH_BACKEND_X86_64_DEFAULT) && \

examples/monolithic_build/mlkem_native_monobuild.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,15 @@
287287
#undef MLK_CT_TESTING_DECLASSIFY
288288
#undef MLK_CT_TESTING_SECRET
289289
#undef MLK_DEFAULT_ALIGN
290+
#undef MLK_HAVE_INLINE_ASM
290291
#undef MLK_INLINE
291292
#undef MLK_RESTRICT
292293
#undef MLK_SYS_AARCH64
293294
#undef MLK_SYS_AARCH64_EB
294295
#undef MLK_SYS_BIG_ENDIAN
295296
#undef MLK_SYS_H
296297
#undef MLK_SYS_LITTLE_ENDIAN
298+
#undef MLK_SYS_WINDOWS
297299
#undef MLK_SYS_X86_64
298300
#undef MLK_SYS_X86_64_AVX2
299301
/* mlkem/verify.h */
@@ -307,6 +309,7 @@
307309
#undef ct_opt_blocker_u64
308310
#undef ct_sel_int16
309311
#undef ct_sel_uint8
312+
#undef ct_zeroize
310313
#undef value_barrier_i32
311314
#undef value_barrier_u32
312315
#undef value_barrier_u8

mlkem/config.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,46 @@
235235
*****************************************************************************/
236236
/* #define MLK_FIPS202X4_CUSTOM_HEADER "SOME_FILE.h" */
237237

238+
/******************************************************************************
239+
* Name: MLK_USE_CT_ZEROIZE_NATIVE
240+
*
241+
* Description: In compliance with FIPS-203 Section 3.3, mlkem-native zeroizes
242+
* intermediate stack buffers before returning from function calls.
243+
*
244+
* Set this option and define `ct_zeroize_native` if you want to
245+
* use a custom method to zeroize intermediate stack buffers.
246+
* The default implementation uses SecureZeroMemory on Windows
247+
* and a memset + compiler barrier otherwise. If neither of those
248+
* is available on the target platform, compilation will fail,
249+
* and you will need to use MLK_USE_CT_ZEROIZE_NATIVE to provide
250+
* a custom implementation of `ct_zeroize_native()`.
251+
*
252+
* WARNING:
253+
* The explicit stack zeroization conducted by mlkem-native
254+
* reduces the likelihood of data leaking on the stack, but
255+
* does not eliminate it! The C standard makes no guarantee about
256+
* where a compiler allocates structures and whether/where it makes
257+
* copies of them. Also, in addition to entire structures, there
258+
* may also be potentially exploitable leakage of individual values
259+
* on the stack.
260+
*
261+
* If you need bullet-proof zeroization of the stack, you need to
262+
* consider additional measures instead of of what this feature
263+
* provides. In this case, you can set ct_zeroize_native to a
264+
* no-op.
265+
*
266+
*****************************************************************************/
267+
/* #define MLK_USE_CT_ZEROIZE_NATIVE
268+
#if !defined(__ASSEMBLER__)
269+
#include <stdint.h>
270+
#include "sys.h"
271+
static MLK_INLINE void ct_zeroize_native(void *ptr, size_t len)
272+
{
273+
... your implementation ...
274+
}
275+
#endif
276+
*/
277+
238278
/************************* Config internals ********************************/
239279

240280
/* Default namespace

mlkem/fips202/fips202.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <stddef.h>
1818
#include <stdint.h>
1919
#include <string.h>
20+
#include "../verify.h"
2021
#include "fips202.h"
2122
#include "keccakf1600.h"
2223

@@ -184,7 +185,11 @@ void shake128_squeezeblocks(uint8_t *output, size_t nblocks, shake128ctx *state)
184185
}
185186

186187
void shake128_init(shake128ctx *state) { (void)state; }
187-
void shake128_release(shake128ctx *state) { (void)state; }
188+
void shake128_release(shake128ctx *state)
189+
{
190+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
191+
ct_zeroize(state, sizeof(shake128ctx));
192+
}
188193

189194
#define shake256ctx MLK_NAMESPACE(shake256ctx)
190195
typedef shake128ctx shake256ctx;
@@ -196,6 +201,8 @@ void shake256(uint8_t *output, size_t outlen, const uint8_t *input,
196201
keccak_absorb_once(state.ctx, SHAKE256_RATE, input, inlen, 0x1F);
197202
/* Squeeze output */
198203
keccak_squeeze_once(output, outlen, state.ctx, SHAKE256_RATE);
204+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
205+
ct_zeroize(&state, sizeof(state));
199206
}
200207

201208
void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen)
@@ -205,6 +212,8 @@ void sha3_256(uint8_t *output, const uint8_t *input, size_t inlen)
205212
keccak_absorb_once(ctx, SHA3_256_RATE, input, inlen, 0x06);
206213
/* Squeeze output */
207214
keccak_squeeze_once(output, 32, ctx, SHA3_256_RATE);
215+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
216+
ct_zeroize(ctx, sizeof(ctx));
208217
}
209218

210219
void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen)
@@ -214,6 +223,8 @@ void sha3_512(uint8_t *output, const uint8_t *input, size_t inlen)
214223
keccak_absorb_once(ctx, SHA3_512_RATE, input, inlen, 0x06);
215224
/* Squeeze output */
216225
keccak_squeeze_once(output, 64, ctx, SHA3_512_RATE);
226+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
227+
ct_zeroize(ctx, sizeof(ctx));
217228
}
218229

219230
#else /* MLK_MULTILEVEL_BUILD_NO_SHARED */

mlkem/fips202/fips202x4.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#if !defined(MLK_MULTILEVEL_BUILD_NO_SHARED)
77

88
#include <string.h>
9+
#include "../verify.h"
910
#include "fips202.h"
1011
#include "fips202x4.h"
1112
#include "keccakf1600.h"
@@ -132,7 +133,11 @@ void shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
132133
}
133134

134135
void shake128x4_init(shake128x4ctx *state) { (void)state; }
135-
void shake128x4_release(shake128x4ctx *state) { (void)state; }
136+
void shake128x4_release(shake128x4ctx *state)
137+
{
138+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
139+
ct_zeroize(state, sizeof(shake128x4ctx));
140+
}
136141

137142
static void shake256x4_absorb_once(shake256x4_ctx *state, const uint8_t *in0,
138143
const uint8_t *in1, const uint8_t *in2,
@@ -180,6 +185,13 @@ void shake256x4(uint8_t *out0, uint8_t *out1, uint8_t *out2, uint8_t *out3,
180185
memcpy(out2, tmp2, outlen);
181186
memcpy(out3, tmp3, outlen);
182187
}
188+
189+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
190+
ct_zeroize(&statex, sizeof(statex));
191+
ct_zeroize(tmp0, sizeof(tmp0));
192+
ct_zeroize(tmp1, sizeof(tmp1));
193+
ct_zeroize(tmp2, sizeof(tmp2));
194+
ct_zeroize(tmp3, sizeof(tmp3));
183195
}
184196

185197
#else /* MLK_MULTILEVEL_BUILD_NO_SHARED */

mlkem/indcpa.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ void gen_matrix(polyvec *a, const uint8_t seed[MLKEM_SYMBYTES], int transposed)
243243
poly_permute_bitrev_to_custom(a[i].vec[j].coeffs);
244244
}
245245
}
246+
247+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
248+
ct_zeroize(seed0, sizeof(seed0));
249+
ct_zeroize(seed1, sizeof(seed1));
250+
ct_zeroize(seed2, sizeof(seed2));
251+
ct_zeroize(seed3, sizeof(seed3));
246252
}
247253

248254
/*************************************************
@@ -343,6 +349,14 @@ void indcpa_keypair_derand(uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES],
343349

344350
pack_sk(sk, &skpv);
345351
pack_pk(pk, &pkpv, publicseed);
352+
353+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
354+
ct_zeroize(buf, sizeof(buf));
355+
ct_zeroize(coins_with_domain_separator, sizeof(coins_with_domain_separator));
356+
ct_zeroize(a, sizeof(a));
357+
ct_zeroize(&e, sizeof(e));
358+
ct_zeroize(&skpv, sizeof(skpv));
359+
ct_zeroize(&skpv_cache, sizeof(skpv_cache));
346360
}
347361

348362

@@ -409,6 +423,17 @@ void indcpa_enc(uint8_t c[MLKEM_INDCPA_BYTES],
409423
poly_reduce(&v);
410424

411425
pack_ciphertext(c, &b, &v);
426+
427+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
428+
ct_zeroize(seed, sizeof(seed));
429+
ct_zeroize(&sp, sizeof(sp));
430+
ct_zeroize(&sp_cache, sizeof(sp_cache));
431+
ct_zeroize(&b, sizeof(b));
432+
ct_zeroize(&v, sizeof(v));
433+
ct_zeroize(at, sizeof(at));
434+
ct_zeroize(&k, sizeof(k));
435+
ct_zeroize(&ep, sizeof(ep));
436+
ct_zeroize(&epp, sizeof(epp));
412437
}
413438

414439
MLK_INTERNAL_API
@@ -432,6 +457,13 @@ void indcpa_dec(uint8_t m[MLKEM_INDCPA_MSGBYTES],
432457
poly_reduce(&v);
433458

434459
poly_tomsg(m, &v);
460+
461+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
462+
ct_zeroize(&skpv, sizeof(skpv));
463+
ct_zeroize(&b, sizeof(b));
464+
ct_zeroize(&b_cache, sizeof(b_cache));
465+
ct_zeroize(&v, sizeof(v));
466+
ct_zeroize(&sb, sizeof(sb));
435467
}
436468

437469
/* To facilitate single-compilation-unit (SCU) builds, undefine all macros.

mlkem/kem.c

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,21 @@ __contract__(
4444
**************************************************/
4545
static int check_pk(const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES])
4646
{
47+
int res;
4748
polyvec p;
4849
uint8_t p_reencoded[MLKEM_POLYVECBYTES];
50+
4951
polyvec_frombytes(&p, pk);
5052
polyvec_reduce(&p);
5153
polyvec_tobytes(p_reencoded, &p);
54+
5255
/* Data is public, so a variable-time memcmp() is OK */
53-
if (memcmp(pk, p_reencoded, MLKEM_POLYVECBYTES))
54-
{
55-
return -1;
56-
}
57-
return 0;
56+
res = memcmp(pk, p_reencoded, MLKEM_POLYVECBYTES) ? -1 : 0;
57+
58+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
59+
ct_zeroize(p_reencoded, sizeof(p_reencoded));
60+
ct_zeroize(&p, sizeof(p));
61+
return res;
5862
}
5963

6064
/*************************************************
@@ -73,6 +77,7 @@ static int check_pk(const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES])
7377
**************************************************/
7478
static int check_sk(const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES])
7579
{
80+
int res;
7681
MLK_ALIGN uint8_t test[MLKEM_SYMBYTES];
7782
/*
7883
* The parts of `sk` being hashed and compared here are public, so
@@ -87,12 +92,14 @@ static int check_sk(const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES])
8792
sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, MLKEM_SYMBYTES);
8893

8994
hash_h(test, sk + MLKEM_INDCPA_SECRETKEYBYTES, MLKEM_INDCCA_PUBLICKEYBYTES);
90-
if (memcmp(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, test,
91-
MLKEM_SYMBYTES))
92-
{
93-
return -1;
94-
}
95-
return 0;
95+
res = memcmp(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, test,
96+
MLKEM_SYMBYTES)
97+
? -1
98+
: 0;
99+
100+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
101+
ct_zeroize(test, sizeof(test));
102+
return res;
96103
}
97104

98105
int crypto_kem_keypair_derand(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
@@ -115,6 +122,10 @@ int crypto_kem_keypair(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
115122
MLK_ALIGN uint8_t coins[2 * MLKEM_SYMBYTES];
116123
randombytes(coins, 2 * MLKEM_SYMBYTES);
117124
crypto_kem_keypair_derand(pk, sk, coins);
125+
126+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
127+
ct_zeroize(coins, sizeof(coins));
128+
118129
return 0;
119130
}
120131

@@ -142,16 +153,26 @@ int crypto_kem_enc_derand(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
142153
indcpa_enc(ct, buf, pk, kr + MLKEM_SYMBYTES);
143154

144155
memcpy(ss, kr, MLKEM_SYMBYTES);
156+
157+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
158+
ct_zeroize(buf, sizeof(buf));
159+
ct_zeroize(kr, sizeof(kr));
160+
145161
return 0;
146162
}
147163

148164
int crypto_kem_enc(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
149165
uint8_t ss[MLKEM_SSBYTES],
150166
const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES])
151167
{
168+
int res;
152169
MLK_ALIGN uint8_t coins[MLKEM_SYMBYTES];
153170
randombytes(coins, MLKEM_SYMBYTES);
154-
return crypto_kem_enc_derand(ct, ss, pk, coins);
171+
res = crypto_kem_enc_derand(ct, ss, pk, coins);
172+
173+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
174+
ct_zeroize(coins, sizeof(coins));
175+
return res;
155176
}
156177

157178
int crypto_kem_dec(uint8_t ss[MLKEM_SSBYTES],
@@ -162,6 +183,8 @@ int crypto_kem_dec(uint8_t ss[MLKEM_SSBYTES],
162183
MLK_ALIGN uint8_t buf[2 * MLKEM_SYMBYTES];
163184
/* Will contain key, coins */
164185
MLK_ALIGN uint8_t kr[2 * MLKEM_SYMBYTES];
186+
MLK_ALIGN uint8_t tmp[MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES];
187+
165188
const uint8_t *pk = sk + MLKEM_INDCPA_SECRETKEYBYTES;
166189

167190
if (check_sk(sk))
@@ -177,27 +200,24 @@ int crypto_kem_dec(uint8_t ss[MLKEM_SSBYTES],
177200
hash_g(kr, buf, 2 * MLKEM_SYMBYTES);
178201

179202
/* Recompute and compare ciphertext */
180-
{
181-
/* Temporary buffer */
182-
MLK_ALIGN uint8_t cmp[MLKEM_INDCCA_CIPHERTEXTBYTES];
183-
/* coins are in kr+MLKEM_SYMBYTES */
184-
indcpa_enc(cmp, buf, pk, kr + MLKEM_SYMBYTES);
185-
fail = ct_memcmp(ct, cmp, MLKEM_INDCCA_CIPHERTEXTBYTES);
186-
}
203+
/* coins are in kr+MLKEM_SYMBYTES */
204+
indcpa_enc(tmp, buf, pk, kr + MLKEM_SYMBYTES);
205+
fail = ct_memcmp(ct, tmp, MLKEM_INDCCA_CIPHERTEXTBYTES);
187206

188207
/* Compute rejection key */
189-
{
190-
/* Temporary buffer */
191-
MLK_ALIGN uint8_t tmp[MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES];
192-
memcpy(tmp, sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES,
193-
MLKEM_SYMBYTES);
194-
memcpy(tmp + MLKEM_SYMBYTES, ct, MLKEM_INDCCA_CIPHERTEXTBYTES);
195-
hash_j(ss, tmp, sizeof(tmp));
196-
}
208+
memcpy(tmp, sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES,
209+
MLKEM_SYMBYTES);
210+
memcpy(tmp + MLKEM_SYMBYTES, ct, MLKEM_INDCCA_CIPHERTEXTBYTES);
211+
hash_j(ss, tmp, sizeof(tmp));
197212

198213
/* Copy true key to return buffer if fail is 0 */
199214
ct_cmov_zero(ss, kr, MLKEM_SYMBYTES, fail);
200215

216+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
217+
ct_zeroize(buf, sizeof(buf));
218+
ct_zeroize(kr, sizeof(kr));
219+
ct_zeroize(tmp, sizeof(tmp));
220+
201221
return 0;
202222
}
203223

mlkem/native/x86_64/src/basemul.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#if defined(MLK_ARITH_BACKEND_X86_64_DEFAULT) && \
99
!defined(MLK_MULTILEVEL_BUILD_NO_SHARED)
1010

11+
#include "../../../verify.h"
1112
#include "arith_native_x86_64.h"
1213
#include "consts.h"
1314

@@ -57,6 +58,9 @@ void polyvec_basemul_acc_montgomery_cached_avx2(unsigned k, int16_t r[MLKEM_N],
5758
poly_basemul_montgomery_avx2(t, &a[i * MLKEM_N], &b[i * MLKEM_N]);
5859
poly_add_avx2(r, r, t);
5960
}
61+
62+
/* FIPS 203. Section 3.3 Destruction of intermediate values. */
63+
ct_zeroize(t, sizeof(t));
6064
}
6165

6266
#else /* defined(MLK_ARITH_BACKEND_X86_64_DEFAULT) && \

0 commit comments

Comments
 (0)