From f9603fa366aa61c8aa17205b8ee20915e38cd277 Mon Sep 17 00:00:00 2001 From: Mark Brand Date: Fri, 29 May 2020 11:15:39 +0000 Subject: [PATCH] QoL improvements for the MojoLPM fuzzer. Adds mojo/tools/fuzzers/mojom.gni which has a template rule that wraps most of the build-file mechanics necessary to build fuzzer targets using MojoLPM, and adds pipe date size limits to the MojoLPM data pipe implementations to prevent fuzzers from OOMing so easily. In order to make the mojolpm_fuzzer_target work, it needs to fix handling for testonly in a couple of places: - adding testonly propagation to protoc_convert. - adding testonly to seed_corpus copy rule. Bug: 1076336 Change-Id: Ib6b2efbe533151e28c3ef26593ac0dc322725770 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2172079 Commit-Queue: Mark Brand Reviewed-by: Nico Weber Reviewed-by: Robert Sesek Reviewed-by: Ken Rockot Reviewed-by: Jonathan Metzman Cr-Commit-Position: refs/heads/master@{#773121} --- mojo/public/tools/fuzzers/mojolpm.cc | 38 ++++-- mojo/public/tools/fuzzers/mojolpm.gni | 153 +++++++++++++++++++++++++ testing/libfuzzer/fuzzer_test.gni | 2 + third_party/protobuf/proto_library.gni | 4 + 4 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 mojo/public/tools/fuzzers/mojolpm.gni diff --git a/mojo/public/tools/fuzzers/mojolpm.cc b/mojo/public/tools/fuzzers/mojolpm.cc index 32005fe48af377..4863593e1d82bd 100644 --- a/mojo/public/tools/fuzzers/mojolpm.cc +++ b/mojo/public/tools/fuzzers/mojolpm.cc @@ -10,6 +10,10 @@ namespace mojolpm { +const uint32_t kPipeElementMaxSize = 0x1000u; +const uint32_t kPipeCapacityMaxSize = 0x100000u; +const uint32_t kPipeActionMaxSize = 0x100000u; + Context::Context() : message_(0, 0, 0, 0, nullptr) {} Context::~Context() = default; @@ -209,8 +213,10 @@ bool FromProto(const ::mojolpm::DataPipeConsumerHandle& input, options.struct_size = sizeof(MojoCreateDataPipeOptions); options.flags = input.new_().flags(); - options.element_num_bytes = input.new_().element_num_bytes(); - options.capacity_num_bytes = input.new_().capacity_num_bytes(); + options.element_num_bytes = + std::min(input.new_().element_num_bytes(), kPipeElementMaxSize); + options.capacity_num_bytes = + std::min(input.new_().capacity_num_bytes(), kPipeCapacityMaxSize); if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, &producer, &consumer)) { @@ -246,8 +252,10 @@ bool FromProto(const ::mojolpm::DataPipeProducerHandle& input, options.struct_size = sizeof(MojoCreateDataPipeOptions); options.flags = input.new_().flags(); - options.element_num_bytes = input.new_().element_num_bytes(); - options.capacity_num_bytes = input.new_().capacity_num_bytes(); + options.element_num_bytes = + std::min(input.new_().element_num_bytes(), kPipeElementMaxSize); + options.capacity_num_bytes = + std::min(input.new_().capacity_num_bytes(), kPipeCapacityMaxSize); if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, &producer, &consumer)) { @@ -310,8 +318,10 @@ void HandleDataPipeRead(const ::mojolpm::DataPipeRead& input) { options.struct_size = sizeof(MojoCreateDataPipeOptions); options.flags = input.handle().new_().flags(); - options.element_num_bytes = input.handle().new_().element_num_bytes(); - options.capacity_num_bytes = input.handle().new_().capacity_num_bytes(); + options.element_num_bytes = std::min( + input.handle().new_().element_num_bytes(), kPipeElementMaxSize); + options.capacity_num_bytes = std::min( + input.handle().new_().capacity_num_bytes(), kPipeCapacityMaxSize); if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, &producer, &consumer)) { @@ -323,7 +333,10 @@ void HandleDataPipeRead(const ::mojolpm::DataPipeRead& input) { } if (consumer_ptr) { - uint32_t size = input.size(); + unsigned int size = input.size(); + if (size > kPipeActionMaxSize) { + size = kPipeActionMaxSize; + } std::vector data(size); consumer_ptr->get().ReadData(data.data(), &size, 0); } @@ -344,8 +357,10 @@ void HandleDataPipeWrite(const ::mojolpm::DataPipeWrite& input) { options.struct_size = sizeof(MojoCreateDataPipeOptions); options.flags = input.handle().new_().flags(); - options.element_num_bytes = input.handle().new_().element_num_bytes(); - options.capacity_num_bytes = input.handle().new_().capacity_num_bytes(); + options.element_num_bytes = std::min( + input.handle().new_().element_num_bytes(), kPipeElementMaxSize); + options.capacity_num_bytes = std::min( + input.handle().new_().capacity_num_bytes(), kPipeCapacityMaxSize); if (MOJO_RESULT_OK == mojo::CreateDataPipe(&options, &producer, &consumer)) { @@ -357,7 +372,10 @@ void HandleDataPipeWrite(const ::mojolpm::DataPipeWrite& input) { } if (producer_ptr) { - uint32_t size = static_cast(input.data().size()); + unsigned int size = input.data().size(); + if (size > kPipeActionMaxSize) { + size = kPipeActionMaxSize; + } producer_ptr->get().WriteData(input.data().data(), &size, 0); } } diff --git a/mojo/public/tools/fuzzers/mojolpm.gni b/mojo/public/tools/fuzzers/mojolpm.gni new file mode 100644 index 00000000000000..5e2750418f76bb --- /dev/null +++ b/mojo/public/tools/fuzzers/mojolpm.gni @@ -0,0 +1,153 @@ +# 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. + +import("//testing/libfuzzer/fuzzer_test.gni") +import("//third_party/protobuf/proto_library.gni") +import("//tools/ipc_fuzzer/ipc_fuzzer.gni") + +# Generate a MojoLPM-based fuzzer test. +# +# This rule will copy the proto file defining the fuzzer testcases into the +# output directory so that it can be compiled against the generated MojoLPM +# protos. It then adds a rule to compile that proto, and finally a fuzzer +# test target which uses the compiled proto. +# +# Optionally it can also handle converting a seed corpus of text protos into +# a binary corpus as part of the build. +# +# Parameters: +# sources +# List of source .cc files to compile. +# +# deps +# List of dependencies to compile this target. +# +# proto_source +# Single source .proto file defining the structure of a testcase. +# +# proto_deps +# List of additional dependencies for compiling proto_source. +# +# testcase_proto_kind (optional, required if seed_corpus_sources provided) +# Name of proto message type representing a testcase. +# +# seed_corpus_sources (optional) +# List of source .textproto files used to build a seed corpus. +# +# Example: +# mojolpm_fuzzer_test("foo_mojolpm_fuzzer") { +# sources = [ "foo_mojolpm_fuzzer.cc" ] +# +# deps = [ +# "//content/browser/foo:foo_mojolpm_fuzzer_proto", +# "//content/browser:for_content_tests", +# "//content/public/browser:browser_sources", +# "//content/test:test_support", +# "//mojo/core/embedder", +# "//mojo/public/tools/fuzzers:mojolpm", +# "//third_party/libprotobuf-mutator", +# ] +# +# proto_deps = [ +# "//content/browser/bar/mojom:mojom_mojolpm"," +# ] +# +# testcase_proto = "foo_mojolpm_fuzzer.proto" +# testcase_proto_kind = "foo.mojolpm.proto.Testcase" +# +# seed_corpus_sources = [ +# "foo_mojolpm_fuzzer_corpus/seed_one.textproto", +# "foo_mojolpm_fuzzer_corpus/seed_two.textproto", +# ] +# } +template("mojolpm_fuzzer_test") { + assert(defined(invoker.sources) && defined(invoker.proto_source), + "\"sources\" and \"proto_source\" must be defined for $target_name") + + assert( + !defined(invoker.seed_corpus_sources) || + defined(invoker.testcase_proto_kind), + "\"testcase_proto_kind\" must be defined for $target_name since \"seed_corpus_sources\" is defined.") + + if (enable_ipc_fuzzer) { + proto_copy_target_name = "${target_name}_proto_copy" + proto_target_name = "${target_name}_proto" + + proto_file_name = get_path_info(invoker.proto_source, "file") + proto_source_path = "$root_gen_dir/${proto_file_name}" + + copy(proto_copy_target_name) { + sources = [ invoker.proto_source ] + outputs = [ proto_source_path ] + testonly = true + } + + proto_library(proto_target_name) { + sources = [ proto_source_path ] + generate_python = false + + proto_deps = [ + ":${proto_copy_target_name}", + "//mojo/public/tools/fuzzers:mojolpm_proto_copy", + ] + + link_deps = [] + + if (defined(invoker.proto_deps)) { + proto_deps += invoker.proto_deps + link_deps += invoker.proto_deps + } + + testonly = true + } + + if (defined(invoker.seed_corpus_sources)) { + protoc_convert_target_name = "${target_name}_protoc_convert" + seed_corpus_path = "${target_gen_dir}/${target_name}_seed_corpus" + + protoc_convert(protoc_convert_target_name) { + sources = invoker.seed_corpus_sources + + inputs = [ proto_source_path ] + + output_pattern = "${seed_corpus_path}/{{source_name_part}}.binarypb" + + args = [ + "--encode=${invoker.testcase_proto_kind}", + "-I", + rebase_path("$root_gen_dir"), + rebase_path(inputs[0]), + ] + + deps = [ ":${proto_copy_target_name}" ] + + if (defined(invoker.proto_deps)) { + deps += invoker.proto_deps + } + + testonly = true + } + } + + fuzzer_test(target_name) { + sources = invoker.sources + deps = [ + ":${proto_target_name}", + "//mojo/core/embedder", + "//mojo/public/tools/fuzzers:mojolpm", + "//third_party/libprotobuf-mutator", + ] + if (defined(invoker.deps)) { + deps += invoker.deps + } + + if (defined(invoker.seed_corpus_sources)) { + seed_corpus = seed_corpus_path + seed_corpus_deps = [ ":${protoc_convert_target_name}" ] + } + } + } else { + not_needed(invoker, "*") + } +} diff --git a/testing/libfuzzer/fuzzer_test.gni b/testing/libfuzzer/fuzzer_test.gni index b46b653564ad01..9ea466b86929e2 100644 --- a/testing/libfuzzer/fuzzer_test.gni +++ b/testing/libfuzzer/fuzzer_test.gni @@ -63,6 +63,8 @@ template("fuzzer_test") { action(target_name + "_seed_corpus") { script = "//testing/libfuzzer/archive_corpus.py" + testonly = true + args = [ "--output", rebase_path(out, root_build_dir), diff --git a/third_party/protobuf/proto_library.gni b/third_party/protobuf/proto_library.gni index 44c7fefa25d714..87a94d386f1823 100644 --- a/third_party/protobuf/proto_library.gni +++ b/third_party/protobuf/proto_library.gni @@ -509,6 +509,10 @@ template("protoc_convert") { deps += invoker.deps } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + outputs = [ invoker.output_pattern ] args = [