// Copyright 2017 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "util/posix/process_info.h" #include #include "base/files/file_path.h" #include "base/logging.h" #include "util/file/delimited_file_reader.h" #include "util/file/file_reader.h" #include "util/file/string_file.h" #include "util/linux/proc_stat_reader.h" #include "util/misc/lexing.h" #include "util/misc/time.h" namespace crashpad { ProcessInfo::ProcessInfo() : connection_(), supplementary_groups_(), start_time_(), pid_(-1), ppid_(-1), uid_(-1), euid_(-1), suid_(-1), gid_(-1), egid_(-1), sgid_(-1), start_time_initialized_(), initialized_() {} ProcessInfo::~ProcessInfo() {} bool ProcessInfo::InitializeWithPtrace(PtraceConnection* connection) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); DCHECK(connection); connection_ = connection; pid_ = connection->GetProcessID(); is_64_bit_ = connection->Is64Bit(); { char path[32]; snprintf(path, sizeof(path), "/proc/%d/status", pid_); std::string contents; if (!connection->ReadFileContents(base::FilePath(path), &contents)) { return false; } StringFile status_file; status_file.SetString(contents); DelimitedFileReader status_file_line_reader(&status_file); bool have_ppid = false; bool have_uids = false; bool have_gids = false; bool have_groups = false; std::string line; DelimitedFileReader::Result result; while ((result = status_file_line_reader.GetLine(&line)) == DelimitedFileReader::Result::kSuccess) { if (line.back() != '\n') { LOG(ERROR) << "format error: unterminated line at EOF"; return false; } bool understood_line = false; const char* line_c = line.c_str(); if (AdvancePastPrefix(&line_c, "PPid:\t")) { if (have_ppid) { LOG(ERROR) << "format error: multiple PPid lines"; return false; } have_ppid = AdvancePastNumber(&line_c, &ppid_); if (!have_ppid) { LOG(ERROR) << "format error: unrecognized PPid format"; return false; } understood_line = true; } else if (AdvancePastPrefix(&line_c, "Uid:\t")) { if (have_uids) { LOG(ERROR) << "format error: multiple Uid lines"; return false; } uid_t fsuid; have_uids = AdvancePastNumber(&line_c, &uid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &euid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &suid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &fsuid); if (!have_uids) { LOG(ERROR) << "format error: unrecognized Uid format"; return false; } understood_line = true; } else if (AdvancePastPrefix(&line_c, "Gid:\t")) { if (have_gids) { LOG(ERROR) << "format error: multiple Gid lines"; return false; } gid_t fsgid; have_gids = AdvancePastNumber(&line_c, &gid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &egid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &sgid_) && AdvancePastPrefix(&line_c, "\t") && AdvancePastNumber(&line_c, &fsgid); if (!have_gids) { LOG(ERROR) << "format error: unrecognized Gid format"; return false; } understood_line = true; } else if (AdvancePastPrefix(&line_c, "Groups:\t")) { if (have_groups) { LOG(ERROR) << "format error: multiple Groups lines"; return false; } if (!AdvancePastPrefix(&line_c, " ")) { // In Linux 4.10, even an empty Groups: line has a trailing space. gid_t group; while (AdvancePastNumber(&line_c, &group)) { supplementary_groups_.insert(group); if (!AdvancePastPrefix(&line_c, " ")) { LOG(ERROR) << "format error: unrecognized Groups format"; return false; } } } have_groups = true; understood_line = true; } if (understood_line && line_c != &line.back()) { LOG(ERROR) << "format error: unconsumed trailing data"; return false; } } if (result != DelimitedFileReader::Result::kEndOfFile) { return false; } if (!have_ppid || !have_uids || !have_gids || !have_groups) { LOG(ERROR) << "format error: missing fields"; return false; } } INITIALIZATION_STATE_SET_VALID(initialized_); return true; } pid_t ProcessInfo::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return pid_; } pid_t ProcessInfo::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return ppid_; } uid_t ProcessInfo::RealUserID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return uid_; } uid_t ProcessInfo::EffectiveUserID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return euid_; } uid_t ProcessInfo::SavedUserID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return suid_; } gid_t ProcessInfo::RealGroupID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return gid_; } gid_t ProcessInfo::EffectiveGroupID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return egid_; } gid_t ProcessInfo::SavedGroupID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return sgid_; } std::set ProcessInfo::SupplementaryGroups() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return supplementary_groups_; } std::set ProcessInfo::AllGroups() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); std::set all_groups = SupplementaryGroups(); all_groups.insert(RealGroupID()); all_groups.insert(EffectiveGroupID()); all_groups.insert(SavedGroupID()); return all_groups; } bool ProcessInfo::DidChangePrivileges() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); // TODO(jperaza): Is this possible to determine? return false; } bool ProcessInfo::Is64Bit() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return is_64_bit_; } bool ProcessInfo::StartTime(timeval* start_time) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (start_time_initialized_.is_uninitialized()) { start_time_initialized_.set_invalid(); ProcStatReader reader; if (!reader.Initialize(connection_, pid_)) { return false; } timespec boot_time_ts; if (!GetBootTime(&boot_time_ts)) { return false; } timeval boot_time; TimespecToTimeval(boot_time_ts, &boot_time); if (!reader.StartTime(boot_time, &start_time_)) { return false; } start_time_initialized_.set_valid(); } if (!start_time_initialized_.is_valid()) { return false; } *start_time = start_time_; return true; } bool ProcessInfo::Arguments(std::vector* argv) const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); char path[32]; snprintf(path, sizeof(path), "/proc/%d/cmdline", pid_); std::string contents; if (!connection_->ReadFileContents(base::FilePath(path), &contents)) { return false; } StringFile cmdline_file; cmdline_file.SetString(contents); DelimitedFileReader cmdline_file_field_reader(&cmdline_file); std::vector local_argv; std::string argument; DelimitedFileReader::Result result; while ((result = cmdline_file_field_reader.GetDelim('\0', &argument)) == DelimitedFileReader::Result::kSuccess) { if (argument.back() != '\0') { LOG(ERROR) << "format error"; return false; } argument.pop_back(); local_argv.push_back(argument); } if (result != DelimitedFileReader::Result::kEndOfFile) { return false; } argv->swap(local_argv); return true; } } // namespace crashpad