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

Fuzz testing integration #4129

Merged
merged 15 commits into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ test/test_protocol
test/test_sphinx
tests/.pytest.restart
tests/plugins/test_libplugin
tests/fuzz/fuzz-*
!tests/fuzz/fuzz-*.c
gossip_store
.pytest_cache
tools/headerversions
Expand Down
34 changes: 28 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,18 @@ VG=VALGRIND=1 valgrind -q --error-exitcode=7
VG_TEST_ARGS = --track-origins=yes --leak-check=full --show-reachable=yes --errors-for-leak-kinds=all
endif

SANITIZER_FLAGS :=

ifneq ($(ASAN),0)
SANITIZER_FLAGS=-fsanitize=address
else
SANITIZER_FLAGS=
SANITIZER_FLAGS += -fsanitize=address
endif

ifneq ($(UBSAN),0)
SANITIZER_FLAGS += -fsanitize=undefined
endif

ifneq ($(FUZZING), 0)
SANITIZER_FLAGS += -fsanitize=fuzzer-no-link
endif

ifeq ($(DEVELOPER),1)
Expand Down Expand Up @@ -208,6 +216,7 @@ WIRE_GEN_DEPS := $(WIRE_GEN) $(wildcard tools/gen/*_template)
# These are filled by individual Makefiles
ALL_PROGRAMS :=
ALL_TEST_PROGRAMS :=
ALL_FUZZ_TARGETS :=
ALL_C_SOURCES :=
ALL_C_HEADERS := gen_header_versions.h gen_version.h

Expand All @@ -224,6 +233,8 @@ unexport CFLAGS
CONFIGURATOR_CC := $(CC)

LDFLAGS += $(PIE_LDFLAGS) $(SANITIZER_FLAGS) $(COPTFLAGS)
CFLAGS += $(SANITIZER_FLAGS)

ifeq ($(STATIC),1)
# For MacOS, Jacob Rapoport <[email protected]> changed this to:
# -L/usr/local/lib -Wl,-lgmp -lsqlite3 -lz -Wl,-lm -lpthread -ldl $(COVFLAGS)
Expand Down Expand Up @@ -300,6 +311,9 @@ include devtools/Makefile
include tools/Makefile
include plugins/Makefile
include tests/plugins/Makefile
ifneq ($(FUZZING),0)
include tests/fuzz/Makefile
endif

# We make pretty much everything depend on these.
ALL_GEN_HEADERS := $(filter gen%.h %printgen.h %wiregen.h,$(ALL_C_HEADERS))
Expand Down Expand Up @@ -465,10 +479,10 @@ gen_header_versions.h: tools/headerversions
@tools/headerversions $@

# All binaries require the external libs, ccan and system library versions.
$(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS): $(EXTERNAL_LIBS) $(CCAN_OBJS)
$(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS): $(EXTERNAL_LIBS) $(CCAN_OBJS)

# Each test program depends on its own object.
$(ALL_TEST_PROGRAMS): %: %.o
$(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS): %: %.o

# Without this rule, the (built-in) link line contains
# external/libwallycore.a directly, which causes a symbol clash (it
Expand All @@ -477,6 +491,13 @@ $(ALL_TEST_PROGRAMS): %: %.o
$(ALL_PROGRAMS) $(ALL_TEST_PROGRAMS):
@$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) -o $@)

# We special case the fuzzing target binaries, as they need to link against libfuzzer,
# which brings its own main().
FUZZ_LDFLAGS = -fsanitize=fuzzer
$(ALL_FUZZ_TARGETS):
@$(call VERBOSE, "ld $@", $(LINK.o) $(filter-out %.a,$^) $(LOADLIBES) $(EXTERNAL_LDLIBS) $(LDLIBS) $(FUZZ_LDFLAGS) -o $@)


# Everything depends on the CCAN headers, and Makefile
$(CCAN_OBJS) $(CDUMP_OBJS): $(CCAN_HEADERS) Makefile

Expand All @@ -496,7 +517,7 @@ update-ccan:

# Now ALL_PROGRAMS is fully populated, we can expand it.
all-programs: $(ALL_PROGRAMS)
all-test-programs: $(ALL_TEST_PROGRAMS)
all-test-programs: $(ALL_TEST_PROGRAMS) $(ALL_FUZZ_TARGETS)

distclean: clean
$(RM) ccan/config.h config.vars
Expand All @@ -510,6 +531,7 @@ clean:
$(RM) $(CCAN_OBJS) $(CDUMP_OBJS) $(ALL_OBJS)
$(RM) $(ALL_PROGRAMS)
$(RM) $(ALL_TEST_PROGRAMS)
$(RM) $(ALL_FUZZ_TARGETS)
$(RM) gen_*.h */gen_* ccan/tools/configurator/configurator
$(RM) ccan/ccan/cdump/tools/cdump-enumstr.o
find . -name '*gcda' -delete
Expand Down
44 changes: 29 additions & 15 deletions common/amount.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const char *fmt_amount_msat_btc(const tal_t *ctx,
const struct amount_msat *msat,
bool append_unit)
{
if (msat->millisatoshis == 0)
return tal_fmt(ctx, append_unit ? "0btc" : "0");
darosior marked this conversation as resolved.
Show resolved Hide resolved

return tal_fmt(ctx, "%"PRIu64".%011"PRIu64"%s",
msat->millisatoshis / MSAT_PER_BTC,
msat->millisatoshis % MSAT_PER_BTC,
Expand All @@ -49,6 +52,9 @@ const char *fmt_amount_sat_btc(const tal_t *ctx,
const struct amount_sat *sat,
bool append_unit)
{
if (sat->satoshis == 0)
return tal_fmt(ctx, append_unit ? "0btc" : "0");
darosior marked this conversation as resolved.
Show resolved Hide resolved

return tal_fmt(ctx, "%"PRIu64".%08"PRIu64"%s",
sat->satoshis / SAT_PER_BTC,
sat->satoshis % SAT_PER_BTC,
Expand Down Expand Up @@ -80,7 +86,8 @@ static bool breakup(const char *str, size_t slen,
*suffix_len = 0;

for (i = 0;; i++) {
if (i >= slen)
/* The string may be null-terminated. */
if (i >= slen || str[i] == '\0')
return i != 0;
if (cisdigit(str[i]))
(*whole_number_len)++;
Expand All @@ -93,7 +100,7 @@ static bool breakup(const char *str, size_t slen,
*post_decimal_ptr = str + i;
for (;; i++) {
/* True if > 0 decimals. */
if (i >= slen)
if (i >= slen || str[i] == '\0')
return str + i != *post_decimal_ptr;
if (cisdigit(str[i]))
(*post_decimal_len)++;
Expand Down Expand Up @@ -168,14 +175,18 @@ bool parse_amount_msat(struct amount_msat *msat, const char *s, size_t slen)

if (!post_decimal_ptr && !suffix_ptr)
return from_number(&msat->millisatoshis, s, whole_number_len, 0);
if (!post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "msat"))
if (!post_decimal_ptr && memstarts_str(suffix_ptr, suffix_len, "msat"))
return from_number(&msat->millisatoshis, s, whole_number_len, 0);
if (!post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "sat"))
if (!post_decimal_ptr && memstarts_str(suffix_ptr, suffix_len, "sat"))
return from_number(&msat->millisatoshis, s, whole_number_len, 3);
if (post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "btc"))
return from_numbers(&msat->millisatoshis,
s, whole_number_len, 11,
post_decimal_ptr, post_decimal_len);
if (memstarts_str(suffix_ptr, suffix_len, "btc")) {
if (post_decimal_len > 0)
return from_numbers(&msat->millisatoshis,
s, whole_number_len, 11,
post_decimal_ptr, post_decimal_len);
return from_number(&msat->millisatoshis, s, whole_number_len, 11);
}

return false;
}

Expand All @@ -198,21 +209,24 @@ bool parse_amount_sat(struct amount_sat *sat, const char *s, size_t slen)

if (!post_decimal_ptr && !suffix_ptr)
return from_number(&sat->satoshis, s, whole_number_len, 0);
if (!post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "sat"))
if (!post_decimal_ptr && memstarts_str(suffix_ptr, suffix_len, "sat"))
return from_number(&sat->satoshis, s, whole_number_len, 0);
if (!post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "msat")) {
if (!post_decimal_ptr && memstarts_str(suffix_ptr, suffix_len, "msat")) {
if (!memends(s, whole_number_len, "000", strlen("000"))) {
if (memeqstr(s, whole_number_len, "0"))
if (memstarts_str(s, whole_number_len, "0"))
return from_number(&sat->satoshis, s,
whole_number_len, 0);
return false;
}
return from_number(&sat->satoshis, s, whole_number_len - 3, 0);
}
if (post_decimal_ptr && memeqstr(suffix_ptr, suffix_len, "btc"))
return from_numbers(&sat->satoshis,
s, whole_number_len, 8,
post_decimal_ptr, post_decimal_len);
if (memstarts_str(suffix_ptr, suffix_len, "btc")) {
if (post_decimal_len > 0)
return from_numbers(&sat->satoshis,
s, whole_number_len, 8,
post_decimal_ptr, post_decimal_len);
return from_number(&sat->satoshis, s, whole_number_len, 8);
}

return false;
}
Expand Down
21 changes: 0 additions & 21 deletions common/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,3 @@ char *b64_encode(const tal_t *ctx, const u8 *data, size_t len)
len, sodium_base64_VARIANT_ORIGINAL);
return str;
}

u8 *b64_decode(const tal_t *ctx, const char *str, size_t len)
{
size_t bin_len = len + 1;

u8 *ret = tal_arr(ctx, u8, bin_len);

if (!sodium_base642bin(ret,
tal_count(ret),
(const char * const)str,
len,
NULL,
&bin_len,
NULL,
sodium_base64_VARIANT_ORIGINAL))
return tal_free(ret);

ret[bin_len] = 0;
tal_resize(&ret, bin_len + 1);
return ret;
}
1 change: 0 additions & 1 deletion common/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
#include <ccan/tal/tal.h>

char *b64_encode(const tal_t *ctx, const u8 *data, size_t len);
u8 *b64_decode(const tal_t *ctx, const char *str, size_t len);

#endif /* LIGHTNING_COMMON_BASE64_H */
2 changes: 0 additions & 2 deletions common/test/run-amount.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ int main(void)
PASS_MSAT(&msat, "1.234567890btc", 123456789000);
PASS_MSAT(&msat, "1.2345678901btc", 123456789010);
PASS_MSAT(&msat, "1.23456789012btc", 123456789012);
FAIL_MSAT(&msat, "1btc");
darosior marked this conversation as resolved.
Show resolved Hide resolved
FAIL_MSAT(&msat, "1.000000000000btc");
FAIL_MSAT(&msat, "-1.23456789btc");
FAIL_MSAT(&msat, "-1.23456789012btc");
Expand Down Expand Up @@ -177,7 +176,6 @@ int main(void)
PASS_SAT(&sat, "1.2345678btc", 123456780);
PASS_SAT(&sat, "1.23456789btc", 123456789);
FAIL_SAT(&sat, "1.234567890btc");
FAIL_SAT(&sat, "1btc");
FAIL_SAT(&sat, "-1.23456789btc");

/* Overflowingly big. */
Expand Down
23 changes: 23 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,13 @@ set_defaults()
COMPAT=${COMPAT:-1}
STATIC=${STATIC:-0}
ASAN=${ASAN:-0}
UBSAN=${UBSAN:-0}
PYTEST=${PYTEST-$(default_pytest)}
COPTFLAGS=${COPTFLAGS-$(default_coptflags "$DEVELOPER")}
CONFIGURATOR_CC=${CONFIGURATOR_CC-$CC}
VALGRIND=${VALGRIND:-$(default_valgrind_setting)}
TEST_NETWORK=${TEST_NETWORK:-regtest}
FUZZING=${FUZZING:-0}
}

