Skip to content

Commit e53c19c

Browse files
tob-joecodex
andcommitted
Clear ML-KEM outputs on KEM failure
Zero caller-visible keypair, encapsulation, and decapsulation outputs whenever the public KEM paths return an error. Add regression test coverage for RNG failure, invalid key validation, and allocation-failure cleanup so stale caller buffers cannot be mistaken for successful outputs. Verification: - pre-fix targeted tests reproduced uncleared output on RNG and invalid-key paths - make run_rng_fail - make run_func - make run_alloc Co-authored-by: Codex <codex@openai.com> Signed-off-by: Joe Doyle <joseph.doyle@trailofbits.com>
1 parent 22fda7f commit e53c19c

4 files changed

Lines changed: 169 additions & 0 deletions

File tree

mlkem/src/kem.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,12 @@ int mlk_kem_keypair(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES],
276276
ret = mlk_kem_keypair_derand(pk, sk, coins, context);
277277

278278
cleanup:
279+
if (ret != 0)
280+
{
281+
mlk_zeroize(pk, MLKEM_INDCCA_PUBLICKEYBYTES);
282+
mlk_zeroize(sk, MLKEM_INDCCA_SECRETKEYBYTES);
283+
}
284+
279285
/* Specification: Partially implements
280286
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
281287
MLK_FREE(coins, uint8_t, 2 * MLKEM_SYMBYTES, context);
@@ -326,6 +332,12 @@ int mlk_kem_enc_derand(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
326332
mlk_memcpy(ss, kr, MLKEM_SYMBYTES);
327333

328334
cleanup:
335+
if (ret != 0)
336+
{
337+
mlk_zeroize(ct, MLKEM_INDCCA_CIPHERTEXTBYTES);
338+
mlk_zeroize(ss, MLKEM_SSBYTES);
339+
}
340+
329341
/* Specification: Partially implements
330342
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
331343
MLK_FREE(kr, uint8_t, 2 * MLKEM_SYMBYTES, context);
@@ -362,6 +374,12 @@ int mlk_kem_enc(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES],
362374
ret = mlk_kem_enc_derand(ct, ss, pk, coins, context);
363375

364376
cleanup:
377+
if (ret != 0)
378+
{
379+
mlk_zeroize(ct, MLKEM_INDCCA_CIPHERTEXTBYTES);
380+
mlk_zeroize(ss, MLKEM_SSBYTES);
381+
}
382+
365383
/* Specification: Partially implements
366384
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
367385
MLK_FREE(coins, uint8_t, MLKEM_SYMBYTES, context);
@@ -431,6 +449,11 @@ int mlk_kem_dec(uint8_t ss[MLKEM_SSBYTES],
431449
mlk_ct_cmov_zero(ss, kr, MLKEM_SYMBYTES, fail);
432450

433451
cleanup:
452+
if (ret != 0)
453+
{
454+
mlk_zeroize(ss, MLKEM_SSBYTES);
455+
}
456+
434457
/* Specification: Partially implements
435458
* @[FIPS203, Section 3.3, Destruction of intermediate values] */
436459
MLK_FREE(tmp, uint8_t, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES,

test/src/test_alloc.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,20 @@ static void reset_all(test_ctx_t *ctx)
192192
ctx->fail_on_counter = -1;
193193
}
194194

195+
static int all_zero(const uint8_t *buf, size_t len)
196+
{
197+
size_t i;
198+
for (i = 0; i < len; i++)
199+
{
200+
if (buf[i] != 0)
201+
{
202+
return 0;
203+
}
204+
}
205+
206+
return 1;
207+
}
208+
195209
void *custom_alloc(test_ctx_t *ctx, size_t sz, const char *file, int line,
196210
const char *var, const char *type)
197211
{
@@ -329,13 +343,64 @@ void custom_free(test_ctx_t *ctx, void *p, size_t sz, const char *file,
329343
} \
330344
} while (0)
331345

