fuzz: add support for fuzzing tx parsing
What changed, and why it matters
This commit only adds new software testing tools (fuzzing harnesses) for parsing Bitcoin-style transactions and PSBTs. It does not change the actual transaction/PSBT parsing library code, so it cannot by itself introduce a security vulnerability or fix one. It is a test-infrastructure change.
No security action required. Treat as routine test infrastructure. If fuzzing reveals crashes, those should be triaged separately with follow-up commits.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff renames the existing PSBT fuzz target from fuzz_psbt to fuzz_psbt_from_bytes and adds a new fuzz target fuzz_tx_from_bytes that exercises wally_tx_from_bytes with various flag combinations. Both targets parse arbitrary input, and on successful strict/witness parsing attempt a round-trip serialization. No library source code under src/ is modified, and no parsing logic is changed. The commit is purely additive test/fuzz infrastructure.
Changed components
fuzz/fuzz_psbt_from_bytes.cfuzz/fuzz_tx_from_bytes.cfuzz/Makefile.am_CMakeLists.txtInspect captured patch +100 / −48
diff --git a/_CMakeLists.txt b/_CMakeLists.txt
index 9333779..170f7ef 100644
--- a/_CMakeLists.txt
+++ b/_CMakeLists.txt
@@ -60,11 +60,17 @@ add_subdirectory(./src/secp256k1/)
add_subdirectory(./src)
if(WALLYCORE_ENABLE_FUZZING)
- add_executable(fuzz_psbt fuzz/fuzz_psbt.c)
- target_include_directories(fuzz_psbt PRIVATE include)
- target_link_libraries(fuzz_psbt PRIVATE wallycore)
- target_link_options(fuzz_psbt PRIVATE -fsanitize=fuzzer)
- set_target_properties(fuzz_psbt PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/fuzz)
+ 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)
diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am
index 79cab0b..78de163 100644
--- a/fuzz/Makefile.am
+++ b/fuzz/Makefile.am
@@ -1,6 +1,11 @@
-noinst_PROGRAMS = fuzz_psbt
+noinst_PROGRAMS = fuzz_psbt_from_bytes fuzz_tx_from_bytes
-fuzz_psbt_SOURCES = fuzz_psbt.c
-fuzz_psbt_CFLAGS = -I$(top_srcdir)/include $(AM_CFLAGS)
-fuzz_psbt_LDFLAGS = -fsanitize=fuzzer
-fuzz_psbt_LDADD = $(top_builddir)/src/libwallycore.la
+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.c b/fuzz/fuzz_psbt.c
deleted file mode 100644
index 517c2f6..0000000
--- a/fuzz/fuzz_psbt.c
+++ /dev/null
@@ -1,38 +0,0 @@
-#include <wally_psbt.h>
-
-static void test_psbt(const uint8_t *data, size_t size, uint32_t flags)
-{
- struct wally_psbt *psbt = NULL;
- int ret;
-
- /* Test strict parsing */
- 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 */
- 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);
- psbt = NULL;
- }
-}
-
-int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
-{
- /* Test strict parsing */
- test_psbt(data, size, WALLY_PSBT_PARSE_FLAG_STRICT);
- /* Test loose parsing */
- test_psbt(data, size, WALLY_PSBT_PARSE_FLAG_LOOSE);
- /* Test default flags (no flags) */
- test_psbt(data, size, 0);
-
- return 0;
-}
diff --git a/fuzz/fuzz_psbt_from_bytes.c b/fuzz/fuzz_psbt_from_bytes.c
new file mode 100644
index 0000000..d084204
--- /dev/null
+++ b/fuzz/fuzz_psbt_from_bytes.c
@@ -0,0 +1,36 @@
+#include <wally_psbt.h>
+
+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 0000000..671fd14
--- /dev/null
+++ b/fuzz/fuzz_tx_from_bytes.c
@@ -0,0 +1,43 @@
+#include <wally_transaction.h>
+
+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;
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.