Closing a pidfd tears down its entry in pidfd_table by guest fd number alone, after the fd slot has already been released, so a concurrent pidfd_open that reuses the number can lose its entry to the old fd's cleanup.
sys_close calls fd_snapshot_and_close, which marks the slot closed under fd_lock and drops the lock. fd_cleanup_entry then runs pidfd_cleanup(guest_fd) with no lock held. In that window another guest thread can call pidfd_open, receive the same fd number, and take a lower free index in pidfd_table. pidfd_cleanup then finds the new entry first and deactivates it, and the old entry stays active under that fd number:
- Thread A closes fd 5, a pidfd on pid 42 in slot 3. The fd slot is released.
- Thread B opens a pidfd on pid 99, receives fd 5, and is placed in slot 0.
- Thread A's
pidfd_cleanup(5) finds slot 0 first and tears down B's entry.
pidfd_send_signal(5, ...) on thread B looks up fd 5, finds slot 3, and signals pid 42.
#399 gave each pidfd a generation number so a failing monitor completes only its own entry, but the close path still matches by fd number. The cleanup callback receives only the fd number, so closing this means carrying the generation into cleanup, either by storing it in fd_entry_t or by passing the snapshot through, and having pidfd_cleanup match on it.
Raised in review of #399.
Closing a pidfd tears down its entry in
pidfd_tableby guest fd number alone, after the fd slot has already been released, so a concurrentpidfd_openthat reuses the number can lose its entry to the old fd's cleanup.sys_closecallsfd_snapshot_and_close, which marks the slot closed underfd_lockand drops the lock.fd_cleanup_entrythen runspidfd_cleanup(guest_fd)with no lock held. In that window another guest thread can callpidfd_open, receive the same fd number, and take a lower free index inpidfd_table.pidfd_cleanupthen finds the new entry first and deactivates it, and the old entry stays active under that fd number:pidfd_cleanup(5)finds slot 0 first and tears down B's entry.pidfd_send_signal(5, ...)on thread B looks up fd 5, finds slot 3, and signals pid 42.#399 gave each pidfd a generation number so a failing monitor completes only its own entry, but the close path still matches by fd number. The cleanup callback receives only the fd number, so closing this means carrying the generation into cleanup, either by storing it in
fd_entry_tor by passing the snapshot through, and havingpidfd_cleanupmatch on it.Raised in review of #399.