usage()
Expand Down Expand Up @@ -155,6 +157,9 @@ usage()
echo " Static link sqlite3, gmp and zlib libraries"
usage_with_default "--enable/disable-address-sanitizer" "$ASAN" "enable" "disable"
echo " Compile with address-sanitizer"
usage_with_default "--enable/disable-ub-sanitizer" "$UBSAN" "enable" "disable"
echo " Compile with undefined behaviour sanitizer"
usage_with_default "--enable/disable-fuzzing" "$FUZZING" "enable" "disable"
exit 1
}

Expand Down Expand Up @@ -206,6 +211,10 @@ for opt in "$@"; do
--disable-static) STATIC=0;;
--enable-address-sanitizer) ASAN=1;;
--disable-address-sanitizer) ASAN=0;;
--enable-ub-sanitizer) UBSAN=1;;
--disable-ub-sanitize) UBSAN=0;;
--enable-fuzzing) FUZZING=1;;
--disable-fuzzing) FUZZING=0;;
--help|-h) usage;;
*)
echo "Unknown option '$opt'" >&2
Expand All @@ -229,6 +238,18 @@ if [ "$ASAN" = "1" ]; then
fi
fi

if [ "$FUZZING" = "1" ]; then
case "$CC" in
(*"clang"*)
;;
(*)
echo "Fuzzing is currently only supported with clang."
exit 1
;;
esac
fi


