blob: be1adb2e66474eed3f98ee7feb7ecc2bfe3ec5db [file] [log] [blame]
// Copyright 2018 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/metrics/field_trial_memory_mac.h"
#include <bsm/libbsm.h>
#include <libproc.h>
#include <mach/mig.h>
#include <servers/bootstrap.h>
#include <unistd.h>
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_msg_destroy.h"
#include "base/strings/stringprintf.h"
namespace base {
namespace {
// The name to use in the bootstrap server, formatted with the BaseBundleID and
// PID of the server.
const char kBootstrapNameFormat[] = "%s.FieldTrialMemoryServer.%d";
enum FieldTrialMsgId : mach_msg_id_t {
kFieldTrialMsgIdRequest = 'FTrq',
kFieldTrialMsgIdResponse = 'FTsp',
};
// Message received by the server for handling lookup lookup requests.
struct FieldTrialMemoryRequestMessage : public mach_msg_base_t {
// The size of the message excluding the trailer, used for msgh_size.
static const mach_msg_size_t kSendSize;
mach_msg_audit_trailer_t trailer;
};
const mach_msg_size_t FieldTrialMemoryRequestMessage::kSendSize =
sizeof(FieldTrialMemoryRequestMessage) - sizeof(trailer);
// Message used for sending and receiving the memory object handle.
struct FieldTrialMemoryResponseMessage : public mach_msg_base_t {
// The size of the message excluding the trailer, used for msgh_size.
static const mach_msg_size_t kSendSize;
mach_msg_port_descriptor_t port;
mach_msg_trailer_t trailer;
};
const mach_msg_size_t FieldTrialMemoryResponseMessage::kSendSize =
sizeof(FieldTrialMemoryResponseMessage) - sizeof(trailer);
} // namespace
FieldTrialMemoryServer::FieldTrialMemoryServer(mach_port_t memory_object)
: memory_object_(memory_object), server_pid_(getpid()) {
DCHECK(memory_object != MACH_PORT_NULL);
}
FieldTrialMemoryServer::~FieldTrialMemoryServer() {}
bool FieldTrialMemoryServer::Start() {
std::string bootstrap_name = GetBootstrapName();
kern_return_t kr = bootstrap_check_in(
bootstrap_port, bootstrap_name.c_str(),
mac::ScopedMachReceiveRight::Receiver(server_port_).get());
if (kr != KERN_SUCCESS) {
BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_check_in " << bootstrap_name;
return false;
}
dispatch_source_ = std::make_unique<DispatchSourceMach>(
"org.chromium.base.FieldTrialMemoryServer", server_port_.get(), ^{
HandleRequest();
});
dispatch_source_->Resume();
return true;
}
// static
std::string FieldTrialMemoryServer::GetBootstrapName() {
return StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getpid());
}
void FieldTrialMemoryServer::HandleRequest() {
// Receive the request message, using the kernel audit token to ascertain the
// PID of the sender.
FieldTrialMemoryRequestMessage request{};
request.header.msgh_size = sizeof(request);
request.header.msgh_local_port = server_port_.get();
const mach_msg_option_t options =
MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
kern_return_t kr =
mach_msg(&request.header, options, 0, sizeof(request), server_port_.get(),
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "mach_msg receive";
return;
}
// Destroy the message in case of an early return, which will release
// any rights from a bad message. In the case of a disallowed sender,
// the destruction of the reply port will break them out of a mach_msg.
ScopedMachMsgDestroy scoped_message(&request.header);
if (request.header.msgh_id != kFieldTrialMsgIdRequest ||
request.header.msgh_size != request.kSendSize) {
// Do not reply to messages that are unexpected.
return;
}
// A client is allowed to look up the object if the sending process is a
// direct child of this server's process.
pid_t sender_pid = audit_token_to_pid(request.trailer.msgh_audit);
proc_bsdshortinfo sender{};
int rv = proc_pidinfo(sender_pid, PROC_PIDT_SHORTBSDINFO, 0, &sender,
PROC_PIDT_SHORTBSDINFO_SIZE);
if (rv != PROC_PIDT_SHORTBSDINFO_SIZE ||
sender.pbsi_ppid != static_cast<uint32_t>(server_pid_)) {
return;
}
FieldTrialMemoryResponseMessage response{};
response.header.msgh_bits =
MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) |
MACH_MSGH_BITS_COMPLEX;
response.header.msgh_size = response.kSendSize;
response.header.msgh_remote_port = request.header.msgh_remote_port;
response.header.msgh_id = kFieldTrialMsgIdResponse;
response.body.msgh_descriptor_count = 1;
response.port.name = memory_object_;
response.port.disposition = MACH_MSG_TYPE_COPY_SEND;
response.port.type = MACH_MSG_PORT_DESCRIPTOR;
scoped_message.Disarm();
kr = mach_msg(&response.header, MACH_SEND_MSG, response.header.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) << "mach_msg send";
}
// static
mac::ScopedMachSendRight FieldTrialMemoryClient::AcquireMemoryObject() {
mac::ScopedMachSendRight server_port;
std::string bootstrap_name = GetBootstrapName();
kern_return_t kr = bootstrap_look_up(
bootstrap_port, const_cast<char*>(bootstrap_name.c_str()),
mac::ScopedMachSendRight::Receiver(server_port).get());
if (kr != KERN_SUCCESS) {
BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << bootstrap_name;
return mac::ScopedMachSendRight();
}
return ChildSendRequest(std::move(server_port));
}
// static
std::string FieldTrialMemoryClient::GetBootstrapName() {
return StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getppid());
}
// static
mac::ScopedMachSendRight FieldTrialMemoryClient::ChildSendRequest(
mac::ScopedMachSendRight server_port) {
// Perform a send and receive mach_msg.
union {
FieldTrialMemoryRequestMessage request;
FieldTrialMemoryResponseMessage response;
} msg{};
msg.request.header.msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
// The size of |msg| is used for receiving since it includes space for the
// trailer, but for the request being sent, the size is just the base message.
msg.request.header.msgh_size = msg.request.kSendSize;
msg.request.header.msgh_remote_port = server_port.release();
msg.request.header.msgh_local_port = mig_get_reply_port();
msg.request.header.msgh_id = kFieldTrialMsgIdRequest;
kern_return_t kr =
mach_msg(&msg.request.header, MACH_SEND_MSG | MACH_RCV_MSG,
msg.request.header.msgh_size, sizeof(msg.response),
msg.request.header.msgh_local_port, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "mach_msg";
return mac::ScopedMachSendRight();
}
if (msg.response.header.msgh_id != kFieldTrialMsgIdResponse ||
msg.response.header.msgh_size != msg.response.kSendSize) {
return mac::ScopedMachSendRight();
}
return mac::ScopedMachSendRight(msg.response.port.name);
}
FieldTrialMemoryClient::FieldTrialMemoryClient() = default;
FieldTrialMemoryClient::~FieldTrialMemoryClient() = default;
} // namespace base