diff --git a/.gitignore b/.gitignore index 77dc28fad..ba5fad562 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ config.sub configure depcomp dist +fuzz/fuzz_* +!fuzz/fuzz_*.c install-sh libtool ltmain.sh diff --git a/Makefile.am b/Makefile.am index c43744058..4d9b2c594 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,3 +1,7 @@ ACLOCAL_AMFLAGS = -I tools/build-aux/m4 AUTOMAKE_OPTIONS = foreign SUBDIRS = src + +if BUILD_FUZZ +SUBDIRS += fuzz +endif diff --git a/README.md b/README.md index 6dd5f9e89..337dfe8c2 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,11 @@ $ brew install swig - `--disable-tests`. Disables building library tests. (default: no) - `--disable-clear-tests`. Disables just the test_clear test (required to pass the test suite with some compilers). (default: no) +- `--enable-fuzzing`. Enables fuzzing support by compiling with + `-fsanitize=fuzzer-no-link` and builds fuzz targets. (default: no). +- `--enable-address-sanitizer`. Enables the address sanitizer for detecting + memory errors. (default: no). +- `--enable-ub-sanitizer`. Enables the undefined behavior sanitizer. (default: no). ### Recommended development configure options diff --git a/_CMakeLists.txt b/_CMakeLists.txt index 56b31edf3..170f7ef15 100644 --- a/_CMakeLists.txt +++ b/_CMakeLists.txt @@ -13,6 +13,23 @@ option(WALLYCORE_ENABLE_TESTS "Build tests" OFF) option(WALLYCORE_INSTALL "Enable install" OFF) option(WALLYCORE_COVERAGE "Enable coverage" OFF) option(WALLYCORE_BUILD_ELEMENTS "Build elements" ON) +option(WALLYCORE_ENABLE_FUZZING "Enable fuzzing support" OFF) +option(WALLYCORE_ENABLE_ADDRESS_SANITIZER "Enable address sanitizer" OFF) +option(WALLYCORE_ENABLE_UB_SANITIZER "Enable undefined behavior sanitizer" OFF) + +if(WALLYCORE_ENABLE_FUZZING) + add_compile_options(-fsanitize=fuzzer-no-link) +endif() + +if(WALLYCORE_ENABLE_ADDRESS_SANITIZER) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +if(WALLYCORE_ENABLE_UB_SANITIZER) + add_compile_options(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) +endif() include(cmake/utils.cmake) generate_config_file() @@ -42,6 +59,20 @@ add_subdirectory(./src/secp256k1/) add_subdirectory(./src) +if(WALLYCORE_ENABLE_FUZZING) + add_executable(fuzz_psbt_from_bytes fuzz/fuzz_psbt_from_bytes.c) + target_include_directories(fuzz_psbt_from_bytes PRIVATE include) + target_link_libraries(fuzz_psbt_from_bytes PRIVATE wallycore) + target_link_options(fuzz_psbt_from_bytes PRIVATE -fsanitize=fuzzer) + set_target_properties(fuzz_psbt_from_bytes PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/fuzz) + + add_executable(fuzz_tx_from_bytes fuzz/fuzz_tx_from_bytes.c) + target_include_directories(fuzz_tx_from_bytes PRIVATE include) + target_link_libraries(fuzz_tx_from_bytes PRIVATE wallycore) + target_link_options(fuzz_tx_from_bytes PRIVATE -fsanitize=fuzzer) + set_target_properties(fuzz_tx_from_bytes PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/fuzz) +endif() + if(NOT WALLYCORE_ENABLE_TESTS) return() endif() diff --git a/configure.ac b/configure.ac index dc66e6f19..57caee518 100644 --- a/configure.ac +++ b/configure.ac @@ -91,7 +91,17 @@ AC_ARG_ENABLE(secp256k1-tests, AC_ARG_ENABLE(asm, AS_HELP_STRING([--enable-asm],[enable assembly language implementations (default: yes)]), [asm=$enableval], [asm=yes]) +AC_ARG_ENABLE(fuzzing, + AS_HELP_STRING([--enable-fuzzing],[enable fuzzing support (default: no)]), + [fuzzing=$enableval], [fuzzing=no]) +AC_ARG_ENABLE(address-sanitizer, + AS_HELP_STRING([--enable-address-sanitizer],[enable address sanitizer (default: no)]), + [address_sanitizer=$enableval], [address_sanitizer=no]) +AC_ARG_ENABLE(ub-sanitizer, + AS_HELP_STRING([--enable-ub-sanitizer],[enable undefined behavior sanitizer (default: no)]), + [ub_sanitizer=$enableval], [ub_sanitizer=no]) AM_CONDITIONAL([RUN_TESTS], [test "x$tests" = "xyes"]) +AM_CONDITIONAL([BUILD_FUZZ], [test "x$fuzzing" = "xyes"]) AM_CONDITIONAL([BUILD_ELEMENTS], [test "x$elements" = "xyes"]) AM_CONDITIONAL([WALLY_ABI_NO_ELEMENTS], [test "x$elements_abi" = "xno"]) AM_CONDITIONAL([BUILD_STANDARD_SECP], [test "x$standard_secp" = "xyes"]) @@ -156,6 +166,23 @@ if test "x$builtin_memset" = "xno"; then AX_CHECK_COMPILE_FLAG([-fno-builtin-memset], [AM_CFLAGS="$AM_CFLAGS -fno-builtin"]) fi +if test "x$fuzzing" = "xyes"; then + AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer-no-link], [AM_CFLAGS="$AM_CFLAGS -fsanitize=fuzzer-no-link"]) +fi + +if test "x$address_sanitizer" = "xyes"; then + AX_CHECK_COMPILE_FLAG([-fsanitize=address], [AM_CFLAGS="$AM_CFLAGS -fsanitize=address"]) + AX_CHECK_LINK_FLAG([-fsanitize=address], [LDFLAGS="$LDFLAGS -fsanitize=address"]) +fi + +if test "x$ub_sanitizer" = "xyes"; then + AX_CHECK_COMPILE_FLAG([-fsanitize=undefined], [AM_CFLAGS="$AM_CFLAGS -fsanitize=undefined"]) + AX_CHECK_LINK_FLAG([-fsanitize=undefined], [LDFLAGS="$LDFLAGS -fsanitize=undefined"]) + # ubsan complains about unaligned reads/writes even though they are legal + # on x86 archs. Force alignment adjustment to avoid false positives. + AX_CHECK_COMPILE_FLAG([-DAVOID_UNALIGNED_ACCESS=1], [AM_CFLAGS="$AM_CFLAGS -DAVOID_UNALIGNED_ACCESS=1"]) +fi + # -flax-vector-conversions is needed for our arm assembly AX_CHECK_COMPILE_FLAG([-flax-vector-conversions], [AM_CFLAGS="$AM_CFLAGS -flax-vector-conversions"]) AX_CHECK_COMPILE_FLAG([-fno-strict-aliasing], [NOALIAS_CFLAGS="-fno-strict-aliasing"]) @@ -420,6 +447,10 @@ AC_CONFIG_FILES([ src/wallycore.pc ]) +if test "x$fuzzing" = "xyes"; then + AC_CONFIG_FILES([fuzz/Makefile]) +fi + secp_asm="--with-asm=auto" if test "x$asm" = "xno"; then secp_asm="--with-asm=no" diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am new file mode 100644 index 000000000..78de1631c --- /dev/null +++ b/fuzz/Makefile.am @@ -0,0 +1,11 @@ +noinst_PROGRAMS = fuzz_psbt_from_bytes fuzz_tx_from_bytes + +fuzz_psbt_from_bytes_SOURCES = fuzz_psbt_from_bytes.c +fuzz_psbt_from_bytes_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +fuzz_psbt_from_bytes_LDFLAGS = -fsanitize=fuzzer +fuzz_psbt_from_bytes_LDADD = $(top_builddir)/src/libwallycore.la + +fuzz_tx_from_bytes_SOURCES = fuzz_tx_from_bytes.c +fuzz_tx_from_bytes_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS) +fuzz_tx_from_bytes_LDFLAGS = -fsanitize=fuzzer +fuzz_tx_from_bytes_LDADD = $(top_builddir)/src/libwallycore.la diff --git a/fuzz/fuzz_psbt_from_bytes.c b/fuzz/fuzz_psbt_from_bytes.c new file mode 100644 index 000000000..d08420425 --- /dev/null +++ b/fuzz/fuzz_psbt_from_bytes.c @@ -0,0 +1,36 @@ +#include + +static void test_fuzz_psbt_from_bytes(const uint8_t *data, size_t size, uint32_t flags) +{ + struct wally_psbt *psbt = NULL; + int ret; + + ret = wally_psbt_from_bytes(data, size, flags, &psbt); + if (psbt) { + if (ret == WALLY_OK && flags == WALLY_PSBT_PARSE_FLAG_STRICT) { + /* Parsing succeeded: try to serialize it back to bytes */ + size_t len = 0, written = 0; + ret = wally_psbt_get_length(psbt, 0, &len); + if (ret == WALLY_OK && len) { + unsigned char *bytes = malloc(len); + if (bytes) { + wally_psbt_to_bytes(psbt, 0, bytes, len, &written); + free(bytes); + } + } + } + wally_psbt_free(psbt); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + /* Test strict parsing */ + test_fuzz_psbt_from_bytes(data, size, WALLY_PSBT_PARSE_FLAG_STRICT); + /* Test loose parsing */ + test_fuzz_psbt_from_bytes(data, size, WALLY_PSBT_PARSE_FLAG_LOOSE); + /* Test default flags (no flags) */ + test_fuzz_psbt_from_bytes(data, size, 0); + + return 0; +} diff --git a/fuzz/fuzz_tx_from_bytes.c b/fuzz/fuzz_tx_from_bytes.c new file mode 100644 index 000000000..671fd14cc --- /dev/null +++ b/fuzz/fuzz_tx_from_bytes.c @@ -0,0 +1,43 @@ +#include + +static void test_tx_from_bytes(const uint8_t *data, size_t size, uint32_t flags) +{ + struct wally_tx *tx = NULL; + int ret; + + ret = wally_tx_from_bytes(data, size, flags, &tx); + if (tx) { + if (ret == WALLY_OK && + (flags == WALLY_TX_FLAG_USE_WITNESS || + flags == (WALLY_TX_FLAG_USE_WITNESS|WALLY_TX_FLAG_USE_ELEMENTS))) { + /* Parsing succeeded: try to serialize it back to bytes */ + size_t len = 0, written = 0; + ret = wally_tx_get_length(tx, flags, &len); + if (ret == WALLY_OK && len) { + unsigned char *bytes = malloc(len); + if (bytes) { + wally_tx_to_bytes(tx, flags, bytes, len, &written); + free(bytes); + } + } + } + wally_tx_free(tx); + } +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + static const uint32_t flags[6] = { + 0, + WALLY_TX_FLAG_USE_WITNESS, + WALLY_TX_FLAG_USE_ELEMENTS, + WALLY_TX_FLAG_USE_WITNESS | WALLY_TX_FLAG_USE_ELEMENTS, + WALLY_TX_FLAG_ALLOW_PARTIAL, + WALLY_TX_FLAG_PRE_BIP144 + }; + + for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) + test_tx_from_bytes(data, size, flags[i]); + + return 0; +} diff --git a/src/ccan_config.h b/src/ccan_config.h index 87ea13607..c660937b6 100644 --- a/src/ccan_config.h +++ b/src/ccan_config.h @@ -32,11 +32,14 @@ #define HAVE_BSWAP_64 0 #endif -#if defined(HAVE_UNALIGNED_ACCESS) && defined(__arm__) -/* arm unaligned access is incomplete, in that e.g. byte swap instructions - * can fault on unaligned addresses where a normal load/store would be fine. - * Since the compiler can optimise some of our accesses into operations like - * byte swaps, treat this platform as though it doesn't have unaligned access. +#if defined(AVOID_UNALIGNED_ACCESS) || (defined(HAVE_UNALIGNED_ACCESS) && defined(__arm__)) +/* Disable unaligned access when: + * 1) Explicitly asked to via AVOID_UNALIGNED_ACCESS (e.g. for ubsan builds) + * 2) For arm builds where unaligned access is incomplete, in that e.g. byte + * swap instructions can fault on unaligned addresses where a normal load/store + * would be fine. Since the compiler can optimise some of our accesses into + * operations like byte swaps, treat this platform as though it doesn't have + * unaligned access. */ #undef HAVE_UNALIGNED_ACCESS #define HAVE_UNALIGNED_ACCESS 0 diff --git a/src/psbt.c b/src/psbt.c index 74b46a549..5806aab88 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -868,6 +868,15 @@ static int psbt_input_free(struct wally_psbt_input *input, bool free_parent) return WALLY_OK; } +static void psbt_inputs_free(struct wally_psbt_input *inputs, size_t num_inputs) +{ + if (inputs) { + for (size_t i = 0; i < num_inputs; ++i) + psbt_input_free(&inputs[i], false); + wally_free(inputs); + } +} + MAP_INNER_FIELD(output, redeem_script, PSBT_OUT_REDEEM_SCRIPT, psbt_fields) MAP_INNER_FIELD(output, witness_script, PSBT_OUT_WITNESS_SCRIPT, psbt_fields) MAP_INNER_FIELD(output, taproot_internal_key, PSBT_OUT_TAP_INTERNAL_KEY, psbt_fields) @@ -1149,6 +1158,15 @@ static int psbt_output_free(struct wally_psbt_output *output, bool free_parent) return WALLY_OK; } +static void psbt_outputs_free(struct wally_psbt_output *outputs, size_t num_outputs) +{ + if (outputs) { + for (size_t i = 0; i < num_outputs; ++i) + psbt_output_free(&outputs[i], false); + wally_free(outputs); + } +} + static int psbt_init(uint32_t version, size_t num_inputs, size_t num_outputs, size_t num_unknowns, uint32_t flags, size_t max_num_inputs, size_t max_num_outputs, @@ -1291,17 +1309,11 @@ static void psbt_claim_allocated_inputs(struct wally_psbt *psbt, size_t num_inpu int wally_psbt_free(struct wally_psbt *psbt) { - size_t i; if (psbt) { wally_tx_free(psbt->tx); - for (i = 0; i < psbt->num_inputs; ++i) - psbt_input_free(&psbt->inputs[i], false); + psbt_inputs_free(psbt->inputs, psbt->num_inputs); + psbt_outputs_free(psbt->outputs, psbt->num_outputs); - wally_free(psbt->inputs); - for (i = 0; i < psbt->num_outputs; ++i) - psbt_output_free(&psbt->outputs[i], false); - - wally_free(psbt->outputs); wally_map_clear(&psbt->unknowns); wally_map_clear(&psbt->global_xpubs); #ifdef BUILD_ELEMENTS @@ -1591,31 +1603,31 @@ static int psbt_set_global_tx(struct wally_psbt *psbt, struct wally_tx *tx, bool if (psbt->inputs_allocation_len < tx->num_inputs) { new_inputs = wally_malloc(tx->num_inputs * sizeof(struct wally_psbt_input)); - for (i = 0; i < tx->num_inputs; ++i) + for (i = 0; new_inputs && i < tx->num_inputs; ++i) psbt_input_init(&new_inputs[i]); } if (psbt->outputs_allocation_len < tx->num_outputs) { new_outputs = wally_malloc(tx->num_outputs * sizeof(struct wally_psbt_output)); - for (i = 0; i < tx->num_outputs; ++i) + for (i = 0; new_outputs && i < tx->num_outputs; ++i) psbt_output_init(&new_outputs[i]); } if ((psbt->inputs_allocation_len < tx->num_inputs && !new_inputs) || (psbt->outputs_allocation_len < tx->num_outputs && !new_outputs)) { - wally_free(new_inputs); - wally_free(new_outputs); + psbt_inputs_free(new_inputs, tx->num_inputs); + psbt_outputs_free(new_outputs, tx->num_outputs); wally_tx_free(new_tx); return WALLY_ENOMEM; } if (new_inputs) { - wally_free(psbt->inputs); + psbt_inputs_free(psbt->inputs, psbt->num_inputs); psbt->inputs = new_inputs; psbt->inputs_allocation_len = tx->num_inputs; } if (new_outputs) { - wally_free(psbt->outputs); + psbt_outputs_free(psbt->outputs, psbt->num_outputs); psbt->outputs = new_outputs; psbt->outputs_allocation_len = tx->num_outputs; } diff --git a/src/transaction.c b/src/transaction.c index 4d191f423..0c3702100 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -2200,7 +2200,7 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, uint32_t flags, size_t *num_inputs, size_t *num_outputs, bool *expect_witnesses) { - const unsigned char *p = bytes, *end = bytes + bytes_len; + const unsigned char *p, *end; uint64_t v, num_witnesses; size_t i, j; struct wally_tx tmp_tx; @@ -2214,10 +2214,11 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, *expect_witnesses = false; if (!bytes || bytes_len < sizeof(uint32_t) + 2 || (flags & ~WALLY_TX_ALL_FLAGS) || - !num_inputs || !num_outputs || !expect_witnesses || p > end) + !num_inputs || !num_outputs || !expect_witnesses) return WALLY_EINVAL; - p += uint32_from_le_bytes(p, &tmp_tx.version); + end = bytes + bytes_len; + p = bytes + uint32_from_le_bytes(bytes, &tmp_tx.version); if (is_elements) { if (flags & WALLY_TX_FLAG_PRE_BIP144) @@ -2242,8 +2243,9 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, #define ensure_varbuff(dst) ensure_varint((dst)); \ ensure_n(*dst) -#define ensure_commitment(dst, explicit_siz, prefix_a, prefix_b) \ - switch (*dst) { \ +#define ensure_commitment(explicit_siz, prefix_a, prefix_b) \ + ensure_n(sizeof(uint8_t)); \ + switch (*p) { \ case WALLY_TX_ASSET_CT_EMPTY_PREFIX: \ ensure_n(sizeof(uint8_t)); \ p++; \ @@ -2261,14 +2263,14 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, return WALLY_EINVAL; \ } -#define ensure_committed_value(dst) \ - ensure_commitment(dst, WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN, WALLY_TX_ASSET_CT_VALUE_PREFIX_A, WALLY_TX_ASSET_CT_VALUE_PREFIX_B) +#define ensure_committed_value() \ + ensure_commitment(WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN, WALLY_TX_ASSET_CT_VALUE_PREFIX_A, WALLY_TX_ASSET_CT_VALUE_PREFIX_B) -#define ensure_committed_asset(dst) \ - ensure_commitment(dst, WALLY_TX_ASSET_CT_ASSET_LEN, WALLY_TX_ASSET_CT_ASSET_PREFIX_A, WALLY_TX_ASSET_CT_ASSET_PREFIX_B) +#define ensure_committed_asset() \ + ensure_commitment(WALLY_TX_ASSET_CT_ASSET_LEN, WALLY_TX_ASSET_CT_ASSET_PREFIX_A, WALLY_TX_ASSET_CT_ASSET_PREFIX_B) -#define ensure_committed_nonce(dst) \ - ensure_commitment(dst, WALLY_TX_ASSET_CT_NONCE_LEN, WALLY_TX_ASSET_CT_NONCE_PREFIX_A, WALLY_TX_ASSET_CT_NONCE_PREFIX_B) +#define ensure_committed_nonce() \ + ensure_commitment(WALLY_TX_ASSET_CT_NONCE_LEN, WALLY_TX_ASSET_CT_NONCE_PREFIX_A, WALLY_TX_ASSET_CT_NONCE_PREFIX_B) ensure_varint(&v); *num_inputs = v; @@ -2290,8 +2292,8 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, if (expect_issuance) { ensure_n(2 * SHA256_LEN); p += 2 * SHA256_LEN; - ensure_committed_value(p); /* issuance amount */ - ensure_committed_value(p); /* inflation keys */ + ensure_committed_value(); /* issuance amount */ + ensure_committed_value(); /* inflation keys */ } } @@ -2300,9 +2302,9 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, for (i = 0; i < *num_outputs; ++i) { if (is_elements) { - ensure_committed_asset(p); - ensure_committed_value(p); - ensure_committed_nonce(p); + ensure_committed_asset(); + ensure_committed_value(); + ensure_committed_nonce(); } else { ensure_n(sizeof(uint64_t)); p += sizeof(uint64_t); diff --git a/src/wasm_package/package.json b/src/wasm_package/package.json index e2554e0e9..7b61274cc 100644 --- a/src/wasm_package/package.json +++ b/src/wasm_package/package.json @@ -18,7 +18,7 @@ "devDependencies": { "buffer": "^6.0.3", "test": "^3.2.1", - "webpack": "^5.75.0", + "webpack": "^5.105.0", "webpack-cli": "^5.0.0" }, "browser": {