Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a libFuzzer fuzzing harness #744

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -10,6 +10,9 @@ tests
exhaustive_tests
gen_context
valgrind_ctime_test
fuzz_public
fuzz_recovery
fuzz_ecdh
*.exe
*.so
*.a
Expand Down
4 changes: 4 additions & 0 deletions .travis.yml
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions Makefile.am
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions 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 <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# 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
53 changes: 53 additions & 0 deletions 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 <guidod@gmx.de>
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
#
# 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
57 changes: 57 additions & 0 deletions configure.ac
Expand Up @@ -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],
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -518,13 +542,44 @@ 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 <stdint.h>
#include <stddef.h>
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)
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"])
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/fuzz/fuzz.c
@@ -0,0 +1,24 @@
#include <stdint.h>
#include <stddef.h>
#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;
}
99 changes: 99 additions & 0 deletions src/fuzz/fuzz.h
@@ -0,0 +1,99 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#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);