| // 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 |