@@ -21,6 +21,7 @@ import (
2121 "gvisor.dev/gvisor/pkg/abi/linux"
2222 "gvisor.dev/gvisor/pkg/errors/linuxerr"
2323 "gvisor.dev/gvisor/pkg/hostarch"
24+ "gvisor.dev/gvisor/pkg/marshal/primitive"
2425 "gvisor.dev/gvisor/pkg/sentry/arch"
2526 "gvisor.dev/gvisor/pkg/sentry/kernel"
2627 "gvisor.dev/gvisor/pkg/sentry/ktime"
@@ -34,6 +35,16 @@ import (
3435// unrecoverable.
3536const fileCap = 1024 * 1024
3637
38+ var (
39+ // sizeofPollFD is the size of linux.PollFD struct in bytes.
40+ sizeofPollFD = (* linux .PollFD )(nil ).SizeBytes ()
41+
42+ // reventsOffsetInFD is the byte offset of the REvents field within
43+ // linux.PollFD. Since REvents is the last field (int16), its offset
44+ // equals the struct size minus the size of int16.
45+ reventsOffsetInFD = sizeofPollFD - (* primitive .Int16 )(nil ).SizeBytes ()
46+ )
47+
3748// Masks for "readable", "writable", and "exceptional" events as defined by
3849// select(2).
3950const (
@@ -72,18 +83,24 @@ func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan
7283 return nil
7384 }
7485
86+ // Like Linux's fs/select.c:do_pollfd(), always check for POLLHUP and
87+ // POLLERR in addition to the caller-requested events. We add these
88+ // to a local mask rather than modifying pfd.Events, because Linux
89+ // never writes back the events field to userspace (only revents).
90+ mask := pfd .Events | linux .POLLHUP | linux .POLLERR
91+
7592 if ch == nil {
7693 defer file .DecRef (t )
7794 } else {
7895 state .file = file
79- state .waiter .Init (waiter .ChannelNotifier (ch ), waiter .EventMaskFromLinux (uint32 (pfd . Events )))
96+ state .waiter .Init (waiter .ChannelNotifier (ch ), waiter .EventMaskFromLinux (uint32 (mask )))
8097 if err := file .EventRegister (& state .waiter ); err != nil {
8198 return err
8299 }
83100 }
84101
85- r := file .Readiness (waiter .EventMaskFromLinux (uint32 (pfd . Events )))
86- pfd .REvents = int16 (r .ToLinux ()) & pfd . Events
102+ r := file .Readiness (waiter .EventMaskFromLinux (uint32 (mask )))
103+ pfd .REvents = int16 (r .ToLinux ()) & mask
87104 return nil
88105}
89106
@@ -150,8 +167,9 @@ func pollBlock(t *kernel.Task, pfd []linux.PollFD, timeout time.Duration) (time.
150167 continue
151168 }
152169
153- r := state [i ].file .Readiness (waiter .EventMaskFromLinux (uint32 (pfd [i ].Events )))
154- rl := int16 (r .ToLinux ()) & pfd [i ].Events
170+ mask := pfd [i ].Events | linux .POLLHUP | linux .POLLERR
171+ r := state [i ].file .Readiness (waiter .EventMaskFromLinux (uint32 (mask )))
172+ rl := int16 (r .ToLinux ()) & mask
155173 if rl != 0 {
156174 pfd [i ].REvents = rl
157175 n ++
@@ -184,21 +202,23 @@ func doPoll(t *kernel.Task, addr hostarch.Addr, nfds uint, timeout time.Duration
184202 return timeout , 0 , err
185203 }
186204
187- // Compatibility warning: Linux adds POLLHUP and POLLERR just before
188- // polling, in fs/select.c:do_pollfd(). Since pfd is copied out after
189- // polling, changing event masks here is an application-visible difference.
190- // (Linux also doesn't copy out event masks at all, only revents.)
191- for i := range pfd {
192- pfd [i ].Events |= linux .POLLHUP | linux .POLLERR
193- }
194205 remainingTimeout , n , err := pollBlock (t , pfd , timeout )
195206 err = linuxerr .ConvertIntr (err , linuxerr .EINTR )
196207
197- // The poll entries are copied out regardless of whether
198- // any are set or not. This aligns with the Linux behavior.
208+ // Copy out only the revents field, matching Linux behavior.
209+ // Linux's do_sys_poll() only writes back revents via
210+ // unsafe_put_user(fds->revents, &ufds->revents), never the
211+ // events field. Writing back the full struct would corrupt
212+ // the caller's events mask (e.g. libevent's poll backend),
213+ // causing busy-loops when event_del() fails to fully remove
214+ // an fd from the pollfd array due to stale POLLHUP/POLLERR bits.
215+ //
199216 if nfds > 0 && err == nil {
200- if _ , err := linux .CopyPollFDSliceOut (t , addr , pfd ); err != nil {
201- return remainingTimeout , 0 , err
217+ for i := range pfd {
218+ off := hostarch .Addr (i * sizeofPollFD + reventsOffsetInFD )
219+ if _ , copyErr := primitive .CopyInt16Out (t , addr + off , pfd [i ].REvents ); copyErr != nil {
220+ return remainingTimeout , 0 , copyErr
221+ }
202222 }
203223 }
204224
0 commit comments