Loading src/client/intercept.cpp +16 −3 Changes for src/client/intercept.cpp: 16 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ #include <cerrno> #include <cstring> #include <atomic> extern "C" { #include <syscall.h> Loading Loading @@ -80,6 +81,8 @@ void (*intercept_hook_point_post_kernel)(long syscall_number, long arg0, #endif namespace { std::atomic<bool> auxiliary_hooks_enabled{true}; static pthread_key_t reentrance_guard_key; static pthread_once_t key_once_control = PTHREAD_ONCE_INIT; static void Loading Loading @@ -1310,6 +1313,8 @@ socketcall_wrapper(long syscall_number, long& arg0, long& arg1, long& arg2, void hook_forwarded_syscall(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long result) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; pthread_once(&key_once_control, make_key); if(pthread_getspecific(reentrance_guard_key) != NULL) { Loading Loading @@ -1343,6 +1348,8 @@ void hook_clone_at_child(unsigned long flags, void* child_stack, int* ptid, // cppcheck-suppress constParameterCallback int* ctid, long newtls) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD) const long args[gkfs::syscall::MAX_ARGS] = { Loading @@ -1367,6 +1374,8 @@ void hook_clone_at_parent(unsigned long flags, void* child_stack, int* ptid, // cppcheck-suppress constParameterCallback int* ctid, long newtls, long returned_pid) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD) const long args[gkfs::syscall::MAX_ARGS] = { Loading Loading @@ -1528,6 +1537,7 @@ void start_self_interception() { LOG(DEBUG, "Enabling syscall interception for self"); auxiliary_hooks_enabled.store(true, std::memory_order_release); intercept_hook_point = internal_hook_guard_wrapper; intercept_hook_point_post_kernel = hook_forwarded_syscall; Loading @@ -1541,6 +1551,7 @@ start_interception() { assert(CTX->interception_enabled()); LOG(DEBUG, "Enabling syscall interception for client process"); auxiliary_hooks_enabled.store(true, std::memory_order_release); // Set up the callback function pointer intercept_hook_point = hook_guard_wrapper; Loading @@ -1557,9 +1568,11 @@ stop_interception() { // syscalls (during exit/shutdown/atexit) are forwarded to kernel instead of // triggering SIGTRAP. intercept_hook_point = passthrough_hook_wrapper; intercept_hook_point_post_kernel = nullptr; intercept_hook_point_clone_child = nullptr; intercept_hook_point_clone_parent = nullptr; // syscall_intercept checks these pointers before loading them again for // the call. Clearing them races with runtime threads still in a syscall // (including during atexit), allowing a call through nullptr. Leave the // callbacks installed and make them no-ops through an atomic flag instead. auxiliary_hooks_enabled.store(false, std::memory_order_release); } } // namespace gkfs::preload tests/integration/syscalls/test_preload_threaded_io.py +49 −0 Changes for tests/integration/syscalls/test_preload_threaded_io.py: 49 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,3 +38,52 @@ PY timeout=60, ) assert command.exit_code == 0, command.stderr.decode(errors="replace") def test_stop_interception_preserves_callbacks(gkfs_shell, gkfs_daemon): """Stopping interception must not null callbacks used by runtime threads.""" command = gkfs_shell.run(sys.executable, "-c", """ import ctypes import os import threading lib = ctypes.CDLL(None) stop = getattr(lib, '_ZN4gkfs7preload17stop_interceptionEv') start = getattr(lib, '_ZN4gkfs7preload18start_interceptionEv') stop.restype = start.restype = None names = ('intercept_hook_point_post_kernel', 'intercept_hook_point_clone_child', 'intercept_hook_point_clone_parent') callbacks = [ctypes.c_void_p.in_dll(lib, name) for name in names] original = [callback.value for callback in callbacks] assert all(original) stop() try: assert [callback.value for callback in callbacks] == original finally: start() done = threading.Event() ready = threading.Barrier(5) def worker(): ready.wait() while not done.is_set(): os.stat('/tmp') threads = [threading.Thread(target=worker) for _ in range(4)] for thread in threads: thread.start() ready.wait() try: for _ in range(2000): stop() start() finally: start() done.set() for thread in threads: thread.join() print('PASS interception callback shutdown race') """, timeout=60, env={"LIBGKFS_LOG": "none"}) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS interception callback shutdown race" in command.stdout No newline at end of file Loading
src/client/intercept.cpp +16 −3 Changes for src/client/intercept.cpp: 16 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ #include <cerrno> #include <cstring> #include <atomic> extern "C" { #include <syscall.h> Loading Loading @@ -80,6 +81,8 @@ void (*intercept_hook_point_post_kernel)(long syscall_number, long arg0, #endif namespace { std::atomic<bool> auxiliary_hooks_enabled{true}; static pthread_key_t reentrance_guard_key; static pthread_once_t key_once_control = PTHREAD_ONCE_INIT; static void Loading Loading @@ -1310,6 +1313,8 @@ socketcall_wrapper(long syscall_number, long& arg0, long& arg1, long& arg2, void hook_forwarded_syscall(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, long arg5, long result) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; pthread_once(&key_once_control, make_key); if(pthread_getspecific(reentrance_guard_key) != NULL) { Loading Loading @@ -1343,6 +1348,8 @@ void hook_clone_at_child(unsigned long flags, void* child_stack, int* ptid, // cppcheck-suppress constParameterCallback int* ctid, long newtls) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD) const long args[gkfs::syscall::MAX_ARGS] = { Loading @@ -1367,6 +1374,8 @@ void hook_clone_at_parent(unsigned long flags, void* child_stack, int* ptid, // cppcheck-suppress constParameterCallback int* ctid, long newtls, long returned_pid) { if(!auxiliary_hooks_enabled.load(std::memory_order_acquire)) return; #if defined(GKFS_ENABLE_LOGGING) && defined(GKFS_DEBUG_BUILD) const long args[gkfs::syscall::MAX_ARGS] = { Loading Loading @@ -1528,6 +1537,7 @@ void start_self_interception() { LOG(DEBUG, "Enabling syscall interception for self"); auxiliary_hooks_enabled.store(true, std::memory_order_release); intercept_hook_point = internal_hook_guard_wrapper; intercept_hook_point_post_kernel = hook_forwarded_syscall; Loading @@ -1541,6 +1551,7 @@ start_interception() { assert(CTX->interception_enabled()); LOG(DEBUG, "Enabling syscall interception for client process"); auxiliary_hooks_enabled.store(true, std::memory_order_release); // Set up the callback function pointer intercept_hook_point = hook_guard_wrapper; Loading @@ -1557,9 +1568,11 @@ stop_interception() { // syscalls (during exit/shutdown/atexit) are forwarded to kernel instead of // triggering SIGTRAP. intercept_hook_point = passthrough_hook_wrapper; intercept_hook_point_post_kernel = nullptr; intercept_hook_point_clone_child = nullptr; intercept_hook_point_clone_parent = nullptr; // syscall_intercept checks these pointers before loading them again for // the call. Clearing them races with runtime threads still in a syscall // (including during atexit), allowing a call through nullptr. Leave the // callbacks installed and make them no-ops through an atomic flag instead. auxiliary_hooks_enabled.store(false, std::memory_order_release); } } // namespace gkfs::preload
tests/integration/syscalls/test_preload_threaded_io.py +49 −0 Changes for tests/integration/syscalls/test_preload_threaded_io.py: 49 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -38,3 +38,52 @@ PY timeout=60, ) assert command.exit_code == 0, command.stderr.decode(errors="replace") def test_stop_interception_preserves_callbacks(gkfs_shell, gkfs_daemon): """Stopping interception must not null callbacks used by runtime threads.""" command = gkfs_shell.run(sys.executable, "-c", """ import ctypes import os import threading lib = ctypes.CDLL(None) stop = getattr(lib, '_ZN4gkfs7preload17stop_interceptionEv') start = getattr(lib, '_ZN4gkfs7preload18start_interceptionEv') stop.restype = start.restype = None names = ('intercept_hook_point_post_kernel', 'intercept_hook_point_clone_child', 'intercept_hook_point_clone_parent') callbacks = [ctypes.c_void_p.in_dll(lib, name) for name in names] original = [callback.value for callback in callbacks] assert all(original) stop() try: assert [callback.value for callback in callbacks] == original finally: start() done = threading.Event() ready = threading.Barrier(5) def worker(): ready.wait() while not done.is_set(): os.stat('/tmp') threads = [threading.Thread(target=worker) for _ in range(4)] for thread in threads: thread.start() ready.wait() try: for _ in range(2000): stop() start() finally: start() done.set() for thread in threads: thread.join() print('PASS interception callback shutdown race') """, timeout=60, env={"LIBGKFS_LOG": "none"}) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS interception callback shutdown race" in command.stdout No newline at end of file