summaryrefslogtreecommitdiffstats
path: root/ipc/attachment_broker_privileged_mac.cc
blob: 8965eb5ad19f19a3c9f2af7fb8fbe2cc8bd646c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// 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 "ipc/attachment_broker_privileged_mac.h"

#include "base/mac/scoped_mach_port.h"
#include "base/memory/shared_memory.h"
#include "base/process/port_provider_mac.h"
#include "base/process/process.h"
#include "ipc/attachment_broker_messages.h"
#include "ipc/brokerable_attachment.h"
#include "ipc/ipc_channel.h"
#include "ipc/mach_port_attachment_mac.h"

namespace {

// Struct for sending a complex Mach message.
struct MachSendComplexMessage {
  mach_msg_header_t header;
  mach_msg_body_t body;
  mach_msg_port_descriptor_t data;
};

// Sends a Mach port to |endpoint|. Assumes that |endpoint| is a send once
// right. Takes ownership of |endpoint|.
kern_return_t SendMachPort(mach_port_t endpoint,
                           mach_port_t port_to_send,
                           int disposition) {
  MachSendComplexMessage send_msg;
  send_msg.header.msgh_bits =
      MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | MACH_MSGH_BITS_COMPLEX;
  send_msg.header.msgh_size = sizeof(send_msg);
  send_msg.header.msgh_remote_port = endpoint;
  send_msg.header.msgh_local_port = MACH_PORT_NULL;
  send_msg.header.msgh_reserved = 0;
  send_msg.header.msgh_id = 0;
  send_msg.body.msgh_descriptor_count = 1;
  send_msg.data.name = port_to_send;
  send_msg.data.disposition = disposition;
  send_msg.data.type = MACH_MSG_PORT_DESCRIPTOR;

  return mach_msg(&send_msg.header,
                  MACH_SEND_MSG | MACH_SEND_TIMEOUT,
                  send_msg.header.msgh_size,
                  0,                // receive limit
                  MACH_PORT_NULL,   // receive name
                  0,                // timeout
                  MACH_PORT_NULL);  // notification port
}

}  // namespace

