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]
