Skip to content

Commit adab97d

Browse files
committed
Add c-lightning module for differential fuzzing
This commit introduces a c-lightning module to the bitcoinfuzz project, enabling differential fuzzing of lightning invoices. Key changes: Module Integration: - Add `modules/clightning` directory with module implementation and wrapper - Compile c-lightning as a library and link it to the clightning module.cpp file Build System: - Update GitHub workflow to include c-lightning module in fuzzing - Modify Makefile to handle c-lightning dependencies Differential Fuzzing: - Enhance driver to compare invoice deserialization across implementations - Enable detection of discrepancies between c-lightning and other modules Dependencies: - Add c-lightning as a submodule in `external/lightning` - Configure necessary build dependencies
1 parent c63a9b9 commit adab97d

12 files changed

Lines changed: 248 additions & 21 deletions

File tree

.github/workflows/workflow.yml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ jobs:
2626
uses: actions/setup-go@v5
2727
with:
2828
go-version: "stable"
29-
29+
3030
- name: Install .NET SDK
3131
uses: actions/setup-dotnet@v4
3232
with:
33-
dotnet-version: '8.0.x'
34-
33+
dotnet-version: "8.0.x"
34+
3535
- name: Setup Python and install embit
3636
uses: actions/setup-python@v5
3737
with:
38-
python-version: '3.11'
38+
python-version: "3.11"
3939
- run: |
4040
python -m pip install --upgrade pip
4141
pip install -r modules/embit/embit_lib/requirements.txt
42+
pip install mako
4243
4344
- name: Install LLVM and Clang
4445
run: |
@@ -52,7 +53,7 @@ jobs:
5253
- name: Install Boost
5354
run: |
5455
sudo apt install libboost-all-dev
55-
56+
5657
- name: Setup common environment
5758
run: |
5859
export CC=/usr/bin/clang
@@ -87,17 +88,17 @@ jobs:
8788
timeout-minutes: 5
8889
run: |
8990
cd modules/btcd && make
90-
91+
9192
- name: Build - embit
9293
timeout-minutes: 5
9394
run: |
9495
cd modules/embit && make
95-
96+
9697
- name: Build - lnd
9798
timeout-minutes: 5
9899
run: |
99100
cd modules/lnd && make
100-
101+
101102
- name: Build - ldk
102103
timeout-minutes: 5
103104
run: |
@@ -109,6 +110,11 @@ jobs:
109110
run: |
110111
cd modules/nlightning && make
111112
113+
- name: Build - clightning
114+
timeout-minutes: 5
115+
run: |
116+
cd modules/clightning && make
117+
112118
- name: Test - script
113119
timeout-minutes: 5
114120
run: |
@@ -151,10 +157,10 @@ jobs:
151157
152158
- name: Test - deserialize_invoice
153159
run: |
154-
export CXXFLAGS="-DLDK -DLND -DCUSTOM_MUTATOR_BOLT11"
160+
export CXXFLAGS="-DLDK -DLND -DCLIGHTNING -DCUSTOM_MUTATOR_BOLT11"
155161
make
156162
ASAN_OPTIONS=detect_leaks=0 FUZZ=deserialize_invoice ./bitcoinfuzz -runs=100
157-
163+
158164
- name: Test - address_parse
159165
run: |
160166
export CXXFLAGS="-DBITCOIN_CORE -DRUST_BITCOIN"

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "external/lightning"]
2+
path = external/lightning
3+
url = https://github.com/ElementsProject/lightning.git
4+
ignore = all

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ ifeq ($(UNAME_S), Darwin)
1212
LDFLAGS = -framework CoreFoundation -Wl,-ld_classic
1313
endif
1414

15+
SODIUM_LDLIBS = $(shell pkg-config --silence-errors --libs libsodium 2>/dev/null)
16+
1517
bitcoinfuzz: main.cpp driver.o include/bitcoinfuzz/basemodule.o
16-
$(CXX) $(CXXFLAGS) $(LDFLAGS) main.cpp $(MODULES) driver.o include/bitcoinfuzz/basemodule.o -o bitcoinfuzz $(PYTHON_LDFLAGS)
18+
$(CXX) $(CXXFLAGS) $(LDFLAGS) main.cpp $(MODULES) driver.o include/bitcoinfuzz/basemodule.o -o bitcoinfuzz $(PYTHON_LDFLAGS) $(SODIUM_LDLIBS)
1719