namespace IPC {

AttachmentBrokerPrivilegedMac::AttachmentBrokerPrivilegedMac(
    base::PortProvider* port_provider)
    : port_provider_(port_provider) {}

AttachmentBrokerPrivilegedMac::~AttachmentBrokerPrivilegedMac() {}

bool AttachmentBrokerPrivilegedMac::SendAttachmentToProcess(
    const scoped_refptr<IPC::BrokerableAttachment>& attachment,
    base::ProcessId destination_process) {
  switch (attachment->GetBrokerableType()) {
    case BrokerableAttachment::MACH_PORT: {
      internal::MachPortAttachmentMac* mach_port_attachment =
          static_cast<internal::MachPortAttachmentMac*>(attachment.get());
      MachPortWireFormat wire_format =
          mach_port_attachment->GetWireFormat(destination_process);

      if (destination_process == base::Process::Current().Pid()) {
        RouteWireFormatToSelf(wire_format);
        mach_port_attachment->reset_mach_port_ownership();
        return true;
      }

      mach_port_name_t intermediate_port = CreateIntermediateMachPort(
          wire_format.destination_process,
          base::mac::ScopedMachSendRight(wire_format.mach_port));
      mach_port_attachment->reset_mach_port_ownership();
      if (intermediate_port == MACH_PORT_NULL) {
        LogError(ERROR_MAKE_INTERMEDIATE);
        return false;
      }

      MachPortWireFormat intermediate_wire_format =
          CopyWireFormat(wire_format, intermediate_port);
      RouteWireFormatToAnother(intermediate_wire_format);
      return true;
    }
    default:
      NOTREACHED();
      return false;
  }
  return false;
}

bool AttachmentBrokerPrivilegedMac::OnMessageReceived(const Message& msg) {
  bool handled = true;
  switch (msg.type()) {
    IPC_MESSAGE_HANDLER_GENERIC(AttachmentBrokerMsg_DuplicateMachPort,
                                OnDuplicateMachPort(msg))
    IPC_MESSAGE_UNHANDLED(handled = false)
  }
  return handled;
}

void AttachmentBrokerPrivilegedMac::OnDuplicateMachPort(
    const IPC::Message& message) {
  AttachmentBrokerMsg_DuplicateMachPort::Param param;
  if (!AttachmentBrokerMsg_DuplicateMachPort::Read(&message, &param)) {
    LogError(ERROR_PARSE_DUPLICATE_MACH_PORT_MESSAGE);
    return;
  }
  IPC::internal::MachPortAttachmentMac::WireFormat wire_format =
      base::get<0>(param);

  if (wire_format.destination_process == base::kNullProcessId) {
    LogError(NO_DESTINATION);
    return;
  }

  // Acquire a send right to the Mach port.
  base::ProcessId sender_pid = message.get_sender_pid();
  DCHECK_NE(sender_pid, base::GetCurrentProcId());
  base::mac::ScopedMachSendRight send_right(
      AcquireSendRight(sender_pid, wire_format.mach_port));

  if (wire_format.destination_process == base::GetCurrentProcId()) {
    // Intentionally leak the reference, as the consumer of the Chrome IPC
    // message will take ownership.
    mach_port_t final_mach_port = send_right.release();
    MachPortWireFormat final_wire_format(
        CopyWireFormat(wire_format, final_mach_port));
    RouteWireFormatToSelf(final_wire_format);
    return;
  }

  mach_port_name_t intermediate_mach_port = CreateIntermediateMachPort(
      wire_format.destination_process,
      base::mac::ScopedMachSendRight(send_right.release()));
  RouteWireFormatToAnother(CopyWireFormat(wire_format, intermediate_mach_port));
}

void AttachmentBrokerPrivilegedMac::RouteWireFormatToSelf(
    const MachPortWireFormat& wire_format) {
  DCHECK_EQ(wire_format.destination_process, base::Process::Current().Pid());
  scoped_refptr<BrokerableAttachment> attachment(
      new internal::MachPortAttachmentMac(wire_format));
  HandleReceivedAttachment(attachment);
}

void AttachmentBrokerPrivilegedMac::RouteWireFormatToAnother(
    const MachPortWireFormat& wire_format) {
  DCHECK_NE(wire_format.destination_process, base::Process::Current().Pid());

  // Another process is the destination.
  base::ProcessId dest = wire_format.destination_process;
  Sender* sender = GetSenderWithProcessId(dest);
  if (!sender) {
    // Assuming that this message was not sent from a malicious process, the
    // channel endpoint that would have received this message will block
    // forever.
    LOG(ERROR) << "Failed to deliver brokerable attachment to process with id: "
               << dest;
    LogError(DESTINATION_NOT_FOUND);
    return;
  }

  LogError(DESTINATION_FOUND);
  sender->Send(new AttachmentBrokerMsg_MachPortHasBeenDuplicated(wire_format));
}

mach_port_name_t AttachmentBrokerPrivilegedMac::CreateIntermediateMachPort(
    base::ProcessId pid,
    base::mac::ScopedMachSendRight port_to_insert) {
  DCHECK_NE(pid, base::GetCurrentProcId());
  mach_port_t task_port = port_provider_->TaskForPid(pid);
  if (task_port == MACH_PORT_NULL) {
    LogError(ERROR_TASK_FOR_PID);
    return MACH_PORT_NULL;
  }
  return CreateIntermediateMachPort(
      task_port, base::mac::ScopedMachSendRight(port_to_insert.release()));
}

mach_port_name_t AttachmentBrokerPrivilegedMac::CreateIntermediateMachPort(
    mach_port_t task_port,
    base::mac::ScopedMachSendRight port_to_insert) {
  DCHECK_NE(mach_task_self(), task_port);
  DCHECK_NE(static_cast<mach_port_name_t>(MACH_PORT_NULL), task_port);

  // Make a port with receive rights in the destination task.
  mach_port_name_t endpoint;
  kern_return_t kr =
      mach_port_allocate(task_port, MACH_PORT_RIGHT_RECEIVE, &endpoint);
  if (kr != KERN_SUCCESS) {
    LogError(ERROR_MAKE_RECEIVE_PORT);
    return MACH_PORT_NULL;
  }

  // Change its message queue limit so that it accepts one message.
  mach_port_limits limits = {};
  limits.mpl_qlimit = 1;
  kr = mach_port_set_attributes(task_port, endpoint, MACH_PORT_LIMITS_INFO,
                                reinterpret_cast<mach_port_info_t>(&limits),
                                MACH_PORT_LIMITS_INFO_COUNT);
  if (kr != KERN_SUCCESS) {
    LogError(ERROR_SET_ATTRIBUTES);
    mach_port_deallocate(task_port, endpoint);
    return MACH_PORT_NULL;
  }

  // Get a send right.
  mach_port_t send_once_right;
  mach_msg_type_name_t send_right_type;
  kr =
      mach_port_extract_right(task_port, endpoint, MACH_MSG_TYPE_MAKE_SEND_ONCE,
                              &send_once_right, &send_right_type);
  if (kr != KERN_SUCCESS) {
    LogError(ERROR_EXTRACT_RIGHT);
    mach_port_deallocate(task_port, endpoint);
    return MACH_PORT_NULL;
  }
  DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND_ONCE),
            send_right_type);

  // This call takes ownership of |send_once_right|.
  kr = SendMachPort(
      send_once_right, port_to_insert.get(), MACH_MSG_TYPE_COPY_SEND);
  if (kr != KERN_SUCCESS) {
    LogError(ERROR_SEND_MACH_PORT);
    mach_port_deallocate(task_port, endpoint);
    return MACH_PORT_NULL;
  }

  // Endpoint is intentionally leaked into the destination task. An IPC must be
  // sent to the destination task so that it can clean up this port.
  return endpoint;
}

