OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "sandbox/mac/bootstrap_sandbox.h" | 5 #include "sandbox/mac/bootstrap_sandbox.h" |
6 | 6 |
7 #include <servers/bootstrap.h> | 7 #include <servers/bootstrap.h> |
8 #include <unistd.h> | 8 #include <unistd.h> |
9 | 9 |
10 #include "base/logging.h" | 10 #include "base/logging.h" |
11 #include "base/mac/foundation_util.h" | 11 #include "base/mac/foundation_util.h" |
12 #include "base/mac/mach_logging.h" | 12 #include "base/mac/mach_logging.h" |
| 13 #include "base/rand_util.h" |
13 #include "base/strings/stringprintf.h" | 14 #include "base/strings/stringprintf.h" |
14 #include "sandbox/mac/launchd_interception_server.h" | 15 #include "sandbox/mac/launchd_interception_server.h" |
| 16 #include "sandbox/mac/pre_exec_delegate.h" |
15 | 17 |
16 namespace sandbox { | 18 namespace sandbox { |
17 | 19 |
18 const int kNotAPolicy = -1; | 20 namespace { |
| 21 |
| 22 struct SandboxCheckInRequest { |
| 23 mach_msg_header_t header; |
| 24 uint64_t token; |
| 25 }; |
| 26 |
| 27 struct SandboxCheckInReply { |
| 28 mach_msg_header_t header; |
| 29 mach_msg_body_t body; |
| 30 mach_msg_port_descriptor_t bootstrap_port; |
| 31 }; |
| 32 |
| 33 class ScopedCallMachMsgDestroy { |
| 34 public: |
| 35 explicit ScopedCallMachMsgDestroy(mach_msg_header_t* message) |
| 36 : message_(message) {} |
| 37 |
| 38 ~ScopedCallMachMsgDestroy() { |
| 39 if (message_) |
| 40 mach_msg_destroy(message_); |
| 41 } |
| 42 |
| 43 void Disarm() { |
| 44 message_ = nullptr; |
| 45 } |
| 46 |
| 47 private: |
| 48 mach_msg_header_t* message_; |
| 49 |
| 50 DISALLOW_COPY_AND_ASSIGN(ScopedCallMachMsgDestroy); |
| 51 }; |
| 52 |
| 53 } // namespace |
19 | 54 |
20 // static | 55 // static |
21 scoped_ptr<BootstrapSandbox> BootstrapSandbox::Create() { | 56 scoped_ptr<BootstrapSandbox> BootstrapSandbox::Create() { |
22 scoped_ptr<BootstrapSandbox> null; // Used for early returns. | 57 scoped_ptr<BootstrapSandbox> null; // Used for early returns. |
23 scoped_ptr<BootstrapSandbox> sandbox(new BootstrapSandbox()); | 58 scoped_ptr<BootstrapSandbox> sandbox(new BootstrapSandbox()); |
24 sandbox->server_.reset(new LaunchdInterceptionServer(sandbox.get())); | 59 sandbox->launchd_server_.reset(new LaunchdInterceptionServer(sandbox.get())); |
25 | 60 |
26 // Check in with launchd to get the receive right for the server that is | 61 // Check in with launchd to get the receive right for the server that is |
27 // published in the bootstrap namespace. | 62 // published in the bootstrap namespace. |
28 mach_port_t port = MACH_PORT_NULL; | 63 mach_port_t port = MACH_PORT_NULL; |
29 kern_return_t kr = bootstrap_check_in(bootstrap_port, | 64 kern_return_t kr = bootstrap_check_in(bootstrap_port, |
30 sandbox->server_bootstrap_name().c_str(), &port); | 65 sandbox->server_bootstrap_name().c_str(), &port); |
31 if (kr != KERN_SUCCESS) { | 66 if (kr != KERN_SUCCESS) { |
32 BOOTSTRAP_LOG(ERROR, kr) | 67 BOOTSTRAP_LOG(ERROR, kr) |
33 << "Failed to bootstrap_check_in the sandbox server."; | 68 << "Failed to bootstrap_check_in the sandbox server."; |
34 return null.Pass(); | 69 return null.Pass(); |
35 } | 70 } |
36 base::mac::ScopedMachReceiveRight scoped_port(port); | 71 sandbox->check_in_port_.reset(port); |
| 72 |
| 73 BootstrapSandbox* __block sandbox_ptr = sandbox.get(); |
| 74 sandbox->check_in_server_.reset(new base::DispatchSourceMach( |
| 75 "org.chromium.sandbox.BootstrapClientManager", |
| 76 sandbox->check_in_port_.get(), |
| 77 ^{ sandbox_ptr->HandleChildCheckIn(); })); |
| 78 sandbox->check_in_server_->Resume(); |
37 | 79 |
38 // Start the sandbox server. | 80 // Start the sandbox server. |
39 if (sandbox->server_->Initialize(scoped_port.get())) | 81 if (!sandbox->launchd_server_->Initialize(MACH_PORT_NULL)) |
40 ignore_result(scoped_port.release()); // Transferred to server_. | |
41 else | |
42 return null.Pass(); | 82 return null.Pass(); |
43 | 83 |
44 return sandbox.Pass(); | 84 return sandbox.Pass(); |
45 } | 85 } |
46 | 86 |
| 87 // Warning: This function must be safe to call in |
| 88 // PreExecDelegate::RunAsyncSafe(). |
| 89 // static |
| 90 bool BootstrapSandbox::ClientCheckIn(mach_port_t sandbox_server_port, |
| 91 uint64_t sandbox_token, |
| 92 mach_port_t* new_bootstrap_port) { |
| 93 // Create a reply port for the check in message. |
| 94 mach_port_t reply_port; |
| 95 kern_return_t kr = mach_port_allocate(mach_task_self(), |
| 96 MACH_PORT_RIGHT_RECEIVE, |
| 97 &reply_port); |
| 98 if (kr != KERN_SUCCESS) { |
| 99 RAW_LOG(ERROR, "ClientCheckIn: mach_port_allocate failed"); |
| 100 return false; |
| 101 } |
| 102 base::mac::ScopedMachReceiveRight scoped_reply_port(reply_port); |
| 103 |
| 104 // Check in with the sandbox server, presenting the |sandbox_token| in |
| 105 // exchange for a new task bootstrap port. |
| 106 union { |
| 107 SandboxCheckInRequest request; |
| 108 struct { |
| 109 SandboxCheckInReply reply; |
| 110 mach_msg_trailer_t trailer; |
| 111 }; |
| 112 } msg = {}; |
| 113 msg.request.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, |
| 114 MACH_MSG_TYPE_MAKE_SEND_ONCE); |
| 115 msg.request.header.msgh_remote_port = sandbox_server_port; |
| 116 msg.request.header.msgh_local_port = reply_port; |
| 117 msg.request.header.msgh_size = sizeof(msg); |
| 118 msg.request.token = sandbox_token; |
| 119 |
| 120 kr = mach_msg(&msg.request.header, MACH_SEND_MSG | MACH_RCV_MSG, |
| 121 sizeof(msg.request), sizeof(msg), reply_port, |
| 122 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| 123 if (kr == KERN_SUCCESS) { |
| 124 *new_bootstrap_port = msg.reply.bootstrap_port.name; |
| 125 return true; |
| 126 } else { |
| 127 RAW_LOG(ERROR, "ClientCheckIn: mach_msg failed"); |
| 128 return false; |
| 129 } |
| 130 } |
| 131 |
47 BootstrapSandbox::~BootstrapSandbox() { | 132 BootstrapSandbox::~BootstrapSandbox() { |
48 } | 133 } |
49 | 134 |
50 void BootstrapSandbox::RegisterSandboxPolicy( | 135 void BootstrapSandbox::RegisterSandboxPolicy( |
51 int sandbox_policy_id, | 136 int sandbox_policy_id, |
52 const BootstrapSandboxPolicy& policy) { | 137 const BootstrapSandboxPolicy& policy) { |
53 CHECK(IsPolicyValid(policy)); | 138 CHECK(IsPolicyValid(policy)); |
54 CHECK_GT(sandbox_policy_id, kNotAPolicy); | |
55 base::AutoLock lock(lock_); | 139 base::AutoLock lock(lock_); |
56 DCHECK(policies_.find(sandbox_policy_id) == policies_.end()); | 140 DCHECK(policies_.find(sandbox_policy_id) == policies_.end()); |
57 policies_.insert(std::make_pair(sandbox_policy_id, policy)); | 141 policies_.insert(std::make_pair(sandbox_policy_id, policy)); |
58 } | 142 } |
59 | 143 |
60 void BootstrapSandbox::PrepareToForkWithPolicy(int sandbox_policy_id) { | 144 scoped_ptr<PreExecDelegate> BootstrapSandbox::NewClient(int sandbox_policy_id) { |
61 base::AutoLock lock(lock_); | 145 base::AutoLock lock(lock_); |
62 | 146 |
63 // Verify that this is a real policy. | 147 DCHECK(policies_.find(sandbox_policy_id) != policies_.end()); |
64 CHECK(policies_.find(sandbox_policy_id) != policies_.end()); | |
65 CHECK_EQ(kNotAPolicy, effective_policy_id_) | |
66 << "Cannot nest calls to PrepareToForkWithPolicy()"; | |
67 | 148 |
68 // Store the policy for the process we're about to create. | 149 uint64_t token; |
69 effective_policy_id_ = sandbox_policy_id; | 150 while (true) { |
| 151 token = base::RandUint64(); |
| 152 if (awaiting_processes_.find(token) == awaiting_processes_.end()) |
| 153 break; |
| 154 } |
| 155 |
| 156 awaiting_processes_[token] = sandbox_policy_id; |
| 157 return make_scoped_ptr(new PreExecDelegate(server_bootstrap_name_, token)); |
70 } | 158 } |
71 | 159 |
72 // TODO(rsesek): The |lock_| needs to be taken twice because | 160 void BootstrapSandbox::RevokeToken(uint64_t token) { |
73 // base::LaunchProcess handles both fork+exec, and holding the lock for the | |
74 // duration would block servicing of other bootstrap messages. If a better | |
75 // LaunchProcess existed (do arbitrary work without layering violations), this | |
76 // could be avoided. | |
77 | |
78 void BootstrapSandbox::FinishedFork(base::ProcessHandle handle) { | |
79 base::AutoLock lock(lock_); | 161 base::AutoLock lock(lock_); |
80 | 162 const auto& it = awaiting_processes_.find(token); |
81 CHECK_NE(kNotAPolicy, effective_policy_id_) | 163 if (it != awaiting_processes_.end()) |
82 << "Must PrepareToForkWithPolicy() before FinishedFork()"; | 164 awaiting_processes_.erase(it); |
83 | |
84 // Apply the policy to the new process. | |
85 if (handle != base::kNullProcessHandle) { | |
86 const auto& existing_process = sandboxed_processes_.find(handle); | |
87 CHECK(existing_process == sandboxed_processes_.end()); | |
88 sandboxed_processes_.insert(std::make_pair(handle, effective_policy_id_)); | |
89 VLOG(3) << "Bootstrap sandbox enforced for pid " << handle; | |
90 } | |
91 | |
92 effective_policy_id_ = kNotAPolicy; | |
93 } | 165 } |
94 | 166 |
95 void BootstrapSandbox::ChildDied(base::ProcessHandle handle) { | 167 void BootstrapSandbox::InvalidateClient(base::ProcessHandle handle) { |
96 base::AutoLock lock(lock_); | 168 base::AutoLock lock(lock_); |
97 const auto& it = sandboxed_processes_.find(handle); | 169 const auto& it = sandboxed_processes_.find(handle); |
98 if (it != sandboxed_processes_.end()) | 170 if (it != sandboxed_processes_.end()) |
99 sandboxed_processes_.erase(it); | 171 sandboxed_processes_.erase(it); |
100 } | 172 } |
101 | 173 |
102 const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( | 174 const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( |
103 pid_t pid) const { | 175 pid_t pid) const { |
104 base::AutoLock lock(lock_); | 176 base::AutoLock lock(lock_); |
105 const auto& process = sandboxed_processes_.find(pid); | 177 const auto& process = sandboxed_processes_.find(pid); |
106 | |
107 // The new child could send bootstrap requests before the parent calls | |
108 // FinishedFork(). | |
109 int policy_id = effective_policy_id_; | |
110 if (process != sandboxed_processes_.end()) { | 178 if (process != sandboxed_processes_.end()) { |
111 policy_id = process->second; | 179 return &policies_.find(process->second)->second; |
112 } | 180 } |
113 | 181 |
114 if (policy_id == kNotAPolicy) | 182 return nullptr; |
115 return NULL; | |
116 | |
117 return &policies_.find(policy_id)->second; | |
118 } | 183 } |
119 | 184 |
120 BootstrapSandbox::BootstrapSandbox() | 185 BootstrapSandbox::BootstrapSandbox() |
121 : server_bootstrap_name_( | 186 : server_bootstrap_name_( |
122 base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), | 187 base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), |
123 getpid())), | 188 getpid())), |
124 real_bootstrap_port_(MACH_PORT_NULL), | 189 real_bootstrap_port_(MACH_PORT_NULL) { |
125 effective_policy_id_(kNotAPolicy) { | |
126 mach_port_t port = MACH_PORT_NULL; | 190 mach_port_t port = MACH_PORT_NULL; |
127 kern_return_t kr = task_get_special_port( | 191 kern_return_t kr = task_get_special_port( |
128 mach_task_self(), TASK_BOOTSTRAP_PORT, &port); | 192 mach_task_self(), TASK_BOOTSTRAP_PORT, &port); |
129 MACH_CHECK(kr == KERN_SUCCESS, kr); | 193 MACH_CHECK(kr == KERN_SUCCESS, kr); |
130 real_bootstrap_port_.reset(port); | 194 real_bootstrap_port_.reset(port); |
131 } | 195 } |
132 | 196 |
| 197 void BootstrapSandbox::HandleChildCheckIn() { |
| 198 struct { |
| 199 SandboxCheckInRequest request; |
| 200 mach_msg_audit_trailer_t trailer; |
| 201 } msg = {}; |
| 202 msg.request.header.msgh_local_port = check_in_port_.get(); |
| 203 msg.request.header.msgh_size = sizeof(msg.request); |
| 204 const mach_msg_option_t kOptions = MACH_RCV_MSG | |
| 205 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | |
| 206 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); |
| 207 kern_return_t kr = mach_msg(&msg.request.header, kOptions, 0, |
| 208 sizeof(msg), check_in_port_.get(), |
| 209 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); |
| 210 if (kr != KERN_SUCCESS) { |
| 211 MACH_LOG(ERROR, kr) << "HandleChildCheckIn mach_msg MACH_RCV_MSG"; |
| 212 return; |
| 213 } |
| 214 |
| 215 // Call mach_msg_destroy to clean up the reply send-once right. |
| 216 ScopedCallMachMsgDestroy message_destroyer(&msg.request.header); |
| 217 |
| 218 pid_t client_pid; |
| 219 audit_token_to_au32(msg.trailer.msgh_audit, nullptr, nullptr, nullptr, |
| 220 nullptr, nullptr, &client_pid, nullptr, nullptr); |
| 221 |
| 222 { |
| 223 base::AutoLock lock(lock_); |
| 224 |
| 225 auto awaiting_it = awaiting_processes_.find(msg.request.token); |
| 226 if (awaiting_it == awaiting_processes_.end()) { |
| 227 LOG(ERROR) << "Received sandbox check-in message from unknown client."; |
| 228 return; |
| 229 } |
| 230 |
| 231 CHECK(sandboxed_processes_.find(client_pid) == sandboxed_processes_.end()); |
| 232 sandboxed_processes_[client_pid] = awaiting_it->second; |
| 233 awaiting_processes_.erase(awaiting_it); |
| 234 } |
| 235 |
| 236 SandboxCheckInReply reply = {}; |
| 237 reply.header.msgh_bits = MACH_MSGH_BITS_REMOTE(msg.request.header.msgh_bits) | |
| 238 MACH_MSGH_BITS_COMPLEX; |
| 239 reply.header.msgh_remote_port = msg.request.header.msgh_remote_port; |
| 240 reply.header.msgh_size = sizeof(reply); |
| 241 reply.body.msgh_descriptor_count = 1; |
| 242 reply.bootstrap_port.name = launchd_server_->server_port(); |
| 243 reply.bootstrap_port.disposition = MACH_MSG_TYPE_MAKE_SEND; |
| 244 reply.bootstrap_port.type = MACH_MSG_PORT_DESCRIPTOR; |
| 245 |
| 246 kr = mach_msg(&reply.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, |
| 247 sizeof(reply), 0, MACH_PORT_NULL, 100 /*ms*/, MACH_PORT_NULL); |
| 248 if (kr == KERN_SUCCESS) { |
| 249 message_destroyer.Disarm(); // The send-once was consumed at mach_msg(). |
| 250 } else { |
| 251 { |
| 252 base::AutoLock lock(lock_); |
| 253 sandboxed_processes_.erase(client_pid); |
| 254 } |
| 255 MACH_LOG(ERROR, kr) << "HandleChildCheckIn mach_msg MACH_SEND_MSG"; |
| 256 } |
| 257 } |
| 258 |
133 } // namespace sandbox | 259 } // namespace sandbox |
OLD | NEW |