346+
#define TEST_ALLOC_FAILURE_CLEARS_OUTPUTS(test_name, setup_outputs, call, \
347+
outputs_are_clear) \
348+
do \
349+
{ \
350+
int num_allocs, i, rc; \
351+
reset_all(ctx); \
352+
rc = call; \
353+
if (rc != 0) \
354+
{ \
355+
fprintf(stderr, "ERROR: %s failed with %d in cleanup counting pass\n", \
356+
test_name, rc); \
357+
return 1; \
358+
} \
359+
num_allocs = ctx->alloc_counter; \
360+
for (i = 0; i < num_allocs; i++) \
361+
{ \
362+
reset_all(ctx); \
363+
setup_outputs; \
364+
ctx->fail_on_counter = i; \
365+
rc = call; \
366+
if (rc != MLK_ERR_OUT_OF_MEMORY) \
367+
{ \
368+
fprintf(stderr, \
369+
"ERROR: %s returned %d instead of %d when allocation %d/%d " \
370+
"was instrumented to fail\n", \
371+
test_name, rc, MLK_ERR_OUT_OF_MEMORY, i + 1, num_allocs); \
372+
return 1; \
373+
} \
374+
if (!(outputs_are_clear)) \
375+
{ \
376+
fprintf(stderr, \
377+
"ERROR: %s left stale caller output after allocation %d/%d " \
378+
"failed\n", \
379+
test_name, i + 1, num_allocs); \
380+
return 1; \
381+
} \
382+
} \
383+
printf( \
384+
"Allocation output cleanup test for %s PASSED.\n" \
385+
" Checked %d allocation failure point(s)\n", \
386+
test_name, num_allocs); \
387+
} while (0)
388+
332389
static int test_keygen_alloc_failure(test_ctx_t *ctx)
333390
{
334391
uint8_t pk[CRYPTO_PUBLICKEYBYTES];
335392
uint8_t sk[CRYPTO_SECRETKEYBYTES];
336393

337394
TEST_ALLOC_FAILURE("crypto_kem_keypair", crypto_kem_keypair(pk, sk, ctx),
338395
MLK_TOTAL_ALLOC_KEYPAIR, &ctx->global_high_mark_keypair);
396+
TEST_ALLOC_FAILURE_CLEARS_OUTPUTS(
397+
"crypto_kem_keypair",
398+
{
399+
memset(pk, 0xA5, sizeof(pk));
400+
memset(sk, 0x5A, sizeof(sk));
401+
},
402+
crypto_kem_keypair(pk, sk, ctx),
403+
all_zero(pk, sizeof(pk)) && all_zero(sk, sizeof(sk)));
339404
return 0;
340405
}
341406

@@ -356,6 +421,14 @@ static int test_enc_alloc_failure(test_ctx_t *ctx)
356421

357422
TEST_ALLOC_FAILURE("crypto_kem_enc", crypto_kem_enc(ct, key, pk, ctx),
358423
MLK_TOTAL_ALLOC_ENCAPS, &ctx->global_high_mark_encaps);
424+
TEST_ALLOC_FAILURE_CLEARS_OUTPUTS(
425+
"crypto_kem_enc",
426+
{
427+
memset(ct, 0xA5, sizeof(ct));
428+
memset(key, 0x5A, sizeof(key));
429+
},
430+
crypto_kem_enc(ct, key, pk, ctx),
431+
all_zero(ct, sizeof(ct)) && all_zero(key, sizeof(key)));
359432
return 0;
360433
}
361434

@@ -383,6 +456,9 @@ static int test_dec_alloc_failure(test_ctx_t *ctx)
383456

384457
TEST_ALLOC_FAILURE("crypto_kem_dec", crypto_kem_dec(key_dec, ct, sk, ctx),
385458
MLK_TOTAL_ALLOC_DECAPS, &ctx->global_high_mark_decaps);
459+
TEST_ALLOC_FAILURE_CLEARS_OUTPUTS(
460+
"crypto_kem_dec", memset(key_dec, 0xA5, sizeof(key_dec)),
461+
crypto_kem_dec(key_dec, ct, sk, ctx), all_zero(key_dec, sizeof(key_dec)));
386462
return 0;
387463
}
388464

test/src/test_mlkem.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ static int test_keys_unaligned(void)
6868
return test_keys_core(pk + 1, sk + 1, ct + 1, key_a + 1, key_b + 1);
6969
}
7070

