diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 70deb75..fc1e191 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,5 @@ updates: open-pull-requests-limit: 10 labels: - "dependencies" + cooldown: + default-days: 7 diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..93c6b30 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,107 @@ +# +# BSD 3-Clause License +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +name: Fuzz Testing (AFL) + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Weekly on Wednesday at 04:00 UTC + - cron: '0 4 * * 3' + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + fuzz: + name: AFL Fuzz - Config Parser + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y afl++ libavutil-dev libavformat-dev libavcodec-dev libswscale-dev pkg-config + + - name: Verify environment + run: | + echo "=== AFL++ ===" + afl-clang-fast --version || { echo "ERROR: afl-clang-fast not found"; exit 1; } + echo "" + echo "=== libavutil ===" + dpkg -l libavutil-dev | grep -q ii || { echo "ERROR: libavutil-dev not installed"; exit 1; } + echo " Header: $(find /usr/include -name 'avutil.h' | head -1)" + echo " Library: $(find /usr/lib -name 'libavutil.so*' | head -1)" + echo "" + echo "=== Compiler ===" + gcc --version | head -1 + echo "" + echo "Environment OK" + + - name: Build fuzz harness + run: | + cd fuzz + export CC=afl-clang-fast + # Compile each file separately with minimal flags to avoid AFL MAX_PARAMS_NUM + $CC -g -O1 -fno-omit-frame-pointer -I../include -c fuzz_config_reader.c -o fuzz_config_reader.o + $CC -g -O1 -fno-omit-frame-pointer -I../include -c ../src/util/config_reader.c -o config_reader.o + $CC -g -O1 -fno-omit-frame-pointer -I../include -c ../src/util/logger.c -o logger.o + # Link with afl-clang-fast (needs AFL runtime), pass only linker libs directly + $CC -o fuzz_config_reader fuzz_config_reader.o config_reader.o logger.o -lavutil -lm + echo "Build successful: $(file fuzz_config_reader)" + + - name: Run AFL fuzzer (timed) + run: | + cd fuzz + mkdir -p findings + export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 + export AFL_SKIP_CPUFREQ=1 + # Run AFL for 5 minutes (CI time-boxed) + timeout 300 afl-fuzz -i corpus/ -o findings/ -V 300 -- ./fuzz_config_reader @@ || true + + - name: Check for crashes + run: | + cd fuzz + CRASH_COUNT=$(find findings/default/crashes -type f ! -name "README.txt" 2>/dev/null | wc -l) + echo "Crashes found: $CRASH_COUNT" + if [ "$CRASH_COUNT" -gt 0 ]; then + echo "::error::AFL found $CRASH_COUNT crash(es)!" + ls -la findings/default/crashes/ + exit 1 + fi + echo "No crashes found — fuzzing passed." + + - name: Sanitize AFL filenames for upload + if: always() + run: | + cd fuzz/findings + # Rename files with colons (AFL naming) to use underscores + find . -name '*:*' | while read f; do + mv "$f" "$(echo "$f" | tr ':' '_')" + done + + - name: Upload fuzzing results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: afl-fuzz-results-${{ github.run_id }} + path: fuzz/findings/ + retention-days: 14 diff --git a/fuzz/corpus/empty_object.json b/fuzz/corpus/empty_object.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/fuzz/corpus/empty_object.json @@ -0,0 +1 @@ +{} diff --git a/fuzz/corpus/malformed.json b/fuzz/corpus/malformed.json new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/fuzz/corpus/malformed.json @@ -0,0 +1 @@ + diff --git a/fuzz/corpus/multi_session.json b/fuzz/corpus/multi_session.json new file mode 100644 index 0000000..20c9bdb --- /dev/null +++ b/fuzz/corpus/multi_session.json @@ -0,0 +1 @@ +{"tx_sessions": [{"udp_port": 20000, "payload_type": 96, "crop": {"x": 0, "y": 0, "w": 640, "h": 1080}}, {"udp_port": 20002, "payload_type": 96, "crop": {"x": 640, "y": 0, "w": 640, "h": 1080}}, {"udp_port": 20004, "payload_type": 96, "crop": {"x": 1280, "y": 0, "w": 640, "h": 1080}}], "interfaces": [{"name": "0000:06:00.0", "sip": "192.168.50.29", "dip": "239.168.85.20"}], "video": {"width": 1920, "height": 1080, "fps": 60, "fmt": "yuv420", "tx_url": "/dev/null"}} diff --git a/fuzz/corpus/valid_config.json b/fuzz/corpus/valid_config.json new file mode 100644 index 0000000..6bddc6f --- /dev/null +++ b/fuzz/corpus/valid_config.json @@ -0,0 +1,24 @@ +{ + "log_file": "dvledtx.log", + "interfaces": [ + { + "name": "0000:06:00.0", + "sip": "192.168.50.29", + "dip": "239.168.85.20" + } + ], + "video": { + "width": 1920, + "height": 1080, + "fps": 30, + "fmt": "yuv422p10le", + "tx_url": "/dev/null" + }, + "tx_sessions": [ + { + "udp_port": 20000, + "payload_type": 96, + "crop": { "x": 0, "y": 0, "w": 1920, "h": 1080 } + } + ] +} diff --git a/fuzz/fuzz_config_reader.c b/fuzz/fuzz_config_reader.c new file mode 100644 index 0000000..880e594 --- /dev/null +++ b/fuzz/fuzz_config_reader.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2026 Intel Corporation + * + * AFL fuzzing harness for the JSON config parser. + * Build: afl-gcc -o fuzz_config_reader fuzz_config_reader.c ../src/util/config_reader.c + * ../src/util/logger.c -I../include $(pkg-config --cflags --libs libavutil) -lm + * Run: afl-fuzz -i corpus/ -o findings/ -- ./fuzz_config_reader @@ + */ + +#include +#include +#include +#include +#include "util/config_reader.h" + +#ifdef __AFL_HAVE_MANUAL_CONTROL +__AFL_FUZZ_INIT(); +#endif + +int main(int argc, char *argv[]) { +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); + unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; + + while (__AFL_LOOP(10000)) { + int len = __AFL_FUZZ_TESTCASE_LEN; + if (len < 2) continue; + + /* Write fuzz input to a temporary file for parse_tx_config */ + char tmpfile[] = "/tmp/fuzz_cfg_XXXXXX"; + int fd = mkstemp(tmpfile); + if (fd < 0) continue; + write(fd, buf, len); + close(fd); + + struct dvledtx_config config; + memset(&config, 0, sizeof(config)); + parse_tx_config(tmpfile, &config); + validate_tx_config(&config); + + unlink(tmpfile); + } +#else + /* Standard mode: read from file argument */ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + struct dvledtx_config config; + memset(&config, 0, sizeof(config)); + parse_tx_config(argv[1], &config); + validate_tx_config(&config); +#endif + + return 0; +}