github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/syscalls/linux/sys_poll.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package linux
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/linux"
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/errors/linuxerr"
    23  	"github.com/ttpreport/gvisor-ligolo/pkg/hostarch"
    24  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/arch"
    25  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel"
    26  	ktime "github.com/ttpreport/gvisor-ligolo/pkg/sentry/kernel/time"
    27  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/limits"
    28  	"github.com/ttpreport/gvisor-ligolo/pkg/sentry/vfs"
    29  	"github.com/ttpreport/gvisor-ligolo/pkg/waiter"
    30  )
    31  
    32  // fileCap is the maximum allowable files for poll & select. This has no
    33  // equivalent in Linux; it exists in gVisor since allocation failure in Go is
    34  // unrecoverable.
    35  const fileCap = 1024 * 1024
    36  
    37  // Masks for "readable", "writable", and "exceptional" events as defined by
    38  // select(2).
    39  const (
    40  	// selectReadEvents is analogous to the Linux kernel's
    41  	// fs/select.c:POLLIN_SET.
    42  	selectReadEvents = linux.POLLIN | linux.POLLHUP | linux.POLLERR
    43  
    44  	// selectWriteEvents is analogous to the Linux kernel's
    45  	// fs/select.c:POLLOUT_SET.
    46  	selectWriteEvents = linux.POLLOUT | linux.POLLERR
    47  
    48  	// selectExceptEvents is analogous to the Linux kernel's
    49  	// fs/select.c:POLLEX_SET.
    50  	selectExceptEvents = linux.POLLPRI
    51  )
    52  
    53  // pollState tracks the associated file description and waiter of a PollFD.
    54  type pollState struct {
    55  	file   *vfs.FileDescription
    56  	waiter waiter.Entry
    57  }
    58  
    59  // initReadiness gets the current ready mask for the file represented by the FD
    60  // stored in pfd.FD. If a channel is passed in, the waiter entry in "state" is
    61  // used to register with the file for event notifications, and a reference to
    62  // the file is stored in "state".
    63  func initReadiness(t *kernel.Task, pfd *linux.PollFD, state *pollState, ch chan struct{}) error {
    64  	if pfd.FD < 0 {
    65  		pfd.REvents = 0
    66  		return nil
    67  	}
    68  
    69  	file := t.GetFile(pfd.FD)
    70  	if file == nil {
    71  		pfd.REvents = linux.POLLNVAL
    72  		return nil
    73  	}
    74  
    75  	if ch == nil {
    76  		defer file.DecRef(t)
    77  	} else {
    78  		state.file = file
    79  		state.waiter.Init(waiter.ChannelNotifier(ch), waiter.EventMaskFromLinux(uint32(pfd.Events)))
    80  		if err := file.EventRegister(&state.waiter); err != nil {
    81  			return err
    82  		}
    83  	}
    84  
    85  	r := file.Readiness(waiter.EventMaskFromLinux(uint32(pfd.Events)))
    86  	pfd.REvents = int16(r.ToLinux()) & pfd.Events
    87  	return nil
    88  }
    89  
    90  // releaseState releases all the pollState in "state".
    91  func releaseState(t *kernel.Task, state []pollState) {
    92  	for i := range state {
    93  		if state[i].file != nil {
    94  			state[i].file.EventUnregister(&state[i].waiter)
    95  			state[i].file.DecRef(t)
    96  		}
    97  	}
    98  }
    99  
   100  // pollBlock polls the PollFDs in "pfd" with a bounded time specified in "timeout"
   101  // when "timeout" is greater than zero.
   102  //
   103  // pollBlock returns the remaining timeout, which is always 0 on a timeout; and 0 or
   104  // positive if interrupted by a signal.
   105  func pollBlock(t *kernel.Task, pfd []linux.PollFD, timeout time.Duration) (time.Duration, uintptr, error) {
   106  	var ch chan struct{}
   107  	if timeout != 0 {
   108  		ch = make(chan struct{}, 1)
   109  	}
   110  
   111  	// Register for event notification in the files involved if we may
   112  	// block (timeout not zero). Once we find a file that has a non-zero
   113  	// result, we stop registering for events but still go through all files
   114  	// to get their ready masks.
   115  	state := make([]pollState, len(pfd))
   116  	defer releaseState(t, state)
   117  	n := uintptr(0)
   118  	for i := range pfd {
   119  		if err := initReadiness(t, &pfd[i], &state[i], ch); err != nil {
   120  			return timeout, 0, err
   121  		}
   122  		if pfd[i].REvents != 0 {
   123  			n++
   124  			ch = nil
   125  		}
   126  	}
   127  
   128  	if timeout == 0 {
   129  		return timeout, n, nil
   130  	}
   131  
   132  	haveTimeout := timeout >= 0
   133  
   134  	for n == 0 {
   135  		var err error
   136  		// Wait for a notification.
   137  		timeout, err = t.BlockWithTimeout(ch, haveTimeout, timeout)
   138  		if err != nil {
   139  			if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
   140  				err = nil
   141  			}
   142  			return timeout, 0, err
   143  		}
   144  
   145  		// We got notified, count how many files are ready. If none,
   146  		// then this was a spurious notification, and we just go back
   147  		// to sleep with the remaining timeout.
   148  		for i := range state {
   149  			if state[i].file == nil {
   150  				continue
   151  			}
   152  
   153  			r := state[i].file.Readiness(waiter.EventMaskFromLinux(uint32(pfd[i].Events)))
   154  			rl := int16(r.ToLinux()) & pfd[i].Events
   155  			if rl != 0 {
   156  				pfd[i].REvents = rl
   157  				n++
   158  			}
   159  		}
   160  	}
   161  
   162  	return timeout, n, nil
   163  }
   164  
   165  // CopyInPollFDs copies an array of struct pollfd unless nfds exceeds the max.
   166  func CopyInPollFDs(t *kernel.Task, addr hostarch.Addr, nfds uint) ([]linux.PollFD, error) {
   167  	if uint64(nfds) > t.ThreadGroup().Limits().GetCapped(limits.NumberOfFiles, fileCap) {
   168  		return nil, linuxerr.EINVAL
   169  	}
   170  
   171  	pfd := make([]linux.PollFD, nfds)
   172  	if nfds > 0 {
   173  		if _, err := linux.CopyPollFDSliceIn(t, addr, pfd); err != nil {
   174  			return nil, err
   175  		}
   176  	}
   177  
   178  	return pfd, nil
   179  }
   180  
   181  func doPoll(t *kernel.Task, addr hostarch.Addr, nfds uint, timeout time.Duration) (time.Duration, uintptr, error) {
   182  	pfd, err := CopyInPollFDs(t, addr, nfds)
   183  	if err != nil {
   184  		return timeout, 0, err
   185  	}
   186  
   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  	}
   194  	remainingTimeout, n, err := pollBlock(t, pfd, timeout)
   195  	err = linuxerr.ConvertIntr(err, linuxerr.EINTR)
   196  
   197  	// The poll entries are copied out regardless of whether
   198  	// any are set or not. This aligns with the Linux behavior.
   199  	if nfds > 0 && err == nil {
   200  		if _, err := linux.CopyPollFDSliceOut(t, addr, pfd); err != nil {
   201  			return remainingTimeout, 0, err
   202  		}
   203  	}
   204  
   205  	return remainingTimeout, n, err
   206  }
   207  
   208  // CopyInFDSet copies an fd set from select(2)/pselect(2).
   209  func CopyInFDSet(t *kernel.Task, addr hostarch.Addr, nBytes, nBitsInLastPartialByte int) ([]byte, error) {
   210  	set := make([]byte, nBytes)
   211  
   212  	if addr != 0 {
   213  		if _, err := t.CopyInBytes(addr, set); err != nil {
   214  			return nil, err
   215  		}
   216  		// If we only use part of the last byte, mask out the extraneous bits.
   217  		//
   218  		// N.B. This only works on little-endian architectures.
   219  		if nBitsInLastPartialByte != 0 {
   220  			set[nBytes-1] &^= byte(0xff) << nBitsInLastPartialByte
   221  		}
   222  	}
   223  	return set, nil
   224  }
   225  
   226  func doSelect(t *kernel.Task, nfds int, readFDs, writeFDs, exceptFDs hostarch.Addr, timeout time.Duration) (uintptr, error) {
   227  	if nfds < 0 || nfds > fileCap {
   228  		return 0, linuxerr.EINVAL
   229  	}
   230  
   231  	// Calculate the size of the fd sets (one bit per fd).
   232  	nBytes := (nfds + 7) / 8
   233  	nBitsInLastPartialByte := nfds % 8
   234  
   235  	// Capture all the provided input vectors.
   236  	r, err := CopyInFDSet(t, readFDs, nBytes, nBitsInLastPartialByte)
   237  	if err != nil {
   238  		return 0, err
   239  	}
   240  	w, err := CopyInFDSet(t, writeFDs, nBytes, nBitsInLastPartialByte)
   241  	if err != nil {
   242  		return 0, err
   243  	}
   244  	e, err := CopyInFDSet(t, exceptFDs, nBytes, nBitsInLastPartialByte)
   245  	if err != nil {
   246  		return 0, err
   247  	}
   248  
   249  	// Count how many FDs are actually being requested so that we can build
   250  	// a PollFD array.
   251  	fdCount := 0
   252  	for i := 0; i < nBytes; i++ {
   253  		v := r[i] | w[i] | e[i]
   254  		for v != 0 {
   255  			v &= (v - 1)
   256  			fdCount++
   257  		}
   258  	}
   259  
   260  	// Build the PollFD array.
   261  	pfd := make([]linux.PollFD, 0, fdCount)
   262  	var fd int32
   263  	for i := 0; i < nBytes; i++ {
   264  		rV, wV, eV := r[i], w[i], e[i]
   265  		v := rV | wV | eV
   266  		m := byte(1)
   267  		for j := 0; j < 8; j++ {
   268  			if (v & m) != 0 {
   269  				// Make sure the fd is valid and decrement the reference
   270  				// immediately to ensure we don't leak. Note, another thread
   271  				// might be about to close fd. This is racy, but that's
   272  				// OK. Linux is racy in the same way.
   273  				file := t.GetFile(fd)
   274  				if file == nil {
   275  					return 0, linuxerr.EBADF
   276  				}
   277  				file.DecRef(t)
   278  
   279  				var mask int16
   280  				if (rV & m) != 0 {
   281  					mask |= selectReadEvents
   282  				}
   283  
   284  				if (wV & m) != 0 {
   285  					mask |= selectWriteEvents
   286  				}
   287  
   288  				if (eV & m) != 0 {
   289  					mask |= selectExceptEvents
   290  				}
   291  
   292  				pfd = append(pfd, linux.PollFD{
   293  					FD:     fd,
   294  					Events: mask,
   295  				})
   296  			}
   297  
   298  			fd++
   299  			m <<= 1
   300  		}
   301  	}
   302  
   303  	// Do the syscall, then count the number of bits set.
   304  	if _, _, err = pollBlock(t, pfd, timeout); err != nil {
   305  		return 0, linuxerr.ConvertIntr(err, linuxerr.EINTR)
   306  	}
   307  
   308  	// r, w, and e are currently event mask bitsets; unset bits corresponding
   309  	// to events that *didn't* occur.
   310  	bitSetCount := uintptr(0)
   311  	for idx := range pfd {
   312  		events := pfd[idx].REvents
   313  		i, j := pfd[idx].FD/8, uint(pfd[idx].FD%8)
   314  		m := byte(1) << j
   315  		if r[i]&m != 0 {
   316  			if (events & selectReadEvents) != 0 {
   317  				bitSetCount++
   318  			} else {
   319  				r[i] &^= m
   320  			}
   321  		}
   322  		if w[i]&m != 0 {
   323  			if (events & selectWriteEvents) != 0 {
   324  				bitSetCount++
   325  			} else {
   326  				w[i] &^= m
   327  			}
   328  		}
   329  		if e[i]&m != 0 {
   330  			if (events & selectExceptEvents) != 0 {
   331  				bitSetCount++
   332  			} else {
   333  				e[i] &^= m
   334  			}
   335  		}
   336  	}
   337  
   338  	// Copy updated vectors back.
   339  	if readFDs != 0 {
   340  		if _, err := t.CopyOutBytes(readFDs, r); err != nil {
   341  			return 0, err
   342  		}
   343  	}
   344  
   345  	if writeFDs != 0 {
   346  		if _, err := t.CopyOutBytes(writeFDs, w); err != nil {
   347  			return 0, err
   348  		}
   349  	}
   350  
   351  	if exceptFDs != 0 {
   352  		if _, err := t.CopyOutBytes(exceptFDs, e); err != nil {
   353  			return 0, err
   354  		}
   355  	}
   356  
   357  	return bitSetCount, nil
   358  }
   359  
   360  // timeoutRemaining returns the amount of time remaining for the specified
   361  // timeout or 0 if it has elapsed.
   362  //
   363  // startNs must be from CLOCK_MONOTONIC.
   364  func timeoutRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration) time.Duration {
   365  	now := t.Kernel().MonotonicClock().Now()
   366  	remaining := timeout - now.Sub(startNs)
   367  	if remaining < 0 {
   368  		remaining = 0
   369  	}
   370  	return remaining
   371  }
   372  
   373  // copyOutTimespecRemaining copies the time remaining in timeout to timespecAddr.
   374  //
   375  // startNs must be from CLOCK_MONOTONIC.
   376  func copyOutTimespecRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration, timespecAddr hostarch.Addr) error {
   377  	if timeout <= 0 {
   378  		return nil
   379  	}
   380  	remaining := timeoutRemaining(t, startNs, timeout)
   381  	tsRemaining := linux.NsecToTimespec(remaining.Nanoseconds())
   382  	_, err := tsRemaining.CopyOut(t, timespecAddr)
   383  	return err
   384  }
   385  
   386  // copyOutTimevalRemaining copies the time remaining in timeout to timevalAddr.
   387  //
   388  // startNs must be from CLOCK_MONOTONIC.
   389  func copyOutTimevalRemaining(t *kernel.Task, startNs ktime.Time, timeout time.Duration, timevalAddr hostarch.Addr) error {
   390  	if timeout <= 0 {
   391  		return nil
   392  	}
   393  	remaining := timeoutRemaining(t, startNs, timeout)
   394  	tvRemaining := linux.NsecToTimeval(remaining.Nanoseconds())
   395  	_, err := tvRemaining.CopyOut(t, timevalAddr)
   396  	return err
   397  }
   398  
   399  // pollRestartBlock encapsulates the state required to restart poll(2) via
   400  // restart_syscall(2).
   401  //
   402  // +stateify savable
   403  type pollRestartBlock struct {
   404  	pfdAddr hostarch.Addr
   405  	nfds    uint
   406  	timeout time.Duration
   407  }
   408  
   409  // Restart implements kernel.SyscallRestartBlock.Restart.
   410  func (p *pollRestartBlock) Restart(t *kernel.Task) (uintptr, error) {
   411  	return poll(t, p.pfdAddr, p.nfds, p.timeout)
   412  }
   413  
   414  func poll(t *kernel.Task, pfdAddr hostarch.Addr, nfds uint, timeout time.Duration) (uintptr, error) {
   415  	remainingTimeout, n, err := doPoll(t, pfdAddr, nfds, timeout)
   416  	// On an interrupt poll(2) is restarted with the remaining timeout.
   417  	if linuxerr.Equals(linuxerr.EINTR, err) {
   418  		t.SetSyscallRestartBlock(&pollRestartBlock{
   419  			pfdAddr: pfdAddr,
   420  			nfds:    nfds,
   421  			timeout: remainingTimeout,
   422  		})
   423  		return 0, linuxerr.ERESTART_RESTARTBLOCK
   424  	}
   425  	return n, err
   426  }
   427  
   428  // Poll implements linux syscall poll(2).
   429  func Poll(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   430  	pfdAddr := args[0].Pointer()
   431  	nfds := uint(args[1].Uint()) // poll(2) uses unsigned long.
   432  	timeout := time.Duration(args[2].Int()) * time.Millisecond
   433  	n, err := poll(t, pfdAddr, nfds, timeout)
   434  	return n, nil, err
   435  }
   436  
   437  // Ppoll implements linux syscall ppoll(2).
   438  func Ppoll(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   439  	pfdAddr := args[0].Pointer()
   440  	nfds := uint(args[1].Uint()) // poll(2) uses unsigned long.
   441  	timespecAddr := args[2].Pointer()
   442  	maskAddr := args[3].Pointer()
   443  	maskSize := uint(args[4].Uint())
   444  
   445  	timeout, err := copyTimespecInToDuration(t, timespecAddr)
   446  	if err != nil {
   447  		return 0, nil, err
   448  	}
   449  
   450  	var startNs ktime.Time
   451  	if timeout > 0 {
   452  		startNs = t.Kernel().MonotonicClock().Now()
   453  	}
   454  
   455  	if err := setTempSignalSet(t, maskAddr, maskSize); err != nil {
   456  		return 0, nil, err
   457  	}
   458  
   459  	_, n, err := doPoll(t, pfdAddr, nfds, timeout)
   460  	copyErr := copyOutTimespecRemaining(t, startNs, timeout, timespecAddr)
   461  	// doPoll returns EINTR if interrupted, but ppoll is normally restartable
   462  	// if interrupted by something other than a signal handled by the
   463  	// application (i.e. returns ERESTARTNOHAND). However, if
   464  	// copyOutTimespecRemaining failed, then the restarted ppoll would use the
   465  	// wrong timeout, so the error should be left as EINTR.
   466  	//
   467  	// Note that this means that if err is nil but copyErr is not, copyErr is
   468  	// ignored. This is consistent with Linux.
   469  	if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil {
   470  		err = linuxerr.ERESTARTNOHAND
   471  	}
   472  	return n, nil, err
   473  }
   474  
   475  // Select implements linux syscall select(2).
   476  func Select(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   477  	nfds := int(args[0].Int()) // select(2) uses an int.
   478  	readFDs := args[1].Pointer()
   479  	writeFDs := args[2].Pointer()
   480  	exceptFDs := args[3].Pointer()
   481  	timevalAddr := args[4].Pointer()
   482  
   483  	// Use a negative Duration to indicate "no timeout".
   484  	timeout := time.Duration(-1)
   485  	if timevalAddr != 0 {
   486  		var timeval linux.Timeval
   487  		if _, err := timeval.CopyIn(t, timevalAddr); err != nil {
   488  			return 0, nil, err
   489  		}
   490  		if timeval.Sec < 0 || timeval.Usec < 0 {
   491  			return 0, nil, linuxerr.EINVAL
   492  		}
   493  		timeout = time.Duration(timeval.ToNsecCapped())
   494  	}
   495  	startNs := t.Kernel().MonotonicClock().Now()
   496  	n, err := doSelect(t, nfds, readFDs, writeFDs, exceptFDs, timeout)
   497  	copyErr := copyOutTimevalRemaining(t, startNs, timeout, timevalAddr)
   498  	// See comment in Ppoll.
   499  	if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil {
   500  		err = linuxerr.ERESTARTNOHAND
   501  	}
   502  	return n, nil, err
   503  }
   504  
   505  // +marshal
   506  type sigSetWithSize struct {
   507  	sigsetAddr   uint64
   508  	sizeofSigset uint64
   509  }
   510  
   511  // Pselect6 implements linux syscall pselect6(2).
   512  func Pselect6(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   513  	nfds := int(args[0].Int()) // select(2) uses an int.
   514  	readFDs := args[1].Pointer()
   515  	writeFDs := args[2].Pointer()
   516  	exceptFDs := args[3].Pointer()
   517  	timespecAddr := args[4].Pointer()
   518  	maskWithSizeAddr := args[5].Pointer()
   519  
   520  	timeout, err := copyTimespecInToDuration(t, timespecAddr)
   521  	if err != nil {
   522  		return 0, nil, err
   523  	}
   524  
   525  	var startNs ktime.Time
   526  	if timeout > 0 {
   527  		startNs = t.Kernel().MonotonicClock().Now()
   528  	}
   529  
   530  	if maskWithSizeAddr != 0 {
   531  		if t.Arch().Width() != 8 {
   532  			panic(fmt.Sprintf("unsupported sizeof(void*): %d", t.Arch().Width()))
   533  		}
   534  		var maskStruct sigSetWithSize
   535  		if _, err := maskStruct.CopyIn(t, maskWithSizeAddr); err != nil {
   536  			return 0, nil, err
   537  		}
   538  		if err := setTempSignalSet(t, hostarch.Addr(maskStruct.sigsetAddr), uint(maskStruct.sizeofSigset)); err != nil {
   539  			return 0, nil, err
   540  		}
   541  	}
   542  
   543  	n, err := doSelect(t, nfds, readFDs, writeFDs, exceptFDs, timeout)
   544  	copyErr := copyOutTimespecRemaining(t, startNs, timeout, timespecAddr)
   545  	// See comment in Ppoll.
   546  	if linuxerr.Equals(linuxerr.EINTR, err) && copyErr == nil {
   547  		err = linuxerr.ERESTARTNOHAND
   548  	}
   549  	return n, nil, err
   550  }
   551  
   552  func setTempSignalSet(t *kernel.Task, maskAddr hostarch.Addr, maskSize uint) error {
   553  	if maskAddr == 0 {
   554  		return nil
   555  	}
   556  	if maskSize != linux.SignalSetSize {
   557  		return linuxerr.EINVAL
   558  	}
   559  	var mask linux.SignalSet
   560  	if _, err := mask.CopyIn(t, maskAddr); err != nil {
   561  		return err
   562  	}
   563  	mask &^= kernel.UnblockableSignals
   564  	oldmask := t.SignalMask()
   565  	t.SetSignalMask(mask)
   566  	t.SetSavedSignalMask(oldmask)
   567  	return nil
   568  }