Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(150)

Side by Side Diff: content/browser/zygote_host/zygote_communication_linux.cc

Issue 1532423003: Have each SandboxedProcessLauncherDelegate maintain a zygote. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/zygote_host/zygote_communication_linux.h"
6
7 #include <sys/socket.h>
8
9 #include "base/base_switches.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/metrics/sparse_histogram.h"
13 #include "base/path_service.h"
14 #include "base/pickle.h"
15 #include "base/posix/eintr_wrapper.h"
16 #include "base/posix/unix_domain_socket_linux.h"
17 #include "content/browser/renderer_host/render_sandbox_host_linux.h"
18 #include "content/browser/zygote_host/zygote_host_impl_linux.h"
19 #include "content/common/child_process_sandbox_support_impl_linux.h"
20 #include "content/common/zygote_commands_linux.h"
21 #include "content/public/browser/content_browser_client.h"
22 #include "content/public/common/content_switches.h"
23 #include "content/public/common/result_codes.h"
24 #include "sandbox/linux/services/credentials.h"
25 #include "sandbox/linux/services/namespace_sandbox.h"
26 #include "sandbox/linux/suid/client/setuid_sandbox_host.h"
27 #include "ui/gfx/switches.h"
28
29 namespace content {
30
31 namespace {
32
33 // Receive a fixed message on fd and return the sender's PID.
34 // Returns true if the message received matches the expected message.
35 bool ReceiveFixedMessage(int fd,
36 const char* expect_msg,
37 size_t expect_len,
38 base::ProcessId* sender_pid) {
39 char buf[expect_len + 1];
40 std::vector<base::ScopedFD> fds_vec;
41
42 const ssize_t len = base::UnixDomainSocket::RecvMsgWithPid(
43 fd, buf, sizeof(buf), &fds_vec, sender_pid);
44 if (static_cast<size_t>(len) != expect_len)
45 return false;
46 if (memcmp(buf, expect_msg, expect_len) != 0)
Avi (use Gerrit) 2016/01/06 19:07:55 #include <string.h>
Greg K 2016/01/06 22:24:21 Done.
47 return false;
48 if (!fds_vec.empty())
49 return false;
50 return true;
51 }
52
53 } // namespace
54
55 ZygoteCommunication::ZygoteCommunication()
56 : control_fd_(-1),
57 control_lock_(),
58 pid_(),
59 list_of_running_zygote_children_(),
60 child_tracking_lock_(),
61 sandbox_status_(0),
62 have_read_sandbox_status_word_(false),
63 use_suid_sandbox_for_adj_oom_score_(false),
64 init_(false) {}
65
66 ZygoteCommunication::~ZygoteCommunication() {}
67
68 bool ZygoteCommunication::SendMessage(const base::Pickle& data,
69 const std::vector<int>* fds) {
70 DCHECK_NE(-1, control_fd_);
71 CHECK(data.size() <= kZygoteMaxMessageLength)
72 << "Trying to send too-large message to zygote (sending " << data.size()
73 << " bytes, max is " << kZygoteMaxMessageLength << ")";
74 CHECK(!fds || fds->size() <= base::UnixDomainSocket::kMaxFileDescriptors)
75 << "Trying to send message with too many file descriptors to zygote "
76 << "(sending " << fds->size() << ", max is "
77 << base::UnixDomainSocket::kMaxFileDescriptors << ")";
78
79 return base::UnixDomainSocket::SendMsg(control_fd_, data.data(), data.size(),
80 fds ? *fds : std::vector<int>());
81 }
82
83 ssize_t ZygoteCommunication::ReadSandboxStatus() {
84 DCHECK_NE(-1, control_fd_);
85 // At startup we send a kZygoteCommandGetSandboxStatus request to the zygote,
86 // but don't wait for the reply. Thus, the first time that we read from the
87 // zygote, we get the reply to that request.
88 ssize_t bytes_read = HANDLE_EINTR(
89 read(control_fd_, &sandbox_status_, sizeof(sandbox_status_)));
90 if (bytes_read != sizeof(sandbox_status_)) {
91 return -1;
92 }
93 return bytes_read;
94 }
95
96 ssize_t ZygoteCommunication::ReadReply(void* buf, size_t buf_len) {
97 DCHECK_NE(-1, control_fd_);
98 if (!have_read_sandbox_status_word_) {
99 if (ReadSandboxStatus() == -1) {
100 return -1;
101 }
102 have_read_sandbox_status_word_ = true;
103 UMA_HISTOGRAM_SPARSE_SLOWLY("Linux.SandboxStatus", sandbox_status_);
104 }
105
106 return HANDLE_EINTR(read(control_fd_, buf, buf_len));
107 }
108
109 pid_t ZygoteCommunication::ForkRequest(const std::vector<std::string>& argv,
110 scoped_ptr<FileDescriptorInfo> mapping,
111 const std::string& process_type) {
112 DCHECK(init_);
113
114 base::Pickle pickle;
115 int raw_socks[2];
116 PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, raw_socks));
117 base::ScopedFD my_sock(raw_socks[0]);
118 base::ScopedFD peer_sock(raw_socks[1]);
119 CHECK(base::UnixDomainSocket::EnableReceiveProcessId(my_sock.get()));
120
121 pickle.WriteInt(kZygoteCommandFork);
122 pickle.WriteString(process_type);
123 pickle.WriteInt(argv.size());
124 for (std::vector<std::string>::const_iterator i = argv.begin();
125 i != argv.end(); ++i)
126 pickle.WriteString(*i);
127
128 // Fork requests contain one file descriptor for the PID oracle, and one
129 // more for each file descriptor mapping for the child process.
130 const size_t num_fds_to_send = 1 + mapping->GetMappingSize();
131 pickle.WriteInt(num_fds_to_send);
132
133 std::vector<int> fds;
134
135 // First FD to send is peer_sock.
136 // TODO(morrita): Ideally, this should be part of the mapping so that
137 // FileDescriptorInfo can manages its lifetime.
138 fds.push_back(peer_sock.get());
139
140 // The rest come from mapping.
141 for (size_t i = 0; i < mapping->GetMappingSize(); ++i) {
142 pickle.WriteUInt32(mapping->GetIDAt(i));
143 fds.push_back(mapping->GetFDAt(i));
144 }
145
146 // Sanity check that we've populated |fds| correctly.
147 DCHECK_EQ(num_fds_to_send, fds.size());
148
149 pid_t pid;
150 {
151 base::AutoLock lock(control_lock_);
152 if (!SendMessage(pickle, &fds))
153 return base::kNullProcessHandle;
154 mapping.reset();
155 peer_sock.reset();
156
157 {
158 char buf[sizeof(kZygoteChildPingMessage) + 1];
159 std::vector<base::ScopedFD> recv_fds;
160 base::ProcessId real_pid;
161
162 ssize_t n = base::UnixDomainSocket::RecvMsgWithPid(
163 my_sock.get(), buf, sizeof(buf), &recv_fds, &real_pid);
164 if (n != sizeof(kZygoteChildPingMessage) ||
165 0 != memcmp(buf, kZygoteChildPingMessage,
166 sizeof(kZygoteChildPingMessage))) {
167 // Zygote children should still be trustworthy when they're supposed to
168 // ping us, so something's broken if we don't receive a valid ping.
169 LOG(ERROR) << "Did not receive ping from zygote child";
170 NOTREACHED();
171 real_pid = -1;
172 }
173 my_sock.reset();
174
175 // Always send PID back to zygote.
176 base::Pickle pid_pickle;
177 pid_pickle.WriteInt(kZygoteCommandForkRealPID);
178 pid_pickle.WriteInt(real_pid);
179 if (!SendMessage(pid_pickle, NULL))
180 return base::kNullProcessHandle;
181 }
182
183 // Read the reply, which pickles the PID and an optional UMA enumeration.
184 static const unsigned kMaxReplyLength = 2048;
185 char buf[kMaxReplyLength];
186 const ssize_t len = ReadReply(buf, sizeof(buf));
187
188 base::Pickle reply_pickle(buf, len);
189 base::PickleIterator iter(reply_pickle);
190 if (len <= 0 || !iter.ReadInt(&pid))
191 return base::kNullProcessHandle;
192
193 // If there is a nonempty UMA name string, then there is a UMA
194 // enumeration to record.
195 std::string uma_name;
196 int uma_sample;
197 int uma_boundary_value;
198 if (iter.ReadString(&uma_name) && !uma_name.empty() &&
199 iter.ReadInt(&uma_sample) && iter.ReadInt(&uma_boundary_value)) {
200 // We cannot use the UMA_HISTOGRAM_ENUMERATION macro here,
201 // because that's only for when the name is the same every time.
202 // Here we're using whatever name we got from the other side.
203 // But since it's likely that the same one will be used repeatedly
204 // (even though it's not guaranteed), we cache it here.
205 static base::HistogramBase* uma_histogram;
206 if (!uma_histogram || uma_histogram->histogram_name() != uma_name) {
207 uma_histogram = base::LinearHistogram::FactoryGet(
208 uma_name, 1, uma_boundary_value, uma_boundary_value + 1,
209 base::HistogramBase::kUmaTargetedHistogramFlag);
210 }
211 uma_histogram->Add(uma_sample);
212 }
213
214 if (pid <= 0)
215 return base::kNullProcessHandle;
216 }
217
218 #if !defined(OS_OPENBSD)
219 // This is just a starting score for a renderer or extension (the
220 // only types of processes that will be started this way). It will
221 // get adjusted as time goes on. (This is the same value as
222 // chrome::kLowestRendererOomScore in chrome/chrome_constants.h, but
223 // that's not something we can include here.)
224 const int kLowestRendererOomScore = 300;
225 ZygoteHostImpl::GetInstance()->AdjustRendererOOMScore(
226 pid, kLowestRendererOomScore);
227 #endif
228
229 ZygoteChildBorn(pid);
230 return pid;
231 }
232
233 void ZygoteCommunication::EnsureProcessTerminated(pid_t process) {
234 DCHECK(init_);
235 base::Pickle pickle;
236
237 pickle.WriteInt(kZygoteCommandReap);
238 pickle.WriteInt(process);
239 if (!SendMessage(pickle, NULL))
240 LOG(ERROR) << "Failed to send Reap message to zygote";
241 ZygoteChildDied(process);
242 }
243
244 void ZygoteCommunication::ZygoteChildBorn(pid_t process) {
245 base::AutoLock lock(child_tracking_lock_);
246 bool new_element_inserted =
247 list_of_running_zygote_children_.insert(process).second;
248 DCHECK(new_element_inserted);
249 }
250
251 void ZygoteCommunication::ZygoteChildDied(pid_t process) {
252 base::AutoLock lock(child_tracking_lock_);
253 size_t num_erased = list_of_running_zygote_children_.erase(process);
254 DCHECK_EQ(1U, num_erased);
255 }
256
257 void ZygoteCommunication::Init() {
258 CHECK(!init_);
259
260 base::FilePath chrome_path;
261 CHECK(PathService::Get(base::FILE_EXE, &chrome_path));
262 base::CommandLine cmd_line(chrome_path);
263
264 cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess);
265
266 int fds[2];
267 CHECK(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) == 0);
268 CHECK(base::UnixDomainSocket::EnableReceiveProcessId(fds[0]));
269 base::FileHandleMappingVector fds_to_map;
270 fds_to_map.push_back(std::make_pair(fds[1], kZygoteSocketPairFd));
271
272 base::LaunchOptions options;
273 const base::CommandLine& browser_command_line =
274 *base::CommandLine::ForCurrentProcess();
275 if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) {
276 cmd_line.PrependWrapper(
277 browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix));
278 }
279 // Append any switches from the browser process that need to be forwarded on
280 // to the zygote/renderers.
281 // Should this list be obtained from browser_render_process_host.cc?
282 static const char* kForwardSwitches[] = {
283 switches::kAllowSandboxDebugging, switches::kDisableSeccompFilterSandbox,
284 switches::kEnableHeapProfiling,
285 switches::kEnableLogging, // Support, e.g., --enable-logging=stderr.
286 // Zygote process needs to know what resources to have loaded when it
287 // becomes a renderer process.
288 switches::kForceDeviceScaleFactor, switches::kLoggingLevel,
289 switches::kNoSandbox, switches::kPpapiInProcess,
290 switches::kRegisterPepperPlugins, switches::kV, switches::kVModule,
291 };
292 cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches,
293 arraysize(kForwardSwitches));
294
295 GetContentClient()->browser()->AppendExtraCommandLineSwitches(&cmd_line, -1);
296
297 const bool using_namespace_sandbox = ShouldUseNamespaceSandbox();
298 // A non empty sandbox_cmd means we want a SUID sandbox.
299 const bool using_suid_sandbox =
300 !ZygoteHostImpl::GetInstance()->SandboxCommand().empty() &&
301 !using_namespace_sandbox;
302 // Use the SUID sandbox for adjusting OOM scores when we are using the setuid
303 // or namespace sandbox. This is needed beacuse the processes are
304 // non-dumpable, so /proc/pid/oom_score_adj can only be written by root.
305 use_suid_sandbox_for_adj_oom_score_ =
306 !ZygoteHostImpl::GetInstance()->SandboxCommand().empty() &&
307 using_suid_sandbox;
308
309 // Start up the sandbox host process and get the file descriptor for the
310 // renderers to talk to it.
311 const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
312 fds_to_map.push_back(std::make_pair(sfd, GetSandboxFD()));
313
314 base::ScopedFD dummy_fd;
315 if (using_suid_sandbox) {
316 scoped_ptr<sandbox::SetuidSandboxHost> sandbox_host(
317 sandbox::SetuidSandboxHost::Create());
318 sandbox_host->PrependWrapper(&cmd_line);
319 sandbox_host->SetupLaunchOptions(&options, &fds_to_map, &dummy_fd);
320 sandbox_host->SetupLaunchEnvironment();
321 }
322
323 options.fds_to_remap = &fds_to_map;
324 base::Process process =
325 using_namespace_sandbox
326 ? sandbox::NamespaceSandbox::LaunchProcess(cmd_line, options)
327 : base::LaunchProcess(cmd_line, options);
328 CHECK(process.IsValid()) << "Failed to launch zygote process";
329
330 dummy_fd.reset();
331
332 if (using_suid_sandbox || using_namespace_sandbox) {
333 // The SUID sandbox will execute the zygote in a new PID namespace, and
334 // the main zygote process will then fork from there. Watch now our
335 // elaborate dance to find and validate the zygote's PID.
336
337 // First we receive a message from the zygote boot process.
338 base::ProcessId boot_pid;
339 CHECK(ReceiveFixedMessage(fds[0], kZygoteBootMessage,
340 sizeof(kZygoteBootMessage), &boot_pid));
341
342 // Within the PID namespace, the zygote boot process thinks it's PID 1,
343 // but its real PID can never be 1. This gives us a reliable test that
344 // the kernel is translating the sender's PID to our namespace.
345 CHECK_GT(boot_pid, 1)
346 << "Received invalid process ID for zygote; kernel might be too old? "
347 "See crbug.com/357670 or try using --"
348 << switches::kDisableSetuidSandbox << " to workaround.";
349
350 // Now receive the message that the zygote's ready to go, along with the
351 // main zygote process's ID.
352 CHECK(ReceiveFixedMessage(fds[0], kZygoteHelloMessage,
353 sizeof(kZygoteHelloMessage), &pid_));
354 CHECK_GT(pid_, 1);
355
356 if (process.Pid() != pid_) {
357 // Reap the sandbox.
358 base::EnsureProcessGetsReaped(process.Pid());
359 }
360 } else {
361 // Not using the SUID sandbox.
362 // Note that ~base::Process() will reset the internal value, but there's no
363 // real "handle" on POSIX so that is safe.
364 pid_ = process.Pid();
365 }
366
367 close(fds[1]);
368 control_fd_ = fds[0];
369
370 ZygoteHostImpl::GetInstance()->AddZygotePid(pid_);
371
372 base::Pickle pickle;
373 pickle.WriteInt(kZygoteCommandGetSandboxStatus);
374 if (!SendMessage(pickle, NULL))
375 LOG(FATAL) << "Cannot communicate with zygote";
376
377 init_ = true;
378 }
379
380 base::TerminationStatus ZygoteCommunication::GetTerminationStatus(
381 base::ProcessHandle handle,
382 bool known_dead,
383 int* exit_code) {
384 DCHECK(init_);
385 base::Pickle pickle;
386 pickle.WriteInt(kZygoteCommandGetTerminationStatus);
387 pickle.WriteBool(known_dead);
388 pickle.WriteInt(handle);
389
390 static const unsigned kMaxMessageLength = 128;
391 char buf[kMaxMessageLength];
392 ssize_t len;
393 {
394 base::AutoLock lock(control_lock_);
395 if (!SendMessage(pickle, NULL))
396 LOG(ERROR) << "Failed to send GetTerminationStatus message to zygote";
397 len = ReadReply(buf, sizeof(buf));
398 }
399
400 // Set this now to handle the error cases.
401 if (exit_code)
402 *exit_code = RESULT_CODE_NORMAL_EXIT;
403 int status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
404
405 if (len == -1) {
406 LOG(WARNING) << "Error reading message from zygote: " << errno;
407 } else if (len == 0) {
408 LOG(WARNING) << "Socket closed prematurely.";
409 } else {
410 base::Pickle read_pickle(buf, len);
411 int tmp_status, tmp_exit_code;
412 base::PickleIterator iter(read_pickle);
413 if (!iter.ReadInt(&tmp_status) || !iter.ReadInt(&tmp_exit_code)) {
414 LOG(WARNING)
415 << "Error parsing GetTerminationStatus response from zygote.";
416 } else {
417 if (exit_code)
418 *exit_code = tmp_exit_code;
419 status = tmp_status;
420 }
421 }
422
423 if (status != base::TERMINATION_STATUS_STILL_RUNNING) {
424 ZygoteChildDied(handle);
425 }
426 return static_cast<base::TerminationStatus>(status);
427 }
428
429 bool ZygoteCommunication::ShouldUseNamespaceSandbox() {
430 const base::CommandLine& command_line =
431 *base::CommandLine::ForCurrentProcess();
432 if (command_line.HasSwitch(switches::kNoSandbox)) {
433 return false;
434 }
435
436 if (command_line.HasSwitch(switches::kDisableNamespaceSandbox)) {
437 return false;
438 }
439
440 if (!sandbox::Credentials::CanCreateProcessInNewUserNS()) {
441 return false;
442 }
443
444 return true;
445 }
446
447 int ZygoteCommunication::GetSandboxStatus() {
448 if (have_read_sandbox_status_word_) {
449 return sandbox_status_;
450 }
451 if (ReadSandboxStatus() == -1) {
452 return 0;
453 }
454 have_read_sandbox_status_word_ = true;
455 return sandbox_status_;
456 }
457
458 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698