crashpad/util/linux/ptrace_client.cc
Lei Zhang c63c073d27 Do IWYU for check_op.h
Include check_op.h directly, instead of relying on the transitive
include from logging.h. This transitive include does not exist in
Chromium's //base.

Change-Id: I15962a9cdc26ac206032157b8d2659cf263ad695
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4950200
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
2023-10-18 20:01:37 +00:00

387 lines
9.7 KiB
C++

// 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/linux/ptrace_client.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <iterator>
#include <string>
#include "base/check_op.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<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) {
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;
}
ProcessMemoryLinux* PtraceClient::Memory() {
INITIALIZATION_STATE_DCHECK_VALID(initialized_);
if (!memory_) {
memory_ = std::make_unique<ProcessMemoryLinux>(this);
}
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, std::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;
}
ssize_t PtraceClient::ReadUpTo(VMAddress address, size_t size, void* buffer) {
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 (static_cast<size_t>(bytes_read) > size) {
LOG(ERROR) << "invalid size " << bytes_read;
return -1;
}
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