github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/abi/linux" 21 "github.com/nicocha30/gvisor-ligolo/pkg/context" 22 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 23 "github.com/nicocha30/gvisor-ligolo/pkg/log" 24 "github.com/nicocha30/gvisor-ligolo/pkg/metric" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel" 26 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs" 27 "github.com/nicocha30/gvisor-ligolo/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 }