| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <vector> |
| |
| #include "base/at_exit.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/eme_constants.h" |
| #include "media/base/media.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/pipeline_status.h" |
| #include "media/media_buildflags.h" |
| #include "media/test/pipeline_integration_test_base.h" |
| #include "media/test/test_media_source.h" |
| #include "third_party/googletest/src/googletest/src/gtest-internal-inl.h" |
| |
| namespace { |
| |
| // Keep these aligned with BUILD.gn's pipeline_integration_fuzzer_variants |
| enum FuzzerVariant { |
| SRC, |
| WEBM_OPUS, |
| WEBM_VORBIS, |
| WEBM_VP8, |
| WEBM_VP9, |
| WEBM_OPUS_VP9, |
| #if BUILDFLAG(ENABLE_AV1_DECODER) |
| MP4_AV1, |
| #endif |
| MP4_FLAC, |
| MP4_OPUS, |
| MP3, |
| #if BUILDFLAG(USE_PROPRIETARY_CODECS) |
| ADTS, |
| MP4_AACLC, |
| MP4_AACSBR, |
| MP4_AVC1, |
| MP4_AACLC_AVC, |
| #if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) |
| MP2T_AACLC, |
| MP2T_AACSBR, |
| MP2T_AVC, |
| MP2T_MP3, |
| MP2T_AACLC_AVC, |
| #endif // BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) |
| #endif // BUILDFLAG(USE_PROPRIETARY_CODECS) |
| }; |
| |
| std::string MseFuzzerVariantEnumToMimeTypeString(FuzzerVariant variant) { |
| switch (variant) { |
| case WEBM_OPUS: |
| return "audio/webm; codecs=\"opus\""; |
| case WEBM_VORBIS: |
| return "audio/webm; codecs=\"vorbis\""; |
| case WEBM_VP8: |
| return "video/webm; codecs=\"vp8\""; |
| case WEBM_VP9: |
| return "video/webm; codecs=\"vp9\""; |
| case WEBM_OPUS_VP9: |
| return "video/webm; codecs=\"opus,vp9\""; |
| #if BUILDFLAG(ENABLE_AV1_DECODER) |
| case MP4_AV1: |
| return "video/mp4; codecs=\"av01.0.04M.08\""; |
| #endif // BUILDFLAG(ENABLE_AV1_DECODER) |
| case MP4_FLAC: |
| return "audio/mp4; codecs=\"flac\""; |
| case MP4_OPUS: |
| return "audio/mp4; codecs=\"opus\""; |
| case MP3: |
| return "audio/mpeg"; |
| #if BUILDFLAG(USE_PROPRIETARY_CODECS) |
| case ADTS: |
| return "audio/aac"; |
| case MP4_AACLC: |
| return "audio/mp4; codecs=\"mp4a.40.2\""; |
| case MP4_AACSBR: |
| return "audio/mp4; codecs=\"mp4a.40.5\""; |
| case MP4_AVC1: |
| return "video/mp4; codecs=\"avc1.42E01E\""; |
| case MP4_AACLC_AVC: |
| return "video/mp4; codecs=\"mp4a.40.2,avc1.42E01E\""; |
| #if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) |
| case MP2T_AACLC: |
| return "video/mp2t; codecs=\"mp4a.67\""; |
| case MP2T_AACSBR: |
| return "video/mp2t; codecs=\"mp4a.40.5\""; |
| case MP2T_AVC: |
| return "video/mp2t; codecs=\"avc1.42E01E\""; |
| case MP2T_MP3: |
| // Note, "mp4a.6B" appears to be an equivalent codec substring. |
| return "video/mp2t; codecs=\"mp4a.69\""; |
| case MP2T_AACLC_AVC: |
| return "video/mp2t; codecs=\"mp4a.40.2,avc1.42E01E\""; |
| #endif // BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) |
| #endif // BUILDFLAG(USE_PROPRIETARY_CODECS) |
| |
| case SRC: |
| NOTREACHED() << "SRC is an invalid MSE fuzzer variant"; |
| break; |
| } |
| |
| return ""; |
| } |
| |
| } // namespace |
| |
| namespace media { |
| |
| // Limit the amount of initial (or post-seek) audio silence padding allowed in |
| // rendering of fuzzed input. |
| constexpr base::TimeDelta kMaxPlayDelay = base::Seconds(10); |
| |
| void OnEncryptedMediaInitData(media::PipelineIntegrationTestBase* test, |
| media::EmeInitDataType /* type */, |
| const std::vector<uint8_t>& /* init_data */) { |
| // Encrypted media is not supported in this test. For an encrypted media file, |
| // we will start demuxing the data but media pipeline will wait for a CDM to |
| // be available to start initialization, which will not happen in this case. |
| // To prevent the test timeout, we'll just fail the test immediately here. |
| // Note: Since the callback is on the media task runner but the test is on |
| // the main task runner, this must be posted. |
| // TODO(xhwang): Support encrypted media in this fuzzer test. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&PipelineIntegrationTestBase::FailTest, |
| base::Unretained(test), |
| media::PIPELINE_ERROR_INITIALIZATION_FAILED)); |
| } |
| |
| void OnAudioPlayDelay(media::PipelineIntegrationTestBase* test, |
| base::TimeDelta play_delay) { |
| CHECK_GT(play_delay, base::TimeDelta()); |
| if (play_delay > kMaxPlayDelay) { |
| // Note: Since the callback is on the media task runner but the test is on |
| // the main task runner, this must be posted. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&PipelineIntegrationTestBase::FailTest, |
| base::Unretained(test), |
| media::PIPELINE_ERROR_INITIALIZATION_FAILED)); |
| } |
| } |
| |
| class ProgressivePipelineIntegrationFuzzerTest |
| : public PipelineIntegrationTestBase { |
| public: |
| ProgressivePipelineIntegrationFuzzerTest() { |
| set_encrypted_media_init_data_cb( |
| base::BindRepeating(&OnEncryptedMediaInitData, this)); |
| set_audio_play_delay_cb( |
| BindToCurrentLoop(base::BindRepeating(&OnAudioPlayDelay, this))); |
| } |
| |
| ~ProgressivePipelineIntegrationFuzzerTest() override = default; |
| |
| void RunTest(const uint8_t* data, size_t size) { |
| if (PIPELINE_OK != Start(data, size, kUnreliableDuration | kFuzzing)) |
| return; |
| |
| Play(); |
| if (PIPELINE_OK != WaitUntilEndedOrError()) |
| return; |
| |
| Seek(base::TimeDelta()); |
| } |
| }; |
| |
| class MediaSourcePipelineIntegrationFuzzerTest |
| : public PipelineIntegrationTestBase { |
| public: |
| MediaSourcePipelineIntegrationFuzzerTest() { |
| set_encrypted_media_init_data_cb( |
| base::BindRepeating(&OnEncryptedMediaInitData, this)); |
| set_audio_play_delay_cb( |
| BindToCurrentLoop(base::BindRepeating(&OnAudioPlayDelay, this))); |
| } |
| |
| ~MediaSourcePipelineIntegrationFuzzerTest() override = default; |
| |
| void RunTest(const uint8_t* data, size_t size, const std::string& mimetype) { |
| if (size == 0) |
| return; |
| |
| scoped_refptr<media::DecoderBuffer> buffer( |
| DecoderBuffer::CopyFrom(data, size)); |
| |
| TestMediaSource source(buffer, mimetype, kAppendWholeFile); |
| |
| // Prevent timeout in the case of not enough media appended to complete |
| // demuxer initialization, yet no error in the media appended. The |
| // following will trigger DEMUXER_ERROR_COULD_NOT_OPEN state transition in |
| // this case. |
| source.set_do_eos_after_next_append(true); |
| |
| source.set_encrypted_media_init_data_cb( |
| base::BindRepeating(&OnEncryptedMediaInitData, this)); |
| |
| // Allow parsing to either pass or fail without emitting a gtest failure |
| // from TestMediaSource. |
| source.set_expected_append_result( |
| TestMediaSource::ExpectedAppendResult::kSuccessOrFailure); |
| |
| // TODO(wolenetz): Vary the behavior (abort/remove/seek/endOfStream/Append |
| // in pieces/append near play-head/vary append mode/etc), perhaps using |
| // CustomMutator and Seed to insert/update the variation information into/in |
| // the |data| we process here. See https://crbug.com/750818. |
| // Use |kFuzzing| test type to allow pipeline start to either pass or fail |
| // without emitting a gtest failure. |
| if (PIPELINE_OK != StartPipelineWithMediaSource(&source, kFuzzing, nullptr)) |
| return; |
| |
| Play(); |
| } |
| }; |
| |
| } // namespace media |
| |
| // Disable noisy logging. |
| struct Environment { |
| Environment() { |
| base::CommandLine::Init(0, nullptr); |
| |
| // |test| instances uses TaskEnvironment, which needs TestTimeouts. |
| TestTimeouts::Initialize(); |
| |
| media::InitializeMediaLibrary(); |
| |
| // Note, instead of LOG_FATAL, use a value at or below logging::LOG_VERBOSE |
| // here to assist local debugging. |
| logging::SetMinLogLevel(logging::LOG_FATAL); |
| } |
| }; |
| |
| Environment* env = new Environment(); |
| |
| // Entry point for LibFuzzer. |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| // Media pipeline starts new threads, which needs AtExitManager. |
| base::AtExitManager at_exit; |
| |
| FuzzerVariant variant = PIPELINE_FUZZER_VARIANT; |
| |
| // These tests use GoogleTest assertions without using the GoogleTest |
| // framework. While this is the case, tell GoogleTest's stack trace getter |
| // that GoogleTest is being left now so that there is a basis for traces |
| // collected upon assertion failure. TODO(https://crbug.com/1039559): use |
| // RUN_ALL_TESTS() and remove this code. |
| ::testing::internal::GetUnitTestImpl() |
| ->os_stack_trace_getter() |
| ->UponLeavingGTest(); |
| if (variant == SRC) { |
| media::ProgressivePipelineIntegrationFuzzerTest test; |
| test.RunTest(data, size); |
| } else { |
| media::MediaSourcePipelineIntegrationFuzzerTest test; |
| test.RunTest(data, size, MseFuzzerVariantEnumToMimeTypeString(variant)); |
| } |
| |
| return 0; |
| } |