From dcdf9f35d5e3b96f8ac86d68b927f6891d39197e Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 26 Oct 2026 14:26:56 +0100 Subject: [PATCH] fix(libkernel): drop the kqueue and knotes of a descriptor that dup2 replaces with a socket dup2 from a guest socket notified the event queues only when the replaced descriptor was itself a guest socket. A kqueue replaced this way stayed registered, so kevent on the number that now names the socket still returned the old queue's events, and knotes registered on a replaced pipe or file stayed attached. FreeBSD 11.4's kern_dup closes the replaced file through closefp, whose knote_fdclose drops every knote on the descriptor, as the other dup2 paths here already do. Fixes #11429 Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 2 +- core/libs/tests/GuestKqueue.cpp | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 0ae0c29327..f1cc80860a 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -731,7 +731,7 @@ int GuestSockets::DuplicateTo(int descriptor, int target) { } } } - if (replaced) EqueueDescriptorClosed(target); + EqueueDescriptorClosed(target); { std::lock_guard lock(socketsMutex); closeOnExecDescriptors.erase(target); diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index c421f32f45..cf542bb265 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -148,6 +148,30 @@ static void CreateAndClose() { Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); } +static void SocketReplacesDescriptor() { + const int kq = kqueue_nid_postfix(); + int marker = 0; + Require(Apply(kq, Change(1, EvfiltTimer, EvAdd | EvOneshot, 0, 1, &marker)) == 0); + int pair[2]; + Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); + Require(dup2_nid_postfix(pair[0], kq) == kq); + KernelEvent event{}; + RequireFailure(Wait(kq, &event, 1), GuestEbadf); + Require(close_nid_postfix(kq) == 0); + const int watcher = kqueue_nid_postfix(); + int pipe[2]; + Require(pipe_nid_postfix(pipe) == 0); + Require(write_nid_postfix(pipe[1], "x", 1) == 1); + Require(Apply(watcher, Change(static_cast(pipe[0]), EvfiltRead, EvAdd)) == 0); + Require(Poll(watcher, &event, 1) == 1); + Require(dup2_nid_postfix(pair[1], pipe[0]) == pipe[0]); + Require(Poll(watcher, &event, 1) == 0); + RequireFailure(Apply(watcher, Change(static_cast(pipe[0]), EvfiltRead, EvDelete)), GuestEnoent); + Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); + Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); + Require(close_nid_postfix(watcher) == 0); +} + static void Timers() { const int kq = kqueue_nid_postfix(); int marker = 0; @@ -1584,6 +1608,7 @@ int main(int argc, char** argv) { StandardInputReplacement(); StandardInput(); CreateAndClose(); + SocketReplacesDescriptor(); Timers(); TimerReadd(); UnsupportedFilters();