| // 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 |