mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 13:33:09 +07:00
test: fuzz target for the message parser
__msg_parse() turns bytes off a socket into pointers, before anything has vouched for the peer, and it is the only place in libink that does. It had no test of its own beyond whatever the other tests happened to send it, all of it well-formed. The target checks the parser's contract, not merely that it survived. A header field must point into the header field array, and terminate inside it, and the parse must never claim more bytes than it was handed. Crash-only would pass a parser that walked into the body and returned fields from there, since those bytes were handed over too. The expected bounds are derived from the raw header rather than from the parser, so the two have to agree independently. Every input is copied into an allocation sized to it first. Reading past the end of a roomy buffer stays inside the allocation and the sanitizer never sees it; against an exact one the same read is a fault, which is where the sharpest findings come from. Under libFuzzer it is an ordinary fuzz target and named files replay, which is how a find gets reproduced. With no arguments it runs a fixed sweep -- every truncation, every single-byte corruption, every value of the length that decides where the header ends, and seeded garbage -- so the suite covers the same contract on every build, without clang or a corpus in the tree. It takes 40 ms. CI fuzzes it properly on every pull request, keeps the crashers, and carries the corpus between runs so it reaches deeper over time than any single run can. Note that clang links the fuzzer runtime against the newest GCC tree it finds, so the libstdc++ headers have to match that one and not the default compiler, which is worth saying since installing the obvious package leaves you exactly where you started. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -18,6 +18,74 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fuzz:
|
||||
# The message parser is the only place bytes off a socket become
|
||||
# pointers, so give it a real fuzzer on every PR, not just the
|
||||
# fixed sweep 'make check' runs.
|
||||
name: fuzz msg-parse
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'push' || github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install pkg-config libconfuse-dev clang
|
||||
# clang picks the newest gcc tree it finds and needs the
|
||||
# matching libstdc++ headers to link the fuzzer runtime
|
||||
sudo apt-get -y install libstdc++-14-dev || true
|
||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||
tar xf libuev-2.4.1.tar.xz
|
||||
tar xf libite-2.6.2.tar.gz
|
||||
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
|
||||
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
|
||||
sudo ldconfig
|
||||
- uses: actions/checkout@v4
|
||||
- name: Configure
|
||||
run: |
|
||||
./autogen.sh
|
||||
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var
|
||||
- name: Build fuzz target
|
||||
run: |
|
||||
clang -fsanitize=fuzzer,address -DLINK_FUZZ_LIBFUZZER -D_GNU_SOURCE \
|
||||
-I libink -I . -o fuzz-msg-parse \
|
||||
test/src/fuzz-msg-parse.c libink/*.c
|
||||
# Restores the newest corpus and saves a fresh one, since a cache
|
||||
# entry is immutable once written. Caches made on a branch are
|
||||
# private to it, so the corpus that accumulates on master is what
|
||||
# pull requests start from, rather than nothing.
|
||||
- name: Restore corpus
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .fuzz-corpus
|
||||
key: fuzz-corpus-${{ github.run_id }}
|
||||
restore-keys: fuzz-corpus-
|
||||
- name: Fuzz
|
||||
run: |
|
||||
mkdir -p .fuzz-corpus
|
||||
./fuzz-msg-parse .fuzz-corpus -max_total_time=120 -max_len=4096 \
|
||||
-print_final_stats=1
|
||||
# Without this the corpus only ever grows, and most of what it
|
||||
# accumulates reaches code some earlier input already reached.
|
||||
- name: Minimise corpus
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p .fuzz-corpus-min
|
||||
./fuzz-msg-parse -merge=1 .fuzz-corpus-min .fuzz-corpus
|
||||
rm -rf .fuzz-corpus
|
||||
mv .fuzz-corpus-min .fuzz-corpus
|
||||
echo "corpus: $(ls .fuzz-corpus | wc -l) inputs"
|
||||
- name: Upload crashers
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fuzz-crashers
|
||||
path: |
|
||||
crash-*
|
||||
leak-*
|
||||
timeout-*
|
||||
if-no-files-found: ignore
|
||||
|
||||
build:
|
||||
# Verify we can build on latest Ubuntu with both gcc and clang
|
||||
name: ${{ matrix.compiler }}
|
||||
|
||||
Reference in New Issue
Block a user