/* * Copyright (c) 2020, Itamar S. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include namespace Ptrace { KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, Process& caller) { ScopedSpinLock scheduler_lock(g_scheduler_lock); if (params.request == PT_TRACE_ME) { if (Process::current()->tracer()) return EBUSY; caller.set_wait_for_tracer_at_next_execve(true); return KSuccess; } // FIXME: PID/TID BUG // This bug allows to request PT_ATTACH (or anything else) the same process, as // long it is not the main thread. Alternatively, if this is desired, then the // bug is that this prevents PT_ATTACH to the main thread from another thread. if (params.tid == caller.pid().value()) return EINVAL; auto peer = Thread::from_tid(params.tid); if (!peer) return ESRCH; if ((peer->process().uid() != caller.euid()) || (peer->process().uid() != peer->process().euid())) // Disallow tracing setuid processes return EACCES; if (!peer->process().is_dumpable()) return EACCES; auto& peer_process = peer->process(); if (params.request == PT_ATTACH) { if (peer_process.tracer()) { return EBUSY; } peer_process.start_tracing_from(caller.pid()); ScopedSpinLock lock(peer->get_lock()); if (peer->state() != Thread::State::Stopped) { peer->send_signal(SIGSTOP, &caller); } return KSuccess; } auto* tracer = peer_process.tracer(); if (!tracer) return EPERM; if (tracer->tracer_pid() != caller.pid()) return EBUSY; if (peer->state() == Thread::State::Running) return EBUSY; scheduler_lock.unlock(); switch (params.request) { case PT_CONTINUE: peer->send_signal(SIGCONT, &caller); break; case PT_DETACH: peer_process.stop_tracing(); peer->send_signal(SIGCONT, &caller); break; case PT_SYSCALL: tracer->set_trace_syscalls(true); peer->send_signal(SIGCONT, &caller); break; case PT_GETREGS: { if (!tracer->has_regs()) return EINVAL; auto* regs = reinterpret_cast(params.addr); if (!copy_to_user(regs, &tracer->regs())) return EFAULT; break; } case PT_SETREGS: { if (!tracer->has_regs()) return EINVAL; PtraceRegisters regs; if (!copy_from_user(®s, (const PtraceRegisters*)params.addr)) return EFAULT; auto& peer_saved_registers = peer->get_register_dump_from_stack(); // Verify that the saved registers are in usermode context if ((peer_saved_registers.cs & 0x03) != 3) return EFAULT; tracer->set_regs(regs); copy_ptrace_registers_into_kernel_registers(peer_saved_registers, regs); break; } case PT_PEEK: { Kernel::Syscall::SC_ptrace_peek_params peek_params; if (!copy_from_user(&peek_params, reinterpret_cast(params.addr))) return EFAULT; if (!is_user_address(VirtualAddress { peek_params.address })) return EFAULT; auto result = peer->process().peek_user_data(Userspace { (FlatPtr)peek_params.address }); if (result.is_error()) return result.error(); if (!copy_to_user(peek_params.out_data, &result.value())) return EFAULT; break; } case PT_POKE: if (!is_user_address(VirtualAddress { params.addr })) return EFAULT; return peer->process().poke_user_data(Userspace { (FlatPtr)params.addr }, params.data); default: return EINVAL; } return KSuccess; } void copy_kernel_registers_into_ptrace_registers(PtraceRegisters& ptrace_regs, const RegisterState& kernel_regs) { ptrace_regs.eax = kernel_regs.eax, ptrace_regs.ecx = kernel_regs.ecx, ptrace_regs.edx = kernel_regs.edx, ptrace_regs.ebx = kernel_regs.ebx, ptrace_regs.esp = kernel_regs.userspace_esp, ptrace_regs.ebp = kernel_regs.ebp, ptrace_regs.esi = kernel_regs.esi, ptrace_regs.edi = kernel_regs.edi, ptrace_regs.eip = kernel_regs.eip, ptrace_regs.eflags = kernel_regs.eflags, ptrace_regs.cs = 0; ptrace_regs.ss = 0; ptrace_regs.ds = 0; ptrace_regs.es = 0; ptrace_regs.fs = 0; ptrace_regs.gs = 0; } void copy_ptrace_registers_into_kernel_registers(RegisterState& kernel_regs, const PtraceRegisters& ptrace_regs) { kernel_regs.eax = ptrace_regs.eax; kernel_regs.ecx = ptrace_regs.ecx; kernel_regs.edx = ptrace_regs.edx; kernel_regs.ebx = ptrace_regs.ebx; kernel_regs.esp = ptrace_regs.esp; kernel_regs.ebp = ptrace_regs.ebp; kernel_regs.esi = ptrace_regs.esi; kernel_regs.edi = ptrace_regs.edi; kernel_regs.eip = ptrace_regs.eip; kernel_regs.eflags = (kernel_regs.eflags & ~safe_eflags_mask) | (ptrace_regs.eflags & safe_eflags_mask); } }