Hello FFmpeg developers,

This patch adds several new fuzzer targets to the tools/ directory to
improve
test coverage for various components, including libavutil (AVOptions),
libavfilter,
muxers, network protocols, and the CLI options parsing.

It also introduces tools/run_fuzzer_loop.c, a simple harness that allows
running
these fuzzers in a loop without requiring a full libFuzzer environment.

I have verified that the new targets compile and run successfully.

Please find the patch attached.

Best,

Leslie
From 497c3929ba8ff6299aa3dd214fb73a841587852f Mon Sep 17 00:00:00 2001
From: "Leslie P. Polzer" <[email protected]>
Date: Thu, 18 Dec 2025 12:51:04 +0000
Subject: [PATCH] tools: add new fuzzers for avutil, avfilter, mux, network and
 cli

This patch adds new fuzzer targets to the tools directory:
- target_avutil_fuzzer: targets libavutil and AVOptions
- target_avfilter_fuzzer: targets specific filters
- target_mux_fuzzer: targets specific muxers
- target_network_fuzzer: targets network protocols
- target_cli_fuzzer: targets the ffmpeg CLI (exposes ffmpeg_cleanup)

It also adds run_fuzzer_loop.c, a harness for running these fuzzers
in a loop without libFuzzer.

Signed-off-by: Leslie P. Polzer <[email protected]>
---
 fftools/ffmpeg.c               |   2 +-
 fftools/ffmpeg.h               |   1 +
 tools/Makefile                 |  18 +++
 tools/run_fuzzer_loop.c        |  77 ++++++++++++
 tools/target_avfilter_fuzzer.c | 164 ++++++++++++++++++++++++++
 tools/target_avutil_fuzzer.c   |  82 +++++++++++++
 tools/target_cli_fuzzer.c      | 123 ++++++++++++++++++++
 tools/target_mux_fuzzer.c      | 164 ++++++++++++++++++++++++++
 tools/target_network_fuzzer.c  | 207 +++++++++++++++++++++++++++++++++
 9 files changed, 837 insertions(+), 1 deletion(-)
 create mode 100644 tools/run_fuzzer_loop.c
 create mode 100644 tools/target_avfilter_fuzzer.c
 create mode 100644 tools/target_avutil_fuzzer.c
 create mode 100644 tools/target_cli_fuzzer.c
 create mode 100644 tools/target_mux_fuzzer.c
 create mode 100644 tools/target_network_fuzzer.c

diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c
index c2c85d46bd..0b664f1426 100644
--- a/fftools/ffmpeg.c
+++ b/fftools/ffmpeg.c
@@ -310,7 +310,7 @@ static int decode_interrupt_cb(void *ctx)
 
 const AVIOInterruptCB int_cb = { decode_interrupt_cb, NULL };
 
