Skip to content

Commit ce87d46

Browse files
committed
Debug/CBMC: Interpret debug-assertions as proof obligations
Previously, runtime assertions via debug_assert_xxx and CBMC assertions via cassert(...) were separate. This commit modifies the implementation of the debug assertion macros so that when CBMC is used, debug assertions are intepreted as proof obligations. This removes some redundancy and non-uniformity in the code, and also reduces the likelihood that debug assertions and CBMC contracts get out of sync. In some case, this actually happened, and the commit fixes this. The commit also adds further bounds assertions in alignment with pre/post conditions. A slight nuisance is that the debug assertions cannot flatten nested structures like polyvec for the bounds check, running into issue diffblue/cbmc#8570. We work around this by introducing a new `xxx_2d` (for 2-dimensional) macro which takes two dimensions and uses a two-step array access, circumventing the above CBMC issue. Signed-off-by: Hanno Becker <[email protected]>
1 parent 1ea42f6 commit ce87d46

File tree

12 files changed

+160
-48
lines changed

12 files changed

+160
-48
lines changed

Diff for: examples/monolithic_build/mlkem_native_monobuild.c

+45
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@
373373
#undef debug_assert
374374
#endif
375375

376+
/* mlkem/debug/debug.h */
377+
#if defined(debug_assert)
378+
#undef debug_assert
379+
#endif
380+
376381
/* mlkem/debug/debug.h */
377382
#if defined(debug_assert_abs_bound)
378383
#undef debug_assert_abs_bound
@@ -383,6 +388,31 @@
383388
#undef debug_assert_abs_bound
384389
#endif
385390

391+
/* mlkem/debug/debug.h */
392+
#if defined(debug_assert_abs_bound)
393+
#undef debug_assert_abs_bound
394+
#endif
395+
396+
/* mlkem/debug/debug.h */
397+
#if defined(debug_assert_abs_bound_2d)
398+
#undef debug_assert_abs_bound_2d
399+
#endif
400+
401+
/* mlkem/debug/debug.h */
402+
#if defined(debug_assert_abs_bound_2d)
403+
#undef debug_assert_abs_bound_2d
404+
#endif
405+
406+
/* mlkem/debug/debug.h */
407+
#if defined(debug_assert_abs_bound_2d)
408+
#undef debug_assert_abs_bound_2d
409+
#endif
410+
411+
/* mlkem/debug/debug.h */
412+
#if defined(debug_assert_bound)
413+
#undef debug_assert_bound
414+
#endif
415+
386416
/* mlkem/debug/debug.h */
387417
#if defined(debug_assert_bound)
388418
#undef debug_assert_bound
@@ -393,6 +423,21 @@
393423
#undef debug_assert_bound
394424
#endif
395425

426+
/* mlkem/debug/debug.h */
427+
#if defined(debug_assert_bound_2d)
428+
#undef debug_assert_bound_2d
429+
#endif
430+
431+
/* mlkem/debug/debug.h */
432+
#if defined(debug_assert_bound_2d)
433+
#undef debug_assert_bound_2d
434+
#endif
435+
436+
/* mlkem/debug/debug.h */
437+
#if defined(debug_assert_bound_2d)
438+
#undef debug_assert_bound_2d
439+
#endif
440+
396441
/* mlkem/debug/debug.h */
397442
#if defined(mlkem_debug_assert)
398443
#undef mlkem_debug_assert

Diff for: mlkem/cbmc.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,13 @@
119119
{ \
120120
unsigned qvar; \
121121
((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) ==> \
122-
(((value_lb) <= (array_var[(qvar)])) && \
123-
((array_var[(qvar)]) < (value_ub))) \
122+
(((value_lb) <= ((array_var)[(qvar)])) && \
123+
(((array_var)[(qvar)]) < (value_ub))) \
124124
}
125125

126126
#define array_bound(array_var, qvar_lb, qvar_ub, value_lb, value_ub) \
127127
array_bound_core(CBMC_CONCAT(_cbmc_idx, __LINE__), (qvar_lb), \
128-
(qvar_ub), (array_var), (value_lb), (value_ub))
128+
(qvar_ub), (array_var), (value_lb), (value_ub))
129129
/* clang-format on */
130130

