Skip to content

Commit f29a327

Browse files
Merge #1169: Add support for msan instead of valgrind (for memcheck and ctime test)
0f088ec Rename CTIMETEST -> CTIMETESTS (Pieter Wuille) 74b026f Add runtime checking for DECLASSIFY flag (Pieter Wuille) 5e2e6fc Run ctime test in Linux MSan CI job (Pieter Wuille) 1897406 Make ctime tests building configurable (Pieter Wuille) 5048be1 Rename valgrind_ctime_test -> ctime_tests (Pieter Wuille) 6eed6c1 Update error messages to suggest msan as well (Pieter Wuille) 8e11f89 Add support for msan integration to checkmem.h (Pieter Wuille) 8dc6407 Add compile-time error to valgrind_ctime_test (Pieter Wuille) 0db05a7 Abstract interactions with valgrind behind new checkmem.h (Pieter Wuille) 4f1a54e Move valgrind CPPFLAGS into SECP_CONFIG_DEFINES (Pieter Wuille) Pull request description: This introduces an abstraction layer `src/checkmem.h`, which defines macros for interacting with memory checking tools. Depending on the environment, they're mapped to MemorySanitizer builtins, Valgrind integration macros, or nothing at all. This means that msan builds immediately benefit from existing undefined memory checks in the tests. It also means those builds result in a `ctime_tests` (new name for `valgrind_ctime_test`) binary that can usefully test constant-timeness (not inside Valgrind, and with the downside that it's not running against a production library build, but it's faster and available on more platforms). Such an msan-ctime test is added to the Linux x86_64 msan CI job, as an example. More CI cases could be added (e.g. for MacOs or ARM Linux) later. ACKs for top commit: real-or-random: ACK 0f088ec hebasto: ACK 0f088ec, I have reviewed the code and it looks OK. Able to build `ctime_tests` using MSan. Tree-SHA512: f4ffcc0c2ea794894662d9797b3a349770a4b361996f967f33d7d14b332171de5d525f50bcebaeaf7d0624957083380962079c75e490d1b7d71f8f9eb6211590
2 parents ff8edf8 + 0f088ec commit f29a327

16 files changed

+279
-191
lines changed

.cirrus.yml

+15-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ env:
2323
SECP256K1_TEST_ITERS:
2424
BENCH: yes
2525
SECP256K1_BENCH_ITERS: 2
26-
CTIMETEST: yes
26+
CTIMETESTS: yes
2727
# Compile and run the tests
2828
EXAMPLES: yes
2929

