// 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 #include #include #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 serialized_entry() const { json_proto::JsonProtoConverter converter; std::string json_array = converter.Convert(input_.serialized_entry()); absl::optional 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 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 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 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 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