Files
visor/runsc/container/shared_volume_test.go
Michael PrattandgVisor bot 2e15a085fe nogo: plumb SDK version as language version
Rather than hard-coding the language version, plumb the version from the
rules_go GoSDK provider.

The end result is a no-op: we analyze everything with the GoVersion
matching the language version from go.mod. This is slightly different
from go vet / go build. For dependencies, those would use the language
version from their go.mod, which may be older.

Surprisingly, this seems to be how rules_go works for all builds: it
never passes -lang to the compiler, effectively building everything at
the language version of the toolchain.

The printf analyzer has a stricter check on uses of %q gated behind
language version 1.26:
https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/printf/printf.go;l=892;drc=a54c8a428f1496d97a2c4e795b4300e8985649e1.
Fix the new findings in gVisor and suppress them in dependencies.

PiperOrigin-RevId: 885633196
2026-03-18 09:21:28 -07:00

309 lines
10 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// 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"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/config"
)
// TestSharedVolume checks that modifications to a volume mount are propagated
// into and out of the sandbox.
func TestSharedVolume(t *testing.T) {
conf := testutil.TestConfig(t)
conf.Overlay2.Set("none")
conf.FileAccess = config.FileAccessShared
// Main process just sleeps. We will use "exec" to probe the state of
// the filesystem.
spec := testutil.NewSpecWithArgs("sleep", "1000")
dir, err := os.MkdirTemp(testutil.TmpDir(), "shared-volume-test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
c, err := New(conf, args)
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(conf, 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())
}
// Create the file from outside of the sandbox.
if err := os.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(conf, 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(conf, 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(conf, 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(conf, 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(conf, 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)
}
// Delete the file from within the sandbox.
argsRemove := &control.ExecArgs{
Filename: "/bin/rm",
Argv: []string{"rm", filename},
}
if ws, err := c.executeSync(conf, 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(conf *config.Config, c *Container, filename string, want []byte) error {
cpy := filename + ".copy"
if _, err := execute(conf, c, "/bin/cp", "-f", filename, cpy); err != nil {
return fmt.Errorf("unexpected error copying file %q to %q: %v", filename, cpy, err)
}
got, err := os.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(t)
conf.Overlay2.Set("none")
conf.FileAccess = config.FileAccessShared
// Main process just sleeps. We will use "exec" to probe the state of
// the filesystem.
spec := testutil.NewSpecWithArgs("sleep", "1000")
dir, err := os.MkdirTemp(testutil.TmpDir(), "shared-volume-test")
if err != nil {
t.Fatalf("TempDir failed: %v", err)
}
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
c, err := New(conf, args)
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 := os.WriteFile(filename, []byte(want), 0666); err != nil {
t.Fatalf("Error writing to %q: %v", filename, err)
}
if err := checkFile(conf, c, filename, want); err != nil {
t.Fatal(err.Error())
}
// Append to file inside the container and check that content is not lost.
if _, err := execute(conf, c, "/bin/bash", "-c", "echo -n sandbox- >> "+filename); err != nil {
t.Fatalf("unexpected error appending file %q: %v", filename, err)
}
want = []byte("host-sandbox-")
if err := checkFile(conf, 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 opening 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(conf, 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(conf, c, filename, want); err != nil {
t.Fatal(err.Error())
}
}
// TestSharedVolumeOverlay tests that changes to a shared volume that is
// wrapped in an overlay are not visible externally.
func TestSharedVolumeOverlay(t *testing.T) {
conf := testutil.TestConfig(t)
conf.Overlay2.Set("all:dir=/tmp")
// File that will be used to check consistency inside/outside sandbox.
// Note that TmpDir() is set up as a shared volume by NewSpecWithArgs(). So
// changes inside TmpDir() should not be visible to the host.
filename := filepath.Join(testutil.TmpDir(), "file")
// Create a file in TmpDir() inside the container.
spec := testutil.NewSpecWithArgs("/bin/bash", "-c", "echo Hello > "+filename+"; test -f "+filename)
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
c, err := New(conf, args)
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)
}
if ws, err := c.Wait(); err != nil {
t.Errorf("failed to wait for container: %v", err)
} else if es := ws.ExitStatus(); es != 0 {
t.Errorf("subcontainer exited with non-zero status %d", es)
}
// Ensure that the file does not exist on the host.
if _, err := os.Stat(filename); !errors.Is(err, os.ErrNotExist) {
t.Errorf("file exists on host, stat %q got error %v, wanted ErrNotExist", filename, err)
}
}