crashpad/util/linux/ptrace_broker.cc
Joshua Peraza 20cbfa4971 linux: Use mmap for attachments in PtraceBroker
The broker attempts to use sbrk() to allocate memory to track ptrace
attachments. If the process failed due to an OOM, this system call might
fail, the broker falls back to saving attachments on the stack, and then
overruns the stack.

This change updates the broker to use sys_mmap() instead of sbrk(),
which is expected to work at least as well. If sys_mmap() fails or
the first mapped page is exhausted, further attachments fail without
attempting to save them to the stack.

Bug: chromium:1128441
Change-Id: Ibffaa986403adaf3178ee77e6d210053fbf60f26
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2488280
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
2020-12-03 23:24:55 +00:00

423 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/posix/eintr_wrapper.h"
#include "base/process/process_metrics.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() {
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_;
DISALLOW_COPY_AND_ASSIGN(AttachmentsArray);
};
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