Skip to content

Commit 6369a7e

Browse files
authored
Merge pull request #371 from pq-code-package/constant-time
Constant-time hardening for `caddq`, `poly_chknorm`, `decompose`
2 parents 334c7a2 + 2da7a4f commit 6369a7e

File tree

27 files changed

+956
-11
lines changed

27 files changed

+956
-11
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,42 @@ jobs:
353353
acvp: false
354354
examples: false
355355
stack: true
356-
356+
config_variations:
357+
name: Non-standard configurations
358+
strategy:
359+
fail-fast: false
360+
matrix:
361+
external:
362+
- ${{ github.repository_owner != 'pq-code-package' }}
363+
target:
364+
- runner: pqcp-arm64
365+
name: 'ubuntu-latest (aarch64)'
366+
- runner: pqcp-x64
367+
name: 'ubuntu-latest (x86_64)'
368+
exclude:
369+
- {external: true,
370+
target: {
371+
runner: pqcp-arm64,
372+
name: 'ubuntu-latest (aarch64)',
373+
}}
374+
- {external: true,
375+
target: {
376+
runner: pqcp-x64,
377+
name: 'ubuntu-latest (x86_64)',
378+
}}
379+
runs-on: ${{ matrix.target.runner }}
380+
steps:
381+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
382+
- name: "No ASM"
383+
uses: ./.github/actions/multi-functest
384+
with:
385+
gh_token: ${{ secrets.GITHUB_TOKEN }}
386+
compile_mode: native
387+
cflags: "-std=c11 -D_GNU_SOURCE -DMLD_CONFIG_FILE=\\\\\\\"../test/no_asm_config.h\\\\\\\" -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
388+
func: true
389+
kat: true
390+
acvp: true
391+
examples: false # Some examples use a custom config themselves
357392
check_autogenerated_files:
358393
strategy:
359394
fail-fast: false

mldsa/common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
#ifndef MLD_COMMON_H
77
#define MLD_COMMON_H
88

9+
#if defined(MLD_CONFIG_FILE)
10+
#include MLD_CONFIG_FILE
11+
#else
12+
#include "config.h"
13+
#endif
14+
915
#include "cbmc.h"
1016
#include "params.h"
1117
#include "sys.h"

mldsa/config.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@
2222
#define MLD_NAMESPACE(s) MLD_87_ref_##s
2323
#endif
2424

25+
26+
/******************************************************************************
27+
* Name: MLD_CONFIG_FILE
28+
*
29+
* Description: If defined, this is a header that will be included instead
30+
* of this default configuration file mldsa/config.h.
31+
*
32+
* When you need to build mldsa-native in multiple configurations,
33+
* using varying MLD_CONFIG_FILE can be more convenient
34+
* then configuring everything through CFLAGS.
35+
*
36+
* To use, MLD_CONFIG_FILE _must_ be defined prior
37+
* to the inclusion of any mldsa-native headers. For example,
38+
* it can be set by passing `-DMLD_CONFIG_FILE="..."`
39+
* on the command line.
40+
*
41+
*****************************************************************************/
42+
/* #define MLD_CONFIG_FILE "config.h" */
43+
2544
/******************************************************************************
2645
* Name: MLD_CONFIG_ARITH_BACKEND_FILE
2746
*
@@ -137,4 +156,43 @@
137156
#endif
138157
*/
139158

159+
/******************************************************************************
160+
* Name: MLD_CONFIG_NO_ASM
161+
*
162+
* Description: If this option is set, mldsa-native will be built without
163+
* use of native code or inline assembly.
164+
*
165+
* By default, inline assembly is used to implement value barriers.
166+
* Without inline assembly, mldsa-native will use a global volatile
167+
* 'opt blocker' instead; see ct.h.
168+
*
169+
* Inline assembly is also used to implement a secure zeroization
170+
* function on non-Windows platforms. If this option is set and
171+
* the target platform is not Windows, you MUST set
172+
* MLD_CONFIG_CUSTOM_ZEROIZE and provide a custom zeroization
173+
* function.
174+
*
175+
* If this option is set, MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 and
176+
* and MLD_CONFIG_USE_NATIVE_BACKEND_ARITH will be ignored, and no
177+
* native backends will be used.
178+
*
179+
*****************************************************************************/
180+
/* #define MLD_CONFIG_NO_ASM */
181+
182+
/******************************************************************************
183+
* Name: MLD_CONFIG_NO_ASM_VALUE_BARRIER
184+
*
185+
* Description: If this option is set, mldsa-native will be built without
186+
* use of native code or inline assembly for value barriers.
187+
*
188+
* By default, inline assembly (if available) is used to implement
189+
* value barriers.
190+
* Without inline assembly, mldsa-native will use a global volatile
191+
* 'opt blocker' instead; see ct.h.
192+
*
193+
*****************************************************************************/
194+
/* #define MLD_CONFIG_NO_ASM_VALUE_BARRIER */
195+
196+
197+
140198
#endif /* !MLD_CONFIG_H */

