github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/syscalls/linux/error.go (about)

     1  // Copyright 2018 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  	"io"
    19  
    20  	"github.com/metacubex/gvisor/pkg/abi/linux"
    21  	"github.com/metacubex/gvisor/pkg/context"
    22  	"github.com/metacubex/gvisor/pkg/errors/linuxerr"
    23  	"github.com/metacubex/gvisor/pkg/log"
    24  	"github.com/metacubex/gvisor/pkg/metric"
    25  	"github.com/metacubex/gvisor/pkg/sentry/kernel"
    26  	"github.com/metacubex/gvisor/pkg/sentry/vfs"
    27  	"github.com/metacubex/gvisor/pkg/sync"
    28  )
    29  
    30  var (
    31  	partialResultOnce sync.Once
    32  )
    33  
    34  // incrementPartialResultMetric increments PartialResultMetric by calling
    35  // Increment(). This is added as the func Do() which is called below requires
    36  // us to pass a function which does not take any arguments, whereas Increment()
    37  // takes a variadic number of arguments.
    38  func incrementPartialResultMetric() {
    39  	metric.WeirdnessMetric.Increment(&metric.WeirdnessTypePartialResult)
    40  }
    41  
    42  // HandleIOError handles special error cases for partial results. For some
    43  // errors, we may consume the error and return only the partial read/write.
    44  //
    45  // op and f are used only for panics.
    46  func HandleIOError(ctx context.Context, partialResult bool, ioerr, intr error, op string, f *vfs.FileDescription) error {
    47  	known, err := handleIOErrorImpl(ctx, partialResult, ioerr, intr, op)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	if !known {
    52  		// An unknown error is encountered with a partial read/write.
    53  		fs := f.Mount().Filesystem().VirtualFilesystem()
    54  		root := vfs.RootFromContext(ctx)
    55  		name, _ := fs.PathnameWithDeleted(ctx, root, f.VirtualDentry())
    56  		log.Traceback("Invalid request partialResult %v and err (type %T) %v for %s operation on %q", partialResult, ioerr, ioerr, op, name)
    57  		partialResultOnce.Do(incrementPartialResultMetric)
    58  	}
    59  	return nil
    60  }
    61  
    62  // handleIOError handles special error cases for partial results. For some
    63  // errors, we may consume the error and return only the partial read/write.
    64  //
    65  // Returns false if error is unknown.
    66  func handleIOErrorImpl(ctx context.Context, partialResult bool, errOrig, intr error, op string) (bool, error) {
    67  	if errOrig == nil {
    68  		// Typical successful syscall.
    69  		return true, nil
    70  	}
    71  
    72  	// Translate error, if possible, to consolidate errors from other packages
    73  	// into a smaller set of errors from linuxerr package.
    74  	translatedErr := errOrig
    75  	if errno, ok := linuxerr.TranslateError(errOrig); ok {
    76  		translatedErr = errno
    77  	}
    78  	switch {
    79  	case translatedErr == io.EOF:
    80  		// EOF is always consumed. If this is a partial read/write
    81  		// (result != 0), the application will see that, otherwise
    82  		// they will see 0.
    83  		return true, nil
    84  	case linuxerr.Equals(linuxerr.EFBIG, translatedErr):
    85  		t := kernel.TaskFromContext(ctx)
    86  		if t == nil {
    87  			panic("I/O error should only occur from a context associated with a Task")
    88  		}
    89  		// Ignore partialResult because this error only applies to
    90  		// normal files, and for those files we cannot accumulate
    91  		// write results.
    92  		//
    93  		// Do not consume the error and return it as EFBIG.
    94  		// Simultaneously send a SIGXFSZ per setrlimit(2).
    95  		t.SendSignal(kernel.SignalInfoNoInfo(linux.SIGXFSZ, t, t))
    96  		return true, linuxerr.EFBIG
    97  	case linuxerr.Equals(linuxerr.EINTR, translatedErr):
    98  		// The syscall was interrupted. Return nil if it completed
    99  		// partially, otherwise return the error code that the syscall
   100  		// needs (to indicate to the kernel what it should do).
   101  		if partialResult {
   102  			return true, nil
   103  		}
   104  		return true, intr
   105  	}
   106  
   107  	if !partialResult {
   108  		// Typical syscall error.
   109  		return true, errOrig
   110  	}
   111  
   112  	switch {
   113  	case linuxerr.Equals(linuxerr.EINTR, translatedErr):
   114  		// Syscall interrupted, but completed a partial
   115  		// read/write.  Like ErrWouldBlock, since we have a
   116  		// partial read/write, we consume the error and return
   117  		// the partial result.
   118  		return true, nil
   119  	case linuxerr.Equals(linuxerr.EFAULT, translatedErr):
   120  		// EFAULT is only shown the user if nothing was
   121  		// read/written. If we read something (this case), they see
   122  		// a partial read/write. They will then presumably try again
   123  		// with an incremented buffer, which will EFAULT with
   124  		// result == 0.
   125  		return true, nil
   126  	case linuxerr.Equals(linuxerr.EPIPE, translatedErr):
   127  		// Writes to a pipe or socket will return EPIPE if the other
   128  		// side is gone. The partial write is returned. EPIPE will be
   129  		// returned on the next call.
   130  		//
   131  		// TODO(gvisor.dev/issue/161): In some cases SIGPIPE should
   132  		// also be sent to the application.
   133  		return true, nil
   134  	case linuxerr.Equals(linuxerr.ENOSPC, translatedErr):
   135  		// Similar to EPIPE. Return what we wrote this time, and let
   136  		// ENOSPC be returned on the next call.
   137  		return true, nil
   138  	case linuxerr.Equals(linuxerr.ECONNRESET, translatedErr):
   139  		fallthrough
   140  	case linuxerr.Equals(linuxerr.ECONNABORTED, translatedErr):
   141  		fallthrough
   142  	case linuxerr.Equals(linuxerr.ETIMEDOUT, translatedErr):
   143  		// For TCP sendfile connections, we may have a reset, abort or timeout. But
   144  		// we should just return the partial result. The next call will return the
   145  		// error without a partial IO operation.
   146  		return true, nil
   147  	case linuxerr.Equals(linuxerr.EWOULDBLOCK, translatedErr):
   148  		// Syscall would block, but completed a partial read/write.
   149  		// This case should only be returned by IssueIO for nonblocking
   150  		// files. Since we have a partial read/write, we consume
   151  		// ErrWouldBlock, returning the partial result.
   152  		return true, nil
   153  	case linuxerr.IsRestartError(translatedErr):
   154  		// Identical to the EINTR case.
   155  		return true, nil
   156  	}
   157  
   158  	// Error is unknown and cannot be properly handled.
   159  	return false, nil
   160  }