| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fuzzer/FuzzedDataProvider.h> |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/ash/printing/zeroconf_printer_detector.h" |
| #include "chrome/browser/local_discovery/service_discovery_device_lister.h" |
| |
| namespace { |
| |
| // Describes a single call to ZeroconfDetector. |
| struct CallToDelegate { |
| // method to call |
| enum CallType { |
| kOnDeviceChanged = 0, |
| kOnDeviceRemoved = 1, |
| kOnDeviceCacheFlushed = 2, |
| kMaxValue = 2 |
| } call_type; |
| // parameters (depends on |call_type|) |
| bool added; |
| local_discovery::ServiceDescription description; |
| }; |
| |
| // Shortcut for a map of unique_ptr<ServiceDiscoveryDeviceLister>. |
| // The key is a name of a service type. |
| using MapOfListers = |
| std::map<std::string, |
| std::unique_ptr<local_discovery::ServiceDiscoveryDeviceLister>>; |
| |
| // Objects of this class fuzzes ZeroconfDetector by calling methods from |
| // local_discovery::ServiceDiscoveryDeviceLister::Delegate interface. |
| class FuzzDeviceLister : public local_discovery::ServiceDiscoveryDeviceLister { |
| public: |
| // |calls| contains definition of calls to made in reverse order. |
| FuzzDeviceLister(const std::string& service_type, |
| const std::vector<CallToDelegate>& calls) |
| : service_type_(service_type), calls_(calls) {} |
| |
| ~FuzzDeviceLister() override = default; |
| |
| // Sets a pointer to ZeroconfDetector object and starts fuzzing it. |
| void SetDelegate( |
| local_discovery::ServiceDiscoveryDeviceLister::Delegate* delegate) { |
| delegate_ = delegate; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&FuzzDeviceLister::CallDelegate, |
| base::Unretained(this))); |
| } |
| |
| void Start() override {} |
| void DiscoverNewDevices() override {} |
| const std::string& service_type() const override { return service_type_; } |
| |
| private: |
| // Executes single call to |
| // local_discovery::ServiceDiscoveryDeviceLister::Delegate interface. |
| // This method schedules for execution last call from calls_, removes it from |
| // calls_ and at the end schedules itself for execution. It does nothing |
| // when calls_ is empty. |
| void CallDelegate() { |
| if (calls_.empty()) |
| return; |
| const CallToDelegate call = calls_.back(); |
| calls_.pop_back(); |
| switch (call.call_type) { |
| case CallToDelegate::kOnDeviceChanged: |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&local_discovery::ServiceDiscoveryDeviceLister:: |
| Delegate::OnDeviceChanged, |
| base::Unretained(delegate_), service_type_, |
| call.added, call.description)); |
| break; |
| case CallToDelegate::kOnDeviceRemoved: |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&local_discovery::ServiceDiscoveryDeviceLister:: |
| Delegate::OnDeviceRemoved, |
| base::Unretained(delegate_), service_type_, |
| call.description.service_name)); |
| break; |
| case CallToDelegate::kOnDeviceCacheFlushed: |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&local_discovery::ServiceDiscoveryDeviceLister:: |
| Delegate::OnDeviceCacheFlushed, |
| base::Unretained(delegate_), service_type_)); |
| break; |
| } |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&FuzzDeviceLister::CallDelegate, |
| base::Unretained(this))); |
| } |
| |
| raw_ptr<local_discovery::ServiceDiscoveryDeviceLister::Delegate> delegate_ = |
| nullptr; |
| std::string service_type_; |
| std::vector<CallToDelegate> calls_; |
| }; |
| |
| // Helper for creating a lister. |
| void CreateLister(const std::string& service_type, |
| const std::vector<CallToDelegate>& calls, |
| MapOfListers* listers) { |
| std::unique_ptr<FuzzDeviceLister> fuzz_lister = |
| std::make_unique<FuzzDeviceLister>(service_type, std::move(calls)); |
| listers->emplace(service_type, std::move(fuzz_lister)); |
| } |
| |
| // Helper for creating a vector of (fuzzing) calls to make. |
| std::vector<CallToDelegate> CreateFuzzCalls(FuzzedDataProvider* fuzz_data) { |
| // local function to generate random int |
| auto RandInt = [fuzz_data](int min, int max) -> int { |
| return fuzz_data->ConsumeIntegralInRange(min, max); |
| }; |
| // local function to generate random string with random length |
| auto RandStr = [fuzz_data](size_t max_length) -> std::string { |
| return fuzz_data->ConsumeRandomLengthString(max_length); |
| }; |
| // fuzzing parameters |
| constexpr size_t kMaxNumberOfCalls = 100; |
| constexpr size_t kMaxNameLength = 1000; |
| constexpr size_t kMaxMetadataEntriesCount = 1000; |
| constexpr size_t kMaxMetadataEntryLength = 1000; |
| // an array of fuzzing calls |
| std::vector<CallToDelegate> calls(RandInt(0, kMaxNumberOfCalls)); |
| // in this array we store names of created services |
| std::vector<std::string> names_history; |
| names_history.reserve(calls.size() / 2); |
| // generates random fuzzing calls |
| for (auto& call : calls) { |
| call.call_type = static_cast<CallToDelegate::CallType>( |
| RandInt(0, CallToDelegate::CallType::kMaxValue)); |
| switch (call.call_type) { |
| case CallToDelegate::kOnDeviceChanged: |
| call.description.service_name = RandStr(kMaxNameLength); |
| call.description.metadata.resize(RandInt(0, kMaxMetadataEntriesCount)); |
| for (auto& text : call.description.metadata) |
| text = RandStr(kMaxMetadataEntryLength); |
| names_history.push_back(call.description.service_name); |
| break; |
| case CallToDelegate::kOnDeviceRemoved: |
| // in 10% of cases random string is used as service name, in the rest |
| // 90% one of the name is picked from names_history |
| if (names_history.empty() || RandInt(0, 9) == 0) { |
| call.description.service_name = RandStr(kMaxNameLength); |
| } else { |
| const int n = names_history.size(); |
| call.description.service_name = names_history[RandInt(0, n - 1)]; |
| } |
| break; |
| case CallToDelegate::kOnDeviceCacheFlushed: |
| break; |
| } |
| } |
| return calls; |
| } |
| |
| } // namespace |
| |
| extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
| base::test::TaskEnvironment task_environment; |
| FuzzedDataProvider fuzz_data(data, size); |
| // Creating listers in similar way as in "standard" constructor of |
| // ZeroconfPrinterDetector. |
| MapOfListers listers; |
| std::vector<CallToDelegate> calls = CreateFuzzCalls(&fuzz_data); |
| CreateLister(ash::ZeroconfPrinterDetector::kIppServiceName, calls, &listers); |
| CreateLister(ash::ZeroconfPrinterDetector::kIppsServiceName, calls, &listers); |
| CreateLister(ash::ZeroconfPrinterDetector::kIppEverywhereServiceName, calls, |
| &listers); |
| CreateLister(ash::ZeroconfPrinterDetector::kIppsEverywhereServiceName, calls, |
| &listers); |
| // Creating an object of ZeroconfPrinterDetector to fuzz. |
| auto detector = ash::ZeroconfPrinterDetector::CreateForTesting( |
| &listers, /*ipp_reject_list=*/{}); |
| for (auto& lf : listers) { |
| static_cast<FuzzDeviceLister*>(lf.second.get()) |
| ->SetDelegate(detector.get()); |
| } |
| // Fuzzing. |
| task_environment.RunUntilIdle(); |
| return 0; |
| } |