Implement fcntl options F_GETSIG and F_SETSIG.

These options allow overriding the signal that gets sent to the process when
I/O operations are available on the file descriptor, rather than the default
`SIGIO` signal. Doing so also populates `siginfo` to contain extra information
about which file descriptor caused the event (`si_fd`) and what events happened
on it (`si_band`). The logic around which FD is populated within `si_fd`
matches Linux's, which means it has some weird edge cases where that value may
not actually refer to a file descriptor that is still valid.

This CL also ports extra S/R logic regarding async handler in VFS2.
Without this, async I/O handlers aren't properly re-registered after S/R.

PiperOrigin-RevId: 345436598
This commit is contained in:
Etienne Perot
2020-12-03 06:20:29 -08:00
committed by gVisor bot
parent 80552b936d
commit 6f60a2b0a2
16 changed files with 612 additions and 120 deletions
+2
View File
@@ -25,6 +25,8 @@ const (
F_SETLKW = 7
F_SETOWN = 8
F_GETOWN = 9
F_SETSIG = 10
F_GETSIG = 11
F_SETOWN_EX = 15
F_GETOWN_EX = 16
F_DUPFD_CLOEXEC = 1024 + 6
+23
View File
@@ -251,3 +251,26 @@ func (s *SignalInfo) Arch() uint32 {
func (s *SignalInfo) SetArch(val uint32) {
usermem.ByteOrder.PutUint32(s.Fields[12:16], val)
}
// Band returns the si_band field.
func (s *SignalInfo) Band() int64 {
return int64(usermem.ByteOrder.Uint64(s.Fields[0:8]))
}
// SetBand mutates the si_band field.
func (s *SignalInfo) SetBand(val int64) {
// Note: this assumes the platform uses `long` as `__ARCH_SI_BAND_T`.
// On some platforms, which gVisor doesn't support, `__ARCH_SI_BAND_T` is
// `int`. See siginfo.h.
usermem.ByteOrder.PutUint64(s.Fields[0:8], uint64(val))
}
// FD returns the si_fd field.
func (s *SignalInfo) FD() uint32 {
return usermem.ByteOrder.Uint32(s.Fields[8:12])
}
// SetFD mutates the si_fd field.
func (s *SignalInfo) SetFD(val uint32) {
usermem.ByteOrder.PutUint32(s.Fields[8:12], val)
}
+2 -2
View File
@@ -103,8 +103,8 @@ func (fd *regularFileFD) currentFDLocked(ctx context.Context) (*vfs.FileDescript
for e, mask := range fd.lowerWaiters {
fd.cachedFD.EventUnregister(e)
upperFD.EventRegister(e, mask)
if ready&mask != 0 {
e.Callback.Callback(e)
if m := ready & mask; m != 0 {
e.Callback.Callback(e, m)
}
}
}
+3 -4
View File
@@ -273,7 +273,7 @@ func (e *EventPoll) ReadEvents(max int) []linux.EpollEvent {
//
// Callback is called when one of the files we're polling becomes ready. It
// moves said file to the readyList if it's currently in the waiting list.
func (p *pollEntry) Callback(*waiter.Entry) {
func (p *pollEntry) Callback(*waiter.Entry, waiter.EventMask) {
e := p.epoll
e.listsMu.Lock()
@@ -306,9 +306,8 @@ func (e *EventPoll) initEntryReadiness(entry *pollEntry) {
f.EventRegister(&entry.waiter, entry.mask)
// Check if the file happens to already be in a ready state.
ready := f.Readiness(entry.mask) & entry.mask
if ready != 0 {
entry.Callback(&entry.waiter)
if ready := f.Readiness(entry.mask) & entry.mask; ready != 0 {
entry.Callback(&entry.waiter, ready)
}
}
+2
View File
@@ -8,11 +8,13 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//pkg/abi/linux",
"//pkg/sentry/arch",
"//pkg/sentry/fs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/syserror",
"//pkg/waiter",
],
)
+83 -13
View File
@@ -17,22 +17,45 @@ package fasync
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/waiter"
)
// New creates a new fs.FileAsync.
func New() fs.FileAsync {
return &FileAsync{}
// Table to convert waiter event masks into si_band siginfo codes.
// Taken from fs/fcntl.c:band_table.
var bandTable = map[waiter.EventMask]int64{
// POLL_IN
waiter.EventIn: linux.EPOLLIN | linux.EPOLLRDNORM,
// POLL_OUT
waiter.EventOut: linux.EPOLLOUT | linux.EPOLLWRNORM | linux.EPOLLWRBAND,
// POLL_ERR
waiter.EventErr: linux.EPOLLERR,
// POLL_PRI
waiter.EventPri: linux.EPOLLPRI | linux.EPOLLRDBAND,
// POLL_HUP
waiter.EventHUp: linux.EPOLLHUP | linux.EPOLLERR,
}
// NewVFS2 creates a new vfs.FileAsync.
func NewVFS2() vfs.FileAsync {
return &FileAsync{}
// New returns a function that creates a new fs.FileAsync with the given file
// descriptor.
func New(fd int) func() fs.FileAsync {
return func() fs.FileAsync {
return &FileAsync{fd: fd}
}
}
// NewVFS2 returns a function that creates a new vfs.FileAsync with the given
// file descriptor.
func NewVFS2(fd int) func() vfs.FileAsync {
return func() vfs.FileAsync {
return &FileAsync{fd: fd}
}
}
// FileAsync sends signals when the registered file is ready for IO.
@@ -42,6 +65,12 @@ type FileAsync struct {
// e is immutable after first use (which is protected by mu below).
e waiter.Entry
// fd is the file descriptor to notify about.
// It is immutable, set at allocation time. This matches Linux semantics in
// fs/fcntl.c:fasync_helper.
// The fd value is passed to the signal recipient in siginfo.si_fd.
fd int
// regMu protects registeration and unregistration actions on e.
//
// regMu must be held while registration decisions are being made
@@ -56,6 +85,10 @@ type FileAsync struct {
mu sync.Mutex `state:"nosave"`
requester *auth.Credentials
registered bool
// signal is the signal to deliver upon I/O being available.
// The default value ("zero signal") means the default SIGIO signal will be
// delivered.
signal linux.Signal
// Only one of the following is allowed to be non-nil.
recipientPG *kernel.ProcessGroup
@@ -64,10 +97,10 @@ type FileAsync struct {
}
// Callback sends a signal.
func (a *FileAsync) Callback(e *waiter.Entry) {
func (a *FileAsync) Callback(e *waiter.Entry, mask waiter.EventMask) {
a.mu.Lock()
defer a.mu.Unlock()
if !a.registered {
a.mu.Unlock()
return
}
t := a.recipientT
@@ -80,19 +113,34 @@ func (a *FileAsync) Callback(e *waiter.Entry) {
}
if t == nil {
// No recipient has been registered.
a.mu.Unlock()
return
}
c := t.Credentials()
// Logic from sigio_perm in fs/fcntl.c.
if a.requester.EffectiveKUID == 0 ||
permCheck := (a.requester.EffectiveKUID == 0 ||
a.requester.EffectiveKUID == c.SavedKUID ||
a.requester.EffectiveKUID == c.RealKUID ||
a.requester.RealKUID == c.SavedKUID ||
a.requester.RealKUID == c.RealKUID {
t.SendSignal(kernel.SignalInfoPriv(linux.SIGIO))
a.requester.RealKUID == c.RealKUID)
if !permCheck {
return
}
a.mu.Unlock()
signalInfo := &arch.SignalInfo{
Signo: int32(linux.SIGIO),
Code: arch.SignalInfoKernel,
}
if a.signal != 0 {
signalInfo.Signo = int32(a.signal)
signalInfo.SetFD(uint32(a.fd))
var band int64
for m, bandCode := range bandTable {
if m&mask != 0 {
band |= bandCode
}
}
signalInfo.SetBand(band)
}
t.SendSignal(signalInfo)
}
// Register sets the file which will be monitored for IO events.
@@ -186,3 +234,25 @@ func (a *FileAsync) ClearOwner() {
a.recipientTG = nil
a.recipientPG = nil
}
// Signal returns which signal will be sent to the signal recipient.
// A value of zero means the signal to deliver wasn't customized, which means
// the default signal (SIGIO) will be delivered.
func (a *FileAsync) Signal() linux.Signal {
a.mu.Lock()
defer a.mu.Unlock()
return a.signal
}
// SetSignal overrides which signal to send when I/O is available.
// The default behavior can be reset by specifying signal zero, which means
// to send SIGIO.
func (a *FileAsync) SetSignal(signal linux.Signal) error {
if signal != 0 && !signal.IsValid() {
return syserror.EINVAL
}
a.mu.Lock()
defer a.mu.Unlock()
a.signal = signal
return nil
}
+11 -5
View File
@@ -646,7 +646,7 @@ func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if _, err := primitive.CopyInt32In(t, args[2].Pointer(), &set); err != nil {
return 0, nil, err
}
fSetOwn(t, file, set)
fSetOwn(t, int(fd), file, set)
return 0, nil, nil
case linux.FIOGETOWN, linux.SIOCGPGRP:
@@ -901,8 +901,8 @@ func fGetOwn(t *kernel.Task, file *fs.File) int32 {
//
// If who is positive, it represents a PID. If negative, it represents a PGID.
// If the PID or PGID is invalid, the owner is silently unset.
func fSetOwn(t *kernel.Task, file *fs.File, who int32) error {
a := file.Async(fasync.New).(*fasync.FileAsync)
func fSetOwn(t *kernel.Task, fd int, file *fs.File, who int32) error {
a := file.Async(fasync.New(fd)).(*fasync.FileAsync)
if who < 0 {
// Check for overflow before flipping the sign.
if who-1 > who {
@@ -1049,7 +1049,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
case linux.F_GETOWN:
return uintptr(fGetOwn(t, file)), nil, nil
case linux.F_SETOWN:
return 0, nil, fSetOwn(t, file, args[2].Int())
return 0, nil, fSetOwn(t, int(fd), file, args[2].Int())
case linux.F_GETOWN_EX:
addr := args[2].Pointer()
owner := fGetOwnEx(t, file)
@@ -1062,7 +1062,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if err != nil {
return 0, nil, err
}
a := file.Async(fasync.New).(*fasync.FileAsync)
a := file.Async(fasync.New(int(fd))).(*fasync.FileAsync)
switch owner.Type {
case linux.F_OWNER_TID:
task := t.PIDNamespace().TaskWithID(kernel.ThreadID(owner.PID))
@@ -1111,6 +1111,12 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
}
n, err := sz.SetFifoSize(int64(args[2].Int()))
return uintptr(n), nil, err
case linux.F_GETSIG:
a := file.Async(fasync.New(int(fd))).(*fasync.FileAsync)
return uintptr(a.Signal()), nil, nil
case linux.F_SETSIG:
a := file.Async(fasync.New(int(fd))).(*fasync.FileAsync)
return 0, nil, a.SetSignal(linux.Signal(args[2].Int()))
default:
// Everything else is not yet supported.
return 0, nil, syserror.EINVAL
+14 -4
View File
@@ -165,7 +165,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
ownerType = linux.F_OWNER_PGRP
who = -who
}
return 0, nil, setAsyncOwner(t, file, ownerType, who)
return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who)
case linux.F_GETOWN_EX:
owner, hasOwner := getAsyncOwner(t, file)
if !hasOwner {
@@ -179,7 +179,7 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
if err != nil {
return 0, nil, err
}
return 0, nil, setAsyncOwner(t, file, owner.Type, owner.PID)
return 0, nil, setAsyncOwner(t, int(fd), file, owner.Type, owner.PID)
case linux.F_SETPIPE_SZ:
pipefile, ok := file.Impl().(*pipe.VFSPipeFD)
if !ok {
@@ -207,6 +207,16 @@ func Fcntl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
return 0, nil, err
case linux.F_SETLK, linux.F_SETLKW:
return 0, nil, posixLock(t, args, file, cmd)
case linux.F_GETSIG:
a := file.AsyncHandler()
if a == nil {
// Default behavior aka SIGIO.
return 0, nil, nil
}
return uintptr(a.(*fasync.FileAsync).Signal()), nil, nil
case linux.F_SETSIG:
a := file.SetAsyncHandler(fasync.NewVFS2(int(fd))).(*fasync.FileAsync)
return 0, nil, a.SetSignal(linux.Signal(args[2].Int()))
default:
// Everything else is not yet supported.
return 0, nil, syserror.EINVAL
@@ -241,7 +251,7 @@ func getAsyncOwner(t *kernel.Task, fd *vfs.FileDescription) (ownerEx linux.FOwne
}
}
func setAsyncOwner(t *kernel.Task, fd *vfs.FileDescription, ownerType, pid int32) error {
func setAsyncOwner(t *kernel.Task, fd int, file *vfs.FileDescription, ownerType, pid int32) error {
switch ownerType {
case linux.F_OWNER_TID, linux.F_OWNER_PID, linux.F_OWNER_PGRP:
// Acceptable type.
@@ -249,7 +259,7 @@ func setAsyncOwner(t *kernel.Task, fd *vfs.FileDescription, ownerType, pid int32
return syserror.EINVAL
}
a := fd.SetAsyncHandler(fasync.NewVFS2).(*fasync.FileAsync)
a := file.SetAsyncHandler(fasync.NewVFS2(fd)).(*fasync.FileAsync)
if pid == 0 {
a.ClearOwner()
return nil
+1 -1
View File
@@ -100,7 +100,7 @@ func Ioctl(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
ownerType = linux.F_OWNER_PGRP
who = -who
}
return 0, nil, setAsyncOwner(t, file, ownerType, who)
return 0, nil, setAsyncOwner(t, int(fd), file, ownerType, who)
}
ret, err := file.Ioctl(t, t.MemoryManager(), args)
+5 -5
View File
@@ -204,8 +204,8 @@ func (ep *EpollInstance) AddInterest(file *FileDescription, num int32, event lin
file.EventRegister(&epi.waiter, wmask)
// Check if the file is already ready.
if file.Readiness(wmask)&wmask != 0 {
epi.Callback(nil)
if m := file.Readiness(wmask) & wmask; m != 0 {
epi.Callback(nil, m)
}
// Add epi to file.epolls so that it is removed when the last
@@ -274,8 +274,8 @@ func (ep *EpollInstance) ModifyInterest(file *FileDescription, num int32, event
file.EventRegister(&epi.waiter, wmask)
// Check if the file is already ready with the new mask.
if file.Readiness(wmask)&wmask != 0 {
epi.Callback(nil)
if m := file.Readiness(wmask) & wmask; m != 0 {
epi.Callback(nil, m)
}
return nil
@@ -311,7 +311,7 @@ func (ep *EpollInstance) DeleteInterest(file *FileDescription, num int32) error
}
// Callback implements waiter.EntryCallback.Callback.
func (epi *epollInterest) Callback(*waiter.Entry) {
func (epi *epollInterest) Callback(*waiter.Entry, waiter.EventMask) {
newReady := false
epi.epoll.mu.Lock()
if !epi.ready {
+7 -2
View File
@@ -43,7 +43,7 @@ import (
type FileDescription struct {
FileDescriptionRefs
// flagsMu protects statusFlags and asyncHandler below.
// flagsMu protects `statusFlags`, `saved`, and `asyncHandler` below.
flagsMu sync.Mutex `state:"nosave"`
// statusFlags contains status flags, "initialized by open(2) and possibly
@@ -52,6 +52,11 @@ type FileDescription struct {
// access to asyncHandler.
statusFlags uint32
// saved is true after beforeSave is called. This is used to prevent
// double-unregistration of asyncHandler. This does not work properly for
// save-resume, which is not currently supported in gVisor (see b/26588733).
saved bool `state:"nosave"`
// asyncHandler handles O_ASYNC signal generation. It is set with the
// F_SETOWN or F_SETOWN_EX fcntls. For asyncHandler to be used, O_ASYNC must
// also be set by fcntl(2).
@@ -184,7 +189,7 @@ func (fd *FileDescription) DecRef(ctx context.Context) {
}
fd.vd.DecRef(ctx)
fd.flagsMu.Lock()
if fd.statusFlags&linux.O_ASYNC != 0 && fd.asyncHandler != nil {
if !fd.saved && fd.statusFlags&linux.O_ASYNC != 0 && fd.asyncHandler != nil {
fd.asyncHandler.Unregister(fd)
}
fd.asyncHandler = nil
+18 -1
View File
@@ -18,8 +18,10 @@ import (
"fmt"
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/refsvfs2"
"gvisor.dev/gvisor/pkg/waiter"
)
// FilesystemImplSaveRestoreExtension is an optional extension to
@@ -120,5 +122,20 @@ func (mnt *Mount) afterLoad() {
func (epi *epollInterest) afterLoad() {
// Mark all epollInterests as ready after restore so that the next call to
// EpollInstance.ReadEvents() rechecks their readiness.
epi.Callback(nil)
epi.Callback(nil, waiter.EventMaskFromLinux(epi.mask))
}
// beforeSave is called by stateify.
func (fd *FileDescription) beforeSave() {
fd.saved = true
if fd.statusFlags&linux.O_ASYNC != 0 && fd.asyncHandler != nil {
fd.asyncHandler.Unregister(fd)
}
}
// afterLoad is called by stateify.
func (fd *FileDescription) afterLoad() {
if fd.statusFlags&linux.O_ASYNC != 0 && fd.asyncHandler != nil {
fd.asyncHandler.Register(fd)
}
}
+7 -4
View File
@@ -119,7 +119,10 @@ type EntryCallback interface {
// The callback is supposed to perform minimal work, and cannot call
// any method on the queue itself because it will be locked while the
// callback is running.
Callback(e *Entry)
//
// The mask indicates the events that occurred and that the entry is
// interested in.
Callback(e *Entry, mask EventMask)
}
// Entry represents a waiter that can be add to the a wait queue. It can
@@ -140,7 +143,7 @@ type channelCallback struct {
}
// Callback implements EntryCallback.Callback.
func (c *channelCallback) Callback(*Entry) {
func (c *channelCallback) Callback(*Entry, EventMask) {
select {
case c.ch <- struct{}{}:
default:
@@ -193,8 +196,8 @@ func (q *Queue) EventUnregister(e *Entry) {
func (q *Queue) Notify(mask EventMask) {
q.mu.RLock()
for e := q.list.Front(); e != nil; e = e.Next() {
if mask&e.mask != 0 {
e.Callback.Callback(e)
if m := mask & e.mask; m != 0 {
e.Callback.Callback(e, m)
}
}
q.mu.RUnlock()
+13 -7
View File
@@ -20,12 +20,12 @@ import (
)
type callbackStub struct {
f func(e *Entry)
f func(e *Entry, m EventMask)
}
// Callback implements EntryCallback.Callback.
func (c *callbackStub) Callback(e *Entry) {
c.f(e)
func (c *callbackStub) Callback(e *Entry, m EventMask) {
c.f(e, m)
}
func TestEmptyQueue(t *testing.T) {
@@ -36,7 +36,7 @@ func TestEmptyQueue(t *testing.T) {
// Register then unregister a waiter, then notify the queue.
cnt := 0
e := Entry{Callback: &callbackStub{func(*Entry) { cnt++ }}}
e := Entry{Callback: &callbackStub{func(*Entry, EventMask) { cnt++ }}}
q.EventRegister(&e, EventIn)
q.EventUnregister(&e)
q.Notify(EventIn)
@@ -49,7 +49,7 @@ func TestMask(t *testing.T) {
// Register a waiter.
var q Queue
var cnt int
e := Entry{Callback: &callbackStub{func(*Entry) { cnt++ }}}
e := Entry{Callback: &callbackStub{func(*Entry, EventMask) { cnt++ }}}
q.EventRegister(&e, EventIn|EventErr)
// Notify with an overlapping mask.
@@ -101,11 +101,14 @@ func TestConcurrentRegistration(t *testing.T) {
for i := 0; i < concurrency; i++ {
go func() {
var e Entry
e.Callback = &callbackStub{func(entry *Entry) {
e.Callback = &callbackStub{func(entry *Entry, mask EventMask) {
cnt++
if entry != &e {
t.Errorf("entry = %p, want %p", entry, &e)
}
if mask != EventIn {
t.Errorf("mask = %#x want %#x", mask, EventIn)
}
}}
// Wait for notification, then register.
@@ -158,11 +161,14 @@ func TestConcurrentNotification(t *testing.T) {
// Register waiters.
for i := 0; i < waiterCount; i++ {
var e Entry
e.Callback = &callbackStub{func(entry *Entry) {
e.Callback = &callbackStub{func(entry *Entry, mask EventMask) {
atomic.AddInt32(&cnt, 1)
if entry != &e {
t.Errorf("entry = %p, want %p", entry, &e)
}
if mask != EventIn {
t.Errorf("mask = %#x want %#x", mask, EventIn)
}
}}
q.EventRegister(&e, EventIn|EventErr)
+2 -1
View File
@@ -799,8 +799,8 @@ cc_binary(
deps = [
":socket_test_util",
"//test/util:cleanup",
"//test/util:epoll_util",
"//test/util:eventfd_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/flags:flag",
@@ -811,6 +811,7 @@ cc_binary(
"//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:save_util",
"//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_util",
"//test/util:thread_util",
File diff suppressed because it is too large Load Diff