@@ -40,8 +40,8 @@ cat_logs_snippet: &CAT_LOGS
4040
- cat noverify_tests.log || true
4141
cat_exhaustive_tests_log_script:
4242
- cat exhaustive_tests.log || true
43-
cat_valgrind_ctime_test_log_script:
44-
- cat valgrind_ctime_test.log || true
43+
cat_ctime_tests_log_script:
44+
- cat ctime_tests.log || true
4545
cat_bench_log_script:
4646
- cat bench.log || true
4747
cat_config_log_script:
@@ -81,9 +81,9 @@ task:
8181
- env: {WIDEMUL: int128, ECDH: yes, SCHNORRSIG: yes}
8282
- env: {WIDEMUL: int128, ASM: x86_64}
8383
- env: { RECOVERY: yes, SCHNORRSIG: yes}
84-
- env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETEST: no, BENCH: no}
84+
- env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETESTS: no, BENCH: no}
8585
- env: {CPPFLAGS: -DDETERMINISTIC}
86-
- env: {CFLAGS: -O0, CTIMETEST: no}
86+
- env: {CFLAGS: -O0, CTIMETESTS: no}
8787
- env: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 }
8888
- env: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 }
8989
matrix:
@@ -128,7 +128,7 @@ task:
128128
env:
129129
ASM: no
130130
WITH_VALGRIND: no
131-
CTIMETEST: no
131+
CTIMETESTS: no
132132
matrix:
133133
- env:
134134
CC: gcc
@@ -153,7 +153,7 @@ task:
153153
ECDH: yes
154154
RECOVERY: yes
155155
SCHNORRSIG: yes
156-
CTIMETEST: no
156+
CTIMETESTS: no
157157
<< : *MERGE_BASE
158158
test_script:
159159
# https://sourceware.org/bugzilla/show_bug.cgi?id=27008
@@ -172,7 +172,7 @@ task:
172172
ECDH: yes
173173
RECOVERY: yes
174174
SCHNORRSIG: yes
175-
CTIMETEST: no
175+
CTIMETESTS: no
176176
matrix:
177177
- env: {}
178178
- env: {EXPERIMENTAL: yes, ASM: arm}
@@ -192,7 +192,7 @@ task:
192192
ECDH: yes
193193
RECOVERY: yes
194194
SCHNORRSIG: yes
195-
CTIMETEST: no
195+
CTIMETESTS: no
196196
<< : *MERGE_BASE
197197
test_script:
198198
- ./ci/cirrus.sh
@@ -209,7 +209,7 @@ task:
209209
ECDH: yes
210210
RECOVERY: yes
211211
SCHNORRSIG: yes
212-
CTIMETEST: no
212+
CTIMETESTS: no
213213
<< : *MERGE_BASE
214214
test_script:
215215
- ./ci/cirrus.sh
@@ -223,7 +223,7 @@ task:
223223
ECDH: yes
224224
RECOVERY: yes
225225
SCHNORRSIG: yes
226-
CTIMETEST: no
226+
CTIMETESTS: no
227227
matrix:
228228
- name: "x86_64 (mingw32-w64): Windows (Debian stable, Wine)"
229229
env:
@@ -246,7 +246,7 @@ task:
246246
RECOVERY: yes
247247
EXPERIMENTAL: yes
248248
SCHNORRSIG: yes
249-
CTIMETEST: no
249+
CTIMETESTS: no
250250
# Use a MinGW-w64 host to tell ./configure we're building for Windows.
251251
# This will detect some MinGW-w64 tools but then make will need only
252252
# the MSVC tools CC, AR and NM as specified below.
@@ -285,7 +285,7 @@ task:
285285
ECDH: yes
286286
RECOVERY: yes
287287
SCHNORRSIG: yes
288-
CTIMETEST: no
288+
CTIMETESTS: no
289289
matrix:
290290
- name: "Valgrind (memcheck)"
291291
container:
@@ -330,10 +330,11 @@ task:
330330
ECDH: yes
331331
RECOVERY: yes
332332
SCHNORRSIG: yes
333-
CTIMETEST: no
333+
CTIMETESTS: yes
334334
CC: clang
335335
SECP256K1_TEST_ITERS: 32
336336
ASM: no
337+
WITH_VALGRIND: no
337338
container:
338339
memory: 2G
339340
matrix:

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ tests
66
exhaustive_tests
77
precompute_ecmult_gen
88
precompute_ecmult
9-
valgrind_ctime_test
9+
ctime_tests
1010
ecdh_example
1111
ecdsa_example
1212
schnorr_example

Makefile.am

+8-10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ noinst_HEADERS += src/modinv64_impl.h
4747
noinst_HEADERS += src/precomputed_ecmult.h
4848
noinst_HEADERS += src/precomputed_ecmult_gen.h
4949
noinst_HEADERS += src/assumptions.h
50+
noinst_HEADERS += src/checkmem.h
5051
noinst_HEADERS += src/util.h
5152
noinst_HEADERS += src/int128.h
5253
noinst_HEADERS += src/int128_impl.h
@@ -98,10 +99,6 @@ libsecp256k1_la_CPPFLAGS = $(SECP_INCLUDES) $(SECP_CONFIG_DEFINES)
9899
libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
99100
libsecp256k1_la_LDFLAGS = -no-undefined -version-info $(LIB_VERSION_CURRENT):$(LIB_VERSION_REVISION):$(LIB_VERSION_AGE)
100101

