gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/fuse/dev_test.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  	"fmt"
    19  	"math/rand"
    20  	"testing"
    21  
    22  	"gvisor.dev/gvisor/pkg/abi/linux"
    23  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    24  	"gvisor.dev/gvisor/pkg/marshal/primitive"
    25  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
    26  	"gvisor.dev/gvisor/pkg/sentry/kernel"
    27  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    28  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    29  	"gvisor.dev/gvisor/pkg/usermem"
    30  	"gvisor.dev/gvisor/pkg/waiter"
    31  )
    32  
    33  // echoTestOpcode is the Opcode used during testing. The server used in tests
    34  // will simply echo the payload back with the appropriate headers.
    35  const echoTestOpcode linux.FUSEOpcode = 1000
    36  
    37  // TestFUSECommunication tests that the communication layer between the Sentry and the
    38  // FUSE server daemon works as expected.
    39  func TestFUSECommunication(t *testing.T) {
    40  	s := setup(t)
    41  	defer s.Destroy()
    42  
    43  	k := kernel.KernelFromContext(s.Ctx)
    44  	creds := auth.CredentialsFromContext(s.Ctx)
    45  
    46  	// Create test cases with different number of concurrent clients and servers.
    47  	testCases := []struct {
    48  		Name              string
    49  		NumClients        int
    50  		NumServers        int
    51  		MaxActiveRequests uint64
    52  	}{
    53  		{
    54  			Name:              "SingleClientSingleServer",
    55  			NumClients:        1,
    56  			NumServers:        1,
    57  			MaxActiveRequests: maxActiveRequestsDefault,
    58  		},
    59  		{
    60  			Name:              "SingleClientMultipleServers",
    61  			NumClients:        1,
    62  			NumServers:        10,
    63  			MaxActiveRequests: maxActiveRequestsDefault,
    64  		},
    65  		{
    66  			Name:              "MultipleClientsSingleServer",
    67  			NumClients:        10,
    68  			NumServers:        1,
    69  			MaxActiveRequests: maxActiveRequestsDefault,
    70  		},
    71  		{
    72  			Name:              "MultipleClientsMultipleServers",
    73  			NumClients:        10,
    74  			NumServers:        10,
    75  			MaxActiveRequests: maxActiveRequestsDefault,
    76  		},
    77  		{
    78  			Name:              "RequestCapacityFull",
    79  			NumClients:        10,
    80  			NumServers:        1,
    81  			MaxActiveRequests: 1,
    82  		},
    83  		{
    84  			Name:              "RequestCapacityContinuouslyFull",
    85  			NumClients:        100,
    86  			NumServers:        2,
    87  			MaxActiveRequests: 2,
    88  		},
    89  	}
    90  
    91  	for _, testCase := range testCases {
    92  		t.Run(testCase.Name, func(t *testing.T) {
    93  			conn, fd, err := newTestConnection(s, testCase.MaxActiveRequests)
    94  			if err != nil {
    95  				t.Fatalf("newTestConnection: %v", err)
    96  			}
    97  
    98  			clientsDone := make([]chan struct{}, testCase.NumClients)
    99  			serversDone := make([]chan struct{}, testCase.NumServers)
   100  			serversKill := make([]chan struct{}, testCase.NumServers)
   101  
   102  			// FUSE clients.
   103  			for i := 0; i < testCase.NumClients; i++ {
   104  				clientsDone[i] = make(chan struct{})
   105  				go func(i int) {
   106  					fuseClientRun(t, s, k, conn, creds, uint32(i), uint64(i), clientsDone[i])
   107  				}(i)
   108  			}
   109  
   110  			// FUSE servers.
   111  			for j := 0; j < testCase.NumServers; j++ {
   112  				serversDone[j] = make(chan struct{})
   113  				serversKill[j] = make(chan struct{}, 1) // The kill command shouldn't block.
   114  				go func(j int) {
   115  					fuseServerRun(t, s, k, fd, serversDone[j], serversKill[j])
   116  				}(j)
   117  			}
   118  
   119  			// Tear down.
   120  			//
   121  			// Make sure all the clients are done.
   122  			for i := 0; i < testCase.NumClients; i++ {
   123  				<-clientsDone[i]
   124  			}
   125  
   126  			// Kill any server that is potentially waiting.
   127  			for j := 0; j < testCase.NumServers; j++ {
   128  				serversKill[j] <- struct{}{}
   129  			}
   130  
   131  			// Make sure all the servers are done.
   132  			for j := 0; j < testCase.NumServers; j++ {
   133  				<-serversDone[j]
   134  			}
   135  		})
   136  	}
   137  }
   138  
   139  func TestReuseFd(t *testing.T) {
   140  	s := setup(t)
   141  	defer s.Destroy()
   142  	_, fd, err := newTestConnection(s, maxActiveRequestsDefault)
   143  	if err != nil {
   144  		t.Fatalf("newTestConnection: %v", err)
   145  	}
   146  	fs1, err := newTestFilesystem(s, fd, maxActiveRequestsDefault)
   147  	if err != nil {
   148  		t.Fatalf("newTestFilesystem: %v", err)
   149  	}
   150  	defer fs1.Release(s.Ctx)
   151  	fs2, err := newTestFilesystem(s, fd, maxActiveRequestsDefault)
   152  	if err != nil {
   153  		t.Fatalf("newTestFilesystem: %v", err)
   154  	}
   155  	defer fs2.Release(s.Ctx)
   156  	if fs1.conn != fs2.conn {
   157  		t.Errorf("second fs connection = %v, want = %v", fs2.conn, fs1.conn)
   158  	}
   159  }
   160  
   161  // CallTest makes a request to the server and blocks the invoking
   162  // goroutine until a server responds with a response. Doesn't block
   163  // a kernel.Task. Analogous to Connection.Call but used for testing.
   164  func CallTest(conn *connection, t *kernel.Task, r *Request, i uint32) (*Response, error) {
   165  	conn.fd.mu.Lock()
   166  
   167  	// Wait until we're certain that a new request can be processed.
   168  	for conn.fd.numActiveRequests == conn.maxActiveRequests {
   169  		conn.fd.mu.Unlock()
   170  		select {
   171  		case <-conn.fd.fullQueueCh:
   172  		}
   173  		conn.fd.mu.Lock()
   174  	}
   175  
   176  	fut, err := conn.callFutureLocked(r) // No task given.
   177  	conn.fd.mu.Unlock()
   178  
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	// Resolve the response.
   184  	//
   185  	// Block without a task.
   186  	select {
   187  	case <-fut.ch:
   188  	}
   189  
   190  	// A response is ready. Resolve and return it.
   191  	return fut.getResponse(), nil
   192  }
   193  
   194  // ReadTest is analogous to vfs.FileDescription.Read and reads from the FUSE
   195  // device. However, it does so by - not blocking the task that is calling - and
   196  // instead just waits on a channel. The behaviour is essentially the same as
   197  // DeviceFD.Read except it guarantees that the task is not blocked.
   198  func ReadTest(serverTask *kernel.Task, fd *vfs.FileDescription, inIOseq usermem.IOSequence, killServer chan struct{}) (int64, bool, error) {
   199  	var err error
   200  	var n, total int64
   201  
   202  	dev := fd.Impl().(*DeviceFD)
   203  
   204  	// Register for notifications.
   205  	w, ch := waiter.NewChannelEntry(waiter.ReadableEvents)
   206  	dev.EventRegister(&w)
   207  	for {
   208  		// Issue the request and break out if it completes with anything other than
   209  		// "would block".
   210  		n, err = dev.Read(serverTask, inIOseq, vfs.ReadOptions{})
   211  		total += n
   212  		if err != linuxerr.ErrWouldBlock {
   213  			break
   214  		}
   215  
   216  		// Wait for a notification that we should retry.
   217  		// Emulate the blocking for when no requests are available
   218  		select {
   219  		case <-ch:
   220  		case <-killServer:
   221  			// Server killed by the main program.
   222  			return 0, true, nil
   223  		}
   224  	}
   225  
   226  	dev.EventUnregister(&w)
   227  	return total, false, err
   228  }
   229  
   230  // fuseClientRun emulates all the actions of a normal FUSE request. It creates
   231  // a header, a payload, calls the server, waits for the response, and processes
   232  // the response.
   233  func fuseClientRun(t *testing.T, s *testutil.System, k *kernel.Kernel, conn *connection, creds *auth.Credentials, pid uint32, inode uint64, clientDone chan struct{}) {
   234  	defer func() {
   235  		if !t.Failed() {
   236  			clientDone <- struct{}{}
   237  		}
   238  	}()
   239  
   240  	tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   241  	clientTask, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("fuse-client-%v", pid), tc, s.MntNs, s.Root, s.Root)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  
   246  	testObj := primitive.Uint32(rand.Uint32())
   247  	req := conn.NewRequest(creds, pid, inode, echoTestOpcode, &testObj)
   248  
   249  	// Queue up a request.
   250  	// Analogous to Call except it doesn't block on the task.
   251  	resp, err := CallTest(conn, clientTask, req, pid)
   252  	if err != nil {
   253  		t.Fatalf("CallTaskNonBlock failed: %v", err)
   254  	}
   255  
   256  	if err = resp.Error(); err != nil {
   257  		t.Fatalf("Server responded with an error: %v", err)
   258  	}
   259  
   260  	var respTestPayload primitive.Uint32
   261  	if err := resp.UnmarshalPayload(&respTestPayload); err != nil {
   262  		t.Fatalf("Unmarshalling payload error: %v", err)
   263  	}
   264  
   265  	if resp.hdr.Unique != req.hdr.Unique {
   266  		t.Fatalf("got response for another request. Expected response for req %v but got response for req %v",
   267  			req.hdr.Unique, resp.hdr.Unique)
   268  	}
   269  
   270  	if respTestPayload != testObj {
   271  		t.Fatalf("read incorrect data. Data expected: %d, but got %d", testObj, respTestPayload)
   272  	}
   273  
   274  }
   275  
   276  // fuseServerRun creates a task and emulates all the actions of a simple FUSE server
   277  // that simply reads a request and echos the same struct back as a response using the
   278  // appropriate headers.
   279  func fuseServerRun(t *testing.T, s *testutil.System, k *kernel.Kernel, fd *vfs.FileDescription, serverDone, killServer chan struct{}) {
   280  	defer func() {
   281  		if !t.Failed() {
   282  			serverDone <- struct{}{}
   283  		}
   284  	}()
   285  
   286  	// Create the tasks that the server will be using.
   287  	tc := k.NewThreadGroup(k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
   288  
   289  	var readPayload primitive.Uint32
   290  	serverTask, err := testutil.CreateTask(s.Ctx, "fuse-server", tc, s.MntNs, s.Root, s.Root)
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  
   295  	// Read the request.
   296  	for {
   297  		payloadLen := uint32(readPayload.SizeBytes())
   298  
   299  		// The read buffer must meet some certain size criteria.
   300  		buffSize := linux.SizeOfFUSEHeaderIn + payloadLen
   301  		if buffSize < linux.FUSE_MIN_READ_BUFFER {
   302  			buffSize = linux.FUSE_MIN_READ_BUFFER
   303  		}
   304  		inBuf := make([]byte, buffSize)
   305  		inIOseq := usermem.BytesIOSequence(inBuf)
   306  
   307  		n, serverKilled, err := ReadTest(serverTask, fd, inIOseq, killServer)
   308  		if err != nil {
   309  			t.Fatalf("Read failed :%v", err)
   310  		}
   311  
   312  		// The server should shut down. No new requests are going to be made.
   313  		if serverKilled {
   314  			break
   315  		}
   316  
   317  		if n <= 0 {
   318  			t.Fatalf("Read read no bytes")
   319  		}
   320  
   321  		var readFUSEHeaderIn linux.FUSEHeaderIn
   322  		inBuf = readFUSEHeaderIn.UnmarshalUnsafe(inBuf)
   323  		readPayload.UnmarshalUnsafe(inBuf)
   324  
   325  		if readFUSEHeaderIn.Opcode != echoTestOpcode {
   326  			t.Fatalf("read incorrect data. Header: %v, Payload: %v", readFUSEHeaderIn, readPayload)
   327  		}
   328  
   329  		// Write the response.
   330  		outBuf := make([]byte, linux.SizeOfFUSEHeaderOut+payloadLen)
   331  		outHeader := linux.FUSEHeaderOut{
   332  			Len:    linux.SizeOfFUSEHeaderOut + payloadLen,
   333  			Error:  0,
   334  			Unique: readFUSEHeaderIn.Unique,
   335  		}
   336  
   337  		// Echo the payload back.
   338  		outHeader.MarshalUnsafe(outBuf[:linux.SizeOfFUSEHeaderOut])
   339  		readPayload.MarshalUnsafe(outBuf[linux.SizeOfFUSEHeaderOut:])
   340  		outIOseq := usermem.BytesIOSequence(outBuf)
   341  
   342  		_, err = fd.Write(s.Ctx, outIOseq, vfs.WriteOptions{})
   343  		if err != nil {
   344  			t.Fatalf("Write failed :%v", err)
   345  		}
   346  	}
   347  }