upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormtg0001 <106211915+mtg0001@users.noreply.github.com>2026-08-04 15:31:55 -0300
committerGitHub <noreply@github.com>2026-08-04 15:31:55 -0300
commitb3a5a858e536acf65e6c6761293626628f2227bf (patch)
tree881dbe44ac6c0ea28f5a65cbfd9b835ba73ea648
parent173b4649e90f4f5ba49e92569eb063709e647c7d (diff)
fuzz: add P2P message parser harness (#142)
* fuzz: add P2P message parser harness * fuzz: avoid duplicate legacy header includes * fuzz: seed the corpus with our network magic, not Bitcoin's The two seed inputs began f5 be b6 d9, which is Bitcoin's message start. Ours is bf 20 5c fd. CMessageHeader::IsValid rejects on that field first, so both seeds died at the first check and the smoke run exercised the reject path and nothing else. libFuzzer would have to mutate its way onto four exact bytes before reaching any parser, which does not happen in 128 runs. The job would have gone green while covering none of what it was added to cover. --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra> Co-authored-by: Bitflash-sh <btf01@proton.me>
-rw-r--r--.github/workflows/ci.yml3
-rw-r--r--Makefile7
-rw-r--r--src/Makefile34
-rw-r--r--src/fuzz_net_message.cpp32
4 files changed, 73 insertions, 3 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 985ba8a..9efd66f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,6 +25,9 @@ jobs:
25 - name: Run Linux selftests 25 - name: Run Linux selftests
26 run: make tests 26 run: make tests
27 27
28 - name: Run P2P message fuzz smoke
29 run: make fuzz-net-message-smoke
30
28 windows-ucrt64: 31 windows-ucrt64:
29 name: Windows UCRT64 selftests 32 name: Windows UCRT64 selftests
30 runs-on: windows-latest 33 runs-on: windows-latest
diff --git a/Makefile b/Makefile
index 0788b61..736f5d8 100644
--- a/Makefile
+++ b/Makefile
@@ -42,7 +42,7 @@ deps-apt:
42 @if command -v apt-get >/dev/null 2>&1; then \ 42 @if command -v apt-get >/dev/null 2>&1; then \
43 $(SUDO) apt-get install -y build-essential cmake git pkg-config autoconf \ 43 $(SUDO) apt-get install -y build-essential cmake git pkg-config autoconf \
44 libtool libssl-dev libdb5.3++-dev libsodium-dev nlohmann-json3-dev \ 44 libtool libssl-dev libdb5.3++-dev libsodium-dev nlohmann-json3-dev \
45 libboost-system-dev libglfw3-dev libgl-dev python3-pil; \ 45 libboost-system-dev libglfw3-dev libgl-dev python3-pil clang; \
46 else \ 46 else \
47 echo "Not an apt system — install: g++ cmake git autoconf libtool"; \ 47 echo "Not an apt system — install: g++ cmake git autoconf libtool"; \
48 echo " libssl libdb++ libsodium nlohmann-json boost glfw3 opengl python3-pil"; \ 48 echo " libssl libdb++ libsodium nlohmann-json boost glfw3 opengl python3-pil"; \
@@ -191,6 +191,9 @@ clean:
191tests: 191tests:
192 $(MAKE) -C src -f Makefile tests 192 $(MAKE) -C src -f Makefile tests
193 193
194fuzz-net-message-smoke: deps-linux
195 $(MAKE) -C src -f Makefile fuzz-net-message-smoke
196
194checksums: 197checksums:
195 ./scripts/make-release-checksums.sh 198 ./scripts/make-release-checksums.sh
196 199
@@ -201,5 +204,5 @@ verify-release:
201 ./scripts/verify-release.sh $(if $(TAG),$(TAG),latest) 204 ./scripts/verify-release.sh $(if $(TAG),$(TAG),latest)
202 205
203.PHONY: linux windows clean appimage \ 206.PHONY: linux windows clean appimage \
204 tests checksums sign-checksums verify-release \ 207 tests fuzz-net-message-smoke checksums sign-checksums verify-release \
205 deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx 208 deps-linux deps-windows deps-apt deps-secp256k1 deps-randomx
diff --git a/src/Makefile b/src/Makefile
index 3577dd6..ac0226e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2,6 +2,7 @@
2# Called by: make linux (root Makefile) 2# Called by: make linux (root Makefile)
3 3
4CXX ?= g++ 4CXX ?= g++
5FUZZ_CXX ?= clang++
5 6
6SECP_INC ?= /usr/local/include 7SECP_INC ?= /usr/local/include
7SECP_LIB ?= /usr/local/lib 8SECP_LIB ?= /usr/local/lib
@@ -33,6 +34,9 @@ CXXFLAGS = -std=gnu++14 -O2 $(WARNFLAGS) -fpermissive -pthread \
33 -I$(SECP_INC) -I$(RANDOMX_DIR)/src \ 34 -I$(SECP_INC) -I$(RANDOMX_DIR)/src \
34 -I/usr/include/nlohmann \ 35 -I/usr/include/nlohmann \
35 -I$(IMGUI_DIR) 36 -I$(IMGUI_DIR)
37FUZZ_CXXFLAGS = $(filter-out -O2,$(CXXFLAGS)) -O1 -g -fno-omit-frame-pointer \
38 -fsanitize=fuzzer-no-link,address,undefined
39FUZZ_LDFLAGS = -fsanitize=fuzzer,address,undefined
36 40
37LIBS = -L$(SECP_LIB) -lsecp256k1 \ 41LIBS = -L$(SECP_LIB) -lsecp256k1 \
38 -L$(RANDOMX_DIR)/build -lrandomx \ 42 -L$(RANDOMX_DIR)/build -lrandomx \
@@ -117,6 +121,9 @@ NODE_LIBS = -L$(SECP_LIB) -lsecp256k1 \
117NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \ 121NODE_ONLY_OBJS = $(patsubst obj/%,obj-node/%,$(NODE_OBJS)) \
118 obj-node/main_gui.o obj-node/headless.o 122 obj-node/main_gui.o obj-node/headless.o
119 123
124FUZZ_OBJS = $(patsubst obj/%,obj-fuzz/%,$(NODE_OBJS)) \
125 obj-fuzz/fuzz_net_message.o
126
120obj-node/%.o: %.cpp $(HEADERS) 127obj-node/%.o: %.cpp $(HEADERS)
121 @mkdir -p obj-node 128 @mkdir -p obj-node
122 $(CXX) -c $(CXXFLAGS) -DBITFLASH_NO_GUI -o $@ $< 129 $(CXX) -c $(CXXFLAGS) -DBITFLASH_NO_GUI -o $@ $<
@@ -129,8 +136,31 @@ bitflash-node: $(NODE_ONLY_OBJS)
129 $(CXX) $(CXXFLAGS) -o $@ $^ $(NODE_LIBS) 136 $(CXX) $(CXXFLAGS) -o $@ $^ $(NODE_LIBS)
130 @echo "Built: bitflash-node (headless, no libGL needed)" 137 @echo "Built: bitflash-node (headless, no libGL needed)"
131 138
139obj-fuzz/%.o: %.cpp $(HEADERS)
140 @mkdir -p obj-fuzz
141 $(FUZZ_CXX) -c $(FUZZ_CXXFLAGS) -DBITFLASH_FUZZ -o $@ $<
142
143obj-fuzz/sha.o: sha.cpp sha.h
144 @mkdir -p obj-fuzz
145 $(FUZZ_CXX) -c $(FUZZ_CXXFLAGS) -DBITFLASH_FUZZ -O1 -o $@ $<
146
147obj-fuzz/fuzz_net_message.o: fuzz_net_message.cpp $(HEADERS)
148 @mkdir -p obj-fuzz
149 $(FUZZ_CXX) -c $(FUZZ_CXXFLAGS) -DBITFLASH_FUZZ -o $@ $<
150
151fuzz-net-message: $(FUZZ_OBJS)
152 $(FUZZ_CXX) $(filter-out -fsanitize=fuzzer-no-link%,$(FUZZ_CXXFLAGS)) \
153 $(FUZZ_LDFLAGS) -o $@ $^ $(NODE_LIBS)
154 @echo "Built: fuzz-net-message"
155
156fuzz-net-message-smoke: fuzz-net-message
157 @mkdir -p ../work/fuzz-corpus/net-message
158 @printf '\277\040\134\375ping\000\000\000\000\000\000\000\000\000\000\000\000' > ../work/fuzz-corpus/net-message/ping-header
159 @printf '\277\040\134\375version\000\000\000\000\000\000\000\000\000' > ../work/fuzz-corpus/net-message/version-header
160 ./fuzz-net-message -runs=128 ../work/fuzz-corpus/net-message
161
132clean: 162clean:
133 rm -rf obj obj-node bitflash bitflash-node test_bip32 163 rm -rf obj obj-node obj-fuzz bitflash bitflash-node fuzz-net-message test_bip32
134 164
135test_bip32: test_bip32.cpp bip32.cpp bip32.h bip39_english.h 165test_bip32: test_bip32.cpp bip32.cpp bip32.h bip39_english.h
136 $(CXX) $(CXXFLAGS) -o $@ test_bip32.cpp bip32.cpp -lssl -lcrypto 166 $(CXX) $(CXXFLAGS) -o $@ test_bip32.cpp bip32.cpp -lssl -lcrypto
@@ -144,3 +174,5 @@ tests: test_bip32 bitflash-node
144 ./bitflash-node -selftest=wallet-encrypt 174 ./bitflash-node -selftest=wallet-encrypt
145 ./bitflash-node -selftest=net-message 175 ./bitflash-node -selftest=net-message
146 ./bitflash-node -selftest=consensus-limits 176 ./bitflash-node -selftest=consensus-limits
177
178.PHONY: tests fuzz-net-message-smoke
diff --git a/src/fuzz_net_message.cpp b/src/fuzz_net_message.cpp
new file mode 100644
index 0000000..1104d90
--- /dev/null
+++ b/src/fuzz_net_message.cpp
@@ -0,0 +1,32 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// libFuzzer harness for the P2P message envelope parser. It feeds arbitrary
5// receive-buffer bytes into ProcessMessages() through a dummy CNode, without
6// opening sockets or starting node threads.
7
8#include "headers_core.h"
9
10#include <stdint.h>
11#include <stddef.h>
12
13// Stand-ins normally supplied by the GUI/headless entry objects.
14map<string,string> mapAddressBook;
15bool gPoolServerRunning = false;
16void MainFrameRepaint() {}
17
18extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
19{
20 // Keep this first target focused on parser behavior, not multi-megabyte
21 // allocation pressure. The protocol size guards have their own selftest.
22 if (size > 4096)
23 return 0;
24
25 CNode node(INVALID_SOCKET, CAddress("127.0.0.1"));
26 node.nVersion = 0; // exercise pre-version gating plus the version parser
27 if (size > 0)
28 node.vRecv.write((const char*)data, (int)size);
29
30 ProcessMessages(&node);
31 return 0;
32}