Fuzz BackoffEntrySerializer with net_backoff_entry_serializer_fuzzer.

This fuzzer tests both the "serialize-then-deserialize" and
"deserialize-then-serialize" directions.

Change-Id: I571de9e67377bb811262febd37378f1b06f190b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2185797
Reviewed-by: Jonathan Metzman <[email protected]>
Reviewed-by: Maksim Orlovich <[email protected]>
Commit-Queue: Dan McArdle <[email protected]>
Cr-Commit-Position: refs/heads/master@{#767349}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index eabb49ce..9712bc3 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -4918,6 +4918,26 @@
   ]
 }
 
+proto_library("backoff_entry_serializer_fuzzer_input") {
+  proto_in_dir = "//"
+  sources = [ "base/backoff_entry_serializer_fuzzer_input.proto" ]
+  link_deps = [ "//testing/libfuzzer/proto:json_proto" ]
+}
+
+fuzzer_test("net_backoff_entry_serializer_fuzzer") {
+  sources = [ "base/backoff_entry_serializer_fuzzer.cc" ]
+  deps = [
+    ":backoff_entry_serializer_fuzzer_input",
+    ":net_fuzzer_test_support",
+    ":test_support",
+    "//base",
+    "//net",
+    "//testing/libfuzzer/proto:json_proto",
+    "//testing/libfuzzer/proto:json_proto_converter",
+    "//third_party/libprotobuf-mutator",
+  ]
+}
+
 fuzzer_test("net_data_url_fuzzer") {
   sources = [ "base/data_url_fuzzer.cc" ]
   deps = [
diff --git a/net/base/backoff_entry_serializer_fuzzer.cc b/net/base/backoff_entry_serializer_fuzzer.cc
new file mode 100644
index 0000000..d66eacbd
--- /dev/null
+++ b/net/base/backoff_entry_serializer_fuzzer.cc
@@ -0,0 +1,141 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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/optional.h"
+#include "base/strings/string_piece_forward.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"
+
+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 TimeFromProto(input_.parse_time()); }
+  base::Time serialize_time() const {
+    return TimeFromProto(input_.serialize_time());
+  }
+
+  base::Optional<base::Value> serialized_entry() const {
+    json_proto::JsonProtoConverter converter;
+    std::string json_array = converter.Convert(input_.serialized_entry());
+    base::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) {
+    return BackoffEntry::Policy{
+        .num_errors_to_ignore = policy.num_errors_to_ignore(),
+        .initial_delay_ms = policy.initial_delay_ms(),
+        .multiply_factor = policy.multiply_factor(),
+        .jitter_factor = policy.jitter_factor(),
+        .maximum_backoff_ms = policy.maximum_backoff_ms(),
+        .entry_lifetime_ms = policy.entry_lifetime_ms(),
+        .always_use_initial_delay = policy.always_use_initial_delay(),
+    };
+  }
+
+  static base::Time TimeFromProto(uint64_t raw_time) {
+    return base::Time() + base::TimeDelta::FromMicroseconds(raw_time);
+  }
+};
+
+// Tests the "deserialize-reserialize" property. Deserializes a BackoffEntry
+// from JSON, reserializes it, and checks that the JSON values match.
+void TestDeserialize(const ProtoTranslator& translator) {
+  // Attempt to convert the json_proto.ArrayValue to a base::Value.
+  base::Optional<base::Value> value = translator.serialized_entry();
+  if (!value)
+    return;
+  DCHECK(value->is_list());
+
+  BackoffEntry::Policy policy = translator.policy();
+
+  // Attempt to deserialize a BackoffEntry.
+  std::unique_ptr<BackoffEntry> entry =
+      BackoffEntrySerializer::DeserializeFromValue(*value, &policy, nullptr,
+                                                   translator.parse_time());
+  if (!entry)
+    return;
+
+  // Serializing |entry| it should recreate the original JSON input!
+  std::unique_ptr<base::Value> reserialized =
+      BackoffEntrySerializer::SerializeToValue(*entry,
+                                               translator.serialize_time());
+  CHECK(reserialized);
+  CHECK_EQ(*reserialized, *value);
+}
+
+// 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);
+  std::unique_ptr<base::Value> serialized =
+      BackoffEntrySerializer::SerializeToValue(native_entry,
+                                               translator.serialize_time());
+  CHECK(serialized);
+
+  // Deserialize it.
+  std::unique_ptr<BackoffEntry> deserialized_entry =
+      BackoffEntrySerializer::DeserializeFromValue(
+          *serialized, &policy, nullptr, translator.parse_time());
+  // Even though SerializeToValue 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);
+  TestSerialize(translator);
+  TestDeserialize(translator);
+}
+
+}  // namespace net
diff --git a/net/base/backoff_entry_serializer_fuzzer_input.proto b/net/base/backoff_entry_serializer_fuzzer_input.proto
new file mode 100644
index 0000000..d92f72e
--- /dev/null
+++ b/net/base/backoff_entry_serializer_fuzzer_input.proto
@@ -0,0 +1,29 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+package fuzz_proto;
+
+import "testing/libfuzzer/proto/json.proto";
+
+message FuzzerInput {
+  // Using int64 to match base::Time's internal representation.
+  required int64 parse_time = 1;
+  required int64 serialize_time = 2;
+  required BackoffEntryPolicy policy = 3;
+  required json_proto.ArrayValue serialized_entry = 4;
+}
+
+// Input for the fuzzer to try serializing a BackoffEntry.
+// Keep aligned with |net::BackoffEntry::Policy|.
+message BackoffEntryPolicy {
+  required int64 num_errors_to_ignore = 1;
+  required int64 initial_delay_ms = 2;
+  required double multiply_factor = 3;
+  required double jitter_factor = 4;
+  required int64 maximum_backoff_ms = 5;
+  required int64 entry_lifetime_ms = 6;
+  required bool always_use_initial_delay = 7;
+}
\ No newline at end of file
diff --git a/testing/libfuzzer/proto/json_proto_converter.cc b/testing/libfuzzer/proto/json_proto_converter.cc
index 155a3800..1013be4 100644
--- a/testing/libfuzzer/proto/json_proto_converter.cc
+++ b/testing/libfuzzer/proto/json_proto_converter.cc
@@ -66,4 +66,10 @@
   return data_.str();
 }
 
+std::string JsonProtoConverter::Convert(
+    const json_proto::ArrayValue& json_array) {
+  AppendArray(json_array);
+  return data_.str();
+}
+
 }  // namespace json_proto
diff --git a/testing/libfuzzer/proto/json_proto_converter.h b/testing/libfuzzer/proto/json_proto_converter.h
index 5dedb938..d63eec0c 100644
--- a/testing/libfuzzer/proto/json_proto_converter.h
+++ b/testing/libfuzzer/proto/json_proto_converter.h
@@ -15,6 +15,7 @@
 class JsonProtoConverter {
  public:
   std::string Convert(const json_proto::JsonObject&);
+  std::string Convert(const json_proto::ArrayValue&);
 
  private:
   std::stringstream data_;