From d78b003ef1fb6efc6dfd7deb5ec9371ddff01a1b Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Tue, 16 Dec 2014 14:10:16 -0500 Subject: [PATCH] Add NotifyServer and its test. TEST=util_test NotifyServerTest.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/804633002 --- util/mach/child_port_server.h | 3 - util/mach/exc_server_variants.h | 3 - util/mach/notify_server.cc | 245 ++++++++++++++ util/mach/notify_server.h | 193 +++++++++++ util/mach/notify_server_test.cc | 550 ++++++++++++++++++++++++++++++++ util/util.gyp | 6 + 6 files changed, 994 insertions(+), 6 deletions(-) create mode 100644 util/mach/notify_server.cc create mode 100644 util/mach/notify_server.h create mode 100644 util/mach/notify_server_test.cc diff --git a/util/mach/child_port_server.h b/util/mach/child_port_server.h index 7c40809c..bc42cc09 100644 --- a/util/mach/child_port_server.h +++ b/util/mach/child_port_server.h @@ -59,13 +59,10 @@ class ChildPortServer : public MachMessageServer::Interface { explicit ChildPortServer(Interface* interface); // MachMessageServer::Interface: - bool MachMessageServerFunction(const mach_msg_header_t* in_header, mach_msg_header_t* out_header, bool* destroy_complex_request) override; - std::set MachMessageServerRequestIDs() override; - mach_msg_size_t MachMessageServerRequestSize() override; mach_msg_size_t MachMessageServerReplySize() override; diff --git a/util/mach/exc_server_variants.h b/util/mach/exc_server_variants.h index 46978679..d5d5cc54 100644 --- a/util/mach/exc_server_variants.h +++ b/util/mach/exc_server_variants.h @@ -102,13 +102,10 @@ class UniversalMachExcServer final : public MachMessageServer::Interface { ~UniversalMachExcServer(); // MachMessageServer::Interface: - bool MachMessageServerFunction(const mach_msg_header_t* in_header, mach_msg_header_t* out_header, bool* destroy_complex_request) override; - std::set MachMessageServerRequestIDs() override; - mach_msg_size_t MachMessageServerRequestSize() override; mach_msg_size_t MachMessageServerReplySize() override; diff --git a/util/mach/notify_server.cc b/util/mach/notify_server.cc new file mode 100644 index 00000000..05495c65 --- /dev/null +++ b/util/mach/notify_server.cc @@ -0,0 +1,245 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/mach/notify_server.h" + +#include "base/logging.h" +#include "util/mach/notifyServer.h" +#include "util/mach/mach_message.h" + +extern "C" { + +// These five functions are not used, and are in fact obsoleted by the other +// functionality implemented in this file. The standard MIG-generated +// notify_server() (in notifyServer.c) server dispatch routine usable with the +// standard mach_msg_server() function calls out to this function. +// notify_server() is unused and is replaced by the more flexible NotifyServer, +// but the linker still needs to see these five function definitions. + +kern_return_t do_mach_notify_port_deleted(notify_port_t notify, + mach_port_name_t name) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t do_mach_notify_port_destroyed(notify_port_t notify, + mach_port_t rights) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t do_mach_notify_no_senders(notify_port_t notify, + mach_port_mscount_t mscount) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t do_mach_notify_send_once(notify_port_t notify) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t do_mach_notify_dead_name(notify_port_t notify, + mach_port_name_t name) { + NOTREACHED(); + return KERN_FAILURE; +} + +} // extern "C" + +namespace { + +// The MIG-generated __MIG_check__Request__*() functions are not declared as +// accepting const data, but they could have been because they in fact do not +// modify the data. These wrapper functions are provided to bridge the const gap +// between the code in this file, which is const-correct and treats request +// message data as const, and the generated functions. + +kern_return_t MIGCheckRequestMachNotifyPortDeleted( + const __Request__mach_notify_port_deleted_t* in_request) { + using Request = __Request__mach_notify_port_deleted_t; + return __MIG_check__Request__mach_notify_port_deleted_t( + const_cast(in_request)); +} + +kern_return_t MIGCheckRequestMachNotifyPortDestroyed( + const __Request__mach_notify_port_destroyed_t* in_request) { + using Request = __Request__mach_notify_port_destroyed_t; + return __MIG_check__Request__mach_notify_port_destroyed_t( + const_cast(in_request)); +} + +kern_return_t MIGCheckRequestMachNotifyNoSenders( + const __Request__mach_notify_no_senders_t* in_request) { + using Request = __Request__mach_notify_no_senders_t; + return __MIG_check__Request__mach_notify_no_senders_t( + const_cast(in_request)); +} + +kern_return_t MIGCheckRequestMachNotifySendOnce( + const __Request__mach_notify_send_once_t* in_request) { + using Request = __Request__mach_notify_send_once_t; + return __MIG_check__Request__mach_notify_send_once_t( + const_cast(in_request)); +} + +kern_return_t MIGCheckRequestMachNotifyDeadName( + const __Request__mach_notify_dead_name_t* in_request) { + using Request = __Request__mach_notify_dead_name_t; + return __MIG_check__Request__mach_notify_dead_name_t( + const_cast(in_request)); +} + +} // namespace + +namespace crashpad { + +NotifyServer::NotifyServer(NotifyServer::Interface* interface) + : MachMessageServer::Interface(), + interface_(interface) { +} + +bool NotifyServer::MachMessageServerFunction( + const mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) { + PrepareMIGReplyFromRequest(in_header, out_header); + + const mach_msg_trailer_t* in_trailer = + MachMessageTrailerFromHeader(in_header); + + switch (in_header->msgh_id) { + case MACH_NOTIFY_PORT_DELETED: { + // mach_notify_port_deleted(), do_mach_notify_port_deleted(). + using Request = __Request__mach_notify_port_deleted_t; + const Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = MIGCheckRequestMachNotifyPortDeleted(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetMIGReplyError(out_header, kr); + return true; + } + + using Reply = __Reply__mach_notify_port_deleted_t; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->DoMachNotifyPortDeleted(in_header->msgh_local_port, + in_request->name, + in_trailer); + return true; + } + + case MACH_NOTIFY_PORT_DESTROYED: { + // mach_notify_port_destroyed(), do_mach_notify_port_destroyed(). + using Request = __Request__mach_notify_port_destroyed_t; + const Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = MIGCheckRequestMachNotifyPortDestroyed(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetMIGReplyError(out_header, kr); + return true; + } + + using Reply = __Reply__mach_notify_port_destroyed_t; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->DoMachNotifyPortDestroyed(in_header->msgh_local_port, + in_request->rights.name, + in_trailer, + destroy_complex_request); + return true; + } + + case MACH_NOTIFY_NO_SENDERS: { + // mach_notify_no_senders(), do_mach_notify_no_senders(). + using Request = __Request__mach_notify_no_senders_t; + const Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = MIGCheckRequestMachNotifyNoSenders(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetMIGReplyError(out_header, kr); + return true; + } + + using Reply = __Reply__mach_notify_no_senders_t; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->DoMachNotifyNoSenders(in_header->msgh_local_port, + in_request->mscount, + in_trailer); + return true; + } + + case MACH_NOTIFY_SEND_ONCE: { + // mach_notify_send_once(), do_mach_notify_send_once(). + using Request = __Request__mach_notify_send_once_t; + const Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = MIGCheckRequestMachNotifySendOnce(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetMIGReplyError(out_header, kr); + return true; + } + + using Reply = __Reply__mach_notify_send_once_t; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->DoMachNotifySendOnce(in_header->msgh_local_port, + in_trailer); + return true; + } + + case MACH_NOTIFY_DEAD_NAME: { + // mach_notify_dead_name(), do_mach_notify_dead_name(). + using Request = __Request__mach_notify_dead_name_t; + const Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = MIGCheckRequestMachNotifyDeadName(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetMIGReplyError(out_header, kr); + return true; + } + + using Reply = __Reply__mach_notify_dead_name_t; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->DoMachNotifyDeadName(in_header->msgh_local_port, + in_request->name, + in_trailer); + return true; + } + + default: { + SetMIGReplyError(out_header, MIG_BAD_ID); + return false; + } + } +} + +std::set NotifyServer::MachMessageServerRequestIDs() { + const mach_msg_id_t request_ids[] = { + MACH_NOTIFY_PORT_DELETED, + MACH_NOTIFY_PORT_DESTROYED, + MACH_NOTIFY_NO_SENDERS, + MACH_NOTIFY_SEND_ONCE, + MACH_NOTIFY_DEAD_NAME, + }; + return std::set(&request_ids[0], + &request_ids[arraysize(request_ids)]); +} + +mach_msg_size_t NotifyServer::MachMessageServerRequestSize() { + return sizeof(__RequestUnion__do_notify_subsystem); +} + +mach_msg_size_t NotifyServer::MachMessageServerReplySize() { + return sizeof(__ReplyUnion__do_notify_subsystem); +} + +} // namespace crashpad diff --git a/util/mach/notify_server.h b/util/mach/notify_server.h new file mode 100644 index 00000000..311ad3a0 --- /dev/null +++ b/util/mach/notify_server.h @@ -0,0 +1,193 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_ +#define CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_ + +#include + +#include + +#include "base/basictypes.h" +#include "util/mach/mach_message_server.h" + +namespace crashpad { + +//! \brief A server interface for the `notify` Mach subsystem. +//! +//! The mach +//! port notifications thread on the darwin-development +//! mailing list (now known as darwin-dev) +//! is good background for the various notification types. +class NotifyServer : public MachMessageServer::Interface { + public: + //! \brief An interface that the different request messages that are a part of + //! the `notify` Mach subsystem can be dispatched to. + class Interface { + public: + //! \brief Handles port-deleted notifications sent by + //! `mach_notify_port_deleted()`. + //! + //! A port-deleted notification is generated when a port with a dead-name + //! notification request is destroyed and the port name becomes available + //! for reuse. + //! + //! This behaves equivalently to a `do_mach_notify_port_deleted()` function + //! used with `notify_server()`. + //! + //! \param[in] notify The Mach port that the notification was sent to. + //! \param[in] name The name that formerly referenced the deleted port. When + //! this method is called, \a name no longer corresponds to the port + //! that has been deleted, and may be reused for another purpose. + //! \param[in] trailer The trailer received with the notification message. + virtual kern_return_t DoMachNotifyPortDeleted( + notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer) = 0; + + //! \brief Handles port-destroyed notifications sent by + //! `mach_notify_port_destroyed()`. + //! + //! A port-destroyed notification is generated when a receive right with a + //! port-destroyed notification request is destroyed. Rather than destroying + //! the receive right, it is transferred via this notification’s \a rights + //! parameter. + //! + //! This behaves equivalently to a `do_mach_notify_port_destroyed()` + //! function used with `notify_server()`. + //! + //! \param[in] notify The Mach port that the notification was sent to. + //! \param[in] rights A receive right for the port that would have been + //! destroyed. The callee takes ownership of this port, however, if the + //! callee does not wish to take ownership, it may set \a + //! destroy_request to `true`. + //! \param[in] trailer The trailer received with the notification message. + //! \param[out] destroy_request `true` if the request message is to be + //! destroyed even when this method returns success. See + //! MachMessageServer::Interface. + virtual kern_return_t DoMachNotifyPortDestroyed( + notify_port_t notify, + mach_port_t rights, + const mach_msg_trailer_t* trailer, + bool* destroy_request) = 0; + + //! \brief Handles no-senders notifications sent by + //! `mach_notify_no_senders()`. + //! + //! A no-senders notification is generated when a receive right with a + //! no-senders notification request loses its last corresponding send right. + //! + //! This behaves equivalently to a `do_mach_notify_no_senders()` function + //! used with `notify_server()`. + //! + //! \param[in] notify The Mach port that the notification was sent to. + //! \param[in] mscount The value of the sender-less port’s make-send count + //! at the time the notification was generated. + //! \param[in] trailer The trailer received with the notification message. + virtual kern_return_t DoMachNotifyNoSenders( + notify_port_t notify, + mach_port_mscount_t mscount, + const mach_msg_trailer_t* trailer) = 0; + + //! \brief Handles send-once notifications sent by + //! `mach_notify_send_once()`. + //! + //! A send-once notification is generated when a send-once right is + //! destroyed without being used. + //! + //! This behaves equivalently to a `do_mach_notify_send_once()` function + //! used with `notify_server()`. + //! + //! \param[in] notify The Mach port that the notification was sent to. + //! \param[in] trailer The trailer received with the notification message. + //! + //! \note Unlike the other notifications in the `notify` subsystem, + //! send-once notifications are not generated as a result of a + //! notification request, but are generated any time a send-once right + //! is destroyed rather than being used. The notification is sent via + //! the send-once right to its receiver. These notifications are more + //! useful for clients, not servers. Send-once notifications are + //! normally handled by MIG-generated client routines, which make + //! send-once rights for their reply ports and interpret send-once + //! notifications as a signal that there will be no reply. Although not + //! expected to be primarily useful for servers, this method is provided + //! because send-once notifications are defined as a part of the + //! `notify` subsystem. + virtual kern_return_t DoMachNotifySendOnce( + notify_port_t notify, + const mach_msg_trailer_t* trailer) = 0; + + //! \brief Handles dead-name notifications sent by + //! `mach_notify_dead_name()`. + //! + //! A dead-name notification is generated when a port with a dead-name + //! notification request is destroyed and the right becomes a dead name. + //! + //! This behaves equivalently to a `do_mach_notify_dead_name()` function + //! used with `notify_server()`. + //! + //! \param[in] notify The Mach port that the notification was sent to. + //! \param[in] name The dead name. Although this is transferred as a + //! `mach_port_name_t` and not a `mach_port_t`, the callee assumes an + //! additional reference to this port when this method is called. See + //! the note below. + //! \param[in] trailer The trailer received with the notification message. + //! + //! \note When a dead-name notification is generated, the user reference + //! count of the dead name is incremented. A send right with one + //! reference that becomes a dead name will have one dead-name + //! reference, and the dead-name notification will add another dead-name + //! reference, for a total of 2. DoMachNotifyDeadName() implementations + //! must take care to deallocate this extra reference. There is no \a + //! destroy_request parameter to simplify this operation because + //! dead-name notifications carry a port name only (\a name is of type + //! `mach_port_name_t`) without transferring port rights, and are thus + //! not complex Mach messages. + virtual kern_return_t DoMachNotifyDeadName( + notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer) = 0; + + protected: + ~Interface() {} + }; + + //! \brief Constructs an object of this class. + //! + //! \param[in] interface The interface to dispatch requests to. Weak. + explicit NotifyServer(Interface* interface); + + // MachMessageServer::Interface: + + bool MachMessageServerFunction(const mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override; + + std::set MachMessageServerRequestIDs() override; + + mach_msg_size_t MachMessageServerRequestSize() override; + mach_msg_size_t MachMessageServerReplySize() override; + + private: + Interface* interface_; // weak + + DISALLOW_COPY_AND_ASSIGN(NotifyServer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_NOTIFY_SERVER_H_ diff --git a/util/mach/notify_server_test.cc b/util/mach/notify_server_test.cc new file mode 100644 index 00000000..56529032 --- /dev/null +++ b/util/mach/notify_server_test.cc @@ -0,0 +1,550 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/mach/notify_server.h" + +#include "base/compiler_specific.h" +#include "base/mac/scoped_mach_port.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "util/mach/mach_extensions.h" +#include "util/mach/mach_message.h" +#include "util/mach/mach_message_server.h" +#include "util/test/mac/mach_errors.h" + +namespace crashpad { +namespace test { +namespace { + +using testing::AllOf; +using testing::Eq; +using testing::Invoke; +using testing::Ne; +using testing::Pointee; +using testing::ResultOf; +using testing::Return; +using testing::SetArgPointee; +using testing::StrictMock; +using testing::WithArg; + +//! \brief Allocates and returns a new receive right. +//! +//! \return The new receive right. On failure, `MACH_PORT_NULL` with a gtest +//! failure added. +mach_port_t NewReceiveRight() { + mach_port_t receive_right; + kern_return_t kr = mach_port_allocate( + mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &receive_right); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate"); + return MACH_PORT_NULL; + } + return receive_right; +} + +//! \brief Adds a send right to an existing receive right. +//! +//! \param[in] receive_right The receive right to add a send right to. +//! +//! \return The send right, which will have the same name as the receive right. +//! On failure, `MACH_PORT_NULL` with a gtest failure added. +mach_port_t SendRightFromReceiveRight(mach_port_t receive_right) { + kern_return_t kr = mach_port_insert_right( + mach_task_self(), receive_right, receive_right, MACH_MSG_TYPE_MAKE_SEND); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) + << MachErrorMessage(kr, "mach_port_insert_right"); + return MACH_PORT_NULL; + } + + return receive_right; +} + +//! \brief Extracts a send-once right from a receive right. +//! +//! \param[in] receive_right The receive right to make a send-once right from. +//! +//! \return The send-once right. On failure, `MACH_PORT_NULL` with a gtest +//! failure added. +mach_port_t SendOnceRightFromReceiveRight(mach_port_t receive_right) { + mach_port_t send_once_right; + mach_msg_type_name_t acquired_type; + kern_return_t kr = mach_port_extract_right(mach_task_self(), + receive_right, + MACH_MSG_TYPE_MAKE_SEND_ONCE, + &send_once_right, + &acquired_type); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) + << MachErrorMessage(kr, "mach_port_extract_right"); + return MACH_PORT_NULL; + } + + EXPECT_EQ(implicit_cast(MACH_MSG_TYPE_PORT_SEND_ONCE), + acquired_type); + + return send_once_right; +} + +//! \brief Deallocates a Mach port by calling `mach_port_deallocate()`. +//! +//! This function exists to adapt `mach_port_deallocate()` to a function that +//! accepts a single argument and has no return value. It can be used with the +//! testing::Invoke() gmock action. +//! +//! On failure, a gtest failure will be added. +void MachPortDeallocate(mach_port_t port) { + kern_return_t kr = mach_port_deallocate(mach_task_self(), port); + EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_deallocate"); +} + +//! \brief Determines whether a specific right is held for a Mach port. +//! +//! \param[in] port The port to check for a right. +//! \param[in] right The right to check for. +//! +//! \return `true` if \a port has \a right, `false` otherwise. On faliure, +//! `false` with a gtest failure added. +bool IsRight(mach_port_t port, mach_port_type_t right) { + mach_port_type_t type; + kern_return_t kr = mach_port_type(mach_task_self(), port, &type); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_type"); + return false; + } + + return type & right; +} + +//! \brief Determines whether a receive right is held for a Mach port. +//! +//! This is a special single-argument form of IsRight() for ease of use in a +//! gmock matcher. +//! +//! \param[in] port The port to check for a receive right. +//! +//! \return `true` if a receive right is held, `false` otherwise. On faliure, +//! `false` with a gtest failure added. +bool IsReceiveRight(mach_port_t port) { + return IsRight(port, MACH_PORT_TYPE_RECEIVE); +} + +//! \brief Returns the user reference count for port rights. +//! +//! \param[in] port The port whose user reference count should be returned. +//! \param[in] right The port right to return the user reference count for. +//! +//! \return The user reference count for the specified port and right. On +//! failure, `-1` with a gtest failure added. +mach_port_urefs_t RightRefCount(mach_port_t port, mach_port_right_t right) { + mach_port_urefs_t refs; + kern_return_t kr = mach_port_get_refs(mach_task_self(), port, right, &refs); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_get_refs"); + return -1; + } + + return refs; +} + +//! \brief Returns the user reference count for a port’s dead-name rights. +//! +//! This is a special single-argument form of RightRefCount() for ease of use in +//! a gmock matcher. +//! +//! \param[in] port The port whose dead-name user reference count should be +//! returned. +//! +//! \return The user reference count for the port’s dead-name rights. On +//! failure, `-1` with a gtest failure added. +mach_port_urefs_t DeadNameRightRefCount(mach_port_t port) { + return RightRefCount(port, MACH_PORT_RIGHT_DEAD_NAME); +} + +class NotifyServerTestBase : public testing::Test, + public NotifyServer::Interface { + public: + // NotifyServer::Interface: + + MOCK_METHOD3(DoMachNotifyPortDeleted, + kern_return_t(notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer)); + + MOCK_METHOD4(DoMachNotifyPortDestroyed, + kern_return_t(notify_port_t notify, + mach_port_t rights, + const mach_msg_trailer_t* trailer, + bool* destroy_request)); + + MOCK_METHOD3(DoMachNotifyNoSenders, + kern_return_t(notify_port_t notify, + mach_port_mscount_t mscount, + const mach_msg_trailer_t* trailer)); + + MOCK_METHOD2(DoMachNotifySendOnce, + kern_return_t(notify_port_t notify, + const mach_msg_trailer_t* trailer)); + + MOCK_METHOD3(DoMachNotifyDeadName, + kern_return_t(notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer)); + + protected: + NotifyServerTestBase() : testing::Test(), NotifyServer::Interface() {} + + ~NotifyServerTestBase() override {} + + //! \brief Requests a Mach port notification. + //! + //! \a name, \a variant, and \a sync are passed as-is to + //! `mach_port_request_notification()`. The notification will be sent to a + //! send-once right made from ServerPort(). Any previous send right for the + //! notification will be deallocated. + //! + //! \return `true` on success, `false` on failure with a gtest failure added. + bool RequestMachPortNotification(mach_port_t name, + mach_msg_id_t variant, + mach_port_mscount_t sync) { + mach_port_t previous; + kern_return_t kr = + mach_port_request_notification(mach_task_self(), + name, + variant, + sync, + ServerPort(), + MACH_MSG_TYPE_MAKE_SEND_ONCE, + &previous); + if (kr != KERN_SUCCESS) { + EXPECT_EQ(KERN_SUCCESS, kr) + << MachErrorMessage(kr, "mach_port_request_notification"); + return false; + } + + base::mac::ScopedMachSendRight previous_owner(previous); + EXPECT_EQ(kMachPortNull, previous); + + return true; + } + + //! \brief Runs a NotifyServer Mach message server. + //! + //! The server will listen on ServerPort() in persistent nonblocking mode, and + //! dispatch received messages to the appropriate NotifyServer::Interface + //! method. gmock expectations check that the proper method, if any, is called + //! exactly once, and that no undesired methods are called. + //! + //! MachMessageServer::Run() is expected to return `MACH_RCV_TIMED_OUT`, + //! because it runs in persistent nonblocking mode. If it returns anything + //! else, a gtest assertion is added. + void RunServer() { + NotifyServer notify_server(this); + mach_msg_return_t mr = + MachMessageServer::Run(¬ify_server, + ServerPort(), + MACH_MSG_OPTION_NONE, + MachMessageServer::kPersistent, + MachMessageServer::kReceiveLargeError, + kMachMessageTimeoutNonblocking); + ASSERT_EQ(MACH_RCV_TIMED_OUT, mr) + << MachErrorMessage(mr, "MachMessageServer::Run"); + } + + //! \brief Returns the receive right to be used for the server. + //! + //! This receive right is created lazily on a per-test basis. It is destroyed + //! by TearDown() at the conclusion of each test. + //! + //! \return The server port receive right, creating it if one has not yet been + //! established for the current test. On failure, returns `MACH_PORT_NULL` + //! with a gtest failure added. + mach_port_t ServerPort() { + if (!server_port_) { + server_port_.reset(NewReceiveRight()); + } + + return server_port_; + } + + // testing::Test: + void TearDown() override { + server_port_.reset(); + } + + private: + base::mac::ScopedMachReceiveRight server_port_; + + DISALLOW_COPY_AND_ASSIGN(NotifyServerTestBase); +}; + +using NotifyServerTest = StrictMock; + +TEST_F(NotifyServerTest, Basic) { + NotifyServer server(this); + + std::set expect_request_ids; + expect_request_ids.insert(MACH_NOTIFY_PORT_DELETED); + expect_request_ids.insert(MACH_NOTIFY_PORT_DESTROYED); + expect_request_ids.insert(MACH_NOTIFY_NO_SENDERS); + expect_request_ids.insert(MACH_NOTIFY_SEND_ONCE); + expect_request_ids.insert(MACH_NOTIFY_DEAD_NAME); + EXPECT_EQ(expect_request_ids, server.MachMessageServerRequestIDs()); + + // The port-destroyed notification is the largest request message in the + // subsystem. defines the same structure, but with a basic + // trailer, so use offsetof to get the size of the basic structure without any + // trailer. + EXPECT_EQ(offsetof(mach_port_destroyed_notification_t, trailer), + server.MachMessageServerRequestSize()); + + mig_reply_error_t reply; + EXPECT_EQ(sizeof(reply), server.MachMessageServerReplySize()); +} + +// When no notifications are requested, nothing should happen. +TEST_F(NotifyServerTest, NoNotification) { + RunServer(); +} + +// When a send-once right with a dead-name notification request is deallocated, +// a port-deleted notification should be generated. +TEST_F(NotifyServerTest, MachNotifyPortDeleted) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + base::mac::ScopedMachSendRight send_once_right( + SendOnceRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_once_right); + + ASSERT_TRUE(RequestMachPortNotification( + send_once_right, MACH_NOTIFY_DEAD_NAME, 0)); + + EXPECT_CALL(*this, DoMachNotifyPortDeleted(ServerPort(), + send_once_right.get(), + Ne(nullptr))) + .WillOnce(Return(MIG_NO_REPLY)) + .RetiresOnSaturation(); + + send_once_right.reset(); + + RunServer(); +} + +// When a receive right with a port-destroyed notification request is destroyed, +// a port-destroyed notification should be generated. +TEST_F(NotifyServerTest, MachNotifyPortDestroyed) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + ASSERT_TRUE(RequestMachPortNotification( + receive_right, MACH_NOTIFY_PORT_DESTROYED, 0)); + + EXPECT_CALL(*this, DoMachNotifyPortDestroyed(ServerPort(), + ResultOf(IsReceiveRight, true), + Ne(nullptr), + Pointee(Eq(false)))) + .WillOnce(DoAll(SetArgPointee<3>(true), Return(MIG_NO_REPLY))) + .RetiresOnSaturation(); + + receive_right.reset(); + + RunServer(); +} + +// When a receive right with a port-destroyed notification request is not +// destroyed, no port-destroyed notification should be generated. +TEST_F(NotifyServerTest, MachNotifyPortDestroyed_NoNotification) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + ASSERT_TRUE(RequestMachPortNotification( + receive_right, MACH_NOTIFY_PORT_DESTROYED, 0)); + + RunServer(); +} + +// When a no-senders notification request is registered for a receive right with +// no senders, a no-senders notification should be generated. +TEST_F(NotifyServerTest, MachNotifyNoSenders_NoSendRight) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + ASSERT_TRUE(RequestMachPortNotification( + receive_right, MACH_NOTIFY_NO_SENDERS, 0)); + + EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 0, Ne(nullptr))) + .WillOnce(Return(MIG_NO_REPLY)) + .RetiresOnSaturation(); + + RunServer(); +} + +// When the last send right corresponding to a receive right with a no-senders +// notification request is deallocated, a no-senders notification should be +// generated. +TEST_F(NotifyServerTest, MachNotifyNoSenders_SendRightDeallocated) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + base::mac::ScopedMachSendRight send_right( + SendRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_right); + + ASSERT_TRUE(RequestMachPortNotification( + receive_right, MACH_NOTIFY_NO_SENDERS, 1)); + + EXPECT_CALL(*this, DoMachNotifyNoSenders(ServerPort(), 1, Ne(nullptr))) + .WillOnce(Return(MIG_NO_REPLY)) + .RetiresOnSaturation(); + + send_right.reset(); + + RunServer(); +} + +// When the a receive right with a no-senders notification request never loses +// all senders, no no-senders notification should be generated. +TEST_F(NotifyServerTest, MachNotifyNoSenders_NoNotification) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + base::mac::ScopedMachSendRight send_right_0( + SendRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_right_0); + + base::mac::ScopedMachSendRight send_right_1( + SendRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_right_1); + + ASSERT_TRUE(RequestMachPortNotification( + receive_right, MACH_NOTIFY_NO_SENDERS, 1)); + + send_right_1.reset(); + + RunServer(); + + EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_RECEIVE)); + EXPECT_EQ(1u, RightRefCount(receive_right, MACH_PORT_RIGHT_SEND)); +} + +// When a send-once right is deallocated without being used, a send-once +// notification notification should be sent via the send-once right. +TEST_F(NotifyServerTest, MachNotifySendOnce_ExplicitDeallocation) { + base::mac::ScopedMachSendRight send_once_right( + SendOnceRightFromReceiveRight(ServerPort())); + ASSERT_NE(kMachPortNull, send_once_right); + + EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr))) + .WillOnce(Return(MIG_NO_REPLY)) + .RetiresOnSaturation(); + + send_once_right.reset(); + + RunServer(); +} + +// When a send-once right is sent to a receiver that never dequeues the message, +// the send-once right is destroyed, and a send-once notification should appear +// on the reply port. +TEST_F(NotifyServerTest, MachNotifySendOnce_ImplicitDeallocation) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + mach_msg_empty_send_t message = {}; + message.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); + message.header.msgh_size = sizeof(message); + message.header.msgh_remote_port = receive_right; + message.header.msgh_local_port = ServerPort(); + mach_msg_return_t mr = mach_msg(&message.header, + MACH_SEND_MSG | MACH_SEND_TIMEOUT, + message.header.msgh_size, + 0, + MACH_PORT_NULL, + 0, + MACH_PORT_NULL); + ASSERT_EQ(MACH_MSG_SUCCESS, mr) << MachErrorMessage(mr, "mach_msg"); + + EXPECT_CALL(*this, DoMachNotifySendOnce(ServerPort(), Ne(nullptr))) + .WillOnce(Return(MIG_NO_REPLY)) + .RetiresOnSaturation(); + + receive_right.reset(); + + RunServer(); +} + +// When the receive right corresponding to a send-once right with a dead-name +// notification request is destroyed, a dead-name notification should be +// generated. +TEST_F(NotifyServerTest, MachNotifyDeadName) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + base::mac::ScopedMachSendRight send_once_right( + SendOnceRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_once_right); + + ASSERT_TRUE(RequestMachPortNotification( + send_once_right, MACH_NOTIFY_DEAD_NAME, 0)); + + // send_once_right becomes a dead name with the send-once right’s original + // user reference count of 1, but the dead-name notification increments the + // dead-name reference count, so it becomes 2. Take care to deallocate that + // reference. The original reference is managed by send_once_right_owner. + EXPECT_CALL(*this, + DoMachNotifyDeadName(ServerPort(), + AllOf(send_once_right.get(), + ResultOf(DeadNameRightRefCount, 2)), + Ne(nullptr))) + .WillOnce(DoAll(WithArg<1>(Invoke(MachPortDeallocate)), + Return(MIG_NO_REPLY))) + .RetiresOnSaturation(); + + receive_right.reset(); + + RunServer(); + + EXPECT_TRUE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME)); + + EXPECT_EQ(0u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE)); + EXPECT_EQ(1u, DeadNameRightRefCount(send_once_right)); +} + +// When the receive right corresponding to a send-once right with a dead-name +// notification request is not destroyed, no dead-name notification should be +// generated. +TEST_F(NotifyServerTest, MachNotifyDeadName_NoNotification) { + base::mac::ScopedMachReceiveRight receive_right(NewReceiveRight()); + ASSERT_NE(kMachPortNull, receive_right); + + base::mac::ScopedMachSendRight send_once_right( + SendOnceRightFromReceiveRight(receive_right)); + ASSERT_NE(kMachPortNull, send_once_right); + + ASSERT_TRUE(RequestMachPortNotification( + send_once_right, MACH_NOTIFY_DEAD_NAME, 0)); + + RunServer(); + + EXPECT_FALSE(IsRight(send_once_right, MACH_PORT_TYPE_DEAD_NAME)); + + EXPECT_EQ(1u, RightRefCount(send_once_right, MACH_PORT_RIGHT_SEND_ONCE)); + EXPECT_EQ(0u, DeadNameRightRefCount(send_once_right)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index 0264f63f..2882b046 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -62,6 +62,8 @@ 'mach/mach_message.h', 'mach/mach_message_server.cc', 'mach/mach_message_server.h', + 'mach/notify_server.cc', + 'mach/notify_server.h', 'mach/scoped_task_suspend.cc', 'mach/scoped_task_suspend.h', 'mach/symbolic_constants_mach.cc', @@ -126,12 +128,14 @@ 'files': [ '$(SDKROOT)/usr/include/mach/exc.defs', '$(SDKROOT)/usr/include/mach/mach_exc.defs', + '$(SDKROOT)/usr/include/mach/notify.defs', ], }, ], 'sources': [ '<(INTERMEDIATE_DIR)/util/mach/exc.defs', '<(INTERMEDIATE_DIR)/util/mach/mach_exc.defs', + '<(INTERMEDIATE_DIR)/util/mach/notify.defs', ], }, { # else: GENERATOR!="ninja" # The Xcode generator does copies after rules, so the above trick @@ -140,6 +144,7 @@ 'sources': [ '$(SDKROOT)/usr/include/mach/exc.defs', '$(SDKROOT)/usr/include/mach/mach_exc.defs', + '$(SDKROOT)/usr/include/mach/notify.defs', ], }], ], @@ -243,6 +248,7 @@ 'mach/mach_extensions_test.cc', 'mach/mach_message_server_test.cc', 'mach/mach_message_test.cc', + 'mach/notify_server_test.cc', 'mach/scoped_task_suspend_test.cc', 'mach/symbolic_constants_mach_test.cc', 'mach/task_memory_test.cc',