From cac944400f79e681255ed3870a8aa623a3e09c88 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 8 Oct 2025 18:13:47 -0600 Subject: [PATCH] Rework subprocess spawning using clone3/execveat --- dll/kernel32/internal.h | 1 + dll/kernel32/processthreadsapi.cpp | 10 ++-- dll/rpcrt4.cpp | 3 +- src/main.cpp | 14 ----- src/processes.cpp | 94 ++++++++++++++++++++---------- 5 files changed, 69 insertions(+), 53 deletions(-) diff --git a/dll/kernel32/internal.h b/dll/kernel32/internal.h index 87139f1..46ecc68 100644 --- a/dll/kernel32/internal.h +++ b/dll/kernel32/internal.h @@ -55,6 +55,7 @@ struct ProcessObject final : WaitableObject { static constexpr ObjectType kType = ObjectType::Process; pid_t pid; + pid_t tid; int pidfd; DWORD exitCode = STILL_ACTIVE; bool forcedExitCode = false; diff --git a/dll/kernel32/processthreadsapi.cpp b/dll/kernel32/processthreadsapi.cpp index 87db7ac..bf74af7 100644 --- a/dll/kernel32/processthreadsapi.cpp +++ b/dll/kernel32/processthreadsapi.cpp @@ -648,13 +648,13 @@ BOOL WIN_FUNC CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSE wibo::lastError = (spawnResult == ENOENT) ? ERROR_FILE_NOT_FOUND : ERROR_ACCESS_DENIED; return FALSE; } - pid_t pid = obj->pid; if (lpProcessInformation) { - lpProcessInformation->hProcess = wibo::handles().alloc(std::move(obj), 0 /* TODO: access */, 0); - lpProcessInformation->hThread = nullptr; - lpProcessInformation->dwProcessId = static_cast(pid); - lpProcessInformation->dwThreadId = 0; + lpProcessInformation->dwProcessId = static_cast(obj->pid); + lpProcessInformation->dwThreadId = static_cast(obj->tid); + lpProcessInformation->hProcess = wibo::handles().alloc(obj.clone(), 0 /* TODO: access */, 0); + // Give hThread a process handle for now + lpProcessInformation->hThread = wibo::handles().alloc(std::move(obj), 0 /* TODO: access */, 0); } (void)lpProcessAttributes; (void)lpThreadAttributes; diff --git a/dll/rpcrt4.cpp b/dll/rpcrt4.cpp index c6d13d7..2eb04fb 100644 --- a/dll/rpcrt4.cpp +++ b/dll/rpcrt4.cpp @@ -247,8 +247,7 @@ RPC_STATUS WIN_FUNC RpcStringFreeW(RPC_WSTR *string) { return RPC_S_OK; } -CLIENT_CALL_RETURN __attribute__((force_align_arg_pointer, callee_pop_aggregate_return(0), cdecl)) -NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...) { +CLIENT_CALL_RETURN WIN_ENTRY NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...) { DEBUG_LOG("STUB: NdrClientCall2 stubDescriptor=%p format=%p\n", stubDescriptor, format); CLIENT_CALL_RETURN result = {}; result.Simple = RPC_S_SERVER_UNAVAILABLE; diff --git a/src/main.cpp b/src/main.cpp index d6cdb53..10c91e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,6 @@ thread_local uint32_t wibo::lastError = 0; char **wibo::argv; int wibo::argc; std::filesystem::path wibo::guestExecutablePath; -std::string wibo::executableName; std::string wibo::commandLine; std::vector wibo::commandLineW; wibo::ModuleInfo *wibo::mainModule = nullptr; @@ -431,18 +430,6 @@ int main(int argc, char **argv) { return 1; } - // Try to resolve our own executable path - std::error_code ec; - auto resolved = std::filesystem::read_symlink("/proc/self/exe", ec); - std::string executablePath; - if (!ec) { - executablePath = resolved.string(); - } else { - const char *selfArg = argv[0] ? argv[0] : ""; - auto absCandidate = std::filesystem::absolute(selfArg, ec); - executablePath = ec ? std::string(selfArg) : absCandidate.string(); - } - if (!chdirPath.empty()) { if (chdir(chdirPath.c_str()) != 0) { std::string message = std::string("Failed to chdir to ") + chdirPath; @@ -562,7 +549,6 @@ int main(int argc, char **argv) { DEBUG_LOG("Command line: %s\n", wibo::commandLine.c_str()); wibo::guestExecutablePath = resolvedGuestPath; - wibo::executableName = executablePath; // Build argv/argc std::vector guestArgv; diff --git a/src/processes.cpp b/src/processes.cpp index f636c4f..bc9cca3 100644 --- a/src/processes.cpp +++ b/src/processes.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -372,54 +375,81 @@ std::optional resolveExecutable(const std::string &comman static int spawnInternal(const std::vector &args, Pin &pinOut) { std::vector argv; argv.reserve(args.size() + 2); - argv.push_back(const_cast(wibo::executableName.c_str())); + argv.push_back(const_cast("wibo")); for (auto &arg : args) { argv.push_back(const_cast(arg.c_str())); } argv.push_back(nullptr); - DEBUG_LOG("Spawning process: %s, args: [", wibo::executableName.c_str()); - for (size_t i = 0; i < args.size(); ++i) { - if (i != 0) { - DEBUG_LOG(", "); + if (wibo::debugEnabled) { + std::string cmdline; + for (size_t i = 1; i < argv.size() - 1; ++i) { + if (i != 1) { + cmdline += ' '; + } + cmdline += '\''; + cmdline += argv[i]; + cmdline += '\''; } - DEBUG_LOG("'%s'", args[i].c_str()); - } - DEBUG_LOG("]\n"); - - std::vector childEnv; - for (char **e = environ; *e != nullptr; ++e) { - if (strncmp(*e, "WIBO_DEBUG_INDENT=", 18) != 0) { - childEnv.push_back(*e); - } - } - std::string indent = "WIBO_DEBUG_INDENT=" + std::to_string(wibo::debugIndent + 1); - childEnv.push_back(strdup(indent.c_str())); - childEnv.push_back(nullptr); - - pid_t pid = -1; - int spawnResult = posix_spawn(&pid, wibo::executableName.c_str(), nullptr, nullptr, argv.data(), childEnv.data()); - if (spawnResult != 0) { - return spawnResult; + DEBUG_LOG("Spawning process: %s %s\n", argv[0], cmdline.c_str()); } - DEBUG_LOG("Spawned process with PID %d\n", pid); - - int pidfd = static_cast(syscall(SYS_pidfd_open, pid, 0)); - if (pidfd < 0) { - perror("pidfd_open"); - return false; + std::vector ownedEnv; + ownedEnv.reserve(256); + for (char **e = environ; *e; ++e) { + if (strncmp(*e, "WIBO_DEBUG_INDENT=", 18) != 0) + ownedEnv.emplace_back(*e); } + ownedEnv.emplace_back("WIBO_DEBUG_INDENT=" + std::to_string(wibo::debugIndent + 1)); + + std::vector envp; + envp.reserve(ownedEnv.size() + 1); + for (auto &s : ownedEnv) + envp.push_back(const_cast(s.c_str())); + envp.push_back(nullptr); + + int exefd = open("/proc/self/exe", O_PATH | O_CLOEXEC); + if (exefd < 0) { + int err = errno; + perror("open /proc/self/exe"); + return err; + } + + int pidfd = -1; + int tid = -1; + struct clone_args ca = {}; + ca.flags = CLONE_PIDFD | CLONE_PARENT_SETTID | CLONE_CLEAR_SIGHAND; + ca.pidfd = reinterpret_cast(&pidfd); + ca.parent_tid = reinterpret_cast(&tid); + pid_t pid = static_cast(syscall(SYS_clone3, &ca, sizeof(ca))); + if (pid < 0) { + close(exefd); + int err = errno; + perror("clone3"); + return err; + } else if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + int rc = execveat(exefd, "", argv.data(), envp.data(), AT_EMPTY_PATH); + fprintf(stderr, "execveat failed: %s\n", strerror(rc)); + _exit(127); + } + close(exefd); + + DEBUG_LOG("Spawned process with PID %d (pidfd=%d, tid=%d)\n", pid, pidfd, tid); auto obj = make_pin(pid, pidfd); + obj->tid = tid; pinOut = obj.clone(); - processes().addProcess(std::move(obj)); + if (!processes().addProcess(std::move(obj))) { + fprintf(stderr, "Failed to add process to process manager\n"); + abort(); + } return 0; } int spawnWithCommandLine(const std::string &applicationName, const std::string &commandLine, Pin &pinOut) { - if (wibo::executableName.empty() || (applicationName.empty() && commandLine.empty())) { + if (applicationName.empty() && commandLine.empty()) { return ENOENT; } @@ -438,7 +468,7 @@ int spawnWithCommandLine(const std::string &applicationName, const std::string & int spawnWithArgv(const std::string &applicationName, const std::vector &argv, Pin &pinOut) { - if (wibo::executableName.empty() || (applicationName.empty() && argv.empty())) { + if (applicationName.empty() && argv.empty()) { return ENOENT; }