base::mac::ScopedMachSendRight AttachmentBrokerPrivilegedMac::AcquireSendRight(
    base::ProcessId pid,
    mach_port_name_t named_right) {
  if (pid == base::GetCurrentProcId()) {
    kern_return_t kr = mach_port_mod_refs(mach_task_self(), named_right,
                                          MACH_PORT_RIGHT_SEND, 1);
    if (kr != KERN_SUCCESS)
      return base::mac::ScopedMachSendRight(MACH_PORT_NULL);
    return base::mac::ScopedMachSendRight(named_right);
  }

  mach_port_t task_port = port_provider_->TaskForPid(pid);
  return ExtractNamedRight(task_port, named_right);
}

base::mac::ScopedMachSendRight AttachmentBrokerPrivilegedMac::ExtractNamedRight(
    mach_port_t task_port,
    mach_port_name_t named_right) {
  mach_port_t extracted_right = MACH_PORT_NULL;
  mach_msg_type_name_t extracted_right_type;
  kern_return_t kr =
      mach_port_extract_right(task_port, named_right, MACH_MSG_TYPE_COPY_SEND,
                              &extracted_right, &extracted_right_type);
  if (kr != KERN_SUCCESS)
    return base::mac::ScopedMachSendRight(MACH_PORT_NULL);

  DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND),
            extracted_right_type);

  // Decrement the reference count of the send right from the source process.
  kr = mach_port_mod_refs(task_port, named_right, MACH_PORT_RIGHT_SEND, -1);
  if (kr != KERN_SUCCESS) {
    LogError(ERROR_DECREASE_REF);
    // Failure does not actually affect attachment brokering, so there's no need
    // to return |MACH_PORT_NULL|.
  }

  return base::mac::ScopedMachSendRight(extracted_right);
}

AttachmentBrokerPrivilegedMac::MachPortWireFormat
AttachmentBrokerPrivilegedMac::CopyWireFormat(
    const MachPortWireFormat& wire_format,
    uint32_t mach_port) {
  return MachPortWireFormat(mach_port, wire_format.destination_process,
                            wire_format.attachment_id);
}

}  // namespace IPC