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

Side by Side Diff: content/browser/zygote_host_impl_linux.cc

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

Powered by Google App Engine
This is Rietveld 408576698