131131
/* Wrapper around array_bound operating on absolute values.

Diff for: mlkem/debug/debug.h

+47-11
Original file line numberDiff line numberDiff line change
@@ -50,24 +50,17 @@ void mlkem_debug_check_bounds(const char *file, int line, const int16_t *ptr,
5050
*
5151
* val: Value that's asserted to be non-zero
5252
*/
53-
#define debug_assert(val) \
54-
do \
55-
{ \
56-
mlkem_debug_assert(__FILE__, __LINE__, (val)); \
57-
} while (0)
53+
#define debug_assert(val) mlkem_debug_assert(__FILE__, __LINE__, (val))
5854

5955
/* Check bounds in array of int16_t's
6056
* ptr: Base of int16_t array; will be explicitly cast to int16_t*,
6157
* so you may pass a byte-compatible type such as poly or polyvec.
6258
* len: Number of int16_t in array
6359
* value_lb: Inclusive lower value bound
6460
* value_ub: Exclusive upper value bound */
65-
#define debug_assert_bound(ptr, len, value_lb, value_ub) \
66-
do \
67-
{ \
68-
mlkem_debug_check_bounds(__FILE__, __LINE__, (const int16_t *)(ptr), \
69-
(len), (value_lb)-1, (value_ub)); \
70-
} while (0)
61+
#define debug_assert_bound(ptr, len, value_lb, value_ub) \
62+
mlkem_debug_check_bounds(__FILE__, __LINE__, (const int16_t *)(ptr), (len), \
63+
(value_lb)-1, (value_ub))
7164

7265
/* Check absolute bounds in array of int16_t's
7366
* ptr: Base of array, expression of type int16_t*
@@ -76,6 +69,38 @@ void mlkem_debug_check_bounds(const char *file, int line, const int16_t *ptr,
7669
#define debug_assert_abs_bound(ptr, len, value_abs_bd) \
7770
debug_assert_bound((ptr), (len), (-(value_abs_bd) + 1), (value_abs_bd))
7871

72+
/* Version of bounds assertions for 2-dimensional arrays */
73+
#define debug_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \
74+
debug_assert_bound((ptr), ((len0) * (len1)), (value_lb), (value_ub))
75+
76+
#define debug_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \
77+
debug_assert_abs_bound((ptr), ((len0) * (len1)), (value_abs_bd))
78+
79+
/* When running CBMC, convert debug assertions into proof obligations */
80+
#elif defined(CBMC)
81+
82+
#include "../cbmc.h"
83+
84+
#define debug_assert(val) cassert(val)
85+
86+
#define debug_assert_bound(ptr, len, value_lb, value_ub) \
87+
cassert(array_bound(((int16_t *)(ptr)), 0, (len), (value_lb), (value_ub)))
88+
89+
#define debug_assert_abs_bound(ptr, len, value_abs_bd) \
90+
cassert(array_abs_bound(((int16_t *)(ptr)), 0, (len), (value_abs_bd)))
91+
92+
/* Because of https://github.com/diffblue/cbmc/issues/8570, we can't
93+
* just use a single flattened array_bound(...) here. */
94+
#define debug_assert_bound_2d(ptr, N, M, value_lb, value_ub) \
95+
cassert(forall(kN, 0, (M), \
96+
array_bound(&((int16_t(*)[(N)])(ptr))[kN][0], 0, (N), \
97+
(value_lb), (value_ub))))
98+
99+
#define debug_assert_abs_bound_2d(ptr, N, M, value_abs_bd) \
100+
cassert(forall(kN, 0, (M), \
101+
array_abs_bound(&((int16_t(*)[(N)])(ptr))[kN][0], 0, (N), \
102+
(value_abs_bd))))
103+
79104
#else /* MLKEM_DEBUG */
80105