101-
if VALGRIND_ENABLED
102-
libsecp256k1_la_CPPFLAGS += -DVALGRIND
103-
endif
104-
105102
noinst_PROGRAMS =
106103
if USE_BENCHMARK
107104
noinst_PROGRAMS += bench bench_internal bench_ecmult
@@ -124,12 +121,6 @@ noverify_tests_SOURCES = src/tests.c
124121
noverify_tests_CPPFLAGS = $(SECP_INCLUDES) $(SECP_TEST_INCLUDES) $(SECP_CONFIG_DEFINES)
125122
noverify_tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB)
126123
noverify_tests_LDFLAGS = -static
127-
if VALGRIND_ENABLED
128-
noverify_tests_CPPFLAGS += -DVALGRIND
129-
noinst_PROGRAMS += valgrind_ctime_test
130-
valgrind_ctime_test_SOURCES = src/valgrind_ctime_test.c
131-
valgrind_ctime_test_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB)
132-
endif
133124
if !ENABLE_COVERAGE
134125
TESTS += tests
135126
noinst_PROGRAMS += tests
@@ -140,6 +131,13 @@ tests_LDFLAGS = $(noverify_tests_LDFLAGS)
140131
endif
141132
endif
142133

134+
if USE_CTIME_TESTS
135+
noinst_PROGRAMS += ctime_tests
136+
ctime_tests_SOURCES = src/ctime_tests.c
137+
ctime_tests_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB)
138+
ctime_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES)
139+
endif
140+
143141
if USE_EXHAUSTIVE_TESTS
144142
noinst_PROGRAMS += exhaustive_tests
145143
exhaustive_tests_SOURCES = src/tests_exhaustive.c

ci/cirrus.sh

+15-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ print_environment() {
1313
for var in WERROR_CFLAGS MAKEFLAGS BUILD \
1414
ECMULTWINDOW ECMULTGENPRECISION ASM WIDEMUL WITH_VALGRIND EXTRAFLAGS \
1515
EXPERIMENTAL ECDH RECOVERY SCHNORRSIG \
16-
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETEST\
16+
SECP256K1_TEST_ITERS BENCH SECP256K1_BENCH_ITERS CTIMETESTS\
1717
EXAMPLES \
1818
HOST WRAPPER_CMD \
1919
CC CFLAGS CPPFLAGS AR NM
@@ -62,6 +62,7 @@ fi
6262
--enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \
6363
--enable-module-schnorrsig="$SCHNORRSIG" \
6464
--enable-examples="$EXAMPLES" \
65+
--enable-ctime-tests="$CTIMETESTS" \
6566
--with-valgrind="$WITH_VALGRIND" \
6667
--host="$HOST" $EXTRAFLAGS
6768

@@ -78,24 +79,29 @@ export LOG_COMPILER="$WRAPPER_CMD"
7879

7980
make "$BUILD"
8081

82+
# Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool
83+
EXEC='./libtool --mode=execute'
84+
if [ -n "$WRAPPER_CMD" ]
85+
then
86+
EXEC="$EXEC $WRAPPER_CMD"
87+
fi
88+
8189
if [ "$BENCH" = "yes" ]
8290
then
83-
# Using the local `libtool` because on macOS the system's libtool has nothing to do with GNU libtool
84-
EXEC='./libtool --mode=execute'
85-
if [ -n "$WRAPPER_CMD" ]
86-
then
87-
EXEC="$EXEC $WRAPPER_CMD"
88-
fi
8991
{
9092
$EXEC ./bench_ecmult
9193
$EXEC ./bench_internal
9294
$EXEC ./bench
9395
} >> bench.log 2>&1
9496
fi
9597

96-
if [ "$CTIMETEST" = "yes" ]
98+
if [ "$CTIMETESTS" = "yes" ]
9799
then
98-
./libtool --mode=execute valgrind --error-exitcode=42 ./valgrind_ctime_test > valgrind_ctime_test.log 2>&1
100+
if [ "$WITH_VALGRIND" = "yes" ]; then
101+
./libtool --mode=execute valgrind --error-exitcode=42 ./ctime_tests > ctime_tests.log 2>&1
102+
else
103+
$EXEC ./ctime_tests > ctime_tests.log 2>&1
104+
fi
99105
fi
100106

101107
# Rebuild precomputed files (if not cross-compiling).

configure.ac

+11-2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ AC_ARG_ENABLE(tests,
142142
AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), [],
143143
[SECP_SET_DEFAULT([enable_tests], [yes], [yes])])
144144

