diff options
Diffstat (limited to 'runsc/boot')
-rw-r--r-- | runsc/boot/BUILD | 116 | ||||
-rw-r--r-- | runsc/boot/compat_test.go | 85 | ||||
-rw-r--r-- | runsc/boot/filter/BUILD | 28 | ||||
-rw-r--r-- | runsc/boot/fs_test.go | 193 | ||||
-rw-r--r-- | runsc/boot/loader_test.go | 631 | ||||
-rw-r--r-- | runsc/boot/user_test.go | 253 |
6 files changed, 0 insertions, 1306 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD deleted file mode 100644 index 7ec15a524..000000000 --- a/runsc/boot/BUILD +++ /dev/null @@ -1,116 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "boot", - srcs = [ - "compat.go", - "compat_amd64.go", - "config.go", - "controller.go", - "debug.go", - "events.go", - "fds.go", - "fs.go", - "limits.go", - "loader.go", - "network.go", - "pprof.go", - "strace.go", - "user.go", - ], - importpath = "gvisor.dev/gvisor/runsc/boot", - visibility = [ - "//runsc:__subpackages__", - "//test:__subpackages__", - ], - deps = [ - "//pkg/abi", - "//pkg/abi/linux", - "//pkg/control/server", - "//pkg/cpuid", - "//pkg/eventchannel", - "//pkg/log", - "//pkg/memutil", - "//pkg/rand", - "//pkg/sentry/arch", - "//pkg/sentry/arch:registers_go_proto", - "//pkg/sentry/context", - "//pkg/sentry/control", - "//pkg/sentry/fs", - "//pkg/sentry/fs/dev", - "//pkg/sentry/fs/gofer", - "//pkg/sentry/fs/host", - "//pkg/sentry/fs/proc", - "//pkg/sentry/fs/ramfs", - "//pkg/sentry/fs/sys", - "//pkg/sentry/fs/tmpfs", - "//pkg/sentry/fs/tty", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel:uncaught_signal_go_proto", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/kernel/kdefs", - "//pkg/sentry/limits", - "//pkg/sentry/loader", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/platform/kvm", - "//pkg/sentry/platform/ptrace", - "//pkg/sentry/sighandling", - "//pkg/sentry/socket/epsocket", - "//pkg/sentry/socket/hostinet", - "//pkg/sentry/socket/netlink", - "//pkg/sentry/socket/netlink/route", - "//pkg/sentry/socket/unix", - "//pkg/sentry/state", - "//pkg/sentry/strace", - "//pkg/sentry/syscalls/linux", - "//pkg/sentry/time", - "//pkg/sentry/unimpl:unimplemented_syscall_go_proto", - "//pkg/sentry/usage", - "//pkg/sentry/usermem", - "//pkg/sentry/watchdog", - "//pkg/syserror", - "//pkg/tcpip", - "//pkg/tcpip/link/fdbased", - "//pkg/tcpip/link/loopback", - "//pkg/tcpip/link/sniffer", - "//pkg/tcpip/network/arp", - "//pkg/tcpip/network/ipv4", - "//pkg/tcpip/network/ipv6", - "//pkg/tcpip/stack", - "//pkg/tcpip/transport/icmp", - "//pkg/tcpip/transport/tcp", - "//pkg/tcpip/transport/udp", - "//pkg/urpc", - "//runsc/boot/filter", - "//runsc/specutils", - "@com_github_golang_protobuf//proto:go_default_library", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) - -go_test( - name = "boot_test", - size = "small", - srcs = [ - "compat_test.go", - "fs_test.go", - "loader_test.go", - "user_test.go", - ], - embed = [":boot"], - deps = [ - "//pkg/control/server", - "//pkg/log", - "//pkg/p9", - "//pkg/sentry/arch:registers_go_proto", - "//pkg/sentry/context/contexttest", - "//pkg/sentry/fs", - "//pkg/unet", - "//runsc/fsgofer", - "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - ], -) diff --git a/runsc/boot/compat_test.go b/runsc/boot/compat_test.go deleted file mode 100644 index 388298d8d..000000000 --- a/runsc/boot/compat_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2018 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 boot - -import ( - "testing" - - rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto" -) - -func TestOnceTracker(t *testing.T) { - o := onceTracker{} - if !o.shouldReport(nil) { - t.Error("first call to checkAndMark, got: false, want: true") - } - o.onReported(nil) - for i := 0; i < 2; i++ { - if o.shouldReport(nil) { - t.Error("after first call to checkAndMark, got: true, want: false") - } - } -} - -func TestArgsTracker(t *testing.T) { - for _, tc := range []struct { - name string - idx []int - rdi1 uint64 - rdi2 uint64 - rsi1 uint64 - rsi2 uint64 - want bool - }{ - {name: "same rdi", idx: []int{0}, rdi1: 123, rdi2: 123, want: false}, - {name: "same rsi", idx: []int{1}, rsi1: 123, rsi2: 123, want: false}, - {name: "diff rdi", idx: []int{0}, rdi1: 123, rdi2: 321, want: true}, - {name: "diff rsi", idx: []int{1}, rsi1: 123, rsi2: 321, want: true}, - {name: "cmd is uint32", idx: []int{0}, rsi1: 0xdead00000123, rsi2: 0xbeef00000123, want: false}, - {name: "same 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 123, rdi2: 321, want: false}, - {name: "diff 2 args", idx: []int{0, 1}, rsi1: 123, rdi1: 321, rsi2: 789, rdi2: 987, want: true}, - } { - t.Run(tc.name, func(t *testing.T) { - c := newArgsTracker(tc.idx...) - regs := &rpb.AMD64Registers{Rdi: tc.rdi1, Rsi: tc.rsi1} - if !c.shouldReport(regs) { - t.Error("first call to shouldReport, got: false, want: true") - } - c.onReported(regs) - - regs.Rdi, regs.Rsi = tc.rdi2, tc.rsi2 - if got := c.shouldReport(regs); tc.want != got { - t.Errorf("second call to shouldReport, got: %t, want: %t", got, tc.want) - } - }) - } -} - -func TestArgsTrackerLimit(t *testing.T) { - c := newArgsTracker(0, 1) - for i := 0; i < reportLimit; i++ { - regs := &rpb.AMD64Registers{Rdi: 123, Rsi: uint64(i)} - if !c.shouldReport(regs) { - t.Error("shouldReport before limit was reached, got: false, want: true") - } - c.onReported(regs) - } - - // Should hit the count limit now. - regs := &rpb.AMD64Registers{Rdi: 123, Rsi: 123456} - if c.shouldReport(regs) { - t.Error("shouldReport after limit was reached, got: true, want: false") - } -} diff --git a/runsc/boot/filter/BUILD b/runsc/boot/filter/BUILD deleted file mode 100644 index 07898f3de..000000000 --- a/runsc/boot/filter/BUILD +++ /dev/null @@ -1,28 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filter", - srcs = [ - "config.go", - "extra_filters.go", - "extra_filters_msan.go", - "extra_filters_race.go", - "filter.go", - ], - importpath = "gvisor.dev/gvisor/runsc/boot/filter", - visibility = [ - "//runsc/boot:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/seccomp", - "//pkg/sentry/platform", - "//pkg/sentry/platform/kvm", - "//pkg/sentry/platform/ptrace", - "//pkg/tcpip/link/fdbased", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/boot/fs_test.go b/runsc/boot/fs_test.go deleted file mode 100644 index 49ab34b33..000000000 --- a/runsc/boot/fs_test.go +++ /dev/null @@ -1,193 +0,0 @@ -// 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 boot - -import ( - "path" - "reflect" - "strings" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -func TestPodMountHintsHappy(t *testing.T) { - spec := &specs.Spec{ - Annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - - path.Join(MountPrefix, "mount2", "source"): "bar", - path.Join(MountPrefix, "mount2", "type"): "bind", - path.Join(MountPrefix, "mount2", "share"): "container", - path.Join(MountPrefix, "mount2", "options"): "rw,private", - }, - } - podHints, err := newPodMountHints(spec) - if err != nil { - t.Errorf("newPodMountHints failed: %v", err) - } - - // Check that fields were set correctly. - mount1 := podHints.mounts["mount1"] - if want := "mount1"; want != mount1.name { - t.Errorf("mount1 name, want: %q, got: %q", want, mount1.name) - } - if want := "foo"; want != mount1.mount.Source { - t.Errorf("mount1 source, want: %q, got: %q", want, mount1.mount.Source) - } - if want := "tmpfs"; want != mount1.mount.Type { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Type) - } - if want := pod; want != mount1.share { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.share) - } - if want := []string(nil); !reflect.DeepEqual(want, mount1.mount.Options) { - t.Errorf("mount1 type, want: %q, got: %q", want, mount1.mount.Options) - } - - mount2 := podHints.mounts["mount2"] - if want := "mount2"; want != mount2.name { - t.Errorf("mount2 name, want: %q, got: %q", want, mount2.name) - } - if want := "bar"; want != mount2.mount.Source { - t.Errorf("mount2 source, want: %q, got: %q", want, mount2.mount.Source) - } - if want := "bind"; want != mount2.mount.Type { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Type) - } - if want := container; want != mount2.share { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.share) - } - if want := []string{"private", "rw"}; !reflect.DeepEqual(want, mount2.mount.Options) { - t.Errorf("mount2 type, want: %q, got: %q", want, mount2.mount.Options) - } -} - -func TestPodMountHintsErrors(t *testing.T) { - for _, tst := range []struct { - name string - annotations map[string]string - error string - }{ - { - name: "too short", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1"): "foo", - }, - error: "invalid mount annotation", - }, - { - name: "no name", - annotations: map[string]string{ - MountPrefix + "//source": "foo", - }, - error: "invalid mount name", - }, - { - name: "missing source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "source field", - }, - { - name: "missing type", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "type field", - }, - { - name: "missing share", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - }, - error: "share field", - }, - { - name: "invalid field name", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "invalid"): "foo", - }, - error: "invalid mount annotation", - }, - { - name: "invalid source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "source cannot be empty", - }, - { - name: "invalid type", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "invalid-type", - path.Join(MountPrefix, "mount1", "share"): "pod", - }, - error: "invalid type", - }, - { - name: "invalid share", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "invalid-share", - }, - error: "invalid share", - }, - { - name: "invalid options", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - path.Join(MountPrefix, "mount1", "options"): "invalid-option", - }, - error: "unknown mount option", - }, - { - name: "duplicate source", - annotations: map[string]string{ - path.Join(MountPrefix, "mount1", "source"): "foo", - path.Join(MountPrefix, "mount1", "type"): "tmpfs", - path.Join(MountPrefix, "mount1", "share"): "pod", - - path.Join(MountPrefix, "mount2", "source"): "foo", - path.Join(MountPrefix, "mount2", "type"): "bind", - path.Join(MountPrefix, "mount2", "share"): "container", - }, - error: "have the same mount source", - }, - } { - t.Run(tst.name, func(t *testing.T) { - spec := &specs.Spec{Annotations: tst.annotations} - podHints, err := newPodMountHints(spec) - if err == nil || !strings.Contains(err.Error(), tst.error) { - t.Errorf("newPodMountHints invalid error, want: .*%s.*, got: %v", tst.error, err) - } - if podHints != nil { - t.Errorf("newPodMountHints must return nil on failure: %+v", podHints) - } - }) - } -} diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go deleted file mode 100644 index eca592e5b..000000000 --- a/runsc/boot/loader_test.go +++ /dev/null @@ -1,631 +0,0 @@ -// Copyright 2018 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 boot - -import ( - "fmt" - "math/rand" - "os" - "reflect" - "sync" - "syscall" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/control/server" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/context/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/runsc/fsgofer" -) - -func init() { - log.SetLevel(log.Debug) - rand.Seed(time.Now().UnixNano()) - if err := fsgofer.OpenProcSelfFD(); err != nil { - panic(err) - } -} - -func testConfig() *Config { - return &Config{ - RootDir: "unused_root_dir", - Network: NetworkNone, - DisableSeccomp: true, - } -} - -// testSpec returns a simple spec that can be used in tests. -func testSpec() *specs.Spec { - return &specs.Spec{ - // The host filesystem root is the sandbox root. - Root: &specs.Root{ - Path: "/", - Readonly: true, - }, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - } -} - -// startGofer starts a new gofer routine serving 'root' path. It returns the -// sandbox side of the connection, and a function that when called will stop the -// gofer. -func startGofer(root string) (int, func(), error) { - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) - if err != nil { - return 0, nil, err - } - sandboxEnd, goferEnd := fds[0], fds[1] - - socket, err := unet.NewSocket(goferEnd) - if err != nil { - syscall.Close(sandboxEnd) - syscall.Close(goferEnd) - return 0, nil, fmt.Errorf("error creating server on FD %d: %v", goferEnd, err) - } - at, err := fsgofer.NewAttachPoint(root, fsgofer.Config{ROMount: true}) - if err != nil { - return 0, nil, err - } - go func() { - s := p9.NewServer(at) - if err := s.Handle(socket); err != nil { - log.Infof("Gofer is stopping. FD: %d, err: %v\n", goferEnd, err) - } - }() - // Closing the gofer socket will stop the gofer and exit goroutine above. - cleanup := func() { - if err := socket.Close(); err != nil { - log.Warningf("Error closing gofer socket: %v", err) - } - } - return sandboxEnd, cleanup, nil -} - -func createLoader() (*Loader, func(), error) { - fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10])) - if err != nil { - return nil, nil, err - } - conf := testConfig() - spec := testSpec() - - sandEnd, cleanup, err := startGofer(spec.Root.Path) - if err != nil { - return nil, nil, err - } - - stdio := []int{int(os.Stdin.Fd()), int(os.Stdout.Fd()), int(os.Stderr.Fd())} - args := Args{ - ID: "foo", - Spec: spec, - Conf: conf, - ControllerFD: fd, - GoferFDs: []int{sandEnd}, - StdioFDs: stdio, - } - l, err := New(args) - if err != nil { - cleanup() - return nil, nil, err - } - return l, cleanup, nil -} - -// TestRun runs a simple application in a sandbox and checks that it succeeds. -func TestRun(t *testing.T) { - l, cleanup, err := createLoader() - if err != nil { - t.Fatalf("error creating loader: %v", err) - } - defer l.Destroy() - defer cleanup() - - // Start a goroutine to read the start chan result, otherwise Run will - // block forever. - var resultChanErr error - var wg sync.WaitGroup - wg.Add(1) - go func() { - resultChanErr = <-l.ctrl.manager.startResultChan - wg.Done() - }() - - // Run the container. - if err := l.Run(); err != nil { - t.Errorf("error running container: %v", err) - } - - // We should have not gotten an error on the startResultChan. - wg.Wait() - if resultChanErr != nil { - t.Errorf("error on startResultChan: %v", resultChanErr) - } - - // Wait for the application to exit. It should succeed. - if status := l.WaitExit(); status.Code != 0 || status.Signo != 0 { - t.Errorf("application exited with status %+v, want 0", status) - } -} - -// TestStartSignal tests that the controller Start message will cause -// WaitForStartSignal to return. -func TestStartSignal(t *testing.T) { - l, cleanup, err := createLoader() - if err != nil { - t.Fatalf("error creating loader: %v", err) - } - defer l.Destroy() - defer cleanup() - - // We aren't going to wait on this application, so the control server - // needs to be shut down manually. - defer l.ctrl.srv.Stop() - - // Start a goroutine that calls WaitForStartSignal and writes to a - // channel when it returns. - waitFinished := make(chan struct{}) - go func() { - l.WaitForStartSignal() - // Pretend that Run() executed and returned no error. - l.ctrl.manager.startResultChan <- nil - waitFinished <- struct{}{} - }() - - // Nothing has been written to the channel, so waitFinished should not - // return. Give it a little bit of time to make sure the goroutine has - // started. - select { - case <-waitFinished: - t.Errorf("WaitForStartSignal completed but it should not have") - case <-time.After(50 * time.Millisecond): - // OK. - } - - // Trigger the control server StartRoot method. - cid := "foo" - if err := l.ctrl.manager.StartRoot(&cid, nil); err != nil { - t.Errorf("error calling StartRoot: %v", err) - } - - // Now WaitForStartSignal should return (within a short amount of - // time). - select { - case <-waitFinished: - // OK. - case <-time.After(50 * time.Millisecond): - t.Errorf("WaitForStartSignal did not complete but it should have") - } - -} - -// Test that MountNamespace can be created with various specs. -func TestCreateMountNamespace(t *testing.T) { - testCases := []struct { - name string - // Spec that will be used to create the mount manager. Note - // that we can't mount procfs without a kernel, so each spec - // MUST contain something other than procfs mounted at /proc. - spec specs.Spec - // Paths that are expected to exist in the resulting fs. - expectedPaths []string - }{ - { - // Only proc. - name: "only proc mount", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - // /proc, /dev, and /sys should always be mounted. - expectedPaths: []string{"/proc", "/dev", "/sys"}, - }, - { - // Mount at a deep path, with many components that do - // not exist in the root. - name: "deep mount path", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/some/very/very/deep/path", - Type: "tmpfs", - }, - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - // /some/deep/path should be mounted, along with /proc, - // /dev, and /sys. - expectedPaths: []string{"/some/very/very/deep/path", "/proc", "/dev", "/sys"}, - }, - { - // Mounts are nested inside each other. - name: "nested mounts", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - { - Destination: "/foo", - Type: "tmpfs", - }, - { - Destination: "/foo/qux", - Type: "tmpfs", - }, - { - // File mounts with the same prefix. - Destination: "/foo/qux-quz", - Type: "tmpfs", - }, - { - Destination: "/foo/bar", - Type: "tmpfs", - }, - { - Destination: "/foo/bar/baz", - Type: "tmpfs", - }, - { - // A deep path that is in foo but not the other mounts. - Destination: "/foo/some/very/very/deep/path", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/foo", "/foo/bar", "/foo/bar/baz", "/foo/qux", - "/foo/qux-quz", "/foo/some/very/very/deep/path", "/proc", "/dev", "/sys"}, - }, - { - name: "mount inside /dev", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - { - Destination: "/dev", - Type: "tmpfs", - }, - { - // Mounted by runsc by default. - Destination: "/dev/fd", - Type: "tmpfs", - }, - { - // Mount with the same prefix. - Destination: "/dev/fd-foo", - Type: "tmpfs", - }, - { - // Unsupported fs type. - Destination: "/dev/mqueue", - Type: "mqueue", - }, - { - Destination: "/dev/foo", - Type: "tmpfs", - }, - { - Destination: "/dev/bar", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/proc", "/dev", "/dev/fd-foo", "/dev/foo", "/dev/bar", "/sys"}, - }, - { - name: "mounts inside mandatory mounts", - spec: specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - // We don't include /sys, and /tmp in - // the spec, since they will be added - // automatically. - // - // Instead, add submounts inside these - // directories and make sure they are - // visible under the mandatory mounts. - { - Destination: "/sys/bar", - Type: "tmpfs", - }, - { - Destination: "/tmp/baz", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz"}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - conf := testConfig() - ctx := contexttest.Context(t) - - sandEnd, cleanup, err := startGofer(tc.spec.Root.Path) - if err != nil { - t.Fatalf("failed to create gofer: %v", err) - } - defer cleanup() - - // setupRootContainer needs to find root from the context after the - // namespace is created. - var mns *fs.MountNamespace - setMountNS := func(m *fs.MountNamespace) { - mns = m - ctx.(*contexttest.TestContext).RegisterValue(fs.CtxRoot, mns.Root()) - } - mntr := newContainerMounter(&tc.spec, "", []int{sandEnd}, nil, &podMountHints{}) - if err := mntr.setupRootContainer(ctx, ctx, conf, setMountNS); err != nil { - t.Fatalf("createMountNamespace test case %q failed: %v", tc.name, err) - } - root := mns.Root() - defer root.DecRef() - for _, p := range tc.expectedPaths { - maxTraversals := uint(0) - if d, err := mns.FindInode(ctx, root, root, p, &maxTraversals); err != nil { - t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) - } else { - d.DecRef() - } - } - }) - } -} - -// TestRestoreEnvironment tests that the correct mounts are collected from the spec and config -// in order to build the environment for restoring. -func TestRestoreEnvironment(t *testing.T) { - testCases := []struct { - name string - spec *specs.Spec - ioFDs []int - errorExpected bool - expectedRenv fs.RestoreEnvironment - }{ - { - name: "basic spec test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/some/very/very/deep/path", - Type: "tmpfs", - }, - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - }, - ioFDs: []int{0}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - }, - { - Dev: "none", - }, - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - { - name: "bind type test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/dev/fd-foo", - Type: "bind", - }, - }, - }, - ioFDs: []int{0, 1}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - { - Dev: "9pfs-/dev/fd-foo", - DataString: "trans=fd,rfdno=1,wfdno=1,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "proc": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - { - name: "options test", - spec: &specs.Spec{ - Root: &specs.Root{ - Path: os.TempDir(), - Readonly: true, - }, - Mounts: []specs.Mount{ - { - Destination: "/dev/fd-foo", - Type: "tmpfs", - Options: []string{"uid=1022", "noatime"}, - }, - }, - }, - ioFDs: []int{0}, - errorExpected: false, - expectedRenv: fs.RestoreEnvironment{ - MountSources: map[string][]fs.MountArgs{ - "9p": { - { - Dev: "9pfs-/", - Flags: fs.MountSourceFlags{ReadOnly: true}, - DataString: "trans=fd,rfdno=0,wfdno=0,privateunixsocket=true,cache=remote_revalidating", - }, - }, - "tmpfs": { - { - Dev: "none", - Flags: fs.MountSourceFlags{NoAtime: true}, - DataString: "uid=1022", - }, - { - Dev: "none", - }, - }, - "devtmpfs": { - { - Dev: "none", - }, - }, - "devpts": { - { - Dev: "none", - }, - }, - "proc": { - { - Dev: "none", - }, - }, - "sysfs": { - { - Dev: "none", - }, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - conf := testConfig() - mntr := newContainerMounter(tc.spec, "", tc.ioFDs, nil, &podMountHints{}) - actualRenv, err := mntr.createRestoreEnvironment(conf) - if !tc.errorExpected && err != nil { - t.Fatalf("could not create restore environment for test:%s", tc.name) - } else if tc.errorExpected { - if err == nil { - t.Errorf("expected an error, but no error occurred.") - } - } else { - if !reflect.DeepEqual(*actualRenv, tc.expectedRenv) { - t.Errorf("restore environments did not match for test:%s\ngot:%+v\nwant:%+v\n", tc.name, *actualRenv, tc.expectedRenv) - } - } - }) - } -} diff --git a/runsc/boot/user_test.go b/runsc/boot/user_test.go deleted file mode 100644 index 834003430..000000000 --- a/runsc/boot/user_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// 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 boot - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - "syscall" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/sentry/context/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" -) - -func setupTempDir() (string, error) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "exec-user-test") - if err != nil { - return "", err - } - return tmpDir, nil -} - -func setupPasswd(contents string, perms os.FileMode) func() (string, error) { - return func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - f, err := os.Create(filepath.Join(tmpDir, "etc", "passwd")) - if err != nil { - return "", err - } - defer f.Close() - - _, err = f.WriteString(contents) - if err != nil { - return "", err - } - - err = f.Chmod(perms) - if err != nil { - return "", err - } - return tmpDir, nil - } -} - -// TestGetExecUserHome tests the getExecUserHome function. -func TestGetExecUserHome(t *testing.T) { - tests := map[string]struct { - uid uint32 - createRoot func() (string, error) - expected string - }{ - "success": { - uid: 1000, - createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0666), - expected: "/home/adin", - }, - "no_passwd": { - uid: 1000, - createRoot: setupTempDir, - expected: "/", - }, - "no_perms": { - uid: 1000, - createRoot: setupPasswd("adin::1000:1111::/home/adin:/bin/sh", 0000), - expected: "/", - }, - "directory": { - uid: 1000, - createRoot: func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - if err := syscall.Mkdir(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil { - return "", err - } - - return tmpDir, nil - }, - expected: "/", - }, - // Currently we don't allow named pipes. - "named_pipe": { - uid: 1000, - createRoot: func() (string, error) { - tmpDir, err := setupTempDir() - if err != nil { - return "", err - } - - if err := os.Mkdir(filepath.Join(tmpDir, "etc"), 0777); err != nil { - return "", err - } - - if err := syscall.Mkfifo(filepath.Join(tmpDir, "etc", "passwd"), 0666); err != nil { - return "", err - } - - return tmpDir, nil - }, - expected: "/", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - tmpDir, err := tc.createRoot() - if err != nil { - t.Fatalf("failed to create root dir: %v", err) - } - - sandEnd, cleanup, err := startGofer(tmpDir) - if err != nil { - t.Fatalf("failed to create gofer: %v", err) - } - defer cleanup() - - ctx := contexttest.Context(t) - conf := &Config{ - RootDir: "unused_root_dir", - Network: NetworkNone, - DisableSeccomp: true, - } - - spec := &specs.Spec{ - Root: &specs.Root{ - Path: tmpDir, - Readonly: true, - }, - // Add /proc mount as tmpfs to avoid needing a kernel. - Mounts: []specs.Mount{ - { - Destination: "/proc", - Type: "tmpfs", - }, - }, - } - - var mns *fs.MountNamespace - setMountNS := func(m *fs.MountNamespace) { - mns = m - ctx.(*contexttest.TestContext).RegisterValue(fs.CtxRoot, mns.Root()) - } - mntr := newContainerMounter(spec, "", []int{sandEnd}, nil, &podMountHints{}) - if err := mntr.setupRootContainer(ctx, ctx, conf, setMountNS); err != nil { - t.Fatalf("failed to create mount namespace: %v", err) - } - - got, err := getExecUserHome(ctx, mns, tc.uid) - if err != nil { - t.Fatalf("failed to get user home: %v", err) - } - - if got != tc.expected { - t.Fatalf("expected %v, got: %v", tc.expected, got) - } - }) - } -} - -// TestFindHomeInPasswd tests the findHomeInPasswd function's passwd file parsing. -func TestFindHomeInPasswd(t *testing.T) { - tests := map[string]struct { - uid uint32 - passwd string - expected string - def string - }{ - "empty": { - uid: 1000, - passwd: "", - expected: "/", - def: "/", - }, - "whitespace": { - uid: 1000, - passwd: " ", - expected: "/", - def: "/", - }, - "full": { - uid: 1000, - passwd: "adin::1000:1111::/home/adin:/bin/sh", - expected: "/home/adin", - def: "/", - }, - // For better or worse, this is how runc works. - "partial": { - uid: 1000, - passwd: "adin::1000:1111:", - expected: "", - def: "/", - }, - "multiple": { - uid: 1001, - passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1001:1111::/home/ian:/bin/sh", - expected: "/home/ian", - def: "/", - }, - "duplicate": { - uid: 1000, - passwd: "adin::1000:1111::/home/adin:/bin/sh\nian::1000:1111::/home/ian:/bin/sh", - expected: "/home/adin", - def: "/", - }, - "empty_lines": { - uid: 1001, - passwd: "adin::1000:1111::/home/adin:/bin/sh\n\n\nian::1001:1111::/home/ian:/bin/sh", - expected: "/home/ian", - def: "/", - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got, err := findHomeInPasswd(tc.uid, strings.NewReader(tc.passwd), tc.def) - if err != nil { - t.Fatalf("error parsing passwd: %v", err) - } - if tc.expected != got { - t.Fatalf("expected %v, got: %v", tc.expected, got) - } - }) - } -} |