diff --git a/images/basic/integrationtest/host_fd.c b/images/basic/integrationtest/host_fd.c index 0066a1b6f..253cbe041 100644 --- a/images/basic/integrationtest/host_fd.c +++ b/images/basic/integrationtest/host_fd.c @@ -14,8 +14,10 @@ #include #include +#include #include #include +#include #include // Tests that FIONREAD is supported with host FD. @@ -49,8 +51,24 @@ void testEpoll() { } } +// Docker maps stdin to /dev/null. Check that select(2) works with stdin. +void testSelect() { + fd_set rfds; + struct timeval tv; + int res; + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1; + res = select(1, &rfds, NULL, NULL, &tv); + if (res == -1) { + err(1, "select(1, [STDIN], NULL, NULL) returned error"); + } +} + int main(int argc, char** argv) { testFionread(); testEpoll(); + testSelect(); return 0; } diff --git a/pkg/sentry/devices/tundev/tundev.go b/pkg/sentry/devices/tundev/tundev.go index f8efd0ed0..176c2055a 100644 --- a/pkg/sentry/devices/tundev/tundev.go +++ b/pkg/sentry/devices/tundev/tundev.go @@ -166,6 +166,11 @@ func (fd *tunFD) EventUnregister(e *waiter.Entry) { fd.device.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *tunFD) Epollable() bool { + return true +} + // IsNetTunSupported returns whether /dev/net/tun device is supported for s. func IsNetTunSupported(s inet.Stack) bool { _, ok := s.(*netstack.Stack) diff --git a/pkg/sentry/fsimpl/devpts/master.go b/pkg/sentry/fsimpl/devpts/master.go index 35a682b40..c2faa0b7b 100644 --- a/pkg/sentry/fsimpl/devpts/master.go +++ b/pkg/sentry/fsimpl/devpts/master.go @@ -118,6 +118,11 @@ func (mfd *masterFileDescription) Readiness(mask waiter.EventMask) waiter.EventM return mfd.t.ld.masterReadiness() } +// Epollable implements FileDescriptionImpl.Epollable. +func (mfd *masterFileDescription) Epollable() bool { + return true +} + // Read implements vfs.FileDescriptionImpl.Read. func (mfd *masterFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) { return mfd.t.ld.outputQueueRead(ctx, dst) diff --git a/pkg/sentry/fsimpl/devpts/replica.go b/pkg/sentry/fsimpl/devpts/replica.go index eced9e618..d1ddf0045 100644 --- a/pkg/sentry/fsimpl/devpts/replica.go +++ b/pkg/sentry/fsimpl/devpts/replica.go @@ -127,6 +127,11 @@ func (rfd *replicaFileDescription) Readiness(mask waiter.EventMask) waiter.Event return rfd.inode.t.ld.replicaReadiness() } +// Epollable implements FileDescriptionImpl.Epollable. +func (rfd *replicaFileDescription) Epollable() bool { + return true +} + // Read implements vfs.FileDescriptionImpl.Read. func (rfd *replicaFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) { return rfd.inode.t.ld.inputQueueRead(ctx, dst) diff --git a/pkg/sentry/fsimpl/eventfd/eventfd.go b/pkg/sentry/fsimpl/eventfd/eventfd.go index 131db320c..07c16874b 100644 --- a/pkg/sentry/fsimpl/eventfd/eventfd.go +++ b/pkg/sentry/fsimpl/eventfd/eventfd.go @@ -294,3 +294,8 @@ func (efd *EventFileDescription) EventUnregister(entry *waiter.Entry) { } } } + +// Epollable implements FileDescriptionImpl.Epollable. +func (efd *EventFileDescription) Epollable() bool { + return true +} diff --git a/pkg/sentry/fsimpl/fuse/dev.go b/pkg/sentry/fsimpl/fuse/dev.go index a03170b85..fba7168e2 100644 --- a/pkg/sentry/fsimpl/fuse/dev.go +++ b/pkg/sentry/fsimpl/fuse/dev.go @@ -389,6 +389,11 @@ func (fd *DeviceFD) EventUnregister(e *waiter.Entry) { fd.waitQueue.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *DeviceFD) Epollable() bool { + return true +} + // Seek implements vfs.FileDescriptionImpl.Seek. func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { // Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted. diff --git a/pkg/sentry/fsimpl/gofer/special_file.go b/pkg/sentry/fsimpl/gofer/special_file.go index 65af8856e..5c9aa9195 100644 --- a/pkg/sentry/fsimpl/gofer/special_file.go +++ b/pkg/sentry/fsimpl/gofer/special_file.go @@ -190,6 +190,14 @@ func (fd *specialFileFD) EventUnregister(e *waiter.Entry) { fd.fileDescription.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *specialFileFD) Epollable() bool { + if fd.haveQueue { + return true + } + return fd.fileDescription.Epollable() +} + func (fd *specialFileFD) Allocate(ctx context.Context, mode, offset, length uint64) error { if fd.isRegularFile { d := fd.dentry() diff --git a/pkg/sentry/fsimpl/host/host.go b/pkg/sentry/fsimpl/host/host.go index c4e58f82c..8fa22b74d 100644 --- a/pkg/sentry/fsimpl/host/host.go +++ b/pkg/sentry/fsimpl/host/host.go @@ -74,6 +74,22 @@ func (v *virtualOwner) atomicMode() uint32 { return atomic.LoadUint32(&v.mode) } +func isEpollable(fd int) bool { + epollfd, err := unix.EpollCreate1(0) + if err != nil { + // This shouldn't happen. If it does, just say file doesn't support epoll. + return false + } + defer unix.Close(epollfd) + + event := unix.EpollEvent{ + Fd: int32(fd), + Events: unix.EPOLLIN, + } + err = unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, fd, &event) + return err == nil +} + // inode implements kernfs.Inode. // // +stateify savable @@ -105,11 +121,11 @@ type inode struct { // This field is initialized at creation time and is immutable. ftype uint16 - // mayBlock is true if hostFD is non-blocking, and operations on it may - // return EAGAIN or EWOULDBLOCK instead of blocking. + // epollable indicates whether the hostFD can be used with epoll_ctl(2). This + // also indicates that hostFD has been set to non-blocking. // // This field is initialized at creation time and is immutable. - mayBlock bool + epollable bool // seekable is false if lseek(hostFD) returns ESPIPE. We assume that file // offsets are meaningful iff seekable is true. @@ -156,20 +172,20 @@ func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, fil } i := &inode{ - hostFD: hostFD, - ino: fs.NextIno(), - ftype: uint16(fileType), - mayBlock: fileType != unix.S_IFREG && fileType != unix.S_IFDIR, - seekable: seekable, - isTTY: isTTY, - savable: savable, + hostFD: hostFD, + ino: fs.NextIno(), + ftype: uint16(fileType), + epollable: isEpollable(hostFD), + seekable: seekable, + isTTY: isTTY, + savable: savable, } i.InitRefs() i.CachedMappable.Init(hostFD) // If the hostFD can return EWOULDBLOCK when set to non-blocking, do so and // handle blocking behavior in the sentry. - if i.mayBlock { + if i.epollable { if err := unix.SetNonblock(i.hostFD, true); err != nil { return nil, err } @@ -551,7 +567,7 @@ func (i *inode) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Cre // DecRef implements kernfs.Inode.DecRef. func (i *inode) DecRef(ctx context.Context) { i.inodeRefs.DecRef(func() { - if i.mayBlock { + if i.epollable { fdnotifier.RemoveFD(int32(i.hostFD)) } if err := unix.Close(i.hostFD); err != nil { @@ -909,7 +925,7 @@ func (f *fileDescription) ConfigureMMap(_ context.Context, opts *memmap.MMapOpts // EventRegister implements waiter.Waitable.EventRegister. func (f *fileDescription) EventRegister(e *waiter.Entry) error { f.inode.queue.EventRegister(e) - if f.inode.mayBlock { + if f.inode.epollable { if err := fdnotifier.UpdateFD(int32(f.inode.hostFD)); err != nil { f.inode.queue.EventUnregister(e) return err @@ -921,7 +937,7 @@ func (f *fileDescription) EventRegister(e *waiter.Entry) error { // EventUnregister implements waiter.Waitable.EventUnregister. func (f *fileDescription) EventUnregister(e *waiter.Entry) { f.inode.queue.EventUnregister(e) - if f.inode.mayBlock { + if f.inode.epollable { if err := fdnotifier.UpdateFD(int32(f.inode.hostFD)); err != nil { panic(fmt.Sprint("UpdateFD:", err)) } @@ -933,6 +949,11 @@ func (f *fileDescription) Readiness(mask waiter.EventMask) waiter.EventMask { return fdnotifier.NonBlockingPoll(int32(f.inode.hostFD), mask) } +// Epollable implements FileDescriptionImpl.Epollable. +func (f *fileDescription) Epollable() bool { + return f.inode.epollable +} + // Ioctl queries the underlying FD for allowed ioctl commands. func (f *fileDescription) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { switch cmd := args[1].Int(); cmd { diff --git a/pkg/sentry/fsimpl/host/save_restore.go b/pkg/sentry/fsimpl/host/save_restore.go index 31301c715..5fb44d03f 100644 --- a/pkg/sentry/fsimpl/host/save_restore.go +++ b/pkg/sentry/fsimpl/host/save_restore.go @@ -59,7 +59,7 @@ func (i *inode) beforeSave() { // afterLoad is invoked by stateify. func (i *inode) afterLoad() { - if i.mayBlock { + if i.epollable { if err := unix.SetNonblock(i.hostFD, true); err != nil { panic(fmt.Sprintf("host.inode.afterLoad: failed to set host FD %d non-blocking: %v", i.hostFD, err)) } diff --git a/pkg/sentry/fsimpl/mqfs/queue.go b/pkg/sentry/fsimpl/mqfs/queue.go index eee2bd8f6..2f4b96e44 100644 --- a/pkg/sentry/fsimpl/mqfs/queue.go +++ b/pkg/sentry/fsimpl/mqfs/queue.go @@ -143,3 +143,8 @@ func (fd *queueFD) EventRegister(e *waiter.Entry) error { func (fd *queueFD) EventUnregister(e *waiter.Entry) { fd.queue.EventUnregister(e) } + +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *queueFD) Epollable() bool { + return true +} diff --git a/pkg/sentry/fsimpl/overlay/regular_file.go b/pkg/sentry/fsimpl/overlay/regular_file.go index 8d92336c4..3d3aab6dc 100644 --- a/pkg/sentry/fsimpl/overlay/regular_file.go +++ b/pkg/sentry/fsimpl/overlay/regular_file.go @@ -261,6 +261,21 @@ func (fd *regularFileFD) EventUnregister(e *waiter.Entry) { fd.cachedFD.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *regularFileFD) Epollable() bool { + fd.mu.Lock() + defer fd.mu.Unlock() + wrappedFD, err := fd.currentFDLocked(context.Background()) + if err != nil { + // TODO(b/171089913): Just use fd.cachedFD since EventRegister can't + // return an error. This is obviously wrong, but at least consistent + // with VFS1. + log.Warningf("overlay.regularFileFD.Epollable: currentFDLocked failed: %v", err) + wrappedFD = fd.cachedFD + } + return wrappedFD.Epollable() +} + // PRead implements vfs.FileDescriptionImpl.PRead. func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { wrappedFD, err := fd.getCurrentFD(ctx) diff --git a/pkg/sentry/fsimpl/signalfd/signalfd.go b/pkg/sentry/fsimpl/signalfd/signalfd.go index a1f8aed14..0954d2e47 100644 --- a/pkg/sentry/fsimpl/signalfd/signalfd.go +++ b/pkg/sentry/fsimpl/signalfd/signalfd.go @@ -146,6 +146,11 @@ func (sfd *SignalFileDescription) NotifyEvent(mask waiter.EventMask) { sfd.queue.Notify(waiter.EventIn) // Always notify data available. } +// Epollable implements FileDescriptionImpl.Epollable. +func (sfd *SignalFileDescription) Epollable() bool { + return true +} + // Release implements vfs.FileDescriptionImpl.Release. func (sfd *SignalFileDescription) Release(context.Context) { sfd.target.SignalUnregister(&sfd.entry) diff --git a/pkg/sentry/fsimpl/timerfd/timerfd.go b/pkg/sentry/fsimpl/timerfd/timerfd.go index 467c76596..b740aa396 100644 --- a/pkg/sentry/fsimpl/timerfd/timerfd.go +++ b/pkg/sentry/fsimpl/timerfd/timerfd.go @@ -122,6 +122,11 @@ func (tfd *TimerFileDescription) EventUnregister(e *waiter.Entry) { tfd.events.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (tfd *TimerFileDescription) Epollable() bool { + return true +} + // PauseTimer pauses the associated Timer. func (tfd *TimerFileDescription) PauseTimer() { tfd.timer.Pause() diff --git a/pkg/sentry/kernel/pipe/vfs.go b/pkg/sentry/kernel/pipe/vfs.go index be59e10ef..53507ccdc 100644 --- a/pkg/sentry/kernel/pipe/vfs.go +++ b/pkg/sentry/kernel/pipe/vfs.go @@ -220,6 +220,11 @@ func (fd *VFSPipeFD) EventUnregister(e *waiter.Entry) { fd.pipe.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (fd *VFSPipeFD) Epollable() bool { + return true +} + // Read implements vfs.FileDescriptionImpl.Read. func (fd *VFSPipeFD) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) { return fd.pipe.Read(ctx, dst) diff --git a/pkg/sentry/socket/hostinet/socket_vfs2.go b/pkg/sentry/socket/hostinet/socket_vfs2.go index 5bea70106..6c2635264 100644 --- a/pkg/sentry/socket/hostinet/socket_vfs2.go +++ b/pkg/sentry/socket/hostinet/socket_vfs2.go @@ -98,6 +98,11 @@ func (s *socketVFS2) EventUnregister(e *waiter.Entry) { s.socketOpsCommon.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (s *socketVFS2) Epollable() bool { + return true +} + // Ioctl implements vfs.FileDescriptionImpl. func (s *socketVFS2) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) { return ioctl(ctx, s.fd, uio, args) diff --git a/pkg/sentry/socket/netlink/socket_vfs2.go b/pkg/sentry/socket/netlink/socket_vfs2.go index 0de568e5a..c1fcfbe98 100644 --- a/pkg/sentry/socket/netlink/socket_vfs2.go +++ b/pkg/sentry/socket/netlink/socket_vfs2.go @@ -105,6 +105,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { s.socketOpsCommon.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (s *SocketVFS2) Epollable() bool { + return true +} + // Ioctl implements vfs.FileDescriptionImpl. func (*SocketVFS2) Ioctl(context.Context, usermem.IO, arch.SyscallArguments) (uintptr, error) { // TODO(b/68878065): no ioctls supported. diff --git a/pkg/sentry/socket/netstack/netstack_vfs2.go b/pkg/sentry/socket/netstack/netstack_vfs2.go index 145951145..cbe23ce65 100644 --- a/pkg/sentry/socket/netstack/netstack_vfs2.go +++ b/pkg/sentry/socket/netstack/netstack_vfs2.go @@ -100,6 +100,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { s.socketOpsCommon.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (s *SocketVFS2) Epollable() bool { + return true +} + // Read implements vfs.FileDescriptionImpl. func (s *SocketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { // All flags other than RWF_NOWAIT should be ignored. diff --git a/pkg/sentry/socket/unix/unix_vfs2.go b/pkg/sentry/socket/unix/unix_vfs2.go index fd1194bc1..676407f81 100644 --- a/pkg/sentry/socket/unix/unix_vfs2.go +++ b/pkg/sentry/socket/unix/unix_vfs2.go @@ -324,6 +324,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) { s.socketOpsCommon.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (s *SocketVFS2) Epollable() bool { + return true +} + // SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by // a transport.Endpoint. func (s *SocketVFS2) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error { diff --git a/pkg/sentry/vfs/epoll.go b/pkg/sentry/vfs/epoll.go index 128e743f3..b6d35bffd 100644 --- a/pkg/sentry/vfs/epoll.go +++ b/pkg/sentry/vfs/epoll.go @@ -161,6 +161,11 @@ func (ep *EpollInstance) EventUnregister(e *waiter.Entry) { ep.q.EventUnregister(e) } +// Epollable implements FileDescriptionImpl.Epollable. +func (ep *EpollInstance) Epollable() bool { + return true +} + // Seek implements FileDescriptionImpl.Seek. func (ep *EpollInstance) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { // Linux: fs/eventpoll.c:eventpoll_fops.llseek == noop_llseek @@ -171,6 +176,10 @@ func (ep *EpollInstance) Seek(ctx context.Context, offset int64, whence int32) ( // // Preconditions: A reference must be held on file. func (ep *EpollInstance) AddInterest(file *FileDescription, num int32, event linux.EpollEvent) error { + if !file.Epollable() { + return linuxerr.EPERM + } + // Check for cyclic polling if necessary. subep, _ := file.impl.(*EpollInstance) if subep != nil { diff --git a/pkg/sentry/vfs/file_description.go b/pkg/sentry/vfs/file_description.go index 90f512921..cf8dd0053 100644 --- a/pkg/sentry/vfs/file_description.go +++ b/pkg/sentry/vfs/file_description.go @@ -352,6 +352,9 @@ type FileDescriptionImpl interface { // waiter.Waitable methods may be used to poll for I/O events. waiter.Waitable + // Epollable indicates whether this file can be used with epoll_ctl(2). + Epollable() bool + // PRead reads from the file into dst, starting at the given offset, and // returns the number of bytes read. PRead is permitted to return partial // reads with a nil error. @@ -597,6 +600,11 @@ func (fd *FileDescription) EventUnregister(e *waiter.Entry) { fd.impl.EventUnregister(e) } +// Epollable returns whether this file can be used with epoll_ctl(2). +func (fd *FileDescription) Epollable() bool { + return fd.impl.Epollable() +} + // PRead reads from the file represented by fd into dst, starting at the given // offset, and returns the number of bytes read. PRead is permitted to return // partial reads with a nil error. diff --git a/pkg/sentry/vfs/file_description_impl_util.go b/pkg/sentry/vfs/file_description_impl_util.go index 834f0f7be..56e93274b 100644 --- a/pkg/sentry/vfs/file_description_impl_util.go +++ b/pkg/sentry/vfs/file_description_impl_util.go @@ -87,6 +87,11 @@ func (FileDescriptionDefaultImpl) EventRegister(e *waiter.Entry) error { func (FileDescriptionDefaultImpl) EventUnregister(e *waiter.Entry) { } +// Epollable implements FileDescriptionImpl.Epollable. +func (FileDescriptionDefaultImpl) Epollable() bool { + return false +} + // PRead implements FileDescriptionImpl.PRead analogously to // file_operations::read == file_operations::read_iter == NULL in Linux. func (FileDescriptionDefaultImpl) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) { diff --git a/pkg/sentry/vfs/inotify.go b/pkg/sentry/vfs/inotify.go index 9b5e63317..af8174b9b 100644 --- a/pkg/sentry/vfs/inotify.go +++ b/pkg/sentry/vfs/inotify.go @@ -183,6 +183,11 @@ func (i *Inotify) Readiness(mask waiter.EventMask) waiter.EventMask { return mask & ready } +// Epollable implements FileDescriptionImpl.Epollable. +func (i *Inotify) Epollable() bool { + return true +} + // PRead implements FileDescriptionImpl.PRead. func (*Inotify) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) { return 0, linuxerr.ESPIPE diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 88bea0d82..bd2a307ed 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -610,6 +610,7 @@ cc_binary( "//test/util:file_descriptor", gtest, "//test/util:posix_error", + "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", diff --git a/test/syscalls/linux/epoll.cc b/test/syscalls/linux/epoll.cc index c2dc8174c..95e1dabf8 100644 --- a/test/syscalls/linux/epoll.cc +++ b/test/syscalls/linux/epoll.cc @@ -29,6 +29,7 @@ #include "test/util/eventfd_util.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" +#include "test/util/temp_path.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -532,6 +533,19 @@ TEST(EpollTest, DoubleLayerEpoll) { } } +TEST(EPollTest, RegularFiles) { + auto epollfd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD()); + + struct epoll_event event; + event.events = EPOLLIN | EPOLLOUT; + event.data.u64 = kMagicConstant; + + auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); + auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path.path(), O_RDONLY)); + EXPECT_THAT(epoll_ctl(epollfd.get(), EPOLL_CTL_ADD, fd.get(), &event), + SyscallFailsWithErrno(EPERM)); +} + } // namespace } // namespace testing