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  }