1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/base/backoff_entry_serializer.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <memory>
11 #include <optional>
12
13 #include "base/json/json_reader.h"
14 #include "base/logging.h"
15 #include "base/time/tick_clock.h"
16 #include "base/time/time.h"
17 #include "net/base/backoff_entry.h"
18 #include "net/base/backoff_entry_serializer_fuzzer_input.pb.h"
19 #include "testing/libfuzzer/proto/json_proto_converter.h"
20 #include "testing/libfuzzer/proto/lpm_interface.h"
21
22 namespace net {
23
24 namespace {
25 struct Environment {
Environmentnet::__anon133962160111::Environment26 Environment() { logging::SetMinLogLevel(logging::LOGGING_ERROR); }
27 };
28
29 class ProtoTranslator {
30 public:
ProtoTranslator(const fuzz_proto::FuzzerInput & input)31 explicit ProtoTranslator(const fuzz_proto::FuzzerInput& input)
32 : input_(input) {}
33
policy() const34 BackoffEntry::Policy policy() const {
35 return PolicyFromProto(input_->policy());
36 }
parse_time() const37 base::Time parse_time() const {
38 return base::Time() + base::Microseconds(input_->parse_time());
39 }
parse_time_ticks() const40 base::TimeTicks parse_time_ticks() const {
41 return base::TimeTicks() + base::Microseconds(input_->parse_time());
42 }
serialize_time() const43 base::Time serialize_time() const {
44 return base::Time() + base::Microseconds(input_->serialize_time());
45 }
now_ticks() const46 base::TimeTicks now_ticks() const {
47 return base::TimeTicks() + base::Microseconds(input_->now_ticks());
48 }
serialized_entry() const49 std::optional<base::Value> serialized_entry() const {
50 json_proto::JsonProtoConverter converter;
51 std::string json_array = converter.Convert(input_->serialized_entry());
52 std::optional<base::Value> value = base::JSONReader::Read(json_array);
53 return value;
54 }
55
56 private:
57 const raw_ref<const fuzz_proto::FuzzerInput> input_;
58
PolicyFromProto(const fuzz_proto::BackoffEntryPolicy & policy)59 static BackoffEntry::Policy PolicyFromProto(
60 const fuzz_proto::BackoffEntryPolicy& policy) {
61 BackoffEntry::Policy new_policy;
62 new_policy.num_errors_to_ignore = policy.num_errors_to_ignore();
63 new_policy.initial_delay_ms = policy.initial_delay_ms();
64 new_policy.multiply_factor = policy.multiply_factor();
65 new_policy.jitter_factor = policy.jitter_factor();
66 new_policy.maximum_backoff_ms = policy.maximum_backoff_ms();
67 new_policy.entry_lifetime_ms = policy.entry_lifetime_ms();
68 new_policy.always_use_initial_delay = policy.always_use_initial_delay();
69 return new_policy;
70 }
71 };
72
73 class MockClock : public base::TickClock {
74 public:
75 MockClock() = default;
76 ~MockClock() override = default;
77
SetNow(base::TimeTicks now)78 void SetNow(base::TimeTicks now) { now_ = now; }
NowTicks() const79 base::TimeTicks NowTicks() const override { return now_; }
80
81 private:
82 base::TimeTicks now_;
83 };
84
85 // Tests the "deserialize-reserialize" property. Deserializes a BackoffEntry
86 // from JSON, reserializes it, then deserializes again. Holding time constant,
87 // we check that the parsed BackoffEntry values are equivalent.
TestDeserialize(const ProtoTranslator & translator)88 void TestDeserialize(const ProtoTranslator& translator) {
89 // Attempt to convert the json_proto.ArrayValue to a base::Value.
90 std::optional<base::Value> value = translator.serialized_entry();
91 if (!value)
92 return;
93 DCHECK(value->is_list());
94
95 BackoffEntry::Policy policy = translator.policy();
96
97 MockClock clock;
98 clock.SetNow(translator.parse_time_ticks());
99
100 // Attempt to deserialize a BackoffEntry.
101 std::unique_ptr<BackoffEntry> entry =
102 BackoffEntrySerializer::DeserializeFromList(
103 value->GetList(), &policy, &clock, translator.parse_time());
104 if (!entry)
105 return;
106
107 base::Value::List reserialized =
108 BackoffEntrySerializer::SerializeToList(*entry, translator.parse_time());
109
110 // Due to fuzzy interpretation in BackoffEntrySerializer::
111 // DeserializeFromList, we cannot assert that |*reserialized == *value|.
112 // Rather, we can deserialize |reserialized| and check that some weaker
113 // properties are preserved.
114 std::unique_ptr<BackoffEntry> entry_reparsed =
115 BackoffEntrySerializer::DeserializeFromList(reserialized, &policy, &clock,
116 translator.parse_time());
117 CHECK(entry_reparsed);
118 CHECK_EQ(entry_reparsed->failure_count(), entry->failure_count());
119 CHECK_LE(entry_reparsed->GetReleaseTime(), entry->GetReleaseTime());
120 }
121
122 // Tests the "serialize-deserialize" property. Serializes an arbitrary
123 // BackoffEntry to JSON, deserializes to another BackoffEntry, and checks
124 // equality of the two entries. Our notion of equality is *very weak* and needs
125 // improvement.
TestSerialize(const ProtoTranslator & translator)126 void TestSerialize(const ProtoTranslator& translator) {
127 BackoffEntry::Policy policy = translator.policy();
128
129 // Serialize the BackoffEntry.
130 BackoffEntry native_entry(&policy);
131 base::Value::List serialized = BackoffEntrySerializer::SerializeToList(
132 native_entry, translator.serialize_time());
133
134 MockClock clock;
135 clock.SetNow(translator.now_ticks());
136
137 // Deserialize it.
138 std::unique_ptr<BackoffEntry> deserialized_entry =
139 BackoffEntrySerializer::DeserializeFromList(serialized, &policy, &clock,
140 translator.parse_time());
141 // Even though SerializeToList was successful, we're not guaranteed to have a
142 // |deserialized_entry|. One reason deserialization may fail is if the parsed
143 // |absolute_release_time_us| is below zero.
144 if (!deserialized_entry)
145 return;
146
147 // TODO(dmcardle) Develop a stronger equality check for BackoffEntry.
148
149 // Note that while |BackoffEntry::GetReleaseTime| looks like an accessor, it
150 // returns a |value that is computed based on a random double, so it's not
151 // suitable for CHECK_EQ here. See |BackoffEntry::CalculateReleaseTime|.
152
153 CHECK_EQ(native_entry.failure_count(), deserialized_entry->failure_count());
154 }
155 } // namespace
156
DEFINE_PROTO_FUZZER(const fuzz_proto::FuzzerInput & input)157 DEFINE_PROTO_FUZZER(const fuzz_proto::FuzzerInput& input) {
158 static Environment env;
159
160 // Print the entire |input| protobuf if asked.
161 if (getenv("LPM_DUMP_NATIVE_INPUT")) {
162 std::cout << "input: " << input.DebugString();
163 }
164
165 ProtoTranslator translator(input);
166 // Skip this input if any of the time values are infinite.
167 if (translator.now_ticks().is_inf() || translator.parse_time().is_inf() ||
168 translator.serialize_time().is_inf()) {
169 return;
170 }
171 TestDeserialize(translator);
172 TestSerialize(translator);
173 }
174
175 } // namespace net
176