| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/run_loop.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/browser/renderer_host/media/media_stream_dispatcher_host.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/mock_navigation_handle.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/test/fuzzer/media_stream_dispatcher_host_mojolpm_fuzzer.pb.h" |
| #include "content/test/fuzzer/mojolpm_fuzzer_support.h" |
| #include "content/test/test_render_frame_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/audio_system_impl.h" |
| #include "media/audio/audio_thread_impl.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-mojolpm.h" |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h" |
| #include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h" |
| |
| const char* kCmdline[] = { |
| "media_stream_dispatcher_host_mojolpm_fuzzer", |
| "--use-fake-device-for-media-stream", // Make sure we use fake devices to |
| // avoid long delays. |
| nullptr}; |
| |
| content::mojolpm::FuzzerEnvironment& GetEnvironment() { |
| static base::NoDestructor<content::mojolpm::FuzzerEnvironment> environment( |
| 2, kCmdline); |
| return *environment; |
| } |
| |
| scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() { |
| return GetEnvironment().fuzzer_task_runner(); |
| } |
| |
| class MediaStreamDispatcherHostTestcase |
| : public mojolpm::Testcase< |
| content::fuzzing::media_stream_dispatcher_host::proto::Testcase, |
| content::fuzzing::media_stream_dispatcher_host::proto::Action> { |
| public: |
| using ProtoTestcase = |
| content::fuzzing::media_stream_dispatcher_host::proto::Testcase; |
| using ProtoAction = |
| content::fuzzing::media_stream_dispatcher_host::proto::Action; |
| |
| explicit MediaStreamDispatcherHostTestcase(const ProtoTestcase& testcase); |
| ~MediaStreamDispatcherHostTestcase(); |
| |
| void SetUp(base::OnceClosure done_closure) override; |
| void TearDown(base::OnceClosure done_closure) override; |
| |
| void RunAction(const ProtoAction& action, |
| base::OnceClosure done_closure) override; |
| |
| private: |
| void SetUpOnUIThread(base::OnceClosure done_closure); |
| |
| void TearDownOnIOThread(); |
| void TearDownOnUIThread(base::OnceClosure done_closure); |
| |
| void AddMediaStreamDispatcherHost(uint32_t id, |
| base::OnceClosure done_closure); |
| void AddMediaStreamDispatcherHostImpl( |
| mojo::PendingReceiver<blink::mojom::MediaStreamDispatcherHost>&& |
| receiver); |
| |
| // Prerequisite state. |
| content::mojolpm::RenderViewHostTestHarnessAdapter test_adapter_; |
| std::unique_ptr<media::AudioManager> audio_manager_ = nullptr; |
| std::unique_ptr<media::AudioSystem> audio_system_ = nullptr; |
| std::unique_ptr<content::MediaStreamManager> media_stream_manager_ = nullptr; |
| raw_ptr<content::TestRenderFrameHost> render_frame_host_ = nullptr; |
| }; |
| |
| MediaStreamDispatcherHostTestcase::MediaStreamDispatcherHostTestcase( |
| const ProtoTestcase& testcase) |
| : Testcase<ProtoTestcase, ProtoAction>(testcase) { |
| test_adapter_.SetUp(); |
| } |
| |
| MediaStreamDispatcherHostTestcase::~MediaStreamDispatcherHostTestcase() { |
| test_adapter_.TearDown(); |
| audio_manager_.reset(); |
| media_stream_manager_.reset(); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::RunAction( |
| const ProtoAction& action, |
| base::OnceClosure run_closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const auto ThreadId_UI = content::fuzzing::media_stream_dispatcher_host:: |
| proto::RunThreadAction_ThreadId_UI; |
| const auto ThreadId_IO = content::fuzzing::media_stream_dispatcher_host:: |
| proto::RunThreadAction_ThreadId_IO; |
| |
| switch (action.action_case()) { |
| case ProtoAction::kRunThread: |
| // These actions ensure that any tasks currently queued on the named |
| // thread have chance to run before the fuzzer continues. |
| // |
| // We don't provide any particular guarantees here; this does not mean |
| // that the named thread is idle, nor does it prevent any other threads |
| // from running (or the consequences of any resulting callbacks, for |
| // example). |
| if (action.run_thread().id() == ThreadId_UI) { |
| content::GetUIThreadTaskRunner({})->PostTaskAndReply( |
| FROM_HERE, base::DoNothing(), std::move(run_closure)); |
| } else if (action.run_thread().id() == ThreadId_IO) { |
| content::GetIOThreadTaskRunner({})->PostTaskAndReply( |
| FROM_HERE, base::DoNothing(), std::move(run_closure)); |
| } |
| return; |
| |
| case ProtoAction::kNewMediaStreamDispatcherHost: |
| AddMediaStreamDispatcherHost( |
| action.new_media_stream_dispatcher_host().id(), |
| std::move(run_closure)); |
| return; |
| |
| case ProtoAction::kMediaStreamDispatcherHostRemoteAction: |
| mojolpm::HandleRemoteAction( |
| action.media_stream_dispatcher_host_remote_action()); |
| break; |
| |
| case ProtoAction::ACTION_NOT_SET: |
| break; |
| } |
| |
| GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(run_closure)); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::SetUp(base::OnceClosure done_closure) { |
| mojolpm::GetContext()->StartTestcase(); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamDispatcherHostTestcase::SetUpOnUIThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::SetUpOnUIThread( |
| base::OnceClosure done_closure) { |
| render_frame_host_ = |
| static_cast<content::TestWebContents*>(test_adapter_.web_contents()) |
| ->GetPrimaryMainFrame(); |
| |
| render_frame_host_->InitializeRenderFrameIfNeeded(); |
| |
| audio_manager_ = media::AudioManager::CreateForTesting( |
| std::make_unique<media::AudioThreadImpl>()); |
| |
| audio_system_ = |
| std::make_unique<media::AudioSystemImpl>(audio_manager_.get()); |
| |
| media_stream_manager_ = |
| std::make_unique<content::MediaStreamManager>(audio_system_.get()); |
| |
| GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(done_closure)); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::TearDown( |
| base::OnceClosure done_closure) { |
| mojolpm::GetContext()->EndTestcase(); |
| |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamDispatcherHostTestcase::TearDownOnIOThread, |
| base::Unretained(this))); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamDispatcherHostTestcase::TearDownOnUIThread, |
| base::Unretained(this), std::move(done_closure))); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::TearDownOnIOThread() { |
| media_stream_manager_->WillDestroyCurrentMessageLoop(); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::TearDownOnUIThread( |
| base::OnceClosure done_closure) { |
| audio_manager_->Shutdown(); |
| |
| std::move(done_closure).Run(); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::AddMediaStreamDispatcherHostImpl( |
| mojo::PendingReceiver<blink::mojom::MediaStreamDispatcherHost>&& receiver) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| // MediaStreamDispatcherHost is a self-owned receiver. |
| content::MediaStreamDispatcherHost::Create(render_frame_host_->GetGlobalId(), |
| media_stream_manager_.get(), |
| std::move(receiver)); |
| } |
| |
| // Component(s) which we fuzz |
| static void AddMediaStreamDispatcherHostInstance( |
| uint32_t id, |
| mojo::Remote<::blink::mojom::MediaStreamDispatcherHost>&& remote, |
| base::OnceClosure run_closure) { |
| mojolpm::GetContext()->AddInstance(id, std::move(remote)); |
| |
| std::move(run_closure).Run(); |
| } |
| |
| void MediaStreamDispatcherHostTestcase::AddMediaStreamDispatcherHost( |
| uint32_t id, |
| base::OnceClosure done_closure) { |
| mojo::Remote<blink::mojom::MediaStreamDispatcherHost> remote; |
| |
| auto receiver = remote.BindNewPipeAndPassReceiver(); |
| |
| content::GetIOThreadTaskRunner({})->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce( |
| &MediaStreamDispatcherHostTestcase::AddMediaStreamDispatcherHostImpl, |
| base::Unretained(this), std::move(receiver)), |
| base::BindOnce(AddMediaStreamDispatcherHostInstance, id, |
| std::move(remote), std::move(done_closure))); |
| } |
| |
| DEFINE_BINARY_PROTO_FUZZER( |
| const content::fuzzing::media_stream_dispatcher_host::proto::Testcase& |
| proto_testcase) { |
| // bail out early if no work to do |
| if (!proto_testcase.actions_size() || !proto_testcase.sequences_size() || |
| !proto_testcase.sequence_indexes_size()) { |
| return; |
| } |
| |
| // Make sure that the environment is initialized before we do anything else. |
| GetEnvironment(); |
| |
| MediaStreamDispatcherHostTestcase testcase(proto_testcase); |
| |
| base::RunLoop main_run_loop; |
| |
| // Unretained is safe here, because `main_run_loop` has to finish before |
| // testcase goes out of scope. |
| GetFuzzerTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&mojolpm::RunTestcase<MediaStreamDispatcherHostTestcase>, |
| base::Unretained(&testcase), GetFuzzerTaskRunner(), |
| main_run_loop.QuitClosure())); |
| |
| main_run_loop.Run(); |
| } |