81106
#define debug_assert(val) \
@@ -91,6 +116,17 @@ void mlkem_debug_check_bounds(const char *file, int line, const int16_t *ptr,
91116
{ \
92117
} while (0)
93118

119+
#define debug_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \
120+
do \
121+
{ \
122+
} while (0)
123+
124+
#define debug_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \
125+
do \
126+
{ \
127+
} while (0)
128+
129+
94130
#endif /* MLKEM_DEBUG */
95131

96132
#endif /* MLKEM_DEBUG_H */

Diff for: mlkem/indcpa.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
static void pack_pk(uint8_t r[MLKEM_INDCPA_PUBLICKEYBYTES], polyvec *pk,
5252
const uint8_t seed[MLKEM_SYMBYTES])
5353
{
54-
debug_assert_abs_bound(pk, MLKEM_K * MLKEM_N, MLKEM_Q);
54+
debug_assert_bound_2d(pk, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
5555
polyvec_tobytes(r, pk);
5656
memcpy(r + MLKEM_POLYVECBYTES, seed, MLKEM_SYMBYTES);
5757
}
@@ -91,7 +91,7 @@ static void unpack_pk(polyvec *pk, uint8_t seed[MLKEM_SYMBYTES],
9191
**************************************************/
9292
static void pack_sk(uint8_t r[MLKEM_INDCPA_SECRETKEYBYTES], polyvec *sk)
9393
{
94-
debug_assert_abs_bound(sk, MLKEM_K * MLKEM_N, MLKEM_Q);
94+
debug_assert_bound_2d(sk, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
9595
polyvec_tobytes(r, sk);
9696
}
9797

@@ -354,7 +354,7 @@ void gen_matrix(polyvec *a, const uint8_t seed[MLKEM_SYMBYTES], int transposed)
354354
i++;
355355
}
356356

357-
cassert(i == MLKEM_K * MLKEM_K);
357+
debug_assert(i == MLKEM_K * MLKEM_K);
358358

359359
/*
360360
* The public matrix is generated in NTT domain. If the native backend

Diff for: mlkem/poly.c

+15-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ MLKEM_NATIVE_INTERNAL_API
2020
void poly_compress_du(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_DU], const poly *a)
2121
{
2222
unsigned j;
23+
debug_assert_bound(a, MLKEM_N, 0, MLKEM_Q);
24+
2325
#if (MLKEM_POLYCOMPRESSEDBYTES_DU == 352)
2426
for (j = 0; j < MLKEM_N / 8; j++)
2527
__loop__(invariant(j >= 0 && j <= MLKEM_N / 8))
@@ -139,6 +141,8 @@ void poly_decompress_du(poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_DU])
139141
#else
140142
#error "MLKEM_POLYCOMPRESSEDBYTES_DU needs to be in {320,352}"
141143
#endif
144+
145+
debug_assert_bound(r, MLKEM_N, 0, MLKEM_Q);
142146
}
143147

144148
MLKEM_NATIVE_INTERNAL_API
@@ -260,7 +264,6 @@ void poly_tobytes(uint8_t r[MLKEM_POLYBYTES], const poly *a)
260264
unsigned i;
261265
debug_assert_bound(a, MLKEM_N, 0, MLKEM_Q);
262266

263-
264267
for (i = 0; i < MLKEM_N / 2; i++)
265268
__loop__(invariant(i >= 0 && i <= MLKEM_N / 2))
266269
{
@@ -489,7 +492,7 @@ void poly_tomont(poly *r)
489492
for (i = 0; i < MLKEM_N; i++)
490493
__loop__(
491494
invariant(i >= 0 && i <= MLKEM_N)
492-
invariant(array_abs_bound(r->coeffs ,0, i, MLKEM_Q)))
495+
invariant(array_abs_bound(r->coeffs, 0, i, MLKEM_Q)))
493496
{
494497
r->coeffs[i] = fqmul(r->coeffs[i], f);
495498
}
@@ -566,11 +569,20 @@ void poly_mulcache_compute(poly_mulcache *x, const poly *a)
566569
{
567570
unsigned i;
568571
for (i = 0; i < MLKEM_N / 4; i++)
569-
__loop__(invariant(i >= 0 && i <= MLKEM_N / 4))
572+
__loop__(
573+
invariant(i >= 0 && i <= MLKEM_N / 4)
574+
invariant(array_abs_bound(x->coeffs, 0, 2 * i, MLKEM_Q)))
570575
{
571576
x->coeffs[2 * i + 0] = fqmul(a->coeffs[4 * i + 1], zetas[64 + i]);
572577
x->coeffs[2 * i + 1] = fqmul(a->coeffs[4 * i + 3], -zetas[64 + i]);
573578
}
579+
580+
/*
581+
* This bound is true for the C implementation, but not needed
582+
* in the higher level bounds reasoning. It is thus omitted
583+
* them from the spec to not unnecessarily constrain native
584+
* implementations, but checked here nonetheless.
585+
*/
574586
debug_assert_abs_bound(x, MLKEM_N / 2, MLKEM_Q);
575587
}
576588
#else /* MLKEM_USE_NATIVE_POLY_MULCACHE_COMPUTE */

Diff for: mlkem/poly.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,17 @@ __contract__(
307307
************************************************************/
308308
static INLINE uint16_t scalar_signed_to_unsigned_q(int16_t c)
309309
__contract__(
310-
requires(c >= -(MLKEM_Q - 1) && c <= (MLKEM_Q - 1))
311-
ensures(return_value >= 0 && return_value <= (MLKEM_Q - 1))
310+
requires(c > -MLKEM_Q && c < MLKEM_Q)
311+
ensures(return_value >= 0 && return_value < MLKEM_Q)
312312
ensures(return_value == (int32_t)c + (((int32_t)c < 0) * MLKEM_Q)))
313313
{
314+
debug_assert_abs_bound(&c, 1, MLKEM_Q);
315+
314316
/* Add Q if c is negative, but in constant time */
315317
c = ct_sel_int16(c + MLKEM_Q, c, ct_cmask_neg_i16(c));
316318

317-
cassert(c >= 0);
318-
cassert(c < MLKEM_Q);
319-
320319
/* and therefore cast to uint16_t is safe. */
320+
debug_assert_bound(&c, 1, 0, MLKEM_Q);
321321
return (uint16_t)c;
322322
}
323323

Diff for: mlkem/polyvec.c

+20-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ void polyvec_compress_du(uint8_t r[MLKEM_POLYVECCOMPRESSEDBYTES_DU],
1515
const polyvec *a)
1616
{
1717
unsigned i;
18-
debug_assert_bound(a, MLKEM_K * MLKEM_N, 0, MLKEM_Q);
18+
debug_assert_bound_2d(a, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
1919

2020
for (i = 0; i < MLKEM_K; i++)
2121
{
@@ -33,13 +33,15 @@ void polyvec_decompress_du(polyvec *r,
3333
poly_decompress_du(&r->vec[i], a + i * MLKEM_POLYCOMPRESSEDBYTES_DU);
3434
}
3535

36-
debug_assert_bound(r, MLKEM_K * MLKEM_N, 0, MLKEM_Q);
36+
debug_assert_bound_2d(r, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
3737
}
3838

3939
MLKEM_NATIVE_INTERNAL_API
4040
void polyvec_tobytes(uint8_t r[MLKEM_POLYVECBYTES], const polyvec *a)
4141
{
4242
unsigned i;
43+
debug_assert_bound_2d(a, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
44+
4345
for (i = 0; i < MLKEM_K; i++)
4446
{
4547
poly_tobytes(r + i * MLKEM_POLYBYTES, &a->vec[i]);
@@ -54,6 +56,8 @@ void polyvec_frombytes(polyvec *r, const uint8_t a[MLKEM_POLYVECBYTES])
5456
{
5557
poly_frombytes(&r->vec[i], a + i * MLKEM_POLYBYTES);
5658
}
59+
60+
debug_assert_bound_2d(r, MLKEM_N, MLKEM_K, 0, UINT12_LIMIT);
5761
}
5862

5963
MLKEM_NATIVE_INTERNAL_API
@@ -64,6 +68,8 @@ void polyvec_ntt(polyvec *r)
6468
{
6569
poly_ntt(&r->vec[i]);
6670
}
71+
72+
debug_assert_abs_bound_2d(r, MLKEM_N, MLKEM_K, NTT_BOUND);
6773
}
6874

6975
MLKEM_NATIVE_INTERNAL_API
@@ -74,6 +80,8 @@ void polyvec_invntt_tomont(polyvec *r)
7480
{
7581
poly_invntt_tomont(&r->vec[i]);
7682
}
83+
84+
debug_assert_abs_bound_2d(r, MLKEM_N, MLKEM_K, INVNTT_BOUND);
7785
}
7886

7987
#if !defined(MLKEM_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED)
@@ -84,27 +92,22 @@ void polyvec_basemul_acc_montgomery_cached(poly *r, const polyvec *a,
8492
{
8593
unsigned i;
8694
poly t;
87-
88-
debug_assert_bound(a, MLKEM_K * MLKEM_N, 0, UINT12_LIMIT);
89-
debug_assert_abs_bound(b, MLKEM_K * MLKEM_N, NTT_BOUND);
90-
debug_assert_abs_bound(b_cache, MLKEM_K * (MLKEM_N / 2), MLKEM_Q);
95+
debug_assert_bound_2d(a, MLKEM_N, MLKEM_K, 0, UINT12_LIMIT);
9196

9297
poly_basemul_montgomery_cached(r, &a->vec[0], &b->vec[0], &b_cache->vec[0]);
9398
for (i = 1; i < MLKEM_K; i++)
9499
{
95100
poly_basemul_montgomery_cached(&t, &a->vec[i], &b->vec[i],
96101
&b_cache->vec[i]);
97102
poly_add(r, &t);
98-
/* abs bounds: < (i+1) * 3/2 * q */
99103
}
100104

101105
/*
102-
* Those bounds are true for the C implementation, but not needed
103-
* in the higher level bounds reasoning. It is thus best to omit
104-
* them from the spec to not unnecessarily constraint native implementations.
106+
* This bound is true for the C implementation, but not needed
107+
* in the higher level bounds reasoning. It is thus omitted
108+
* them from the spec to not unnecessarily constrain native
109+
* implementations, but checked here nonetheless.
105110
*/
106-
cassert(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_K * 2 * MLKEM_Q));
107-
/* TODO: Integrate CBMC assertion into debug_asserts if CBMC is set */
108111
debug_assert_abs_bound(r, MLKEM_N, MLKEM_K * 2 * MLKEM_Q);
109112
}
110113
#else /* !MLKEM_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED */
@@ -113,8 +116,7 @@ void polyvec_basemul_acc_montgomery_cached(poly *r, const polyvec *a,
113116
const polyvec *b,
114117
const polyvec_mulcache *b_cache)
115118
{
116-
debug_assert_bound(a, MLKEM_K * MLKEM_N, 0, UINT12_LIMIT);
117-
debug_assert_abs_bound(b, MLKEM_K * MLKEM_N, NTT_BOUND);
119+
debug_assert_bound_2d(a, MLKEM_N, MLKEM_K, 0, UINT12_LIMIT);
118120
/* Omitting bounds assertion for cache since native implementations may
119121
* decide not to use a mulcache. Note that the C backend implementation
120122
* of poly_basemul_montgomery_cached() does still include the check. */
@@ -148,6 +150,8 @@ void polyvec_reduce(polyvec *r)
148150
{
149151
poly_reduce(&r->vec[i]);
150152
}
153+
154+
debug_assert_bound_2d(r, MLKEM_N, MLKEM_K, 0, MLKEM_Q);
151155
}
152156

153157
MLKEM_NATIVE_INTERNAL_API
@@ -168,4 +172,6 @@ void polyvec_tomont(polyvec *r)
168172
{
169173
poly_tomont(&r->vec[i]);
170174
}
175+
176+
debug_assert_abs_bound_2d(r, MLKEM_N, MLKEM_K, MLKEM_Q);
171177
}

0 commit comments

Comments
 (0)