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