71+
static int all_zero(const uint8_t *buf, size_t len)
72+
{
73+
size_t i;
74+
for (i = 0; i < len; i++)
75+
{
76+
if (buf[i] != 0)
77+
{
78+
return 0;
79+
}
80+
}
81+
82+
return 1;
83+
}
84+
7185
static int test_invalid_pk(void)
7286
{
7387
uint8_t pk[CRYPTO_PUBLICKEYBYTES];
@@ -82,7 +96,11 @@ static int test_invalid_pk(void)
8296
pk[0] = 0xFF;
8397
pk[1] |= 0x0F;
8498
/* Bob derives a secret key and creates a response */
99+
memset(ct, 0xA5, sizeof(ct));
100+
memset(key_b, 0x5A, sizeof(key_b));
85101
CHECK(crypto_kem_enc(ct, key_b, pk) != 0);
102+
CHECK(all_zero(ct, sizeof(ct)));
103+
CHECK(all_zero(key_b, sizeof(key_b)));
86104
return 0;
87105
}
88106

@@ -125,7 +143,9 @@ static int test_invalid_sk_b(void)
125143
CHECK(randombytes(sk + CRYPTO_SECRETKEYBYTES - 64, 32) == 0);
126144
/* Alice uses Bobs response to get her shared key
127145
* This should fail due to the input validation */
146+
memset(key_a, 0xA5, sizeof(key_a));
128147
CHECK(crypto_kem_dec(key_a, ct, sk) != 0);
148+
CHECK(all_zero(key_a, sizeof(key_a)));
129149
return 0;
130150
}
131151

test/src/test_rng_fail.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
#include <stddef.h>
66
#include <stdio.h>
7+
#include <string.h>
78

89
/* Expose internal functions */
910
#define MLK_BUILD_INTERNAL
@@ -109,12 +110,44 @@ int randombytes(uint8_t *buf, size_t len)
109110
test_name, num_randombytes_calls); \
110111
} while (0)
111112

113+
static int all_zero(const uint8_t *buf, size_t len)
114+
{
115+
size_t i;
116+
for (i = 0; i < len; i++)
117+
{
118+
if (buf[i] != 0)
119+
{
120+
return 0;
121+
}
122+
}
123+
124+
return 1;
125+
}
126+
112127
static int test_keygen_rng_failure(void)
113128
{
114129
uint8_t pk[CRYPTO_PUBLICKEYBYTES];
115130
uint8_t sk[CRYPTO_SECRETKEYBYTES];
116131

117132
TEST_RNG_FAILURE("crypto_kem_keypair", crypto_kem_keypair(pk, sk));
133+
134+
memset(pk, 0xA5, sizeof(pk));
135+
memset(sk, 0x5A, sizeof(sk));
136+
reset_all();
137+
randombytes_fail_on_counter = 0;
138+
if (crypto_kem_keypair(pk, sk) != MLK_ERR_RNG_FAIL)
139+
{
140+
fprintf(stderr, "ERROR: crypto_kem_keypair did not fail on RNG failure\n");
141+
return 1;
142+
}
143+
if (!all_zero(pk, sizeof(pk)) || !all_zero(sk, sizeof(sk)))
144+
{
145+
fprintf(stderr,
146+
"ERROR: crypto_kem_keypair did not clear outputs on RNG "
147+
"failure\n");
148+
return 1;
149+
}
150+
118151
return 0;
119152
}
120153

@@ -134,6 +167,23 @@ static int test_enc_rng_failure(void)
134167
}
135168

136169
TEST_RNG_FAILURE("crypto_kem_enc", crypto_kem_enc(ct, ss, pk));
170+
171+
memset(ct, 0xA5, sizeof(ct));
172+
memset(ss, 0x5A, sizeof(ss));
173+
reset_all();
174+
randombytes_fail_on_counter = 0;
175+
if (crypto_kem_enc(ct, ss, pk) != MLK_ERR_RNG_FAIL)
176+
{
177+
fprintf(stderr, "ERROR: crypto_kem_enc did not fail on RNG failure\n");
178+
return 1;
179+
}
180+
if (!all_zero(ct, sizeof(ct)) || !all_zero(ss, sizeof(ss)))
181+
{
182+
fprintf(stderr,
183+
"ERROR: crypto_kem_enc did not clear outputs on RNG failure\n");
184+
return 1;
185+
}
186+
137187
return 0;
138188
}
139189

0 commit comments

Comments
 (0)