This commit fixes two critical bugs in close_all_non_term_fd() that caused
undefined behavior and potential deadlocks when called after fork() before
execve() in multi-threaded programs.
Bug 1: Self-Referential Directory FD Closure
----------------------------------------------
When iterating through /proc/self/fd, opendir() creates a file descriptor
for the directory stream. This fd appears in the enumeration while we're
iterating, and if we close it, readdir() operates on a corrupted DIR*
stream, causing undefined behavior, crashes, or missed file descriptors.
Fix: Use dirfd() to obtain the directory's fd and explicitly skip closing it.
Bug 2: Heap Allocation After fork() in Multi-Threaded Programs
----------------------------------------------------------------
In multi-threaded programs, when fork() is called while other threads hold
malloc locks, the child process inherits a "frozen" state where those locks
remain locked (the owning threads don't exist in the child). Any heap
allocation (malloc/free/new/delete) in the child before execve() can deadlock.
The original code used:
- std::stol(std::string(dir->d_name)) - creates a temporary std::string
- std::find() - may allocate internally
Fix: Replace with heap-allocation-free alternatives:
- atoi(dir->d_name) instead of std::stol(std::string(...))
- Simple C loops instead of std::find()
Additional Improvements
-----------------------
1. Added close_range() syscall support (Linux 5.9+) with runtime detection
- O(1) atomic operation, most efficient method
- Only used when excludeFDs is empty (closes all fds >= 3)
- Falls back to /proc/self/fd iteration when excludeFDs is non-empty
2. Added extensive doxygen documentation covering:
- Security implications (preventing fd leaks to child processes)
- Resource management (preventing fd exhaustion)
- Deadlock prevention in multi-threaded fork() contexts
- Implementation details (three strategies: close_range, /proc/self/fd, rlimit)
- fork() safety design considerations
- Example usage and portability notes
3. Added required includes: dirent.h, sys/syscall.h, linux/close_range.h
Workflow Safety
---------------
The function is now safe to use in the common fork() -> close_all_non_term_fd()
-> execve() workflow, even in multi-threaded programs.
Files Modified
--------------
- lib/proxysql_utils.cpp