OLD | NEW |
| (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 } | |
OLD | NEW |