SQLITE3_CFLAGS=""
SQLITE3_LDLIBS="-lsqlite3"
if command -v "${PKG_CONFIG}" >/dev/null; then
Expand Down Expand Up @@ -397,9 +418,11 @@ add_var COMPAT "$COMPAT" $CONFIG_HEADER
add_var PYTEST "$PYTEST"
add_var STATIC "$STATIC"
add_var ASAN "$ASAN"
add_var UBSAN "$UBSAN"
add_var TEST_NETWORK "$TEST_NETWORK"
add_var HAVE_PYTHON3_MAKO "$HAVE_PYTHON3_MAKO"
add_var SHA256SUM "$SHA256SUM"
add_var FUZZING "$FUZZING"

# Hack to avoid sha256 name clash with libwally: will be fixed when that
# becomes a standalone shared lib.
Expand Down
3 changes: 0 additions & 3 deletions contrib/sanitizer_suppressions/asan

This file was deleted.

69 changes: 69 additions & 0 deletions doc/FUZZING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Fuzz testing

C-lightning currently supports coverage-guided fuzz testing using [LLVM's libfuzzer](https://www.llvm.org/docs/LibFuzzer.html)
when built with `clang`.

The goal of fuzzing is to generate mutated -and often unexpected- inputs (`seed`s) to pass
to (parts of) a program (`target`) in order to make sure the codepaths used:
- do not crash
- are valid (if combined with sanitizers)
The generated seeds can be stored and form a `corpus`, which we try to optimise (don't
store two seeds that lead to the same codepath).

For more info about fuzzing see [here](https://github.com/google/fuzzing/tree/master/docs),
and for more about `libfuzzer` in particular see [here](https://www.llvm.org/docs/LibFuzzer.html).


## Build the fuzz targets

In order to build the C-lightning binaries with code coverage you will need a recent
[clang](http://clang.llvm.org/). The more recent the compiler version the better.

Then you'll need to enable support at configuration time. You likely want to enable
a few sanitizers for bug detections as well as experimental features for an extended
coverage (not required though).

```
DEVELOPER=1 EXPERIMENTAL_FEATURES=1 ASAN=1 UBSAN=1 VALGRIND=0 FUZZING=1 CC=clang ./configure && make
```

The targets will be built in `tests/fuzz/` as `fuzz-` binaries.


## Run one or more target(s)

You can run each target independently. Pass `-help=1` to see available options, for
darosior marked this conversation as resolved.
Show resolved Hide resolved
example:
```
./tests/fuzz/fuzz-addr -help=1
```

Otherwise, you can use the Python runner to either run the targets against a given seed
darosior marked this conversation as resolved.
Show resolved Hide resolved
corpus:
```
./tests/fuzz/run.py fuzz_corpus -j2
```
Or extend this corpus:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is a corpus?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A set of seeds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this definition to this doc.

```
./tests/fuzz/run.py fuzz_corpus -j2 --generate --runs 12345
```

The latter will run all targets two by two `12345` times.
darosior marked this conversation as resolved.
Show resolved Hide resolved

If you want to contribute new seeds, be sure to merge your corpus with the main one:
```
./tests/fuzz/run.py my_locally_extended_fuzz_corpus -j2 --generate --runs 12345
./tests/fuzz/run.py main_fuzz_corpus --merge_dir my_locally_extended_fuzz_corpus
```


## Write new fuzzing targets

In order to write a new target:
darosior marked this conversation as resolved.
Show resolved Hide resolved
- include the `libfuzz.h` header
- fill two functions: `init()` for static stuff and `run()` which will be called
repeatedly with mutated data.
- read about [what makes a good fuzz target](https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md).

A simple example is [`fuzz-addr`](tests/fuzz/fuzz-addr.c). It setups the chainparams and
context (wally, tmpctx, ..) in `init()` then bruteforces the bech32 encoder in `run()`.
Loading