| // 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 <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) { |
| 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::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(); |
| } |