176 lines
6.1 KiB
C++
176 lines
6.1 KiB
C++
// 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 <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <memory>
|
|
|
|
#include "base/json/json_reader.h"
|
|
#include "base/logging.h"
|
|
#include "base/strings/string_piece_forward.h"
|
|
#include "base/time/tick_clock.h"
|
|
#include "base/time/time.h"
|
|
#include "net/base/backoff_entry.h"
|
|
#include "net/base/backoff_entry_serializer.h"
|
|
#include "net/base/backoff_entry_serializer_fuzzer_input.pb.h"
|
|
#include "testing/libfuzzer/proto/json_proto_converter.h"
|
|
#include "testing/libfuzzer/proto/lpm_interface.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
struct Environment {
|
|
Environment() { logging::SetMinLogLevel(logging::LOG_ERROR); }
|
|
};
|
|
|
|
class ProtoTranslator {
|
|
public:
|
|
explicit ProtoTranslator(const fuzz_proto::FuzzerInput& input)
|
|
: input_(input) {}
|
|
|
|
BackoffEntry::Policy policy() const {
|
|
return PolicyFromProto(input_.policy());
|
|
}
|
|
base::Time parse_time() const {
|
|
return base::Time() + base::Microseconds(input_.parse_time());
|
|
}
|
|
base::TimeTicks parse_time_ticks() const {
|
|
return base::TimeTicks() + base::Microseconds(input_.parse_time());
|
|
}
|
|
base::Time serialize_time() const {
|
|
return base::Time() + base::Microseconds(input_.serialize_time());
|
|
}
|
|
base::TimeTicks now_ticks() const {
|
|
return base::TimeTicks() + base::Microseconds(input_.now_ticks());
|
|
}
|
|
absl::optional<base::Value> serialized_entry() const {
|
|
json_proto::JsonProtoConverter converter;
|
|
std::string json_array = converter.Convert(input_.serialized_entry());
|
|
absl::optional<base::Value> value = base::JSONReader::Read(json_array);
|
|
return value;
|
|
}
|
|
|
|
private:
|
|
const fuzz_proto::FuzzerInput& input_;
|
|
|
|
static BackoffEntry::Policy PolicyFromProto(
|
|
const fuzz_proto::BackoffEntryPolicy& policy) {
|
|
BackoffEntry::Policy new_policy;
|
|
new_policy.num_errors_to_ignore = policy.num_errors_to_ignore();
|
|
new_policy.initial_delay_ms = policy.initial_delay_ms();
|
|
new_policy.multiply_factor = policy.multiply_factor();
|
|
new_policy.jitter_factor = policy.jitter_factor();
|
|
new_policy.maximum_backoff_ms = policy.maximum_backoff_ms();
|
|
new_policy.entry_lifetime_ms = policy.entry_lifetime_ms();
|
|
new_policy.always_use_initial_delay = policy.always_use_initial_delay();
|
|
return new_policy;
|
|
}
|
|
};
|
|
|
|
class MockClock : public base::TickClock {
|
|
public:
|
|
MockClock() = default;
|
|
~MockClock() override = default;
|
|
|
|
void SetNow(base::TimeTicks now) { now_ = now; }
|
|
base::TimeTicks NowTicks() const override { return now_; }
|
|
|
|
private:
|
|
base::TimeTicks now_;
|
|
};
|
|
|
|
// Tests the "deserialize-reserialize" property. Deserializes a BackoffEntry
|
|
// from JSON, reserializes it, then deserializes again. Holding time constant,
|
|
// we check that the parsed BackoffEntry values are equivalent.
|
|
void TestDeserialize(const ProtoTranslator& translator) {
|
|
// Attempt to convert the json_proto.ArrayValue to a base::Value.
|
|
absl::optional<base::Value> value = translator.serialized_entry();
|
|
if (!value)
|
|
return;
|
|
DCHECK(value->is_list());
|
|
|
|
BackoffEntry::Policy policy = translator.policy();
|
|
|
|
MockClock clock;
|
|
clock.SetNow(translator.parse_time_ticks());
|
|
|
|
// Attempt to deserialize a BackoffEntry.
|
|
std::unique_ptr<BackoffEntry> entry =
|
|
BackoffEntrySerializer::DeserializeFromList(
|
|
value->GetList(), &policy, &clock, translator.parse_time());
|
|
if (!entry)
|
|
return;
|
|
|
|
base::Value::List reserialized =
|
|
BackoffEntrySerializer::SerializeToList(*entry, translator.parse_time());
|
|
|
|
// Due to fuzzy interpretation in BackoffEntrySerializer::
|
|
// DeserializeFromList, we cannot assert that |*reserialized == *value|.
|
|
// Rather, we can deserialize |reserialized| and check that some weaker
|
|
// properties are preserved.
|
|
std::unique_ptr<BackoffEntry> entry_reparsed =
|
|
BackoffEntrySerializer::DeserializeFromList(reserialized, &policy, &clock,
|
|
translator.parse_time());
|
|
CHECK(entry_reparsed);
|
|
CHECK_EQ(entry_reparsed->failure_count(), entry->failure_count());
|
|
CHECK_LE(entry_reparsed->GetReleaseTime(), entry->GetReleaseTime());
|
|
}
|
|
|
|
// Tests the "serialize-deserialize" property. Serializes an arbitrary
|
|
// BackoffEntry to JSON, deserializes to another BackoffEntry, and checks
|
|
// equality of the two entries. Our notion of equality is *very weak* and needs
|
|
// improvement.
|
|
void TestSerialize(const ProtoTranslator& translator) {
|
|
BackoffEntry::Policy policy = translator.policy();
|
|
|
|
// Serialize the BackoffEntry.
|
|
BackoffEntry native_entry(&policy);
|
|
base::Value::List serialized = BackoffEntrySerializer::SerializeToList(
|
|
native_entry, translator.serialize_time());
|
|
|
|
MockClock clock;
|
|
clock.SetNow(translator.now_ticks());
|
|
|
|
// Deserialize it.
|
|
std::unique_ptr<BackoffEntry> deserialized_entry =
|
|
BackoffEntrySerializer::DeserializeFromList(serialized, &policy, &clock,
|
|
translator.parse_time());
|
|
// Even though SerializeToList was successful, we're not guaranteed to have a
|
|
// |deserialized_entry|. One reason deserialization may fail is if the parsed
|
|
// |absolute_release_time_us| is below zero.
|
|
if (!deserialized_entry)
|
|
return;
|
|
|
|
// TODO(dmcardle) Develop a stronger equality check for BackoffEntry.
|
|
|
|
// Note that while |BackoffEntry::GetReleaseTime| looks like an accessor, it
|
|
// returns a |value that is computed based on a random double, so it's not
|
|
// suitable for CHECK_EQ here. See |BackoffEntry::CalculateReleaseTime|.
|
|
|
|
CHECK_EQ(native_entry.failure_count(), deserialized_entry->failure_count());
|
|
}
|
|
} // namespace
|
|
|
|
DEFINE_PROTO_FUZZER(const fuzz_proto::FuzzerInput& input) {
|
|
static Environment env;
|
|
|
|
// Print the entire |input| protobuf if asked.
|
|
if (getenv("LPM_DUMP_NATIVE_INPUT")) {
|
|
std::cout << "input: " << input.DebugString();
|
|
}
|
|
|
|
ProtoTranslator translator(input);
|
|
// Skip this input if any of the time values are infinite.
|
|
if (translator.now_ticks().is_inf() || translator.parse_time().is_inf() ||
|
|
translator.serialize_time().is_inf()) {
|
|
return;
|
|
}
|
|
TestDeserialize(translator);
|
|
TestSerialize(translator);
|
|
}
|
|
|
|
} // namespace net
|