diff --git a/.gitignore b/.gitignore index cb4331aa90..ee61325548 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ tests exhaustive_tests gen_context valgrind_ctime_test +fuzz_public +fuzz_recovery +fuzz_ecdh *.exe *.so *.a diff --git a/.travis.yml b/.travis.yml index 3d315b1010..0db9c426aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,6 +79,10 @@ matrix: env: # The same as above but without endomorphism. - BIGNUM=no ENDOMORPHISM=no ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes - VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD= + - compiler: clang + env: + - BIGNUM=no ENDOMORPHISM=no ASM=no EXPERIMENTAL=yes ECDH=yes RECOVERY=yes + - EXTRAFLAGS="--enable-fuzz" BENCH= CTIMETEST= before_script: ./autogen.sh diff --git a/Makefile.am b/Makefile.am index d8c1c79e8c..79e0840fc5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,6 +90,14 @@ bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) bench_ecmult_CPPFLAGS = -DSECP256K1_BUILD $(SECP_INCLUDES) endif +if ENABLE_FUZZ +noinst_PROGRAMS += fuzz_public +fuzz_public_SOURCES = src/fuzz/fuzz_public.c +fuzz_public_SOURCES += src/fuzz/fuzz.c +fuzz_public_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +endif + + TESTS = if USE_TESTS noinst_PROGRAMS += tests diff --git a/build-aux/m4/ax_check_compile_flag.m4 b/build-aux/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000000..bd753b34d7 --- /dev/null +++ b/build-aux/m4/ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/build-aux/m4/ax_check_link_flag.m4 b/build-aux/m4/ax_check_link_flag.m4 new file mode 100644 index 0000000000..03a30ce4c7 --- /dev/null +++ b/build-aux/m4/ax_check_link_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_link_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_LINK_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the linker or gives an error. +# (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the linker's default flags +# when the check is done. The check is thus made with the flags: "LDFLAGS +# EXTRA-FLAGS FLAG". This can for example be used to force the linker to +# issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_LINK_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,COMPILE}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_LINK_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_ldflags_$4_$1])dnl +AC_CACHE_CHECK([whether the linker accepts $1], CACHEVAR, [ + ax_check_save_flags=$LDFLAGS + LDFLAGS="$LDFLAGS $4 $1" + AC_LINK_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + LDFLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_LINK_FLAGS diff --git a/configure.ac b/configure.ac index 7f762fa31b..7583cfdc4b 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,17 @@ AC_ARG_ENABLE(coverage, [enable_coverage=$enableval], [enable_coverage=no]) +AC_ARG_ENABLE(fuzz, + AS_HELP_STRING([--enable-fuzz],[enable building fuzz targets, this will disable all other targets [default=yes]]), + [enable_fuzz=$enableval], + [enable_fuzz=no]) + +dnl Enable different -fsanitize options +AC_ARG_WITH(sanitizers, + [AS_HELP_STRING([--with-sanitizers], + [comma separated list of extra sanitizers to build with (default is none enabled)])], + [use_sanitizers=$withval]) + AC_ARG_ENABLE(tests, AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), [use_tests=$enableval], @@ -183,6 +194,19 @@ else CFLAGS="-O2 $CFLAGS" fi +dnl enable-fuzz should disable all other targets +if test x"$enable_fuzz" = x"yes"; then + use_sanitizers="fuzzer,$use_sanitizers" + AC_MSG_WARN(enable-fuzz will disable all other targets, asm implementation and gmp usage) + req_asm=no + req_bignum=no + use_tests=no + use_benchmark=no + enable_coverage=no + enable_openssl_tests=no + use_exhaustive_tests=no +fi + if test x"$use_ecmult_static_precomputation" != x"no"; then # Temporarily switch to an environment for the native compiler save_cross_compiling=$cross_compiling @@ -518,6 +542,36 @@ else fi fi +dnl This has to be last because adding `-fsanitize=fuzz` will collide the `main` function with everything else here +if test x"$use_sanitizers" != x; then + dnl First check if the compiler accepts flags. If an incompatible pair like + dnl -fsanitize=address,thread is used here, this check will fail. This will also + dnl fail if a bad argument is passed, e.g. -fsanitize=undfeined + AX_CHECK_COMPILE_FLAG( + [[-fsanitize=$use_sanitizers]], + [[CFLAGS="-fsanitize=$use_sanitizers $CFLAGS"]], + [AC_MSG_ERROR([compiler did not accept requested flags])]) + + dnl Some compilers (e.g. GCC) require additional libraries like libasan, + dnl libtsan, libubsan, etc. Make sure linking still works with the sanitize + dnl flag. This is a separate check so we can give a better error message when + dnl the sanitize flags are supported by the compiler but the actual sanitizer + dnl libs are missing. + AX_CHECK_LINK_FLAG( + [[-fsanitize=$use_sanitizers]], + [[LDFLAGS="-fsanitize=$use_sanitizers $LDFLAGS"]], + [AC_MSG_ERROR([linker did not accept requested flags, you are missing required libraries])], + [], + [AC_LANG_PROGRAM([[ + #include + #include + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; } + __attribute__((weak)) + /* AC-LANG-PROGRAM will put int main(..) after this which will conflict with libFuzzer's main, weak symbol resolves it */ + ]],[[]])]) +fi + + AC_CONFIG_HEADERS([src/libsecp256k1-config.h]) AC_CONFIG_FILES([Makefile libsecp256k1.pc]) AC_SUBST(SECP_INCLUDES) @@ -525,6 +579,7 @@ AC_SUBST(SECP_LIBS) AC_SUBST(SECP_TEST_LIBS) AC_SUBST(SECP_TEST_INCLUDES) AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"]) +AM_CONDITIONAL([ENABLE_FUZZ],[test x$enable_fuzz = x"yes"]) AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"]) AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"]) AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"]) @@ -548,6 +603,8 @@ echo " with ecmult precomp = $set_precomp" echo " with external callbacks = $use_external_default_callbacks" echo " with benchmarks = $use_benchmark" echo " with coverage = $enable_coverage" +echo " with fuzz = $enable_fuzz" +echo " with sanitizers = $use_sanitizers" echo " module ecdh = $enable_module_ecdh" echo " module recovery = $enable_module_recovery" echo diff --git a/src/fuzz/fuzz.c b/src/fuzz/fuzz.c new file mode 100644 index 0000000000..1721cd594d --- /dev/null +++ b/src/fuzz/fuzz.c @@ -0,0 +1,24 @@ +#include +#include +#include "fuzz.h" + +/* Default initialization: Override using a non-weak initialize(). */ +__attribute__((weak)) void initialize(void) {} + + +/* This function is used by libFuzzer */ +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + fuzzed_data_provider provider = initialize_fuzzed_data_provider(data, size); + test_one_input(&provider); + return 0; +} + +/* This function is used by libFuzzer */ +int LLVMFuzzerInitialize(int* argc, char*** argv) +{ + (void)argc; + (void)argv; + initialize(); + return 0; +} diff --git a/src/fuzz/fuzz.h b/src/fuzz/fuzz.h new file mode 100644 index 0000000000..5d8afc0208 --- /dev/null +++ b/src/fuzz/fuzz.h @@ -0,0 +1,99 @@ +#include +#include +#include +#include "include/secp256k1.h" + +typedef struct { + const unsigned char* data; + size_t remaining; +} fuzzed_data_provider; + +static fuzzed_data_provider initialize_fuzzed_data_provider(const unsigned char* data, size_t size) { + fuzzed_data_provider res; + res.data = data; + res.remaining = size; + + return res; +} + +/* Should check remaining bounds *before* calling this */ +static void advance(fuzzed_data_provider* provider, size_t num) { + provider->data += num; + provider->remaining -= num; +} + +/* Consumes num bytes from the provider, returns NULL if there's not enough data left. */ +static const unsigned char* consume_bytes(fuzzed_data_provider* provider, size_t num) { + if (num > provider->remaining) { + return NULL; + } else { + const unsigned char* res = provider->data; + advance(provider, num); + return res; + } +} + +/* Consumes at least 32 bytes until it find a valid seckey, returns NULL if there's not enough data left */ +static const unsigned char* consume_seckey(fuzzed_data_provider* provider) { + const unsigned char* end = provider->data + provider->remaining; + const unsigned char* seckey; + if (provider->remaining < 32) { + return NULL; + } + + while (!secp256k1_ec_seckey_verify(secp256k1_context_no_precomp, provider->data)) { + if (++provider->data > end - 32) { + provider->remaining = end - provider->data; + return NULL; + } + } + seckey = provider->data; + provider->data += 32; + provider->remaining = end - provider->data; + return seckey; +} + +/* Consumes at least 33 bytes until it find a valid pubkey, returns 1 on sucess and returns 0 if there's not enough data left +* (have 50% probablity of finding a key for uniforma random bytes) +*/ +static int consume_pubkey(fuzzed_data_provider* provider, secp256k1_pubkey* res) { + const unsigned char* end = provider->data + provider->remaining; + unsigned char pubkey[33]; + if (provider->remaining < 33) { + return 0; + } + + pubkey[0] = 0x02 + (provider->data[0] & 1); + memcpy(pubkey, provider->data + 1, 32); + + while(!secp256k1_ec_pubkey_parse(secp256k1_context_no_precomp, res, pubkey, 33)) { + if (++provider->data > end - 33) { + provider->remaining = end - provider->data; + return 0; + } + pubkey[0] = 0x02 + (provider->data[0] & 1); + memcpy(pubkey, provider->data + 1, 32); + } + provider->data += 33; + provider->remaining = end - provider->data; + return 1; +} + +/* Consume an int from the provider and returns it, defaults to zero if the provider is empty. */ +static int consume_int(fuzzed_data_provider* provider) { + int res = 0; + int bytes_to_copy; + + if (provider->remaining > sizeof(int)) { + bytes_to_copy = sizeof(int); + } else { + bytes_to_copy = provider->remaining; + } + + memcpy(&res, provider->data, bytes_to_copy); + advance(provider, bytes_to_copy); + return res; +} + +void initialize(void); +void test_one_input(fuzzed_data_provider* provider); diff --git a/src/fuzz/fuzz_public.c b/src/fuzz/fuzz_public.c new file mode 100644 index 0000000000..7066ccd8db --- /dev/null +++ b/src/fuzz/fuzz_public.c @@ -0,0 +1,220 @@ +/********************************************************************** + * Copyright (c) 2020 Elichai Turkel * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include +#include +#include +#include "include/secp256k1_recovery.h" +#include "src/util.h" +#include "src/fuzz/fuzz.h" + +static secp256k1_context* ctx = NULL; + +void initialize() { + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + CHECK(ctx != NULL); +} + +void test_one_input(fuzzed_data_provider* provider) { + /* Create and destroy a scratch space */ + { + const unsigned char* byte; + byte = consume_bytes(provider, 1); + if (byte) { + secp256k1_scratch_space_destroy(ctx, secp256k1_scratch_space_create(ctx, *byte)); + } + } + /* Randomize the context */ + { + const unsigned char* randomize; + randomize = consume_bytes(provider, 32); + if (randomize) { + CHECK(secp256k1_context_randomize(ctx, randomize) == 1); + } + } + /* Sign & Verify */ + { + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig, sig_normalized; + const unsigned char* seckey; + const unsigned char* msg; + seckey = consume_seckey(provider); + msg = consume_bytes(provider, 32); + if (seckey && msg) { + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey) == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &sig, msg, seckey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pubkey) == 1); + + /* libsecp256k1, only generates normalized signatures, check that */ + CHECK(secp256k1_ecdsa_signature_normalize(ctx, &sig_normalized, &sig) == 0); + CHECK(memcmp(&sig, &sig_normalized, sizeof(sig)) == 0); + } + } + /* Check valid pubkey compact parsing + serializing */ + { + secp256k1_pubkey pubkey; + if (consume_pubkey(provider, &pubkey)) { + unsigned char serialized_pubkey[65]; + secp256k1_pubkey new_pubkey; + size_t n = sizeof(serialized_pubkey); + unsigned char compressed; + + CHECK(secp256k1_ec_pubkey_serialize(ctx, serialized_pubkey, &n, &pubkey, SECP256K1_EC_COMPRESSED) == 1); + CHECK(n == 33); + CHECK(secp256k1_ec_pubkey_parse(ctx, &new_pubkey, serialized_pubkey, n) == 1); + CHECK(memcmp(&pubkey, &new_pubkey, sizeof(pubkey)) == 0); + compressed = serialized_pubkey[0]; + n = 65; + CHECK(secp256k1_ec_pubkey_serialize(ctx, serialized_pubkey, &n, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + CHECK(n == 65); + CHECK(secp256k1_ec_pubkey_parse(ctx, &new_pubkey, serialized_pubkey, n) == 1); + CHECK(memcmp(&pubkey, &new_pubkey, sizeof(pubkey)) == 0); + serialized_pubkey[0] += compressed; + CHECK(secp256k1_ec_pubkey_parse(ctx, &new_pubkey, serialized_pubkey, n) == 1); + CHECK(memcmp(&pubkey, &new_pubkey, sizeof(pubkey)) == 0); + } + } + /* Check valid pubkey DER parsing + serializing */ + { + unsigned char serialized_sig[72]; + secp256k1_ecdsa_signature sig, newsig; + size_t n = sizeof(serialized_sig); + const unsigned char* input64; + input64 = consume_bytes(provider, 64); + /* This should succeed as long as r and s (first and second 32 bytes) aren't bigger than the order */ + if (input64 && secp256k1_ecdsa_signature_parse_compact(ctx, &sig, input64)) { + CHECK(secp256k1_ecdsa_signature_serialize_der(ctx, serialized_sig, &n, &sig) == 1); + CHECK(secp256k1_ecdsa_signature_parse_der(ctx, &newsig, serialized_sig, n) == 1); + CHECK(memcmp(&sig, &newsig, sizeof(sig)) == 0); + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_sig, &sig) == 1); + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &newsig, serialized_sig) == 1); + CHECK(memcmp(&sig, &newsig, sizeof(sig)) == 0); + } + } + + /* Double negating a seckey should stay equal */ + { + const unsigned char* seckey; + seckey = consume_seckey(provider); + if (seckey) { + unsigned char seckey_copy[32]; + memcpy(seckey_copy, seckey, sizeof(seckey_copy)); + + CHECK(secp256k1_ec_privkey_negate(ctx, seckey_copy) == 1); + CHECK(secp256k1_ec_privkey_negate(ctx, seckey_copy) == 1); + CHECK(memcmp(seckey_copy, seckey, sizeof(seckey_copy)) == 0); + } + } + /* Double negating a pubkey should stay equal */ + { + secp256k1_pubkey pubkey; + if (consume_pubkey(provider, &pubkey)) { + secp256k1_pubkey pubkey_copy; + pubkey_copy = pubkey; + + CHECK(secp256k1_ec_pubkey_negate(ctx, &pubkey) == 1); + CHECK(secp256k1_ec_pubkey_negate(ctx, &pubkey) == 1); + CHECK(memcmp(&pubkey_copy, &pubkey, sizeof(pubkey_copy)) == 0); + } + } + /* Check that Pubkey(seckey1+seckey2) == Pubkey(seckey1) + Pubkey(seckey2). */ + { + const unsigned char* seckey1; + const unsigned char* seckey2; + seckey1 = consume_seckey(provider); + seckey2 = consume_seckey(provider); + if (seckey1 && seckey2) { + secp256k1_pubkey pubkey1, pubkey2, combined_pubkey1, combined_pubkey2; + unsigned char combined_seckey[32]; + const secp256k1_pubkey* pubkey_ptrs[2]; + + + memcpy(combined_seckey, seckey1, sizeof(combined_seckey)); + CHECK(secp256k1_ec_privkey_tweak_add(ctx, combined_seckey, seckey2) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &combined_pubkey2, combined_seckey) == 1); + + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey1, seckey1) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, seckey2) == 1); + + pubkey_ptrs[0] = &pubkey1; pubkey_ptrs[1] = &pubkey2; + CHECK(secp256k1_ec_pubkey_combine(ctx, &combined_pubkey1, pubkey_ptrs, 2) == 1); + + CHECK(memcmp(&combined_pubkey1, &combined_pubkey2, sizeof(combined_pubkey1)) == 0); + + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &pubkey1, seckey2) == 1); + CHECK(memcmp(&combined_pubkey1, &pubkey1, sizeof(combined_pubkey1)) == 0); + } + } + /* Check that Pubkey(seckey1)*seckey2 == Pubkey(seckey1 * seckey2) */ + { + const unsigned char* seckey1; + const unsigned char* seckey2; + seckey1 = consume_seckey(provider); + seckey2 = consume_seckey(provider); + if (seckey1 && seckey2) { + secp256k1_pubkey mul_pubkey1, mul_pubkey2; + unsigned char mul_seckey[32]; + + CHECK(secp256k1_ec_pubkey_create(ctx, &mul_pubkey1, seckey1) == 1); + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &mul_pubkey1, seckey2) == 1); + + memcpy(&mul_seckey, seckey1, sizeof(mul_seckey)); + CHECK(secp256k1_ec_privkey_tweak_mul(ctx, mul_seckey, seckey2) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &mul_pubkey2, mul_seckey) == 1); + + CHECK(memcmp(&mul_pubkey1, &mul_pubkey2, sizeof(mul_pubkey1)) == 0); + } + } + /*Fuzz Garbage through parsing functions */ + { + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + const unsigned char* random32; + const unsigned char* random64; + const unsigned char* tmp; + unsigned char random33[33], random65[65]; + int ret = 0; + random32 = consume_bytes(provider, 32); + if (random32) { + ret |= secp256k1_ec_seckey_verify(ctx, random32); + } + tmp = consume_bytes(provider, 33); + if (tmp) { + memcpy(random33, tmp, sizeof(random33)); + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random33, 33); + random33[0] = 0x02; + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random33, 33); + random33[0] = 0x03; + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random33, 33); + } + tmp = consume_bytes(provider, 65); + if (tmp) { + memcpy(random65, tmp, sizeof(random65)); + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random65, 65); + random65[0] = 0x04; + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random65, 65); + random65[0] = 0x06; + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random65, 65); + random65[0] = 0x07; + ret |= secp256k1_ec_pubkey_parse(ctx, &pubkey, random65, 65); + } + random64 = consume_bytes(provider, 64); + if (random64) { + int tmp_ret = secp256k1_ecdsa_signature_parse_compact(ctx, &sig, random64); + if(tmp_ret) { + /* It's statistically impossible to randomally find a random sig that will validate a given msg+key. */ + const unsigned char secret_msg[32] = "This is the super secret message"; + const unsigned char predefined_pubkey[33] = "\002A public key without private key"; + CHECK(secp256k1_ec_pubkey_parse(ctx, &pubkey, predefined_pubkey, sizeof(predefined_pubkey)) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &sig, secret_msg, &pubkey) == 0); + } + ret |= tmp_ret; + } + /* Fuzz the rest of the data into the parse_der function */ + ret |= secp256k1_ecdsa_signature_parse_der(ctx, &sig, provider->data, provider->remaining); + CHECK(ret == 0 || ret == 1); + } +} diff --git a/src/modules/ecdh/Makefile.am.include b/src/modules/ecdh/Makefile.am.include index e3088b4697..7252e1b109 100644 --- a/src/modules/ecdh/Makefile.am.include +++ b/src/modules/ecdh/Makefile.am.include @@ -6,3 +6,10 @@ noinst_PROGRAMS += bench_ecdh bench_ecdh_SOURCES = src/bench_ecdh.c bench_ecdh_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) endif + +if ENABLE_FUZZ +noinst_PROGRAMS += fuzz_ecdh +fuzz_ecdh_SOURCES = src/modules/ecdh/fuzz_ecdh.c +fuzz_ecdh_SOURCES += src/fuzz/fuzz.c +fuzz_ecdh_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +endif \ No newline at end of file diff --git a/src/modules/ecdh/fuzz_ecdh.c b/src/modules/ecdh/fuzz_ecdh.c new file mode 100644 index 0000000000..6270df5d92 --- /dev/null +++ b/src/modules/ecdh/fuzz_ecdh.c @@ -0,0 +1,72 @@ +/********************************************************************** + * Copyright (c) 2020 Elichai Turkel * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ +#ifndef SECP256K1_MODULE_ECDH_FUZZ_IMPL_H +#define SECP256K1_MODULE_ECDH_FUZZ_IMPL_H + +#include +#include +#include +#include "include/secp256k1_ecdh.h" +#include "src/util.h" +#include "src/fuzz/fuzz.h" + +static secp256k1_context* ctx = NULL; + +void initialize() { + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + CHECK(ctx != NULL); +} +static int ecdh_hash_function_custom(unsigned char *output65, const unsigned char *x, const unsigned char *y, void *data) { + (void)data; + /* Save x and y as uncompressed public key */ + output65[0] = 0x04; + memcpy(output65 + 1, x, 32); + memcpy(output65 + 33, y, 32); + return 1; +} + +void test_one_input(fuzzed_data_provider* provider) { + /* Make sure ECDH(seckey,pubkey) == seckey * pubkey */ + { + const unsigned char* seckey; + secp256k1_pubkey pubkey; + + seckey = consume_seckey(provider); + if (seckey && consume_pubkey(provider, &pubkey)) { + unsigned char result1[65]; + unsigned char result2[65]; + size_t n = sizeof(result2); + CHECK(secp256k1_ecdh(ctx, result1, &pubkey, seckey, ecdh_hash_function_custom, NULL) == 1); + + CHECK(secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, seckey) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, result2, &n, &pubkey, SECP256K1_EC_UNCOMPRESSED) == 1); + CHECK(n == sizeof(result2)); + CHECK(memcmp(result1, result2, n) == 0); + } + } + /* Make sure ECDH(seckey1, pubkey2) == ECDH(seckey2, pubkey1) */ + { + const unsigned char* seckey1; + const unsigned char* seckey2; + seckey1 = consume_seckey(provider); + seckey2 = consume_seckey(provider); + if (seckey1 && seckey2) { + secp256k1_pubkey pubkey1, pubkey2; + unsigned char result1[32]; + unsigned char result2[32]; + + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey1, seckey1) == 1); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey2, seckey2) == 1); + + CHECK(secp256k1_ecdh(ctx, result1, &pubkey1, seckey2, NULL, NULL) == 1); + CHECK(secp256k1_ecdh(ctx, result2, &pubkey2, seckey1, NULL, NULL) == 1); + + CHECK(memcmp(result1, result2, sizeof(result1)) == 0); + } + } +} + +#endif /* SECP256K1_MODULE_ECDH_FUZZ_IMPL_H */ diff --git a/src/modules/recovery/Makefile.am.include b/src/modules/recovery/Makefile.am.include index bf23c26e71..697a288762 100644 --- a/src/modules/recovery/Makefile.am.include +++ b/src/modules/recovery/Makefile.am.include @@ -6,3 +6,10 @@ noinst_PROGRAMS += bench_recover bench_recover_SOURCES = src/bench_recover.c bench_recover_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) endif + +if ENABLE_FUZZ +noinst_PROGRAMS += fuzz_recovery +fuzz_recovery_SOURCES = src/modules/recovery/fuzz_recovery.c +fuzz_recovery_SOURCES += src/fuzz/fuzz.c +fuzz_recovery_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB) +endif \ No newline at end of file diff --git a/src/modules/recovery/fuzz_recovery.c b/src/modules/recovery/fuzz_recovery.c new file mode 100644 index 0000000000..88941c2afa --- /dev/null +++ b/src/modules/recovery/fuzz_recovery.c @@ -0,0 +1,96 @@ +/********************************************************************** + * Copyright (c) 2020 Elichai Turkel * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ +#ifndef SECP256K1_MODULE_RECOVERY_FUZZ_IMPL_H +#define SECP256K1_MODULE_RECOVERY_FUZZ_IMPL_H + +#include +#include +#include +#include "include/secp256k1_recovery.h" +#include "src/util.h" +#include "src/fuzz/fuzz.h" + +static secp256k1_context* ctx = NULL; + +void initialize() { + ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + CHECK(ctx != NULL); +} + +/* Try parsing with all recids possible (0..=3), if succeeded serialize and compare. */ +static int parse_compare_recoverable_signature(const unsigned char* data64) { + secp256k1_ecdsa_recoverable_signature sig; + unsigned char serialized_sig[64]; + int recid; + int success = 0; + for (recid = 0; recid < 4; recid++) { + int ret = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &sig, data64, recid); + CHECK(ret == 0 || ret == 1); + if (ret) { + int new_recid; + int new_ret = secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, serialized_sig, &new_recid, &sig); + CHECK(new_ret == 1); + CHECK(new_recid == recid); + CHECK(memcmp(serialized_sig, data64, sizeof(serialized_sig)) == 0); + success = 1; + } + } + return success; +} + + +void test_one_input(fuzzed_data_provider* provider) { + secp256k1_pubkey pubkey, recovered_pubkey; + secp256k1_ecdsa_recoverable_signature sig; + int recid; + unsigned char serialized_sig[64]; + const unsigned char* seckey; + const unsigned char* msg; + + seckey = consume_seckey(provider); + msg = consume_bytes(provider, 32); + if (!seckey || !msg) { + return; + } + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey) == 1); + + /* Compare recover(sign(seckey,msg)) == PubKey(seckey) */ + { + CHECK(secp256k1_ecdsa_sign_recoverable(ctx, &sig, msg, seckey, NULL, NULL) == 1); + CHECK(secp256k1_ecdsa_recover(ctx, &recovered_pubkey, &sig, msg) == 1); + CHECK(memcmp(&recovered_pubkey, &pubkey, sizeof(pubkey)) == 0); + } + /* Compare parse(serialize(sig)) == sig */ + { + secp256k1_ecdsa_recoverable_signature new_sig; + CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, serialized_sig, &recid, &sig) == 1); + CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &new_sig, serialized_sig, recid) == 1); + CHECK(memcmp(&sig, &new_sig, sizeof(sig)) == 0); + + } + /* Compare serialize(to_regular(sig)) == serialize(sig) (modulo the recid) */ + { + unsigned char serialized_sig2[64]; + secp256k1_ecdsa_signature regular_sig; + CHECK(secp256k1_ecdsa_recoverable_signature_convert(ctx, ®ular_sig, &sig) == 1); + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_sig2, ®ular_sig) == 1); + CHECK(memcmp(serialized_sig, serialized_sig2, sizeof(serialized_sig)) == 0); + } + /* Try to parse+serialzie the serialized sig with all recids, check that it succeeds at least once. */ + { + int ret = parse_compare_recoverable_signature(serialized_sig); + CHECK(ret == 1); + } + /* Fuzz Garbage through the parsing function */ + { + const unsigned char* data64 = consume_bytes(provider, 64); + if (data64) { + parse_compare_recoverable_signature(data64); + } + } +} + +#endif /* SECP256K1_MODULE_RECOVERY_FUZZ_IMPL_H */