crashpad/util/linux/ptrace_broker.cc
Peter Boström 1aa478d161 Remove DISALLOW_* macros in crashpad
This change was partially scripted and partially done manually with vim
regex + manually placing the deleted constructors.

The script change looked for destructors in the public: section of a
class, if that existed the deleted constructors would go before the
destructor.

For manual placement I looked for any constructor in the public: section
of the corresponding class. If there wasn't one, then it would ideally
have gone as the first entry except below enums, classes and typedefs.
This may not have been perfect, but is hopefully good enough. Fingers
crossed.

#include "base/macros.h" is removed from files that don't use
ignore_result, which is the only other thing defined in base/macros.h.

Bug: chromium:1010217
Change-Id: I099526255a40b1ac1264904b4ece2f3f503c9418
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3171034
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Peter Boström <pbos@chromium.org>
2021-09-21 15:09:44 +00:00

424 lines
11 KiB
C++

// 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_broker.h"
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <sys/mman.h>
#include <syscall.h>
#include <unistd.h>
#include <algorithm>
#include "base/check_op.h"
#include "base/memory/page_size.h"
#include "base/posix/eintr_wrapper.h"
#include "third_party/lss/lss.h"
#include "util/linux/scoped_ptrace_attach.h"
#include "util/misc/memory_sanitizer.h"
#include "util/posix/scoped_mmap.h"
namespace crashpad {
namespace {
size_t FormatPID(char* buffer, pid_t pid) {
DCHECK_GE(pid, 0);
char pid_buf[16];
size_t length = 0;
do {
DCHECK_LT(length, sizeof(pid_buf));
pid_buf[length] = '0' + pid % 10;
pid /= 10;
++length;
} while (pid > 0);
for (size_t index = 0; index < length; ++index) {
buffer[index] = pid_buf[length - index - 1];
}
return length;
}
} // namespace
class PtraceBroker::AttachmentsArray {
public:
AttachmentsArray() : allocation_(false), attach_count_(0) {}
AttachmentsArray(const AttachmentsArray&) = delete;
AttachmentsArray& operator=(const AttachmentsArray&) = delete;
~AttachmentsArray() {
for (size_t index = 0; index < attach_count_; ++index) {
PtraceDetach(Attachments()[index], false);
}
}
bool Initialize() {
return allocation_.ResetMmap(nullptr,
base::GetPageSize(),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
}
bool Attach(pid_t pid) {
pid_t* attach = AllocateAttachment();
if (!attach || !PtraceAttach(pid, false)) {
return false;
}
*attach = pid;
return true;
}
private:
pid_t* AllocateAttachment() {
if (attach_count_ >= (allocation_.len() / sizeof(pid_t))) {
return nullptr;
}
return &Attachments()[attach_count_++];
}
pid_t* Attachments() { return allocation_.addr_as<pid_t*>(); }
ScopedMmap allocation_;
size_t attach_count_;
};
PtraceBroker::PtraceBroker(int sock, pid_t pid, bool is_64_bit)
: ptracer_(is_64_bit, /* can_log= */ false),
file_root_(file_root_buffer_),
memory_file_(),
sock_(sock),
memory_pid_(pid),
tried_opening_mem_file_(false) {
static constexpr char kProc[] = "/proc/";
size_t root_length = strlen(kProc);
memcpy(file_root_buffer_, kProc, root_length);
if (pid >= 0) {
root_length += FormatPID(file_root_buffer_ + root_length, pid);
DCHECK_LT(root_length, sizeof(file_root_buffer_));
file_root_buffer_[root_length] = '/';
++root_length;
}
DCHECK_LT(root_length, sizeof(file_root_buffer_));
file_root_buffer_[root_length] = '\0';
}
PtraceBroker::~PtraceBroker() = default;
void PtraceBroker::SetFileRoot(const char* new_root) {
DCHECK_EQ(new_root[strlen(new_root) - 1], '/');
memory_pid_ = -1;
file_root_ = new_root;
}
int PtraceBroker::Run() {
AttachmentsArray attachments;
attachments.Initialize();
return RunImpl(&attachments);
}
int PtraceBroker::RunImpl(AttachmentsArray* attachments) {
while (true) {
Request request = {};
if (!ReadFileExactly(sock_, &request, sizeof(request))) {
return errno;
}
if (request.version != Request::kVersion) {
return EINVAL;
}
switch (request.type) {
case Request::kTypeAttach: {
ExceptionHandlerProtocol::Bool status =
attachments->Attach(request.tid)
? ExceptionHandlerProtocol::kBoolTrue
: ExceptionHandlerProtocol::kBoolFalse;
if (!WriteFile(sock_, &status, sizeof(status))) {
return errno;
}
if (status == ExceptionHandlerProtocol::kBoolFalse) {
ExceptionHandlerProtocol::Errno error = errno;
if (!WriteFile(sock_, &error, sizeof(error))) {
return errno;
}
}
continue;
}
case Request::kTypeIs64Bit: {
ExceptionHandlerProtocol::Bool is_64_bit =
ptracer_.Is64Bit() ? ExceptionHandlerProtocol::kBoolTrue
: ExceptionHandlerProtocol::kBoolFalse;
if (!WriteFile(sock_, &is_64_bit, sizeof(is_64_bit))) {
return errno;
}
continue;
}
case Request::kTypeGetThreadInfo: {
GetThreadInfoResponse response;
response.success = ptracer_.GetThreadInfo(request.tid, &response.info)
? ExceptionHandlerProtocol::kBoolTrue
: ExceptionHandlerProtocol::kBoolFalse;
if (!WriteFile(sock_, &response, sizeof(response))) {
return errno;
}
if (response.success == ExceptionHandlerProtocol::kBoolFalse) {
ExceptionHandlerProtocol::Errno error = errno;
if (!WriteFile(sock_, &error, sizeof(error))) {
return errno;
}
}
continue;
}
case Request::kTypeReadFile: {
ScopedFileHandle handle;
int result = ReceiveAndOpenFilePath(request.path.path_length,
/* is_directory= */ false,
&handle);
if (result != 0) {
return result;
}
if (!handle.is_valid()) {
continue;
}
result = SendFileContents(handle.get());
if (result != 0) {
return result;
}
continue;
}
case Request::kTypeReadMemory: {
int result =
SendMemory(request.tid, request.iov.base, request.iov.size);
if (result != 0) {
return result;
}
continue;
}
case Request::kTypeListDirectory: {
ScopedFileHandle handle;
int result = ReceiveAndOpenFilePath(request.path.path_length,
/* is_directory= */ true,
&handle);
if (result != 0) {
return result;
}
if (!handle.is_valid()) {
continue;
}
result = SendDirectory(handle.get());
if (result != 0) {
return result;
}
continue;
}
case Request::kTypeExit:
return 0;
}
DCHECK(false);
return EINVAL;
}
}
int PtraceBroker::SendError(ExceptionHandlerProtocol::Errno err) {
return WriteFile(sock_, &err, sizeof(err)) ? 0 : errno;
}
int PtraceBroker::SendReadError(ReadError error) {
int32_t rv = -1;
return WriteFile(sock_, &rv, sizeof(rv)) &&
WriteFile(sock_, &error, sizeof(error))
? 0
: errno;
}
int PtraceBroker::SendOpenResult(OpenResult result) {
return WriteFile(sock_, &result, sizeof(result)) ? 0 : errno;
}
int PtraceBroker::SendFileContents(FileHandle handle) {
char buffer[4096];
int32_t rv;
do {
rv = ReadFile(handle, buffer, sizeof(buffer));
if (rv < 0) {
return SendReadError(static_cast<ReadError>(errno));
}
if (!WriteFile(sock_, &rv, sizeof(rv))) {
return errno;
}
if (rv > 0) {
if (!WriteFile(sock_, buffer, static_cast<size_t>(rv))) {
return errno;
}
}
} while (rv > 0);
return 0;
}
void PtraceBroker::TryOpeningMemFile() {
if (tried_opening_mem_file_) {
return;
}
tried_opening_mem_file_ = true;
if (memory_pid_ < 0) {
return;
}
char mem_path[32];
size_t root_length = strlen(file_root_buffer_);
static constexpr char kMem[] = "mem";
DCHECK_LT(root_length + strlen(kMem) + 1, sizeof(mem_path));
memcpy(mem_path, file_root_buffer_, root_length);
// Include the trailing NUL.
memcpy(mem_path + root_length, kMem, strlen(kMem) + 1);
memory_file_.reset(
HANDLE_EINTR(open(mem_path, O_RDONLY | O_CLOEXEC | O_NOCTTY)));
}
int PtraceBroker::SendMemory(pid_t pid, VMAddress address, VMSize size) {
if (memory_pid_ >= 0 && pid != memory_pid_) {
return SendReadError(kReadErrorAccessDenied);
}
TryOpeningMemFile();
auto read_memory = [this, pid](VMAddress address, size_t size, char* buffer) {
return this->memory_file_.is_valid()
? HANDLE_EINTR(
pread64(this->memory_file_.get(), buffer, size, address))
: this->ptracer_.ReadUpTo(pid, address, size, buffer);
};
char buffer[4096];
while (size > 0) {
size_t to_read = std::min(size, VMSize{sizeof(buffer)});
int32_t bytes_read = read_memory(address, to_read, buffer);
if (bytes_read < 0) {
return SendReadError(static_cast<ReadError>(errno));
}
if (!WriteFile(sock_, &bytes_read, sizeof(bytes_read))) {
return errno;
}
if (bytes_read == 0) {
return 0;
}
if (!WriteFile(sock_, buffer, bytes_read)) {
return errno;
}
size -= bytes_read;
address += bytes_read;
}
return 0;
}
#if defined(MEMORY_SANITIZER)
// MSan doesn't intercept syscall() and doesn't see that buffer is initialized.
__attribute__((no_sanitize("memory")))
#endif // defined(MEMORY_SANITIZER)
int PtraceBroker::SendDirectory(FileHandle handle) {
char buffer[4096];
int rv;
do {
rv = syscall(SYS_getdents64, handle, buffer, sizeof(buffer));
if (rv < 0) {
return SendReadError(static_cast<ReadError>(errno));
}
if (!WriteFile(sock_, &rv, sizeof(rv))) {
return errno;
}
if (rv > 0) {
if (!WriteFile(sock_, buffer, static_cast<size_t>(rv))) {
return errno;
}
}
} while (rv > 0);
return 0;
}
int PtraceBroker::ReceiveAndOpenFilePath(VMSize path_length,
bool is_directory,
ScopedFileHandle* handle) {
char path[std::max(4096, PATH_MAX)];
if (path_length >= sizeof(path)) {
return SendOpenResult(kOpenResultTooLong);
}
if (!ReadFileExactly(sock_, path, path_length)) {
return errno;
}
path[path_length] = '\0';
if (strncmp(path, file_root_, strlen(file_root_)) != 0) {
return SendOpenResult(kOpenResultAccessDenied);
}
int flags = O_RDONLY | O_CLOEXEC | O_NOCTTY;
if (is_directory) {
flags |= O_DIRECTORY;
}
ScopedFileHandle local_handle(HANDLE_EINTR(open(path, flags)));
if (!local_handle.is_valid()) {
return SendOpenResult(static_cast<OpenResult>(errno));
}
handle->reset(local_handle.release());
return SendOpenResult(kOpenResultSuccess);
}
} // namespace crashpad