diff options
Diffstat (limited to 'runsc/container/shared_volume_test.go')
-rw-r--r-- | runsc/container/shared_volume_test.go | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/runsc/container/shared_volume_test.go b/runsc/container/shared_volume_test.go new file mode 100644 index 000000000..8f81ed630 --- /dev/null +++ b/runsc/container/shared_volume_test.go @@ -0,0 +1,267 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package container + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "gvisor.googlesource.com/gvisor/pkg/sentry/control" + "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth" + "gvisor.googlesource.com/gvisor/runsc/boot" + "gvisor.googlesource.com/gvisor/runsc/test/testutil" +) + +// TestSharedVolume checks that modifications to a volume mount are propagated +// into and out of the sandbox. +func TestSharedVolume(t *testing.T) { + conf := testutil.TestConfig() + conf.FileAccess = boot.FileAccessShared + t.Logf("Running test with conf: %+v", conf) + + // Main process just sleeps. We will use "exec" to probe the state of + // the filesystem. + spec := testutil.NewSpecWithArgs("sleep", "1000") + + dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") + if err != nil { + t.Fatalf("TempDir failed: %v", err) + } + + rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) + if err != nil { + t.Fatalf("error setting up container: %v", err) + } + defer os.RemoveAll(rootDir) + defer os.RemoveAll(bundleDir) + + // Create and start the container. + c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "") + if err != nil { + t.Fatalf("error creating container: %v", err) + } + defer c.Destroy() + if err := c.Start(conf); err != nil { + t.Fatalf("error starting container: %v", err) + } + + // File that will be used to check consistency inside/outside sandbox. + filename := filepath.Join(dir, "file") + + // File does not exist yet. Reading from the sandbox should fail. + argsTestFile := &control.ExecArgs{ + Filename: "/usr/bin/test", + Argv: []string{"test", "-f", filename}, + } + if ws, err := c.executeSync(argsTestFile); err != nil { + t.Fatalf("unexpected error testing file %q: %v", filename, err) + } else if ws.ExitStatus() == 0 { + t.Errorf("test %q exited with code %v, wanted not zero", ws.ExitStatus(), err) + } + + // Create the file from outside of the sandbox. + if err := ioutil.WriteFile(filename, []byte("foobar"), 0777); err != nil { + t.Fatalf("error writing to file %q: %v", filename, err) + } + + // Now we should be able to test the file from within the sandbox. + if ws, err := c.executeSync(argsTestFile); err != nil { + t.Fatalf("unexpected error testing file %q: %v", filename, err) + } else if ws.ExitStatus() != 0 { + t.Errorf("test %q exited with code %v, wanted zero", filename, ws.ExitStatus()) + } + + // Rename the file from outside of the sandbox. + newFilename := filepath.Join(dir, "newfile") + if err := os.Rename(filename, newFilename); err != nil { + t.Fatalf("os.Rename(%q, %q) failed: %v", filename, newFilename, err) + } + + // File should no longer exist at the old path within the sandbox. + if ws, err := c.executeSync(argsTestFile); err != nil { + t.Fatalf("unexpected error testing file %q: %v", filename, err) + } else if ws.ExitStatus() == 0 { + t.Errorf("test %q exited with code %v, wanted not zero", filename, ws.ExitStatus()) + } + + // We should be able to test the new filename from within the sandbox. + argsTestNewFile := &control.ExecArgs{ + Filename: "/usr/bin/test", + Argv: []string{"test", "-f", newFilename}, + } + if ws, err := c.executeSync(argsTestNewFile); err != nil { + t.Fatalf("unexpected error testing file %q: %v", newFilename, err) + } else if ws.ExitStatus() != 0 { + t.Errorf("test %q exited with code %v, wanted zero", newFilename, ws.ExitStatus()) + } + + // Delete the renamed file from outside of the sandbox. + if err := os.Remove(newFilename); err != nil { + t.Fatalf("error removing file %q: %v", filename, err) + } + + // Renamed file should no longer exist at the old path within the sandbox. + if ws, err := c.executeSync(argsTestNewFile); err != nil { + t.Fatalf("unexpected error testing file %q: %v", newFilename, err) + } else if ws.ExitStatus() == 0 { + t.Errorf("test %q exited with code %v, wanted not zero", newFilename, ws.ExitStatus()) + } + + // Now create the file from WITHIN the sandbox. + argsTouch := &control.ExecArgs{ + Filename: "/usr/bin/touch", + Argv: []string{"touch", filename}, + KUID: auth.KUID(os.Getuid()), + KGID: auth.KGID(os.Getgid()), + } + if ws, err := c.executeSync(argsTouch); err != nil { + t.Fatalf("unexpected error touching file %q: %v", filename, err) + } else if ws.ExitStatus() != 0 { + t.Errorf("touch %q exited with code %v, wanted zero", filename, ws.ExitStatus()) + } + + // File should exist outside the sandbox. + if _, err := os.Stat(filename); err != nil { + t.Errorf("stat %q got error %v, wanted nil", filename, err) + } + + // File should exist outside the sandbox. + if _, err := os.Stat(filename); err != nil { + t.Errorf("stat %q got error %v, wanted nil", filename, err) + } + + // Delete the file from within the sandbox. + argsRemove := &control.ExecArgs{ + Filename: "/bin/rm", + Argv: []string{"rm", filename}, + } + if ws, err := c.executeSync(argsRemove); err != nil { + t.Fatalf("unexpected error removing file %q: %v", filename, err) + } else if ws.ExitStatus() != 0 { + t.Errorf("remove %q exited with code %v, wanted zero", filename, ws.ExitStatus()) + } + + // File should not exist outside the sandbox. + if _, err := os.Stat(filename); !os.IsNotExist(err) { + t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err) + } +} + +func checkFile(c *Container, filename string, want []byte) error { + cpy := filename + ".copy" + argsCp := &control.ExecArgs{ + Filename: "/bin/cp", + Argv: []string{"cp", "-f", filename, cpy}, + } + if _, err := c.executeSync(argsCp); err != nil { + return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err) + } + got, err := ioutil.ReadFile(cpy) + if err != nil { + return fmt.Errorf("Error reading file %q: %v", filename, err) + } + if !bytes.Equal(got, want) { + return fmt.Errorf("file content inside the sandbox is wrong, got: %q, want: %q", got, want) + } + return nil +} + +// TestSharedVolumeFile tests that changes to file content outside the sandbox +// is reflected inside. +func TestSharedVolumeFile(t *testing.T) { + conf := testutil.TestConfig() + conf.FileAccess = boot.FileAccessShared + t.Logf("Running test with conf: %+v", conf) + + // Main process just sleeps. We will use "exec" to probe the state of + // the filesystem. + spec := testutil.NewSpecWithArgs("sleep", "1000") + + dir, err := ioutil.TempDir(testutil.TmpDir(), "shared-volume-test") + if err != nil { + t.Fatalf("TempDir failed: %v", err) + } + + rootDir, bundleDir, err := testutil.SetupContainer(spec, conf) + if err != nil { + t.Fatalf("error setting up container: %v", err) + } + defer os.RemoveAll(rootDir) + defer os.RemoveAll(bundleDir) + + // Create and start the container. + c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "") + if err != nil { + t.Fatalf("error creating container: %v", err) + } + defer c.Destroy() + if err := c.Start(conf); err != nil { + t.Fatalf("error starting container: %v", err) + } + + // File that will be used to check consistency inside/outside sandbox. + filename := filepath.Join(dir, "file") + + // Write file from outside the container and check that the same content is + // read inside. + want := []byte("host-") + if err := ioutil.WriteFile(filename, []byte(want), 0666); err != nil { + t.Fatalf("Error writing to %q: %v", filename, err) + } + if err := checkFile(c, filename, want); err != nil { + t.Fatal(err.Error()) + } + + // Append to file inside the container and check that content is not lost. + argsAppend := &control.ExecArgs{ + Filename: "/bin/bash", + Argv: []string{"bash", "-c", "echo -n sandbox- >> " + filename}, + } + if _, err := c.executeSync(argsAppend); err != nil { + t.Fatalf("unexpected error appending file %q: %v", filename, err) + } + want = []byte("host-sandbox-") + if err := checkFile(c, filename, want); err != nil { + t.Fatal(err.Error()) + } + + // Write again from outside the container and check that the same content is + // read inside. + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0) + if err != nil { + t.Fatalf("Error openning file %q: %v", filename, err) + } + defer f.Close() + if _, err := f.Write([]byte("host")); err != nil { + t.Fatalf("Error writing to file %q: %v", filename, err) + } + want = []byte("host-sandbox-host") + if err := checkFile(c, filename, want); err != nil { + t.Fatal(err.Error()) + } + + // Shrink file outside and check that the same content is read inside. + if err := f.Truncate(5); err != nil { + t.Fatalf("Error truncating file %q: %v", filename, err) + } + want = want[:5] + if err := checkFile(c, filename, want); err != nil { + t.Fatal(err.Error()) + } +} |