summaryrefslogtreecommitdiff
path: root/internal/eventloop_exit.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-28 10:43:37 +0300
committerPaul Buetow <paul@buetow.org>2026-05-28 10:43:37 +0300
commitff8774b5ce3f6b37e5152d0dc06ae46b7a36d1da (patch)
tree7224ccb001a0945216d6e30b5b9c326396ceba76 /internal/eventloop_exit.go
parent99e99c6ea35ae97e84d727449f9ad7c4c0a9fa23 (diff)
close_range: honor last bound and CLOSE_RANGE_CLOEXEC flag
close_range was captured as a single-fd fd_event carrying only first, so the runtime evicted every tracked fd >= first, ignoring the last upper bound and the flags. Bounded calls wrongly dropped still-open higher fds, and CLOSE_RANGE_CLOEXEC (which keeps fds open) was treated as a full close. Reclassify close_range to the two_fd_event kind, mapping fd_a/fd_b/extra to first/last/flags. The runtime now closes only the inclusive [first, last] range (a negative last from ~0U means unbounded) and skips eviction when CLOSE_RANGE_CLOEXEC is set or the syscall fails. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'internal/eventloop_exit.go')
-rw-r--r--internal/eventloop_exit.go42
1 files changed, 29 insertions, 13 deletions
diff --git a/internal/eventloop_exit.go b/internal/eventloop_exit.go
index cb11074..a5b38d4 100644
--- a/internal/eventloop_exit.go
+++ b/internal/eventloop_exit.go
@@ -118,8 +118,10 @@ func (e *eventLoop) handlePathExit(ep *event.Pair, pathEv *types.PathEvent) bool
}
// handleFdExit processes exit events for fd-based syscalls. It resolves the fd
-// to a file, applies close/close_range state transitions, filters the pair, and
-// handles dup/pidfd_getfd fd-transfer operations before finalising bytes.
+// to a file, applies the close state transition, filters the pair, and handles
+// dup/pidfd_getfd fd-transfer operations before finalising bytes. close_range is
+// not handled here: it carries (first, last, flags) and is routed through
+// handleTwoFdExit so the upper bound and flags are honoured.
func (e *eventLoop) handleFdExit(ep *event.Pair, fdEv *types.FdEvent) bool {
fd := fdEv.Fd
ep.File = e.fdState().resolve(fd, fdEv.Pid)
@@ -134,21 +136,11 @@ func (e *eventLoop) handleFdExit(ep *event.Pair, fdEv *types.FdEvent) bool {
return true
}
-// applyFdCloseState updates fd-tracking state for close and close_range syscalls.
+// applyFdCloseState updates fd-tracking state for the close syscall.
func (e *eventLoop) applyFdCloseState(ep *event.Pair, fd int32, pid uint32) {
if ep.Is(types.SYS_ENTER_CLOSE) {
e.fdState().delete(fd)
e.fdState().deleteProcFdCache(fd, pid)
- return
- }
- if ep.Is(types.SYS_ENTER_CLOSE_RANGE) {
- // close_range provides (first, last), but fd_event only carries the first
- // argument, so we approximate by closing all tracked fds >= first.
- retEv, ok := ep.ExitEv.(*types.RetEvent)
- if ok && retEv.Ret == 0 {
- e.fdState().closeRangeFrom(fd)
- e.fdState().deleteProcFdCacheFrom(fd, pid)
- }
}
}
@@ -385,9 +377,33 @@ func (e *eventLoop) handlePollExit(ep *event.Pair, pollEv *types.PollEvent) bool
func (e *eventLoop) handleTwoFdExit(ep *event.Pair, twoFdEv *types.TwoFdEvent) bool {
ep.File = e.fdState().resolve(twoFdEv.FdA, twoFdEv.Pid)
+ if ep.Is(types.SYS_ENTER_CLOSE_RANGE) {
+ e.applyCloseRangeState(ep, twoFdEv)
+ }
return e.finishPairForTid(ep, twoFdEv.GetTid())
}
+// closeRangeCloexec mirrors CLOSE_RANGE_CLOEXEC from <linux/close_range.h>: when
+// set, close_range only marks the descriptors close-on-exec instead of closing
+// them, so the fds stay open and must remain tracked.
+const closeRangeCloexec = 1 << 2
+
+// applyCloseRangeState evicts the fds closed by a successful close_range. The
+// enter event carries (first, last, flags) in fd_a/fd_b/extra. fd_b is an __s32
+// view of the unsigned "last" argument, so a negative value (e.g. ~0U meaning
+// "close everything from first up") is treated as having no upper bound.
+func (e *eventLoop) applyCloseRangeState(ep *event.Pair, ev *types.TwoFdEvent) {
+ retEv, ok := ep.ExitEv.(*types.RetEvent)
+ if !ok || retEv.Ret != 0 {
+ return
+ }
+ if ev.Extra&closeRangeCloexec != 0 {
+ return
+ }
+ e.fdState().closeRange(ev.FdA, ev.FdB)
+ e.fdState().deleteProcFdCacheRange(ev.FdA, ev.FdB, ev.Pid)
+}
+
func (e *eventLoop) handleMemExit(ep *event.Pair, memEv *types.MemEvent) bool {
return e.finishPairForTid(ep, memEv.GetTid())
}