• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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_host_impl_linux.h"
6 
7 #include <sys/socket.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11 
12 #include "base/base_switches.h"
13 #include "base/command_line.h"
14 #include "base/environment.h"
15 #include "base/file_util.h"
16 #include "base/files/file_enumerator.h"
17 #include "base/linux_util.h"
18 #include "base/logging.h"
19 #include "base/memory/linked_ptr.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/metrics/histogram.h"
22 #include "base/path_service.h"
23 #include "base/posix/eintr_wrapper.h"
24 #include "base/posix/unix_domain_socket_linux.h"
25 #include "base/process/launch.h"
26 #include "base/process/memory.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/string_util.h"
29 #include "base/strings/utf_string_conversions.h"
30 #include "base/time/time.h"
31 #include "content/browser/renderer_host/render_sandbox_host_linux.h"
32 #include "content/common/child_process_sandbox_support_impl_linux.h"
33 #include "content/common/zygote_commands_linux.h"
34 #include "content/public/browser/content_browser_client.h"
35 #include "content/public/common/content_switches.h"
36 #include "content/public/common/result_codes.h"
37 #include "sandbox/linux/suid/client/setuid_sandbox_client.h"
38 #include "sandbox/linux/suid/common/sandbox.h"
39 #include "ui/base/ui_base_switches.h"
40 #include "ui/gfx/switches.h"
41 
42 #if defined(USE_TCMALLOC)
43 #include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h"
44 #endif
45 
46 namespace content {
47 
48 // static
GetInstance()49 ZygoteHost* ZygoteHost::GetInstance() {
50   return ZygoteHostImpl::GetInstance();
51 }
52 
ZygoteHostImpl()53 ZygoteHostImpl::ZygoteHostImpl()
54     : control_fd_(-1),
55       control_lock_(),
56       pid_(-1),
57       init_(false),
58       using_suid_sandbox_(false),
59       sandbox_binary_(),
60       have_read_sandbox_status_word_(false),
61       sandbox_status_(0),
62       child_tracking_lock_(),
63       list_of_running_zygote_children_(),
64       should_teardown_after_last_child_exits_(false) {}
65 
~ZygoteHostImpl()66 ZygoteHostImpl::~ZygoteHostImpl() { TearDown(); }
67 
68 // static
GetInstance()69 ZygoteHostImpl* ZygoteHostImpl::GetInstance() {
70   return Singleton<ZygoteHostImpl>::get();
71 }
72 
Init(const std::string & sandbox_cmd)73 void ZygoteHostImpl::Init(const std::string& sandbox_cmd) {
74   DCHECK(!init_);
75   init_ = true;
76 
77   base::FilePath chrome_path;
78   CHECK(PathService::Get(base::FILE_EXE, &chrome_path));
79   CommandLine cmd_line(chrome_path);
80 
81   cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kZygoteProcess);
82 
83   int fds[2];
84 #if defined(OS_FREEBSD) || defined(OS_OPENBSD)
85   // The BSDs often don't support SOCK_SEQPACKET yet, so fall back to
86   // SOCK_DGRAM if necessary.
87   if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) != 0)
88     CHECK(socketpair(PF_UNIX, SOCK_DGRAM, 0, fds) == 0);
89 #else
90   CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0);
91 #endif
92   base::FileHandleMappingVector fds_to_map;
93   fds_to_map.push_back(std::make_pair(fds[1], kZygoteSocketPairFd));
94 
95   const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
96   if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) {
97     cmd_line.PrependWrapper(
98         browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix));
99   }
100   // Append any switches from the browser process that need to be forwarded on
101   // to the zygote/renderers.
102   // Should this list be obtained from browser_render_process_host.cc?
103   static const char* kForwardSwitches[] = {
104     switches::kAllowSandboxDebugging,
105     switches::kLoggingLevel,
106     switches::kEnableLogging,  // Support, e.g., --enable-logging=stderr.
107     switches::kV,
108     switches::kVModule,
109     switches::kRegisterPepperPlugins,
110     switches::kDisableSeccompFilterSandbox,
111 
112     // Zygote process needs to know what resources to have loaded when it
113     // becomes a renderer process.
114     switches::kForceDeviceScaleFactor,
115     switches::kTouchOptimizedUI,
116 
117     switches::kNoSandbox,
118   };
119   cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches,
120                             arraysize(kForwardSwitches));
121 
122   GetContentClient()->browser()->AppendExtraCommandLineSwitches(&cmd_line, -1);
123 
124   sandbox_binary_ = sandbox_cmd.c_str();
125 
126   // A non empty sandbox_cmd means we want a SUID sandbox.
127   using_suid_sandbox_ = !sandbox_cmd.empty();
128 
129   if (using_suid_sandbox_) {
130     struct stat st;
131     if (stat(sandbox_binary_.c_str(), &st) != 0) {
132       LOG(FATAL) << "The SUID sandbox helper binary is missing: "
133                  << sandbox_binary_ << " Aborting now.";
134     }
135 
136     if (access(sandbox_binary_.c_str(), X_OK) == 0 &&
137         (st.st_uid == 0) &&
138         (st.st_mode & S_ISUID) &&
139         (st.st_mode & S_IXOTH)) {
140       cmd_line.PrependWrapper(sandbox_binary_);
141 
142       scoped_ptr<sandbox::SetuidSandboxClient>
143           sandbox_client(sandbox::SetuidSandboxClient::Create());
144       sandbox_client->SetupLaunchEnvironment();
145     } else {
146       LOG(FATAL) << "The SUID sandbox helper binary was found, but is not "
147                     "configured correctly. Rather than run without sandboxing "
148                     "I'm aborting now. You need to make sure that "
149                  << sandbox_binary_ << " is owned by root and has mode 4755.";
150     }
151   }
152 
153   // Start up the sandbox host process and get the file descriptor for the
154   // renderers to talk to it.
155   const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket();
156   fds_to_map.push_back(std::make_pair(sfd, GetSandboxFD()));
157 
158   int dummy_fd = -1;
159   if (using_suid_sandbox_) {
160     dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
161     CHECK(dummy_fd >= 0);
162     fds_to_map.push_back(std::make_pair(dummy_fd, kZygoteIdFd));
163   }
164 
165   base::ProcessHandle process = -1;
166   base::LaunchOptions options;
167   options.fds_to_remap = &fds_to_map;
168   base::LaunchProcess(cmd_line.argv(), options, &process);
169   CHECK(process != -1) << "Failed to launch zygote process";
170 
171   if (using_suid_sandbox_) {
172     // In the SUID sandbox, the real zygote is forked from the sandbox.
173     // We need to look for it.
174     // But first, wait for the zygote to tell us it's running.
175     // The sending code is in content/browser/zygote_main_linux.cc.
176     std::vector<int> fds_vec;
177     const int kExpectedLength = sizeof(kZygoteHelloMessage);
178     char buf[kExpectedLength];
179     const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf),
180                                                   &fds_vec);
181     CHECK(len == kExpectedLength) << "Incorrect zygote magic length";
182     CHECK(0 == strcmp(buf, kZygoteHelloMessage))
183         << "Incorrect zygote hello";
184 
185     std::string inode_output;
186     ino_t inode = 0;
187     // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end,
188     // and find the zygote process holding |dummy_fd|.
189     if (base::FileDescriptorGetInode(&inode, dummy_fd)) {
190       close(dummy_fd);
191       std::vector<std::string> get_inode_cmdline;
192       get_inode_cmdline.push_back(sandbox_binary_);
193       get_inode_cmdline.push_back(base::kFindInodeSwitch);
194       get_inode_cmdline.push_back(base::Int64ToString(inode));
195       CommandLine get_inode_cmd(get_inode_cmdline);
196       if (base::GetAppOutput(get_inode_cmd, &inode_output)) {
197         base::StringToInt(inode_output, &pid_);
198       }
199     }
200     CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary "
201         << sandbox_binary_ << ")";
202 
203     if (process != pid_) {
204       // Reap the sandbox.
205       base::EnsureProcessGetsReaped(process);
206     }
207   } else {
208     // Not using the SUID sandbox.
209     pid_ = process;
210   }
211 
212   close(fds[1]);
213   control_fd_ = fds[0];
214 
215   Pickle pickle;
216   pickle.WriteInt(kZygoteCommandGetSandboxStatus);
217   if (!SendMessage(pickle, NULL))
218     LOG(FATAL) << "Cannot communicate with zygote";
219   // We don't wait for the reply. We'll read it in ReadReply.
220 }
221 
TearDownAfterLastChild()222 void ZygoteHostImpl::TearDownAfterLastChild() {
223   bool do_teardown = false;
224   {
225     base::AutoLock lock(child_tracking_lock_);
226     should_teardown_after_last_child_exits_ = true;
227     do_teardown = list_of_running_zygote_children_.empty();
228   }
229   if (do_teardown) {
230     TearDown();
231   }
232 }
233 
234 // Note: this is also called from the destructor.
TearDown()235 void ZygoteHostImpl::TearDown() {
236   base::AutoLock lock(control_lock_);
237   if (control_fd_ > -1) {
238     // Closing the IPC channel will act as a notification to exit
239     // to the Zygote.
240     if (IGNORE_EINTR(close(control_fd_))) {
241       PLOG(ERROR) << "Could not close Zygote control channel.";
242       NOTREACHED();
243     }
244     control_fd_ = -1;
245   }
246 }
247 
ZygoteChildBorn(pid_t process)248 void ZygoteHostImpl::ZygoteChildBorn(pid_t process) {
249   base::AutoLock lock(child_tracking_lock_);
250   bool new_element_inserted =
251       list_of_running_zygote_children_.insert(process).second;
252   DCHECK(new_element_inserted);
253 }
254 
ZygoteChildDied(pid_t process)255 void ZygoteHostImpl::ZygoteChildDied(pid_t process) {
256   bool do_teardown = false;
257   {
258     base::AutoLock lock(child_tracking_lock_);
259     size_t num_erased = list_of_running_zygote_children_.erase(process);
260     DCHECK_EQ(1U, num_erased);
261     do_teardown = should_teardown_after_last_child_exits_ &&
262                   list_of_running_zygote_children_.empty();
263   }
264   if (do_teardown) {
265     TearDown();
266   }
267 }
268 
SendMessage(const Pickle & data,const std::vector<int> * fds)269 bool ZygoteHostImpl::SendMessage(const Pickle& data,
270                                  const std::vector<int>* fds) {
271   DCHECK_NE(-1, control_fd_);
272   CHECK(data.size() <= kZygoteMaxMessageLength)
273       << "Trying to send too-large message to zygote (sending " << data.size()
274       << " bytes, max is " << kZygoteMaxMessageLength << ")";
275   CHECK(!fds || fds->size() <= UnixDomainSocket::kMaxFileDescriptors)
276       << "Trying to send message with too many file descriptors to zygote "
277       << "(sending " << fds->size() << ", max is "
278       << UnixDomainSocket::kMaxFileDescriptors << ")";
279 
280   return UnixDomainSocket::SendMsg(control_fd_,
281                                    data.data(), data.size(),
282                                    fds ? *fds : std::vector<int>());
283 }
284 
ReadReply(void * buf,size_t buf_len)285 ssize_t ZygoteHostImpl::ReadReply(void* buf, size_t buf_len) {
286   DCHECK_NE(-1, control_fd_);
287   // At startup we send a kZygoteCommandGetSandboxStatus request to the zygote,
288   // but don't wait for the reply. Thus, the first time that we read from the
289   // zygote, we get the reply to that request.
290   if (!have_read_sandbox_status_word_) {
291     if (HANDLE_EINTR(read(control_fd_, &sandbox_status_,
292                           sizeof(sandbox_status_))) !=
293         sizeof(sandbox_status_)) {
294       return -1;
295     }
296     have_read_sandbox_status_word_ = true;
297   }
298 
299   return HANDLE_EINTR(read(control_fd_, buf, buf_len));
300 }
301 
ForkRequest(const std::vector<std::string> & argv,const std::vector<FileDescriptorInfo> & mapping,const std::string & process_type)302 pid_t ZygoteHostImpl::ForkRequest(
303     const std::vector<std::string>& argv,
304     const std::vector<FileDescriptorInfo>& mapping,
305     const std::string& process_type) {
306   DCHECK(init_);
307   Pickle pickle;
308 
309   pickle.WriteInt(kZygoteCommandFork);
310   pickle.WriteString(process_type);
311   pickle.WriteInt(argv.size());
312   for (std::vector<std::string>::const_iterator
313        i = argv.begin(); i != argv.end(); ++i)
314     pickle.WriteString(*i);
315 
316   pickle.WriteInt(mapping.size());
317 
318   std::vector<int> fds;
319   // Scoped pointers cannot be stored in containers, so we have to use a
320   // linked_ptr.
321   std::vector<linked_ptr<file_util::ScopedFD> > autodelete_fds;
322   for (std::vector<FileDescriptorInfo>::const_iterator
323        i = mapping.begin(); i != mapping.end(); ++i) {
324     pickle.WriteUInt32(i->id);
325     fds.push_back(i->fd.fd);
326     if (i->fd.auto_close) {
327       // Auto-close means we need to close the FDs after they have been passed
328       // to the other process.
329       linked_ptr<file_util::ScopedFD> ptr(
330           new file_util::ScopedFD(&(fds.back())));
331       autodelete_fds.push_back(ptr);
332     }
333   }
334 
335   pid_t pid;
336   {
337     base::AutoLock lock(control_lock_);
338     if (!SendMessage(pickle, &fds))
339       return base::kNullProcessHandle;
340 
341     // Read the reply, which pickles the PID and an optional UMA enumeration.
342     static const unsigned kMaxReplyLength = 2048;
343     char buf[kMaxReplyLength];
344     const ssize_t len = ReadReply(buf, sizeof(buf));
345 
346     Pickle reply_pickle(buf, len);
347     PickleIterator iter(reply_pickle);
348     if (len <= 0 || !reply_pickle.ReadInt(&iter, &pid))
349       return base::kNullProcessHandle;
350 
351     // If there is a nonempty UMA name string, then there is a UMA
352     // enumeration to record.
353     std::string uma_name;
354     int uma_sample;
355     int uma_boundary_value;
356     if (reply_pickle.ReadString(&iter, &uma_name) &&
357         !uma_name.empty() &&
358         reply_pickle.ReadInt(&iter, &uma_sample) &&
359         reply_pickle.ReadInt(&iter, &uma_boundary_value)) {
360       // We cannot use the UMA_HISTOGRAM_ENUMERATION macro here,
361       // because that's only for when the name is the same every time.
362       // Here we're using whatever name we got from the other side.
363       // But since it's likely that the same one will be used repeatedly
364       // (even though it's not guaranteed), we cache it here.
365       static base::HistogramBase* uma_histogram;
366       if (!uma_histogram || uma_histogram->histogram_name() != uma_name) {
367         uma_histogram = base::LinearHistogram::FactoryGet(
368             uma_name, 1,
369             uma_boundary_value,
370             uma_boundary_value + 1,
371             base::HistogramBase::kUmaTargetedHistogramFlag);
372       }
373       uma_histogram->Add(uma_sample);
374     }
375 
376     if (pid <= 0)
377       return base::kNullProcessHandle;
378   }
379 
380 #if !defined(OS_OPENBSD)
381   // This is just a starting score for a renderer or extension (the
382   // only types of processes that will be started this way).  It will
383   // get adjusted as time goes on.  (This is the same value as
384   // chrome::kLowestRendererOomScore in chrome/chrome_constants.h, but
385   // that's not something we can include here.)
386   const int kLowestRendererOomScore = 300;
387   AdjustRendererOOMScore(pid, kLowestRendererOomScore);
388 #endif
389 
390   ZygoteChildBorn(pid);
391   return pid;
392 }
393 
394 #if !defined(OS_OPENBSD)
AdjustRendererOOMScore(base::ProcessHandle pid,int score)395 void ZygoteHostImpl::AdjustRendererOOMScore(base::ProcessHandle pid,
396                                             int score) {
397   // 1) You can't change the oom_score_adj of a non-dumpable process
398   //    (EPERM) unless you're root. Because of this, we can't set the
399   //    oom_adj from the browser process.
400   //
401   // 2) We can't set the oom_score_adj before entering the sandbox
402   //    because the zygote is in the sandbox and the zygote is as
403   //    critical as the browser process. Its oom_adj value shouldn't
404   //    be changed.
405   //
406   // 3) A non-dumpable process can't even change its own oom_score_adj
407   //    because it's root owned 0644. The sandboxed processes don't
408   //    even have /proc, but one could imagine passing in a descriptor
409   //    from outside.
410   //
411   // So, in the normal case, we use the SUID binary to change it for us.
412   // However, Fedora (and other SELinux systems) don't like us touching other
413   // process's oom_score_adj (or oom_adj) values
414   // (https://bugzilla.redhat.com/show_bug.cgi?id=581256).
415   //
416   // The offical way to get the SELinux mode is selinux_getenforcemode, but I
417   // don't want to add another library to the build as it's sure to cause
418   // problems with other, non-SELinux distros.
419   //
420   // So we just check for files in /selinux. This isn't foolproof, but it's not
421   // bad and it's easy.
422 
423   static bool selinux;
424   static bool selinux_valid = false;
425 
426   if (!selinux_valid) {
427     const base::FilePath kSelinuxPath("/selinux");
428     base::FileEnumerator en(kSelinuxPath, false, base::FileEnumerator::FILES);
429     bool has_selinux_files = !en.Next().empty();
430 
431     selinux = access(kSelinuxPath.value().c_str(), X_OK) == 0 &&
432               has_selinux_files;
433     selinux_valid = true;
434   }
435 
436   if (using_suid_sandbox_ && !selinux) {
437 #if defined(USE_TCMALLOC)
438     // If heap profiling is running, these processes are not exiting, at least
439     // on ChromeOS. The easiest thing to do is not launch them when profiling.
440     // TODO(stevenjb): Investigate further and fix.
441     if (IsHeapProfilerRunning())
442       return;
443 #endif
444     std::vector<std::string> adj_oom_score_cmdline;
445     adj_oom_score_cmdline.push_back(sandbox_binary_);
446     adj_oom_score_cmdline.push_back(sandbox::kAdjustOOMScoreSwitch);
447     adj_oom_score_cmdline.push_back(base::Int64ToString(pid));
448     adj_oom_score_cmdline.push_back(base::IntToString(score));
449 
450     base::ProcessHandle sandbox_helper_process;
451     if (base::LaunchProcess(adj_oom_score_cmdline, base::LaunchOptions(),
452                             &sandbox_helper_process)) {
453       base::EnsureProcessGetsReaped(sandbox_helper_process);
454     }
455   } else if (!using_suid_sandbox_) {
456     if (!base::AdjustOOMScore(pid, score))
457       PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid;
458   }
459 }
460 #endif
461 
EnsureProcessTerminated(pid_t process)462 void ZygoteHostImpl::EnsureProcessTerminated(pid_t process) {
463   DCHECK(init_);
464   Pickle pickle;
465 
466   pickle.WriteInt(kZygoteCommandReap);
467   pickle.WriteInt(process);
468   if (!SendMessage(pickle, NULL))
469     LOG(ERROR) << "Failed to send Reap message to zygote";
470   ZygoteChildDied(process);
471 }
472 
GetTerminationStatus(base::ProcessHandle handle,bool known_dead,int * exit_code)473 base::TerminationStatus ZygoteHostImpl::GetTerminationStatus(
474     base::ProcessHandle handle,
475     bool known_dead,
476     int* exit_code) {
477   DCHECK(init_);
478   Pickle pickle;
479   pickle.WriteInt(kZygoteCommandGetTerminationStatus);
480   pickle.WriteBool(known_dead);
481   pickle.WriteInt(handle);
482 
483   static const unsigned kMaxMessageLength = 128;
484   char buf[kMaxMessageLength];
485   ssize_t len;
486   {
487     base::AutoLock lock(control_lock_);
488     if (!SendMessage(pickle, NULL))
489       LOG(ERROR) << "Failed to send GetTerminationStatus message to zygote";
490     len = ReadReply(buf, sizeof(buf));
491   }
492 
493   // Set this now to handle the error cases.
494   if (exit_code)
495     *exit_code = RESULT_CODE_NORMAL_EXIT;
496   int status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
497 
498   if (len == -1) {
499     LOG(WARNING) << "Error reading message from zygote: " << errno;
500   } else if (len == 0) {
501     LOG(WARNING) << "Socket closed prematurely.";
502   } else {
503     Pickle read_pickle(buf, len);
504     int tmp_status, tmp_exit_code;
505     PickleIterator iter(read_pickle);
506     if (!read_pickle.ReadInt(&iter, &tmp_status) ||
507         !read_pickle.ReadInt(&iter, &tmp_exit_code)) {
508       LOG(WARNING)
509           << "Error parsing GetTerminationStatus response from zygote.";
510     } else {
511       if (exit_code)
512         *exit_code = tmp_exit_code;
513       status = tmp_status;
514     }
515   }
516 
517   if (status != base::TERMINATION_STATUS_STILL_RUNNING) {
518     ZygoteChildDied(handle);
519   }
520   return static_cast<base::TerminationStatus>(status);
521 }
522 
GetPid() const523 pid_t ZygoteHostImpl::GetPid() const {
524   return pid_;
525 }
526 
GetSandboxHelperPid() const527 pid_t ZygoteHostImpl::GetSandboxHelperPid() const {
528   return RenderSandboxHostLinux::GetInstance()->pid();
529 }
530 
GetSandboxStatus() const531 int ZygoteHostImpl::GetSandboxStatus() const {
532   if (have_read_sandbox_status_word_)
533     return sandbox_status_;
534   return 0;
535 }
536 
537 }  // namespace content
538