mldsa/ct.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) The mlkem-native project authors
3+
* Copyright (c) The mldsa-native project authors
4+
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
5+
*/
6+
#include "ct.h"
7+
8+
#if !defined(MLD_USE_ASM_VALUE_BARRIER) && \
9+
!defined(MLD_CONFIG_MULTILEVEL_NO_SHARED)
10+
/*
11+
* Masking value used in constant-time functions from
12+
* ct.h to block the compiler's range analysis and
13+
* thereby reduce the risk of compiler-introduced branches.
14+
*/
15+
volatile uint64_t mld_ct_opt_blocker_u64 = 0;
16+
17+
#else /* !MLD_USE_ASM_VALUE_BARRIER && !MLD_CONFIG_MULTILEVEL_NO_SHARED */
18+
19+
MLD_EMPTY_CU(ct)
20+
21+
#endif /* !(!MLD_USE_ASM_VALUE_BARRIER && !MLD_CONFIG_MULTILEVEL_NO_SHARED) */

mldsa/ct.h

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (c) The mlkem-native project authors
3+
* Copyright (c) The mldsa-native project authors
4+
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
5+
*/
6+
7+
#ifndef MLD_CT_H
8+
#define MLD_CT_H
9+
10+
/* References
11+
* ==========
12+
*
13+
* - [libmceliece]
14+
* libmceliece implementation of Classic McEliece
15+
* Bernstein, Chou
16+
* https://lib.mceliece.org/
17+
*
18+
* - [optblocker]
19+
* PQC forum post on opt-blockers using volatile globals
20+
* Daniel J. Bernstein
21+
* https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/hqbtIGFKIpU/m/H14H0wOlBgAJ
22+
*/
23+
24+
#include <stdint.h>
25+
#include "cbmc.h"
26+
#include "common.h"
27+
28+
/* Constant-time comparisons and conditional operations
29+
30+
We reduce the risk for compilation into variable-time code
31+
through the use of 'value barriers'.
32+
33+
Functionally, a value barrier is a no-op. To the compiler, however,
34+
it constitutes an arbitrary modification of its input, and therefore
35+
harden's value propagation and range analysis.
36+
37+
We consider two approaches to implement a value barrier:
38+
- An empty inline asm block which marks the target value as clobbered.
39+
- XOR'ing with the value of a volatile global that's set to 0;
40+
see @[optblocker] for a discussion of this idea, and
41+
@[libmceliece, inttypes/crypto_intN.h] for an implementation.
42+
43+
The first approach is cheap because it only prevents the compiler
44+
from reasoning about the value of the variable past the barrier,
45+
but does not directly generate additional instructions.
46+
47+
The second approach generates redundant loads and XOR operations
48+
and therefore comes at a higher runtime cost. However, it appears
49+
more robust towards optimization, as compilers should never drop
50+
a volatile load.
51+
52+
We use the empty-ASM value barrier for GCC and clang, and fall
53+
back to the global volatile barrier otherwise.
54+
55+
The global value barrier can be forced by setting
56+
MLD_CONFIG_NO_ASM_VALUE_BARRIER.
57+
58+
*/
59+
60+
#if defined(MLD_HAVE_INLINE_ASM) && !defined(MLD_CONFIG_NO_ASM_VALUE_BARRIER)
61+
#define MLD_USE_ASM_VALUE_BARRIER
62+
#endif
63+
64+
65+
#if !defined(MLD_USE_ASM_VALUE_BARRIER)
66+
/*
67+
* Declaration of global volatile that the global value barrier
68+
* is loading from and masking with.
69+
*/
70+
#define mld_ct_opt_blocker_u64 MLD_NAMESPACE(ct_opt_blocker_u64)
71+
extern volatile uint64_t mld_ct_opt_blocker_u64;
72+
73+
74+
/* Helper functions for obtaining global masks of various sizes */
75+
76+
/* This contract is not proved but treated as an axiom.
77+
*
78+
* Its validity relies on the assumption that the global opt-blocker
79+
* constant mld_ct_opt_blocker_u64 is not modified.
80+
*/
81+
static MLD_INLINE uint64_t mld_ct_get_optblocker_u64(void)
82+
__contract__(ensures(return_value == 0)) { return mld_ct_opt_blocker_u64; }
83+
84+
static MLD_INLINE int64_t mld_ct_get_optblocker_i64(void)
85+
__contract__(ensures(return_value == 0)) { return (int64_t)mld_ct_get_optblocker_u64(); }
86+
87+
static MLD_INLINE uint32_t mld_ct_get_optblocker_u32(void)
88+
__contract__(ensures(return_value == 0)) { return (uint32_t)mld_ct_get_optblocker_u64(); }
89+
90+
/* Opt-blocker based implementation of value barriers */
91+
static MLD_INLINE int64_t mld_value_barrier_i64(int64_t b)
92+
__contract__(ensures(return_value == b)) { return (b ^ mld_ct_get_optblocker_i64()); }
93+
94+
static MLD_INLINE uint32_t mld_value_barrier_u32(uint32_t b)
95+
__contract__(ensures(return_value == b)) { return (b ^ mld_ct_get_optblocker_u32()); }
96+
97+
98+
#else /* !MLD_USE_ASM_VALUE_BARRIER */
99+
static MLD_INLINE int64_t mld_value_barrier_i64(int64_t b)
100+
__contract__(ensures(return_value == b))
101+
{
102+
__asm__("" : "+r"(b));
103+
return b;
104+
}
105+
106+
static MLD_INLINE uint32_t mld_value_barrier_u32(uint32_t b)
107+
__contract__(ensures(return_value == b))
108+
{
109+
__asm__("" : "+r"(b));
110+
return b;
111+
}
112+
#endif /* MLD_USE_ASM_VALUE_BARRIER */
113+
114+
115+
/*
116+
* The mld_ct_sel_int32 function below makes deliberate use of unsigned
117+
* to signed integer conversion, which is implementation-defined
118+
* behaviour. Here, we assume that uint16_t -> int16_t is inverse
119+
* to int16_t -> uint16_t.
120+
*/
121+
#ifdef CBMC
122+
#pragma CPROVER check push
123+
#pragma CPROVER check disable "conversion"
124+
#endif
125+
126+
/*************************************************
127+
* Name: mld_ct_sel_int32
128+
*
129+
* Description: Functionally equivalent to cond ? a : b,
130+
* but implemented with guards against
131+
* compiler-introduced branches.
132+
*
133+
* Arguments: int32_t a: First alternative
134+
* int32_t b: Second alternative
135+
* uint32_t cond: Condition variable.
136+
*
137+
*
138+
**************************************************/
139+
static MLD_INLINE int32_t mld_ct_sel_int32(int32_t a, int32_t b, uint32_t cond)
140+
/* TODO: proof */
141+
__contract__(
142+
requires(cond == 0x0 || cond == 0xFFFFFFFF)
143+
ensures(return_value == (cond ? a : b))
144+
)
145+
{
146+
uint32_t au = a, bu = b;
147+
uint32_t res = bu ^ (mld_value_barrier_u32(cond) & (au ^ bu));
148+
return (int32_t)res;
149+
}
150+
151+
/* Put unsigned-to-signed warnings in CBMC back into scope */
152+
#ifdef CBMC
153+
#pragma CPROVER check pop
154+
#endif
155+
156+
157+
158+
/*
159+
* The mld_ct_cmask_neg_i32 function below makes deliberate use of
160+
* signed to unsigned integer conversion, which is fully defined
161+
* behaviour in C. It is thus safe to disable this warning.
162+
*/
163+
#ifdef CBMC
164+
#pragma CPROVER check push
165+
#pragma CPROVER check disable "conversion"
166+
#endif
167+
168+
169+
/*************************************************
170+
* Name: mld_ct_cmask_neg_i32
171+
*
172+
* Description: Return 0 if input is non-negative, and -1 otherwise.
173+
*
174+
* Arguments: int32_t x: Value to be converted into a mask
175+
*
176+
**************************************************/
177+
/* TODO: proof */
178+
static MLD_INLINE uint32_t mld_ct_cmask_neg_i32(int32_t x)
179+
__contract__(ensures(return_value == ((x < 0) ? 0xFFFFFFFF : 0)))
180+
{
181+
int64_t tmp = mld_value_barrier_i64((int64_t)x);
182+
tmp >>= 31;
183+
return (uint32_t)tmp;
184+
}
185+
186+
/* Put signed-to-unsigned warnings in CBMC back into scope */
187+
#ifdef CBMC
188+
#pragma CPROVER check pop
189+
#endif
190+
191+
/*************************************************
192+
* Name: mld_ct_abs_i32
193+
*
194+
* Description: Return -x if x<0, x otherwise
195+
*
196+
* Arguments: int32_t x: Input value
197+
*
198+
**************************************************/
199+
/* TODO: proof */
200+
static MLD_INLINE uint32_t mld_ct_abs_i32(int32_t x)
201+
__contract__(
202+
requires(x >= -INT32_MAX)
203+
ensures(return_value == ((x < 0) ? -x : x))
204+
)
205+
{
206+
return mld_ct_sel_int32(-x, x, mld_ct_cmask_neg_i32(x));
207+
}
208+
209+
210+
#endif /* !MLD_CT_H */

mldsa/params.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#ifndef MLD_PARAMS_H
66
#define MLD_PARAMS_H
77

8-
#include "config.h"
8+
#include "common.h"
99

1010
#define MLDSA_SEEDBYTES 32
1111
#define MLDSA_CRHBYTES 64

mldsa/poly.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <stdint.h>
77
#include <string.h>
88

9+
#include "ct.h"
910
#include "debug.h"
1011
#include "fips202/fips202x4.h"
1112
#include "ntt.h"
@@ -253,8 +254,7 @@ int mld_poly_chknorm(const mld_poly *a, int32_t B)
253254
)
254255
{
255256
/* Absolute value */
256-
t = a->coeffs[i] >> 31;
257-
t = a->coeffs[i] - (t & 2 * a->coeffs[i]);
257+
t = mld_ct_abs_i32(a->coeffs[i]);
258258

259259
if (t >= B)
260260
{

0 commit comments

Comments
 (0)