chromium/content/test/fuzzer/file_system_manager_mojolpm_fuzzer.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include <stdint.h>
#include <utility>

#include "base/files/scoped_temp_dir.h"
#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"  // nogncheck
#include "content/browser/file_system/file_system_manager_impl.h"  // nogncheck
#include "content/browser/storage_partition_impl_map.h"            // nogncheck
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_content_client_initializer.h"
#include "content/test/fuzzer/file_system_manager_mojolpm_fuzzer.pb.h"
#include "content/test/fuzzer/mojolpm_fuzzer_support.h"
#include "mojo/public/tools/fuzzers/mojolpm.h"
#include "storage/browser/file_system/file_permission_policy.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/quota/special_storage_policy.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "storage/browser/test/test_file_system_context.h"
#include "testing/libfuzzer/proto/url_proto_converter.h"
#include "third_party/blink/public/common/storage_key/proto/storage_key.pb.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/common/storage_key/storage_key_proto_converter.h"
#include "third_party/blink/public/mojom/filesystem/file_system.mojom-mojolpm.h"
#include "third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h"
#include "url/gurl.h"
#include "url/origin.h"

using url::Origin;

namespace content {

const size_t kNumRenderers = 2;
const char* kCmdline[] = {"file_system_manager_mojolpm_fuzzer", nullptr};

mojolpm::FuzzerEnvironment& GetEnvironment() {
  static base::NoDestructor<mojolpm::FuzzerEnvironmentWithTaskEnvironment>
      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.
//
// For FileSystemManager, this needs the basic common Browser process state
// provided by TestBrowserContext, and to set up the storage contexts that will
// be used. The filesystem APIs also depend on the Blob subsystem, so in
// addition to the FileSystemContext we also need a BlobStorageContext.
//
// Since the Browser process will host one FileSystemManagerImpl per
// RenderProcessHost, we emulate this by allowing the fuzzer to create (and
// destroy) multiple FileSystemManagerImpl instances.
class FileSystemManagerTestcase
    : public ::mojolpm::Testcase<
          content::fuzzing::file_system_manager::proto::Testcase,
          content::fuzzing::file_system_manager::proto::Action> {
 public:
  using ProtoTestcase = content::fuzzing::file_system_manager::proto::Testcase;
  using ProtoAction = content::fuzzing::file_system_manager::proto::Action;

  explicit FileSystemManagerTestcase(
      const content::fuzzing::file_system_manager::proto::Testcase& testcase);

  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 SetUpOnIOThread(base::OnceClosure done_closure);
  void SetUpOnUIThread(base::OnceClosure done_closure);
  void TearDownOnIOThread(base::OnceClosure done_closure);
  void TearDownOnUIThread(base::OnceClosure done_closure);

  // Used by AddFileSystemManager to create and bind FileSystemManagerImpl on the
  // UI thread.
  void AddFileSystemManagerImpl(
      uint32_t id,
      content::fuzzing::file_system_manager::proto::NewFileSystemManagerAction::
          RenderProcessId render_process_id,
      const storage_key_proto::StorageKey& storage_key,
      mojo::PendingReceiver<::blink::mojom::FileSystemManager>&& receiver);

  // 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 AddFileSystemManager(
      uint32_t id,
      content::fuzzing::file_system_manager::proto::NewFileSystemManagerAction::
          RenderProcessId render_process_id,
      const storage_key_proto::StorageKey& storage_key,
      base::OnceClosure done_closure);

  // Prerequisite state
  TestBrowserContext browser_context_;
  base::ScopedTempDir temp_dir_;
  scoped_refptr<storage::FileSystemContext> file_system_context_;
  scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;

  // Mapping from renderer id to FileSystemManagerImpl instances being fuzzed.
  // Access only from UI thread.
  std::unique_ptr<FileSystemManagerImpl>
      file_system_manager_impls_[kNumRenderers];
};

FileSystemManagerTestcase::FileSystemManagerTestcase(
    const content::fuzzing::file_system_manager::proto::Testcase& testcase)
    : Testcase<ProtoTestcase, ProtoAction>(testcase), browser_context_() {
  // FileSystemManagerTestcase is created on the main thread, but the actions
  // that we want to validate the sequencing of take place on the fuzzer
  // sequence.
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

void FileSystemManagerTestcase::SetUp(base::OnceClosure done_closure) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileSystemManagerTestcase::SetUpOnIOThread,
                     base::Unretained(this), std::move(done_closure)));
}

void FileSystemManagerTestcase::SetUpOnIOThread(
    base::OnceClosure done_closure) {
  CHECK(temp_dir_.CreateUniqueTempDir());
  file_system_context_ =
      storage::CreateFileSystemContextForTesting(nullptr, temp_dir_.GetPath());

  blob_storage_context_ = base::MakeRefCounted<ChromeBlobStorageContext>();
  blob_storage_context_->InitializeOnIOThread(
      temp_dir_.GetPath(), temp_dir_.GetPath(), GetIOThreadTaskRunner({}));

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileSystemManagerTestcase::SetUpOnUIThread,
                     base::Unretained(this), std::move(done_closure)));
}

