diff --git a/fuzzers/common.h b/fuzzers/common.h
new file mode 100644
index 0000000000..3b329029e9
--- /dev/null
+++ b/fuzzers/common.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#define MPV_STRINGIFY_(X) #X
+#define MPV_STRINGIFY(X) MPV_STRINGIFY_(X)
+
+static inline void check_error(int status)
+{
+ if (status < 0) {
+ fprintf(stderr, "mpv API error: %s\n", mpv_error_string(status));
+ exit(1);
+ }
+}
+
+static inline bool str_startswith(const char *str, size_t str_len,
+ const char *prefix, size_t prefix_len)
+{
+ if (str_len < prefix_len)
+ return false;
+ return !memcmp(str, prefix, prefix_len);
+}
diff --git a/fuzzers/fuzzer_loadfile.c b/fuzzers/fuzzer_loadfile.c
new file mode 100644
index 0000000000..e9dd4ca503
--- /dev/null
+++ b/fuzzers/fuzzer_loadfile.c
@@ -0,0 +1,71 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include "common.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if (size == 0)
+ return -1;
+
+ char filename[15 + 10 + 1];
+ sprintf(filename, "/tmp/libfuzzer.%d", getpid());
+
+ FILE *fp = fopen(filename, "wb");
+ if (!fp)
+ exit(1);
+
+ if (fwrite(data, size, 1, fp) != 1)
+ exit(1);
+
+ if (fclose(fp))
+ exit(1);
+
+ mpv_handle *ctx = mpv_create();
+ if (!ctx)
+ exit(1);
+
+ check_error(mpv_set_option_string(ctx, "vo", "null"));
+ check_error(mpv_set_option_string(ctx, "ao", "null"));
+ check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "video-osd", "no"));
+ check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
+
+ check_error(mpv_initialize(ctx));
+
+ const char *cmd[] = {"loadfile", filename, NULL};
+ check_error(mpv_command(ctx, cmd));
+
+ while (1) {
+ mpv_event *event = mpv_wait_event(ctx, 10000);
+ if (event->event_id == MPV_EVENT_IDLE)
+ break;
+ }
+
+ mpv_terminate_destroy(ctx);
+
+ return 0;
+}
diff --git a/fuzzers/fuzzer_loadfile_direct.c b/fuzzers/fuzzer_loadfile_direct.c
new file mode 100644
index 0000000000..f6578f9f98
--- /dev/null
+++ b/fuzzers/fuzzer_loadfile_direct.c
@@ -0,0 +1,77 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see .
+ */
+
+#include
+#include
+
+#include
+
+#include
+
+#include "common.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ if (size <= 1 || data[size - 1] != '\0')
+ return -1;
+
+ // Exlude data with null bytes inside
+ if (strlen(data) != size - 1)
+ return -1;
+
+#ifdef MPV_PROTO
+ if (!str_startswith(data, size - 1, MPV_STRINGIFY(MPV_PROTO) "://", strlen(MPV_STRINGIFY(MPV_PROTO) "://")))
+ return -1;
+#else
+ // Exclude some common paths that are not useful for testing.
+ // Exclude -
+ if (size == 2 && !strncmp(data, "-", 1))
+ return -1;
+ // Exclude relative paths
+ if (str_startswith(data, size - 1, ".", 1))
+ return -1;
+ // Exclude absolute paths
+ if (str_startswith(data, size - 1, "/", 1))
+ return -1;
+#endif
+
+ mpv_handle *ctx = mpv_create();
+ if (!ctx)
+ exit(1);
+
+ check_error(mpv_set_option_string(ctx, "vo", "null"));
+ check_error(mpv_set_option_string(ctx, "ao", "null"));
+ check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "video-osd", "no"));
+ check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
+
+ check_error(mpv_initialize(ctx));
+
+ const char *cmd[] = {"loadfile", data, NULL};
+ check_error(mpv_command(ctx, cmd));
+
+ while (1) {
+ mpv_event *event = mpv_wait_event(ctx, 10000);
+ if (event->event_id == MPV_EVENT_IDLE)
+ break;
+ }
+
+ mpv_terminate_destroy(ctx);
+
+ return 0;
+}
diff --git a/fuzzers/fuzzer_set_property.c b/fuzzers/fuzzer_set_property.c
new file mode 100644
index 0000000000..a11573825d
--- /dev/null
+++ b/fuzzers/fuzzer_set_property.c
@@ -0,0 +1,89 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see .
+ */
+
+#include
+
+#include "common.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ size_t value_len;
+ switch (MPV_FORMAT)
+ {
+ case MPV_FORMAT_STRING:
+ value_len = strnlen(data, size);
+ if (!value_len || value_len == size)
+ return -1;
+ value_len += 1;
+ break;
+ case MPV_FORMAT_FLAG:
+ value_len = sizeof(int);
+ break;
+ case MPV_FORMAT_INT64:
+ value_len = sizeof(int64_t);
+ break;
+ case MPV_FORMAT_DOUBLE:
+ value_len = sizeof(double);
+ break;
+ default:
+ exit(1);
+ break;
+ }
+
+ // at least two bytes for the name
+ if (size < value_len + 2)
+ return -1;
+
+ const char *name = (const char *)data + value_len;
+ size_t name_len = strnlen(name, size - value_len);
+ if (!name_len || name_len != size - value_len - 1)
+ return -1;
+
+ mpv_handle *ctx = mpv_create();
+ if (!ctx)
+ exit(1);
+
+#if MPV_RUN
+ check_error(mpv_set_option_string(ctx, "vo", "null"));
+ check_error(mpv_set_option_string(ctx, "ao", "null"));
+ check_error(mpv_set_option_string(ctx, "ao-null-untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "untimed", "yes"));
+ check_error(mpv_set_option_string(ctx, "video-osd", "no"));
+ check_error(mpv_set_option_string(ctx, "msg-level", "all=trace"));
+
+ check_error(mpv_initialize(ctx));
+#endif
+
+ const void *value = data;
+ mpv_set_property(ctx, name, MPV_FORMAT, &value);
+
+#if MPV_RUN
+ check_error(mpv_set_option_string(ctx, "audio-files", "av://lavfi:sine=d=0.1"));
+ const char *cmd[] = {"loadfile", "av://lavfi:yuvtestsrc=d=0.1", NULL};
+ check_error(mpv_command(ctx, cmd));
+
+ while (1) {
+ mpv_event *event = mpv_wait_event(ctx, 10000);
+ if (event->event_id == MPV_EVENT_IDLE)
+ break;
+ }
+#endif
+
+ mpv_terminate_destroy(ctx);
+
+ return 0;
+}
diff --git a/fuzzers/meson.build b/fuzzers/meson.build
new file mode 100644
index 0000000000..a5a3ba7145
--- /dev/null
+++ b/fuzzers/meson.build
@@ -0,0 +1,26 @@
+incdir = include_directories('../')
+
+executable('fuzzer_loadfile', 'fuzzer_loadfile.c',
+ include_directories: incdir, link_with: libmpv)
+executable('fuzzer_loadfile_direct', 'fuzzer_loadfile_direct.c',
+ include_directories: incdir, link_with: libmpv)
+
+foreach p : ['bd', 'cdda', 'dvb', 'dvd', 'edl', 'file', 'hex', 'lavf', 'memory',
+ 'mf', 'slice', 'smb']
+ executable('fuzzer_protocol_' + p,
+ 'fuzzer_loadfile_direct.c',
+ c_args: ['-DMPV_PROTO=' + p],
+ include_directories: incdir,
+ link_with: libmpv)
+endforeach
+
+
+foreach f : ['MPV_FORMAT_STRING', 'MPV_FORMAT_FLAG', 'MPV_FORMAT_INT64', 'MPV_FORMAT_DOUBLE']
+ foreach i : ['0', '1']
+ executable('fuzzer_set_property_' + f + '_' + i,
+ 'fuzzer_set_property.c',
+ c_args: ['-DMPV_FORMAT=' + f, '-DMPV_RUN=' + i],
+ include_directories: incdir,
+ link_with: libmpv)
+ endforeach
+endforeach
diff --git a/meson.build b/meson.build
index be43e8e1c8..a7edacb7a7 100644
--- a/meson.build
+++ b/meson.build
@@ -379,6 +379,15 @@ pthread_debug = get_option('pthread-debug').require(
)
features += {'pthread-debug': pthread_debug.allowed()}
+if get_option('fuzzers')
+ if get_option('cplayer') or not get_option('libmpv')
+ error('fuzzers require !cplayer and libmpv')
+ endif
+ # Adding flags manually until https://github.com/mesonbuild/meson/pull/9825
+ flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
+ link_flags += ['-fsanitize=address,undefined,fuzzer', '-fno-omit-frame-pointer']
+endif
+
add_project_arguments(flags, language: 'c')
add_project_arguments(['-Wno-unused-parameter'], language: 'objc')
add_project_link_arguments(link_flags, language: ['c', 'objc'])
@@ -1803,6 +1812,10 @@ if get_option('tests')
subdir('test')
endif
+if get_option('fuzzers')
+ subdir('fuzzers')
+endif
+
summary({'d3d11': features['d3d11'],
'javascript': features['javascript'],
'libmpv': get_option('libmpv'),
diff --git a/meson_options.txt b/meson_options.txt
index e488f6ee88..aa80e927de 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,6 +4,7 @@ option('cplayer', type: 'boolean', value: true, description: 'mpv CLI player')
option('libmpv', type: 'boolean', value: false, description: 'libmpv library')
option('build-date', type: 'boolean', value: true, description: 'include compile timestamp in binary')
option('tests', type: 'boolean', value: false, description: 'meson unit tests')
+option('fuzzers', type: 'boolean', value: false, description: 'fuzzer binaries')
# Reminder: normally always built, but enabled by MPV_LEAK_REPORT.
# Building it can be disabled only by defining NDEBUG through CFLAGS.
option('ta-leak-report', type: 'boolean', value: false, description: 'enable ta leak report by default (development only)')