michael@0: // Copyright (c) 2008 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/process_util.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/debug_util.h" michael@0: #include "base/eintr_wrapper.h" michael@0: #include "base/file_util.h" michael@0: #include "base/logging.h" michael@0: #include "base/string_tokenizer.h" michael@0: #include "base/string_util.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: /* michael@0: * AID_APP is the first application UID used by Android. We're using michael@0: * it as our unprivilegied UID. This ensure the UID used is not michael@0: * shared with any other processes than our own childs. michael@0: */ michael@0: # include michael@0: # define CHILD_UNPRIVILEGED_UID AID_APP michael@0: # define CHILD_UNPRIVILEGED_GID AID_APP michael@0: #else michael@0: /* michael@0: * On platforms that are not gonk based, we fall back to an arbitrary michael@0: * UID. This is generally the UID for user `nobody', albeit it is not michael@0: * always the case. michael@0: */ michael@0: # define CHILD_UNPRIVILEGED_UID 65534 michael@0: # define CHILD_UNPRIVILEGED_GID 65534 michael@0: #endif michael@0: michael@0: #ifdef ANDROID michael@0: #include michael@0: /* michael@0: * Currently, PR_DuplicateEnvironment is implemented in michael@0: * mozglue/build/BionicGlue.cpp michael@0: */ michael@0: #define HAVE_PR_DUPLICATE_ENVIRONMENT michael@0: michael@0: #include "plstr.h" michael@0: #include "prenv.h" michael@0: #include "prmem.h" michael@0: /* Temporary until we have PR_DuplicateEnvironment in prenv.h */ michael@0: extern "C" { NSPR_API(pthread_mutex_t *)PR_GetEnvLock(void); } michael@0: #endif michael@0: michael@0: namespace { michael@0: michael@0: enum ParsingState { michael@0: KEY_NAME, michael@0: KEY_VALUE michael@0: }; michael@0: michael@0: static mozilla::EnvironmentLog gProcessLog("MOZ_PROCESS_LOG"); michael@0: michael@0: } // namespace michael@0: michael@0: namespace base { michael@0: michael@0: #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT michael@0: /* michael@0: * I tried to put PR_DuplicateEnvironment down in mozglue, but on android michael@0: * this winds up failing because the strdup/free calls wind up mismatching. michael@0: */ michael@0: michael@0: static char ** michael@0: PR_DuplicateEnvironment(void) michael@0: { michael@0: char **result = NULL; michael@0: char **s; michael@0: char **d; michael@0: pthread_mutex_lock(PR_GetEnvLock()); michael@0: for (s = environ; *s != NULL; s++) michael@0: ; michael@0: if ((result = (char **)PR_Malloc(sizeof(char *) * (s - environ + 1))) != NULL) { michael@0: for (s = environ, d = result; *s != NULL; s++, d++) { michael@0: *d = PL_strdup(*s); michael@0: } michael@0: *d = NULL; michael@0: } michael@0: pthread_mutex_unlock(PR_GetEnvLock()); michael@0: return result; michael@0: } michael@0: michael@0: class EnvironmentEnvp michael@0: { michael@0: public: michael@0: EnvironmentEnvp() michael@0: : mEnvp(PR_DuplicateEnvironment()) {} michael@0: michael@0: EnvironmentEnvp(const environment_map &em) michael@0: { michael@0: mEnvp = (char **)PR_Malloc(sizeof(char *) * (em.size() + 1)); michael@0: if (!mEnvp) { michael@0: return; michael@0: } michael@0: char **e = mEnvp; michael@0: for (environment_map::const_iterator it = em.begin(); michael@0: it != em.end(); ++it, ++e) { michael@0: std::string str = it->first; michael@0: str += "="; michael@0: str += it->second; michael@0: *e = PL_strdup(str.c_str()); michael@0: } michael@0: *e = NULL; michael@0: } michael@0: michael@0: ~EnvironmentEnvp() michael@0: { michael@0: if (!mEnvp) { michael@0: return; michael@0: } michael@0: for (char **e = mEnvp; *e; ++e) { michael@0: PL_strfree(*e); michael@0: } michael@0: PR_Free(mEnvp); michael@0: } michael@0: michael@0: char * const *AsEnvp() { return mEnvp; } michael@0: michael@0: void ToMap(environment_map &em) michael@0: { michael@0: if (!mEnvp) { michael@0: return; michael@0: } michael@0: em.clear(); michael@0: for (char **e = mEnvp; *e; ++e) { michael@0: const char *eq; michael@0: if ((eq = strchr(*e, '=')) != NULL) { michael@0: std::string varname(*e, eq - *e); michael@0: em[varname.c_str()] = &eq[1]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: private: michael@0: char **mEnvp; michael@0: }; michael@0: michael@0: class Environment : public environment_map michael@0: { michael@0: public: michael@0: Environment() michael@0: { michael@0: EnvironmentEnvp envp; michael@0: envp.ToMap(*this); michael@0: } michael@0: michael@0: char * const *AsEnvp() { michael@0: mEnvp.reset(new EnvironmentEnvp(*this)); michael@0: return mEnvp->AsEnvp(); michael@0: } michael@0: michael@0: void Merge(const environment_map &em) michael@0: { michael@0: for (const_iterator it = em.begin(); it != em.end(); ++it) { michael@0: (*this)[it->first] = it->second; michael@0: } michael@0: } michael@0: private: michael@0: std::auto_ptr mEnvp; michael@0: }; michael@0: #endif // HAVE_PR_DUPLICATE_ENVIRONMENT michael@0: michael@0: bool LaunchApp(const std::vector& argv, michael@0: const file_handle_mapping_vector& fds_to_remap, michael@0: bool wait, ProcessHandle* process_handle) { michael@0: return LaunchApp(argv, fds_to_remap, environment_map(), michael@0: wait, process_handle); michael@0: } michael@0: michael@0: bool LaunchApp(const std::vector& argv, michael@0: const file_handle_mapping_vector& fds_to_remap, michael@0: const environment_map& env_vars_to_set, michael@0: bool wait, ProcessHandle* process_handle, michael@0: ProcessArchitecture arch) { michael@0: return LaunchApp(argv, fds_to_remap, env_vars_to_set, michael@0: PRIVILEGES_INHERIT, michael@0: wait, process_handle); michael@0: } michael@0: michael@0: bool LaunchApp(const std::vector& argv, michael@0: const file_handle_mapping_vector& fds_to_remap, michael@0: const environment_map& env_vars_to_set, michael@0: ChildPrivileges privs, michael@0: bool wait, ProcessHandle* process_handle, michael@0: ProcessArchitecture arch) { michael@0: scoped_array argv_cstr(new char*[argv.size() + 1]); michael@0: // Illegal to allocate memory after fork and before execvp michael@0: InjectiveMultimap fd_shuffle1, fd_shuffle2; michael@0: fd_shuffle1.reserve(fds_to_remap.size()); michael@0: fd_shuffle2.reserve(fds_to_remap.size()); michael@0: michael@0: #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT michael@0: Environment env; michael@0: env.Merge(env_vars_to_set); michael@0: char * const *envp = env.AsEnvp(); michael@0: if (!envp) { michael@0: DLOG(ERROR) << "FAILED to duplicate environment for: " << argv_cstr[0]; michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: pid_t pid = fork(); michael@0: if (pid < 0) michael@0: return false; michael@0: michael@0: if (pid == 0) { michael@0: for (file_handle_mapping_vector::const_iterator michael@0: it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { michael@0: fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); michael@0: fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); michael@0: } michael@0: michael@0: if (!ShuffleFileDescriptors(&fd_shuffle1)) michael@0: _exit(127); michael@0: michael@0: CloseSuperfluousFds(fd_shuffle2); michael@0: michael@0: for (size_t i = 0; i < argv.size(); i++) michael@0: argv_cstr[i] = const_cast(argv[i].c_str()); michael@0: argv_cstr[argv.size()] = NULL; michael@0: michael@0: SetCurrentProcessPrivileges(privs); michael@0: michael@0: #ifdef HAVE_PR_DUPLICATE_ENVIRONMENT michael@0: execve(argv_cstr[0], argv_cstr.get(), envp); michael@0: #else michael@0: for (environment_map::const_iterator it = env_vars_to_set.begin(); michael@0: it != env_vars_to_set.end(); ++it) { michael@0: if (setenv(it->first.c_str(), it->second.c_str(), 1/*overwrite*/)) michael@0: _exit(127); michael@0: } michael@0: execv(argv_cstr[0], argv_cstr.get()); michael@0: #endif michael@0: // if we get here, we're in serious trouble and should complain loudly michael@0: DLOG(ERROR) << "FAILED TO exec() CHILD PROCESS, path: " << argv_cstr[0]; michael@0: _exit(127); michael@0: } else { michael@0: gProcessLog.print("==> process %d launched child process %d\n", michael@0: GetCurrentProcId(), pid); michael@0: if (wait) michael@0: HANDLE_EINTR(waitpid(pid, 0, 0)); michael@0: michael@0: if (process_handle) michael@0: *process_handle = pid; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool LaunchApp(const CommandLine& cl, michael@0: bool wait, bool start_hidden, michael@0: ProcessHandle* process_handle) { michael@0: file_handle_mapping_vector no_files; michael@0: return LaunchApp(cl.argv(), no_files, wait, process_handle); michael@0: } michael@0: michael@0: void SetCurrentProcessPrivileges(ChildPrivileges privs) { michael@0: if (privs == PRIVILEGES_INHERIT) { michael@0: return; michael@0: } michael@0: michael@0: gid_t gid = CHILD_UNPRIVILEGED_GID; michael@0: uid_t uid = CHILD_UNPRIVILEGED_UID; michael@0: #ifdef MOZ_WIDGET_GONK michael@0: { michael@0: static bool checked_pix_max, pix_max_ok; michael@0: if (!checked_pix_max) { michael@0: checked_pix_max = true; michael@0: int fd = open("/proc/sys/kernel/pid_max", O_CLOEXEC | O_RDONLY); michael@0: if (fd < 0) { michael@0: DLOG(ERROR) << "Failed to open pid_max"; michael@0: _exit(127); michael@0: } michael@0: char buf[PATH_MAX]; michael@0: ssize_t len = read(fd, buf, sizeof(buf) - 1); michael@0: close(fd); michael@0: if (len < 0) { michael@0: DLOG(ERROR) << "Failed to read pid_max"; michael@0: _exit(127); michael@0: } michael@0: buf[len] = '\0'; michael@0: int pid_max = atoi(buf); michael@0: pix_max_ok = michael@0: (pid_max + CHILD_UNPRIVILEGED_UID > CHILD_UNPRIVILEGED_UID); michael@0: } michael@0: if (!pix_max_ok) { michael@0: DLOG(ERROR) << "Can't safely get unique uid/gid"; michael@0: _exit(127); michael@0: } michael@0: gid += getpid(); michael@0: uid += getpid(); michael@0: } michael@0: #endif michael@0: if (setgid(gid) != 0) { michael@0: DLOG(ERROR) << "FAILED TO setgid() CHILD PROCESS"; michael@0: _exit(127); michael@0: } michael@0: if (setuid(uid) != 0) { michael@0: DLOG(ERROR) << "FAILED TO setuid() CHILD PROCESS"; michael@0: _exit(127); michael@0: } michael@0: if (chdir("/") != 0) michael@0: gProcessLog.print("==> could not chdir()\n"); michael@0: } michael@0: michael@0: NamedProcessIterator::NamedProcessIterator(const std::wstring& executable_name, michael@0: const ProcessFilter* filter) michael@0: : executable_name_(executable_name), filter_(filter) { michael@0: procfs_dir_ = opendir("/proc"); michael@0: } michael@0: michael@0: NamedProcessIterator::~NamedProcessIterator() { michael@0: if (procfs_dir_) { michael@0: closedir(procfs_dir_); michael@0: procfs_dir_ = NULL; michael@0: } michael@0: } michael@0: michael@0: const ProcessEntry* NamedProcessIterator::NextProcessEntry() { michael@0: bool result = false; michael@0: do { michael@0: result = CheckForNextProcess(); michael@0: } while (result && !IncludeEntry()); michael@0: michael@0: if (result) michael@0: return &entry_; michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: bool NamedProcessIterator::CheckForNextProcess() { michael@0: // TODO(port): skip processes owned by different UID michael@0: michael@0: dirent* slot = 0; michael@0: const char* openparen; michael@0: const char* closeparen; michael@0: michael@0: // Arbitrarily guess that there will never be more than 200 non-process michael@0: // files in /proc. Hardy has 53. michael@0: int skipped = 0; michael@0: const int kSkipLimit = 200; michael@0: while (skipped < kSkipLimit) { michael@0: slot = readdir(procfs_dir_); michael@0: // all done looking through /proc? michael@0: if (!slot) michael@0: return false; michael@0: michael@0: // If not a process, keep looking for one. michael@0: bool notprocess = false; michael@0: int i; michael@0: for (i = 0; i < NAME_MAX && slot->d_name[i]; ++i) { michael@0: if (!isdigit(slot->d_name[i])) { michael@0: notprocess = true; michael@0: break; michael@0: } michael@0: } michael@0: if (i == NAME_MAX || notprocess) { michael@0: skipped++; michael@0: continue; michael@0: } michael@0: michael@0: // Read the process's status. michael@0: char buf[NAME_MAX + 12]; michael@0: sprintf(buf, "/proc/%s/stat", slot->d_name); michael@0: FILE *fp = fopen(buf, "r"); michael@0: if (!fp) michael@0: return false; michael@0: const char* result = fgets(buf, sizeof(buf), fp); michael@0: fclose(fp); michael@0: if (!result) michael@0: return false; michael@0: michael@0: // Parse the status. It is formatted like this: michael@0: // %d (%s) %c %d ... michael@0: // pid (name) runstate ppid michael@0: // To avoid being fooled by names containing a closing paren, scan michael@0: // backwards. michael@0: openparen = strchr(buf, '('); michael@0: closeparen = strrchr(buf, ')'); michael@0: if (!openparen || !closeparen) michael@0: return false; michael@0: char runstate = closeparen[2]; michael@0: michael@0: // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped? michael@0: // Allowed values: D R S T Z michael@0: if (runstate != 'Z') michael@0: break; michael@0: michael@0: // Nope, it's a zombie; somebody isn't cleaning up after their children. michael@0: // (e.g. WaitForProcessesToExit doesn't clean up after dead children yet.) michael@0: // There could be a lot of zombies, can't really decrement i here. michael@0: } michael@0: if (skipped >= kSkipLimit) { michael@0: NOTREACHED(); michael@0: return false; michael@0: } michael@0: michael@0: entry_.pid = atoi(slot->d_name); michael@0: entry_.ppid = atoi(closeparen + 3); michael@0: michael@0: // TODO(port): read pid's commandline's $0, like killall does. Using the michael@0: // short name between openparen and closeparen won't work for long names! michael@0: int len = closeparen - openparen - 1; michael@0: if (len > NAME_MAX) michael@0: len = NAME_MAX; michael@0: memcpy(entry_.szExeFile, openparen + 1, len); michael@0: entry_.szExeFile[len] = 0; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool NamedProcessIterator::IncludeEntry() { michael@0: // TODO(port): make this also work for non-ASCII filenames michael@0: if (WideToASCII(executable_name_) != entry_.szExeFile) michael@0: return false; michael@0: if (!filter_) michael@0: return true; michael@0: return filter_->Includes(entry_.pid, entry_.ppid); michael@0: } michael@0: michael@0: } // namespace base