// Copyright 2019 Google LLC // // 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 // // https://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 "sandboxed_api/sandbox2/unwind/ptrace_hook.h" #include // For NT_PRSTATUS #include #include #include #include #include #include #include #include #include #include "sandboxed_api/sandbox2/util/syscall_trap.h" // Android doesn't use an enum for __ptrace_request, use int instead. #if defined(__ANDROID__) using PtraceRequest = int; #else using PtraceRequest = __ptrace_request; #endif namespace sandbox2 { namespace { // Register size is `long` for the supported architectures according to the // kernel. using RegType = long; // NOLINT constexpr size_t kRegSize = sizeof(RegType); // Contains the register values in a ptrace specified format. This format is // pretty opaque which is why we just forward the raw bytes (up to a certain // limit). auto* g_registers = new std::vector(); pid_t g_pid; int g_mem_fd; // Hooks ptrace. // This wrapper makes use of process_vm_readv to read process memory instead of // issuing ptrace syscalls. Accesses to registers will be emulated, for this the // register values should be set via EnablePtraceEmulationWithUserRegs(). long int ptrace_hook( // NOLINT PtraceRequest request, pid_t pid, void* addr, void* data) { switch (request) { case PTRACE_PEEKDATA: { if (pid != g_pid) { return -1; } RegType read_data; if (pread(g_mem_fd, &read_data, sizeof(read_data), reinterpret_cast(addr)) != sizeof(read_data)) { return -1; } *reinterpret_cast(data) = read_data; break; } case PTRACE_PEEKUSER: { // Make sure read is in-bounds and aligned. auto offset = reinterpret_cast(addr); if (offset + kRegSize > g_registers->size() * kRegSize || offset % kRegSize != 0) { return -1; } *reinterpret_cast(data) = (*g_registers)[offset / kRegSize]; break; } case PTRACE_GETREGSET: { // Only return general-purpose registers. if (auto kind = reinterpret_cast(addr); kind != NT_PRSTATUS) { return -1; } auto reg_set = reinterpret_cast(data); if (reg_set->iov_len > g_registers->size() * kRegSize) { return -1; } memcpy(reg_set->iov_base, g_registers->data(), reg_set->iov_len); break; } default: fprintf(stderr, "ptrace_hook(): operation not permitted: %d\n", request); abort(); } return 0; } } // namespace void EnablePtraceEmulationWithUserRegs(pid_t pid, absl::string_view regs, int mem_fd) { g_pid = pid; g_mem_fd = mem_fd; g_registers->resize((regs.size() + 1) / kRegSize); memcpy(&g_registers->front(), regs.data(), regs.size()); SyscallTrap::Install([](int nr, SyscallTrap::Args args, uintptr_t* rv) { if (nr != __NR_ptrace) { return false; } *rv = ptrace_hook( static_cast(args[0]), static_cast(args[1]), reinterpret_cast(args[2]), reinterpret_cast(args[3])); return true; }); } } // namespace sandbox2