gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/shared_volume_test.go (about)

     1  // Copyright 2019 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 container
    16  
    17  import (
    18  	"bytes"
    19  	"errors"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"gvisor.dev/gvisor/pkg/sentry/control"
    27  	"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
    28  	"gvisor.dev/gvisor/pkg/test/testutil"
    29  	"gvisor.dev/gvisor/runsc/config"
    30  )
    31  
    32  // TestSharedVolume checks that modifications to a volume mount are propagated
    33  // into and out of the sandbox.
    34  func TestSharedVolume(t *testing.T) {
    35  	conf := testutil.TestConfig(t)
    36  	conf.Overlay2.Set("none")
    37  	conf.FileAccess = config.FileAccessShared
    38  
    39  	// Main process just sleeps. We will use "exec" to probe the state of
    40  	// the filesystem.
    41  	spec := testutil.NewSpecWithArgs("sleep", "1000")
    42  
    43  	dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test")
    44  	if err != nil {
    45  		t.Fatalf("TempDir failed: %v", err)
    46  	}
    47  
    48  	_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
    49  	if err != nil {
    50  		t.Fatalf("error setting up container: %v", err)
    51  	}
    52  	defer cleanup()
    53  
    54  	// Create and start the container.
    55  	args := Args{
    56  		ID:        testutil.RandomContainerID(),
    57  		Spec:      spec,
    58  		BundleDir: bundleDir,
    59  	}
    60  	c, err := New(conf, args)
    61  	if err != nil {
    62  		t.Fatalf("error creating container: %v", err)
    63  	}
    64  	defer c.Destroy()
    65  	if err := c.Start(conf); err != nil {
    66  		t.Fatalf("error starting container: %v", err)
    67  	}
    68  
    69  	// File that will be used to check consistency inside/outside sandbox.
    70  	filename := filepath.Join(dir, "file")
    71  
    72  	// File does not exist yet. Reading from the sandbox should fail.
    73  	argsTestFile := &control.ExecArgs{
    74  		Filename: "/usr/bin/test",
    75  		Argv:     []string{"test", "-f", filename},
    76  	}
    77  	if ws, err := c.executeSync(conf, argsTestFile); err != nil {
    78  		t.Fatalf("unexpected error testing file %q: %v", filename, err)
    79  	} else if ws.ExitStatus() == 0 {
    80  		t.Errorf("test %q exited with code %v, wanted not zero", ws.ExitStatus(), err)
    81  	}
    82  
    83  	// Create the file from outside of the sandbox.
    84  	if err := ioutil.WriteFile(filename, []byte("foobar"), 0777); err != nil {
    85  		t.Fatalf("error writing to file %q: %v", filename, err)
    86  	}
    87  
    88  	// Now we should be able to test the file from within the sandbox.
    89  	if ws, err := c.executeSync(conf, argsTestFile); err != nil {
    90  		t.Fatalf("unexpected error testing file %q: %v", filename, err)
    91  	} else if ws.ExitStatus() != 0 {
    92  		t.Errorf("test %q exited with code %v, wanted zero", filename, ws.ExitStatus())
    93  	}
    94  
    95  	// Rename the file from outside of the sandbox.
    96  	newFilename := filepath.Join(dir, "newfile")
    97  	if err := os.Rename(filename, newFilename); err != nil {
    98  		t.Fatalf("os.Rename(%q, %q) failed: %v", filename, newFilename, err)
    99  	}
   100  
   101  	// File should no longer exist at the old path within the sandbox.
   102  	if ws, err := c.executeSync(conf, argsTestFile); err != nil {
   103  		t.Fatalf("unexpected error testing file %q: %v", filename, err)
   104  	} else if ws.ExitStatus() == 0 {
   105  		t.Errorf("test %q exited with code %v, wanted not zero", filename, ws.ExitStatus())
   106  	}
   107  
   108  	// We should be able to test the new filename from within the sandbox.
   109  	argsTestNewFile := &control.ExecArgs{
   110  		Filename: "/usr/bin/test",
   111  		Argv:     []string{"test", "-f", newFilename},
   112  	}
   113  	if ws, err := c.executeSync(conf, argsTestNewFile); err != nil {
   114  		t.Fatalf("unexpected error testing file %q: %v", newFilename, err)
   115  	} else if ws.ExitStatus() != 0 {
   116  		t.Errorf("test %q exited with code %v, wanted zero", newFilename, ws.ExitStatus())
   117  	}
   118  
   119  	// Delete the renamed file from outside of the sandbox.
   120  	if err := os.Remove(newFilename); err != nil {
   121  		t.Fatalf("error removing file %q: %v", filename, err)
   122  	}
   123  
   124  	// Renamed file should no longer exist at the old path within the sandbox.
   125  	if ws, err := c.executeSync(conf, argsTestNewFile); err != nil {
   126  		t.Fatalf("unexpected error testing file %q: %v", newFilename, err)
   127  	} else if ws.ExitStatus() == 0 {
   128  		t.Errorf("test %q exited with code %v, wanted not zero", newFilename, ws.ExitStatus())
   129  	}
   130  
   131  	// Now create the file from WITHIN the sandbox.
   132  	argsTouch := &control.ExecArgs{
   133  		Filename: "/usr/bin/touch",
   134  		Argv:     []string{"touch", filename},
   135  		KUID:     auth.KUID(os.Getuid()),
   136  		KGID:     auth.KGID(os.Getgid()),
   137  	}
   138  	if ws, err := c.executeSync(conf, argsTouch); err != nil {
   139  		t.Fatalf("unexpected error touching file %q: %v", filename, err)
   140  	} else if ws.ExitStatus() != 0 {
   141  		t.Errorf("touch %q exited with code %v, wanted zero", filename, ws.ExitStatus())
   142  	}
   143  
   144  	// File should exist outside the sandbox.
   145  	if _, err := os.Stat(filename); err != nil {
   146  		t.Errorf("stat %q got error %v, wanted nil", filename, err)
   147  	}
   148  
   149  	// Delete the file from within the sandbox.
   150  	argsRemove := &control.ExecArgs{
   151  		Filename: "/bin/rm",
   152  		Argv:     []string{"rm", filename},
   153  	}
   154  	if ws, err := c.executeSync(conf, argsRemove); err != nil {
   155  		t.Fatalf("unexpected error removing file %q: %v", filename, err)
   156  	} else if ws.ExitStatus() != 0 {
   157  		t.Errorf("remove %q exited with code %v, wanted zero", filename, ws.ExitStatus())
   158  	}
   159  
   160  	// File should not exist outside the sandbox.
   161  	if _, err := os.Stat(filename); !os.IsNotExist(err) {
   162  		t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err)
   163  	}
   164  }
   165  
   166  func checkFile(conf *config.Config, c *Container, filename string, want []byte) error {
   167  	cpy := filename + ".copy"
   168  	if _, err := execute(conf, c, "/bin/cp", "-f", filename, cpy); err != nil {
   169  		return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err)
   170  	}
   171  	got, err := ioutil.ReadFile(cpy)
   172  	if err != nil {
   173  		return fmt.Errorf("error reading file %q: %v", filename, err)
   174  	}
   175  	if !bytes.Equal(got, want) {
   176  		return fmt.Errorf("file content inside the sandbox is wrong, got: %q, want: %q", got, want)
   177  	}
   178  	return nil
   179  }
   180  
   181  // TestSharedVolumeFile tests that changes to file content outside the sandbox
   182  // is reflected inside.
   183  func TestSharedVolumeFile(t *testing.T) {
   184  	conf := testutil.TestConfig(t)
   185  	conf.Overlay2.Set("none")
   186  	conf.FileAccess = config.FileAccessShared
   187  
   188  	// Main process just sleeps. We will use "exec" to probe the state of
   189  	// the filesystem.
   190  	spec := testutil.NewSpecWithArgs("sleep", "1000")
   191  
   192  	dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test")
   193  	if err != nil {
   194  		t.Fatalf("TempDir failed: %v", err)
   195  	}
   196  
   197  	_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
   198  	if err != nil {
   199  		t.Fatalf("error setting up container: %v", err)
   200  	}
   201  	defer cleanup()
   202  
   203  	// Create and start the container.
   204  	args := Args{
   205  		ID:        testutil.RandomContainerID(),
   206  		Spec:      spec,
   207  		BundleDir: bundleDir,
   208  	}
   209  	c, err := New(conf, args)
   210  	if err != nil {
   211  		t.Fatalf("error creating container: %v", err)
   212  	}
   213  	defer c.Destroy()
   214  	if err := c.Start(conf); err != nil {
   215  		t.Fatalf("error starting container: %v", err)
   216  	}
   217  
   218  	// File that will be used to check consistency inside/outside sandbox.
   219  	filename := filepath.Join(dir, "file")
   220  
   221  	// Write file from outside the container and check that the same content is
   222  	// read inside.
   223  	want := []byte("host-")
   224  	if err := ioutil.WriteFile(filename, []byte(want), 0666); err != nil {
   225  		t.Fatalf("Error writing to %q: %v", filename, err)
   226  	}
   227  	if err := checkFile(conf, c, filename, want); err != nil {
   228  		t.Fatal(err.Error())
   229  	}
   230  
   231  	// Append to file inside the container and check that content is not lost.
   232  	if _, err := execute(conf, c, "/bin/bash", "-c", "echo -n sandbox- >> "+filename); err != nil {
   233  		t.Fatalf("unexpected error appending file %q: %v", filename, err)
   234  	}
   235  	want = []byte("host-sandbox-")
   236  	if err := checkFile(conf, c, filename, want); err != nil {
   237  		t.Fatal(err.Error())
   238  	}
   239  
   240  	// Write again from outside the container and check that the same content is
   241  	// read inside.
   242  	f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0)
   243  	if err != nil {
   244  		t.Fatalf("Error openning file %q: %v", filename, err)
   245  	}
   246  	defer f.Close()
   247  	if _, err := f.Write([]byte("host")); err != nil {
   248  		t.Fatalf("Error writing to file %q: %v", filename, err)
   249  	}
   250  	want = []byte("host-sandbox-host")
   251  	if err := checkFile(conf, c, filename, want); err != nil {
   252  		t.Fatal(err.Error())
   253  	}
   254  
   255  	// Shrink file outside and check that the same content is read inside.
   256  	if err := f.Truncate(5); err != nil {
   257  		t.Fatalf("Error truncating file %q: %v", filename, err)
   258  	}
   259  	want = want[:5]
   260  	if err := checkFile(conf, c, filename, want); err != nil {
   261  		t.Fatal(err.Error())
   262  	}
   263  }
   264  
   265  // TestSharedVolumeOverlay tests that changes to a shared volume that is
   266  // wrapped in an overlay are not visible externally.
   267  func TestSharedVolumeOverlay(t *testing.T) {
   268  	conf := testutil.TestConfig(t)
   269  	conf.Overlay2.Set("all:dir=/tmp")
   270  
   271  	// File that will be used to check consistency inside/outside sandbox.
   272  	// Note that TmpDir() is set up as a shared volume by NewSpecWithArgs(). So
   273  	// changes inside TmpDir() should not be visible to the host.
   274  	filename := filepath.Join(testutil.TmpDir(), "file")
   275  
   276  	// Create a file in TmpDir() inside the container.
   277  	spec := testutil.NewSpecWithArgs("/bin/bash", "-c", "echo Hello > "+filename+"; test -f "+filename)
   278  	_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
   279  	if err != nil {
   280  		t.Fatalf("error setting up container: %v", err)
   281  	}
   282  	defer cleanup()
   283  
   284  	// Create and start the container.
   285  	args := Args{
   286  		ID:        testutil.RandomContainerID(),
   287  		Spec:      spec,
   288  		BundleDir: bundleDir,
   289  	}
   290  	c, err := New(conf, args)
   291  	if err != nil {
   292  		t.Fatalf("error creating container: %v", err)
   293  	}
   294  	defer c.Destroy()
   295  	if err := c.Start(conf); err != nil {
   296  		t.Fatalf("error starting container: %v", err)
   297  	}
   298  
   299  	if ws, err := c.Wait(); err != nil {
   300  		t.Errorf("failed to wait for container: %v", err)
   301  	} else if es := ws.ExitStatus(); es != 0 {
   302  		t.Errorf("subcontainer exited with non-zero status %d", es)
   303  	}
   304  
   305  	// Ensure that the file does not exist on the host.
   306  	if _, err := os.Stat(filename); !errors.Is(err, os.ErrNotExist) {
   307  		t.Errorf("file exists on host, stat %q got error %v, wanted ErrNotExist", filename, err)
   308  	}
   309  }