145+
AC_ARG_ENABLE(ctime_tests,
146+
AS_HELP_STRING([--enable-ctime-tests],[compile constant-time tests [default=yes if valgrind enabled]]), [],
147+
[SECP_SET_DEFAULT([enable_ctime_tests], [auto], [auto])])
148+
145149
AC_ARG_ENABLE(experimental,
146150
AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]), [],
147151
[SECP_SET_DEFAULT([enable_experimental], [no], [yes])])
@@ -225,7 +229,10 @@ else
225229
enable_valgrind=yes
226230
fi
227231
fi
228-
AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"])
232+
233+
if test x"$enable_ctime_tests" = x"auto"; then
234+
enable_ctime_tests=$enable_valgrind
235+
fi
229236

230237
if test x"$enable_coverage" = x"yes"; then
231238
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DCOVERAGE=1"
@@ -344,7 +351,7 @@ case $set_ecmult_gen_precision in
344351
esac
345352

346353
if test x"$enable_valgrind" = x"yes"; then
347-
SECP_INCLUDES="$SECP_INCLUDES $VALGRIND_CPPFLAGS"
354+
SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES $VALGRIND_CPPFLAGS -DVALGRIND"
348355
fi
349356

350357
# Add -Werror and similar flags passed from the outside (for testing, e.g., in CI).
@@ -407,6 +414,7 @@ AC_SUBST(SECP_CFLAGS)
407414
AC_SUBST(SECP_CONFIG_DEFINES)
408415
AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"])
409416
AM_CONDITIONAL([USE_TESTS], [test x"$enable_tests" != x"no"])
417+
AM_CONDITIONAL([USE_CTIME_TESTS], [test x"$enable_ctime_tests" = x"yes"])
410418
AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$enable_exhaustive_tests" != x"no"])
411419
AM_CONDITIONAL([USE_EXAMPLES], [test x"$enable_examples" != x"no"])
412420
AM_CONDITIONAL([USE_BENCHMARK], [test x"$enable_benchmark" = x"yes"])
@@ -428,6 +436,7 @@ echo "Build Options:"
428436
echo " with external callbacks = $enable_external_default_callbacks"
429437
echo " with benchmarks = $enable_benchmark"
430438
echo " with tests = $enable_tests"
439+
echo " with ctime tests = $enable_ctime_tests"
431440
echo " with coverage = $enable_coverage"
432441
echo " with examples = $enable_examples"
433442
echo " module ecdh = $enable_module_ecdh"

doc/safegcd_implementation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ sufficient even. Given that every loop iteration performs *N* divsteps, it will
410410

