github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/fuse/connection_control.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 fuse 16 17 import ( 18 "golang.org/x/sys/unix" 19 "github.com/nicocha30/gvisor-ligolo/pkg/abi/linux" 20 "github.com/nicocha30/gvisor-ligolo/pkg/context" 21 "github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr" 22 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth" 23 ) 24 25 // consts used by FUSE_INIT negotiation. 26 const ( 27 // fuseMaxMaxPages is the maximum value for MaxPages received in InitOut. 28 // Follow the same behavior as unix fuse implementation. 29 fuseMaxMaxPages = 256 30 31 // Maximum value for the time granularity for file time stamps, 1s. 32 // Follow the same behavior as unix fuse implementation. 33 fuseMaxTimeGranNs = 1000000000 34 35 // Minimum value for MaxWrite and MaxRead. 36 // Follow the same behavior as unix fuse implementation. 37 fuseMinMaxWrite = 4096 38 fuseMinMaxRead = 4096 39 40 // Temporary default value for max readahead, 128kb. 41 fuseDefaultMaxReadahead = 131072 42 43 // The FUSE_INIT_IN flags sent to the daemon. 44 // TODO(gvisor.dev/issue/3199): complete the flags. 45 fuseDefaultInitFlags = linux.FUSE_MAX_PAGES 46 47 // An INIT response needs to be at least this long. 48 minInitSize = 24 49 ) 50 51 // Adjustable maximums for Connection's cogestion control parameters. 52 // Used as the upperbound of the config values. 53 // Currently we do not support adjustment to them. 54 var ( 55 MaxUserBackgroundRequest uint16 = fuseDefaultMaxBackground 56 MaxUserCongestionThreshold uint16 = fuseDefaultCongestionThreshold 57 ) 58 59 // SetInitialized atomically sets the connection as initialized. 60 func (conn *connection) SetInitialized() { 61 // Unblock the requests sent before INIT. 62 close(conn.initializedChan) 63 64 // Close the channel first to avoid the non-atomic situation 65 // where conn.initialized is true but there are 66 // tasks being blocked on the channel. 67 // And it prevents the newer tasks from gaining 68 // unnecessary higher chance to be issued before the blocked one. 69 70 conn.initialized.Store(1) 71 } 72 73 // Initialized atomically check if the connection is initialized. pairs with 74 // SetInitialized(). 75 func (conn *connection) Initialized() bool { 76 return conn.initialized.Load() != 0 77 } 78 79 // InitSend sends a FUSE_INIT request. 80 func (conn *connection) InitSend(creds *auth.Credentials, pid uint32) error { 81 in := linux.FUSEInitIn{ 82 Major: linux.FUSE_KERNEL_VERSION, 83 Minor: linux.FUSE_KERNEL_MINOR_VERSION, 84 // TODO(gvisor.dev/issue/3196): find appropriate way to calculate this 85 MaxReadahead: fuseDefaultMaxReadahead, 86 Flags: fuseDefaultInitFlags, 87 } 88 89 req := conn.NewRequest(creds, pid, 0, linux.FUSE_INIT, &in) 90 // Since there is no task to block on and FUSE_INIT is the request 91 // to unblock other requests, use context.Background(). 92 return conn.CallAsync(context.Background(), req) 93 } 94 95 // InitRecv receives a FUSE_INIT reply and process it. 96 // 97 // Preconditions: conn.asyncMu must not be held if minor version is newer than 13. 98 func (conn *connection) InitRecv(res *Response, hasSysAdminCap bool) error { 99 if err := res.Error(); err != nil { 100 return err 101 } 102 103 if res.DataLen() < minInitSize { 104 return linuxerr.EINVAL 105 } 106 initRes := fuseInitRes{initLen: res.DataLen()} 107 if err := res.UnmarshalPayload(&initRes); err != nil { 108 return err 109 } 110 111 return conn.initProcessReply(&initRes.initOut, hasSysAdminCap) 112 } 113 114 // Process the FUSE_INIT reply from the FUSE server. 115 // It tries to acquire the conn.asyncMu lock if minor version is newer than 13. 116 func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap bool) error { 117 conn.mu.Lock() 118 // No matter error or not, always set initialized. 119 // to unblock the blocked requests. 120 defer func() { 121 conn.SetInitialized() 122 conn.mu.Unlock() 123 }() 124 125 // No support for old major fuse versions. 126 if out.Major != linux.FUSE_KERNEL_VERSION { 127 conn.connInitError = true 128 return nil 129 } 130 131 // Start processing the reply. 132 conn.connInitSuccess = true 133 conn.minor = out.Minor 134 135 // No support for negotiating MaxWrite before minor version 5. 136 if out.Minor >= 5 { 137 conn.maxWrite = out.MaxWrite 138 } else { 139 conn.maxWrite = fuseMinMaxWrite 140 } 141 if conn.maxWrite < fuseMinMaxWrite { 142 conn.maxWrite = fuseMinMaxWrite 143 } 144 145 // No support for the following flags before minor version 6. 146 if out.Minor >= 6 { 147 conn.asyncRead = out.Flags&linux.FUSE_ASYNC_READ != 0 148 conn.bigWrites = out.Flags&linux.FUSE_BIG_WRITES != 0 149 conn.dontMask = out.Flags&linux.FUSE_DONT_MASK != 0 150 conn.writebackCache = out.Flags&linux.FUSE_WRITEBACK_CACHE != 0 151 conn.atomicOTrunc = out.Flags&linux.FUSE_ATOMIC_O_TRUNC != 0 152 153 // TODO(gvisor.dev/issue/3195): figure out how to use TimeGran (0 < TimeGran <= fuseMaxTimeGranNs). 154 155 if out.Flags&linux.FUSE_MAX_PAGES != 0 { 156 maxPages := out.MaxPages 157 if maxPages < 1 { 158 maxPages = 1 159 } 160 if maxPages > fuseMaxMaxPages { 161 maxPages = fuseMaxMaxPages 162 } 163 conn.maxPages = maxPages 164 } 165 } 166 167 // No support for limits before minor version 13. 168 if out.Minor >= 13 { 169 conn.asyncMu.Lock() 170 171 if out.MaxBackground > 0 { 172 conn.asyncNumMax = out.MaxBackground 173 174 if !hasSysAdminCap && 175 conn.asyncNumMax > MaxUserBackgroundRequest { 176 conn.asyncNumMax = MaxUserBackgroundRequest 177 } 178 } 179 180 if out.CongestionThreshold > 0 { 181 conn.asyncCongestionThreshold = out.CongestionThreshold 182 183 if !hasSysAdminCap && 184 conn.asyncCongestionThreshold > MaxUserCongestionThreshold { 185 conn.asyncCongestionThreshold = MaxUserCongestionThreshold 186 } 187 } 188 189 conn.asyncMu.Unlock() 190 } 191 192 return nil 193 } 194 195 // Abort this FUSE connection. 196 // It tries to acquire conn.fd.mu, conn.lock, conn.bgLock in order. 197 // All possible requests waiting or blocking will be aborted. 198 // 199 // +checklocks:conn.fd.mu 200 func (conn *connection) Abort(ctx context.Context) { 201 conn.mu.Lock() 202 conn.asyncMu.Lock() 203 204 if !conn.connected { 205 conn.asyncMu.Unlock() 206 conn.mu.Unlock() 207 return 208 } 209 210 conn.connected = false 211 212 // Empty the `fd.queue` that holds the requests 213 // not yet read by the FUSE daemon yet. 214 // These are a subset of the requests in `fuse.completion` map. 215 for !conn.fd.queue.Empty() { 216 req := conn.fd.queue.Front() 217 conn.fd.queue.Remove(req) 218 } 219 220 var terminate []linux.FUSEOpID 221 222 // 2. Collect the requests have not been sent to FUSE daemon, 223 // or have not received a reply. 224 for unique := range conn.fd.completions { 225 terminate = append(terminate, unique) 226 } 227 228 // Release locks to avoid deadlock. 229 conn.asyncMu.Unlock() 230 conn.mu.Unlock() 231 232 // 1. The request blocked before initialization. 233 // Will reach call() `connected` check and return. 234 if !conn.Initialized() { 235 conn.SetInitialized() 236 } 237 238 // 2. Terminate the requests collected above. 239 // Set ECONNABORTED error. 240 // sendError() will remove them from `fd.completion` map. 241 // Will enter the path of a normally received error. 242 for _, toTerminate := range terminate { 243 conn.fd.sendError(ctx, -int32(unix.ECONNABORTED), toTerminate) 244 } 245 246 // 3. The requests not yet written to FUSE device. 247 // Early terminate. 248 // Will reach callFutureLocked() `connected` check and return. 249 close(conn.fd.fullQueueCh) 250 251 // TODO(gvisor.dev/issue/3528): Forget all pending forget reqs. 252 }