Skip to content

Commit

Permalink
Add a new LPM fuzzer for Mach message servers.
Browse files Browse the repository at this point in the history
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}
  • Loading branch information
rsesek committed Mar 19, 2019
1 parent e14c680 commit d45d88c
Show file tree
Hide file tree
Showing 11 changed files with 512 additions and 0 deletions.
36 changes: 36 additions & 0 deletions base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import("//build/timestamp.gni")
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
Expand Down Expand Up @@ -3401,6 +3406,37 @@ fuzzer_test("base_json_string_escape_fuzzer") {
]
}

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",
Expand Down
1 change: 1 addition & 0 deletions base/mac/mach_port_rendezvous.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class BASE_EXPORT MachPortRendezvousServer {

private:
friend class MachPortRendezvousServerTest;
friend struct MachPortRendezvousFuzzer;

struct ClientData {
ClientData(ScopedDispatchObject<dispatch_source_t> exit_watcher,
Expand Down
51 changes: 51 additions & 0 deletions base/mac/mach_port_rendezvous_fuzzer.cc
Original file line number Diff line number Diff line change
@@ -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);
}
3 changes: 3 additions & 0 deletions base/test/data/mach_port_rendezvous_fuzz/dead_name.textproto
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local_port: DEAD_NAME
id: 0x6d727a76
include_body_if_not_complex: false
3 changes: 3 additions & 0 deletions base/test/data/mach_port_rendezvous_fuzz/send.textproto
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local_port: SEND
id: 0x6d727a76
include_body_if_not_complex: false
24 changes: 24 additions & 0 deletions testing/libfuzzer/fuzzers/mach/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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",
]
}
61 changes: 61 additions & 0 deletions testing/libfuzzer/fuzzers/mach/mach_message.proto
Original file line number Diff line number Diff line change
@@ -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;
}
171 changes: 171 additions & 0 deletions testing/libfuzzer/fuzzers/mach/mach_message_converter.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit d45d88c

Please sign in to comment.