// 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;
}