blob: 3ffd96aba0172d73de99059357a012fe0da730e6 [file] [log] [blame]
// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// 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 <stdio.h>
#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<gid_t> ProcessInfo::SupplementaryGroups() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
return supplementary_groups_;
}
std::set<gid_t> ProcessInfo::AllGroups() const {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
std::set<gid_t> 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<std::string>* 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<std::string> 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