Skip to content

Commit

Permalink
QoL improvements for the MojoLPM fuzzer.
Browse files Browse the repository at this point in the history
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 <markbrand@google.com>
Reviewed-by: Nico Weber <thakis@chromium.org>
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Reviewed-by: Ken Rockot <rockot@google.com>
Reviewed-by: Jonathan Metzman <metzman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#773121}
  • Loading branch information
c01db33f authored and Commit Bot committed May 29, 2020
1 parent 65708d4 commit f9603fa
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 10 deletions.
38 changes: 28 additions & 10 deletions mojo/public/tools/fuzzers/mojolpm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)) {
Expand All @@ -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<char> data(size);
consumer_ptr->get().ReadData(data.data(), &size, 0);
}
Expand All @@ -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)) {
Expand All @@ -357,7 +372,10 @@ void HandleDataPipeWrite(const ::mojolpm::DataPipeWrite& input) {
}

if (producer_ptr) {
uint32_t size = static_cast<uint32_t>(input.data().size());
unsigned int size = input.data().size();
if (size > kPipeActionMaxSize) {
size = kPipeActionMaxSize;
}
producer_ptr->get().WriteData(input.data().data(), &size, 0);
}
}
Expand Down
153 changes: 153 additions & 0 deletions mojo/public/tools/fuzzers/mojolpm.gni
Original file line number Diff line number Diff line change
@@ -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, "*")
}
}
2 changes: 2 additions & 0 deletions testing/libfuzzer/fuzzer_test.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 4 additions & 0 deletions third_party/protobuf/proto_library.gni
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,10 @@ template("protoc_convert") {
deps += invoker.deps
}

if (defined(invoker.testonly)) {
testonly = invoker.testonly
}

outputs = [ invoker.output_pattern ]

args = [
Expand Down

0 comments on commit f9603fa

Please sign in to comment.