1820
driver.o: driver.cpp driver.h
1921
$(CXX) $(CXXFLAGS) -c driver.cpp -o driver.o
@@ -24,4 +26,4 @@ include/bitcoinfuzz/basemodule.o: include/bitcoinfuzz/basemodule.cpp include/bit
2426
clean:
2527
rm -rf *.o module.a bitcoinfuzz include/bitcoinfuzz/*.o $(MODULES)
2628

27-
.PHONY: all bitcoinfuzz
29+
.PHONY: all bitcoinfuzz

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ Note this project is a WIP and might be not stable.
3030
3131
### boost
3232
33-
To build the bitcoin core module the boost library is required. Minimum version
33+
To build the bitcoin core module the boost library is required. Minimum version
3434
3535
The module uses only libboost-filesystem and libboost-system modules. For ubuntu you can install with:
3636
3737
```
3838
sudo apt install libboost-filesystem-dev libboost-system-dev
3939
```
4040
41-
Or install the complete boost library with
41+
Or install the complete boost library with
4242
```
4343
sudo apt install libboost-all-dev
4444
```
@@ -131,6 +131,16 @@ make
131131
export CXXFLAGS="$CXXFLAGS -DNLIGHTNING"
132132
```
133133

134+
### C-lightning
135+
136+
```bash
137+
pip install mako
138+
git submodule update --init --recursive external/lightning
139+
cd modules/clightning
140+
make
141+
export CXXFLAGS="$CXXFLAGS -DCLIGHTNING"
142+
```
143+
134144
Once the modules are compiled, you can compile bitcoinfuzz and execute it:
135145
```bash
136146
make

driver.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,25 @@ namespace bitcoinfuzz
108108
FuzzedDataProvider provider(buffer.data(), buffer.size());
109109
std::string invoice{provider.ConsumeRemainingBytesAsString()};
110110
std::optional<std::string> last_response{std::nullopt};
111+
std::string last_module_name;
112+
111113
for (auto &module : modules)
112114
{
113115
std::optional<std::string> res{module.second->deserialize_invoice(invoice)};
114116
if (!res.has_value()) continue;
115-
if (last_response.has_value()) assert(*res == *last_response);
117+
if (last_response.has_value()) {
118+
if (*res != *last_response) {
119+
std::cout << "Invoice deserialization failed for " << invoice << std::endl;
120+
std::cout << "Module: " << module.first << std::endl;
121+
std::cout << "Result: " << *res << std::endl;
122+
std::cout << "Module: " << last_module_name << std::endl;
123+
std::cout << "Result: " << *last_response << std::endl;
124+
}
125+
assert(*res == *last_response);
126+
}
127+
116128
last_response = res.value();
129+
last_module_name = module.first;
117130
}
118131
}
119132

external/lightning

Submodule lightning added at 90b5f04

main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@
4040
#include <modules/embit/module.h>
4141
#endif
4242

43+
#ifdef CLIGHTNING
44+
#include <modules/clightning/module.h>
45+
#endif
46+
4347
#ifdef CUSTOM_MUTATOR_BOLT11
4448
#include <modules/bolt11mutator/bech32.h>
4549
#endif
4650

51+
4752
std::shared_ptr<bitcoinfuzz::Driver> driver = nullptr;
4853

4954
#ifdef CUSTOM_MUTATOR_BOLT11
@@ -351,6 +356,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
351356
#ifdef EMBIT
352357
driver->LoadModule(std::make_shared<bitcoinfuzz::module::Embit>());
353358
#endif
359+
#ifdef CLIGHTNING
360+
driver->LoadModule(std::make_shared<bitcoinfuzz::module::CLightning>());
361+
#endif
354362

355363
driver->Run(Data, Size, target);
356364
return 0;

modules/clightning/Makefile

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
CXX = clang++
2+
CXXFLAGS += -Wall -Wextra -std=c++20 -fPIC
3+
4+
INCLUDE_DIRS += -I.
5+
INCLUDE_DIRS += -I../../include
6+
INCLUDE_DIRS += -I../../external/lightning
7+
INCLUDE_DIRS += -I../../external/lightning/ccan
8+
INCLUDE_DIRS += -I../../external/lightning/external/libwally-core/src/secp256k1/include
9+
10+
CXXFLAGS += $(INCLUDE_DIRS)
11+
12+
ROOT_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/../..)
13+
LIGHTNING_DIR := $(ROOT_DIR)/external/lightning
14+
CLIGHTNING_MAKEFILE := ./Makefile-clightning
15+
LIBCLIGHTNING_WRAPPER := ./libcln.a
16+
17+
all: module.a
18+
19+
$(LIGHTNING_DIR)/config.vars:
20+
@echo "Running ./configure in $(LIGHTNING_DIR)..."
21+
cd $(LIGHTNING_DIR) && ./configure --enable-address-sanitizer --enable-fuzzing --disable-valgrind CC=clang
22+
23+
24+
libcln.a: $(LIGHTNING_DIR)/config.vars
25+
@echo "Appending Makefile-clightning to $(LIGHTNING_DIR)/Makefile if not already present..."
26+
@marker="$$(head -n 2 $(CLIGHTNING_MAKEFILE) | tail -n 1)"; \
27+
if ! grep -Fxq "$$marker" $(LIGHTNING_DIR)/Makefile; then \
28+
echo "Marker not found, appending..."; \
29+
awk '{ print }' $(CLIGHTNING_MAKEFILE) >> $(LIGHTNING_DIR)/Makefile; \
30+
else \
31+
echo "Makefile already includes clightning makefile content."; \
32+
fi
33+
34+
@echo "Building lightning in $(LIGHTNING_DIR)..."
35+
cd $(LIGHTNING_DIR) && make libcln.a
36+
37+
@echo "Copying static library in $(LIGHTNING_DIR)..."
38+
cp $(LIGHTNING_DIR)/libcln.a $(LIBCLIGHTNING_WRAPPER)
39+
40+
module.a: libcln.a module.o
41+
bash ../../merge.sh module.a libcln.a module.o
42+
43+
module.o: module.cpp module.h
44+
$(CXX) $(CXXFLAGS) -fPIC -c module.cpp -o module.o
45+
46+
clean:
47+
rm -f *.o *.a
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
libcln.a: libccan.a $(BITCOIN_OBJS) $(COMMON_OBJS) $(WIRE_OBJS) $(EXTERNAL_LIBS) $(CCAN_OBJS)
3+
@$(call VERBOSE, "ar $@", $(AR) r $@ $(BITCOIN_OBJS) $(COMMON_OBJS) $(WIRE_OBJS) $(CCAN_OBJS))
4+
@echo "Extracting external libraries..."
5+
@for lib in ${EXTERNAL_LIBS}; do \
6+
echo "Processing $$lib"; \
7+
if [ -f "$$lib" ]; then \
8+
tmpdir=$$(mktemp -d); \
9+
echo "Created temp dir: $$tmpdir"; \
10+
fullpath="$$(pwd)/$$lib"; \
11+
echo "Full path: $$fullpath"; \
12+
(cd $$tmpdir && $(AR) x "$$fullpath" && echo "Extraction completed successfully"); \
13+
if [ -n "$$(ls -A $$tmpdir)" ]; then \
14+
echo "Found object files: $$(ls $$tmpdir)"; \
15+
$(AR) r $@ $$tmpdir/*.o; \
16+
else \
17+
echo "Warning: No object files found in $$lib"; \
18+
fi; \
19+
rm -rf $$tmpdir; \
20+
else \
21+
echo "Warning: Library $$lib not found"; \
22+
fi; \
23+
done

modules/clightning/module.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#define template c_template // avoid C++ keyword conflict just during includes
2+
3+
extern "C" {
4+
#include "common/bolt11.h"
5+
#include "bitcoin/pubkey.h"
6+
#include "common/node_id.h"
7+
#include "common/utils.h"
8+
#include <bitcoin/chainparams.h>
9+
#include <ccan/tal/tal.h>
10+
}
11+
12+
#undef template
13+
14+
#include <string>
15+
#include <sstream>
16+
#include <iomanip>
17+
#include <cstring>
18+
#include <vector>
19+
#include <memory>
20+
#include <iostream>
21+
#include <iostream>
22+
#include <span>
23+
#include "module.h"
24+
25+
struct TalFree {
26+
void operator()(void* ptr) const { tal_free(ptr); }
27+
};
28+
29+
std::string hex_encode(const unsigned char* data, size_t len) {
30+
std::ostringstream oss;
31+
oss << std::hex << std::setfill('0');
32+
for (size_t i = 0; i < len; ++i) {
33+
oss << std::setw(2) << static_cast<int>(data[i]);
34+
}
35+
return oss.str();
36+
}
37+
38+
std::string clightning_des_invoice(const std::string& input) {
39+
char* fail = nullptr;
40+
const struct chainparams* params = chainparams_for_network("bitcoin");
41+
42+
std::unique_ptr<bolt11, TalFree> invoice(
43+
bolt11_decode(nullptr, input.c_str(), nullptr, nullptr, params, &fail)
44+
);
45+
46+
if (!invoice) {
47+
tal_free(fail);
48+
return "";
49+
}
50+
51+
std::ostringstream result;
52+
result << "HASH=" << hex_encode(invoice->payment_hash.u.u8, 32) << ";";
53+
54+
result << "AMOUNT=";
55+
if (invoice->msat) {
56+
result << invoice->msat->millisatoshis;
57+
} else {
58+
result << "0";
59+
}
60+
result << ";";
61+
62+
result << "DESCRIPTION=";
63+
if (invoice->description) {
64+
result << invoice->description;
65+
}
66+
result << ";";
67+
68+
struct pubkey key;
69+
assert(pubkey_from_node_id(&key, &invoice->receiver_id));
70+
71+
uint8_t compressed[33];
72+
pubkey_to_der(compressed, &key);
73+
result << "RECIPIENT=" << hex_encode(compressed, 33) << ";";
74+
75+
result << "EXPIRY=" << invoice->expiry << ";";
76+
result << "TIMESTAMP=" << invoice->timestamp << ";";
77+
result << "ROUTING_HINTS=" << tal_count(invoice->routes) << ";";
78+
result << "MIN_CLTV=" << invoice->min_final_cltv_expiry;
79+
80+
return result.str();
81+
}
82+
83+
namespace bitcoinfuzz
84+
{
85+
namespace module
86+
{
87+
CLightning::CLightning(void) : BaseModule("CLightning") {}
88+
89+
std::optional<std::string> CLightning::deserialize_invoice(std::string str) const
90+
{
91+
return clightning_des_invoice(str.c_str());
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)