Skip to content
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 .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ updates:
open-pull-requests-limit: 10
labels:
- "dependencies"
cooldown:
default-days: 7
107 changes: 107 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions fuzz/corpus/empty_object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions fuzz/corpus/malformed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions fuzz/corpus/multi_session.json
Original file line number Diff line number Diff line change
@@ -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"}}
24 changes: 24 additions & 0 deletions fuzz/corpus/valid_config.json
Original file line number Diff line number Diff line change
@@ -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 }
}
]
}
57 changes: 57 additions & 0 deletions fuzz/fuzz_config_reader.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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 <json_config_file>\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;
}
Loading