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 |