// Copyright 2020 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 <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread.h"
#include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck
#include "content/browser/presentation/presentation_service_impl.h" // nogncheck
#include "content/browser/presentation/presentation_test_utils.h"
#include "content/browser/site_instance_impl.h" // nogncheck
#include "content/public/browser/presentation_request.h"
#include "content/public/browser/presentation_service_delegate.h"
#include "content/public/browser/site_instance.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/fuzzer/controller_presentation_service_delegate_for_fuzzing.h"
#include "content/test/fuzzer/mojolpm_fuzzer_support.h"
#include "content/test/fuzzer/presentation_service_mojolpm_fuzzer.pb.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.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/presentation/presentation.mojom-mojolpm.h"
#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
#include "ui/events/devices/device_data_manager.h"
const char* kCmdline[] = {"presentation_service_mojolpm_fuzzer", nullptr};
content::mojolpm::FuzzerEnvironment& GetEnvironment() {
static base::NoDestructor<content::mojolpm::FuzzerEnvironment> environment(
1, kCmdline);
return *environment;
}
scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() {
return GetEnvironment().fuzzer_task_runner();
}
// Per-testcase state needed to run the interface being tested.
//
// The lifetime of this is scoped to a single testcase, and it is created and
// destroyed from the fuzzer sequence.
//
// This owns the instance of `PresentationServiceImpl` to be fuzzed, and the
// mock delegate instance that will be used by the service instance.
// This class inherits from `RenderViewHostTestHarness` as the service
// instance relies on using a `RenderFrameHost` instance.
//
// TODO(clovispj) investigate performance loss this causes:
// The test harness has the drawback it owns a `BrowserTaskEnvironment`, so it
// becomes scoped per testcase - it would be preferred to be global to all.
//
// We use a single `PresentationServiceImpl` which can be bound to multiple
// remotes, to imitate true behaviour as much as possbile.
class PresentationServiceTestcase : public content::RenderViewHostTestHarness {
public:
explicit PresentationServiceTestcase(
const content::fuzzing::presentation_service::proto::Testcase& testcase);
~PresentationServiceTestcase() override;
// Returns true once either all of the actions in the testcase have been
// performed, or the per-testcase action limit has been exceeded.
//
// This should only be called from the fuzzer sequence.
bool IsFinished();
// If there are still actions remaining in the testcase, this will perform the
// next sequence of actions before returning.
//
// If IsFinished() would return true, then calling this function is a no-op.
//
// This should only be called from the fuzzer sequence.
void NextAction();
private:
using Action = content::fuzzing::presentation_service::proto::Action;
void SetUp() override;
void SetUpOnUIThread();
void TearDown() override;
void TearDownOnUIThread();
// Create and bind a new instance for fuzzing. This needs to make sure that
// the new instance has been created and bound on the correct sequence before
// returning.
void AddPresentationService(uint32_t id);
void TestBody() override {}
// The proto message describing the test actions to perform.
const raw_ref<const content::fuzzing::presentation_service::proto::Testcase>
testcase_;
// Apply a reasonable upper-bound on testcase complexity to avoid timeouts.
const int max_action_count_ = 512;
// Apply a reasonable upper-bound on maximum size of action that we will
// deserialize. (This is deliberately slightly larger than max mojo message
// size)
const size_t max_action_size_ = 300 * 1024 * 1024;
// Count of total actions performed in this testcase.
int action_count_ = 0;
// The index of the next sequence of actions to execute.
int next_sequence_idx_ = 0;
// A fake delegate which we can control with protobuf messages,
// the actions of which are also within our fuzzer's actions.
// Required as `PresentationServiceDelegateImpl` expects UI interaction.
std::unique_ptr<ControllerPresentationServiceDelegateForFuzzing>
controller_delegate_;
// Component which we fuzz
std::unique_ptr<content::PresentationServiceImpl> presentation_service_;
SEQUENCE_CHECKER(sequence_checker_);
};
PresentationServiceTestcase::PresentationServiceTestcase(
const content::fuzzing::presentation_service::proto::Testcase& testcase)
: RenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::MainThreadType::DEFAULT,
base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC,
base::test::TaskEnvironment::ThreadingMode::MULTIPLE_THREADS,
content::BrowserTaskEnvironment::REAL_IO_THREAD),
testcase_(testcase) {
SetUp();
}
PresentationServiceTestcase::~PresentationServiceTestcase() {
TearDown();
}
bool PresentationServiceTestcase::IsFinished() {
return next_sequence_idx_ >= testcase_->sequence_indexes_size();
}
void PresentationServiceTestcase::NextAction() {
if (next_sequence_idx_ < testcase_->sequence_indexes_size()) {
auto sequence_idx = testcase_->sequence_indexes(next_sequence_idx_++);
const auto& sequence =
testcase_->sequences(sequence_idx % testcase_->sequences_size());
for (auto action_idx : sequence.action_indexes()) {
if (!testcase_->actions_size() || ++action_count_ > max_action_count_) {
return;
}
const auto& action =
testcase_->actions(action_idx % testcase_->actions_size());
if (action.ByteSizeLong() > max_action_size_) {
return;
}
switch (action.action_case()) {
case Action::kRunThread: {
if (action.run_thread().id()) {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
} else {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
} break;
case Action::kNewPresentationService: {
AddPresentationService(action.new_presentation_service().id());
} break;
case Action::kPresentationServiceRemoteAction: {
mojolpm::HandleRemoteAction(
action.presentation_service_remote_action());
} break;
case Action::kPresentationControllerReceiverAction: {
mojolpm::HandleReceiverAction(
action.presentation_controller_receiver_action());
} break;
case Action::kPresentationReceiverReceiverAction: {
mojolpm::HandleReceiverAction(
action.presentation_receiver_receiver_action());
} break;
case Action::kControllerDelegateAction: {
controller_delegate_->NextAction(action.controller_delegate_action());
} break;
case Action::ACTION_NOT_SET:
break;
}
}
}
}
void PresentationServiceTestcase::SetUp() {
RenderViewHostTestHarness::SetUp();
base::RunLoop run_loop;
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&PresentationServiceTestcase::SetUpOnUIThread,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
}
void PresentationServiceTestcase::SetUpOnUIThread() {
content::TestRenderFrameHost* render_frame_host =
static_cast<content::TestWebContents*>(web_contents())
->GetPrimaryMainFrame();
render_frame_host->InitializeRenderFrameIfNeeded();
presentation_service_ =
content::PresentationServiceImpl::Create(render_frame_host);
controller_delegate_ =
std::make_unique<ControllerPresentationServiceDelegateForFuzzing>();
presentation_service_->SetControllerDelegateForTesting(
controller_delegate_.get());
}
void PresentationServiceTestcase::TearDown() {
base::RunLoop run_loop;
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&PresentationServiceTestcase::TearDownOnUIThread,
base::Unretained(this)),
run_loop.QuitClosure());
run_loop.Run();
RenderViewHostTestHarness::TearDown();
}
void PresentationServiceTestcase::TearDownOnUIThread() {
presentation_service_.reset();
controller_delegate_.reset();
}
void PresentationServiceTestcase::AddPresentationService(uint32_t id) {
mojo::Remote<::blink::mojom::PresentationService> remote;
auto receiver = remote.BindNewPipeAndPassReceiver();
// `Unretained` is safe here, as `run_loop.Run()` blocks until
// `PostTaskAndReply` calls the quit closure.
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&content::PresentationServiceImpl::Bind,
base::Unretained(presentation_service_.get()),
std::move(receiver)),
run_loop.QuitClosure());
run_loop.Run();
mojolpm::GetContext()->AddInstance(id, std::move(remote));
}
// Helper function to keep scheduling fuzzer actions on the current runloop
// until the testcase has completed, and then quit the runloop.
void NextAction(PresentationServiceTestcase* testcase,
base::RepeatingClosure quit_closure) {
if (!testcase->IsFinished()) {
testcase->NextAction();
GetFuzzerTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
std::move(quit_closure)));
} else {
GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(quit_closure));
}
}
// Helper function to setup and run the testcase, since we need to do that from
// the fuzzer sequence rather than the main thread.
void RunTestcase(PresentationServiceTestcase* testcase) {
mojo::Message message;
auto dispatch_context =
std::make_unique<mojo::internal::MessageDispatchContext>(&message);
mojolpm::GetContext()->StartTestcase();
base::RunLoop fuzzer_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
GetFuzzerTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
fuzzer_run_loop.QuitClosure()));
fuzzer_run_loop.Run();
mojolpm::GetContext()->EndTestcase();
}
DEFINE_BINARY_PROTO_FUZZER(
const content::fuzzing::presentation_service::proto::Testcase&
proto_testcase) {
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();
PresentationServiceTestcase testcase(proto_testcase);
base::RunLoop ui_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
// Unretained is safe here, because ui_run_loop has to finish before testcase
// goes out of scope.
GetFuzzerTaskRunner()->PostTaskAndReply(
FROM_HERE, base::BindOnce(RunTestcase, base::Unretained(&testcase)),
ui_run_loop.QuitClosure());
ui_run_loop.Run();
}