Add a new LPM fuzzer for Mach message servers.

This fuzzer tool allows modeling the complex Mach IPC structures,
including the generation and transfer of port rights. It also adds a
build rule to convert textproto to binarypb, so that the seed corpus for
these fuzzers can be human-editable/readable.

Adds a simple fuzzer for the MachPortRendezvousServer.

Bug: 932175
Change-Id: I909e4a8bac802ea1d4d73d26fcb0834803324360
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1526561
Reviewed-by: Mark Mentovai <mark@chromium.org>
Reviewed-by: Jonathan Metzman <metzman@chromium.org>
Reviewed-by: Nico Weber <thakis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641854}
diff --git a/base/BUILD.gn b/base/BUILD.gn
index fafaa7a..6e98ca4 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -33,6 +33,11 @@
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//testing/test.gni")
 
+if (is_mac) {
+  # Used to generate fuzzer corpus :base_mach_port_rendezvous_convert_corpus.
+  import("//third_party/protobuf/proto_library.gni")
+}
+
 declare_args() {
   # Indicates if the Location object contains the source code information
   # (file, function, line). False means only the program counter (and currently
@@ -3401,6 +3406,37 @@
   ]
 }
 
+if (is_mac) {
+  protoc_convert("base_mach_port_rendezvous_convert_corpus") {
+    sources = [
+      "test/data/mach_port_rendezvous_fuzz/dead_name.textproto",
+      "test/data/mach_port_rendezvous_fuzz/send.textproto",
+    ]
+    inputs = [
+      "//testing/libfuzzer/fuzzers/mach/mach_message.proto",
+    ]
+    output_pattern = "$target_gen_dir/base_mach_port_rendezvous_corpus/{{source_name_part}}.binarypb"
+    args = [
+      "--encode=mach_fuzzer.MachMessage",
+      "-I",
+      rebase_path("//testing/libfuzzer/fuzzers/mach"),
+      rebase_path(inputs[0]),
+    ]
+  }
+  fuzzer_test("base_mach_port_rendezvous_fuzzer") {
+    sources = [
+      "mac/mach_port_rendezvous_fuzzer.cc",
+    ]
+    deps = [
+      "//base",
+      "//testing/libfuzzer/fuzzers/mach:converter",
+      "//third_party/libprotobuf-mutator",
+    ]
+    seed_corpus = "$target_gen_dir/base_mach_port_rendezvous_corpus"
+    seed_corpus_deps = [ ":base_mach_port_rendezvous_convert_corpus" ]
+  }
+}
+
 fuzzer_test("string_number_conversions_fuzzer") {
   sources = [
     "strings/string_number_conversions_fuzzer.cc",
diff --git a/base/mac/mach_port_rendezvous.h b/base/mac/mach_port_rendezvous.h
index 5c1c61c..422b2df 100644
--- a/base/mac/mach_port_rendezvous.h
+++ b/base/mac/mach_port_rendezvous.h
@@ -102,6 +102,7 @@
 
  private:
   friend class MachPortRendezvousServerTest;
+  friend struct MachPortRendezvousFuzzer;
 
   struct ClientData {
     ClientData(ScopedDispatchObject<dispatch_source_t> exit_watcher,
diff --git a/base/mac/mach_port_rendezvous_fuzzer.cc b/base/mac/mach_port_rendezvous_fuzzer.cc
new file mode 100644
index 0000000..3ab32c15
--- /dev/null
+++ b/base/mac/mach_port_rendezvous_fuzzer.cc
@@ -0,0 +1,51 @@
+// Copyright 2019 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 "base/mac/mach_port_rendezvous.h"
+
+#include "base/logging.h"
+#include "base/mac/mach_logging.h"
+#include "base/synchronization/lock.h"
+#include "testing/libfuzzer/fuzzers/mach/mach_message_converter.h"
+#include "testing/libfuzzer/proto/lpm_interface.h"
+
+namespace base {
+
+struct MachPortRendezvousFuzzer {
+  MachPortRendezvousFuzzer() {
+    logging::SetMinLogLevel(logging::LOG_FATAL);
+
+    mach_port_t port =
+        base::MachPortRendezvousServer::GetInstance()->server_port_.get();
+    kern_return_t kr = mach_port_insert_right(mach_task_self(), port, port,
+                                              MACH_MSG_TYPE_MAKE_SEND);
+    MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right";
+
+    server_send_right.reset(port);
+  }
+
+  void ClearClientData() {
+    base::MachPortRendezvousServer::GetInstance()->client_data_.clear();
+  }
+
+  base::mac::ScopedMachSendRight server_send_right;
+};
+
+}  // namespace base
+
+DEFINE_BINARY_PROTO_FUZZER(const mach_fuzzer::MachMessage& message) {
+  static base::MachPortRendezvousFuzzer environment;
+
+  {
+    auto* server = base::MachPortRendezvousServer::GetInstance();
+    base::AutoLock lock(server->GetLock());
+    environment.ClearClientData();
+    server->RegisterPortsForPid(
+        getpid(), {std::make_pair(0xbadbeef, base::MachRendezvousPort{
+                                                 mach_task_self(),
+                                                 MACH_MSG_TYPE_COPY_SEND})});
+  }
+
+  SendMessage(environment.server_send_right.get(), message);
+}
diff --git a/base/test/data/mach_port_rendezvous_fuzz/dead_name.textproto b/base/test/data/mach_port_rendezvous_fuzz/dead_name.textproto
new file mode 100644
index 0000000..940775a
--- /dev/null
+++ b/base/test/data/mach_port_rendezvous_fuzz/dead_name.textproto
@@ -0,0 +1,3 @@
+local_port: DEAD_NAME
+id: 0x6d727a76
+include_body_if_not_complex: false
diff --git a/base/test/data/mach_port_rendezvous_fuzz/send.textproto b/base/test/data/mach_port_rendezvous_fuzz/send.textproto
new file mode 100644
index 0000000..32d35fb
--- /dev/null
+++ b/base/test/data/mach_port_rendezvous_fuzz/send.textproto
@@ -0,0 +1,3 @@
+local_port: SEND
+id: 0x6d727a76
+include_body_if_not_complex: false
diff --git a/testing/libfuzzer/fuzzers/mach/BUILD.gn b/testing/libfuzzer/fuzzers/mach/BUILD.gn
new file mode 100644
index 0000000..238cf11
--- /dev/null
+++ b/testing/libfuzzer/fuzzers/mach/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 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("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+  sources = [
+    "mach_message.proto",
+  ]
+}
+
+source_set("converter") {
+  sources = [
+    "mach_message_converter.cc",
+    "mach_message_converter.h",
+  ]
+  public_deps = [
+    ":proto",
+  ]
+  deps = [
+    "//base",
+  ]
+}
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message.proto b/testing/libfuzzer/fuzzers/mach/mach_message.proto
new file mode 100644
index 0000000..4a3c1b6
--- /dev/null
+++ b/testing/libfuzzer/fuzzers/mach/mach_message.proto
@@ -0,0 +1,61 @@
+// Copyright 2019 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 mach_fuzzer;
+
+// Specifies a type of Mach port right to create.
+enum MachPortType {
+  // Create a receive right and move it to the peer, while holding
+  // a send right.
+  RECEIVE = 0;
+  // Create a receive right and make a send right during mach_msg.
+  SEND = 1;
+  // Create a receive right and vend a send-once right during mach_msg.
+  SEND_ONCE = 2;
+  // Create a dead name right and give a send right to it to the peer.
+  DEAD_NAME = 3;
+  // Create a receive right with no senders and move the receive right
+  // to the peer.
+  RECEIVE_NO_SENDERS = 4;
+}
+
+// Data to send in an out-of-line memory region in Mach message descriptor.
+message OutOfLineMemory {
+  required bytes data = 1;
+}
+
+// Models a mach_msg_descriptor_t.
+message Descriptor {
+  oneof descriptor_oneof {
+    MachPortType port = 1;
+    OutOfLineMemory ool = 2;
+  }
+}
+
+// Models a Mach message structure including the header, optional body,
+// and inline data.
+message MachMessage {
+  // Creates an optional port to put in msgh_local_port. If this is a receive
+  // right, it will be dropped.
+  optional MachPortType local_port = 1;
+
+  // The msgh_id field.
+  optional uint32 id = 2;
+
+  // Optional Descriptors to carry in the message.
+  repeated Descriptor descriptors = 3;
+
+  // If no Descriptors are present, whether or not to include a mach_msg_body_t
+  // in the message.
+  required bool include_body_if_not_complex = 4;
+
+  // Raw data bytes to send inline with the message.
+  optional bytes data = 5;
+
+  // Extensions can be used by clients to express structured message data,
+  // which can be converted into bytes and placed in |data|.
+  extensions 100 to 199;
+}
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
new file mode 100644
index 0000000..313f82d
--- /dev/null
+++ b/testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
@@ -0,0 +1,171 @@
+// Copyright 2019 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 "testing/libfuzzer/fuzzers/mach/mach_message_converter.h"
+
+#include <string.h>
+#include <sys/types.h>
+
+#include <utility>
+
+#include "base/containers/buffer_iterator.h"
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_msg_destroy.h"
+
+namespace mach_fuzzer {
+
+namespace {
+
+SendablePort ConvertPort(const MachPortType& port_proto) {
+  constexpr struct {
+    bool insert_send_right;
+    bool deallocate_receive_right;
+    mach_msg_type_name_t disposition;
+  } kPortRecipes[] = {
+      [RECEIVE] = {true, false, MACH_MSG_TYPE_MOVE_RECEIVE},
+      [SEND] = {false, false, MACH_MSG_TYPE_MAKE_SEND},
+      [SEND_ONCE] = {false, false, MACH_MSG_TYPE_MAKE_SEND_ONCE},
+      [DEAD_NAME] = {true, true, MACH_MSG_TYPE_COPY_SEND},
+      [RECEIVE_NO_SENDERS] = {false, false, MACH_MSG_TYPE_MOVE_RECEIVE},
+  };
+  const auto* recipe = &kPortRecipes[port_proto];
+
+  SendablePort port;
+  kern_return_t kr = mach_port_allocate(
+      mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
+      base::mac::ScopedMachReceiveRight::Receiver(port.receive_right).get());
+  MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_allocate";
+
+  port.name = port.receive_right.get();
+  port.disposition = recipe->disposition;
+  port.proto_type = port_proto;
+
+  if (recipe->insert_send_right) {
+    kr = mach_port_insert_right(mach_task_self(), port.name, port.name,
+                                MACH_MSG_TYPE_MAKE_SEND);
+    MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right";
+    port.send_right.reset(port.name);
+  }
+
+  if (recipe->deallocate_receive_right) {
+    port.receive_right.reset();
+  }
+
+  return port;
+}
+
+bool ConvertDescriptor(base::BufferIterator<uint8_t>* iterator,
+                       const Descriptor& descriptor_proto,
+                       SendablePort* opt_port) {
+  switch (descriptor_proto.descriptor_oneof_case()) {
+    case Descriptor::kPort: {
+      auto* descriptor = iterator->MutableObject<mach_msg_port_descriptor_t>();
+      SendablePort port = ConvertPort(descriptor_proto.port());
+      descriptor->name = port.name;
+      descriptor->pad1 = 0;
+      descriptor->pad2 = 0;
+      descriptor->disposition = port.disposition;
+      descriptor->type = MACH_MSG_PORT_DESCRIPTOR;
+      *opt_port = std::move(port);
+      return true;
+    }
+    case Descriptor::kOol: {
+      auto* descriptor = iterator->MutableObject<mach_msg_ool_descriptor_t>();
+      descriptor->address =
+          const_cast<char*>(descriptor_proto.ool().data().data());
+      descriptor->size = descriptor_proto.ool().data().size();
+      descriptor->copy = MACH_MSG_VIRTUAL_COPY;
+      descriptor->pad1 = 0;
+      descriptor->type = MACH_MSG_OOL_DESCRIPTOR;
+      return true;
+    }
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
+SendableMessage ConvertProtoToMachMessage(const MachMessage& proto) {
+  SendableMessage message;
+
+  const size_t descriptor_count = proto.descriptors().size();
+  const size_t data_size = proto.data().size();
+  const bool include_body =
+      proto.include_body_if_not_complex() || descriptor_count > 0;
+
+  // This is the maximum size of the message. Depending on the descriptor type,
+  // the actual msgh_size may be less.
+  const size_t message_size =
+      sizeof(mach_msg_header_t) + (include_body ? sizeof(mach_msg_body_t) : 0) +
+      (sizeof(mach_msg_descriptor_t) * descriptor_count) + data_size;
+  message.buffer = std::make_unique<uint8_t[]>(round_msg(message_size));
+
+  base::BufferIterator<uint8_t> iterator(message.buffer.get(), message_size);
+
+  auto* header = iterator.MutableObject<mach_msg_header_t>();
+  message.header = header;
+  header->msgh_id = proto.id();
+
+  if (proto.has_local_port()) {
+    SendablePort port = ConvertPort(proto.local_port());
+    auto disposition = port.disposition;
+    // It's not legal to have a receive reply report.
+    if (disposition != MACH_MSG_TYPE_MOVE_RECEIVE) {
+      header->msgh_bits |= MACH_MSGH_BITS(0, disposition);
+      header->msgh_local_port = port.name;
+      message.ports.push_back(std::move(port));
+    }
+  }
+
+  if (include_body) {
+    auto* body = iterator.MutableObject<mach_msg_body_t>();
+    body->msgh_descriptor_count = descriptor_count;
+  }
+
+  if (descriptor_count > 0) {
+    header->msgh_bits |= MACH_MSGH_BITS_COMPLEX;
+    for (const auto& descriptor : proto.descriptors()) {
+      SendablePort opt_port;
+      if (!ConvertDescriptor(&iterator, descriptor, &opt_port)) {
+        return SendableMessage();
+      }
+      if (opt_port.name != MACH_PORT_NULL) {
+        message.ports.push_back(std::move(opt_port));
+      }
+    }
+  }
+
+  auto data = iterator.MutableSpan<uint8_t>(data_size);
+  memcpy(data.data(), proto.data().data(), proto.data().size());
+
+  header->msgh_size = round_msg(iterator.position());
+
+  return message;
+}
+
+SendResult SendMessage(mach_port_t remote_port, const MachMessage& proto) {
+  SendResult result;
+  result.message = ConvertProtoToMachMessage(proto);
+  if (!result.message.header) {
+    result.kr = KERN_FAILURE;
+    return result;
+  }
+
+  result.message.header->msgh_remote_port = remote_port;
+  result.message.header->msgh_bits |=
+      MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+
+  base::ScopedMachMsgDestroy scoped_message(result.message.header);
+
+  result.kr = mach_msg_send(result.message.header);
+
+  if (result.kr == KERN_SUCCESS) {
+    scoped_message.Disarm();
+  }
+
+  return result;
+}
+
+}  // namespace mach_fuzzer
diff --git a/testing/libfuzzer/fuzzers/mach/mach_message_converter.h b/testing/libfuzzer/fuzzers/mach/mach_message_converter.h
new file mode 100644
index 0000000..9c9f61e
--- /dev/null
+++ b/testing/libfuzzer/fuzzers/mach/mach_message_converter.h
@@ -0,0 +1,62 @@
+// Copyright 2019 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.
+
+#ifndef TESTING_LIBFUZZER_FUZZERS_MACH_MACH_MESSAGE_CONVERTER_H_
+#define TESTING_LIBFUZZER_FUZZERS_MACH_MACH_MESSAGE_CONVERTER_H_
+
+#include <mach/mach.h>
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/mac/scoped_mach_port.h"
+#include "testing/libfuzzer/fuzzers/mach/mach_message.pb.h"
+
+namespace mach_fuzzer {
+
+// Container for a Mach port right that will be sent in a message.
+struct SendablePort {
+  mach_port_t name = MACH_PORT_NULL;
+  mach_msg_type_name_t disposition = 0;
+  MachPortType proto_type = static_cast<MachPortType>(-1);
+
+  base::mac::ScopedMachSendRight send_right;
+  base::mac::ScopedMachReceiveRight receive_right;
+};
+
+// Holds the buffer allocation and port references for a message to be sent.
+struct SendableMessage {
+  // The message buffer.
+  std::unique_ptr<uint8_t[]> buffer;
+
+  // The |ports| are also encoded into the body of the message, but they are
+  // accessible here to allow for further manipulation.
+  std::vector<SendablePort> ports;
+
+  // Pointer to the header of the message stored in |buffer|.
+  mach_msg_header_t* header = nullptr;
+};
+
+// Converts the given protobuf message into a live Mach message, including port
+// rights.
+SendableMessage ConvertProtoToMachMessage(const MachMessage& proto);
+
+// Takes the protobuf |proto|, converts it to a Mach message using
+// ConvertProtoToMachMessage(), and then sends it via |local_port|. The port
+// named by |local_port| must have a send right, which will be copied.
+struct SendResult {
+  // The return value from mach_msg_send().
+  kern_return_t kr;
+
+  // The message that was sent, including its descriptors. This allows callers
+  // to control the lifetimes of any Mach rights after the message has been
+  // sent.
+  SendableMessage message;
+};
+SendResult SendMessage(mach_port_t local_port, const MachMessage& proto);
+
+}  // namespace mach_fuzzer
+
+#endif  // TESTING_LIBFUZZER_FUZZERS_MACH_MACH_MESSAGE_CONVERTER_H_
diff --git a/third_party/protobuf/proto_library.gni b/third_party/protobuf/proto_library.gni
index bf22f7b..5c4ffa0 100644
--- a/third_party/protobuf/proto_library.gni
+++ b/third_party/protobuf/proto_library.gni
@@ -429,3 +429,73 @@
     }
   }
 }
+
+# Convert a protocol buffer between text and binary formats.
+# This can be used to run protoc with the --encode or --decode options.
+# Parameters:
+#
+#   sources: list of string
+#       The sources to loop over and run protoc on
+#
+#   inputs: list of string
+#       The file dependencies for the action. This should be the list of .proto
+#       files involved in the conversion operation.
+#
+#   output_pattern: string
+#       A path pattern with source expansion variables (like source_name_part)
+#       for where the result of conversion should be placed.
+#
+#   deps: (optional) list of label
+#       Additional dependencies for the target.
+#
+#   args: list of string
+#       Arguments to pass to the protoc tool. This could include -I for include
+#       paths, as well as the name of the proto file.
+#
+#
+# Example to convert a .textproto to a .binarybp:
+#   protoc_convert("convert_foo") {
+#     sources = [
+#       "test/data/example1.textproto",
+#       "test/data/example2.textproto",
+#     ]
+#     inputs = [
+#       "//component/core/foo.proto",
+#     ]
+#     output_pattern = "$target_gen_dir/foo_data/{{source_name_part}}.binarypb"
+#     args = [
+#       "--encode=foo.FooMessage",
+#       "-I",
+#       rebase_path("//component/core"),
+#       "foo.proto",
+#     ]
+#   }
+template("protoc_convert") {
+  action_foreach(target_name) {
+    script = "//tools/protoc_wrapper/protoc_convert.py"
+
+    sources = invoker.sources
+
+    inputs = invoker.inputs
+
+    deps = [
+      "//third_party/protobuf:protoc",
+    ]
+    if (defined(invoker.deps)) {
+      deps += invoker.deps
+    }
+
+    outputs = [
+      invoker.output_pattern,
+    ]
+
+    args = [
+             "--protoc",
+             rebase_path("$root_out_dir/protoc"),
+             "--infile",
+             "{{source}}",
+             "--outfile",
+             rebase_path(invoker.output_pattern),
+           ] + invoker.args
+  }
+}
diff --git a/tools/protoc_wrapper/protoc_convert.py b/tools/protoc_wrapper/protoc_convert.py
new file mode 100644
index 0000000..1da63e4
--- /dev/null
+++ b/tools/protoc_wrapper/protoc_convert.py
@@ -0,0 +1,30 @@
+# Copyright 2019 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.
+
+"""Helper script for use by GN to encode/decode proto files. The protoc tool
+requires using stdin/stdout for the --encode/--decode options, but that form
+of processing is not supported by GN.
+"""
+
+import argparse
+import subprocess
+
+def Main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--protoc', help='Path to protoc compiler.')
+  parser.add_argument('--infile', required=True,
+                      help='Path to input file that will be used as stdin.')
+  parser.add_argument('--outfile', required=True,
+                      help='Path to output file that will be used as stdout.')
+  args, passthrough_args = parser.parse_known_args()
+
+  stdin = open(args.infile, 'r')
+  stdout = open(args.outfile, 'w')
+
+  subprocess.check_call([args.protoc] + passthrough_args, stdin=stdin,
+      stdout=stdout)
+
+
+if __name__ == '__main__':
+  Main()