-static void ffmpeg_cleanup(int ret)
+void ffmpeg_cleanup(int ret)
 {
     if ((print_graphs || print_graphs_file) && nb_output_files > 0)
         print_filtergraphs(filtergraphs, nb_filtergraphs, input_files, nb_input_files, output_files, nb_output_files);
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index 7720dd9c59..0932079d46 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -798,6 +798,7 @@ extern int recast_media;
 extern FILE *vstats_file;
 
 void term_init(void);
+void ffmpeg_cleanup(int ret);
 void term_exit(void);
 
 void show_usage(void);
diff --git a/tools/Makefile b/tools/Makefile
index 7ae6e3cb75..294fbda81d 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -11,6 +11,24 @@ tools/target_enc_%_fuzzer.o: tools/target_enc_fuzzer.c
 tools/target_bsf_%_fuzzer.o: tools/target_bsf_fuzzer.c
 	$(COMPILE_C) -DFFMPEG_BSF=$*
 
+tools/target_avfilter_%_fuzzer.o: tools/target_avfilter_fuzzer.c
+	$(COMPILE_C) -DFFMPEG_FILTER=$*
+
+tools/target_avutil_fuzzer.o: tools/target_avutil_fuzzer.c
+	$(COMPILE_C)
+
+tools/target_avutil_opt_fuzzer.o: tools/target_avutil_fuzzer.c
+	$(COMPILE_C) -DFFMPEG_AVUTIL_OPT
+
+tools/target_mux_%_fuzzer.o: tools/target_mux_fuzzer.c
+	$(COMPILE_C) -DFFMPEG_MUXER=$*
+
+tools/target_cli_fuzzer.o: tools/target_cli_fuzzer.c
+	$(COMPILE_C)
+
+tools/target_network_fuzzer.o: tools/target_network_fuzzer.c
+	$(COMPILE_C)
+
 tools/target_dem_%_fuzzer.o: tools/target_dem_fuzzer.c
 	$(COMPILE_C) -DFFMPEG_DEMUXER=$* -DIO_FLAT=0
 
diff --git a/tools/run_fuzzer_loop.c b/tools/run_fuzzer_loop.c
new file mode 100644
index 0000000000..7c02572624
--- /dev/null
+++ b/tools/run_fuzzer_loop.c
@@ -0,0 +1,77 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <signal.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static volatile int stop = 0;
+
+void handle_alarm(int sig) {
+    stop = 1;
+}
+
+int main(int argc, char **argv) {
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s <duration_seconds>\n", argv[0]);
+        return 1;
+    }
+
+    int duration = atoi(argv[1]);
+    signal(SIGALRM, handle_alarm);
+    alarm(duration);
+
+    uint8_t buf[65536];
+    size_t iterations = 0;
+    FILE *urandom = fopen("/dev/urandom", "rb");
+    if (!urandom) {
+        perror("fopen /dev/urandom");
+        return 1;
+    }
+
+    printf("Fuzzing for %d seconds...\n", duration);
+
+    while (!stop) {
+        // Generate random size between 1 and sizeof(buf)
+        size_t size = (rand() % sizeof(buf)) + 1;
+        
+        // Read random data
+        if (fread(buf, 1, size, urandom) != size) {
+            break;
+        }
+
+        // Run fuzzer
+        LLVMFuzzerTestOneInput(buf, size);
+        iterations++;
+        
+        if (iterations % 1000 == 0) {
+            printf("Iterations: %zu\r", iterations);
+            fflush(stdout);
+        }
+    }
+
+    fclose(urandom);
+    printf("\nFinished. Total iterations: %zu\n", iterations);
+    return 0;
+}
diff --git a/tools/target_avfilter_fuzzer.c b/tools/target_avfilter_fuzzer.c
new file mode 100644
index 0000000000..70f02d41be
--- /dev/null
+++ b/tools/target_avfilter_fuzzer.c
@@ -0,0 +1,164 @@
+/*
+ * Fuzzer for libavfilter
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "libavutil/buffer.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/samplefmt.h"
+
+#include "libavfilter/avfilter.h"
+#include "libavfilter/buffersink.h"
+#include "libavfilter/buffersrc.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static void error(const char *err)
+{
+    fprintf(stderr, "%s", err);
+    exit(1);
+}
+
+// Global filter to fuzz. Initialize once if possible or via define.
+static const AVFilter *f = NULL;
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    int ret;
+    AVFilterGraph *graph = NULL;
+    AVFilterContext *filt_ctx = NULL, *src_ctx = NULL, *sink_ctx = NULL;
+    AVFrame *frame = NULL;
+    uint8_t *dummy_data = NULL;
+    const uint8_t *end = data + size;
+    char *options_str = NULL;
+    int options_len = 0;
+
+    // Ensure filter is set (compiled in)
+    if (!f) {
+#ifdef FFMPEG_FILTER
+#define XSTR(s) STR(s)
+#define STR(s) #s
+        f = avfilter_get_by_name(XSTR(FFMPEG_FILTER));
+#endif
+        if (!f) {
+             fprintf(stderr, "Filter not found: %s\n", XSTR(FFMPEG_FILTER));
+             return 0; 
+        }
+        av_log_set_level(AV_LOG_PANIC);
+    }
+
+    graph = avfilter_graph_alloc();
+    if (!graph) error("Failed to allocate graph");
+
+    // We need at least some data for options and frame params
+    // Let's reserve up to 1024 bytes for options if available
+    if (size > 0) {
+        options_len = size > 512 ? 512 : size;
+        options_str = av_malloc(options_len + 1);
+        if (!options_str) error("Failed to allocate options");
+        memcpy(options_str, data, options_len);
+        options_str[options_len] = 0;
+        data += options_len;
+        size -= options_len;
+    }
+
+    // Determine filter type to setup source/sink
+    int is_audio = 0;
+    if (f->inputs && avfilter_pad_get_type(f->inputs, 0) == AVMEDIA_TYPE_AUDIO) is_audio = 1;
+    // Some filters might have no inputs (sources), handled separately?
+    // For now assume 1 input 1 output or similar standard filters.
+    
+    const AVFilter *buffersrc = avfilter_get_by_name(is_audio ? "abuffer" : "buffer");
+    const AVFilter *buffersink = avfilter_get_by_name(is_audio ? "abuffersink" : "buffersink");
+    
+    char src_args[256];
+    if (is_audio) {
+         snprintf(src_args, sizeof(src_args), "time_base=1/44100:sample_rate=44100:sample_fmt=flt:channel_layout=stereo");
+    } else {
+         snprintf(src_args, sizeof(src_args), "video_size=640x480:pix_fmt=yuv420p:time_base=1/25:pixel_aspect=1/1");
+    }
+
+    ret = avfilter_graph_create_filter(&src_ctx, buffersrc, "in", src_args, NULL, graph);
+    if (ret < 0) goto end;
+
+    ret = avfilter_graph_create_filter(&filt_ctx, f, "filter", options_str, NULL, graph);
+    if (ret < 0) goto end;
+
+    ret = avfilter_graph_create_filter(&sink_ctx, buffersink, "out", NULL, NULL, graph);
+    if (ret < 0) goto end;
+
+    // Link
+    if (src_ctx && filt_ctx) {
+        ret = avfilter_link(src_ctx, 0, filt_ctx, 0);
+        if (ret < 0) goto end;
+    }
+    if (filt_ctx && sink_ctx) {
+        ret = avfilter_link(filt_ctx, 0, sink_ctx, 0);
+        if (ret < 0) goto end;
+    }
+
+    ret = avfilter_graph_config(graph, NULL);
+    if (ret < 0) goto end;
+
+    // Send a frame
+    frame = av_frame_alloc();
+    if (!frame) error("Failed to allocate frame");
+    
+    if (is_audio) {
+        frame->nb_samples = 1024;
+        frame->format = AV_SAMPLE_FMT_FLT;
+        av_channel_layout_default(&frame->ch_layout, 2);
+        frame->sample_rate = 44100;
+    } else {
+        frame->width = 640;
+        frame->height = 480;
+        frame->format = AV_PIX_FMT_YUV420P;
+    }
+    
+    ret = av_frame_get_buffer(frame, 0);
+    if (ret < 0) goto end;
+
+    // Fill frame with fuzz data if available
+    if (size > 0) {
+        int copy_size = size;
+        // Naive fill, just copy into first plane
+        if (copy_size > frame->buf[0]->size) copy_size = frame->buf[0]->size;
+        memcpy(frame->data[0], data, copy_size);
+    }
+
+    ret = av_buffersrc_add_frame(src_ctx, frame);
+    if (ret < 0) goto end;
+
+    // Receive output
+    while (1) {
+        AVFrame *out = av_frame_alloc();
+        ret = av_buffersink_get_frame(sink_ctx, out);
+        av_frame_free(&out);
+        if (ret < 0) break;
+    }
+
+end:
+    if (options_str) av_free(options_str);
+    if (frame) av_frame_free(&frame);
+    if (graph) avfilter_graph_free(&graph);
+    
+    return 0;
+}
diff --git a/tools/target_avutil_fuzzer.c b/tools/target_avutil_fuzzer.c
new file mode 100644
index 0000000000..4d621f4915
--- /dev/null
+++ b/tools/target_avutil_fuzzer.c
@@ -0,0 +1,82 @@
+/*
+ * Fuzzer for libavutil
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "libavutil/avutil.h"
+#include "libavutil/eval.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+// Dummy class for option fuzzing
+typedef struct DummyContext {
+    const AVClass *class;
+    int int_val;
+    char *str_val;
+    double dbl_val;
+    int64_t i64_val;
+} DummyContext;
+
+static const AVOption dummy_options[] = {
+    { "int", "integer option", offsetof(DummyContext, int_val), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, 0 },
+    { "str", "string option", offsetof(DummyContext, str_val), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, 0 },
+    { "dbl", "double option", offsetof(DummyContext, dbl_val), AV_OPT_TYPE_DOUBLE, {.dbl = 0}, -100.0, 100.0, 0 },
+    { "i64", "int64 option", offsetof(DummyContext, i64_val), AV_OPT_TYPE_INT64, {.i64 = 0}, INT64_MIN, INT64_MAX, 0 },
+    { NULL }
+};
+
+static const AVClass dummy_class = {
+    .class_name = "Dummy",
+    .item_name  = av_default_item_name,
+    .option     = dummy_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    char *str;
+    
+    // Limit size to avoid excessive processing time
+    if (size > 8192) size = 8192;
+
+    str = av_malloc(size + 1);
+    if (!str) return 0;
+    
+    memcpy(str, data, size);
+    str[size] = 0;
+
+#ifdef FFMPEG_AVUTIL_OPT
+    DummyContext obj = { .class = &dummy_class };
+    av_opt_set_defaults(&obj);
+    
+    // Fuzz option parsing (key=value:key2=value2...)
+    av_set_options_string(&obj, str, "=", ":");
+    
+    av_opt_free(&obj);
+#else
+    // Default: av_expr
+    double res;
+    // Fuzz av_expr_parse_and_eval
+    av_expr_parse_and_eval(&res, str, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL);
+#endif
+    
+    av_free(str);
+    return 0;
+}
\ No newline at end of file
diff --git a/tools/target_cli_fuzzer.c b/tools/target_cli_fuzzer.c
new file mode 100644
index 0000000000..5cee732039
--- /dev/null
+++ b/tools/target_cli_fuzzer.c
@@ -0,0 +1,123 @@
+/*
+ * Fuzzer for ffmpeg CLI option parsing
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+
+#include "fftools/ffmpeg.h"
+#include "fftools/ffmpeg_sched.h"
+
+// Globals from ffmpeg.c that need resetting
+extern int nb_input_files;
+extern int nb_output_files;
+extern int nb_filtergraphs;
+extern int nb_decoders;
+
+// Extern function from ffmpeg_opt.c
+int ffmpeg_parse_options(int argc, char **argv, Scheduler *sch);
+
+static jmp_buf jmp_env;
+
+// Mock exit to return control to fuzzer
+void __wrap_exit(int status) {
+    longjmp(jmp_env, 1);
+}
+
+// Mock other functions that might be problematic or unwanted during fuzzing
+// For now, we assume others are safe or handled by ffmpeg_cleanup
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    int argc = 0;
+    char **argv = NULL;
+    char *input_copy = NULL;
+    Scheduler *sch = NULL;
+
+    // We need at least "ffmpeg" as argv[0]
+    // Parse data into argv. We treat null bytes or newlines as separators?
+    // Or just null bytes.
+    // Let's create a copy of data to be safe and mutable
+    input_copy = malloc(size + 1);
+    if (!input_copy) return 0;
+    memcpy(input_copy, data, size);
+    input_copy[size] = 0;
+
+    // Count args
+    argc = 1; // argv[0]
+    for (size_t i = 0; i < size; i++) {
+        if (input_copy[i] == 0 || input_copy[i] == '\n') {
+            argc++;
+        }
+    }
+
+    argv = malloc((argc + 1) * sizeof(char *));
+    if (!argv) {
+        free(input_copy);
+        return 0;
+    }
+
+    argv[0] = "ffmpeg";
+    int current_arg = 1;
+    char *ptr = input_copy;
+    char *end = input_copy + size;
+    
+    // Simple tokenizer
+    char *token = ptr;
+    for (size_t i = 0; i <= size; i++) {
+        if (input_copy[i] == 0 || input_copy[i] == '\n') {
+            input_copy[i] = 0; // Ensure null termination
+            if (current_arg < argc) {
+                argv[current_arg++] = token;
+            }
+            token = &input_copy[i+1];
+        }
+    }
+    argv[argc] = NULL;
+
+    // Allocate scheduler
+    sch = sch_alloc();
+    if (!sch) goto end;
+
+    // Prepare to catch exit
+    if (setjmp(jmp_env) == 0) {
+        // Run option parsing
+        // This will likely fail and call exit() often with fuzz data
+        ffmpeg_parse_options(argc, argv, sch);
+    }
+
+    // Cleanup
+    ffmpeg_cleanup(0);
+    
+    // Reset globals to allow next run
+    nb_input_files = 0;
+    nb_output_files = 0;
+    nb_filtergraphs = 0;
+    nb_decoders = 0;
+    // Also reset option globals if possible? uninit_opts called in cleanup
+
+end:
+    if (sch) sch_free(&sch);
+    free(argv);
+    free(input_copy);
+    return 0;
+}
diff --git a/tools/target_mux_fuzzer.c b/tools/target_mux_fuzzer.c
new file mode 100644
index 0000000000..4c0b904fe3
--- /dev/null
+++ b/tools/target_mux_fuzzer.c
@@ -0,0 +1,164 @@
+/*
+ * Fuzzer for libavformat muxers
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "libavutil/avassert.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/intreadwrite.h"
+#include "libavformat/avformat.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static void error(const char *err)
+{
+    fprintf(stderr, "%s", err);
+    exit(1);
+}
+
+// Global muxer to fuzz.
+static const AVOutputFormat *fmt = NULL;
+
+// Mock IO to discard output
+static int write_packet(void *opaque, const uint8_t *buf, int buf_size)
+{
+    return buf_size;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    AVFormatContext *oc = NULL;
+    AVPacket *pkt = NULL;
+    AVIOContext *pb = NULL;
+    uint8_t *io_buffer = NULL;
+    int io_buffer_size = 32768;
+    int ret;
+    const uint8_t *end = data + size;
+
+    if (!fmt) {
+#ifdef FFMPEG_MUXER
+#define XSTR(s) STR(s)
+#define STR(s) #s
+        fmt = av_guess_format(XSTR(FFMPEG_MUXER), NULL, NULL);
+#endif
+        // Fallback to a common default if not specified
+        if (!fmt) fmt = av_guess_format("mp4", NULL, NULL);
+        if (!fmt) return 0;
+        av_log_set_level(AV_LOG_PANIC);
+    }
+
+    if (size < 16) return 0;
+
+    // Allocate IO buffer
+    io_buffer = av_malloc(io_buffer_size);
+    if (!io_buffer) return 0;
+
+    pb = avio_alloc_context(io_buffer, io_buffer_size, 1, NULL, NULL, write_packet, NULL);
+    if (!pb) {
+        av_free(io_buffer);
+        return 0;
+    }
+
+    ret = avformat_alloc_output_context2(&oc, fmt, NULL, NULL);
+    if (ret < 0 || !oc) {
+        avio_context_free(&pb);
+        return 0;
+    }
+    oc->pb = pb;
+
+    // Parse fuzz data to create streams
+    // First byte: number of streams (cap at 10)
+    int nb_streams = data[0] % 10;
+    if (nb_streams == 0) nb_streams = 1;
+    data++; 
+    size--;
+
+    for (int i = 0; i < nb_streams; i++) {
+        AVStream *st = avformat_new_stream(oc, NULL);
+        if (!st) break;
+        
+        // Randomize stream codec parameters
+        if (size < 4) break;
+        uint32_t codec_tag = AV_RL32(data);
+        data += 4; size -= 4;
+        
+        st->codecpar->codec_type = (i % 2 == 0) ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO;
+        st->codecpar->codec_id = AV_CODEC_ID_H264; // Default to something common
+        st->codecpar->codec_tag = codec_tag;
+        st->time_base = (AVRational){1, 25};
+        
+        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+            st->codecpar->width = 640;
+            st->codecpar->height = 480;
+        } else {
+            st->codecpar->sample_rate = 44100;
+            av_channel_layout_default(&st->codecpar->ch_layout, 2);
+        }
+    }
+
+    // Write header
+    if (avformat_write_header(oc, NULL) < 0) {
+        goto end;
+    }
+
+    pkt = av_packet_alloc();
+    if (!pkt) goto end;
+
+    // Packet loop
+    while (size > 16) {
+        // [stream_idx 1b][pts 8b][dts 8b][flags 1b][size 2b][data ...]
+        uint8_t stream_idx = data[0];
+        int64_t pts = AV_RL64(data + 1);
+        int64_t dts = AV_RL64(data + 9);
+        int flags = data[17];
+        int payload_size = AV_RL16(data + 18);
+        data += 20; size -= 20;
+
+        if (oc->nb_streams == 0) break;
+        stream_idx %= oc->nb_streams;
+
+        if (payload_size > size) payload_size = size;
+
+        // Populate packet
+        av_new_packet(pkt, payload_size);
+        memcpy(pkt->data, data, payload_size);
+        pkt->stream_index = stream_idx;
+        pkt->pts = pts;
+        pkt->dts = dts;
+        pkt->flags = flags;
+
+        // Mux
+        av_interleaved_write_frame(oc, pkt);
+        av_packet_unref(pkt);
+
+        data += payload_size;
+        size -= payload_size;
+    }
+
+    av_write_trailer(oc);
+
+end:
+    av_packet_free(&pkt);
+    // free output context before IO because it might flush
+    if (oc) avformat_free_context(oc);
+    if (pb) avio_context_free(&pb); 
+    // io_buffer is freed by avio_context_free
+    
+    return 0;
+}
diff --git a/tools/target_network_fuzzer.c b/tools/target_network_fuzzer.c
new file mode 100644
index 0000000000..aaaa543b29
--- /dev/null
+++ b/tools/target_network_fuzzer.c
@@ -0,0 +1,207 @@
+/*
+ * Fuzzer for network protocols (mocking sockets)
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "libavformat/avformat.h"
+#include "libavformat/url.h"
+#include "libavutil/avassert.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <errno.h>
+
+// --- Mock State ---
+static const uint8_t *g_fuzz_data = NULL;
+static size_t g_fuzz_size = 0;
+static size_t g_fuzz_pos = 0;
+static int g_fake_fd = 42; // arbitrary number
+
+// --- Mock Functions ---
+
+int __wrap_socket(int domain, int type, int protocol) {
+    return g_fake_fd;
+}
+
+int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+    if (sockfd != g_fake_fd) return -1;
+    return 0; // Success
+}
+
+int __wrap_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+    return 0;
+}
+
+int __wrap_listen(int sockfd, int backlog) {
+    return 0;
+}
+
+int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
+    return -1; // Not fuzzing server side yet
+}
+
+ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) {
+    if (sockfd != g_fake_fd) {
+        errno = EBADF;
+        return -1;
+    }
+    
+    if (g_fuzz_pos >= g_fuzz_size) {
+        // EOF
+        return 0; 
+    }
+
+    size_t remaining = g_fuzz_size - g_fuzz_pos;
+    size_t to_read = len < remaining ? len : remaining;
+    
+    if (to_read == 0) return 0;
+
+    memcpy(buf, g_fuzz_data + g_fuzz_pos, to_read);
+    g_fuzz_pos += to_read;
+    return to_read;
+}
+
+ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) {
+    if (sockfd != g_fake_fd) {
+        errno = EBADF;
+        return -1;
+    }
+    // Sink the data
+    return len;
+}
+
+int __wrap_shutdown(int sockfd, int how) {
+    return 0;
+}
+
+int __wrap_close(int fd) {
+    if (fd == g_fake_fd) return 0;
+    // Call real close? We can't easily without dlsym.
+    // Just assume it's fine or no-op.
+    return 0;
+}
+
+int __wrap_setsockopt(int sockfd, int level, int optname,
+                      const void *optval, socklen_t optlen) {
+    return 0;
+}
+
+int __wrap_getsockopt(int sockfd, int level, int optname,
+                      void *optval, socklen_t *optlen) {
+    // Fake buffer size to avoid errors
+    if (level == SOL_SOCKET && optname == SO_RCVBUF) {
+        int *val = (int*)optval;
+        *val = 32768;
+        return 0;
+    }
+    return 0;
+}
+
+int __wrap_fcntl(int fd, int cmd, ...) {
+    return 0; // Success
+}
+
+int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+    for (nfds_t i = 0; i < nfds; i++) {
+        if (fds[i].fd == g_fake_fd) {
+            fds[i].revents = 0;
+            if (fds[i].events & POLLIN) {
+                // Always say readable, so recv is called. 
+                // recv will return 0 if empty/EOF.
+                fds[i].revents |= POLLIN;
+            }
+            if (fds[i].events & POLLOUT) {
+                fds[i].revents |= POLLOUT;
+            }
+        }
+    }
+    return nfds;
+}
+
+int __wrap_getaddrinfo(const char *node, const char *service,
+                       const struct addrinfo *hints,
+                       struct addrinfo **res) {
+    // Return a fake address
+    struct addrinfo *ai = calloc(1, sizeof(struct addrinfo));
+    struct sockaddr_in *sa = calloc(1, sizeof(struct sockaddr_in));
+    
+    sa->sin_family = AF_INET;
+    sa->sin_port = htons(80);
+    sa->sin_addr.s_addr = htonl(0x7F000001); // 127.0.0.1
+
+    ai->ai_family = AF_INET;
+    ai->ai_socktype = SOCK_STREAM;
+    ai->ai_protocol = IPPROTO_TCP;
+    ai->ai_addr = (struct sockaddr*)sa;
+    ai->ai_addrlen = sizeof(struct sockaddr_in);
+    
+    *res = ai;
+    return 0;
+}
+
+void __wrap_freeaddrinfo(struct addrinfo *res) {
+    if (res) {
+        free(res->ai_addr);
+        free(res);
+    }
+}
+
+
+// --- Fuzzer Entry ---
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    AVFormatContext *fmt = NULL;
+    int ret;
+    
+    // Setup fuzz data
+    g_fuzz_data = data;
+    g_fuzz_size = size;
+    g_fuzz_pos = 0;
+
+    // We target HTTP for now as it's the most common complex protocol.
+    // The input data will be "read" by the HTTP client as the server response.
+    // Note: HTTP client writes a request first. Our mock send sinks it.
+    // Then it reads response.
+    
+    // We need to use a dummy URL that triggers TCP.
+    // "http://127.0.0.1/";
+    
+    // To fuzz different protocols, we could look at the first byte of data?
+    // Or just hardcode for now.
+    
+    ret = avformat_open_input(&fmt, "http://127.0.0.1/fuzz";, NULL, NULL);
+    if (ret >= 0) {
+        // If opened successfully, read some packets
+        AVPacket *pkt = av_packet_alloc();
+        int i = 0;
+        while (i++ < 100 && av_read_frame(fmt, pkt) >= 0) {
+            av_packet_unref(pkt);
+        }
+        av_packet_free(&pkt);
+        avformat_close_input(&fmt);
+    }
+
+    return 0;
+}
-- 
2.43.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to