void FileSystemManagerTestcase::SetUpOnUIThread(
    base::OnceClosure done_closure) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  p->RegisterFileSystemPermissionPolicy(storage::kFileSystemTypeTest,
                                        storage::FILE_PERMISSION_SANDBOX);
  p->RegisterFileSystemPermissionPolicy(storage::kFileSystemTypeTemporary,
                                        storage::FILE_PERMISSION_SANDBOX);

  // Note - FileSystemManagerImpl must be constructed on the UI thread, but all
  // other methods are expected to be called on the IO thread - see comments in
  // content/browser/file_system/file_system_manager_impl.h
  for (size_t i = 0; i < kNumRenderers; i++) {
    file_system_manager_impls_[i] = std::make_unique<FileSystemManagerImpl>(
        i, file_system_context_, blob_storage_context_);
    p->Add(i, &browser_context_);
  }

  GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(done_closure));
}

void FileSystemManagerTestcase::TearDown(base::OnceClosure done_closure) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileSystemManagerTestcase::TearDownOnUIThread,
                     base::Unretained(this), std::move(done_closure)));
}

void FileSystemManagerTestcase::TearDownOnIOThread(
    base::OnceClosure done_closure) {
  for (size_t i = 0; i < kNumRenderers; i++) {
    file_system_manager_impls_[i].reset();
  }

  GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(done_closure));
}

void FileSystemManagerTestcase::TearDownOnUIThread(
    base::OnceClosure done_closure) {
  ChildProcessSecurityPolicyImpl* p =
      ChildProcessSecurityPolicyImpl::GetInstance();
  for (size_t i = 0; i < kNumRenderers; i++) {
    p->Remove(i);
  }

  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&FileSystemManagerTestcase::TearDownOnIOThread,
                     base::Unretained(this), std::move(done_closure)));
}

void FileSystemManagerTestcase::RunAction(const ProtoAction& action,
                                          base::OnceClosure run_closure) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const auto ThreadId_UI =
      content::fuzzing::file_system_manager::proto::RunThreadAction_ThreadId_UI;
  const auto ThreadId_IO =
      content::fuzzing::file_system_manager::proto::RunThreadAction_ThreadId_IO;

  switch (action.action_case()) {
    case ProtoAction::kNewFileSystemManager:
      AddFileSystemManager(action.new_file_system_manager().id(),
                           action.new_file_system_manager().render_process_id(),
                           action.new_file_system_manager().storage_key(),
                           std::move(run_closure));
      return;

    case ProtoAction::kRunThread:
      if (action.run_thread().id() == ThreadId_UI) {
        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::kFileSystemManagerRemoteAction:
      ::mojolpm::HandleRemoteAction(action.file_system_manager_remote_action());
      break;

    case ProtoAction::kFileSystemCancellableOperationRemoteAction:
      ::mojolpm::HandleRemoteAction(
          action.file_system_cancellable_operation_remote_action());
      break;

    case ProtoAction::ACTION_NOT_SET:
      break;
  }

  GetFuzzerTaskRunner()->PostTask(FROM_HERE, std::move(run_closure));
}

void FileSystemManagerTestcase::AddFileSystemManagerImpl(
    uint32_t id,
    content::fuzzing::file_system_manager::proto::NewFileSystemManagerAction::
        RenderProcessId render_process_id,
    const storage_key_proto::StorageKey& storage_key,
    mojo::PendingReceiver<::blink::mojom::FileSystemManager>&& receiver) {
  size_t offset = render_process_id ==
                          content::fuzzing::file_system_manager::proto::
                              NewFileSystemManagerAction_RenderProcessId_ZERO
                      ? 0
                      : 1;
  file_system_manager_impls_[offset]->BindReceiver(
      storage_key_proto::Convert(storage_key), std::move(receiver));
}

static void AddFileSystemManagerInstance(
    uint32_t id,
    mojo::Remote<::blink::mojom::FileSystemManager> remote,
    base::OnceClosure run_closure) {
  ::mojolpm::GetContext()->AddInstance(id, std::move(remote));

  std::move(run_closure).Run();
}

void FileSystemManagerTestcase::AddFileSystemManager(
    uint32_t id,
    content::fuzzing::file_system_manager::proto::NewFileSystemManagerAction::
        RenderProcessId render_process_id,
    const storage_key_proto::StorageKey& storage_key,
    base::OnceClosure run_closure) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  mojo::Remote<::blink::mojom::FileSystemManager> remote;
  auto receiver = remote.BindNewPipeAndPassReceiver();

  GetIOThreadTaskRunner({})->PostTaskAndReply(
      FROM_HERE,
      base::BindOnce(&FileSystemManagerTestcase::AddFileSystemManagerImpl,
                     base::Unretained(this), id, render_process_id, storage_key,
                     std::move(receiver)),
      base::BindOnce(&AddFileSystemManagerInstance, id, std::move(remote),
                     std::move(run_closure)));
}
}  // namespace content

DEFINE_BINARY_PROTO_FUZZER(
    const content::fuzzing::file_system_manager::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.
  content::GetEnvironment();

  content::FileSystemManagerTestcase 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.
  content::GetFuzzerTaskRunner()->PostTask(
      FROM_HERE,
      base::BindOnce(&mojolpm::RunTestcase<content::FileSystemManagerTestcase>,
                     base::Unretained(&testcase),
                     content::GetFuzzerTaskRunner(),
                     main_run_loop.QuitClosure()));

  main_run_loop.Run();
}