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