blob: 75caa343e12d887abec6a59be886b8e796032570 [file] [log] [blame]
// 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;
}