From c956e958e0a386f24e6865ad62ee4fe640f93b18 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 17 Apr 2024 12:42:01 +0200 Subject: zen startup hardening (#49) - Feature: `zen up` command improvements - --`port` allows you to specify a base port when starting an instance - --`base-dir` allows you to specify a base directory for the zenserver executable if it is not located next to the zen.exe executable - Feature: `zen down` - --`port` allows you to specify a base port when shutting down an instance - --`base-dir` allows you to specify a base directory for the zenserver executable if it is not located next to the zen.exe executable - --`force` if regular shutdown fails it tries to find a running zenserver.exe process and terminate it - If it fails to attach to the running server it now waits for it to exit when setting the RequestExit shared memory flag - Improvement: zenserver now checks the RequestExit flag in the shared memory and exist gracefully if it is set - Improvement: When adding a sponsor process to a running zenserver instance, we wait for it to be picked up from the shared memory section to determine success/fail --- src/zencore/process.cpp | 257 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 241 insertions(+), 16 deletions(-) (limited to 'src/zencore/process.cpp') diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp index d8be0d343..df2a87352 100644 --- a/src/zencore/process.cpp +++ b/src/zencore/process.cpp @@ -14,6 +14,7 @@ #if ZEN_PLATFORM_WINDOWS # include # include +# include # include #else # include @@ -23,11 +24,16 @@ # include # include # include +# include # include # include # include #endif +#if ZEN_PLATFORM_MAC +# include +#endif + ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END @@ -48,6 +54,36 @@ const bool bNoZombieChildren = []() { sigaction(SIGCHLD, &Action, nullptr); return true; }(); + +static char +GetPidStatus(int Pid) +{ + std::filesystem::path EntryPath = std::filesystem::path("/proc") / fmt::format("{}", Pid); + std::filesystem::path StatPath = EntryPath / "stat"; + if (std::filesystem::is_regular_file(StatPath)) + { + FILE* StatFile = fopen(StatPath.c_str(), "r"); + if (StatFile) + { + char Buffer[5120]; + int Size = fread(Buffer, 1, 5120 - 1, StatFile); + fclose(StatFile); + if (Size > 0) + { + Buffer[Size + 1] = 0; + char* ScanPtr = strrchr(Buffer, ')'); + if (ScanPtr && ScanPtr[1] != '\0') + { + ScanPtr += 2; + char State = *ScanPtr; + return State; + } + } + } + } + return 0; +} + #endif ProcessHandle::ProcessHandle() = default; @@ -121,7 +157,8 @@ ProcessHandle::IsRunning() const GetExitCodeProcess(m_ProcessHandle, &ExitCode); bActive = (ExitCode == STILL_ACTIVE); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - bActive = (kill(pid_t(m_Pid), 0) == 0); + std::error_code _; + bActive = IsProcessRunning(m_Pid, _); #endif return bActive; @@ -133,29 +170,40 @@ ProcessHandle::IsValid() const return (m_ProcessHandle != nullptr); } -void +bool ProcessHandle::Terminate(int ExitCode) { if (!IsRunning()) { - return; + return true; } - bool bSuccess = false; - #if ZEN_PLATFORM_WINDOWS - TerminateProcess(m_ProcessHandle, ExitCode); + BOOL bTerminated = TerminateProcess(m_ProcessHandle, ExitCode); + if (!bTerminated) + { + return false; + } DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE); - bSuccess = (WaitResult != WAIT_OBJECT_0); + bool bSuccess = (WaitResult == WAIT_OBJECT_0) || (WaitResult == WAIT_ABANDONED_0); + if (!bSuccess) + { + return false; + } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ZEN_UNUSED(ExitCode); - bSuccess = (kill(m_Pid, SIGKILL) == 0); -#endif - - if (!bSuccess) + int Res = kill(pid_t(m_Pid), SIGKILL); + if (Res != 0) { - // What might go wrong here, and what is meaningful to act on? + int err = errno; + if (err != ESRCH) + { + return false; + } } +#endif + Reset(); + return true; } void @@ -649,7 +697,7 @@ ProcessMonitor::IsActive() const ////////////////////////////////////////////////////////////////////////// bool -IsProcessRunning(int pid) +IsProcessRunning(int pid, std::error_code& OutEc) { // This function is arguably not super useful, a pid can be re-used // by the OS so holding on to a pid and polling it over some time @@ -665,7 +713,8 @@ IsProcessRunning(int pid) { return false; } - ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid)); + OutEc = MakeErrorCode(Error); + return false; } auto _ = MakeGuard([hProc]() { CloseHandle(hProc); }); @@ -678,27 +727,72 @@ IsProcessRunning(int pid) else { DWORD Error = GetLastError(); - ThrowSystemError(Error, fmt::format("failed to get process exit code for pid {}", pid)); + OutEc = MakeErrorCode(Error); + return false; } return bStillActive; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Res = kill(pid_t(pid), 0); if (Res == 0) { +# if ZEN_PLATFORM_MAC + struct kinfo_proc Info; + int Mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; + size_t InfoSize = sizeof Info; + + int Res = sysctl(Mib, 4, &Info, &InfoSize, NULL, 0); + if (Res != 0) + { + int Error = errno; + OutEc = MakeErrorCode(Error); + return false; + } + ZEN_INFO("Found process {} with status {}", pid, (int)Info.kp_proc.p_stat); + if (Info.kp_proc.p_stat == SZOMB) + { + // Zombie process + return false; + } + return true; +# endif // ZEN_PLATFORM_MAC +# if ZEN_PLATFORM_LINUX + char Status = GetPidStatus(pid); + if (Status == 'Z' || Status == 0) + { + return false; + } return true; +# endif // ZEN_PLATFORM_LINUX } int Error = errno; if (Error == ESRCH) // No such process { return false; } + else if (Error == ENOENT) + { + return false; + } else { - ThrowSystemError(Error, fmt::format("Failed to signal running process %d: %d", pid, Error)); + OutEc = MakeErrorCode(Error); + return false; } #endif } +bool +IsProcessRunning(int pid) +{ + std::error_code Ec; + bool IsRunning = IsProcessRunning(pid, Ec); + if (Ec) + { + ThrowSystemError(Ec.value(), fmt::format("Failed determining if process with pid {} is running", pid)); + } + return IsRunning; +} + int GetCurrentProcessId() { @@ -719,6 +813,129 @@ GetProcessId(CreateProcResult ProcId) #endif } +std::error_code +FindProcess(const std::filesystem::path& ExecutableImage, ProcessHandle& OutHandle) +{ +#if ZEN_PLATFORM_WINDOWS + HANDLE ProcessSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (ProcessSnapshotHandle == INVALID_HANDLE_VALUE) + { + return MakeErrorCodeFromLastError(); + } + auto _ = MakeGuard([&]() { CloseHandle(ProcessSnapshotHandle); }); + + PROCESSENTRY32 Entry; + Entry.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(ProcessSnapshotHandle, (LPPROCESSENTRY32)&Entry)) + { + do + { + if (ExecutableImage.filename() == Entry.szExeFile) + { + HANDLE ModuleSnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, Entry.th32ProcessID); + if (ModuleSnapshotHandle != INVALID_HANDLE_VALUE) + { + auto __ = MakeGuard([&]() { CloseHandle(ModuleSnapshotHandle); }); + MODULEENTRY32 ModuleEntry; + ModuleEntry.dwSize = sizeof(MODULEENTRY32); + if (Module32First(ModuleSnapshotHandle, (LPMODULEENTRY32)&ModuleEntry)) + { + std::filesystem::path EntryPath(ModuleEntry.szExePath); + if (EntryPath == ExecutableImage) + { + HANDLE Handle = + OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE | PROCESS_QUERY_INFORMATION, FALSE, Entry.th32ProcessID); + if (Handle == NULL) + { + return MakeErrorCodeFromLastError(); + } + OutHandle.Initialize((void*)Handle); + return {}; + } + } + } + } + } while (::Process32Next(ProcessSnapshotHandle, (LPPROCESSENTRY32)&Entry)); + } + return MakeErrorCodeFromLastError(); +#endif // ZEN_PLATFORM_WINDOWS +#if ZEN_PLATFORM_MAC + int Mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; + size_t BufferSize = 0; + + struct kinfo_proc* Processes = nullptr; + uint32_t ProcCount = 0; + + if (sysctl(Mib, 4, NULL, &BufferSize, NULL, 0) != -1 && BufferSize > 0) + { + struct kinfo_proc* Processes = (struct kinfo_proc*)malloc(BufferSize); + auto _ = MakeGuard([&]() { free(Processes); }); + if (sysctl(Mib, 4, Processes, &BufferSize, NULL, 0) != -1) + { + ProcCount = (uint32_t)(BufferSize / sizeof(struct kinfo_proc)); + char Buffer[PROC_PIDPATHINFO_MAXSIZE]; + for (uint32_t ProcIndex = 0; ProcIndex < ProcCount; ProcIndex++) + { + if (Processes[ProcIndex].kp_proc.p_stat != SZOMB) + { + pid_t Pid = Processes[ProcIndex].kp_proc.p_pid; + int Res = proc_pidpath(Pid, Buffer, sizeof(Buffer)); + if (Res > 0) + { + std::filesystem::path EntryPath(Buffer); + if (EntryPath == ExecutableImage) + { + std::error_code Ec; + OutHandle.Initialize(Pid, Ec); + return Ec; + } + } + } + } + } + } + return MakeErrorCodeFromLastError(); +#endif // ZEN_PLATFORM_MAC +#if ZEN_PLATFORM_LINUX + std::vector RunningPids; + DirectoryContent ProcList; + GetDirectoryContent("/proc", DirectoryContent::IncludeDirsFlag, ProcList); + for (const std::filesystem::path& EntryPath : ProcList.Directories) + { + std::string EntryName = EntryPath.stem(); + std::optional Pid = ParseInt(EntryName); + if (Pid.has_value()) + { + RunningPids.push_back(Pid.value()); + } + } + + for (uint32_t Pid : RunningPids) + { + char Status = GetPidStatus(Pid); + if (Status && (Status != 'Z')) + { + std::filesystem::path EntryPath = std::filesystem::path("/proc") / fmt::format("{}", Pid); + std::filesystem::path ExeLinkPath = EntryPath / "exe"; + char Link[4096]; + ssize_t BytesRead = readlink(ExeLinkPath.c_str(), Link, sizeof(Link) - 1); + if (BytesRead > 0) + { + Link[BytesRead] = '\0'; + std::filesystem::path ExePath(Link); + if (ExePath == ExecutableImage) + { + std::error_code Ec; + OutHandle.Initialize(Pid, Ec); + return Ec; + } + } + } + } + return {}; +#endif // ZEN_PLATFORM_LINUX +} + #if ZEN_WITH_TESTS void @@ -735,6 +952,14 @@ TEST_CASE("Process") CHECK(IsProcessRunning(Pid)); } +TEST_CASE("FindProcess") +{ + ProcessHandle Process; + std::error_code Ec = FindProcess(GetRunningExecutablePath(), Process); + CHECK(!Ec); + CHECK(Process.IsValid()); +} + TEST_CASE("BuildArgV") { const char* Words[] = {"one", "two", "three", "four", "five"}; -- cgit v1.2.3