From a4723c5ad160e895fe3eefc570ce114cedf6af52 Mon Sep 17 00:00:00 2001 From: firewave Date: Thu, 15 Feb 2024 16:15:54 +0100 Subject: [PATCH 1/2] added fuzzing client --- .github/workflows/fuzz.yml | 64 ++++++++++++++++++++++++++++++++++ Makefile | 19 ++++++++--- fuzz.cpp | 70 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/fuzz.yml create mode 100644 fuzz.cpp diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 00000000..e0b22d36 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,64 @@ +# Syntax reference https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions +# Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners +name: fuzz +on: [pull_request] + +permissions: + contents: read + +jobs: + fuzz: + runs-on: ubuntu-24.04 + if: ${{ github.repository_owner == 'danmar' }} + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + # the man-db trigger causes package installations to stall for several minutes at times. so just drop the package. + # see https://github.com/actions/runner/issues/4030 + - name: Remove man-db package + run: | + sudo apt-get update + sudo apt-get remove man-db + + - name: Install missing software + run: | + sudo apt-get update + sudo apt-get install -y make + + - name: Install clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 21 + + - name: Build fuzzer + id: build + run: | + # TODO: test O/LTO for best speed + # TODO: use -stdlib=libc++ -lc++ + make -j$(nproc) CXX=clang++ CXXOPTS="-O3 -flto -fno-omit-frame-pointer -g -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize-address-use-after-scope -fno-sanitize=integer -fno-sanitize-recover=undefined" LDOPTS="-flto" LIB_FUZZING_ENGINE="-fsanitize=fuzzer" fuzz + env: + CXX: clang-21 + + - name: Run fuzzer + run: | + mkdir corpus + mkdir artifacts + ./fuzz -only_ascii=1 -timeout=5 -fork=$(nproc) -use_value_profile=0 -max_total_time=60 -artifact_prefix=./artifacts/ corpus corpus_test + + - name: Upload corpus + uses: actions/upload-artifact@v6 + if: (success() || failure()) && steps.build.outcome == 'success' + with: + name: corpus + path: ./corpus + + - name: Upload artifacts + uses: actions/upload-artifact@v6 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./artifacts diff --git a/Makefile b/Makefile index b7b54597..5488bfb4 100644 --- a/Makefile +++ b/Makefile @@ -12,21 +12,32 @@ test.o: CPPFLAGS += $(TEST_CPPFLAGS) test.o: CXXFLAGS += -Wno-multichar %.o: %.cpp simplecpp.h - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< $(LIB_FUZZING_ENGINE) + +fuzz_no.o: fuzz.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DNO_FUZZ -c -o $@ $^ testrunner: test.o simplecpp.o - $(CXX) $(LDFLAGS) simplecpp.o test.o -o testrunner + $(CXX) $(LDFLAGS) -o $@ $^ test: testrunner simplecpp ./testrunner python3 run-tests.py python3 -m pytest integration_test.py -vv +fuzz: fuzz.o simplecpp.o + # TODO: use -stdlib=libc++ -lc++ + # make fuzz CXX=clang++ CXXOPTS="-O2 -fno-omit-frame-pointer -g -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=address,undefined -fsanitize-address-use-after-scope -fno-sanitize=integer -fno-sanitize-recover=undefined" LIB_FUZZING_ENGINE="-fsanitize=fuzzer" + $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^ $(LIB_FUZZING_ENGINE) + +no-fuzz: fuzz_no.o simplecpp.o + $(CXX) $(LDFLAGS) $(CXXFLAGS) -o $@ $^ + selfcheck: simplecpp CXX=$(CXX) ./selfcheck.sh simplecpp: main.o simplecpp.o - $(CXX) $(LDFLAGS) main.o simplecpp.o -o simplecpp + $(CXX) $(LDFLAGS) -o $@ $^ clean: - rm -f testrunner simplecpp *.o + rm -f testrunner fuzz no-fuzz simplecpp *.o diff --git a/fuzz.cpp b/fuzz.cpp new file mode 100644 index 00000000..88b69e3e --- /dev/null +++ b/fuzz.cpp @@ -0,0 +1,70 @@ +/* + * simplecpp - A simple and high-fidelity C/C++ preprocessor library + * Copyright (C) 2016-2024 simplecpp team + */ + +#include "simplecpp.h" + +#include + +#ifdef NO_FUZZ +#include +#include +#include +#include +#endif + +/* + 0 - store in corpus + -1 - omit from corpus +*/ +static int doProcess(const uint8_t *data, size_t dataSize) +{ + simplecpp::OutputList outputList; + std::vector files; + simplecpp::TokenList rawtokens(data, dataSize, files, "test.cpp", &outputList); + + simplecpp::TokenList outputTokens(files); + simplecpp::FileDataCache filedata; + const simplecpp::DUI dui; + std::list macroUsage; + std::list ifCond; + simplecpp::preprocess(outputTokens, rawtokens, files, filedata, dui, &outputList, ¯oUsage, &ifCond); + + simplecpp::cleanup(filedata); + + return 0; +} + +#ifndef NO_FUZZ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize); + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t dataSize) +{ + return doProcess(data, dataSize); +} +#else +int main(int argc, char * argv[]) +{ + if (argc < 2 || argc > 3) + return EXIT_FAILURE; + + std::ifstream f(argv[1]); + if (!f.is_open()) + return EXIT_FAILURE; + + std::ostringstream oss; + oss << f.rdbuf(); + + if (!f.good()) + return EXIT_FAILURE; + + const int cnt = (argc == 3) ? std::stoi(argv[2]) : 1; + + const std::string code = oss.str(); + for (int i = 0; i < cnt; ++i) + doProcess(reinterpret_cast(code.data()), code.size()); + + return EXIT_SUCCESS; +} +#endif From 5e65ebf9bf8c1618a197141738fabc182176d968 Mon Sep 17 00:00:00 2001 From: firewave Date: Wed, 10 Sep 2025 15:14:53 +0200 Subject: [PATCH 2/2] generate a fuzzing corpus by extracting code from `testrunner` [skip ci] --- .github/workflows/fuzz.yml | 12 ++++++++++++ test.cpp | 21 ++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index e0b22d36..ac84b04a 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -34,6 +34,18 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh 21 + - name: Generate corpus + run: | + mkdir corpus_test + make testrunner CXXOPTS="-DSTORE_INPUT_DIR=\"\\\"$(pwd)/corpus_test\\\"\"" + ./testrunner + + - name: Upload corpus (testrunner) + uses: actions/upload-artifact@v6 + with: + name: corpus_test + path: ./corpus_test + - name: Build fuzzer id: build run: | diff --git a/test.cpp b/test.cpp index 300245cd..31f20dfd 100644 --- a/test.cpp +++ b/test.cpp @@ -79,9 +79,28 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons #define TEST_CASE(F) (testcase(#F, F, argc, argv)) +#ifdef STORE_INPUT_DIR +// make testrunner CXXOPTS="-DSTORE_INPUT_DIR=\"\\\"/home/user/simple_corpus\\\"\"" +#include +#include + +static void storeInput(const std::string& str) +{ + static std::atomic_uint64_t num(0); + { + std::ofstream out(STORE_INPUT_DIR "/" + std::to_string(num++)); + out << str; + } +} +#endif + static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) { - std::istringstream istr(std::string(code, size)); + const std::string str(code, size); +#ifdef STORE_INPUT_DIR + storeInput(str); +#endif + std::istringstream istr(str); return simplecpp::TokenList(istr,filenames,filename,outputList); }