411411
To deal with the branches in `divsteps_n_matrix` we will replace them with constant-time bitwise
412412
operations (and hope the C compiler isn't smart enough to turn them back into branches; see
413-
`valgrind_ctime_test.c` for automated tests that this isn't the case). To do so, observe that a
413+
`ctime_tests.c` for automated tests that this isn't the case). To do so, observe that a
414414
divstep can be written instead as (compare to the inner loop of `gcd` in section 1).
415415

416416
```python

src/checkmem.h

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/***********************************************************************
2+
* Copyright (c) 2022 Pieter Wuille *
3+
* Distributed under the MIT software license, see the accompanying *
4+
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
5+
***********************************************************************/
6+
7+
/* The code here is inspired by Kris Kwiatkowski's approach in
8+
* https://github.com/kriskwiatkowski/pqc/blob/main/src/common/ct_check.h
9+
* to provide a general interface for memory-checking mechanisms, primarily
10+
* for constant-time checking.
11+
*/
12+
13+
/* These macros are defined by this header file:
14+
*
15+
* - SECP256K1_CHECKMEM_ENABLED:
16+
* - 1 if memory-checking integration is available, 0 otherwise.
17+
* This is just a compile-time macro. Use the next macro to check it is actually
18+
* available at runtime.
19+
* - SECP256K1_CHECKMEM_RUNNING():
20+
* - Acts like a function call, returning 1 if memory checking is available
21+
* at runtime.
22+
* - SECP256K1_CHECKMEM_CHECK(p, len):
23+
* - Assert or otherwise fail in case the len-byte memory block pointed to by p is
24+
* not considered entirely defined.
25+
* - SECP256K1_CHECKMEM_CHECK_VERIFY(p, len):
26+
* - Like SECP256K1_CHECKMEM_CHECK, but only works in VERIFY mode.
27+
* - SECP256K1_CHECKMEM_UNDEFINE(p, len):
28+
* - marks the len-byte memory block pointed to by p as undefined data (secret data,
29+
* in the context of constant-time checking).
30+
* - SECP256K1_CHECKMEM_DEFINE(p, len):
31+
* - marks the len-byte memory pointed to by p as defined data (public data, in the
32+
* context of constant-time checking).
33+
*
34+
*/
35+
36+
#ifndef SECP256K1_CHECKMEM_H
37+
#define SECP256K1_CHECKMEM_H
38+
39+
/* Define a statement-like macro that ignores the arguments. */
40+
#define SECP256K1_CHECKMEM_NOOP(p, len) do { (void)(p); (void)(len); } while(0)
41+
42+
/* If compiling under msan, map the SECP256K1_CHECKMEM_* functionality to msan.
43+
* Choose this preferentially, even when VALGRIND is defined, as msan-compiled
44+
* binaries can't be run under valgrind anyway. */
45+
#if defined(__has_feature)
46+
# if __has_feature(memory_sanitizer)
47+
# include <sanitizer/msan_interface.h>
48+
# define SECP256K1_CHECKMEM_ENABLED 1
49+
# define SECP256K1_CHECKMEM_UNDEFINE(p, len) __msan_allocated_memory((p), (len))
50+
# define SECP256K1_CHECKMEM_DEFINE(p, len) __msan_unpoison((p), (len))
51+
# define SECP256K1_CHECKMEM_CHECK(p, len) __msan_check_mem_is_initialized((p), (len))
52+
# define SECP256K1_CHECKMEM_RUNNING() (1)
53+
# endif
54+
#endif
55+
56+
/* If valgrind integration is desired (through the VALGRIND define), implement the
57+
* SECP256K1_CHECKMEM_* macros using valgrind. */
58+
#if !defined SECP256K1_CHECKMEM_ENABLED
59+
# if defined VALGRIND
60+
# include <stddef.h>
61+
# include <valgrind/memcheck.h>
62+
# define SECP256K1_CHECKMEM_ENABLED 1
63+
# define SECP256K1_CHECKMEM_UNDEFINE(p, len) VALGRIND_MAKE_MEM_UNDEFINED((p), (len))
64+
# define SECP256K1_CHECKMEM_DEFINE(p, len) VALGRIND_MAKE_MEM_DEFINED((p), (len))
65+
# define SECP256K1_CHECKMEM_CHECK(p, len) VALGRIND_CHECK_MEM_IS_DEFINED((p), (len))
66+
/* VALGRIND_MAKE_MEM_DEFINED returns 0 iff not running on memcheck.
67+
* This is more precise than the RUNNING_ON_VALGRIND macro, which
68+
* checks for valgrind in general instead of memcheck specifically. */
69+
# define SECP256K1_CHECKMEM_RUNNING() (VALGRIND_MAKE_MEM_DEFINED(NULL, 0) != 0)
70+
# endif
71+
#endif
72+
73+
/* As a fall-back, map these macros to dummy statements. */
74+
#if !defined SECP256K1_CHECKMEM_ENABLED
75+
# define SECP256K1_CHECKMEM_ENABLED 0
76+
# define SECP256K1_CHECKMEM_UNDEFINE(p, len) SECP256K1_CHECKMEM_NOOP((p), (len))
77+
# define SECP256K1_CHECKMEM_DEFINE(p, len) SECP256K1_CHECKMEM_NOOP((p), (len))
78+
# define SECP256K1_CHECKMEM_CHECK(p, len) SECP256K1_CHECKMEM_NOOP((p), (len))
79+
# define SECP256K1_CHECKMEM_RUNNING() (0)
80+
#endif
81+
82+
#if defined VERIFY
83+
#define SECP256K1_CHECKMEM_CHECK_VERIFY(p, len) SECP256K1_CHECKMEM_CHECK((p), (len))
84+
#else
85+
#define SECP256K1_CHECKMEM_CHECK_VERIFY(p, len) SECP256K1_CHECKMEM_NOOP((p), (len))
86+
#endif
87+
88+
#endif /* SECP256K1_CHECKMEM_H */

0 commit comments

Comments
 (0)