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)')