@@ -692,14 +692,19 @@ typedef struct {
692692
693693/* Per-fd registration entry within an epoll instance. */
694694typedef struct {
695- uint32_t events ; /* Registered EPOLL* events mask */
696- uint64_t data ; /* User data to return in epoll_wait */
697- bool active ; /* Registered in this instance */
698- bool oneshot_armed ; /* EPOLLONESHOT and event already fired,
699- * waiting for EPOLL_CTL_MOD re-arm.
700- * kqueue removed the event, so poll emulation prevents
701- * reporting but allow MOD.
702- */
695+ uint32_t events ; /* Registered EPOLL* events mask */
696+ uint64_t data ; /* User data to return in epoll_wait */
697+ uint64_t generation ; /* fd_entry_t.generation captured at ADD/MOD. Detects a
698+ * close+reopen ABA: if the guest fd's current
699+ * generation no longer matches, the registered open
700+ * file is gone and this stale entry must not drive
701+ * kevent against the reused host fd. */
702+ bool active ; /* Registered in this instance */
703+ bool oneshot_armed ; /* EPOLLONESHOT and event already fired,
704+ * waiting for EPOLL_CTL_MOD re-arm.
705+ * kqueue removed the event, so poll emulation prevents
706+ * reporting but allow MOD.
707+ */
703708} epoll_reg_t ;
704709
705710/* Per-epoll-instance data, stored in fd_table[epfd].dir. Each instance
@@ -781,18 +786,43 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
781786 return - LINUX_EINVAL ;
782787 }
783788
784- host_fd_ref_t target_ref ;
785- if (host_fd_ref_open (fd , & target_ref ) < 0 ) {
789+ /* Validate the target fd and read its persistent host fd in a single
790+ * fd_lock snapshot, so the kqueue knote ident is taken from the same entry
791+ * that was validated. A kqueue knote is keyed by the fd number and the
792+ * kernel drops it the moment that fd is closed, so the ident must be the
793+ * persistent host fd from the fd table -- not the dup that
794+ * host_fd_ref_open() hands multi-threaded callers, which
795+ * host_fd_ref_close() closes when the syscall returns (silently tearing the
796+ * registration down). Snapshotting (rather than host_fd_ref_open() + a
797+ * separate fd_to_host()) keeps the validate and the ident read atomic under
798+ * one fd_lock. The snapshot's generation then guards the cross-call ABA
799+ * below. Result mapping uses udata (the guest fd), so the ident only needs
800+ * to stay open and refer to the same open file description. */
801+ fd_entry_t target_snap ;
802+ if (!fd_snapshot (fd , & target_snap )) {
786803 host_fd_ref_close (& epoll_ref );
787804 return - LINUX_EBADF ;
788805 }
806+ int target_host_fd = target_snap .host_fd ;
789807
790808 epoll_reg_t * reg = & inst -> regs [fd ];
791809
810+ /* Cross-call ABA guard. If the guest closed this fd and reopened it (or the
811+ * slot was reused) since the registration was stamped, the kernel already
812+ * dropped the original knote when the old host fd closed, yet the guest fd
813+ * number -- and thus reg->active -- still looks live. Acting on it would
814+ * EV_DELETE/EV_MOD the wrong knote on the reused host fd. A mismatched
815+ * generation means the registration is gone: drop it so DEL/MOD report
816+ * ENOENT (matching Linux's auto-removal on close) and ADD starts fresh. */
817+ if ((reg -> active || reg -> oneshot_armed ) &&
818+ reg -> generation != target_snap .generation ) {
819+ reg -> active = false;
820+ reg -> oneshot_armed = false;
821+ }
822+
792823 if (op == LINUX_EPOLL_CTL_DEL ) {
793824 /* Linux returns ENOENT when removing an unregistered fd */
794825 if (!reg -> active ) {
795- host_fd_ref_close (& target_ref );
796826 host_fd_ref_close (& epoll_ref );
797827 return - LINUX_ENOENT ;
798828 }
@@ -804,12 +834,12 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
804834 int nchanges = 0 ;
805835 {
806836 if (reg -> events & (LINUX_EPOLLIN | LINUX_EPOLLRDHUP )) {
807- EV_SET (& changes [nchanges ], target_ref . fd , EVFILT_READ ,
837+ EV_SET (& changes [nchanges ], target_host_fd , EVFILT_READ ,
808838 EV_DELETE , 0 , 0 , NULL );
809839 nchanges ++ ;
810840 }
811841 if (reg -> events & LINUX_EPOLLOUT ) {
812- EV_SET (& changes [nchanges ], target_ref . fd , EVFILT_WRITE ,
842+ EV_SET (& changes [nchanges ], target_host_fd , EVFILT_WRITE ,
813843 EV_DELETE , 0 , 0 , NULL );
814844 nchanges ++ ;
815845 }
@@ -819,7 +849,6 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
819849 /* Clear stale state for potential re-add */
820850 reg -> oneshot_armed = false;
821851 }
822- host_fd_ref_close (& target_ref );
823852 host_fd_ref_close (& epoll_ref );
824853 return 0 ;
825854 }
@@ -829,20 +858,17 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
829858 * (EPOLLONESHOT fired, waiting for re-arm) are still valid for MOD.
830859 */
831860 if (op == LINUX_EPOLL_CTL_ADD && reg -> active ) {
832- host_fd_ref_close (& target_ref );
833861 host_fd_ref_close (& epoll_ref );
834862 return - LINUX_EEXIST ;
835863 }
836864 if (op == LINUX_EPOLL_CTL_MOD && !reg -> active && !reg -> oneshot_armed ) {
837- host_fd_ref_close (& target_ref );
838865 host_fd_ref_close (& epoll_ref );
839866 return - LINUX_ENOENT ;
840867 }
841868
842869 /* ADD or MOD: read the epoll_event from guest */
843870 linux_epoll_event_t ev ;
844871 if (guest_read_small (g , event_gva , & ev , sizeof (ev )) < 0 ) {
845- host_fd_ref_close (& target_ref );
846872 host_fd_ref_close (& epoll_ref );
847873 return - LINUX_EFAULT ;
848874 }
@@ -860,11 +886,11 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
860886 if (op == LINUX_EPOLL_CTL_MOD && reg -> active ) {
861887 struct kevent del ;
862888 if (reg -> events & (LINUX_EPOLLIN | LINUX_EPOLLRDHUP )) {
863- EV_SET (& del , target_ref . fd , EVFILT_READ , EV_DELETE , 0 , 0 , NULL );
889+ EV_SET (& del , target_host_fd , EVFILT_READ , EV_DELETE , 0 , 0 , NULL );
864890 kevent (epoll_ref .fd , & del , 1 , NULL , 0 , NULL );
865891 }
866892 if (reg -> events & LINUX_EPOLLOUT ) {
867- EV_SET (& del , target_ref . fd , EVFILT_WRITE , EV_DELETE , 0 , 0 , NULL );
893+ EV_SET (& del , target_host_fd , EVFILT_WRITE , EV_DELETE , 0 , 0 , NULL );
868894 kevent (epoll_ref .fd , & del , 1 , NULL , 0 , NULL );
869895 }
870896 }
@@ -894,33 +920,34 @@ int64_t sys_epoll_ctl(guest_t *g, int epfd, int op, int fd, uint64_t event_gva)
894920 void * udata = (void * ) (uintptr_t ) fd ;
895921
896922 if (ev .events & (LINUX_EPOLLIN | LINUX_EPOLLRDHUP )) {
897- EV_SET (& changes [nchanges ], target_ref . fd , EVFILT_READ , kflags , 0 , 0 ,
923+ EV_SET (& changes [nchanges ], target_host_fd , EVFILT_READ , kflags , 0 , 0 ,
898924 udata );
899925 nchanges ++ ;
900926 }
901927 if (ev .events & LINUX_EPOLLOUT ) {
902- EV_SET (& changes [nchanges ], target_ref . fd , EVFILT_WRITE , kflags , 0 , 0 ,
928+ EV_SET (& changes [nchanges ], target_host_fd , EVFILT_WRITE , kflags , 0 , 0 ,
903929 udata );
904930 nchanges ++ ;
905931 }
906932
907933 if (nchanges > 0 ) {
908934 if (kevent (epoll_ref .fd , changes , nchanges , NULL , 0 , NULL ) < 0 ) {
909- host_fd_ref_close (& target_ref );
910935 host_fd_ref_close (& epoll_ref );
911936 return linux_errno ();
912937 }
913938 }
914939
915940 /* Store registration data in per-instance table.
916- * Clear oneshot_armed when MOD successfully re-arms.
941+ * Clear oneshot_armed when MOD successfully re-arms. Stamp the snapshot's
942+ * generation so a later close+reopen of this guest fd is detected as a
943+ * stale registration by the ABA guard above.
917944 */
918945 reg -> events = ev .events ;
919946 reg -> data = ev .data ;
947+ reg -> generation = target_snap .generation ;
920948 reg -> active = true;
921949 reg -> oneshot_armed = false;
922950
923- host_fd_ref_close (& target_ref );
924951 host_fd_ref_close (& epoll_ref );
925952 return 0 ;
926953}
0 commit comments