1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stdint.h>
6 #include <memory>
7 #include <string>
8 #include <utility>
9
10 #include "base/at_exit.h"
11 #include "base/base_switches.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/command_line.h"
15 #include "base/i18n/icu_util.h"
16 #include "base/macros.h"
17 #include "base/optional.h"
18 #include "base/run_loop.h"
19 #include "base/task/post_task.h"
20 #include "base/test/test_switches.h"
21 #include "base/test/test_timeouts.h"
22 #include "base/threading/platform_thread.h"
23 #include "base/threading/thread.h"
24 #include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck
25 #include "content/browser/network_service_instance_impl.h" // nogncheck
26 #include "content/browser/presentation/presentation_service_impl.h" // nogncheck
27 #include "content/browser/presentation/presentation_test_utils.h"
28 #include "content/browser/site_instance_impl.h" // nogncheck
29 #include "content/public/browser/browser_task_traits.h"
30 #include "content/public/browser/presentation_request.h"
31 #include "content/public/browser/presentation_service_delegate.h"
32 #include "content/public/browser/site_instance.h"
33 #include "content/public/common/content_client.h"
34 #include "content/public/common/content_switches.h"
35 #include "content/public/test/browser_task_environment.h"
36 #include "content/public/test/mock_navigation_handle.h"
37 #include "content/public/test/test_browser_context.h"
38 #include "content/public/test/test_content_client_initializer.h"
39 #include "content/public/test/test_renderer_host.h"
40 #include "content/test/fuzzer/controller_presentation_service_delegate_for_fuzzing.h"
41 #include "content/test/fuzzer/presentation_service_mojolpm_fuzzer.pb.h"
42 #include "content/test/test_render_frame_host.h"
43 #include "content/test/test_web_contents.h"
44 #include "mojo/core/embedder/embedder.h"
45 #include "mojo/public/cpp/bindings/interface_ptr.h"
46 #include "mojo/public/cpp/bindings/pending_receiver.h"
47 #include "mojo/public/cpp/bindings/pending_remote.h"
48 #include "mojo/public/cpp/bindings/receiver.h"
49 #include "mojo/public/cpp/bindings/remote.h"
50 #include "third_party/blink/public/mojom/presentation/presentation.mojom-mojolpm.h"
51 #include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
52 #include "ui/events/devices/device_data_manager.h"
53
54 const char* cmdline[] = {"presentation_service_mojolpm_fuzzer", nullptr};
55
56 // Global environment needed to run the interface being tested.
57 //
58 // This will be created once, before fuzzing starts, and will be shared between
59 // all testcases. It is created on the main thread.
60 //
61 // At a minimum, we should always be able to set up the command line, i18n and
62 // mojo, and create the thread on which the fuzzer will be run. We want to avoid
63 // (as much as is reasonable) any state being preserved between testcases.
64 //
65 // We try to create an environment that matches the real browser process as
66 // much as possible, so we use real platform threads in the task environment.
67 class ContentFuzzerEnvironment {
68 public:
ContentFuzzerEnvironment()69 ContentFuzzerEnvironment()
70 : fuzzer_thread_((base::CommandLine::Init(1, cmdline), "fuzzer_thread")) {
71 TestTimeouts::Initialize();
72 logging::SetMinLogLevel(logging::LOG_FATAL);
73 mojo::core::Init();
74 base::i18n::InitializeICU();
75 fuzzer_thread_.StartAndWaitForTesting();
76
77 content::ForceCreateNetworkServiceDirectlyForTesting();
78 }
79
fuzzer_task_runner()80 scoped_refptr<base::SequencedTaskRunner> fuzzer_task_runner() {
81 return fuzzer_thread_.task_runner();
82 }
83
84 private:
85 base::AtExitManager at_exit_manager_;
86 base::Thread fuzzer_thread_;
87 content::TestContentClientInitializer content_client_initializer_;
88 };
89
GetEnvironment()90 ContentFuzzerEnvironment& GetEnvironment() {
91 static base::NoDestructor<ContentFuzzerEnvironment> environment;
92 return *environment;
93 }
94
GetFuzzerTaskRunner()95 scoped_refptr<base::SequencedTaskRunner> GetFuzzerTaskRunner() {
96 return GetEnvironment().fuzzer_task_runner();
97 }
98
99 // Per-testcase state needed to run the interface being tested.
100 //
101 // The lifetime of this is scoped to a single testcase, and it is created and
102 // destroyed from the fuzzer sequence.
103 //
104 // This owns the instance of `PresentationServiceImpl` to be fuzzed, and the
105 // mock delegate instance that will be used by the service instance.
106 // This class inherits from `RenderViewHostTestHarness` as the service
107 // instance relies on using a `RenderFrameHost` instance.
108 //
109 // TODO(clovispj) investigate performance loss this causes:
110 // The test harness has the drawback it owns a `BrowserTaskEnvironment`, so it
111 // becomes scoped per testcase - it would be preferred to be global to all.
112 //
113 // We use a single `PresentationServiceImpl` which can be bound to multiple
114 // remotes, to imitate true behaviour as much as possbile.
115 class PresentationServiceTestcase : public content::RenderViewHostTestHarness {
116 public:
117 explicit PresentationServiceTestcase(
118 const content::fuzzing::presentation_service::proto::Testcase& testcase);
119 ~PresentationServiceTestcase() override;
120
121 // Returns true once either all of the actions in the testcase have been
122 // performed, or the per-testcase action limit has been exceeded.
123 //
124 // This should only be called from the fuzzer sequence.
125 bool IsFinished();
126
127 // If there are still actions remaining in the testcase, this will perform the
128 // next sequence of actions before returning.
129 //
130 // If IsFinished() would return true, then calling this function is a no-op.
131 //
132 // This should only be called from the fuzzer sequence.
133 void NextAction();
134
135 private:
136 using Action = content::fuzzing::presentation_service::proto::Action;
137
138 void SetUp() override;
139 void SetUpOnUIThread();
140
141 void TearDown() override;
142 void TearDownOnUIThread();
143
144 // Create and bind a new instance for fuzzing. This needs to make sure that
145 // the new instance has been created and bound on the correct sequence before
146 // returning.
147 void AddPresentationService(uint32_t id);
148
TestBody()149 void TestBody() override {}
150
151 // The proto message describing the test actions to perform.
152 const content::fuzzing::presentation_service::proto::Testcase& testcase_;
153
154 // Apply a reasonable upper-bound on testcase complexity to avoid timeouts.
155 const int max_action_count_ = 512;
156
157 // Apply a reasonable upper-bound on maximum size of action that we will
158 // deserialize. (This is deliberately slightly larger than max mojo message
159 // size)
160 const size_t max_action_size_ = 300 * 1024 * 1024;
161
162 // Count of total actions performed in this testcase.
163 int action_count_ = 0;
164
165 // The index of the next sequence of actions to execute.
166 int next_sequence_idx_ = 0;
167
168 // A fake delegate which we can control with protobuf messages,
169 // the actions of which are also within our fuzzer's actions.
170 // Required as `PresentationServiceDelegateImpl` expects UI interaction.
171 std::unique_ptr<ControllerPresentationServiceDelegateForFuzzing>
172 controller_delegate_;
173
174 // Component which we fuzz
175 std::unique_ptr<content::PresentationServiceImpl> presentation_service_;
176
177 SEQUENCE_CHECKER(sequence_checker_);
178 };
179
PresentationServiceTestcase(const content::fuzzing::presentation_service::proto::Testcase & testcase)180 PresentationServiceTestcase::PresentationServiceTestcase(
181 const content::fuzzing::presentation_service::proto::Testcase& testcase)
182 : RenderViewHostTestHarness(
183 base::test::TaskEnvironment::TimeSource::MOCK_TIME,
184 base::test::TaskEnvironment::MainThreadType::DEFAULT,
185 base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC,
186 base::test::TaskEnvironment::ThreadingMode::MULTIPLE_THREADS,
187 content::BrowserTaskEnvironment::REAL_IO_THREAD),
188 testcase_(testcase) {
189 SetUp();
190 }
191
~PresentationServiceTestcase()192 PresentationServiceTestcase::~PresentationServiceTestcase() {
193 TearDown();
194 }
195
IsFinished()196 bool PresentationServiceTestcase::IsFinished() {
197 return next_sequence_idx_ >= testcase_.sequence_indexes_size();
198 }
199
NextAction()200 void PresentationServiceTestcase::NextAction() {
201 if (next_sequence_idx_ < testcase_.sequence_indexes_size()) {
202 auto sequence_idx = testcase_.sequence_indexes(next_sequence_idx_++);
203 const auto& sequence =
204 testcase_.sequences(sequence_idx % testcase_.sequences_size());
205 for (auto action_idx : sequence.action_indexes()) {
206 if (!testcase_.actions_size() || ++action_count_ > max_action_count_) {
207 return;
208 }
209 const auto& action =
210 testcase_.actions(action_idx % testcase_.actions_size());
211 if (action.ByteSizeLong() > max_action_size_) {
212 return;
213 }
214 switch (action.action_case()) {
215 case Action::kRunThread: {
216 if (action.run_thread().id()) {
217 base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
218 base::PostTask(FROM_HERE, {content::BrowserThread::UI},
219 run_loop.QuitClosure());
220 run_loop.Run();
221 } else {
222 base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
223 base::PostTask(FROM_HERE, {content::BrowserThread::IO},
224 run_loop.QuitClosure());
225 run_loop.Run();
226 }
227 } break;
228
229 case Action::kNewPresentationService: {
230 AddPresentationService(action.new_presentation_service().id());
231 } break;
232
233 case Action::kPresentationServiceRemoteAction: {
234 mojolpm::HandleRemoteAction(
235 action.presentation_service_remote_action());
236 } break;
237
238 case Action::kPresentationControllerReceiverAction: {
239 mojolpm::HandleReceiverAction(
240 action.presentation_controller_receiver_action());
241 } break;
242
243 case Action::kPresentationReceiverReceiverAction: {
244 mojolpm::HandleReceiverAction(
245 action.presentation_receiver_receiver_action());
246 } break;
247
248 case Action::kControllerDelegateAction: {
249 controller_delegate_->NextAction(action.controller_delegate_action());
250 } break;
251
252 case Action::ACTION_NOT_SET:
253 break;
254 }
255 }
256 }
257 }
258
SetUp()259 void PresentationServiceTestcase::SetUp() {
260 RenderViewHostTestHarness::SetUp();
261
262 base::RunLoop run_loop;
263 base::PostTaskAndReply(
264 FROM_HERE, {content::BrowserThread::UI},
265 base::BindOnce(&PresentationServiceTestcase::SetUpOnUIThread,
266 base::Unretained(this)),
267 run_loop.QuitClosure());
268 run_loop.Run();
269 }
270
SetUpOnUIThread()271 void PresentationServiceTestcase::SetUpOnUIThread() {
272 content::TestRenderFrameHost* render_frame_host =
273 static_cast<content::TestWebContents*>(web_contents())->GetMainFrame();
274 render_frame_host->InitializeRenderFrameIfNeeded();
275
276 presentation_service_ =
277 content::PresentationServiceImpl::Create(render_frame_host);
278
279 controller_delegate_ =
280 std::make_unique<ControllerPresentationServiceDelegateForFuzzing>();
281 presentation_service_->SetControllerDelegateForTesting(
282 controller_delegate_.get());
283 }
284
TearDown()285 void PresentationServiceTestcase::TearDown() {
286 base::RunLoop run_loop;
287 base::PostTaskAndReply(
288 FROM_HERE, {content::BrowserThread::UI},
289 base::BindOnce(&PresentationServiceTestcase::TearDownOnUIThread,
290 base::Unretained(this)),
291 run_loop.QuitClosure());
292 run_loop.Run();
293
294 RenderViewHostTestHarness::TearDown();
295 }
296
TearDownOnUIThread()297 void PresentationServiceTestcase::TearDownOnUIThread() {
298 presentation_service_.reset();
299 controller_delegate_.reset();
300 }
301
AddPresentationService(uint32_t id)302 void PresentationServiceTestcase::AddPresentationService(uint32_t id) {
303 mojo::Remote<::blink::mojom::PresentationService> remote;
304 auto receiver = remote.BindNewPipeAndPassReceiver();
305
306 // `Unretained` is safe here, as `run_loop.Run()` blocks until
307 // `PostTaskAndReply` calls the quit closure.
308 base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
309 base::PostTaskAndReply(
310 FROM_HERE, {content::BrowserThread::UI},
311 base::BindOnce(&content::PresentationServiceImpl::Bind,
312 base::Unretained(presentation_service_.get()),
313 std::move(receiver)),
314 run_loop.QuitClosure());
315 run_loop.Run();
316
317 mojolpm::GetContext()->AddInstance(id, std::move(remote));
318 }
319
320 // Helper function to keep scheduling fuzzer actions on the current runloop
321 // until the testcase has completed, and then quit the runloop.
NextAction(PresentationServiceTestcase * testcase,base::RepeatingClosure quit_closure)322 void NextAction(PresentationServiceTestcase* testcase,
323 base::RepeatingClosure quit_closure) {
324 if (!testcase->IsFinished()) {
325 testcase->NextAction();
326 GetFuzzerTaskRunner()->PostTask(
327 FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
328 std::move(quit_closure)));
329 } else {
330 GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(quit_closure));
331 }
332 }
333
334 // Helper function to setup and run the testcase, since we need to do that from
335 // the fuzzer sequence rather than the main thread.
RunTestcase(PresentationServiceTestcase * testcase)336 void RunTestcase(PresentationServiceTestcase* testcase) {
337 mojo::Message message;
338 auto dispatch_context =
339 std::make_unique<mojo::internal::MessageDispatchContext>(&message);
340
341 mojolpm::GetContext()->StartTestcase();
342
343 base::RunLoop fuzzer_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
344 GetFuzzerTaskRunner()->PostTask(
345 FROM_HERE, base::BindOnce(NextAction, base::Unretained(testcase),
346 fuzzer_run_loop.QuitClosure()));
347 fuzzer_run_loop.Run();
348
349 mojolpm::GetContext()->EndTestcase();
350 }
351
DEFINE_BINARY_PROTO_FUZZER(const content::fuzzing::presentation_service::proto::Testcase & proto_testcase)352 DEFINE_BINARY_PROTO_FUZZER(
353 const content::fuzzing::presentation_service::proto::Testcase&
354 proto_testcase) {
355 if (!proto_testcase.actions_size() || !proto_testcase.sequences_size() ||
356 !proto_testcase.sequence_indexes_size()) {
357 return;
358 }
359
360 // Make sure that the environment is initialized before we do anything else.
361 GetEnvironment();
362
363 PresentationServiceTestcase testcase(proto_testcase);
364
365 base::RunLoop ui_run_loop(base::RunLoop::Type::kNestableTasksAllowed);
366
367 // Unretained is safe here, because ui_run_loop has to finish before testcase
368 // goes out of scope.
369 GetFuzzerTaskRunner()->PostTaskAndReply(
370 FROM_HERE, base::BindOnce(RunTestcase, base::Unretained(&testcase)),
371 ui_run_loop.QuitClosure());
372
373 ui_run_loop.Run();
374 }
375