diff options
Diffstat (limited to 'ipc/attachment_broker_privileged_win_unittest.cc')
-rw-r--r-- | ipc/attachment_broker_privileged_win_unittest.cc | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/ipc/attachment_broker_privileged_win_unittest.cc b/ipc/attachment_broker_privileged_win_unittest.cc new file mode 100644 index 0000000..ea0c06d --- /dev/null +++ b/ipc/attachment_broker_privileged_win_unittest.cc @@ -0,0 +1,397 @@ +// Copyright 2015 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 "build/build_config.h" + +#include <windows.h> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "ipc/attachment_broker_privileged_win.h" +#include "ipc/attachment_broker_win.h" +#include "ipc/handle_attachment_win.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_test_base.h" + +namespace { + +const char kDataBuffer[] = "This is some test data to write to the file."; + +// Returns the contents of the file represented by |h| as a std::string. +std::string ReadFromFile(HANDLE h) { + SetFilePointer(h, 0, nullptr, FILE_BEGIN); + char buffer[100]; + DWORD bytes_read; + BOOL success = ::ReadFile(h, buffer, static_cast<DWORD>(strlen(kDataBuffer)), + &bytes_read, nullptr); + return success ? std::string(buffer, bytes_read) : std::string(); +} + +HANDLE GetHandleFromBrokeredAttachment( + const scoped_refptr<IPC::BrokerableAttachment>& attachment) { + if (attachment->GetType() != + IPC::BrokerableAttachment::TYPE_BROKERABLE_ATTACHMENT) + return nullptr; + if (attachment->GetBrokerableType() != IPC::BrokerableAttachment::WIN_HANDLE) + return nullptr; + IPC::internal::HandleAttachmentWin* received_handle_attachment = + static_cast<IPC::internal::HandleAttachmentWin*>(attachment.get()); + return received_handle_attachment->get_handle(); +} + +// Returns true if |attachment| is a file HANDLE whose contents is +// |kDataBuffer|. +bool CheckContentsOfBrokeredAttachment( + const scoped_refptr<IPC::BrokerableAttachment>& attachment) { + HANDLE h = GetHandleFromBrokeredAttachment(attachment); + if (h == nullptr) + return false; + + std::string contents = ReadFromFile(h); + return contents == std::string(kDataBuffer); +} + +enum TestResult { + RESULT_UNKNOWN, + RESULT_SUCCESS, + RESULT_FAILURE, +}; + +// Once the test is finished, send a control message to the parent process with +// the result. The message may require the runloop to be run before its +// dispatched. +void SendControlMessage(IPC::Sender* sender, bool success) { + IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + TestResult result = success ? RESULT_SUCCESS : RESULT_FAILURE; + message->WriteInt(result); + sender->Send(message); +} + +class MockObserver : public IPC::AttachmentBroker::Observer { + public: + void ReceivedBrokerableAttachmentWithId( + const IPC::BrokerableAttachment::AttachmentId& id) override { + id_ = id; + } + IPC::BrokerableAttachment::AttachmentId* get_id() { return &id_; } + + private: + IPC::BrokerableAttachment::AttachmentId id_; +}; + +// Forwards all messages to |listener_|. Quits the message loop after a +// message is received, or the channel has an error. +class ProxyListener : public IPC::Listener { + public: + ProxyListener() : reason_(MESSAGE_RECEIVED) {} + ~ProxyListener() override {} + + // The reason for exiting the message loop. + enum Reason { MESSAGE_RECEIVED, CHANNEL_ERROR }; + + bool OnMessageReceived(const IPC::Message& message) override { + bool result = listener_->OnMessageReceived(message); + reason_ = MESSAGE_RECEIVED; + base::MessageLoop::current()->Quit(); + return result; + } + + void OnChannelError() override { + reason_ = CHANNEL_ERROR; + base::MessageLoop::current()->Quit(); + } + + void set_listener(IPC::Listener* listener) { listener_ = listener; } + Reason get_reason() { return reason_; } + + private: + IPC::Listener* listener_; + Reason reason_; +}; + +// Waits for a result to be sent over the channel. Quits the message loop +// after a message is received, or the channel has an error. +class ResultListener : public IPC::Listener { + public: + ResultListener() : result_(RESULT_UNKNOWN) {} + ~ResultListener() override {} + + bool OnMessageReceived(const IPC::Message& message) override { + base::PickleIterator iter(message); + + int result; + EXPECT_TRUE(iter.ReadInt(&result)); + result_ = static_cast<TestResult>(result); + return true; + } + + TestResult get_result() { return result_; } + + private: + TestResult result_; +}; + +// The parent process acts as an unprivileged process. The forked process acts +// as the privileged process. +class IPCAttachmentBrokerPrivilegedWinTest : public IPCTestBase { + public: + IPCAttachmentBrokerPrivilegedWinTest() : message_index_(0) {} + ~IPCAttachmentBrokerPrivilegedWinTest() override {} + + void SetUp() override { + IPCTestBase::SetUp(); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_path_)); + } + + void TearDown() override { IPCTestBase::TearDown(); } + + // Takes ownership of |broker|. Has no effect if called after CommonSetUp(). + void set_broker(IPC::AttachmentBrokerWin* broker) { broker_.reset(broker); } + + void CommonSetUp() { + if (!broker_.get()) + set_broker(new IPC::AttachmentBrokerWin); + broker_->AddObserver(&observer_); + set_attachment_broker(broker_.get()); + CreateChannel(&proxy_listener_); + broker_->set_sender(channel()); + ASSERT_TRUE(ConnectChannel()); + ASSERT_TRUE(StartClient()); + } + + void CommonTearDown() { + // Close the channel so the client's OnChannelError() gets fired. + channel()->Close(); + + EXPECT_TRUE(WaitForClientShutdown()); + DestroyChannel(); + broker_.reset(); + } + + HANDLE CreateTempFile() { + EXPECT_NE(-1, WriteFile(temp_path_, kDataBuffer, + static_cast<int>(strlen(kDataBuffer)))); + + HANDLE h = + CreateFile(temp_path_.value().c_str(), GENERIC_READ | GENERIC_WRITE, 0, + nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + EXPECT_NE(h, INVALID_HANDLE_VALUE); + return h; + } + + void SendMessageWithAttachment(HANDLE h) { + IPC::Message* message = + new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL); + message->WriteInt(message_index_++); + scoped_refptr<IPC::internal::HandleAttachmentWin> attachment( + new IPC::internal::HandleAttachmentWin(h)); + ASSERT_TRUE(message->WriteAttachment(attachment)); + sender()->Send(message); + } + + ProxyListener* get_proxy_listener() { return &proxy_listener_; } + IPC::AttachmentBrokerWin* get_broker() { return broker_.get(); } + MockObserver* get_observer() { return &observer_; } + + private: + base::ScopedTempDir temp_dir_; + base::FilePath temp_path_; + int message_index_; + ProxyListener proxy_listener_; + scoped_ptr<IPC::AttachmentBrokerWin> broker_; + MockObserver observer_; +}; + +// A broker which always sets the current process as the destination process +// for attachments. +class MockBroker : public IPC::AttachmentBrokerWin { + public: + MockBroker() {} + ~MockBroker() override {} + bool SendAttachmentToProcess(const IPC::BrokerableAttachment* attachment, + base::ProcessId destination_process) override { + return IPC::AttachmentBrokerWin::SendAttachmentToProcess( + attachment, base::Process::Current().Pid()); + } +}; + +// An unprivileged process makes a file HANDLE, and writes a string to it. The +// file HANDLE is sent to the privileged process using the attachment broker. +// The privileged process dups the HANDLE into its own HANDLE table. This test +// checks that the file has the same contents in the privileged process. +TEST_F(IPCAttachmentBrokerPrivilegedWinTest, SendHandle) { + Init("SendHandle"); + + CommonSetUp(); + ResultListener result_listener; + get_proxy_listener()->set_listener(&result_listener); + + HANDLE h = CreateTempFile(); + SendMessageWithAttachment(h); + base::MessageLoop::current()->Run(); + + // Check the result. + ASSERT_EQ(ProxyListener::MESSAGE_RECEIVED, + get_proxy_listener()->get_reason()); + ASSERT_EQ(result_listener.get_result(), RESULT_SUCCESS); + + CommonTearDown(); +} + +// Similar to SendHandle, except the file HANDLE attached to the message has +// neither read nor write permissions. +TEST_F(IPCAttachmentBrokerPrivilegedWinTest, SendHandleWithoutPermissions) { + Init("SendHandleWithoutPermissions"); + + CommonSetUp(); + ResultListener result_listener; + get_proxy_listener()->set_listener(&result_listener); + + HANDLE h = CreateTempFile(); + HANDLE h2; + BOOL result = ::DuplicateHandle(GetCurrentProcess(), h, GetCurrentProcess(), + &h2, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + ASSERT_TRUE(result); + SendMessageWithAttachment(h2); + base::MessageLoop::current()->Run(); + + // Check the result. + ASSERT_EQ(ProxyListener::MESSAGE_RECEIVED, + get_proxy_listener()->get_reason()); + ASSERT_EQ(result_listener.get_result(), RESULT_SUCCESS); + + CommonTearDown(); +} + +// Similar to SendHandle, except the attachment's destination process is this +// process. This is an unrealistic scenario, but simulates an unprivileged +// process sending an attachment to another unprivileged process. +TEST_F(IPCAttachmentBrokerPrivilegedWinTest, SendHandleToSelf) { + Init("SendHandleToSelf"); + + set_broker(new MockBroker); + CommonSetUp(); + get_proxy_listener()->set_listener(get_broker()); + + HANDLE h = CreateTempFile(); + SendMessageWithAttachment(h); + base::MessageLoop::current()->Run(); + + // Get the received attachment. + IPC::BrokerableAttachment::AttachmentId* id = get_observer()->get_id(); + scoped_refptr<IPC::BrokerableAttachment> received_attachment; + get_broker()->GetAttachmentWithId(*id, &received_attachment); + ASSERT_NE(received_attachment.get(), nullptr); + + // Check that it's a new entry in the HANDLE table. + HANDLE h2 = GetHandleFromBrokeredAttachment(received_attachment); + EXPECT_NE(h2, h); + EXPECT_NE(h2, nullptr); + + // But it still points to the same file. + std::string contents = ReadFromFile(h); + EXPECT_EQ(contents, std::string(kDataBuffer)); + + CommonTearDown(); +} + +using OnMessageReceivedCallback = + void (*)(MockObserver* observer, + IPC::AttachmentBrokerPrivilegedWin* broker, + IPC::Sender* sender); + +int CommonPrivilegedProcessMain(OnMessageReceivedCallback callback, + const char* channel_name) { + base::MessageLoopForIO main_message_loop; + ProxyListener listener; + + // Set up IPC channel. + IPC::AttachmentBrokerPrivilegedWin broker; + listener.set_listener(&broker); + scoped_ptr<IPC::Channel> channel(IPC::Channel::CreateClient( + IPCTestBase::GetChannelName(channel_name), &listener, &broker)); + broker.RegisterCommunicationChannel(channel.get()); + CHECK(channel->Connect()); + + MockObserver observer; + broker.AddObserver(&observer); + + while (true) { + base::MessageLoop::current()->Run(); + ProxyListener::Reason reason = listener.get_reason(); + if (reason == ProxyListener::CHANNEL_ERROR) + break; + + callback(&observer, &broker, channel.get()); + } + + return 0; +} + +void SendHandleCallback(MockObserver* observer, + IPC::AttachmentBrokerPrivilegedWin* broker, + IPC::Sender* sender) { + IPC::BrokerableAttachment::AttachmentId* id = observer->get_id(); + scoped_refptr<IPC::BrokerableAttachment> received_attachment; + broker->GetAttachmentWithId(*id, &received_attachment); + + // Check that it's the expected handle. + bool success = CheckContentsOfBrokeredAttachment(received_attachment); + + SendControlMessage(sender, success); +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendHandle) { + return CommonPrivilegedProcessMain(&SendHandleCallback, "SendHandle"); +} + +void SendHandleWithoutPermissionsCallback( + MockObserver* observer, + IPC::AttachmentBrokerPrivilegedWin* broker, + IPC::Sender* sender) { + IPC::BrokerableAttachment::AttachmentId* id = observer->get_id(); + scoped_refptr<IPC::BrokerableAttachment> received_attachment; + broker->GetAttachmentWithId(*id, &received_attachment); + + // Check that it's the expected handle. + HANDLE h = GetHandleFromBrokeredAttachment(received_attachment); + if (h != nullptr) { + SetFilePointer(h, 0, nullptr, FILE_BEGIN); + + char buffer[100]; + DWORD bytes_read; + BOOL success = + ::ReadFile(h, buffer, static_cast<DWORD>(strlen(kDataBuffer)), + &bytes_read, nullptr); + if (!success && GetLastError() == ERROR_ACCESS_DENIED) { + SendControlMessage(sender, true); + return; + } + } + + SendControlMessage(sender, false); +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendHandleWithoutPermissions) { + return CommonPrivilegedProcessMain(&SendHandleWithoutPermissionsCallback, + "SendHandleWithoutPermissions"); +} + +void SendHandleToSelfCallback(MockObserver* observer, + IPC::AttachmentBrokerPrivilegedWin* broker, + IPC::Sender* sender) { + // Do nothing special. The default behavior already runs the + // AttachmentBrokerPrivilegedWin. +} + +MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendHandleToSelf) { + return CommonPrivilegedProcessMain(&SendHandleToSelfCallback, + "SendHandleToSelf"); +} + +} // namespace |