diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-28 10:43:37 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-28 10:43:37 +0300 |
| commit | ff8774b5ce3f6b37e5152d0dc06ae46b7a36d1da (patch) | |
| tree | 7224ccb001a0945216d6e30b5b9c326396ceba76 /internal/eventloop_exit.go | |
| parent | 99e99c6ea35ae97e84d727449f9ad7c4c0a9fa23 (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.go | 42 |
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()) } |
