// 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/linux/ptrace_client.h" #include #include #include #include #include "base/cxx17_backports.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "util/file/file_io.h" #include "util/linux/ptrace_broker.h" #include "util/process/process_memory_linux.h" namespace crashpad { namespace { bool ReceiveAndLogError(int sock, const std::string& operation) { ExceptionHandlerProtocol::Errno error; if (!LoggingReadFileExactly(sock, &error, sizeof(error))) { return false; } errno = error; PLOG(ERROR) << operation; return true; } bool ReceiveAndLogReadError(int sock, const std::string& operation) { PtraceBroker::ReadError err; if (!LoggingReadFileExactly(sock, &err, sizeof(err))) { return false; } switch (err) { case PtraceBroker::kReadErrorAccessDenied: LOG(ERROR) << operation << " access denied"; return true; default: if (err <= 0) { LOG(ERROR) << operation << " invalid error " << err; DCHECK(false); return false; } errno = err; PLOG(ERROR) << operation; return true; } } bool AttachImpl(int sock, pid_t tid) { PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeAttach; request.tid = tid; if (!LoggingWriteFile(sock, &request, sizeof(request))) { return false; } ExceptionHandlerProtocol::Bool success; if (!LoggingReadFileExactly(sock, &success, sizeof(success))) { return false; } if (success != ExceptionHandlerProtocol::kBoolTrue) { ReceiveAndLogError(sock, "PtraceBroker Attach"); return false; } return true; } struct Dirent64 { ino64_t d_ino; off64_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[]; }; void ReadDentsAsThreadIDs(char* buffer, size_t size, std::vector* threads) { while (size > offsetof(Dirent64, d_name)) { auto dirent = reinterpret_cast(buffer); if (size < dirent->d_reclen) { LOG(ERROR) << "short dirent"; break; } buffer += dirent->d_reclen; size -= dirent->d_reclen; const size_t max_name_length = dirent->d_reclen - offsetof(Dirent64, d_name); size_t name_len = strnlen(dirent->d_name, max_name_length); if (name_len >= max_name_length) { LOG(ERROR) << "format error"; break; } if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) { continue; } pid_t tid; if (!base::StringToInt(dirent->d_name, &tid)) { LOG(ERROR) << "format error"; continue; } threads->push_back(tid); } DCHECK_EQ(size, 0u); } } // namespace PtraceClient::PtraceClient() : PtraceConnection(), memory_(), sock_(kInvalidFileHandle), pid_(-1), is_64_bit_(false), initialized_() {} PtraceClient::~PtraceClient() { if (sock_ != kInvalidFileHandle) { PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeExit; LoggingWriteFile(sock_, &request, sizeof(request)); } } bool PtraceClient::Initialize(int sock, pid_t pid) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); sock_ = sock; pid_ = pid; if (!AttachImpl(sock_, pid_)) { return false; } PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeIs64Bit; request.tid = pid_; if (!LoggingWriteFile(sock_, &request, sizeof(request))) { return false; } ExceptionHandlerProtocol::Bool is_64_bit; if (!LoggingReadFileExactly(sock_, &is_64_bit, sizeof(is_64_bit))) { return false; } is_64_bit_ = is_64_bit == ExceptionHandlerProtocol::kBoolTrue; INITIALIZATION_STATE_SET_VALID(initialized_); return true; } pid_t PtraceClient::GetProcessID() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return pid_; } bool PtraceClient::Attach(pid_t tid) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return AttachImpl(sock_, tid); } bool PtraceClient::Is64Bit() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return is_64_bit_; } bool PtraceClient::GetThreadInfo(pid_t tid, ThreadInfo* info) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeGetThreadInfo; request.tid = tid; if (!LoggingWriteFile(sock_, &request, sizeof(request))) { return false; } PtraceBroker::GetThreadInfoResponse response; if (!LoggingReadFileExactly(sock_, &response, sizeof(response))) { return false; } if (response.success == ExceptionHandlerProtocol::kBoolTrue) { *info = response.info; return true; } ReceiveAndLogError(sock_, "PtraceBroker GetThreadInfo"); return false; } bool PtraceClient::ReadFileContents(const base::FilePath& path, std::string* contents) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeReadFile; request.path.path_length = path.value().size(); if (!LoggingWriteFile(sock_, &request, sizeof(request)) || !SendFilePath(path.value().c_str(), request.path.path_length)) { return false; } std::string local_contents; int32_t read_result; do { if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) { return false; } if (read_result < 0) { ReceiveAndLogReadError(sock_, "ReadFileContents"); return false; } if (read_result > 0) { size_t old_length = local_contents.size(); local_contents.resize(old_length + read_result); if (!LoggingReadFileExactly( sock_, &local_contents[old_length], read_result)) { return false; } } } while (read_result > 0); contents->swap(local_contents); return true; } ProcessMemory* PtraceClient::Memory() { INITIALIZATION_STATE_DCHECK_VALID(initialized_); if (!memory_) { memory_ = std::make_unique(this); } return memory_.get(); } bool PtraceClient::Threads(std::vector* threads) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); DCHECK(threads->empty()); // If the broker is unable to read thread IDs, fall-back to just the main // thread's ID. threads->push_back(pid_); char path[32]; snprintf(path, base::size(path), "/proc/%d/task", pid_); PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeListDirectory; request.path.path_length = strlen(path); if (!LoggingWriteFile(sock_, &request, sizeof(request)) || !SendFilePath(path, request.path.path_length)) { return false; } std::vector local_threads; int32_t read_result; do { if (!LoggingReadFileExactly(sock_, &read_result, sizeof(read_result))) { return false; } if (read_result < 0) { return ReceiveAndLogReadError(sock_, "Threads"); } if (read_result > 0) { auto buffer = std::make_unique(read_result); if (!LoggingReadFileExactly(sock_, buffer.get(), read_result)) { return false; } ReadDentsAsThreadIDs(buffer.get(), read_result, &local_threads); } } while (read_result > 0); threads->swap(local_threads); return true; } ssize_t PtraceClient::ReadUpTo(VMAddress address, size_t size, void* buffer) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); char* buffer_c = reinterpret_cast(buffer); PtraceBroker::Request request = {}; request.type = PtraceBroker::Request::kTypeReadMemory; request.tid = pid_; request.iov.base = address; request.iov.size = size; if (!LoggingWriteFile(sock_, &request, sizeof(request))) { return false; } ssize_t total_read = 0; while (size > 0) { int32_t bytes_read; if (!LoggingReadFileExactly(sock_, &bytes_read, sizeof(bytes_read))) { return -1; } if (bytes_read < 0) { ReceiveAndLogReadError(sock_, "PtraceBroker ReadMemory"); return -1; } if (bytes_read == 0) { return total_read; } if (!LoggingReadFileExactly(sock_, buffer_c, bytes_read)) { return -1; } size -= bytes_read; buffer_c += bytes_read; total_read += bytes_read; } return total_read; } bool PtraceClient::SendFilePath(const char* path, size_t length) { if (!LoggingWriteFile(sock_, path, length)) { return false; } PtraceBroker::OpenResult result; if (!LoggingReadFileExactly(sock_, &result, sizeof(result))) { return false; } switch (result) { case PtraceBroker::kOpenResultAccessDenied: LOG(ERROR) << "Broker Open: access denied"; return false; case PtraceBroker::kOpenResultTooLong: LOG(ERROR) << "Broker Open: path too long"; return false; case PtraceBroker::kOpenResultSuccess: return true; default: if (result < 0) { LOG(ERROR) << "Broker Open: invalid result " << result; DCHECK(false); } else { errno = result; PLOG(ERROR) << "Broker Open"; } return false; } } } // namespace crashpad