From 61bf83f07ff8fdc8c83ca09e7fb2dd11f5efccee Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Tue, 17 Dec 2024 20:47:09 -0800 Subject: [PATCH] Fix SecComp (#278) * Track child process independently --- .../Core/Source/Build/SystemAccessTracker.h | 6 +- .../Monitor/Host/Linux/LinuxMonitorProcess.h | 197 +++++++++++++++--- .../Host/Linux/LinuxTraceEventListener.h | 140 ++----------- 3 files changed, 187 insertions(+), 156 deletions(-) diff --git a/Source/Client/Core/Source/Build/SystemAccessTracker.h b/Source/Client/Core/Source/Build/SystemAccessTracker.h index 0df1da42..97a1139b 100644 --- a/Source/Client/Core/Source/Build/SystemAccessTracker.h +++ b/Source/Client/Core/Source/Build/SystemAccessTracker.h @@ -70,11 +70,11 @@ namespace Soup::Core if (exists) { - _input.insert(value); + _input.insert(std::move(value)); } else { - _inputMissing.insert(value); + _inputMissing.insert(std::move(value)); } } } @@ -94,7 +94,7 @@ namespace Soup::Core Log::Diag("TouchFileWrite {}", value); #endif - _output.insert(value); + _output.insert(std::move(value)); } } diff --git a/Source/Monitor/Host/Linux/LinuxMonitorProcess.h b/Source/Monitor/Host/Linux/LinuxMonitorProcess.h index 57b75c60..874fdb34 100644 --- a/Source/Monitor/Host/Linux/LinuxMonitorProcess.h +++ b/Source/Monitor/Host/Linux/LinuxMonitorProcess.h @@ -340,38 +340,107 @@ namespace Monitor::Linux /// The main entry point for the worker thread that will monitor incoming messages from all /// client connections. /// + + struct ProcessTraceState + { + pid_t ProcessId; + bool IsRunning; + bool InSystemCall; + }; + + ProcessTraceState& InitializeProcess( + std::vector& activeProcesses, + pid_t processId) + { + activeProcesses.push_back({ + processId, + true, + false, + }); + + return activeProcesses.at(activeProcesses.size() - 1); + } + + ProcessTraceState& FindProcess( + std::vector& activeProcesses, + pid_t processId) + { + auto result = std::find_if( + activeProcesses.begin(), + activeProcesses.end(), + [processId](const ProcessTraceState& value) { return value.ProcessId == processId; }); + + if (result == activeProcesses.end()) + { + throw std::runtime_error("Missing process trace state"); + } + else + { + return *result; + } + } + void WorkerThread() { Log::Diag("WorkerThread Start"); - int status; + + auto activeProcesses = std::vector(); + InitializeProcess(activeProcesses, m_processId); // Wait for the first notification from the child + int status; auto currentProcessId = waitpid(m_processId, &status, 0); + DebugTrace("WaitPID:", currentProcessId); if (currentProcessId == -1) throw std::runtime_error("Wait failed"); + auto& rootProcess = FindProcess(activeProcesses, currentProcessId); + // Enable SecComp filtering unsigned int ptraceOptions = + // Make it easier to track our SIGTRAP events + PTRACE_O_TRACESYSGOOD | + // Trace Secure Compute PTRACE_O_TRACESECCOMP | + // Auto attach to children PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | - PTRACE_O_TRACEVFORK; - - ptrace(PTRACE_SETOPTIONS, m_processId, 0, ptraceOptions); - ptrace(PTRACE_CONT, m_processId, NULL, NULL); + PTRACE_O_TRACEVFORK | + // Prevent children from running beyond our lifetime + PTRACE_O_EXITKILL | + // Monitor execve + PTRACE_O_TRACEEXEC | + // Monitor child exit + PTRACE_O_TRACEEXIT; + + if (ptrace(PTRACE_SETOPTIONS, m_processId, 0, ptraceOptions) < 0) + throw std::runtime_error(std::format("ptrace PTRACE_SETOPTIONS failed {0}", errno)); + if (ptrace(PTRACE_CONT, m_processId, NULL, NULL) < 0) + throw std::runtime_error(std::format("ptrace PTRACE_CONT failed {0}", errno)); while (true) { - currentProcessId = wait(&status); + currentProcessId = waitpid(-1, &status, __WALL); + int wait_errno = errno; + + DebugTrace("Wait:", currentProcessId); + DebugTrace("Status:", status); + if (currentProcessId == -1) throw std::runtime_error("Wait failed"); - if (WIFEXITED(status)) + bool continueSysCall = false; + bool exited = WIFEXITED(status); + + if (exited) { int exitCode = WEXITSTATUS(status); + auto& currentProcess = FindProcess(activeProcesses, currentProcessId); + currentProcess.IsRunning = false; if (currentProcessId == m_processId) { m_exitCode = exitCode; + DebugTrace("Main exit:", m_exitCode); return; } else @@ -382,63 +451,135 @@ namespace Monitor::Linux else if (WIFSTOPPED(status)) { auto signal = WSTOPSIG(status); + DebugTrace("Signal:", signal); switch (signal) { + case (SIGTRAP | 0x80): + { + DebugTrace("TRACESYSGOOD"); + + // Complete system call + auto& currentProcess = FindProcess(activeProcesses, currentProcessId); + if (currentProcess.InSystemCall) + { + // Process the completed system call + m_eventListener.ProcessSysCall(currentProcessId); + currentProcess.InSystemCall = false; + } + else + { + throw std::runtime_error("Expected to be in a system call"); + } + + break; + } case SIGTRAP: { auto event = (unsigned int)status >> 16; + DebugTrace("Event:", event); switch (event) { - case 0: - // Process start - break; case PTRACE_EVENT_SECCOMP: + { + DebugTrace("PTRACE_EVENT_SECCOMP"); + auto& currentProcess = FindProcess(activeProcesses, currentProcessId); + // Ignore all messages if partial monitor is enabled if (!m_partialMonitor) { - m_eventListener.ProcessSysCall(currentProcessId); + if (!currentProcess.InSystemCall) + { + // Signal the system call to continue so we can monitor the return result + continueSysCall = true; + currentProcess.InSystemCall = true; + } + else + { + throw std::runtime_error("Process was already in a system call"); + } } + break; + } case PTRACE_EVENT_FORK: + { + DebugTrace("PTRACE_EVENT_FORK"); + + break; + } case PTRACE_EVENT_VFORK: + { + DebugTrace("PTRACE_EVENT_VFORK"); + + break; + } case PTRACE_EVENT_CLONE: - // Entering child process + { + DebugTrace("PTRACE_EVENT_CLONE"); + auto& currentProcess = FindProcess(activeProcesses, currentProcessId); + if (currentProcess.InSystemCall) + { + currentProcess.InSystemCall = false; + } + else + { + throw std::runtime_error("Clone expected to be in a system call"); + } + break; - default: - std::cout << "UNKNOWN PTRACE EVENT: " << event << " STATUS: " << status << " PID: " << currentProcessId << std::endl; + } + case PTRACE_EVENT_EXEC: + { + DebugTrace("PTRACE_EVENT_EXEC"); + break; + } + case PTRACE_EVENT_EXIT: + { + DebugTrace("PTRACE_EVENT_EXIT"); + auto& currentProcess = FindProcess(activeProcesses, currentProcessId); + currentProcess.IsRunning = false; + break; + } + default: + { + throw std::runtime_error(std::format("UNKNOWN PTRACE EVENT: {0}", event)); + } } break; } - case SIGWINCH: - { - // Window size changed? - break; - } case SIGSTOP: { // Trace clone - break; - } - case SIGCLD: - { - // Child signaled + DebugTrace("TraceClone"); + InitializeProcess(activeProcesses, currentProcessId); break; } default: { - std::cout << "UNKNOWN SIGNAL: " << signal << " STATUS: " << status << " PID: " << currentProcessId << std::endl; - break; + DebugTrace("Unknown Signal"); } } } else { - std::cout << "UNKNOWN STATUS: " << status << " PID: " << currentProcessId << std::endl; + DebugTrace("WARNING: Unknown Status"); } - ptrace(PTRACE_CONT, currentProcessId, NULL, NULL); + if (!exited) + { + if (continueSysCall) + { + if (ptrace(PTRACE_SYSCALL, currentProcessId, 0, 0) < 0) + throw std::runtime_error(std::format("ptrace PTRACE_SYSCALL failed {0}", errno)); + } + else + { + if (ptrace(PTRACE_CONT, currentProcessId, 0, 0) < 0) + throw std::runtime_error(std::format("ptrace PTRACE_CONT failed {0}", errno)); + } + } } } diff --git a/Source/Monitor/Host/Linux/LinuxTraceEventListener.h b/Source/Monitor/Host/Linux/LinuxTraceEventListener.h index 406a7086..95def966 100644 --- a/Source/Monitor/Host/Linux/LinuxTraceEventListener.h +++ b/Source/Monitor/Host/Linux/LinuxTraceEventListener.h @@ -12,7 +12,7 @@ namespace Monitor::Linux long Command; long Return; long Error; - std::array Arugments; + std::array Arguments; }; typedef union { @@ -48,10 +48,8 @@ namespace Monitor::Linux void ProcessSysCall(pid_t pid) { - WaitForSyscallExit(pid); - auto registers = GetSysCallArgs(pid); - auto args = registers.Arugments; + auto args = registers.Arguments; auto result = registers.Return; switch (registers.Command) { @@ -198,41 +196,6 @@ namespace Monitor::Linux } private: - void WaitForSyscallExit(const pid_t pid) - { - bool entered = false; - int status = 0; - - while (true) - { - ptrace(PTRACE_SYSCALL, pid, 0, 0); - waitpid(pid, &status, 0); - - if (WIFSTOPPED(status)) - { - auto signal = WSTOPSIG(status); - if (signal == SIGTRAP) - { - // Linux 4.8+ only needs a single step to get return - return; - - // TODO: 4.7- - if (entered) - { - // If we had already entered before, then current SIGTRAP signal means exiting - return; - } - - entered = true; - } - else if (WIFEXITED(status) || WIFSIGNALED(status) || WCOREDUMP(status)) - { - throw std::runtime_error("The child has unexpectedly exited."); - } - } - } - } - SysCallStatus GetSysCallArgs(pid_t pid) { user_regs_struct regs; @@ -266,101 +229,37 @@ namespace Monitor::Linux }; } - bool ReadBoolValue(Message& message, uint32_t& offset) - { - auto result = *reinterpret_cast(message.Content + offset); - offset += sizeof(uint32_t); - if (offset > message.ContentSize) - throw std::runtime_error("ReadBoolValue past end of content"); - return result > 0; - } - - int32_t ReadInt32Value(Message& message, uint32_t& offset) - { - auto result = *reinterpret_cast(message.Content + offset); - offset += sizeof(int32_t); - if (offset > message.ContentSize) - throw std::runtime_error("ReadInt32Value past end of content"); - return result; - } - - uint32_t ReadUInt32Value(Message& message, uint32_t& offset) - { - auto result = *reinterpret_cast(message.Content + offset); - offset += sizeof(uint32_t); - if (offset > message.ContentSize) - throw std::runtime_error("ReadUInt32Value past end of content"); - return result; - } - - uint64_t ReadUInt64Value(Message& message, uint32_t& offset) - { - auto result = *reinterpret_cast(message.Content + offset); - offset += sizeof(uint64_t); - if (offset > message.ContentSize) - throw std::runtime_error("ReadUInt64Value past end of content"); - return result; - } - - std::string ReadStringValue(pid_t pid, long addr, size_t length) - { - auto result = std::string(length, '\0'); - char *laddr = result.data(); - int i, j; - union u - { - long val; - char chars[sizeof(long)]; - } data; - - i = 0; - j = length / sizeof(long); - - while (i < j) - { - data.val = ptrace(PTRACE_PEEKDATA, pid, addr + i * 8, NULL); - memcpy(laddr, data.chars, sizeof(long)); - ++i; - laddr += sizeof(long); - } - - j = length % sizeof(long); - if (j != 0) - { - data.val = ptrace(PTRACE_PEEKDATA, - pid, addr + i * 8, - NULL); - memcpy(laddr, data.chars, j); - } - - return result; - } - std::string ReadNullTerminatedStringValue(pid_t pid, long addr) { - auto result = std::string(4096, '\0'); + auto result = std::string(1024, '\0'); char *laddr = result.data(); - unsigned long len = 4096; + unsigned long len = 1024; unsigned int nread = 0; unsigned int residue = addr & (sizeof(long) - 1); void *const orig_addr = laddr; - while (len) { - addr &= -sizeof(long); /* aligned address */ + // aligned address + addr &= -sizeof(long); + while (len) + { errno = 0; dissected_long_t u = { .val = ptrace(PTRACE_PEEKDATA, pid, addr, 0) }; - switch (errno) { + switch (errno) + { case 0: break; - case ESRCH: case EINVAL: + case ESRCH: + case EINVAL: throw std::runtime_error("Could be seen if the process is gone"); - case EFAULT: case EIO: case EPERM: + case EFAULT: + case EIO: + case EPERM: throw std::runtime_error("address space is inaccessible"); default: throw std::runtime_error("all the rest is strange and should be reported"); @@ -386,14 +285,5 @@ namespace Monitor::Linux return result; } - - std::wstring_view ReadWStringValue(Message& message, uint32_t& offset) - { - auto result = std::wstring_view(reinterpret_cast(message.Content + offset)); - offset += 2 * (static_cast(result.size()) + 1); - if (offset > message.ContentSize) - throw std::runtime_error("ReadWStringValue past end of content"); - return result; - } }; }