|
1 // Copyright (c) 2008 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 "base/process_util.h" |
|
6 |
|
7 #include <ctype.h> |
|
8 #include <dirent.h> |
|
9 #include <fcntl.h> |
|
10 #include <memory> |
|
11 #include <unistd.h> |
|
12 #include <string> |
|
13 #include <sys/types.h> |
|
14 #include <sys/wait.h> |
|
15 |
|
16 #include "base/debug_util.h" |
|
17 #include "base/eintr_wrapper.h" |
|
18 #include "base/file_util.h" |
|
19 #include "base/logging.h" |
|
20 #include "base/string_tokenizer.h" |
|
21 #include "base/string_util.h" |
|
22 |
|
23 #ifdef MOZ_WIDGET_GONK |
|
24 /* |
|
25 * AID_APP is the first application UID used by Android. We're using |
|
26 * it as our unprivilegied UID. This ensure the UID used is not |
|
27 * shared with any other processes than our own childs. |
|
28 */ |
|
29 # include <private/android_filesystem_config.h> |
|
30 # define CHILD_UNPRIVILEGED_UID AID_APP |
|
31 # define CHILD_UNPRIVILEGED_GID AID_APP |
|
32 #else |
|
33 /* |
|
34 * On platforms that are not gonk based, we fall back to an arbitrary |
|
35 * UID. This is generally the UID for user `nobody', albeit it is not |
|
36 * always the case. |
|
37 */ |
|
38 # define CHILD_UNPRIVILEGED_UID 65534 |
|
39 # define CHILD_UNPRIVILEGED_GID 65534 |
|
40 #endif |
|
41 |
|
42 #ifdef ANDROID |
|
43 #include <pthread.h> |
|
44 /* |
|
45 * Currently, PR_DuplicateEnvironment is implemented in |
|
46 * mozglue/build/BionicGlue.cpp |
|
47 */ |
|
48 #define HAVE_PR_DUPLICATE_ENVIRONMENT |
|
49 |
|
50 #include "plstr.h" |
|
51 #include "prenv.h" |
|
52 #include "prmem.h" |
|
53 /* Temporary until we have PR_DuplicateEnvironment in prenv.h */ |
|
54 extern "C" { NSPR_API(pthread_mutex_t *)PR_GetEnvLock(void); } |
|
55 #endif |
|
56 |
|
57 namespace { |
|
58 |
|
59 enum ParsingState { |
|
60 KEY_NAME, |
|
61 KEY_VALUE |
|
62 }; |
|
63 |
|
64 static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); |
|
65 |
|
66 } // namespace |
|
67 |
|
68 namespace base { |
|
69 |
|
70 #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT |
|
71 /* |
|
72 * I tried to put PR_DuplicateEnvironment down in mozglue, but on android |
|
73 * this winds up failing because the strdup/free calls wind up mismatching. |
|
74 */ |
|
75 |
|
76 static char ** |
|
77 PR_DuplicateEnvironment(void) |
|
78 { |
|
79 char **result = NULL; |
|
80 char **s; |
|
81 char **d; |
|
82 pthread_mutex_lock(PR_GetEnvLock()); |
|
83 for (s = environ; *s != NULL; s++) |
|
84 ; |
|
85 if ((result = (char **)PR_Malloc(sizeof(char *) * (s - environ + 1))) != NULL) { |
|
86 for (s = environ, d = result; *s != NULL; s++, d++) { |
|
87 *d = PL_strdup(*s); |
|
88 } |
|
89 *d = NULL; |
|
90 } |
|
91 pthread_mutex_unlock(PR_GetEnvLock()); |
|
92 return result; |
|
93 } |
|
94 |
|
95 class EnvironmentEnvp |
|
96 { |
|
97 public: |
|
98 EnvironmentEnvp() |
|
99 : mEnvp(PR_DuplicateEnvironment()) {} |
|
100 |
|
101 EnvironmentEnvp(const environment_map &em) |
|
102 { |
|
103 mEnvp = (char **)PR_Malloc(sizeof(char *) * (em.size() + 1)); |
|
104 if (!mEnvp) { |
|
105 return; |
|
106 } |
|
107 char **e = mEnvp; |
|
108 for (environment_map::const_iterator it = em.begin(); |
|
109 it != em.end(); ++it, ++e) { |
|
110 std::string str = it->first; |
|
111 str += "="; |
|
112 str += it->second; |
|
113 *e = PL_strdup(str.c_str()); |
|
114 } |
|
115 *e = NULL; |
|
116 } |
|
117 |
|
118 ~EnvironmentEnvp() |
|
119 { |
|
120 if (!mEnvp) { |
|
121 return; |
|
122 } |
|
123 for (char **e = mEnvp; *e; ++e) { |
|
124 PL_strfree(*e); |
|
125 } |
|
126 PR_Free(mEnvp); |
|
127 } |
|
128 |
|
129 char * const *AsEnvp() { return mEnvp; } |
|
130 |
|
131 void ToMap(environment_map &em) |
|
132 { |
|
133 if (!mEnvp) { |
|
134 return; |
|
135 } |
|
136 em.clear(); |
|
137 for (char **e = mEnvp; *e; ++e) { |
|
138 const char *eq; |
|
139 if ((eq = strchr(*e, '=')) != NULL) { |
|
140 std::string varname(*e, eq - *e); |
|
141 em[varname.c_str()] = &eq[1]; |
|
142 } |
|
143 } |
|
144 } |
|
145 |
|
146 private: |
|
147 char **mEnvp; |
|
148 }; |
|
149 |
|
150 class Environment : public environment_map |
|
151 { |
|
152 public: |
|
153 Environment() |
|
154 { |
|
155 EnvironmentEnvp envp; |
|
156 envp.ToMap(*this); |
|
157 } |
|
158 |
|
159 char * const *AsEnvp() { |
|
160 mEnvp.reset(new EnvironmentEnvp(*this)); |
|
161 return mEnvp->AsEnvp(); |
|
162 } |
|
163 |
|
164 void Merge(const environment_map &em) |
|
165 { |
|
166 for (const_iterator it = em.begin(); it != em.end(); ++it) { |
|
167 (*this)[it->first] = it->second; |
|
168 } |
|
169 } |
|
170 private: |
|
171 std::auto_ptr<EnvironmentEnvp> mEnvp; |
|
172 }; |
|
173 #endif // HAVE_PR_DUPLICATE_ENVIRONMENT |
|
174 |
|
175 bool LaunchApp(const std::vector<std::string>& argv, |
|
176 const file_handle_mapping_vector& fds_to_remap, |
|
177 bool wait, ProcessHandle* process_handle) { |
|
178 return LaunchApp(argv, fds_to_remap, environment_map(), |
|
179 wait, process_handle); |
|
180 } |
|
181 |
|
182 bool LaunchApp(const std::vector<std::string>& argv, |
|
183 const file_handle_mapping_vector& fds_to_remap, |
|
184 const environment_map& env_vars_to_set, |
|
185 bool wait, ProcessHandle* process_handle, |
|
186 ProcessArchitecture arch) { |
|
187 return LaunchApp(argv, fds_to_remap, env_vars_to_set, |
|
188 PRIVILEGES_INHERIT, |
|
189 wait, process_handle); |
|
190 } |
|
191 |
|
192 bool LaunchApp(const std::vector<std::string>& argv, |
|
193 const file_handle_mapping_vector& fds_to_remap, |
|
194 const environment_map& env_vars_to_set, |
|
195 ChildPrivileges privs, |
|
196 bool wait, ProcessHandle* process_handle, |
|
197 ProcessArchitecture arch) { |
|
198 scoped_array<char*> argv_cstr(new char*[argv.size() + 1]); |
|
199 // Illegal to allocate memory after fork and before execvp |
|
200 InjectiveMultimap fd_shuffle1, fd_shuffle2; |
|
201 fd_shuffle1.reserve(fds_to_remap.size()); |
|
202 fd_shuffle2.reserve(fds_to_remap.size()); |
|
203 |
|
204 #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT |
|
205 Environment env; |
|
206 env.Merge(env_vars_to_set); |
|
207 char * const *envp = env.AsEnvp(); |
|
208 if (!envp) { |
|
209 DLOG(ERROR) << "FAILED to duplicate environment for: " << argv_cstr[0]; |
|
210 return false; |
|
211 } |
|
212 #endif |
|
213 |
|
214 pid_t pid = fork(); |
|
215 if (pid < 0) |
|
216 return false; |
|
217 |
|
218 if (pid == 0) { |
|
219 for (file_handle_mapping_vector::const_iterator |
|
220 it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { |
|
221 fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); |
|
222 fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); |
|
223 } |
|
224 |
|
225 if (!ShuffleFileDescriptors(&fd_shuffle1)) |
|
226 _exit(127); |
|
227 |
|
228 CloseSuperfluousFds(fd_shuffle2); |
|
229 |
|
230 for (size_t i = 0; i < argv.size(); i++) |
|
231 argv_cstr[i] = const_cast<char*>(argv[i].c_str()); |
|
232 argv_cstr[argv.size()] = NULL; |
|
233 |
|
234 SetCurrentProcessPrivileges(privs); |
|
235 |
|
236 #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT |
|
237 execve(argv_cstr[0], argv_cstr.get(), envp); |
|
238 #else |
|
239 for (environment_map::const_iterator it = env_vars_to_set.begin(); |
|
240 it != env_vars_to_set.end(); ++it) { |
|
241 if (setenv(it->first.c_str(), it->second.c_str(), 1/*overwrite*/)) |
|
242 _exit(127); |
|
243 } |
|
244 execv(argv_cstr[0], argv_cstr.get()); |
|
245 #endif |
|
246 // if we get here, we're in serious trouble and should complain loudly |
|
247 DLOG(ERROR) << "FAILED TO exec() CHILD PROCESS, path: " << argv_cstr[0]; |
|
248 _exit(127); |
|
249 } else { |
|
250 gProcessLog.print("==> process %d launched child process %d\n", |
|
251 GetCurrentProcId(), pid); |
|
252 if (wait) |
|
253 HANDLE_EINTR(waitpid(pid, 0, 0)); |
|
254 |
|
255 if (process_handle) |
|
256 *process_handle = pid; |
|
257 } |
|
258 |
|
259 return true; |
|
260 } |
|
261 |
|
262 bool LaunchApp(const CommandLine& cl, |
|
263 bool wait, bool start_hidden, |
|
264 ProcessHandle* process_handle) { |
|
265 file_handle_mapping_vector no_files; |
|
266 return LaunchApp(cl.argv(), no_files, wait, process_handle); |
|
267 } |
|
268 |
|
269 void SetCurrentProcessPrivileges(ChildPrivileges privs) { |
|
270 if (privs == PRIVILEGES_INHERIT) { |
|
271 return; |
|
272 } |
|
273 |
|
274 gid_t gid = CHILD_UNPRIVILEGED_GID; |
|
275 uid_t uid = CHILD_UNPRIVILEGED_UID; |
|
276 #ifdef MOZ_WIDGET_GONK |
|
277 { |
|
278 static bool checked_pix_max, pix_max_ok; |
|
279 if (!checked_pix_max) { |
|
280 checked_pix_max = true; |
|
281 int fd = open("/proc/sys/kernel/pid_max", O_CLOEXEC | O_RDONLY); |
|
282 if (fd < 0) { |
|
283 DLOG(ERROR) << "Failed to open pid_max"; |
|
284 _exit(127); |
|
285 } |
|
286 char buf[PATH_MAX]; |
|
287 ssize_t len = read(fd, buf, sizeof(buf) - 1); |
|
288 close(fd); |
|
289 if (len < 0) { |
|
290 DLOG(ERROR) << "Failed to read pid_max"; |
|
291 _exit(127); |
|
292 } |
|
293 buf[len] = '\0'; |
|
294 int pid_max = atoi(buf); |
|
295 pix_max_ok = |
|
296 (pid_max + CHILD_UNPRIVILEGED_UID > CHILD_UNPRIVILEGED_UID); |
|
297 } |
|
298 if (!pix_max_ok) { |
|
299 DLOG(ERROR) << "Can't safely get unique uid/gid"; |
|
300 _exit(127); |
|
301 } |
|
302 gid += getpid(); |
|
303 uid += getpid(); |
|
304 } |
|
305 #endif |
|
306 if (setgid(gid) != 0) { |
|
307 DLOG(ERROR) << "FAILED TO setgid() CHILD PROCESS"; |
|
308 _exit(127); |
|
309 } |
|
310 if (setuid(uid) != 0) { |
|
311 DLOG(ERROR) << "FAILED TO setuid() CHILD PROCESS"; |
|
312 _exit(127); |
|
313 } |
|
314 if (chdir("/") != 0) |
|
315 gProcessLog.print("==> could not chdir()\n"); |
|
316 } |
|
317 |
|
318 NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name, |
|
319 const ProcessFilter* filter) |
|
320 : executable_name_(executable_name), filter_(filter) { |
|
321 procfs_dir_ = opendir("/proc"); |
|
322 } |
|
323 |
|
324 NamedProcessIterator::~NamedProcessIterator() { |
|
325 if (procfs_dir_) { |
|
326 closedir(procfs_dir_); |
|
327 procfs_dir_ = NULL; |
|
328 } |
|
329 } |
|
330 |
|
331 const ProcessEntry* NamedProcessIterator::NextProcessEntry() { |
|
332 bool result = false; |
|
333 do { |
|
334 result = CheckForNextProcess(); |
|
335 } while (result && !IncludeEntry()); |
|
336 |
|
337 if (result) |
|
338 return &entry_; |
|
339 |
|
340 return NULL; |
|
341 } |
|
342 |
|
343 bool NamedProcessIterator::CheckForNextProcess() { |
|
344 // TODO(port): skip processes owned by different UID |
|
345 |
|
346 dirent* slot = 0; |
|
347 const char* openparen; |
|
348 const char* closeparen; |
|
349 |
|
350 // Arbitrarily guess that there will never be more than 200 non-process |
|
351 // files in /proc. Hardy has 53. |
|
352 int skipped = 0; |
|
353 const int kSkipLimit = 200; |
|
354 while (skipped < kSkipLimit) { |
|
355 slot = readdir(procfs_dir_); |
|
356 // all done looking through /proc? |
|
357 if (!slot) |
|
358 return false; |
|
359 |
|
360 // If not a process, keep looking for one. |
|
361 bool notprocess = false; |
|
362 int i; |
|
363 for (i = 0; i < NAME_MAX && slot->d_name[i]; ++i) { |
|
364 if (!isdigit(slot->d_name[i])) { |
|
365 notprocess = true; |
|
366 break; |
|
367 } |
|
368 } |
|
369 if (i == NAME_MAX || notprocess) { |
|
370 skipped++; |
|
371 continue; |
|
372 } |
|
373 |
|
374 // Read the process's status. |
|
375 char buf[NAME_MAX + 12]; |
|
376 sprintf(buf, "/proc/%s/stat", slot->d_name); |
|
377 FILE *fp = fopen(buf, "r"); |
|
378 if (!fp) |
|
379 return false; |
|
380 const char* result = fgets(buf, sizeof(buf), fp); |
|
381 fclose(fp); |
|
382 if (!result) |
|
383 return false; |
|
384 |
|
385 // Parse the status. It is formatted like this: |
|
386 // %d (%s) %c %d ... |
|
387 // pid (name) runstate ppid |
|
388 // To avoid being fooled by names containing a closing paren, scan |
|
389 // backwards. |
|
390 openparen = strchr(buf, '('); |
|
391 closeparen = strrchr(buf, ')'); |
|
392 if (!openparen || !closeparen) |
|
393 return false; |
|
394 char runstate = closeparen[2]; |
|
395 |
|
396 // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped? |
|
397 // Allowed values: D R S T Z |
|
398 if (runstate != 'Z') |
|
399 break; |
|
400 |
|
401 // Nope, it's a zombie; somebody isn't cleaning up after their children. |
|
402 // (e.g. WaitForProcessesToExit doesn't clean up after dead children yet.) |
|
403 // There could be a lot of zombies, can't really decrement i here. |
|
404 } |
|
405 if (skipped >= kSkipLimit) { |
|
406 NOTREACHED(); |
|
407 return false; |
|
408 } |
|
409 |
|
410 entry_.pid = atoi(slot->d_name); |
|
411 entry_.ppid = atoi(closeparen + 3); |
|
412 |
|
413 // TODO(port): read pid's commandline's $0, like killall does. Using the |
|
414 // short name between openparen and closeparen won't work for long names! |
|
415 int len = closeparen - openparen - 1; |
|
416 if (len > NAME_MAX) |
|
417 len = NAME_MAX; |
|
418 memcpy(entry_.szExeFile, openparen + 1, len); |
|
419 entry_.szExeFile[len] = 0; |
|
420 |
|
421 return true; |
|
422 } |
|
423 |
|
424 bool NamedProcessIterator::IncludeEntry() { |
|
425 // TODO(port): make this also work for non-ASCII filenames |
|
426 if (WideToASCII(executable_name_) != entry_.szExeFile) |
|
427 return false; |
|
428 if (!filter_) |
|
429 return true; |
|
430 return filter_->Includes(entry_.pid, entry_.ppid); |
|
431 } |
|
432 |
|
433 } // namespace base |