| // 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 <errno.h> |
| #include <stdio.h> |
| |
| #include <string> |
| |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "nb/cpp14oncpp11.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<pid_t>* threads) { |
| while (size > offsetof(Dirent64, d_name)) { |
| auto dirent = reinterpret_cast<Dirent64*>(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, bool try_direct_memory) { |
| 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; |
| |
| if (try_direct_memory) { |
| auto direct_mem = std::make_unique<ProcessMemoryLinux>(); |
| if (direct_mem->Initialize(pid)) { |
| memory_.reset(direct_mem.release()); |
| } |
| } |
| if (!memory_) { |
| memory_ = std::make_unique<BrokeredMemory>(this); |
| } |
| |
| 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_); |
| return memory_.get(); |
| } |
| |
| bool PtraceClient::Threads(std::vector<pid_t>* 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<pid_t> 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<char[]>(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; |
| } |
| |
| PtraceClient::BrokeredMemory::BrokeredMemory(PtraceClient* client) |
| : ProcessMemory(), client_(client) {} |
| |
| PtraceClient::BrokeredMemory::~BrokeredMemory() = default; |
| |
| ssize_t PtraceClient::BrokeredMemory::ReadUpTo(VMAddress address, |
| size_t size, |
| void* buffer) const { |
| return client_->ReadUpTo(address, size, buffer); |
| } |
| |
| ssize_t PtraceClient::ReadUpTo(VMAddress address, |
| size_t size, |
| void* buffer) const { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| char* buffer_c = reinterpret_cast<char*>(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 |