Chromium Code Reviews| 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)) |
|
Mark Mentovai
2015/09/17 21:39:56
Is Initialize ever called with an argument other t
Robert Sesek
2015/09/17 22:04:44
Acknowledged.
| |
| 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 // static | |
|
Mark Mentovai
2015/09/17 21:39:56
I think that the static comment should come right
Robert Sesek
2015/09/17 22:04:44
Done.
| |
| 88 // Warning: This function must be safe to call in | |
| 89 // PreExecDelegate::RunAsyncSafe(). | |
| 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; | |
|
Mark Mentovai
2015/09/17 21:39:56
If you never get a check-in, this entry will persi
Mark Mentovai
2015/09/17 21:39:56
If you process the check-in late, after the child
Robert Sesek
2015/09/17 22:04:44
Yup. Do you have a suggestion?
Robert Sesek
2015/09/17 22:04:44
Right, but AFAICT there's nothing that can be done
Mark Mentovai
2015/09/17 22:10:53
Robert Sesek wrote:
Robert Sesek
2015/09/17 22:21:54
How about this? Anything else seems like the compl
| |
| 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::InvalidateClient(base::ProcessHandle handle) { |
| 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_); | |
| 80 | |
| 81 CHECK_NE(kNotAPolicy, effective_policy_id_) | |
| 82 << "Must PrepareToForkWithPolicy() before FinishedFork()"; | |
| 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 } | |
| 94 | |
| 95 void BootstrapSandbox::ChildDied(base::ProcessHandle handle) { | |
| 96 base::AutoLock lock(lock_); | 161 base::AutoLock lock(lock_); |
| 97 const auto& it = sandboxed_processes_.find(handle); | 162 const auto& it = sandboxed_processes_.find(handle); |
| 98 if (it != sandboxed_processes_.end()) | 163 if (it != sandboxed_processes_.end()) |
| 99 sandboxed_processes_.erase(it); | 164 sandboxed_processes_.erase(it); |
| 100 } | 165 } |
| 101 | 166 |
| 102 const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( | 167 const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( |
| 103 pid_t pid) const { | 168 pid_t pid) const { |
| 104 base::AutoLock lock(lock_); | 169 base::AutoLock lock(lock_); |
| 105 const auto& process = sandboxed_processes_.find(pid); | 170 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()) { | 171 if (process != sandboxed_processes_.end()) { |
| 111 policy_id = process->second; | 172 return &policies_.find(process->second)->second; |
| 112 } | 173 } |
| 113 | 174 |
| 114 if (policy_id == kNotAPolicy) | 175 return nullptr; |
| 115 return NULL; | |
| 116 | |
| 117 return &policies_.find(policy_id)->second; | |
| 118 } | 176 } |
| 119 | 177 |
| 120 BootstrapSandbox::BootstrapSandbox() | 178 BootstrapSandbox::BootstrapSandbox() |
| 121 : server_bootstrap_name_( | 179 : server_bootstrap_name_( |
| 122 base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), | 180 base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), |
| 123 getpid())), | 181 getpid())), |
| 124 real_bootstrap_port_(MACH_PORT_NULL), | 182 real_bootstrap_port_(MACH_PORT_NULL) { |
| 125 effective_policy_id_(kNotAPolicy) { | |
| 126 mach_port_t port = MACH_PORT_NULL; | 183 mach_port_t port = MACH_PORT_NULL; |
| 127 kern_return_t kr = task_get_special_port( | 184 kern_return_t kr = task_get_special_port( |
| 128 mach_task_self(), TASK_BOOTSTRAP_PORT, &port); | 185 mach_task_self(), TASK_BOOTSTRAP_PORT, &port); |
| 129 MACH_CHECK(kr == KERN_SUCCESS, kr); | 186 MACH_CHECK(kr == KERN_SUCCESS, kr); |
| 130 real_bootstrap_port_.reset(port); | 187 real_bootstrap_port_.reset(port); |
| 131 } | 188 } |
| 132 | 189 |
| 190 void BootstrapSandbox::HandleChildCheckIn() { | |
| 191 struct { | |
| 192 SandboxCheckInRequest request; | |
| 193 mach_msg_audit_trailer_t trailer; | |
| 194 } msg = {}; | |
| 195 msg.request.header.msgh_local_port = check_in_port_.get(); | |
| 196 msg.request.header.msgh_size = sizeof(msg.request); | |
| 197 const mach_msg_option_t kOptions = MACH_RCV_MSG | | |
| 198 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | | |
| 199 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); | |
| 200 kern_return_t kr = mach_msg(&msg.request.header, kOptions, 0, | |
| 201 sizeof(msg), check_in_port_.get(), | |
| 202 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); | |
| 203 if (kr != KERN_SUCCESS) { | |
| 204 MACH_LOG(ERROR, kr) << "HandleChildCheckIn mach_msg MACH_RCV_MSG"; | |
| 205 return; | |
| 206 } | |
| 207 | |
| 208 // Call mach_msg_destroy to clean up the reply send-once right. | |
| 209 ScopedCallMachMsgDestroy message_destroyer(&msg.request.header); | |
| 210 | |
| 211 pid_t client_pid; | |
| 212 audit_token_to_au32(msg.trailer.msgh_audit, nullptr, nullptr, nullptr, | |
| 213 nullptr, nullptr, &client_pid, nullptr, nullptr); | |
| 214 | |
| 215 { | |
| 216 base::AutoLock lock(lock_); | |
| 217 | |
| 218 auto awaiting_it = awaiting_processes_.find(msg.request.token); | |
| 219 if (awaiting_it == awaiting_processes_.end()) { | |
| 220 LOG(ERROR) << "Received sandbox check-in message from unknown client."; | |
| 221 return; | |
| 222 } | |
| 223 | |
| 224 sandboxed_processes_[client_pid] = awaiting_it->second; | |
|
Mark Mentovai
2015/09/17 21:39:56
Verify that client_pid isn’t already in this map.
Robert Sesek
2015/09/17 22:04:44
Done. That seems CHECK-worthy.
| |
| 225 awaiting_processes_.erase(awaiting_it); | |
| 226 } | |
| 227 | |
| 228 SandboxCheckInReply reply = {}; | |
| 229 reply.header.msgh_bits = MACH_MSGH_BITS_REMOTE(msg.request.header.msgh_bits) | | |
| 230 MACH_MSGH_BITS_COMPLEX; | |
| 231 reply.header.msgh_remote_port = msg.request.header.msgh_remote_port; | |
| 232 reply.header.msgh_size = sizeof(reply); | |
| 233 reply.body.msgh_descriptor_count = 1; | |
| 234 reply.bootstrap_port.name = launchd_server_->server_port(); | |
| 235 reply.bootstrap_port.disposition = MACH_MSG_TYPE_MAKE_SEND; | |
| 236 reply.bootstrap_port.type = MACH_MSG_PORT_DESCRIPTOR; | |
| 237 | |
| 238 kr = mach_msg(&reply.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, | |
| 239 sizeof(reply), 0, MACH_PORT_NULL, 100 /*ms*/, MACH_PORT_NULL); | |
| 240 if (kr == KERN_SUCCESS) { | |
| 241 message_destroyer.Disarm(); // The send-once was consumed at mach_msg(). | |
| 242 } else { | |
| 243 { | |
| 244 base::AutoLock lock(lock_); | |
| 245 sandboxed_processes_.erase(client_pid); | |
| 246 } | |
| 247 MACH_LOG(ERROR, kr) << "HandleChildCheckIn mach_msg MACH_SEND_MSG"; | |
| 248 } | |
| 249 } | |
| 250 | |
| 133 } // namespace sandbox | 251 } // namespace sandbox |
| OLD | NEW |