Loading include/client/gkfs_libc.hpp +16 −0 Changes for include/client/gkfs_libc.hpp: 16 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ */ #ifdef __cplusplus #include <filesystem> extern "C" { #endif Loading @@ -50,6 +51,7 @@ extern "C" { #include <sys/types.h> #include <dirent.h> #include <spawn.h> #ifndef _STAT_VER Loading Loading @@ -349,6 +351,20 @@ ssize_t flistxattr(int fd, char* list, size_t size); #endif //------------------------- Process Spawning ---------------------------------// int posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]); int posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]); //========================= C++ ABI Interceptions //==============================// // Intercepting C++ std::filesystem::remove_all Loading src/client/CMakeLists.txt +2 −0 Changes for src/client/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ target_sources(gkfs_intercept preload.cpp preload_context.cpp preload_util.cpp posix_spawn.cpp ) if (GKFS_BUILD_USER_LIB) Loading Loading @@ -113,6 +114,7 @@ target_sources( preload.cpp preload_context.cpp preload_util.cpp posix_spawn.cpp ) endif () Loading src/client/gkfs_libc.cpp +12 −0 Changes for src/client/gkfs_libc.cpp: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2460,6 +2460,18 @@ faccessat(int dfd, const char* path, int mode, int flags) throw() { int chdir(const char* path) throw() { gkfs_init_routine_placeholder(); if(path != nullptr && CTX && !CTX->mountdir().empty()) { std::string p(path); if(p.rfind(CTX->mountdir(), 0) == 0 && !GKFS_INTERCEPTION_ENABLED()) { long cres = syscall_no_intercept(SYS_chdir, CTX->mountdir().c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } setenv(gkfs::env::CWD, p.c_str(), 1); CTX->cwd(p); return 0; } } if(GKFS_INTERCEPTION_ENABLED()) { std::string resolved; // For chdir, path must be a directory. GekkoFS must update its CWD. Loading src/client/intercept.cpp +31 −0 Changes for src/client/intercept.cpp: 31 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1396,6 +1396,37 @@ internal_hook_guard_wrapper(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long* syscall_return_value) { if(!CTX->interception_enabled()) { if(syscall_number == SYS_chdir) { const char* path = reinterpret_cast<const char*>(arg0); if(path != nullptr && !CTX->mountdir().empty()) { std::string p(path); if(p.rfind(CTX->mountdir(), 0) == 0) { long cres = syscall_no_intercept_wrapper( SYS_chdir, CTX->mountdir().c_str()); if(cres < 0) { syscall_no_intercept_wrapper(SYS_chdir, "/"); } setenv(gkfs::env::CWD, p.c_str(), 1); CTX->cwd(p); *syscall_return_value = 0; return gkfs::syscall::hooked; } } } else if(syscall_number == SYS_getcwd) { char* buf = reinterpret_cast<char*>(arg0); unsigned long size = static_cast<unsigned long>(arg1); if(buf != nullptr && size > 0 && !CTX->cwd().empty() && !CTX->mountdir().empty() && CTX->cwd().rfind(CTX->mountdir(), 0) == 0) { if(CTX->cwd().size() + 1 > size) { *syscall_return_value = -ERANGE; } else { strcpy(buf, CTX->cwd().c_str()); *syscall_return_value = CTX->cwd().size() + 1; } return gkfs::syscall::hooked; } } return gkfs::syscall::forward_to_kernel; } Loading src/client/posix_spawn.cpp 0 → 100644 +522 −0 Changes for src/client/posix_spawn.cpp: 522 added lines, 0 removed lines. Original line number Diff line number Diff line /* Copyright 2018-2026, Barcelona Supercomputing Center (BSC), Spain Copyright 2015-2026, Johannes Gutenberg Universitaet Mainz, Germany This software was partially supported by the EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). This software was partially supported by the ADA-FS project under the SPPEXA project funded by the DFG. This software was partially supported by the the European Union’s Horizon 2020 JTI-EuroHPC research and innovation programme, by the project ADMIRE (Project ID: 956748, admire-eurohpc.eu) This project was partially promoted by the Ministry for Digital Transformation and the Civil Service, within the framework of the Recovery, Transformation and Resilience Plan - Funded by the European Union -NextGenerationEU. This file is part of GekkoFS' POSIX interface. GekkoFS' POSIX interface is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with GekkoFS' POSIX interface. If not, see <https://www.gnu.org/licenses/>. SPDX-License-Identifier: LGPL-3.0-or-later */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <client/hooks.hpp> #include <client/logging.hpp> #include <client/preload.hpp> #include <client/preload_context.hpp> #include <common/env_util.hpp> #include <csignal> #include <dlfcn.h> #include <fcntl.h> #include <sched.h> #include <spawn.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #include <string> #include <vector> extern char** environ; namespace { static int (*real_posix_spawn)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; static int (*real_posix_spawnp)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; int dlsym_posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(!real_posix_spawn) { real_posix_spawn = reinterpret_cast<int (*)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[])>(dlsym(RTLD_NEXT, "posix_spawn")); if(!real_posix_spawn) { LOG(ERROR, "dlsym failed for posix_spawn: {}", dlerror()); errno = ENOSYS; return -1; } } return real_posix_spawn(pid, path, file_actions, attrp, argv, envp); } int dlsym_posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(!real_posix_spawnp) { real_posix_spawnp = reinterpret_cast<int (*)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[])>(dlsym(RTLD_NEXT, "posix_spawnp")); if(!real_posix_spawnp) { LOG(ERROR, "dlsym failed for posix_spawnp: {}", dlerror()); errno = ENOSYS; return -1; } } return real_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } struct gkfs_spawn_action { enum { spawn_do_close, spawn_do_dup2, spawn_do_open, spawn_do_chdir, spawn_do_fchdir, spawn_do_closefrom, spawn_do_tcsetpgrp } tag; union { struct { int fd; } close_action; struct { int fd; int newfd; } dup2_action; struct { int fd; const char* path; int oflag; mode_t mode; } open_action; struct { const char* path; } chdir_action; struct { int fd; } fchdir_action; struct { int lowfd; } closefrom_action; struct { pid_t pgrp; } tcsetpgrp_action; } action; }; struct gkfs_posix_spawn_file_actions { int __allocated; int __used; struct gkfs_spawn_action* __actions; int __pad[16]; }; bool posix_spawn_involves_gkfs(const char* file_or_path, const posix_spawn_file_actions_t* file_actions) { if(!CTX || !CTX->interception_enabled()) { return false; } const std::string& mountdir = CTX->mountdir(); if(!mountdir.empty()) { const std::string& cwd = CTX->cwd(); if(cwd.rfind(mountdir, 0) == 0) { return true; } } if(file_or_path != nullptr && !mountdir.empty()) { std::string p(file_or_path); if(p.rfind(mountdir, 0) == 0) { return true; } } if(file_actions != nullptr && !mountdir.empty()) { auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>( file_actions); if(fa->__actions != nullptr && fa->__used > 0) { for(int i = 0; i < fa->__used; ++i) { const auto& act = fa->__actions[i]; if(act.tag == 3 /* spawn_do_chdir */) { if(act.action.chdir_action.path != nullptr) { std::string target(act.action.chdir_action.path); if(target.rfind(mountdir, 0) == 0) { return true; } } } else if(act.tag == 2 /* spawn_do_open */) { if(act.action.open_action.path != nullptr) { std::string target(act.action.open_action.path); if(target.rfind(mountdir, 0) == 0) { return true; } } } } } } return false; } int gkfs_posix_spawn_impl(pid_t* __restrict pid, const char* __restrict path_or_file, bool is_spawnp, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { int errpipe[2]; if(syscall_no_intercept(SYS_pipe2, errpipe, O_CLOEXEC) != 0) { return errno; } pid_t child_pid = fork(); if(child_pid < 0) { int err = errno; syscall_no_intercept(SYS_close, errpipe[0]); syscall_no_intercept(SYS_close, errpipe[1]); return err; } if(child_pid > 0) { // Parent process syscall_no_intercept(SYS_close, errpipe[1]); int child_error = 0; long n = 0; do { n = syscall_no_intercept(SYS_read, errpipe[0], &child_error, sizeof(child_error)); } while(n < 0 && syscall_error_code(n) == EINTR); syscall_no_intercept(SYS_close, errpipe[0]); if(n > 0) { int status = 0; waitpid(child_pid, &status, 0); return child_error; } if(pid != nullptr) { *pid = child_pid; } return 0; } // Child process: execute completely via direct syscalls without RPC syscall_no_intercept(SYS_close, errpipe[0]); auto fail_child = [&](int err) { syscall_no_intercept(SYS_write, errpipe[1], &err, sizeof(err)); _exit(127); }; if(attrp != nullptr) { short int flags = 0; posix_spawnattr_getflags(attrp, &flags); if(flags & POSIX_SPAWN_RESETIDS) { if(seteuid(getuid()) != 0 || setegid(getgid()) != 0) { fail_child(errno); } } if(flags & POSIX_SPAWN_SETPGROUP) { pid_t pgroup = 0; posix_spawnattr_getpgroup(attrp, &pgroup); if(setpgid(0, pgroup) != 0) { fail_child(errno); } } #ifdef POSIX_SPAWN_SETSID if(flags & POSIX_SPAWN_SETSID) { if(setsid() == -1) { fail_child(errno); } } #endif if(flags & POSIX_SPAWN_SETSIGMASK) { sigset_t mask; posix_spawnattr_getsigmask(attrp, &mask); sigprocmask(SIG_SETMASK, &mask, nullptr); } if(flags & POSIX_SPAWN_SETSIGDEF) { sigset_t defmask; posix_spawnattr_getsigdefault(attrp, &defmask); for(int sig = 1; sig < NSIG; ++sig) { if(sigismember(&defmask, sig)) { signal(sig, SIG_DFL); } } } if(flags & POSIX_SPAWN_SETSCHEDPARAM) { struct sched_param param; posix_spawnattr_getschedparam(attrp, ¶m); sched_setparam(0, ¶m); } if(flags & POSIX_SPAWN_SETSCHEDULER) { int policy = 0; struct sched_param param; posix_spawnattr_getschedpolicy(attrp, &policy); posix_spawnattr_getschedparam(attrp, ¶m); sched_setscheduler(0, policy, ¶m); } } std::string effective_cwd = CTX ? CTX->cwd() : ""; const std::string& mountdir = CTX ? CTX->mountdir() : ""; if(file_actions != nullptr) { auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>( file_actions); if(fa->__actions != nullptr && fa->__used > 0) { for(int i = 0; i < fa->__used; ++i) { const auto& act = fa->__actions[i]; switch(act.tag) { case 0: /* spawn_do_close */ { syscall_no_intercept(SYS_close, act.action.close_action.fd); break; } case 1: /* spawn_do_dup2 */ { long res = syscall_no_intercept( SYS_dup2, act.action.dup2_action.fd, act.action.dup2_action.newfd); if(res < 0) { fail_child(syscall_error_code(res)); } break; } case 2: /* spawn_do_open */ { long res = syscall_no_intercept( SYS_openat, AT_FDCWD, act.action.open_action.path, act.action.open_action.oflag, act.action.open_action.mode); if(res < 0) { fail_child(syscall_error_code(res)); } int fd = static_cast<int>(res); if(fd != act.action.open_action.fd) { long dres = syscall_no_intercept( SYS_dup2, fd, act.action.open_action.fd); syscall_no_intercept(SYS_close, fd); if(dres < 0) { fail_child(syscall_error_code(dres)); } } break; } case 3: /* spawn_do_chdir */ { if(act.action.chdir_action.path != nullptr) { std::string target = act.action.chdir_action.path; if(!mountdir.empty() && target.rfind(mountdir, 0) == 0) { effective_cwd = target; long cres = syscall_no_intercept( SYS_chdir, mountdir.c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } } else { effective_cwd.clear(); long cres = syscall_no_intercept( SYS_chdir, target.c_str()); if(cres < 0) { fail_child(syscall_error_code(cres)); } } } break; } case 4: /* spawn_do_fchdir */ { long cres = syscall_no_intercept( SYS_fchdir, act.action.fchdir_action.fd); if(cres < 0) { fail_child(syscall_error_code(cres)); } break; } case 5: /* spawn_do_closefrom */ { #ifdef SYS_close_range syscall_no_intercept(SYS_close_range, act.action.closefrom_action.lowfd, ~0U, 0); #else for(int fd = act.action.closefrom_action.lowfd; fd < 1024; ++fd) { if(fd != errpipe[1]) syscall_no_intercept(SYS_close, fd); } #endif break; } case 6: /* spawn_do_tcsetpgrp */ { tcsetpgrp(act.action.tcsetpgrp_action.pgrp, act.action.tcsetpgrp_action.pgrp); break; } default: break; } } } } if(!effective_cwd.empty() && !mountdir.empty() && effective_cwd.rfind(mountdir, 0) == 0) { long cres = syscall_no_intercept(SYS_chdir, mountdir.c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } } char* const* base_env = envp != nullptr ? envp : environ; std::vector<std::string> env_strings; std::vector<char*> new_env; bool has_gkfs_cwd = false; bool has_gkfs_mountdir = false; bool has_gkfs_hosts = false; bool has_preload = false; if(base_env != nullptr) { for(char* const* e = base_env; *e != nullptr; ++e) { std::string var(*e); if(var.rfind("LIBGKFS_CWD=", 0) == 0) { if(!effective_cwd.empty()) { has_gkfs_cwd = true; env_strings.push_back("LIBGKFS_CWD=" + effective_cwd); } } else { if(var.rfind("LIBGKFS_MOUNTDIR=", 0) == 0) has_gkfs_mountdir = true; if(var.rfind("LIBGKFS_HOSTS_FILE=", 0) == 0) has_gkfs_hosts = true; if(var.rfind("LD_PRELOAD=", 0) == 0) has_preload = true; env_strings.push_back(var); } } } if(!has_gkfs_cwd && !effective_cwd.empty()) { env_strings.push_back("LIBGKFS_CWD=" + effective_cwd); } if(!has_gkfs_mountdir && !mountdir.empty()) { env_strings.push_back("LIBGKFS_MOUNTDIR=" + mountdir); } if(!has_gkfs_hosts && ::getenv("LIBGKFS_HOSTS_FILE") != nullptr) { env_strings.push_back(std::string("LIBGKFS_HOSTS_FILE=") + ::getenv("LIBGKFS_HOSTS_FILE")); } if(!has_preload && ::getenv("LD_PRELOAD") != nullptr) { env_strings.push_back(std::string("LD_PRELOAD=") + ::getenv("LD_PRELOAD")); } new_env.reserve(env_strings.size() + 1); for(auto& s : env_strings) { new_env.push_back(const_cast<char*>(s.c_str())); } new_env.push_back(nullptr); if(is_spawnp) { execvpe(path_or_file, argv, new_env.data()); } else { execve(path_or_file, argv, new_env.data()); } int err = errno; fail_child(err); return 127; } } // namespace extern "C" { int posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(posix_spawn_involves_gkfs(path, file_actions)) { LOG(DEBUG, "[GKFS] posix_spawn handled internally for path='{}'", path); return gkfs_posix_spawn_impl(pid, path, false, file_actions, attrp, argv, envp); } return dlsym_posix_spawn(pid, path, file_actions, attrp, argv, envp); } int posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(posix_spawn_involves_gkfs(file, file_actions)) { LOG(DEBUG, "[GKFS] posix_spawnp handled internally for file='{}'", file); return gkfs_posix_spawn_impl(pid, file, true, file_actions, attrp, argv, envp); } return dlsym_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } } // extern "C" Loading
include/client/gkfs_libc.hpp +16 −0 Changes for include/client/gkfs_libc.hpp: 16 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ */ #ifdef __cplusplus #include <filesystem> extern "C" { #endif Loading @@ -50,6 +51,7 @@ extern "C" { #include <sys/types.h> #include <dirent.h> #include <spawn.h> #ifndef _STAT_VER Loading Loading @@ -349,6 +351,20 @@ ssize_t flistxattr(int fd, char* list, size_t size); #endif //------------------------- Process Spawning ---------------------------------// int posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]); int posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]); //========================= C++ ABI Interceptions //==============================// // Intercepting C++ std::filesystem::remove_all Loading
src/client/CMakeLists.txt +2 −0 Changes for src/client/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ target_sources(gkfs_intercept preload.cpp preload_context.cpp preload_util.cpp posix_spawn.cpp ) if (GKFS_BUILD_USER_LIB) Loading Loading @@ -113,6 +114,7 @@ target_sources( preload.cpp preload_context.cpp preload_util.cpp posix_spawn.cpp ) endif () Loading
src/client/gkfs_libc.cpp +12 −0 Changes for src/client/gkfs_libc.cpp: 12 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2460,6 +2460,18 @@ faccessat(int dfd, const char* path, int mode, int flags) throw() { int chdir(const char* path) throw() { gkfs_init_routine_placeholder(); if(path != nullptr && CTX && !CTX->mountdir().empty()) { std::string p(path); if(p.rfind(CTX->mountdir(), 0) == 0 && !GKFS_INTERCEPTION_ENABLED()) { long cres = syscall_no_intercept(SYS_chdir, CTX->mountdir().c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } setenv(gkfs::env::CWD, p.c_str(), 1); CTX->cwd(p); return 0; } } if(GKFS_INTERCEPTION_ENABLED()) { std::string resolved; // For chdir, path must be a directory. GekkoFS must update its CWD. Loading
src/client/intercept.cpp +31 −0 Changes for src/client/intercept.cpp: 31 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1396,6 +1396,37 @@ internal_hook_guard_wrapper(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long* syscall_return_value) { if(!CTX->interception_enabled()) { if(syscall_number == SYS_chdir) { const char* path = reinterpret_cast<const char*>(arg0); if(path != nullptr && !CTX->mountdir().empty()) { std::string p(path); if(p.rfind(CTX->mountdir(), 0) == 0) { long cres = syscall_no_intercept_wrapper( SYS_chdir, CTX->mountdir().c_str()); if(cres < 0) { syscall_no_intercept_wrapper(SYS_chdir, "/"); } setenv(gkfs::env::CWD, p.c_str(), 1); CTX->cwd(p); *syscall_return_value = 0; return gkfs::syscall::hooked; } } } else if(syscall_number == SYS_getcwd) { char* buf = reinterpret_cast<char*>(arg0); unsigned long size = static_cast<unsigned long>(arg1); if(buf != nullptr && size > 0 && !CTX->cwd().empty() && !CTX->mountdir().empty() && CTX->cwd().rfind(CTX->mountdir(), 0) == 0) { if(CTX->cwd().size() + 1 > size) { *syscall_return_value = -ERANGE; } else { strcpy(buf, CTX->cwd().c_str()); *syscall_return_value = CTX->cwd().size() + 1; } return gkfs::syscall::hooked; } } return gkfs::syscall::forward_to_kernel; } Loading
src/client/posix_spawn.cpp 0 → 100644 +522 −0 Changes for src/client/posix_spawn.cpp: 522 added lines, 0 removed lines. Original line number Diff line number Diff line /* Copyright 2018-2026, Barcelona Supercomputing Center (BSC), Spain Copyright 2015-2026, Johannes Gutenberg Universitaet Mainz, Germany This software was partially supported by the EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). This software was partially supported by the ADA-FS project under the SPPEXA project funded by the DFG. This software was partially supported by the the European Union’s Horizon 2020 JTI-EuroHPC research and innovation programme, by the project ADMIRE (Project ID: 956748, admire-eurohpc.eu) This project was partially promoted by the Ministry for Digital Transformation and the Civil Service, within the framework of the Recovery, Transformation and Resilience Plan - Funded by the European Union -NextGenerationEU. This file is part of GekkoFS' POSIX interface. GekkoFS' POSIX interface is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with GekkoFS' POSIX interface. If not, see <https://www.gnu.org/licenses/>. SPDX-License-Identifier: LGPL-3.0-or-later */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <client/hooks.hpp> #include <client/logging.hpp> #include <client/preload.hpp> #include <client/preload_context.hpp> #include <common/env_util.hpp> #include <csignal> #include <dlfcn.h> #include <fcntl.h> #include <sched.h> #include <spawn.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #include <string> #include <vector> extern char** environ; namespace { static int (*real_posix_spawn)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; static int (*real_posix_spawnp)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; int dlsym_posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(!real_posix_spawn) { real_posix_spawn = reinterpret_cast<int (*)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[])>(dlsym(RTLD_NEXT, "posix_spawn")); if(!real_posix_spawn) { LOG(ERROR, "dlsym failed for posix_spawn: {}", dlerror()); errno = ENOSYS; return -1; } } return real_posix_spawn(pid, path, file_actions, attrp, argv, envp); } int dlsym_posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(!real_posix_spawnp) { real_posix_spawnp = reinterpret_cast<int (*)( pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[])>(dlsym(RTLD_NEXT, "posix_spawnp")); if(!real_posix_spawnp) { LOG(ERROR, "dlsym failed for posix_spawnp: {}", dlerror()); errno = ENOSYS; return -1; } } return real_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } struct gkfs_spawn_action { enum { spawn_do_close, spawn_do_dup2, spawn_do_open, spawn_do_chdir, spawn_do_fchdir, spawn_do_closefrom, spawn_do_tcsetpgrp } tag; union { struct { int fd; } close_action; struct { int fd; int newfd; } dup2_action; struct { int fd; const char* path; int oflag; mode_t mode; } open_action; struct { const char* path; } chdir_action; struct { int fd; } fchdir_action; struct { int lowfd; } closefrom_action; struct { pid_t pgrp; } tcsetpgrp_action; } action; }; struct gkfs_posix_spawn_file_actions { int __allocated; int __used; struct gkfs_spawn_action* __actions; int __pad[16]; }; bool posix_spawn_involves_gkfs(const char* file_or_path, const posix_spawn_file_actions_t* file_actions) { if(!CTX || !CTX->interception_enabled()) { return false; } const std::string& mountdir = CTX->mountdir(); if(!mountdir.empty()) { const std::string& cwd = CTX->cwd(); if(cwd.rfind(mountdir, 0) == 0) { return true; } } if(file_or_path != nullptr && !mountdir.empty()) { std::string p(file_or_path); if(p.rfind(mountdir, 0) == 0) { return true; } } if(file_actions != nullptr && !mountdir.empty()) { auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>( file_actions); if(fa->__actions != nullptr && fa->__used > 0) { for(int i = 0; i < fa->__used; ++i) { const auto& act = fa->__actions[i]; if(act.tag == 3 /* spawn_do_chdir */) { if(act.action.chdir_action.path != nullptr) { std::string target(act.action.chdir_action.path); if(target.rfind(mountdir, 0) == 0) { return true; } } } else if(act.tag == 2 /* spawn_do_open */) { if(act.action.open_action.path != nullptr) { std::string target(act.action.open_action.path); if(target.rfind(mountdir, 0) == 0) { return true; } } } } } } return false; } int gkfs_posix_spawn_impl(pid_t* __restrict pid, const char* __restrict path_or_file, bool is_spawnp, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { int errpipe[2]; if(syscall_no_intercept(SYS_pipe2, errpipe, O_CLOEXEC) != 0) { return errno; } pid_t child_pid = fork(); if(child_pid < 0) { int err = errno; syscall_no_intercept(SYS_close, errpipe[0]); syscall_no_intercept(SYS_close, errpipe[1]); return err; } if(child_pid > 0) { // Parent process syscall_no_intercept(SYS_close, errpipe[1]); int child_error = 0; long n = 0; do { n = syscall_no_intercept(SYS_read, errpipe[0], &child_error, sizeof(child_error)); } while(n < 0 && syscall_error_code(n) == EINTR); syscall_no_intercept(SYS_close, errpipe[0]); if(n > 0) { int status = 0; waitpid(child_pid, &status, 0); return child_error; } if(pid != nullptr) { *pid = child_pid; } return 0; } // Child process: execute completely via direct syscalls without RPC syscall_no_intercept(SYS_close, errpipe[0]); auto fail_child = [&](int err) { syscall_no_intercept(SYS_write, errpipe[1], &err, sizeof(err)); _exit(127); }; if(attrp != nullptr) { short int flags = 0; posix_spawnattr_getflags(attrp, &flags); if(flags & POSIX_SPAWN_RESETIDS) { if(seteuid(getuid()) != 0 || setegid(getgid()) != 0) { fail_child(errno); } } if(flags & POSIX_SPAWN_SETPGROUP) { pid_t pgroup = 0; posix_spawnattr_getpgroup(attrp, &pgroup); if(setpgid(0, pgroup) != 0) { fail_child(errno); } } #ifdef POSIX_SPAWN_SETSID if(flags & POSIX_SPAWN_SETSID) { if(setsid() == -1) { fail_child(errno); } } #endif if(flags & POSIX_SPAWN_SETSIGMASK) { sigset_t mask; posix_spawnattr_getsigmask(attrp, &mask); sigprocmask(SIG_SETMASK, &mask, nullptr); } if(flags & POSIX_SPAWN_SETSIGDEF) { sigset_t defmask; posix_spawnattr_getsigdefault(attrp, &defmask); for(int sig = 1; sig < NSIG; ++sig) { if(sigismember(&defmask, sig)) { signal(sig, SIG_DFL); } } } if(flags & POSIX_SPAWN_SETSCHEDPARAM) { struct sched_param param; posix_spawnattr_getschedparam(attrp, ¶m); sched_setparam(0, ¶m); } if(flags & POSIX_SPAWN_SETSCHEDULER) { int policy = 0; struct sched_param param; posix_spawnattr_getschedpolicy(attrp, &policy); posix_spawnattr_getschedparam(attrp, ¶m); sched_setscheduler(0, policy, ¶m); } } std::string effective_cwd = CTX ? CTX->cwd() : ""; const std::string& mountdir = CTX ? CTX->mountdir() : ""; if(file_actions != nullptr) { auto fa = reinterpret_cast<const struct gkfs_posix_spawn_file_actions*>( file_actions); if(fa->__actions != nullptr && fa->__used > 0) { for(int i = 0; i < fa->__used; ++i) { const auto& act = fa->__actions[i]; switch(act.tag) { case 0: /* spawn_do_close */ { syscall_no_intercept(SYS_close, act.action.close_action.fd); break; } case 1: /* spawn_do_dup2 */ { long res = syscall_no_intercept( SYS_dup2, act.action.dup2_action.fd, act.action.dup2_action.newfd); if(res < 0) { fail_child(syscall_error_code(res)); } break; } case 2: /* spawn_do_open */ { long res = syscall_no_intercept( SYS_openat, AT_FDCWD, act.action.open_action.path, act.action.open_action.oflag, act.action.open_action.mode); if(res < 0) { fail_child(syscall_error_code(res)); } int fd = static_cast<int>(res); if(fd != act.action.open_action.fd) { long dres = syscall_no_intercept( SYS_dup2, fd, act.action.open_action.fd); syscall_no_intercept(SYS_close, fd); if(dres < 0) { fail_child(syscall_error_code(dres)); } } break; } case 3: /* spawn_do_chdir */ { if(act.action.chdir_action.path != nullptr) { std::string target = act.action.chdir_action.path; if(!mountdir.empty() && target.rfind(mountdir, 0) == 0) { effective_cwd = target; long cres = syscall_no_intercept( SYS_chdir, mountdir.c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } } else { effective_cwd.clear(); long cres = syscall_no_intercept( SYS_chdir, target.c_str()); if(cres < 0) { fail_child(syscall_error_code(cres)); } } } break; } case 4: /* spawn_do_fchdir */ { long cres = syscall_no_intercept( SYS_fchdir, act.action.fchdir_action.fd); if(cres < 0) { fail_child(syscall_error_code(cres)); } break; } case 5: /* spawn_do_closefrom */ { #ifdef SYS_close_range syscall_no_intercept(SYS_close_range, act.action.closefrom_action.lowfd, ~0U, 0); #else for(int fd = act.action.closefrom_action.lowfd; fd < 1024; ++fd) { if(fd != errpipe[1]) syscall_no_intercept(SYS_close, fd); } #endif break; } case 6: /* spawn_do_tcsetpgrp */ { tcsetpgrp(act.action.tcsetpgrp_action.pgrp, act.action.tcsetpgrp_action.pgrp); break; } default: break; } } } } if(!effective_cwd.empty() && !mountdir.empty() && effective_cwd.rfind(mountdir, 0) == 0) { long cres = syscall_no_intercept(SYS_chdir, mountdir.c_str()); if(cres < 0) { syscall_no_intercept(SYS_chdir, "/"); } } char* const* base_env = envp != nullptr ? envp : environ; std::vector<std::string> env_strings; std::vector<char*> new_env; bool has_gkfs_cwd = false; bool has_gkfs_mountdir = false; bool has_gkfs_hosts = false; bool has_preload = false; if(base_env != nullptr) { for(char* const* e = base_env; *e != nullptr; ++e) { std::string var(*e); if(var.rfind("LIBGKFS_CWD=", 0) == 0) { if(!effective_cwd.empty()) { has_gkfs_cwd = true; env_strings.push_back("LIBGKFS_CWD=" + effective_cwd); } } else { if(var.rfind("LIBGKFS_MOUNTDIR=", 0) == 0) has_gkfs_mountdir = true; if(var.rfind("LIBGKFS_HOSTS_FILE=", 0) == 0) has_gkfs_hosts = true; if(var.rfind("LD_PRELOAD=", 0) == 0) has_preload = true; env_strings.push_back(var); } } } if(!has_gkfs_cwd && !effective_cwd.empty()) { env_strings.push_back("LIBGKFS_CWD=" + effective_cwd); } if(!has_gkfs_mountdir && !mountdir.empty()) { env_strings.push_back("LIBGKFS_MOUNTDIR=" + mountdir); } if(!has_gkfs_hosts && ::getenv("LIBGKFS_HOSTS_FILE") != nullptr) { env_strings.push_back(std::string("LIBGKFS_HOSTS_FILE=") + ::getenv("LIBGKFS_HOSTS_FILE")); } if(!has_preload && ::getenv("LD_PRELOAD") != nullptr) { env_strings.push_back(std::string("LD_PRELOAD=") + ::getenv("LD_PRELOAD")); } new_env.reserve(env_strings.size() + 1); for(auto& s : env_strings) { new_env.push_back(const_cast<char*>(s.c_str())); } new_env.push_back(nullptr); if(is_spawnp) { execvpe(path_or_file, argv, new_env.data()); } else { execve(path_or_file, argv, new_env.data()); } int err = errno; fail_child(err); return 127; } } // namespace extern "C" { int posix_spawn(pid_t* __restrict pid, const char* __restrict path, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(posix_spawn_involves_gkfs(path, file_actions)) { LOG(DEBUG, "[GKFS] posix_spawn handled internally for path='{}'", path); return gkfs_posix_spawn_impl(pid, path, false, file_actions, attrp, argv, envp); } return dlsym_posix_spawn(pid, path, file_actions, attrp, argv, envp); } int posix_spawnp(pid_t* __restrict pid, const char* __restrict file, const posix_spawn_file_actions_t* __restrict file_actions, const posix_spawnattr_t* __restrict attrp, char* const argv[], char* const envp[]) { if(posix_spawn_involves_gkfs(file, file_actions)) { LOG(DEBUG, "[GKFS] posix_spawnp handled internally for file='{}'", file); return gkfs_posix_spawn_impl(pid, file, true, file_actions, attrp, argv, envp); } return dlsym_posix_spawnp(pid, file, file_actions, attrp, argv, envp); } } // extern "C"