OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "chrome/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/command_line.h" | |
13 #include "base/eintr_wrapper.h" | |
14 #include "base/environment.h" | |
15 #include "base/linux_util.h" | |
16 #include "base/logging.h" | |
17 #include "base/path_service.h" | |
18 #include "base/pickle.h" | |
19 #include "base/process_util.h" | |
20 #include "base/string_number_conversions.h" | |
21 #include "base/string_util.h" | |
22 #include "base/scoped_ptr.h" | |
23 #include "base/utf_string_conversions.h" | |
24 #include "chrome/browser/renderer_host/render_sandbox_host_linux.h" | |
25 #include "chrome/common/chrome_constants.h" | |
26 #include "chrome/common/chrome_switches.h" | |
27 #include "chrome/common/process_watcher.h" | |
28 #include "chrome/common/result_codes.h" | |
29 #include "chrome/common/unix_domain_socket_posix.h" | |
30 #include "sandbox/linux/suid/suid_unsafe_environment_variables.h" | |
31 | |
32 static void SaveSUIDUnsafeEnvironmentVariables() { | |
33 // The ELF loader will clear many environment variables so we save them to | |
34 // different names here so that the SUID sandbox can resolve them for the | |
35 // renderer. | |
36 | |
37 for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { | |
38 const char* const envvar = kSUIDUnsafeEnvironmentVariables[i]; | |
39 char* const saved_envvar = SandboxSavedEnvironmentVariable(envvar); | |
40 if (!saved_envvar) | |
41 continue; | |
42 | |
43 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
44 std::string value; | |
45 if (env->GetVar(envvar, &value)) | |
46 env->SetVar(saved_envvar, value); | |
47 else | |
48 env->UnSetVar(saved_envvar); | |
49 | |
50 free(saved_envvar); | |
51 } | |
52 } | |
53 | |
54 ZygoteHost::ZygoteHost() | |
55 : control_fd_(-1), | |
56 pid_(-1), | |
57 init_(false), | |
58 using_suid_sandbox_(false), | |
59 have_read_sandbox_status_word_(false), | |
60 sandbox_status_(0) { | |
61 } | |
62 | |
63 ZygoteHost::~ZygoteHost() { | |
64 if (init_) | |
65 close(control_fd_); | |
66 } | |
67 | |
68 // static | |
69 ZygoteHost* ZygoteHost::GetInstance() { | |
70 return Singleton<ZygoteHost>::get(); | |
71 } | |
72 | |
73 void ZygoteHost::Init(const std::string& sandbox_cmd) { | |
74 DCHECK(!init_); | |
75 init_ = true; | |
76 | |
77 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 CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); | |
85 base::file_handle_mapping_vector fds_to_map; | |
86 fds_to_map.push_back(std::make_pair(fds[1], 3)); | |
87 | |
88 const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); | |
89 if (browser_command_line.HasSwitch(switches::kZygoteCmdPrefix)) { | |
90 cmd_line.PrependWrapper( | |
91 browser_command_line.GetSwitchValueNative(switches::kZygoteCmdPrefix)); | |
92 } | |
93 // Append any switches from the browser process that need to be forwarded on | |
94 // to the zygote/renderers. | |
95 // Should this list be obtained from browser_render_process_host.cc? | |
96 static const char* kForwardSwitches[] = { | |
97 switches::kAllowSandboxDebugging, | |
98 switches::kLoggingLevel, | |
99 switches::kEnableLogging, // Support, e.g., --enable-logging=stderr. | |
100 switches::kV, | |
101 switches::kVModule, | |
102 switches::kUserDataDir, // Make logs go to the right file. | |
103 // Load (in-process) Pepper plugins in-process in the zygote pre-sandbox. | |
104 switches::kRegisterPepperPlugins, | |
105 switches::kDisableSeccompSandbox, | |
106 switches::kEnableSeccompSandbox, | |
107 }; | |
108 cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches, | |
109 arraysize(kForwardSwitches)); | |
110 | |
111 sandbox_binary_ = sandbox_cmd.c_str(); | |
112 struct stat st; | |
113 | |
114 if (!sandbox_cmd.empty() && stat(sandbox_binary_.c_str(), &st) == 0) { | |
115 if (access(sandbox_binary_.c_str(), X_OK) == 0 && | |
116 (st.st_uid == 0) && | |
117 (st.st_mode & S_ISUID) && | |
118 (st.st_mode & S_IXOTH)) { | |
119 using_suid_sandbox_ = true; | |
120 cmd_line.PrependWrapper(sandbox_binary_); | |
121 | |
122 SaveSUIDUnsafeEnvironmentVariables(); | |
123 } else { | |
124 LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " | |
125 "configured correctly. Rather than run without sandboxing " | |
126 "I'm aborting now. You need to make sure that " | |
127 << sandbox_binary_ << " is mode 4755 and owned by root."; | |
128 } | |
129 } | |
130 | |
131 // Start up the sandbox host process and get the file descriptor for the | |
132 // renderers to talk to it. | |
133 const int sfd = RenderSandboxHostLinux::GetInstance()->GetRendererSocket(); | |
134 fds_to_map.push_back(std::make_pair(sfd, 5)); | |
135 | |
136 int dummy_fd = -1; | |
137 if (using_suid_sandbox_) { | |
138 dummy_fd = socket(PF_UNIX, SOCK_DGRAM, 0); | |
139 CHECK(dummy_fd >= 0); | |
140 fds_to_map.push_back(std::make_pair(dummy_fd, 7)); | |
141 } | |
142 | |
143 base::ProcessHandle process; | |
144 base::LaunchApp(cmd_line.argv(), fds_to_map, false, &process); | |
145 CHECK(process != -1) << "Failed to launch zygote process"; | |
146 | |
147 if (using_suid_sandbox_) { | |
148 // In the SUID sandbox, the real zygote is forked from the sandbox. | |
149 // We need to look for it. | |
150 // But first, wait for the zygote to tell us it's running. | |
151 // The sending code is in chrome/browser/zygote_main_linux.cc. | |
152 std::vector<int> fds_vec; | |
153 const int kExpectedLength = sizeof(kZygoteMagic); | |
154 char buf[kExpectedLength]; | |
155 const ssize_t len = UnixDomainSocket::RecvMsg(fds[0], buf, sizeof(buf), | |
156 &fds_vec); | |
157 CHECK(len == kExpectedLength) << "Incorrect zygote magic length"; | |
158 CHECK(0 == strcmp(buf, kZygoteMagic)) << "Incorrect zygote magic"; | |
159 | |
160 std::string inode_output; | |
161 ino_t inode = 0; | |
162 // Figure out the inode for |dummy_fd|, close |dummy_fd| on our end, | |
163 // and find the zygote process holding |dummy_fd|. | |
164 if (base::FileDescriptorGetInode(&inode, dummy_fd)) { | |
165 close(dummy_fd); | |
166 std::vector<std::string> get_inode_cmdline; | |
167 get_inode_cmdline.push_back(sandbox_binary_); | |
168 get_inode_cmdline.push_back(base::kFindInodeSwitch); | |
169 get_inode_cmdline.push_back(base::Int64ToString(inode)); | |
170 CommandLine get_inode_cmd(get_inode_cmdline); | |
171 if (base::GetAppOutput(get_inode_cmd, &inode_output)) { | |
172 base::StringToInt(inode_output, &pid_); | |
173 } | |
174 } | |
175 CHECK(pid_ > 0) << "Did not find zygote process (using sandbox binary " | |
176 << sandbox_binary_ << ")"; | |
177 | |
178 if (process != pid_) { | |
179 // Reap the sandbox. | |
180 ProcessWatcher::EnsureProcessGetsReaped(process); | |
181 } | |
182 } else { | |
183 // Not using the SUID sandbox. | |
184 pid_ = process; | |
185 } | |
186 | |
187 close(fds[1]); | |
188 control_fd_ = fds[0]; | |
189 | |
190 Pickle pickle; | |
191 pickle.WriteInt(kCmdGetSandboxStatus); | |
192 std::vector<int> empty_fds; | |
193 if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), | |
194 empty_fds)) | |
195 LOG(FATAL) << "Cannot communicate with zygote"; | |
196 // We don't wait for the reply. We'll read it in ReadReply. | |
197 } | |
198 | |
199 ssize_t ZygoteHost::ReadReply(void* buf, size_t buf_len) { | |
200 // At startup we send a kCmdGetSandboxStatus request to the zygote, but don't | |
201 // wait for the reply. Thus, the first time that we read from the zygote, we | |
202 // get the reply to that request. | |
203 if (!have_read_sandbox_status_word_) { | |
204 if (HANDLE_EINTR(read(control_fd_, &sandbox_status_, | |
205 sizeof(sandbox_status_))) != | |
206 sizeof(sandbox_status_)) { | |
207 return -1; | |
208 } | |
209 have_read_sandbox_status_word_ = true; | |
210 } | |
211 | |
212 return HANDLE_EINTR(read(control_fd_, buf, buf_len)); | |
213 } | |
214 | |
215 pid_t ZygoteHost::ForkRenderer( | |
216 const std::vector<std::string>& argv, | |
217 const base::GlobalDescriptors::Mapping& mapping) { | |
218 DCHECK(init_); | |
219 Pickle pickle; | |
220 | |
221 pickle.WriteInt(kCmdFork); | |
222 pickle.WriteInt(argv.size()); | |
223 for (std::vector<std::string>::const_iterator | |
224 i = argv.begin(); i != argv.end(); ++i) | |
225 pickle.WriteString(*i); | |
226 | |
227 pickle.WriteInt(mapping.size()); | |
228 | |
229 std::vector<int> fds; | |
230 for (base::GlobalDescriptors::Mapping::const_iterator | |
231 i = mapping.begin(); i != mapping.end(); ++i) { | |
232 pickle.WriteUInt32(i->first); | |
233 fds.push_back(i->second); | |
234 } | |
235 | |
236 pid_t pid; | |
237 { | |
238 base::AutoLock lock(control_lock_); | |
239 if (!UnixDomainSocket::SendMsg(control_fd_, pickle.data(), pickle.size(), | |
240 fds)) | |
241 return base::kNullProcessHandle; | |
242 | |
243 if (ReadReply(&pid, sizeof(pid)) != sizeof(pid)) | |
244 return base::kNullProcessHandle; | |
245 if (pid <= 0) | |
246 return base::kNullProcessHandle; | |
247 } | |
248 | |
249 const int kRendererScore = 5; | |
250 AdjustRendererOOMScore(pid, kRendererScore); | |
251 | |
252 return pid; | |
253 } | |
254 | |
255 void ZygoteHost::AdjustRendererOOMScore(base::ProcessHandle pid, int score) { | |
256 // 1) You can't change the oom_adj of a non-dumpable process (EPERM) unless | |
257 // you're root. Because of this, we can't set the oom_adj from the browser | |
258 // process. | |
259 // | |
260 // 2) We can't set the oom_adj before entering the sandbox because the | |
261 // zygote is in the sandbox and the zygote is as critical as the browser | |
262 // process. Its oom_adj value shouldn't be changed. | |
263 // | |
264 // 3) A non-dumpable process can't even change its own oom_adj because it's | |
265 // root owned 0644. The sandboxed processes don't even have /proc, but one | |
266 // could imagine passing in a descriptor from outside. | |
267 // | |
268 // So, in the normal case, we use the SUID binary to change it for us. | |
269 // However, Fedora (and other SELinux systems) don't like us touching other | |
270 // process's oom_adj values | |
271 // (https://bugzilla.redhat.com/show_bug.cgi?id=581256). | |
272 // | |
273 // The offical way to get the SELinux mode is selinux_getenforcemode, but I | |
274 // don't want to add another library to the build as it's sure to cause | |
275 // problems with other, non-SELinux distros. | |
276 // | |
277 // So we just check for /selinux. This isn't foolproof, but it's not bad | |
278 // and it's easy. | |
279 | |
280 static bool selinux; | |
281 static bool selinux_valid = false; | |
282 | |
283 if (!selinux_valid) { | |
284 selinux = access("/selinux", X_OK) == 0; | |
285 selinux_valid = true; | |
286 } | |
287 | |
288 if (using_suid_sandbox_ && !selinux) { | |
289 base::ProcessHandle sandbox_helper_process; | |
290 std::vector<std::string> adj_oom_score_cmdline; | |
291 | |
292 adj_oom_score_cmdline.push_back(sandbox_binary_); | |
293 adj_oom_score_cmdline.push_back(base::kAdjustOOMScoreSwitch); | |
294 adj_oom_score_cmdline.push_back(base::Int64ToString(pid)); | |
295 adj_oom_score_cmdline.push_back(base::IntToString(score)); | |
296 CommandLine adj_oom_score_cmd(adj_oom_score_cmdline); | |
297 if (base::LaunchApp(adj_oom_score_cmd, false, true, | |
298 &sandbox_helper_process)) { | |
299 ProcessWatcher::EnsureProcessGetsReaped(sandbox_helper_process); | |
300 } | |
301 } else if (!using_suid_sandbox_) { | |
302 if (!base::AdjustOOMScore(pid, score)) | |
303 PLOG(ERROR) << "Failed to adjust OOM score of renderer with pid " << pid; | |
304 } | |
305 } | |
306 | |
307 void ZygoteHost::EnsureProcessTerminated(pid_t process) { | |
308 DCHECK(init_); | |
309 Pickle pickle; | |
310 | |
311 pickle.WriteInt(kCmdReap); | |
312 pickle.WriteInt(process); | |
313 | |
314 if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) | |
315 PLOG(ERROR) << "write"; | |
316 } | |
317 | |
318 base::TerminationStatus ZygoteHost::GetTerminationStatus( | |
319 base::ProcessHandle handle, | |
320 int* exit_code) { | |
321 DCHECK(init_); | |
322 Pickle pickle; | |
323 pickle.WriteInt(kCmdGetTerminationStatus); | |
324 pickle.WriteInt(handle); | |
325 | |
326 // Set this now to handle the early termination cases. | |
327 if (exit_code) | |
328 *exit_code = ResultCodes::NORMAL_EXIT; | |
329 | |
330 static const unsigned kMaxMessageLength = 128; | |
331 char buf[kMaxMessageLength]; | |
332 ssize_t len; | |
333 { | |
334 base::AutoLock lock(control_lock_); | |
335 if (HANDLE_EINTR(write(control_fd_, pickle.data(), pickle.size())) < 0) | |
336 PLOG(ERROR) << "write"; | |
337 | |
338 len = ReadReply(buf, sizeof(buf)); | |
339 } | |
340 | |
341 if (len == -1) { | |
342 LOG(WARNING) << "Error reading message from zygote: " << errno; | |
343 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
344 } else if (len == 0) { | |
345 LOG(WARNING) << "Socket closed prematurely."; | |
346 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
347 } | |
348 | |
349 Pickle read_pickle(buf, len); | |
350 int status, tmp_exit_code; | |
351 void* iter = NULL; | |
352 if (!read_pickle.ReadInt(&iter, &status) || | |
353 !read_pickle.ReadInt(&iter, &tmp_exit_code)) { | |
354 LOG(WARNING) << "Error parsing GetTerminationStatus response from zygote."; | |
355 return base::TERMINATION_STATUS_NORMAL_TERMINATION; | |
356 } | |
357 | |
358 if (exit_code) | |
359 *exit_code = tmp_exit_code; | |
360 | |
361 return static_cast<base::TerminationStatus>(status); | |
362 } | |
OLD | NEW |