diff --git a/Applications/Debugger/main.cpp b/Applications/Debugger/main.cpp index 3975d68c10..24d88dceaf 100644 --- a/Applications/Debugger/main.cpp +++ b/Applications/Debugger/main.cpp @@ -161,19 +161,17 @@ int main(int argc, char** argv) auto entry_point = get_entry_point(g_pid); dbg() << "entry point:" << entry_point; - uint32_t data = ptrace(PT_PEEK, g_pid, (void*)entry_point.as_ptr(), 0); + const uint32_t original_instruction_data = ptrace(PT_PEEK, g_pid, (void*)entry_point.as_ptr(), 0); - // u8* as_bytes = reinterpret_cast(&data); - // as_bytes[0] = 0xcc; - dbg() << "peeked data:" << (void*)data; - data = (data & ~(uint32_t)0xff) | 0xcc; - data = 0xccccccc; + dbg() << "peeked data:" << (void*)original_instruction_data; - if (ptrace(PT_POKE, g_pid, (void*)entry_point.as_ptr(), data) < 0) { + if (ptrace(PT_POKE, g_pid, (void*)entry_point.as_ptr(), (original_instruction_data & ~(uint32_t)0xff) | 0xcc) < 0) { perror("poke"); return 1; } + dbg() << "continuting"; + if (ptrace(PT_CONTINUE, g_pid, 0, 0) == -1) { perror("continue"); } @@ -187,14 +185,28 @@ int main(int argc, char** argv) printf("hit breakpoint\n"); + if (ptrace(PT_POKE, g_pid, (void*)entry_point.as_ptr(), original_instruction_data) < 0) { + perror("poke"); + return 1; + } + PtraceRegisters regs; - if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { + if (ptrace(PT_GETREGS, g_pid, ®s, 0) < 0) { perror("getregs"); return 1; } dbg() << "eip after breakpoint: " << (void*)regs.eip; + regs.eip = reinterpret_cast(entry_point.as_ptr()); + dbg() << "settings eip back to:" << (void*)regs.eip; + if (ptrace(PT_SETREGS, g_pid, ®s, 0) < 0) { + perror("setregs"); + return 1; + } + + dbg() << "continuig"; + if (ptrace(PT_CONTINUE, g_pid, 0, 0) == -1) { perror("continue"); } diff --git a/Kernel/Makefile b/Kernel/Makefile index 88ff6c46af..0447b9434a 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -128,7 +128,8 @@ OBJS = \ ACPI/MultiProcessorParser.o \ ACPI/Parser.o \ WaitQueue.o \ - init.o + init.o \ + Ptrace.o OBJ_SUFFIX = .kernel diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 09ed1226eb..2e3b5db1d5 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -1539,7 +1540,6 @@ void Process::crash(int signal, u32 eip) dbg() << "\033[31;1m" << String::format("%p", eip) << " (?)\033[0m\n"; } dump_backtrace(); - m_termination_signal = signal; dump_regions(); ASSERT(is_ring3()); @@ -4875,6 +4875,9 @@ OwnPtr Process::elf_bundle() const if (!m_executable) return nullptr; auto bundle = make(); + if (!m_executable->inode().shared_vmobject()) { + return nullptr; + } ASSERT(m_executable->inode().shared_vmobject()); auto& vmobject = *m_executable->inode().shared_vmobject(); bundle->region = MM.allocate_kernel_region_with_vmobject(const_cast(vmobject), vmobject.size(), "ELF bundle", Region::Access::Read); @@ -4911,135 +4914,8 @@ int Process::sys$ptrace(const Syscall::SC_ptrace_params* user_params) Syscall::SC_ptrace_params params; if (!validate_read_and_copy_typed(¶ms, user_params)) return -EFAULT; - - if (params.request == PT_TRACE_ME) { - if (Thread::current->tracer()) - return -EBUSY; - - m_wait_for_tracer_at_next_execve = true; - return 0; - } - - if (params.pid == m_pid) - return -EINVAL; - - Thread* peer = nullptr; - { - InterruptDisabler disabler; - peer = Thread::from_tid(params.pid); - } - if (!peer) - return -ESRCH; - - if (peer->process().uid() != m_euid) - return -EACCES; - - if (params.request == PT_ATTACH) { - if (peer->tracer()) { - return -EBUSY; - } - peer->start_tracing_from(m_pid); - if (peer->state() != Thread::State::Stopped && !(peer->m_blocker && peer->m_blocker->is_reason_signal())) - peer->send_signal(SIGSTOP, this); - return 0; - } - - auto* tracer = peer->tracer(); - - if (!tracer) - return -EPERM; - - if (tracer->tracer_pid() != m_pid) - return -EBUSY; - - if (peer->m_state == Thread::State::Running) - return -EBUSY; - - switch (params.request) { - case PT_CONTINUE: - peer->send_signal(SIGCONT, this); - break; - - case PT_DETACH: - peer->stop_tracing(); - peer->send_signal(SIGCONT, this); - break; - - case PT_SYSCALL: - tracer->set_trace_syscalls(true); - peer->send_signal(SIGCONT, this); - break; - - case PT_GETREGS: { - if (!tracer->has_regs()) - return -EINVAL; - - PtraceRegisters* regs = reinterpret_cast(params.addr); - if (!validate_write(regs, sizeof(PtraceRegisters))) - return -EFAULT; - - { - SmapDisabler disabler; - *regs = tracer->regs(); - } - break; - } - - case PT_PEEK: { - uint32_t* addr = reinterpret_cast(params.addr); - if (!MM.validate_user_read(peer->process(), VirtualAddress(addr), sizeof(uint32_t))) { - return -EFAULT; - } - - uint32_t result; - - SmapDisabler dis; - ProcessPagingScope scope(peer->process()); - result = *addr; - - return result; - } - - case PT_POKE: { - uint32_t* addr = reinterpret_cast(params.addr); - // We validate for "read" because PT_POKE can write to readonly pages, - // as long as they are user pages - if (!MM.validate_user_read(peer->process(), VirtualAddress(addr), sizeof(uint32_t))) { - return -EFAULT; - } - ProcessPagingScope scope(peer->process()); - Range range = { VirtualAddress(addr), sizeof(uint32_t) }; - auto* region = peer->process().region_containing(range); - ASSERT(region != nullptr); - if (region->is_shared()) { - // If the region is shared, we change its vmobject to a PrivateInodeVMObject - // to prevent the write operation from chaning any shared inode data - ASSERT(region->vmobject().is_shared_inode()); - region->set_vmobject(PrivateInodeVMObject::create_with_inode(static_cast(region->vmobject()).inode())); - region->set_shared(false); - } - const bool was_writable = region->is_writable(); - if (!was_writable) //TODO refactor into scopeguard - region->set_writable(true); - region->remap(); - - { - SmapDisabler dis; - *addr = params.data; - } - - if (!was_writable) { - region->set_writable(false); - region->remap(); - } - break; - } - - default: - return -EINVAL; - } - - return 0; + auto result = Ptrace::handle_syscall(params, *this); + return result.is_error() ? result.error() : result.value(); } bool Process::has_tracee_thread(int tracer_pid) const @@ -5056,4 +4932,57 @@ bool Process::has_tracee_thread(int tracer_pid) const return has_tracee; } +KResultOr Process::peek_user_data(u32* address) +{ + if (!MM.validate_user_read(*this, VirtualAddress(address), sizeof(u32))) { + return KResult(-EFAULT); + } + uint32_t result; + + SmapDisabler dis; + // This function can be called from the context of another + // process that called PT_PEEK + ProcessPagingScope scope(*this); + result = *address; + + return result; +} + +KResult Process::poke_user_data(u32* address, u32 data) +{ + // We validate for read (rather than write) because PT_POKE can write to readonly pages. + // So we wffectively only care that the poke operation only writes to user pages + if (!MM.validate_user_read(*this, VirtualAddress(address), sizeof(u32))) { + return KResult(-EFAULT); + } + ProcessPagingScope scope(*this); + Range range = { VirtualAddress(address), sizeof(u32) }; + auto* region = region_containing(range); + ASSERT(region != nullptr); + if (region->is_shared()) { + // If the region is shared, we change its vmobject to a PrivateInodeVMObject + // to prevent the write operation from chaning any shared inode data + ASSERT(region->vmobject().is_shared_inode()); + region->set_vmobject(PrivateInodeVMObject::create_with_inode(static_cast(region->vmobject()).inode())); + region->set_shared(false); + } + const bool was_writable = region->is_writable(); + if (!was_writable) //TODO refactor into scopeguard + { + region->set_writable(true); + region->remap(); + } + + { + SmapDisabler dis; + *address = data; + } + + if (!was_writable) { + region->set_writable(false); + region->remap(); + } + return KResult(KSuccess); +} + } diff --git a/Kernel/Process.h b/Kernel/Process.h index 708385ac25..98699701cd 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -411,6 +411,11 @@ public: void increment_inspector_count(Badge) { ++m_inspector_count; } void decrement_inspector_count(Badge) { --m_inspector_count; } + void set_wait_for_tracer_at_next_execve(bool val) { m_wait_for_tracer_at_next_execve = val; } + + KResultOr peek_user_data(u32* address); + KResult poke_user_data(u32* address, u32 data); + private: friend class MemoryManager; friend class Scheduler; diff --git a/Kernel/Ptrace.cpp b/Kernel/Ptrace.cpp new file mode 100644 index 0000000000..bf8c191931 --- /dev/null +++ b/Kernel/Ptrace.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include + +namespace Ptrace { + +KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, Process& caller) +{ + if (params.request == PT_TRACE_ME) { + if (Thread::current->tracer()) + return KResult(-EBUSY); + + caller.set_wait_for_tracer_at_next_execve(true); + return KSuccess; + } + + if (params.pid == caller.pid()) + return KResult(-EINVAL); + + Thread* peer = nullptr; + { + InterruptDisabler disabler; + peer = Thread::from_tid(params.pid); + } + if (!peer) + return KResult(-ESRCH); + + if ((peer->process().uid() != caller.euid()) + || (peer->process().uid() != peer->process().euid())) // Disallow tracing setuid processes + return KResult(-EACCES); + + if (params.request == PT_ATTACH) { + if (peer->tracer()) { + return KResult(-EBUSY); + } + peer->start_tracing_from(caller.pid()); + if (peer->state() != Thread::State::Stopped && !(peer->has_blocker() && peer->blocker().is_reason_signal())) + peer->send_signal(SIGSTOP, &caller); + return KSuccess; + } + + auto* tracer = peer->tracer(); + + if (!tracer) + return KResult(-EPERM); + + if (tracer->tracer_pid() != caller.pid()) + return KResult(-EBUSY); + + if (peer->state() == Thread::State::Running) + return KResult(-EBUSY); + + switch (params.request) { + case PT_CONTINUE: + peer->send_signal(SIGCONT, &caller); + break; + + case PT_DETACH: + peer->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 KResult(-EINVAL); + + PtraceRegisters* regs = reinterpret_cast(params.addr); + if (!caller.validate_write(regs, sizeof(PtraceRegisters))) + return KResult(-EFAULT); + + { + SmapDisabler disabler; + *regs = tracer->regs(); + } + break; + } + + case PT_SETREGS: { + if (!tracer->has_regs()) + return KResult(-EINVAL); + + if (!caller.validate_read(params.addr, sizeof(PtraceRegisters))) + return KResult(-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; + { + SmapDisabler disabler; + PtraceRegisters* regs = reinterpret_cast(params.addr); + tracer->set_regs(*regs); + copy_ptrace_registers_into_kernel_registers(peer_saved_registers, *regs); + break; + } + } + + case PT_PEEK: { + u32* addr = reinterpret_cast(params.addr); + return peer->process().peek_user_data(addr); + } + + case PT_POKE: { + u32* addr = reinterpret_cast(params.addr); + return peer->process().poke_user_data(addr, params.data); + } + + default: + return -EINVAL; + } + + return 0; +} + +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 = ptrace_regs.eflags; +} + +} diff --git a/Kernel/Ptrace.h b/Kernel/Ptrace.h new file mode 100644 index 0000000000..c305cf3f89 --- /dev/null +++ b/Kernel/Ptrace.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once +#include +#include +#include +#include + +namespace Ptrace { + +KResultOr handle_syscall(const Kernel::Syscall::SC_ptrace_params& params, Process& caller); + +void copy_kernel_registers_into_ptrace_registers(PtraceRegisters&, const RegisterState&); +void copy_ptrace_registers_into_kernel_registers(RegisterState&, const PtraceRegisters&); + +} diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index d229e46f74..cac6cccd31 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -944,4 +944,10 @@ void Thread::tracer_trap(const RegisterState& regs) send_urgent_signal_to_self(SIGTRAP); } +const Thread::Blocker& Thread::blocker() const +{ + ASSERT(m_blocker); + return *m_blocker; +} + } diff --git a/Kernel/Thread.h b/Kernel/Thread.h index 19914ddfe3..34de549d66 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -266,6 +266,9 @@ public: bool is_stopped() const { return m_state == Stopped; } bool is_blocked() const { return m_state == Blocked; } + bool has_blocker() const { return m_blocker != nullptr; } + const Blocker& blocker() const; + bool in_kernel() const { return (m_tss.cs & 0x03) == 0; } u32 frame_ptr() const { return m_tss.ebp; } diff --git a/Kernel/ThreadTracer.cpp b/Kernel/ThreadTracer.cpp index d7cd685a50..c58e354d0a 100644 --- a/Kernel/ThreadTracer.cpp +++ b/Kernel/ThreadTracer.cpp @@ -26,6 +26,8 @@ #include #include +#include +#include #include namespace Kernel { @@ -37,24 +39,9 @@ ThreadTracer::ThreadTracer(pid_t tracer_pid) void ThreadTracer::set_regs(const RegisterState& regs) { - PtraceRegisters r = { - regs.eax, - regs.ecx, - regs.edx, - regs.ebx, - regs.esp, - regs.ebp, - regs.esi, - regs.edi, - regs.eip, - regs.eflags, - regs.cs, - regs.ss, - regs.ds, - regs.es, - regs.fs, - regs.gs, - }; + PtraceRegisters r; + Ptrace::copy_kernel_registers_into_ptrace_registers(r, regs); m_regs = r; } + } diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index e698a1b3f2..5b89a5f751 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -556,3 +556,4 @@ struct rtentry { #define PT_DETACH 6 #define PT_PEEK 7 #define PT_POKE 8 +#define PT_SETREGS 9 diff --git a/Libraries/LibC/sys/ptrace.h b/Libraries/LibC/sys/ptrace.h index 8182188e7f..b1008f62ca 100644 --- a/Libraries/LibC/sys/ptrace.h +++ b/Libraries/LibC/sys/ptrace.h @@ -38,6 +38,7 @@ __BEGIN_DECLS #define PT_DETACH 6 #define PT_PEEK 7 #define PT_POKE 8 +#define PT_SETREGS 9 int ptrace(int request, pid_t pid, void* addr, int data);