diff --git a/AK/Types.h b/AK/Types.h index aab08774e5e..381b3f8a685 100644 --- a/AK/Types.h +++ b/AK/Types.h @@ -70,6 +70,8 @@ typedef i16 int16_t; typedef i32 int32_t; typedef i64 int64_t; +typedef int pid_t; + #else # include # include @@ -84,9 +86,9 @@ typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; -#ifdef __ptrdiff_t +# ifdef __ptrdiff_t typedef __PTRDIFF_TYPE__ __ptrdiff_t; -#endif +# endif #endif @@ -115,4 +117,6 @@ inline constexpr size_t align_up_to(const size_t value, const size_t alignment) return (value + (alignment - 1)) & ~(alignment - 1); } -enum class TriState : u8 { False, True, Unknown }; +enum class TriState : u8 { False, + True, + Unknown }; diff --git a/Kernel/Forward.h b/Kernel/Forward.h index 8194ebe83a4..1587051c974 100644 --- a/Kernel/Forward.h +++ b/Kernel/Forward.h @@ -50,7 +50,7 @@ class PhysicalPage; class PhysicalRegion; class Process; class ProcessInspectionHandle; -class ProcessTracer; +class ThreadTracer; class Range; class RangeAllocator; class Region; diff --git a/Kernel/Makefile b/Kernel/Makefile index 7b97e15ff6d..da739aa8cf6 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -92,7 +92,7 @@ OBJS = \ PCI/Device.o \ PerformanceEventBuffer.o \ Process.o \ - ProcessTracer.o \ + ThreadTracer.o \ Profiling.o \ RTC.o \ Random.o \ diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index 38e1a967311..054b987e50e 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -56,7 +56,6 @@ #include #include #include -#include #include #include #include @@ -66,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -1235,6 +1235,9 @@ int Process::sys$execve(const Syscall::SC_execve_params* user_params) if (params.arguments.length > ARG_MAX || params.environment.length > ARG_MAX) return -E2BIG; + if (m_wait_for_tracer_at_next_execve) + Thread::current->send_urgent_signal_to_self(SIGSTOP); + String path; { auto path_arg = get_syscall_path_argument(params.path); @@ -3078,9 +3081,6 @@ void Process::die() // slave owner, we have to allow the PTY pair to be torn down. m_tty = nullptr; - if (m_tracer) - m_tracer->set_dead(); - kill_all_threads(); } @@ -3964,24 +3964,6 @@ int Process::sys$watch_file(const char* user_path, size_t path_length) return fd; } -int Process::sys$systrace(pid_t pid) -{ - REQUIRE_PROMISE(proc); - InterruptDisabler disabler; - auto* peer = Process::from_pid(pid); - if (!peer) - return -ESRCH; - if (peer->uid() != m_euid) - return -EACCES; - int fd = alloc_fd(); - if (fd < 0) - return fd; - auto description = FileDescription::create(peer->ensure_tracer()); - description->set_readable(true); - m_fds[fd].set(move(description), 0); - return fd; -} - int Process::sys$halt() { if (!is_superuser()) @@ -4112,13 +4094,6 @@ int Process::sys$umount(const char* user_mountpoint, size_t mountpoint_length) return VFS::the().unmount(guest_inode_id); } -ProcessTracer& Process::ensure_tracer() -{ - if (!m_tracer) - m_tracer = ProcessTracer::create(m_pid); - return *m_tracer; -} - void Process::FileDescriptionAndFlags::clear() { description = nullptr; @@ -4887,4 +4862,102 @@ int Process::sys$get_stack_bounds(FlatPtr* user_stack_base, size_t* user_stack_s return 0; } +int Process::sys$ptrace(const Syscall::SC_ptrace_params* user_params) +{ + REQUIRE_PROMISE(proc); + 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; + + InterruptDisabler disabler; + auto* 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; + } + + default: + return -EINVAL; + } + + return 0; +} + +bool Process::has_tracee_thread(int tracer_pid) const +{ + bool has_tracee = false; + + for_each_thread([&](Thread& t) { + if (t.tracer() && t.tracer()->tracer_pid() == tracer_pid) { + has_tracee = true; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return has_tracee; +} + } diff --git a/Kernel/Process.h b/Kernel/Process.h index 2fefa8aec43..0ea441a994e 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -272,7 +272,6 @@ public: int sys$set_thread_name(int tid, const char* buffer, size_t buffer_size); int sys$get_thread_name(int tid, char* buffer, size_t buffer_size); int sys$rename(const Syscall::SC_rename_params*); - int sys$systrace(pid_t); int sys$mknod(const Syscall::SC_mknod_params*); int sys$shbuf_create(int, void** buffer); int sys$shbuf_allow_pid(int, pid_t peer_pid); @@ -300,6 +299,7 @@ public: int sys$unveil(const Syscall::SC_unveil_params*); int sys$perf_event(int type, FlatPtr arg1, FlatPtr arg2); int sys$get_stack_bounds(FlatPtr* stack_base, size_t* stack_size); + int sys$ptrace(const Syscall::SC_ptrace_params*); template int get_sock_or_peer_name(const Params&); @@ -316,9 +316,6 @@ public: const NonnullOwnPtrVector& regions() const { return m_regions; } void dump_regions(); - ProcessTracer* tracer() { return m_tracer.ptr(); } - ProcessTracer& ensure_tracer(); - u32 m_ticks_in_user { 0 }; u32 m_ticks_in_kernel { 0 }; @@ -442,6 +439,8 @@ private: KResultOr get_syscall_path_argument(const char* user_path, size_t path_length) const; KResultOr get_syscall_path_argument(const Syscall::StringArgument&) const; + bool has_tracee_thread(int tracer_pid) const; + RefPtr m_page_directory; Process* m_prev { nullptr }; @@ -500,8 +499,6 @@ private: FixedArray m_extra_gids; - RefPtr m_tracer; - WeakPtr m_master_tls_region; size_t m_master_tls_size { 0 }; size_t m_master_tls_alignment { 0 }; @@ -526,6 +523,11 @@ private: OwnPtr m_perf_event_buffer; u32 m_inspector_count { 0 }; + + // This member is used in the implementation of ptrace's PT_TRACEME flag. + // If it is set to true, the process will stop at the next execve syscall + // and wait for a tracer to attach. + bool m_wait_for_tracer_at_next_execve { false }; }; class ProcessInspectionHandle { @@ -585,7 +587,7 @@ inline void Process::for_each_child(Callback callback) pid_t my_pid = pid(); for (auto* process = g_processes->head(); process;) { auto* next_process = process->next(); - if (process->ppid() == my_pid) { + if (process->ppid() == my_pid || process->has_tracee_thread(m_pid)) { if (callback(*process) == IterationDecision::Break) break; } diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index a3f7d45dcab..50a9e99c242 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -26,9 +26,9 @@ #include #include -#include #include #include +#include #include namespace Kernel { @@ -92,8 +92,6 @@ int handle(RegisterState& regs, u32 function, u32 arg1, u32 arg2, u32 arg3) if (function == SC_exit || function == SC_exit_thread) { // These syscalls need special handling since they never return to the caller. cli(); - if (auto* tracer = process.tracer()) - tracer->did_syscall(function, arg1, arg2, arg3, 0); if (function == SC_exit) process.sys$exit((int)arg1); else @@ -132,6 +130,11 @@ void syscall_handler(RegisterState& regs) return; } + if (Thread::current->tracer() && Thread::current->tracer()->is_tracing_syscalls()) { + Thread::current->tracer()->set_trace_syscalls(false); + Thread::current->tracer_trap(regs); + } + // Make sure SMAP protection is enabled on syscall entry. clac(); @@ -168,8 +171,12 @@ void syscall_handler(RegisterState& regs) u32 arg2 = regs.ecx; u32 arg3 = regs.ebx; regs.eax = (u32)Syscall::handle(regs, function, arg1, arg2, arg3); - if (auto* tracer = process.tracer()) - tracer->did_syscall(function, arg1, arg2, arg3, regs.eax); + + if (Thread::current->tracer() && Thread::current->tracer()->is_tracing_syscalls()) { + Thread::current->tracer()->set_trace_syscalls(false); + Thread::current->tracer_trap(regs); + } + process.big_lock().unlock(); // Check if we're supposed to return to userspace or just die. diff --git a/Kernel/Syscall.h b/Kernel/Syscall.h index 238ee731403..cfb072c650e 100644 --- a/Kernel/Syscall.h +++ b/Kernel/Syscall.h @@ -133,7 +133,6 @@ namespace Kernel { __ENUMERATE_SYSCALL(donate) \ __ENUMERATE_SYSCALL(rename) \ __ENUMERATE_SYSCALL(ftruncate) \ - __ENUMERATE_SYSCALL(systrace) \ __ENUMERATE_SYSCALL(exit_thread) \ __ENUMERATE_SYSCALL(mknod) \ __ENUMERATE_SYSCALL(writev) \ @@ -182,7 +181,8 @@ namespace Kernel { __ENUMERATE_SYSCALL(unveil) \ __ENUMERATE_SYSCALL(perf_event) \ __ENUMERATE_SYSCALL(shutdown) \ - __ENUMERATE_SYSCALL(get_stack_bounds) + __ENUMERATE_SYSCALL(get_stack_bounds) \ + __ENUMERATE_SYSCALL(ptrace) namespace Syscall { @@ -424,6 +424,13 @@ struct SC_stat_params { bool follow_symlinks; }; +struct SC_ptrace_params { + int request; + pid_t pid; + u8* addr; + int data; +}; + void initialize(); int sync(); diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 4379cad7460..563e0c02ba6 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -508,6 +509,29 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) ASSERT(m_stop_state != State::Invalid); set_state(m_stop_state); m_stop_state = State::Invalid; + // make sure SemiPermanentBlocker is unblocked + if (m_state != Thread::Runnable && m_state != Thread::Running + && m_blocker && m_blocker->is_reason_signal()) + unblock(); + } + + else { + auto* thread_tracer = tracer(); + if (thread_tracer != nullptr) { + // when a thread is traced, it should be stopped whenever it receives a signal + // the tracer is notified of this by using waitpid() + // only "pending signals" from the tracer are sent to the tracee + if (!thread_tracer->has_pending_signal(signal)) { + m_stop_signal = signal; + // make sure SemiPermanentBlocker is unblocked + if (m_blocker && m_blocker->is_reason_signal()) + unblock(); + m_stop_state = m_state; + set_state(Stopped); + return ShouldUnblockThread::No; + } + thread_tracer->unset_signal(signal); + } } auto handler_vaddr = action.handler_or_sigaction; @@ -900,4 +924,21 @@ void Thread::reset_fpu_state() memcpy(m_fpu_state, &s_clean_fpu_state, sizeof(FPUState)); } +void Thread::start_tracing_from(pid_t tracer) +{ + m_tracer = ThreadTracer::create(tracer); +} + +void Thread::stop_tracing() +{ + m_tracer = nullptr; +} + +void Thread::tracer_trap(const RegisterState& regs) +{ + ASSERT(m_tracer.ptr()); + m_tracer->set_regs(regs); + send_urgent_signal_to_self(SIGTRAP); +} + } diff --git a/Kernel/Thread.h b/Kernel/Thread.h index aeebdc462ff..c3abbed6fd3 100644 --- a/Kernel/Thread.h +++ b/Kernel/Thread.h @@ -122,6 +122,7 @@ public: virtual ~Blocker() {} virtual bool should_unblock(Thread&, time_t now_s, long us) = 0; virtual const char* state_string() const = 0; + virtual bool is_reason_signal() const { return false; } void set_interrupted_by_death() { m_was_interrupted_by_death = true; } bool was_interrupted_by_death() const { return m_was_interrupted_by_death; } void set_interrupted_by_signal() { m_was_interrupted_while_blocked = true; } @@ -253,6 +254,7 @@ public: } ASSERT_NOT_REACHED(); } + virtual bool is_reason_signal() const override { return m_reason == Reason::Signal; } private: Reason m_reason; @@ -356,6 +358,7 @@ public: void terminate_due_to_signal(u8 signal); bool should_ignore_signal(u8 signal) const; bool has_signal_handler(u8 signal) const; + bool has_pending_signal(u8 signal) const { return m_pending_signals & (1 << (signal - 1)); } FPUState& fpu_state() { return *m_fpu_state; } @@ -431,6 +434,11 @@ public: static constexpr u32 default_kernel_stack_size = 65536; static constexpr u32 default_userspace_stack_size = 4 * MB; + ThreadTracer* tracer() { return m_tracer.ptr(); } + void start_tracing_from(pid_t tracer); + void stop_tracing(); + void tracer_trap(const RegisterState&); + private: IntrusiveListNode m_runnable_list_node; IntrusiveListNode m_wait_queue_node; @@ -491,6 +499,8 @@ private: bool m_dump_backtrace_on_finalization { false }; bool m_should_die { false }; + OwnPtr m_tracer; + void yield_without_holding_big_lock(); }; diff --git a/Kernel/ProcessTracer.cpp b/Kernel/ThreadTracer.cpp similarity index 66% rename from Kernel/ProcessTracer.cpp rename to Kernel/ThreadTracer.cpp index cead7bfe085..f59bf84d212 100644 --- a/Kernel/ProcessTracer.cpp +++ b/Kernel/ThreadTracer.cpp @@ -25,39 +25,35 @@ */ #include -#include +#include namespace Kernel { -ProcessTracer::ProcessTracer(pid_t pid) - : m_pid(pid) +ThreadTracer::ThreadTracer(pid_t tracer_pid) + : m_tracer_pid(tracer_pid) { } -ProcessTracer::~ProcessTracer() +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, + }; + m_regs = r; } - -void ProcessTracer::did_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3, u32 result) -{ - CallData data = { function, arg1, arg2, arg3, result }; - m_calls.enqueue(data); -} - -int ProcessTracer::read(FileDescription&, u8* buffer, int buffer_size) -{ - if (m_calls.is_empty()) - return 0; - auto data = m_calls.dequeue(); - // FIXME: This should not be an assertion. - ASSERT(buffer_size == sizeof(data)); - memcpy(buffer, &data, sizeof(data)); - return sizeof(data); -} - -String ProcessTracer::absolute_path(const FileDescription&) const -{ - return String::format("tracer:%d", m_pid); -} - } diff --git a/Kernel/ProcessTracer.h b/Kernel/ThreadTracer.h similarity index 52% rename from Kernel/ProcessTracer.h rename to Kernel/ThreadTracer.h index 64e35183d1f..5938203f02b 100644 --- a/Kernel/ProcessTracer.h +++ b/Kernel/ThreadTracer.h @@ -26,46 +26,48 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include #include +#include namespace Kernel { -class ProcessTracer : public File { +class ThreadTracer { public: - static NonnullRefPtr create(pid_t pid) { return adopt(*new ProcessTracer(pid)); } - virtual ~ProcessTracer() override; + static NonnullOwnPtr create(pid_t tracer) { return make(tracer); } - bool is_dead() const { return m_dead; } - void set_dead() { m_dead = true; } + pid_t tracer_pid() const { return m_tracer_pid; } + bool has_pending_signal(u32 signal) const { return m_pending_signals & (1 << (signal - 1)); } + void set_signal(u32 signal) { m_pending_signals |= (1 << (signal - 1)); } + void unset_signal(u32 signal) { m_pending_signals &= ~(1 << (signal - 1)); } - virtual bool can_read(const FileDescription&) const override { return !m_calls.is_empty() || m_dead; } - virtual int read(FileDescription&, u8*, int) override; + bool is_tracing_syscalls() const { return m_trace_syscalls; } + void set_trace_syscalls(bool val) { m_trace_syscalls = val; } - virtual bool can_write(const FileDescription&) const override { return true; } - virtual int write(FileDescription&, const u8*, int) override { return -EIO; } + void set_regs(const RegisterState& regs); + bool has_regs() const { return m_regs.has_value(); } + const PtraceRegisters& regs() const + { + ASSERT(m_regs.has_value()); + return m_regs.value(); + } - virtual String absolute_path(const FileDescription&) const override; - - void did_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3, u32 result); - pid_t pid() const { return m_pid; } + explicit ThreadTracer(pid_t); private: - virtual const char* class_name() const override { return "ProcessTracer"; } - explicit ProcessTracer(pid_t); + pid_t m_tracer_pid { -1 }; - struct CallData { - u32 function; - u32 arg1; - u32 arg2; - u32 arg3; - u32 result; - }; + // This is a bitmap for signals that are sent from the tracer to the tracee + // TODO: Since we do not currently support sending signals + // to the tracee via PT_CONTINUE, this bitmap is always zeroed + u32 m_pending_signals { 0 }; - pid_t m_pid; - bool m_dead { false }; - CircularQueue m_calls; + bool m_trace_syscalls { false }; + Optional m_regs; }; } diff --git a/Kernel/UnixTypes.h b/Kernel/UnixTypes.h index 3b50ef1957a..9706cbd9f20 100644 --- a/Kernel/UnixTypes.h +++ b/Kernel/UnixTypes.h @@ -544,3 +544,10 @@ struct rtentry { #define PURGE_ALL_VOLATILE 0x1 #define PURGE_ALL_CLEAN_INODE 0x2 + +#define PT_TRACE_ME 1 +#define PT_ATTACH 2 +#define PT_CONTINUE 3 +#define PT_SYSCALL 4 +#define PT_GETREGS 5 +#define PT_DETACH 6 diff --git a/Libraries/LibC/sys/arch/i386/regs.h b/Libraries/LibC/sys/arch/i386/regs.h new file mode 100644 index 00000000000..21cdf64116c --- /dev/null +++ b/Libraries/LibC/sys/arch/i386/regs.h @@ -0,0 +1,49 @@ +/* + * 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 + +struct [[gnu::packed]] PtraceRegisters +{ + uint32_t eax; + uint32_t ecx; + uint32_t edx; + uint32_t ebx; + uint32_t esp; + uint32_t ebp; + uint32_t esi; + uint32_t edi; + uint32_t eip; + uint32_t eflags; + uint32_t cs; + uint32_t ss; + uint32_t ds; + uint32_t es; + uint32_t fs; + uint32_t gs; +};