gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/boot/loader_test.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 boot
    16  
    17  import (
    18  	"fmt"
    19  	"math/rand"
    20  	"os"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	specs "github.com/opencontainers/runtime-spec/specs-go"
    26  	"github.com/syndtr/gocapability/capability"
    27  	"golang.org/x/sys/unix"
    28  	"gvisor.dev/gvisor/pkg/control/server"
    29  	"gvisor.dev/gvisor/pkg/cpuid"
    30  	"gvisor.dev/gvisor/pkg/fspath"
    31  	"gvisor.dev/gvisor/pkg/log"
    32  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    33  	"gvisor.dev/gvisor/pkg/sentry/seccheck"
    34  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    35  	"gvisor.dev/gvisor/pkg/sync"
    36  	"gvisor.dev/gvisor/pkg/unet"
    37  	"gvisor.dev/gvisor/runsc/config"
    38  	"gvisor.dev/gvisor/runsc/flag"
    39  	"gvisor.dev/gvisor/runsc/fsgofer"
    40  )
    41  
    42  func init() {
    43  	log.SetLevel(log.Debug)
    44  	rand.Seed(time.Now().UnixNano())
    45  	if err := fsgofer.OpenProcSelfFD("/proc/self/fd"); err != nil {
    46  		panic(err)
    47  	}
    48  }
    49  
    50  func testConfig() *config.Config {
    51  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
    52  	config.RegisterFlags(testFlags)
    53  	conf, err := config.NewFromFlags(testFlags)
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  	// Change test defaults.
    58  	conf.RootDir = "unused_root_dir"
    59  	conf.Network = config.NetworkNone
    60  	conf.DisableSeccomp = true
    61  	return conf
    62  }
    63  
    64  // testSpec returns a simple spec that can be used in tests.
    65  func testSpec() *specs.Spec {
    66  	return &specs.Spec{
    67  		// The host filesystem root is the sandbox root.
    68  		Root: &specs.Root{
    69  			Path:     "/",
    70  			Readonly: true,
    71  		},
    72  		Process: &specs.Process{
    73  			Args: []string{"/bin/true"},
    74  		},
    75  	}
    76  }
    77  
    78  // startGofer starts a new gofer routine serving 'root' path. It returns the
    79  // sandbox side of the connection, and a function that when called will stop the
    80  // gofer.
    81  func startGofer(root string, conf *config.Config) (int, func(), error) {
    82  	fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
    83  	if err != nil {
    84  		return 0, nil, err
    85  	}
    86  	sandboxEnd, goferEnd := fds[0], fds[1]
    87  
    88  	socket, err := unet.NewSocket(goferEnd)
    89  	if err != nil {
    90  		unix.Close(sandboxEnd)
    91  		unix.Close(goferEnd)
    92  		return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err)
    93  	}
    94  	server := fsgofer.NewLisafsServer(fsgofer.Config{
    95  		HostUDS:            conf.GetHostUDS(),
    96  		HostFifo:           conf.HostFifo,
    97  		DonateMountPointFD: conf.DirectFS,
    98  	})
    99  	c, err := server.CreateConnection(socket, root, true /* readonly */)
   100  	if err != nil {
   101  		return 0, nil, err
   102  	}
   103  	server.StartConnection(c)
   104  	// Closing the gofer socket will stop the gofer and exit goroutine above.
   105  	cleanup := func() {
   106  		if err := socket.Close(); err != nil {
   107  			log.Warningf("Error closing gofer socket: %v", err)
   108  		}
   109  		server.Wait()
   110  		server.Destroy()
   111  	}
   112  	return sandboxEnd, cleanup, nil
   113  }
   114  
   115  func createLoader(conf *config.Config, spec *specs.Spec) (*Loader, func(), error) {
   116  	sock := fmt.Sprintf("\x00loader-test.%010d", rand.Int())
   117  	fd, err := server.CreateSocket(sock)
   118  	if err != nil {
   119  		return nil, nil, err
   120  	}
   121  	sandEnd, cleanup, err := startGofer(spec.Root.Path, conf)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  
   126  	// Loader takes ownership of stdio.
   127  	var stdio []int
   128  	for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
   129  		newFd, err := unix.Dup(int(f.Fd()))
   130  		if err != nil {
   131  			return nil, nil, err
   132  		}
   133  		stdio = append(stdio, newFd)
   134  	}
   135  
   136  	args := Args{
   137  		ID:              "foo",
   138  		Spec:            spec,
   139  		Conf:            conf,
   140  		ControllerFD:    fd,
   141  		GoferFDs:        []int{sandEnd},
   142  		DevGoferFD:      -1,
   143  		StdioFDs:        stdio,
   144  		GoferMountConfs: []GoferMountConf{{Lower: Lisafs, Upper: NoOverlay}},
   145  		PodInitConfigFD: -1,
   146  		ExecFD:          -1,
   147  	}
   148  	l, err := New(args)
   149  	if err != nil {
   150  		cleanup()
   151  		return nil, nil, err
   152  	}
   153  	return l, cleanup, nil
   154  }
   155  
   156  // TestRun runs a simple application in a sandbox and checks that it succeeds.
   157  func TestRun(t *testing.T) {
   158  	l, cleanup, err := createLoader(testConfig(), testSpec())
   159  	if err != nil {
   160  		t.Fatalf("error creating loader: %v", err)
   161  	}
   162  
   163  	defer l.Destroy()
   164  	defer cleanup()
   165  
   166  	// Start a goroutine to read the start chan result, otherwise Run will
   167  	// block forever.
   168  	var resultChanErr error
   169  	var wg sync.WaitGroup
   170  	wg.Add(1)
   171  	go func() {
   172  		resultChanErr = <-l.ctrl.manager.startResultChan
   173  		wg.Done()
   174  	}()
   175  
   176  	// Run the container.
   177  	if err := l.Run(); err != nil {
   178  		t.Errorf("error running container: %v", err)
   179  	}
   180  
   181  	// We should have not gotten an error on the startResultChan.
   182  	wg.Wait()
   183  	if resultChanErr != nil {
   184  		t.Errorf("error on startResultChan: %v", resultChanErr)
   185  	}
   186  
   187  	// Wait for the application to exit.  It should succeed.
   188  	if status := l.WaitExit(); !status.Exited() || status.ExitStatus() != 0 {
   189  		t.Errorf("application exited with %s, want exit status 0", status)
   190  	}
   191  }
   192  
   193  // TestStartSignal tests that the controller Start message will cause
   194  // WaitForStartSignal to return.
   195  func TestStartSignal(t *testing.T) {
   196  	l, cleanup, err := createLoader(testConfig(), testSpec())
   197  	if err != nil {
   198  		t.Fatalf("error creating loader: %v", err)
   199  	}
   200  	defer l.Destroy()
   201  	defer cleanup()
   202  
   203  	// We aren't going to wait on this application, so the control server
   204  	// needs to be shut down manually.
   205  	defer l.ctrl.srv.Stop(time.Hour)
   206  
   207  	// Start a goroutine that calls WaitForStartSignal and writes to a
   208  	// channel when it returns.
   209  	waitFinished := make(chan struct{})
   210  	go func() {
   211  		l.WaitForStartSignal()
   212  		// Pretend that Run() executed and returned no error.
   213  		l.ctrl.manager.startResultChan <- nil
   214  		waitFinished <- struct{}{}
   215  	}()
   216  
   217  	// Nothing has been written to the channel, so waitFinished should not
   218  	// return.  Give it a little bit of time to make sure the goroutine has
   219  	// started.
   220  	select {
   221  	case <-waitFinished:
   222  		t.Errorf("WaitForStartSignal completed but it should not have")
   223  	case <-time.After(50 * time.Millisecond):
   224  		// OK.
   225  	}
   226  
   227  	// Trigger the control server StartRoot method.
   228  	cid := "foo"
   229  	if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil {
   230  		t.Errorf("error calling StartRoot: %v", err)
   231  	}
   232  
   233  	// Now WaitForStartSignal should return (within a short amount of
   234  	// time).
   235  	select {
   236  	case <-waitFinished:
   237  		// OK.
   238  	case <-time.After(50 * time.Millisecond):
   239  		t.Errorf("WaitForStartSignal did not complete but it should have")
   240  	}
   241  }
   242  
   243  // Test that network=host with raw sockets enabled requires CAP_NET_RAW on the
   244  // host.
   245  func TestHostnetWithRawSockets(t *testing.T) {
   246  	// Drop CAP_NET_RAW from effective capabilities, if we have it.
   247  	pid := os.Getpid()
   248  	caps, err := capability.NewPid2(os.Getpid())
   249  	if err != nil {
   250  		t.Fatalf("error getting capabilities for pid %d: %v", pid, err)
   251  	}
   252  	if err := caps.Load(); err != nil {
   253  		t.Fatalf("error loading capabilities: %v", err)
   254  	}
   255  	if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) {
   256  		caps.Unset(capability.EFFECTIVE, capability.CAP_NET_RAW)
   257  		if err := caps.Apply(capability.EFFECTIVE); err != nil {
   258  			t.Fatalf("error applying capabilities")
   259  		}
   260  		// Be nice and add it back when we are done.
   261  		defer func() {
   262  			caps.Set(capability.EFFECTIVE, capability.CAP_NET_RAW)
   263  			if err := caps.Apply(capability.EFFECTIVE); err != nil {
   264  				t.Fatalf("error restoring capabilities")
   265  			}
   266  		}()
   267  	}
   268  
   269  	// Configure host network with raw sockets.
   270  	conf := testConfig()
   271  	conf.Network = config.NetworkHost
   272  	conf.EnableRaw = true
   273  
   274  	// Creating loader should fail.
   275  	l, err := New(Args{
   276  		ID:   "should-fail",
   277  		Spec: testSpec(),
   278  		Conf: conf,
   279  	})
   280  	if err == nil {
   281  		l.Destroy()
   282  		t.Fatalf("expected loader.New() to fail but it did not")
   283  	}
   284  	// Error message must be about CAP_NET_RAW.
   285  	if !strings.Contains(err.Error(), "CAP_NET_RAW") {
   286  		t.Errorf("expected error to contain CAP_NET_RAW but got %q", err)
   287  	}
   288  }
   289  
   290  type CreateMountTestcase struct {
   291  	name string
   292  	// Spec that will be used to create the mount manager.  Note
   293  	// that we can't mount procfs without a kernel, so each spec
   294  	// MUST contain something other than procfs mounted at /proc.
   295  	spec specs.Spec
   296  	// Paths that are expected to exist in the resulting fs.
   297  	expectedPaths []string
   298  }
   299  
   300  func createMountTestcases() []*CreateMountTestcase {
   301  	testCases := []*CreateMountTestcase{
   302  		{
   303  			// Only proc.
   304  			name: "only proc mount",
   305  			spec: specs.Spec{
   306  				Root: &specs.Root{
   307  					Path:     os.TempDir(),
   308  					Readonly: true,
   309  				},
   310  				Mounts: []specs.Mount{
   311  					{
   312  						Destination: "/proc",
   313  						Type:        "tmpfs",
   314  					},
   315  				},
   316  			},
   317  			// /proc, /dev, and /sys should always be mounted.
   318  			expectedPaths: []string{"/proc", "/dev", "/sys"},
   319  		},
   320  		{
   321  			// Mount at a deep path, with many components that do
   322  			// not exist in the root.
   323  			name: "deep mount path",
   324  			spec: specs.Spec{
   325  				Root: &specs.Root{
   326  					Path:     os.TempDir(),
   327  					Readonly: true,
   328  				},
   329  				Mounts: []specs.Mount{
   330  					{
   331  						Destination: "/some/very/very/deep/path",
   332  						Type:        "tmpfs",
   333  					},
   334  					{
   335  						Destination: "/proc",
   336  						Type:        "tmpfs",
   337  					},
   338  				},
   339  			},
   340  			// /some/deep/path should be mounted, along with /proc, /dev, and /sys.
   341  			expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"},
   342  		},
   343  		{
   344  			// Mounts are nested inside each other.
   345  			name: "nested mounts",
   346  			spec: specs.Spec{
   347  				Root: &specs.Root{
   348  					Path:     os.TempDir(),
   349  					Readonly: true,
   350  				},
   351  				Mounts: []specs.Mount{
   352  					{
   353  						Destination: "/proc",
   354  						Type:        "tmpfs",
   355  					},
   356  					{
   357  						Destination: "/foo",
   358  						Type:        "tmpfs",
   359  					},
   360  					{
   361  						Destination: "/foo/qux",
   362  						Type:        "tmpfs",
   363  					},
   364  					{
   365  						// File mounts with the same prefix.
   366  						Destination: "/foo/qux-quz",
   367  						Type:        "tmpfs",
   368  					},
   369  					{
   370  						Destination: "/foo/bar",
   371  						Type:        "tmpfs",
   372  					},
   373  					{
   374  						Destination: "/foo/bar/baz",
   375  						Type:        "tmpfs",
   376  					},
   377  					{
   378  						// A deep path that is in foo but not the other mounts.
   379  						Destination: "/foo/some/very/very/deep/path",
   380  						Type:        "tmpfs",
   381  					},
   382  				},
   383  			},
   384  			expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux",
   385  				"/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"},
   386  		},
   387  		{
   388  			name: "mount inside /dev",
   389  			spec: specs.Spec{
   390  				Root: &specs.Root{
   391  					Path:     os.TempDir(),
   392  					Readonly: true,
   393  				},
   394  				Mounts: []specs.Mount{
   395  					{
   396  						Destination: "/proc",
   397  						Type:        "tmpfs",
   398  					},
   399  					{
   400  						Destination: "/dev",
   401  						Type:        "tmpfs",
   402  					},
   403  					{
   404  						// Mounted by runsc by default.
   405  						Destination: "/dev/fd",
   406  						Type:        "tmpfs",
   407  					},
   408  					{
   409  						// Mount with the same prefix.
   410  						Destination: "/dev/fd-foo",
   411  						Type:        "tmpfs",
   412  					},
   413  					{
   414  						// Unsupported fs type.
   415  						Destination: "/dev/mqueue",
   416  						Type:        "mqueue",
   417  					},
   418  					{
   419  						Destination: "/dev/foo",
   420  						Type:        "tmpfs",
   421  					},
   422  					{
   423  						Destination: "/dev/bar",
   424  						Type:        "tmpfs",
   425  					},
   426  				},
   427  			},
   428  			expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"},
   429  		},
   430  		{
   431  			name: "mounts inside mandatory mounts",
   432  			spec: specs.Spec{
   433  				Root: &specs.Root{
   434  					Path:     os.TempDir(),
   435  					Readonly: true,
   436  				},
   437  				Mounts: []specs.Mount{
   438  					{
   439  						Destination: "/proc",
   440  						Type:        "tmpfs",
   441  					},
   442  					{
   443  						Destination: "/sys/bar",
   444  						Type:        "tmpfs",
   445  					},
   446  					{
   447  						Destination: "/tmp/baz",
   448  						Type:        "tmpfs",
   449  					},
   450  					{
   451  						Destination: "/dev/goo",
   452  						Type:        "tmpfs",
   453  					},
   454  				},
   455  			},
   456  			expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz", "/dev/goo"},
   457  		},
   458  	}
   459  
   460  	return testCases
   461  }
   462  
   463  // Test that MountNamespace can be created with various specs.
   464  func TestCreateMountNamespace(t *testing.T) {
   465  	for _, tc := range createMountTestcases() {
   466  		t.Run(tc.name, func(t *testing.T) {
   467  			spec := testSpec()
   468  			spec.Mounts = tc.spec.Mounts
   469  			spec.Root = tc.spec.Root
   470  
   471  			t.Logf("Using root: %q", spec.Root.Path)
   472  			l, loaderCleanup, err := createLoader(testConfig(), spec)
   473  			if err != nil {
   474  				t.Fatalf("failed to create loader: %v", err)
   475  			}
   476  			defer l.Destroy()
   477  			defer loaderCleanup()
   478  
   479  			mntr := newContainerMounter(&l.root, l.k, l.mountHints, l.sharedMounts, "", l.sandboxID)
   480  			ctx := l.k.SupervisorContext()
   481  			creds := auth.NewRootCredentials(l.root.procArgs.Credentials.UserNamespace)
   482  			mns, err := mntr.mountAll(ctx, creds, l.root.spec, l.root.conf, &l.root.procArgs)
   483  			if err != nil {
   484  				t.Fatalf("mountAll: %v", err)
   485  			}
   486  
   487  			root := mns.Root(ctx)
   488  			defer root.DecRef(ctx)
   489  			for _, p := range tc.expectedPaths {
   490  				target := &vfs.PathOperation{
   491  					Root:  root,
   492  					Start: root,
   493  					Path:  fspath.Parse(p),
   494  				}
   495  
   496  				if d, err := l.k.VFS().GetDentryAt(ctx, l.root.procArgs.Credentials, target, &vfs.GetDentryOptions{}); err != nil {
   497  					t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err)
   498  				} else {
   499  					d.DecRef(ctx)
   500  				}
   501  			}
   502  		})
   503  	}
   504  }
   505  
   506  func TestMain(m *testing.M) {
   507  	cpuid.Initialize()
   508  	seccheck.Initialize()
   509  	os.Exit(m.Run())
   510  }