diff --git a/util/posix/process_info.h b/util/posix/process_info.h index 1aa23d8c..10cee96d 100644 --- a/util/posix/process_info.h +++ b/util/posix/process_info.h @@ -141,6 +141,18 @@ class ProcessInfo { private: #if defined(OS_MACOSX) kinfo_proc kern_proc_info_; +#elif defined(OS_LINUX) || defined(OS_ANDROID) + std::set supplementary_groups_; + timeval start_time_; + pid_t pid_; + pid_t ppid_; + uid_t uid_; + uid_t euid_; + uid_t suid_; + gid_t gid_; + gid_t egid_; + gid_t sgid_; + bool is_64_bit_; #endif InitializationStateDcheck initialized_; diff --git a/util/posix/process_info_linux.cc b/util/posix/process_info_linux.cc new file mode 100644 index 00000000..9921b65b --- /dev/null +++ b/util/posix/process_info_linux.cc @@ -0,0 +1,443 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "util/file/file_reader.h" +#include "util/string/split_string.h" + +namespace crashpad { + +namespace { + +// If the string |pattern| is matched exactly at the start of |input|, advance +// |input| past |pattern| and return true. +bool AdvancePastPrefix(const char** input, const char* pattern) { + size_t length = strlen(pattern); + if (strncmp(*input, pattern, length) == 0) { + *input += length; + return true; + } + return false; +} + +#define MAKE_ADAPTER(type, function) \ + bool ConvertStringToNumber(const base::StringPiece& input, type* value) { \ + return function(input, value); \ + } +MAKE_ADAPTER(int, base::StringToInt) +MAKE_ADAPTER(unsigned int, base::StringToUint) +MAKE_ADAPTER(uint64_t, base::StringToUint64) +#undef MAKE_ADAPTER + +// Attempt to convert a prefix of |input| to numeric type T. On success, set +// |value| to the number, advance |input| past the number, and return true. +template +bool AdvancePastNumber(const char** input, T* value) { + size_t length = 0; + if (std::numeric_limits::is_signed && **input == '-') { + ++length; + } + while (isdigit((*input)[length])) { + ++length; + } + bool success = ConvertStringToNumber(base::StringPiece(*input, length), + value); + if (success) { + *input += length; + return true; + } + return false; +} + +struct BufferFreer { + void operator()(char** buffer_ptr) const { + free(*buffer_ptr); + *buffer_ptr = nullptr; + } +}; +using ScopedBufferPtr = std::unique_ptr; + +bool ReadEntireFile(const char* path, std::string* contents) { + FileReader file; + if (!file.Open(base::FilePath(path))) { + return false; + } + + char buffer[4096]; + FileOperationResult length; + while ((length = file.Read(buffer, sizeof(buffer))) > 0) { + contents->append(buffer, length); + } + return length >= 0; +} + +void SubtractTimespec(const timespec& t1, const timespec& t2, + timespec* result) { + result->tv_sec = t1.tv_sec - t2.tv_sec; + result->tv_nsec = t1.tv_nsec - t2.tv_nsec; + if (result->tv_nsec < 0) { + result->tv_sec -= 1; + result->tv_nsec += static_cast(1E9); + } +} + +void TimespecToTimeval(const timespec& ts, timeval* tv) { + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; +} + +} // namespace + +ProcessInfo::ProcessInfo() + : supplementary_groups_(), + start_time_(), + pid_(-1), + ppid_(-1), + uid_(-1), + euid_(-1), + suid_(-1), + gid_(-1), + egid_(-1), + sgid_(-1), + is_64_bit_(false), + initialized_() {} + +ProcessInfo::~ProcessInfo() {} + +bool ProcessInfo::Initialize(pid_t pid) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + pid_ = pid; + + { + char path[32]; + snprintf(path, sizeof(path), "/proc/%d/status", pid_); + base::ScopedFILE status_file(fopen(path, "re")); + if (!status_file.get()) { + PLOG(ERROR) << "fopen " << path; + return false; + } + + size_t buffer_size = 0; + char* buffer = nullptr; + ScopedBufferPtr buffer_owner(&buffer); + + bool have_ppid = false; + bool have_uids = false; + bool have_gids = false; + bool have_groups = false; + ssize_t len; + while ((len = getline(&buffer, &buffer_size, status_file.get())) > 0) { + const char* line = buffer; + + if (AdvancePastPrefix(&line, "PPid:\t")) { + if (have_ppid) { + LOG(ERROR) << "format error: multiple PPid lines"; + return false; + } + have_ppid = AdvancePastNumber(&line, &ppid_); + if (!have_ppid) { + LOG(ERROR) << "format error: unrecognized PPid format"; + return false; + } + } else if (AdvancePastPrefix(&line, "Uid:\t")) { + if (have_uids) { + LOG(ERROR) << "format error: multiple Uid lines"; + return false; + } + have_uids = + AdvancePastNumber(&line, &uid_) && + AdvancePastPrefix(&line, "\t") && + AdvancePastNumber(&line, &euid_) && + AdvancePastPrefix(&line, "\t") && + AdvancePastNumber(&line, &suid_); + if (!have_uids) { + LOG(ERROR) << "format error: unrecognized Uid format"; + return false; + } + } else if (AdvancePastPrefix(&line, "Gid:\t")) { + if (have_gids) { + LOG(ERROR) << "format error: multiple Gid lines"; + return false; + } + have_gids = + AdvancePastNumber(&line, &gid_) && + AdvancePastPrefix(&line, "\t") && + AdvancePastNumber(&line, &egid_) && + AdvancePastPrefix(&line, "\t") && + AdvancePastNumber(&line, &sgid_); + if (!have_gids) { + LOG(ERROR) << "format error: unrecognized Gid format"; + return false; + } + } else if (AdvancePastPrefix(&line, "Groups:\t")) { + if (have_groups) { + LOG(ERROR) << "format error: multiple Groups lines"; + return false; + } + gid_t group; + while (AdvancePastNumber(&line, &group)) { + supplementary_groups_.insert(group); + if (!AdvancePastPrefix(&line, " ")) { + LOG(ERROR) << "format error"; + return false; + } + } + if (!AdvancePastPrefix(&line, "\n") || line != buffer + len) { + LOG(ERROR) << "format error: unrecognized Groups format"; + return false; + } + have_groups = true; + } + } + if (!feof(status_file.get())) { + PLOG(ERROR) << "getline"; + return false; + } + if (!have_ppid || !have_uids || !have_gids || !have_groups) { + LOG(ERROR) << "format error: missing fields"; + return false; + } + } + + { + char path[32]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid_); + std::string stat_contents; + if (!ReadEntireFile(path, &stat_contents)) { + return false; + } + + // The process start time is the 22nd column. + // The second column is the executable name in parentheses. + // The executable name may have parentheses itself, so find the end of the + // second column by working backwards to find the last closing parens and + // then count forward to the 22nd column. + size_t stat_pos = stat_contents.rfind(')'); + if (stat_pos == std::string::npos) { + LOG(ERROR) << "format error"; + return false; + } + + for (int index = 1; + index < 21 && stat_pos < stat_contents.size(); + ++index) { + stat_pos = stat_contents.find(" ", stat_pos); + ++stat_pos; + } + + const char* ticks_ptr = stat_contents.substr(stat_pos).c_str(); + + // start time is in jiffies instead of clock ticks pre 2.6. + uint64_t ticks_after_boot; + if (!AdvancePastNumber(&ticks_ptr, &ticks_after_boot)) { + LOG(ERROR) << "format error"; + return false; + } + long clock_ticks_per_s = sysconf(_SC_CLK_TCK); + if (clock_ticks_per_s <= 0) { + PLOG(ERROR) << "sysconf"; + return false; + } + timeval time_after_boot; + time_after_boot.tv_sec = ticks_after_boot / clock_ticks_per_s; + time_after_boot.tv_usec = + (ticks_after_boot % clock_ticks_per_s) * + (static_cast(1E6) / clock_ticks_per_s); + + timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) { + PLOG(ERROR) << "clock_gettime"; + return false; + } + + timespec current_time; + if (clock_gettime(CLOCK_REALTIME, ¤t_time) != 0) { + PLOG(ERROR) << "clock_gettime"; + return false; + } + + timespec boot_time_ts; + SubtractTimespec(current_time, uptime, &boot_time_ts); + timeval boot_time_tv; + TimespecToTimeval(boot_time_ts, &boot_time_tv); + timeradd(&boot_time_tv, &time_after_boot, &start_time_); + } + +#if defined(ARCH_CPU_64_BITS) + const bool am_64_bit = true; +#else + const bool am_64_bit = false; +#endif + + if (pid_ == getpid()) { + is_64_bit_ = am_64_bit; + } else { + if (ptrace(PTRACE_ATTACH, pid_, nullptr, nullptr) != 0) { + PLOG(ERROR) << "ptrace"; + return false; + } + + if (HANDLE_EINTR(waitpid(pid_, nullptr, __WALL)) < 0) { + PLOG(ERROR) << "waitpid"; + return false; + } + + // Allocate more buffer space than is required to hold registers for this + // process. If the kernel fills the extra space, the target process uses + // more/larger registers than this process. If the kernel fills less space + // than sizeof(regs) then the target process uses smaller/fewer registers. + struct { +#if defined(ARCH_CPU_X86_FAMILY) + using PrStatusType = user_regs_struct; +#elif defined(ARCH_CPU_ARMEL) + using PrStatusType = pt_regs; +#elif defined(ARCH_CPU_ARM64) + using PrStatusType = user_pt_regs; +#endif + PrStatusType regs; + char extra; + } regbuf; + + iovec iov; + iov.iov_base = ®buf; + iov.iov_len = sizeof(regbuf); + if (ptrace(PTRACE_GETREGSET, + pid_, + reinterpret_cast(NT_PRSTATUS), + &iov) != 0) { + PLOG(ERROR) << "ptrace"; + return false; + } + + is_64_bit_ = am_64_bit == (iov.iov_len == sizeof(regbuf.regs)); + + if (ptrace(PTRACE_DETACH, pid_, nullptr, nullptr) != 0) { + PLOG(ERROR) << "ptrace"; + } + } + + 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_; +} + +void ProcessInfo::StartTime(timeval* start_time) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *start_time = start_time_; +} + +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 command; + if (!ReadEntireFile(path, &command)) { + return false; + } + + if (command.size() == 0 || command.back() != '\0') { + LOG(ERROR) << "format error"; + return false; + } + + command.pop_back(); + std::vector local_argv = SplitString(command, '\0'); + argv->swap(local_argv); + return true; +} + +} // namespace crashpad diff --git a/util/posix/process_info_test.cc b/util/posix/process_info_test.cc index c07b1ef7..6b693966 100644 --- a/util/posix/process_info_test.cc +++ b/util/posix/process_info_test.cc @@ -32,14 +32,15 @@ #include #endif +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include +#endif + namespace crashpad { namespace test { namespace { -void TestSelfProcess(const ProcessInfo& process_info) { - EXPECT_EQ(getpid(), process_info.ProcessID()); - EXPECT_EQ(getppid(), process_info.ParentProcessID()); - +void TestProcessClone(const ProcessInfo& process_info) { // There’s no system call to obtain the saved set-user ID or saved set-group // ID in an easy way. Normally, they are the same as the effective user ID and // effective group ID, so just check against those. @@ -47,6 +48,7 @@ void TestSelfProcess(const ProcessInfo& process_info) { const uid_t euid = geteuid(); EXPECT_EQ(euid, process_info.EffectiveUserID()); EXPECT_EQ(euid, process_info.SavedUserID()); + const gid_t gid = getgid(); EXPECT_EQ(gid, process_info.RealGroupID()); const gid_t egid = getegid(); @@ -141,6 +143,12 @@ void TestSelfProcess(const ProcessInfo& process_info) { EXPECT_EQ(std::string(expect_argv[0]), argv[0]); } +void TestSelfProcess(const ProcessInfo& process_info) { + EXPECT_EQ(getpid(), process_info.ProcessID()); + EXPECT_EQ(getppid(), process_info.ParentProcessID()); + + TestProcessClone(process_info); +} TEST(ProcessInfo, Self) { ProcessInfo process_info; @@ -156,6 +164,9 @@ TEST(ProcessInfo, SelfTask) { } #endif +// Applications cannot ptrace PID 1 on Android, which is required for Initialize +// to succeed. +#if !defined(OS_ANDROID) TEST(ProcessInfo, Pid1) { // PID 1 is expected to be init or the system’s equivalent. This tests reading // information about another process. @@ -172,6 +183,29 @@ TEST(ProcessInfo, Pid1) { EXPECT_EQ(implicit_cast(0), process_info.SavedGroupID()); EXPECT_FALSE(process_info.AllGroups().empty()); } +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST(ProcessInfo, ForkedSelf) { + ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)) << ErrnoMessage("prctl"); + + pid_t pid = fork(); + if (pid == 0) { + raise(SIGSTOP); + _exit(0); + } + ASSERT_GE(pid, 0) << ErrnoMessage("fork"); + + ProcessInfo process_info; + ASSERT_TRUE(process_info.Initialize(pid)); + + EXPECT_EQ(pid, process_info.ProcessID()); + EXPECT_EQ(getpid(), process_info.ParentProcessID()); + + TestProcessClone(process_info); + kill(pid, SIGKILL); +} +#endif } // namespace } // namespace test diff --git a/util/util.gyp b/util/util.gyp index f1f004c7..c715233b 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -135,6 +135,7 @@ 'posix/drop_privileges.cc', 'posix/drop_privileges.h', 'posix/process_info.h', + 'posix/process_info_linux.cc', 'posix/process_info_mac.cc', 'posix/signals.cc', 'posix/signals.h', @@ -313,6 +314,13 @@ ], }], ], + 'target_conditions': [ + ['OS=="android"', { + 'sources/': [ + ['include', '^posix/process_info_linux\\.cc$'], + ], + }], + ], }, ], } diff --git a/util/util_test.gyp b/util/util_test.gyp index a33e474e..45ac6bd5 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -120,6 +120,18 @@ ], }, }], + ['OS=="android"', { + # Things not yet ported to Android + 'sources/' : [ + ['exclude', '^net/http_transport_test\\.cc$'], + ] + }], + ['OS=="android" or OS=="linux"' , { + # Things not yet ported to Android or Linux + 'sources/' : [ + ['exclude', '^numeric/checked_address_range_test\\.cc$'], + ] + }], ], }, ],