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