diff options
Diffstat (limited to 'runsc')
70 files changed, 150 insertions, 11413 deletions
diff --git a/runsc/BUILD b/runsc/BUILD deleted file mode 100644 index 3b91b984a..000000000 --- a/runsc/BUILD +++ /dev/null @@ -1,53 +0,0 @@ -load("//tools:defs.bzl", "go_binary") - -package(licenses = ["notice"]) - -go_binary( - name = "runsc", - srcs = [ - "main.go", - "version.go", - ], - pure = True, - visibility = [ - "//visibility:public", - ], - x_defs = {"main.version": "{STABLE_VERSION}"}, - deps = ["//runsc/cli"], -) - -# The runsc-race target is a race-compatible BUILD target. This must be built -# via: bazel build --features=race :runsc-race -# -# This is neccessary because the race feature must apply to all dependencies -# due a bug in gazelle file selection. The pure attribute must be off because -# the race detector requires linking with non-Go components, although we still -# require a static binary. -# -# Note that in the future this might be convertible to a compatible target by -# using the pure and static attributes within a select function, but select is -# not currently compatible with string attributes [1]. -# -# [1] https://github.com/bazelbuild/bazel/issues/1698 -go_binary( - name = "runsc-race", - srcs = [ - "main.go", - "version.go", - ], - static = True, - visibility = [ - "//visibility:public", - ], - x_defs = {"main.version": "{STABLE_VERSION}"}, - deps = ["//runsc/cli"], -) - -sh_test( - name = "version_test", - size = "small", - srcs = ["version_test.sh"], - args = ["$(location :runsc)"], - data = [":runsc"], - tags = ["noguitar"], -) diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD deleted file mode 100644 index 67307ab3c..000000000 --- a/runsc/boot/BUILD +++ /dev/null @@ -1,147 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "boot", - srcs = [ - "compat.go", - "compat_amd64.go", - "compat_arm64.go", - "controller.go", - "debug.go", - "events.go", - "fs.go", - "limits.go", - "loader.go", - "network.go", - "strace.go", - "vfs.go", - ], - visibility = [ - "//pkg/test:__subpackages__", - "//runsc:__subpackages__", - "//test:__subpackages__", - ], - deps = [ - "//pkg/abi", - "//pkg/abi/linux", - "//pkg/bpf", - "//pkg/cleanup", - "//pkg/context", - "//pkg/control/server", - "//pkg/cpuid", - "//pkg/eventchannel", - "//pkg/fd", - "//pkg/flipcall", - "//pkg/fspath", - "//pkg/log", - "//pkg/memutil", - "//pkg/rand", - "//pkg/refs", - "//pkg/refsvfs2", - "//pkg/sentry/arch", - "//pkg/sentry/arch:registers_go_proto", - "//pkg/sentry/control", - "//pkg/sentry/devices/memdev", - "//pkg/sentry/devices/ttydev", - "//pkg/sentry/devices/tundev", - "//pkg/sentry/fdimport", - "//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/fs/user", - "//pkg/sentry/fsimpl/devpts", - "//pkg/sentry/fsimpl/devtmpfs", - "//pkg/sentry/fsimpl/fuse", - "//pkg/sentry/fsimpl/gofer", - "//pkg/sentry/fsimpl/host", - "//pkg/sentry/fsimpl/overlay", - "//pkg/sentry/fsimpl/proc", - "//pkg/sentry/fsimpl/sys", - "//pkg/sentry/fsimpl/tmpfs", - "//pkg/sentry/inet", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel:uncaught_signal_go_proto", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/limits", - "//pkg/sentry/loader", - "//pkg/sentry/pgalloc", - "//pkg/sentry/platform", - "//pkg/sentry/sighandling", - "//pkg/sentry/socket/hostinet", - "//pkg/sentry/socket/netfilter", - "//pkg/sentry/socket/netlink", - "//pkg/sentry/socket/netlink/route", - "//pkg/sentry/socket/netlink/uevent", - "//pkg/sentry/socket/netstack", - "//pkg/sentry/socket/unix", - "//pkg/sentry/state", - "//pkg/sentry/strace", - "//pkg/sentry/syscalls/linux/vfs2", - "//pkg/sentry/time", - "//pkg/sentry/unimpl:unimplemented_syscall_go_proto", - "//pkg/sentry/usage", - "//pkg/sentry/vfs", - "//pkg/sentry/watchdog", - "//pkg/sync", - "//pkg/syserror", - "//pkg/tcpip", - "//pkg/tcpip/link/fdbased", - "//pkg/tcpip/link/loopback", - "//pkg/tcpip/link/packetsocket", - "//pkg/tcpip/link/qdisc/fifo", - "//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/raw", - "//pkg/tcpip/transport/tcp", - "//pkg/tcpip/transport/udp", - "//pkg/urpc", - "//runsc/boot/filter", - "//runsc/boot/platforms", - "//runsc/boot/pprof", - "//runsc/config", - "//runsc/specutils", - "//runsc/specutils/seccomp", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@org_golang_google_protobuf//proto:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "boot_test", - size = "small", - srcs = [ - "compat_test.go", - "fs_test.go", - "loader_test.go", - ], - library = ":boot", - deps = [ - "//pkg/control/server", - "//pkg/fd", - "//pkg/fspath", - "//pkg/log", - "//pkg/p9", - "//pkg/sentry/contexttest", - "//pkg/sentry/fs", - "//pkg/sentry/vfs", - "//pkg/sync", - "//pkg/unet", - "//runsc/config", - "//runsc/fsgofer", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/boot/boot_amd64_state_autogen.go b/runsc/boot/boot_amd64_state_autogen.go new file mode 100644 index 000000000..23dd4b7b3 --- /dev/null +++ b/runsc/boot/boot_amd64_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package boot diff --git a/runsc/boot/boot_arm64_state_autogen.go b/runsc/boot/boot_arm64_state_autogen.go new file mode 100644 index 000000000..23dd4b7b3 --- /dev/null +++ b/runsc/boot/boot_arm64_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package boot diff --git a/runsc/boot/boot_state_autogen.go b/runsc/boot/boot_state_autogen.go new file mode 100644 index 000000000..92736f360 --- /dev/null +++ b/runsc/boot/boot_state_autogen.go @@ -0,0 +1,37 @@ +// automatically generated by stateify. + +package boot + +import ( + "gvisor.dev/gvisor/pkg/state" +) + +func (f *sandboxNetstackCreator) StateTypeName() string { + return "runsc/boot.sandboxNetstackCreator" +} + +func (f *sandboxNetstackCreator) StateFields() []string { + return []string{ + "clock", + "uniqueID", + } +} + +func (f *sandboxNetstackCreator) beforeSave() {} + +func (f *sandboxNetstackCreator) StateSave(stateSinkObject state.Sink) { + f.beforeSave() + stateSinkObject.Save(0, &f.clock) + stateSinkObject.Save(1, &f.uniqueID) +} + +func (f *sandboxNetstackCreator) afterLoad() {} + +func (f *sandboxNetstackCreator) StateLoad(stateSourceObject state.Source) { + stateSourceObject.Load(0, &f.clock) + stateSourceObject.Load(1, &f.uniqueID) +} + +func init() { + state.Register((*sandboxNetstackCreator)(nil)) +} diff --git a/runsc/boot/compat_test.go b/runsc/boot/compat_test.go deleted file mode 100644 index 839c5303b..000000000 --- a/runsc/boot/compat_test.go +++ /dev/null @@ -1,90 +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" -) - -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 - arg1_1 uint64 - arg1_2 uint64 - arg2_1 uint64 - arg2_2 uint64 - want bool - }{ - {name: "same arg1", idx: []int{0}, arg1_1: 123, arg1_2: 123, want: false}, - {name: "same arg2", idx: []int{1}, arg2_1: 123, arg2_2: 123, want: false}, - {name: "diff arg1", idx: []int{0}, arg1_1: 123, arg1_2: 321, want: true}, - {name: "diff arg2", idx: []int{1}, arg2_1: 123, arg2_2: 321, want: true}, - {name: "cmd is uint32", idx: []int{0}, arg2_1: 0xdead00000123, arg2_2: 0xbeef00000123, want: false}, - {name: "same 2 args", idx: []int{0, 1}, arg2_1: 123, arg1_1: 321, arg2_2: 123, arg1_2: 321, want: false}, - {name: "diff 2 args", idx: []int{0, 1}, arg2_1: 123, arg1_1: 321, arg2_2: 789, arg1_2: 987, want: true}, - } { - t.Run(tc.name, func(t *testing.T) { - c := newArgsTracker(tc.idx...) - regs := newRegs() - setArgVal(0, tc.arg1_1, regs) - setArgVal(1, tc.arg2_1, regs) - if !c.shouldReport(regs) { - t.Error("first call to shouldReport, got: false, want: true") - } - c.onReported(regs) - - setArgVal(0, tc.arg1_2, regs) - setArgVal(1, tc.arg2_2, regs) - 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 := newRegs() - setArgVal(0, 123, regs) - setArgVal(1, uint64(i), regs) - 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 := newRegs() - setArgVal(0, 123, regs) - setArgVal(1, 123456, regs) - 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 ed18f0047..000000000 --- a/runsc/boot/filter/BUILD +++ /dev/null @@ -1,28 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filter", - srcs = [ - "config.go", - "config_amd64.go", - "config_arm64.go", - "config_profile.go", - "extra_filters.go", - "extra_filters_msan.go", - "extra_filters_race.go", - "filter.go", - ], - visibility = [ - "//runsc/boot:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/seccomp", - "//pkg/sentry/platform", - "//pkg/tcpip/link/fdbased", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/boot/filter/filter_amd64_state_autogen.go b/runsc/boot/filter/filter_amd64_state_autogen.go new file mode 100644 index 000000000..0f27e5568 --- /dev/null +++ b/runsc/boot/filter/filter_amd64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build amd64 + +package filter diff --git a/runsc/boot/filter/filter_arm64_state_autogen.go b/runsc/boot/filter/filter_arm64_state_autogen.go new file mode 100644 index 000000000..e87cf5af7 --- /dev/null +++ b/runsc/boot/filter/filter_arm64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build arm64 + +package filter diff --git a/runsc/boot/filter/filter_race_state_autogen.go b/runsc/boot/filter/filter_race_state_autogen.go new file mode 100644 index 000000000..c4a858e80 --- /dev/null +++ b/runsc/boot/filter/filter_race_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build race + +package filter diff --git a/runsc/boot/filter/filter_state_autogen.go b/runsc/boot/filter/filter_state_autogen.go new file mode 100644 index 000000000..41ff99424 --- /dev/null +++ b/runsc/boot/filter/filter_state_autogen.go @@ -0,0 +1,6 @@ +// automatically generated by stateify. + +// +build !msan,!race +// +build msan + +package filter diff --git a/runsc/boot/fs_test.go b/runsc/boot/fs_test.go deleted file mode 100644 index e986231e5..000000000 --- a/runsc/boot/fs_test.go +++ /dev/null @@ -1,251 +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 ( - "reflect" - "strings" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/runsc/config" -) - -func TestPodMountHintsHappy(t *testing.T) { - spec := &specs.Spec{ - Annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "pod", - - MountPrefix + "mount2.source": "bar", - MountPrefix + "mount2.type": "bind", - MountPrefix + "mount2.share": "container", - MountPrefix + "mount2.options": "rw,private", - }, - } - podHints, err := newPodMountHints(spec) - if err != nil { - t.Fatalf("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{ - 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{ - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "pod", - }, - error: "source field", - }, - { - name: "missing type", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.share": "pod", - }, - error: "type field", - }, - { - name: "missing share", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "tmpfs", - }, - error: "share field", - }, - { - name: "invalid field name", - annotations: map[string]string{ - MountPrefix + "mount1.invalid": "foo", - }, - error: "invalid mount annotation", - }, - { - name: "invalid source", - annotations: map[string]string{ - MountPrefix + "mount1.source": "", - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "pod", - }, - error: "source cannot be empty", - }, - { - name: "invalid type", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "invalid-type", - MountPrefix + "mount1.share": "pod", - }, - error: "invalid type", - }, - { - name: "invalid share", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "invalid-share", - }, - error: "invalid share", - }, - { - name: "invalid options", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "pod", - MountPrefix + "mount1.options": "invalid-option", - }, - error: "unknown mount option", - }, - { - name: "duplicate source", - annotations: map[string]string{ - MountPrefix + "mount1.source": "foo", - MountPrefix + "mount1.type": "tmpfs", - MountPrefix + "mount1.share": "pod", - - MountPrefix + "mount2.source": "foo", - MountPrefix + "mount2.type": "bind", - 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) - } - }) - } -} - -func TestGetMountAccessType(t *testing.T) { - const source = "foo" - for _, tst := range []struct { - name string - annotations map[string]string - want config.FileAccessType - }{ - { - name: "container=exclusive", - annotations: map[string]string{ - MountPrefix + "mount1.source": source, - MountPrefix + "mount1.type": "bind", - MountPrefix + "mount1.share": "container", - }, - want: config.FileAccessExclusive, - }, - { - name: "pod=shared", - annotations: map[string]string{ - MountPrefix + "mount1.source": source, - MountPrefix + "mount1.type": "bind", - MountPrefix + "mount1.share": "pod", - }, - want: config.FileAccessShared, - }, - { - name: "shared=shared", - annotations: map[string]string{ - MountPrefix + "mount1.source": source, - MountPrefix + "mount1.type": "bind", - MountPrefix + "mount1.share": "shared", - }, - want: config.FileAccessShared, - }, - { - name: "default=shared", - annotations: map[string]string{ - MountPrefix + "mount1.source": source + "mismatch", - MountPrefix + "mount1.type": "bind", - MountPrefix + "mount1.share": "container", - }, - want: config.FileAccessShared, - }, - } { - t.Run(tst.name, func(t *testing.T) { - spec := &specs.Spec{Annotations: tst.annotations} - podHints, err := newPodMountHints(spec) - if err != nil { - t.Fatalf("newPodMountHints failed: %v", err) - } - mounter := containerMounter{hints: podHints} - if got := mounter.getMountAccessType(specs.Mount{Source: source}); got != tst.want { - t.Errorf("getMountAccessType(), want: %v, got: %v", tst.want, got) - } - }) - } -} diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go deleted file mode 100644 index b77b4762e..000000000 --- a/runsc/boot/loader_test.go +++ /dev/null @@ -1,721 +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" - "syscall" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/control/server" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/fspath" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/sentry/contexttest" - "gvisor.dev/gvisor/pkg/sentry/fs" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/runsc/config" - "gvisor.dev/gvisor/runsc/fsgofer" -) - -func init() { - log.SetLevel(log.Debug) - rand.Seed(time.Now().UnixNano()) - if err := fsgofer.OpenProcSelfFD(); err != nil { - panic(err) - } - config.RegisterFlags() -} - -func testConfig() *config.Config { - conf, err := config.NewFromFlags() - if err != nil { - panic(err) - } - // Change test defaults. - conf.RootDir = "unused_root_dir" - conf.Network = config.NetworkNone - conf.DisableSeccomp = true - return conf -} - -// 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(vfsEnabled bool, spec *specs.Spec) (*Loader, func(), error) { - fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10])) - if err != nil { - return nil, nil, err - } - conf := testConfig() - conf.VFS2 = vfsEnabled - - sandEnd, cleanup, err := startGofer(spec.Root.Path) - if err != nil { - return nil, nil, err - } - - // Loader takes ownership of stdio. - var stdio []int - for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} { - newFd, err := unix.Dup(int(f.Fd())) - if err != nil { - return nil, nil, err - } - stdio = append(stdio, newFd) - } - - 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) { - doRun(t, false) -} - -// TestRunVFS2 runs TestRun in VFSv2. -func TestRunVFS2(t *testing.T) { - doRun(t, true) -} - -func doRun(t *testing.T, vfsEnabled bool) { - l, cleanup, err := createLoader(vfsEnabled, testSpec()) - 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) { - doStartSignal(t, false) -} - -// TestStartSignalVFS2 does TestStartSignal with VFS2. -func TestStartSignalVFS2(t *testing.T) { - doStartSignal(t, true) -} - -func doStartSignal(t *testing.T, vfsEnabled bool) { - l, cleanup, err := createLoader(vfsEnabled, testSpec()) - 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") - } - -} - -type CreateMountTestcase 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 -} - -func createMountTestcases() []*CreateMountTestcase { - testCases := []*CreateMountTestcase{ - { - // 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", - }, - { - Destination: "/sys/bar", - Type: "tmpfs", - }, - { - Destination: "/tmp/baz", - Type: "tmpfs", - }, - { - Destination: "/dev/goo", - Type: "tmpfs", - }, - }, - }, - expectedPaths: []string{"/proc", "/sys", "/sys/bar", "/tmp", "/tmp/baz", "/dev/goo"}, - }, - } - - return testCases -} - -// Test that MountNamespace can be created with various specs. -func TestCreateMountNamespace(t *testing.T) { - for _, tc := range createMountTestcases() { - 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() - - mntr := newContainerMounter(&tc.spec, []*fd.FD{fd.New(sandEnd)}, nil, &podMountHints{}) - mns, err := mntr.createMountNamespace(ctx, conf) - if err != nil { - t.Fatalf("failed to create mount namespace: %v", err) - } - ctx = fs.WithRoot(ctx, mns.Root()) - if err := mntr.mountSubmounts(ctx, conf, mns); err != nil { - t.Fatalf("failed to create mount namespace: %v", err) - } - - root := mns.Root() - defer root.DecRef(ctx) - 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(ctx) - } - } - }) - } -} - -// Test that MountNamespace can be created with various specs. -func TestCreateMountNamespaceVFS2(t *testing.T) { - for _, tc := range createMountTestcases() { - t.Run(tc.name, func(t *testing.T) { - spec := testSpec() - spec.Mounts = tc.spec.Mounts - spec.Root = tc.spec.Root - - t.Logf("Using root: %q", spec.Root.Path) - l, loaderCleanup, err := createLoader(true /* VFS2 Enabled */, spec) - if err != nil { - t.Fatalf("failed to create loader: %v", err) - } - defer l.Destroy() - defer loaderCleanup() - - mntr := newContainerMounter(l.root.spec, l.root.goferFDs, l.k, l.mountHints) - if err := mntr.processHints(l.root.conf, l.root.procArgs.Credentials); err != nil { - t.Fatalf("failed process hints: %v", err) - } - - ctx := l.k.SupervisorContext() - mns, err := mntr.mountAll(l.root.conf, &l.root.procArgs) - if err != nil { - t.Fatalf("mountAll: %v", err) - } - - root := mns.Root() - root.IncRef() - defer root.DecRef(ctx) - for _, p := range tc.expectedPaths { - target := &vfs.PathOperation{ - Root: root, - Start: root, - Path: fspath.Parse(p), - } - - if d, err := l.k.VFS().GetDentryAt(ctx, l.root.procArgs.Credentials, target, &vfs.GetDentryOptions{}); err != nil { - t.Errorf("expected path %v to exist with spec %v, but got error %v", p, tc.spec, err) - } else { - d.DecRef(ctx) - } - } - }) - } -} - -// 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", - }, - }, - "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", - }, - { - 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", - }, - }, - "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() - var ioFDs []*fd.FD - for _, ioFD := range tc.ioFDs { - ioFDs = append(ioFDs, fd.New(ioFD)) - } - mntr := newContainerMounter(tc.spec, 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/platforms/BUILD b/runsc/boot/platforms/BUILD deleted file mode 100644 index 77774f43c..000000000 --- a/runsc/boot/platforms/BUILD +++ /dev/null @@ -1,15 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "platforms", - srcs = ["platforms.go"], - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/sentry/platform/kvm", - "//pkg/sentry/platform/ptrace", - ], -) diff --git a/runsc/boot/platforms/platforms_state_autogen.go b/runsc/boot/platforms/platforms_state_autogen.go new file mode 100644 index 000000000..8676d25c1 --- /dev/null +++ b/runsc/boot/platforms/platforms_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package platforms diff --git a/runsc/boot/pprof/BUILD b/runsc/boot/pprof/BUILD deleted file mode 100644 index 29cb42b2f..000000000 --- a/runsc/boot/pprof/BUILD +++ /dev/null @@ -1,11 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "pprof", - srcs = ["pprof.go"], - visibility = [ - "//runsc:__subpackages__", - ], -) diff --git a/runsc/boot/pprof/pprof_state_autogen.go b/runsc/boot/pprof/pprof_state_autogen.go new file mode 100644 index 000000000..cabd43173 --- /dev/null +++ b/runsc/boot/pprof/pprof_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package pprof diff --git a/runsc/cgroup/BUILD b/runsc/cgroup/BUILD deleted file mode 100644 index 37f4253ba..000000000 --- a/runsc/cgroup/BUILD +++ /dev/null @@ -1,27 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "cgroup", - srcs = ["cgroup.go"], - visibility = ["//:sandbox"], - deps = [ - "//pkg/cleanup", - "//pkg/log", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - ], -) - -go_test( - name = "cgroup_test", - size = "small", - srcs = ["cgroup_test.go"], - library = ":cgroup", - tags = ["local"], - deps = [ - "//pkg/test/testutil", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - ], -) diff --git a/runsc/cgroup/cgroup_state_autogen.go b/runsc/cgroup/cgroup_state_autogen.go new file mode 100644 index 000000000..934ed169b --- /dev/null +++ b/runsc/cgroup/cgroup_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package cgroup diff --git a/runsc/cgroup/cgroup_test.go b/runsc/cgroup/cgroup_test.go deleted file mode 100644 index 48d71cfa6..000000000 --- a/runsc/cgroup/cgroup_test.go +++ /dev/null @@ -1,815 +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 cgroup - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/test/testutil" -) - -var debianMountinfo = ` -35 24 0:30 / /sys/fs/cgroup ro shared:9 - tmpfs tmpfs ro -36 35 0:31 / /sys/fs/cgroup/unified rw shared:10 - cgroup2 cgroup2 rw -37 35 0:32 / /sys/fs/cgroup/systemd rw - cgroup cgroup rw,name=systemd -41 35 0:36 / /sys/fs/cgroup/cpu,cpuacct rw shared:16 - cgroup cgroup rw,cpu,cpuacct -42 35 0:37 / /sys/fs/cgroup/freezer rw shared:17 - cgroup cgroup rw,freezer -43 35 0:38 / /sys/fs/cgroup/hugetlb rw shared:18 - cgroup cgroup rw,hugetlb -44 35 0:39 / /sys/fs/cgroup/cpuset rw shared:19 - cgroup cgroup rw,cpuset -45 35 0:40 / /sys/fs/cgroup/net_cls,net_prio rw shared:20 - cgroup cgroup rw,net_cls,net_prio -46 35 0:41 / /sys/fs/cgroup/pids rw shared:21 - cgroup cgroup rw,pids -47 35 0:42 / /sys/fs/cgroup/perf_event rw shared:22 - cgroup cgroup rw,perf_event -48 35 0:43 / /sys/fs/cgroup/memory rw shared:23 - cgroup cgroup rw,memory -49 35 0:44 / /sys/fs/cgroup/blkio rw shared:24 - cgroup cgroup rw,blkio -50 35 0:45 / /sys/fs/cgroup/devices rw shared:25 - cgroup cgroup rw,devices -51 35 0:46 / /sys/fs/cgroup/rdma rw shared:26 - cgroup cgroup rw,rdma -` - -var dindMountinfo = ` -1305 1304 0:64 / /sys/fs/cgroup rw - tmpfs tmpfs rw,mode=755 -1306 1305 0:32 /docker/136 /sys/fs/cgroup/systemd ro master:11 - cgroup cgroup rw,xattr,name=systemd -1307 1305 0:36 /docker/136 /sys/fs/cgroup/cpu,cpuacct ro master:16 - cgroup cgroup rw,cpu,cpuacct -1308 1305 0:37 /docker/136 /sys/fs/cgroup/freezer ro master:17 - cgroup cgroup rw,freezer -1309 1305 0:38 /docker/136 /sys/fs/cgroup/hugetlb ro master:18 - cgroup cgroup rw,hugetlb -1310 1305 0:39 /docker/136 /sys/fs/cgroup/cpuset ro master:19 - cgroup cgroup rw,cpuset -1311 1305 0:40 /docker/136 /sys/fs/cgroup/net_cls,net_prio ro master:20 - cgroup cgroup rw,net_cls,net_prio -1312 1305 0:41 /docker/136 /sys/fs/cgroup/pids ro master:21 - cgroup cgroup rw,pids -1313 1305 0:42 /docker/136 /sys/fs/cgroup/perf_event ro master:22 - cgroup cgroup rw,perf_event -1314 1305 0:43 /docker/136 /sys/fs/cgroup/memory ro master:23 - cgroup cgroup rw,memory -1316 1305 0:44 /docker/136 /sys/fs/cgroup/blkio ro master:24 - cgroup cgroup rw,blkio -1317 1305 0:45 /docker/136 /sys/fs/cgroup/devices ro master:25 - cgroup cgroup rw,devices -1318 1305 0:46 / /sys/fs/cgroup/rdma ro master:26 - cgroup cgroup rw,rdma -` - -func TestUninstallEnoent(t *testing.T) { - c := Cgroup{ - // set a non-existent name - Name: "runsc-test-uninstall-656e6f656e740a", - } - c.Own = make(map[string]bool) - for key := range controllers { - c.Own[key] = true - } - if err := c.Uninstall(); err != nil { - t.Errorf("Uninstall() failed: %v", err) - } -} - -func TestCountCpuset(t *testing.T) { - for _, tc := range []struct { - str string - want int - error bool - }{ - {str: "0", want: 1}, - {str: "0,1,2,8,9,10", want: 6}, - {str: "0-1", want: 2}, - {str: "0-7", want: 8}, - {str: "0-7,16,32-39,64,65", want: 19}, - {str: "a", error: true}, - {str: "5-a", error: true}, - {str: "a-5", error: true}, - {str: "-10", error: true}, - {str: "15-", error: true}, - {str: "-", error: true}, - {str: "--", error: true}, - } { - t.Run(tc.str, func(t *testing.T) { - got, err := countCpuset(tc.str) - if tc.error { - if err == nil { - t.Errorf("countCpuset(%q) should have failed", tc.str) - } - } else { - if err != nil { - t.Errorf("countCpuset(%q) failed: %v", tc.str, err) - } - if tc.want != got { - t.Errorf("countCpuset(%q) want: %d, got: %d", tc.str, tc.want, got) - } - } - }) - } -} - -func uint16Ptr(v uint16) *uint16 { - return &v -} - -func uint32Ptr(v uint32) *uint32 { - return &v -} - -func int64Ptr(v int64) *int64 { - return &v -} - -func uint64Ptr(v uint64) *uint64 { - return &v -} - -func boolPtr(v bool) *bool { - return &v -} - -func checkDir(t *testing.T, dir string, contents map[string]string) { - all, err := ioutil.ReadDir(dir) - if err != nil { - t.Fatalf("ReadDir(%q): %v", dir, err) - } - fileCount := 0 - for _, file := range all { - if file.IsDir() { - // Only want to compare files. - continue - } - fileCount++ - - want, ok := contents[file.Name()] - if !ok { - t.Errorf("file not expected: %q", file.Name()) - continue - } - gotBytes, err := ioutil.ReadFile(filepath.Join(dir, file.Name())) - if err != nil { - t.Fatal(err.Error()) - } - got := strings.TrimSuffix(string(gotBytes), "\n") - if got != want { - t.Errorf("wrong file content, file: %q, want: %q, got: %q", file.Name(), want, got) - } - } - if fileCount != len(contents) { - t.Errorf("file is missing, want: %v, got: %v", contents, all) - } -} - -func makeLinuxWeightDevice(major, minor int64, weight, leafWeight *uint16) specs.LinuxWeightDevice { - rv := specs.LinuxWeightDevice{ - Weight: weight, - LeafWeight: leafWeight, - } - rv.Major = major - rv.Minor = minor - return rv -} - -func makeLinuxThrottleDevice(major, minor int64, rate uint64) specs.LinuxThrottleDevice { - rv := specs.LinuxThrottleDevice{ - Rate: rate, - } - rv.Major = major - rv.Minor = minor - return rv -} - -func TestBlockIO(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxBlockIO - wants map[string]string - }{ - { - name: "simple", - spec: &specs.LinuxBlockIO{ - Weight: uint16Ptr(1), - LeafWeight: uint16Ptr(2), - }, - wants: map[string]string{ - "blkio.weight": "1", - "blkio.leaf_weight": "2", - }, - }, - { - name: "weight_device", - spec: &specs.LinuxBlockIO{ - WeightDevice: []specs.LinuxWeightDevice{ - makeLinuxWeightDevice(1, 2, uint16Ptr(3), uint16Ptr(4)), - }, - }, - wants: map[string]string{ - "blkio.weight_device": "1:2 3", - "blkio.leaf_weight_device": "1:2 4", - }, - }, - { - name: "weight_device_nil_values", - spec: &specs.LinuxBlockIO{ - WeightDevice: []specs.LinuxWeightDevice{ - makeLinuxWeightDevice(1, 2, nil, nil), - }, - }, - }, - { - name: "throttle", - spec: &specs.LinuxBlockIO{ - ThrottleReadBpsDevice: []specs.LinuxThrottleDevice{ - makeLinuxThrottleDevice(1, 2, 3), - }, - ThrottleReadIOPSDevice: []specs.LinuxThrottleDevice{ - makeLinuxThrottleDevice(4, 5, 6), - }, - ThrottleWriteBpsDevice: []specs.LinuxThrottleDevice{ - makeLinuxThrottleDevice(7, 8, 9), - }, - ThrottleWriteIOPSDevice: []specs.LinuxThrottleDevice{ - makeLinuxThrottleDevice(10, 11, 12), - }, - }, - wants: map[string]string{ - "blkio.throttle.read_bps_device": "1:2 3", - "blkio.throttle.read_iops_device": "4:5 6", - "blkio.throttle.write_bps_device": "7:8 9", - "blkio.throttle.write_iops_device": "10:11 12", - }, - }, - { - name: "nil_values", - spec: &specs.LinuxBlockIO{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - BlockIO: tc.spec, - } - ctrlr := blockIO{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestCPU(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxCPU - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxCPU{ - Shares: uint64Ptr(1), - Quota: int64Ptr(2), - Period: uint64Ptr(3), - RealtimeRuntime: int64Ptr(4), - RealtimePeriod: uint64Ptr(5), - }, - wants: map[string]string{ - "cpu.shares": "1", - "cpu.cfs_quota_us": "2", - "cpu.cfs_period_us": "3", - "cpu.rt_runtime_us": "4", - "cpu.rt_period_us": "5", - }, - }, - { - name: "nil_values", - spec: &specs.LinuxCPU{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - CPU: tc.spec, - } - ctrlr := cpu{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestCPUSet(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxCPU - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxCPU{ - Cpus: "foo", - Mems: "bar", - }, - wants: map[string]string{ - "cpuset.cpus": "foo", - "cpuset.mems": "bar", - }, - }, - // Don't test nil values because they are copied from the parent. - // See TestCPUSetAncestor(). - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - CPU: tc.spec, - } - ctrlr := cpuSet{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -// TestCPUSetAncestor checks that, when not available, value is read from -// parent directory. -func TestCPUSetAncestor(t *testing.T) { - // Prepare master directory with cgroup files that will be propagated to - // children. - grandpa, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(grandpa) - - if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.cpus"), []byte("parent-cpus"), 0666); err != nil { - t.Fatalf("ioutil.WriteFile(): %v", err) - } - if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.mems"), []byte("parent-mems"), 0666); err != nil { - t.Fatalf("ioutil.WriteFile(): %v", err) - } - - for _, tc := range []struct { - name string - spec *specs.LinuxCPU - }{ - { - name: "nil_values", - spec: &specs.LinuxCPU{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - // Create empty files in intermediate directory. They should be ignored - // when reading, and then populated from parent. - parent, err := ioutil.TempDir(grandpa, "parent") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(parent) - if _, err := os.Create(filepath.Join(parent, "cpuset.cpus")); err != nil { - t.Fatalf("os.Create(): %v", err) - } - if _, err := os.Create(filepath.Join(parent, "cpuset.mems")); err != nil { - t.Fatalf("os.Create(): %v", err) - } - - // cgroup files mmust exist. - dir, err := ioutil.TempDir(parent, "child") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - if _, err := os.Create(filepath.Join(dir, "cpuset.cpus")); err != nil { - t.Fatalf("os.Create(): %v", err) - } - if _, err := os.Create(filepath.Join(dir, "cpuset.mems")); err != nil { - t.Fatalf("os.Create(): %v", err) - } - - spec := &specs.LinuxResources{ - CPU: tc.spec, - } - ctrlr := cpuSet{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - want := map[string]string{ - "cpuset.cpus": "parent-cpus", - "cpuset.mems": "parent-mems", - } - // Both path and dir must have been populated from grandpa. - checkDir(t, parent, want) - checkDir(t, dir, want) - }) - } -} - -func TestHugeTlb(t *testing.T) { - for _, tc := range []struct { - name string - spec []specs.LinuxHugepageLimit - wants map[string]string - }{ - { - name: "single", - spec: []specs.LinuxHugepageLimit{ - { - Pagesize: "1G", - Limit: 123, - }, - }, - wants: map[string]string{ - "hugetlb.1G.limit_in_bytes": "123", - }, - }, - { - name: "multiple", - spec: []specs.LinuxHugepageLimit{ - { - Pagesize: "1G", - Limit: 123, - }, - { - Pagesize: "2G", - Limit: 456, - }, - { - Pagesize: "1P", - Limit: 789, - }, - }, - wants: map[string]string{ - "hugetlb.1G.limit_in_bytes": "123", - "hugetlb.2G.limit_in_bytes": "456", - "hugetlb.1P.limit_in_bytes": "789", - }, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - HugepageLimits: tc.spec, - } - ctrlr := hugeTLB{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestMemory(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxMemory - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxMemory{ - Limit: int64Ptr(1), - Reservation: int64Ptr(2), - Swap: int64Ptr(3), - Kernel: int64Ptr(4), - KernelTCP: int64Ptr(5), - Swappiness: uint64Ptr(6), - DisableOOMKiller: boolPtr(true), - }, - wants: map[string]string{ - "memory.limit_in_bytes": "1", - "memory.soft_limit_in_bytes": "2", - "memory.memsw.limit_in_bytes": "3", - "memory.kmem.limit_in_bytes": "4", - "memory.kmem.tcp.limit_in_bytes": "5", - "memory.swappiness": "6", - "memory.oom_control": "1", - }, - }, - { - // Disable OOM killer should only write when set to true. - name: "oomkiller", - spec: &specs.LinuxMemory{ - DisableOOMKiller: boolPtr(false), - }, - }, - { - name: "nil_values", - spec: &specs.LinuxMemory{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - Memory: tc.spec, - } - ctrlr := memory{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestNetworkClass(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxNetwork - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxNetwork{ - ClassID: uint32Ptr(1), - }, - wants: map[string]string{ - "net_cls.classid": "1", - }, - }, - { - name: "nil_values", - spec: &specs.LinuxNetwork{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - Network: tc.spec, - } - ctrlr := networkClass{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestNetworkPriority(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxNetwork - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxNetwork{ - Priorities: []specs.LinuxInterfacePriority{ - { - Name: "foo", - Priority: 1, - }, - }, - }, - wants: map[string]string{ - "net_prio.ifpriomap": "foo 1", - }, - }, - { - name: "nil_values", - spec: &specs.LinuxNetwork{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - Network: tc.spec, - } - ctrlr := networkPrio{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestPids(t *testing.T) { - for _, tc := range []struct { - name string - spec *specs.LinuxPids - wants map[string]string - }{ - { - name: "all", - spec: &specs.LinuxPids{Limit: 1}, - wants: map[string]string{ - "pids.max": "1", - }, - }, - { - name: "nil_values", - spec: &specs.LinuxPids{}, - }, - { - name: "nil", - }, - } { - t.Run(tc.name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(dir) - - spec := &specs.LinuxResources{ - Pids: tc.spec, - } - ctrlr := pids{} - if err := ctrlr.set(spec, dir); err != nil { - t.Fatalf("ctrlr.set(): %v", err) - } - checkDir(t, dir, tc.wants) - }) - } -} - -func TestLoadPaths(t *testing.T) { - for _, tc := range []struct { - name string - cgroups string - mountinfo string - want map[string]string - err string - }{ - { - name: "abs-path-unknown-controller", - cgroups: "0:ctr:/path", - mountinfo: debianMountinfo, - want: map[string]string{"ctr": "/path"}, - }, - { - name: "rel-path", - cgroups: "0:ctr:rel-path", - mountinfo: debianMountinfo, - want: map[string]string{"ctr": "rel-path"}, - }, - { - name: "non-controller", - cgroups: "0:name=systemd:/path", - mountinfo: debianMountinfo, - want: map[string]string{"systemd": "path"}, - }, - { - name: "empty", - mountinfo: debianMountinfo, - }, - { - name: "multiple", - cgroups: "0:ctr0:/path0\n" + - "1:ctr1:/path1\n" + - "2::/empty\n", - mountinfo: debianMountinfo, - want: map[string]string{ - "ctr0": "/path0", - "ctr1": "/path1", - }, - }, - { - name: "missing-field", - cgroups: "0:nopath\n", - mountinfo: debianMountinfo, - err: "invalid cgroups file", - }, - { - name: "too-many-fields", - cgroups: "0:ctr:/path:extra\n", - mountinfo: debianMountinfo, - err: "invalid cgroups file", - }, - { - name: "multiple-malformed", - cgroups: "0:ctr0:/path0\n" + - "1:ctr1:/path1\n" + - "2:\n", - mountinfo: debianMountinfo, - err: "invalid cgroups file", - }, - { - name: "nested-cgroup", - cgroups: `9:memory:/docker/136 -2:cpu,cpuacct:/docker/136 -1:name=systemd:/docker/136 -0::/system.slice/containerd.service`, - mountinfo: dindMountinfo, - // we want relative path to /sys/fs/cgroup inside the nested container. - // Subcroup inside the container will be created at /sys/fs/cgroup/cpu - // This will be /sys/fs/cgroup/cpu/docker/136/CGROUP_NAME - // outside the container - want: map[string]string{ - "memory": ".", - "cpu": ".", - "cpuacct": ".", - "systemd": ".", - }, - }, - { - name: "nested-cgroup-submount", - cgroups: "9:memory:/docker/136/test", - mountinfo: dindMountinfo, - want: map[string]string{ - "memory": "test", - }, - }, - { - name: "invalid-mount-info", - cgroups: "0:memory:/path", - mountinfo: "41 35 0:36 / /sys/fs/cgroup/memory rw shared:16 - invalid", - want: map[string]string{ - "memory": "/path", - }, - }, - { - name: "invalid-rel-path-in-proc-cgroup", - cgroups: "9:memory:./invalid", - mountinfo: dindMountinfo, - err: "can't make ./invalid relative to /docker/136", - }, - } { - t.Run(tc.name, func(t *testing.T) { - r := strings.NewReader(tc.cgroups) - mountinfo := strings.NewReader(tc.mountinfo) - got, err := loadPathsHelperWithMountinfo(r, mountinfo) - if len(tc.err) == 0 { - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - } else if !strings.Contains(err.Error(), tc.err) { - t.Fatalf("Wrong error message, want: *%s*, got: %v", tc.err, err) - } - for key, vWant := range tc.want { - vGot, ok := got[key] - if !ok { - t.Errorf("Missing controller %q", key) - } - if vWant != vGot { - t.Errorf("Wrong controller %q value, want: %q, got: %q", key, vWant, vGot) - } - delete(got, key) - } - for k, v := range got { - t.Errorf("Unexpected controller %q: %q", k, v) - } - }) - } -} diff --git a/runsc/cli/BUILD b/runsc/cli/BUILD deleted file mode 100644 index 32cce2a18..000000000 --- a/runsc/cli/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "cli", - srcs = ["main.go"], - visibility = [ - "//:__pkg__", - "//runsc:__pkg__", - ], - deps = [ - "//pkg/log", - "//pkg/refs", - "//pkg/sentry/platform", - "//runsc/cmd", - "//runsc/config", - "//runsc/flag", - "//runsc/specutils", - "@com_github_google_subcommands//:go_default_library", - ], -) diff --git a/runsc/cli/cli_state_autogen.go b/runsc/cli/cli_state_autogen.go new file mode 100644 index 000000000..e81991e0b --- /dev/null +++ b/runsc/cli/cli_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package cli diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD deleted file mode 100644 index 19520d7ab..000000000 --- a/runsc/cmd/BUILD +++ /dev/null @@ -1,98 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "cmd", - srcs = [ - "boot.go", - "capability.go", - "checkpoint.go", - "chroot.go", - "cmd.go", - "create.go", - "debug.go", - "delete.go", - "do.go", - "error.go", - "events.go", - "exec.go", - "gofer.go", - "help.go", - "install.go", - "kill.go", - "list.go", - "path.go", - "pause.go", - "ps.go", - "restore.go", - "resume.go", - "run.go", - "spec.go", - "start.go", - "state.go", - "statefile.go", - "symbolize.go", - "syscalls.go", - "wait.go", - ], - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/coverage", - "//pkg/log", - "//pkg/p9", - "//pkg/sentry/control", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sentry/platform", - "//pkg/state/pretty", - "//pkg/state/statefile", - "//pkg/sync", - "//pkg/unet", - "//pkg/urpc", - "//runsc/boot", - "//runsc/config", - "//runsc/console", - "//runsc/container", - "//runsc/flag", - "//runsc/fsgofer", - "//runsc/fsgofer/filter", - "//runsc/specutils", - "@com_github_google_subcommands//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "cmd_test", - size = "small", - srcs = [ - "capability_test.go", - "delete_test.go", - "exec_test.go", - "gofer_test.go", - ], - data = [ - "//runsc", - ], - library = ":cmd", - deps = [ - "//pkg/abi/linux", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/kernel/auth", - "//pkg/test/testutil", - "//pkg/urpc", - "//runsc/config", - "//runsc/container", - "//runsc/specutils", - "@com_github_google_go_cmp//cmp:go_default_library", - "@com_github_google_go_cmp//cmp/cmpopts:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - ], -) diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go deleted file mode 100644 index e13a94486..000000000 --- a/runsc/cmd/capability_test.go +++ /dev/null @@ -1,127 +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 cmd - -import ( - "flag" - "fmt" - "os" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/syndtr/gocapability/capability" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/config" - "gvisor.dev/gvisor/runsc/container" - "gvisor.dev/gvisor/runsc/specutils" -) - -func init() { - log.SetLevel(log.Debug) - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } -} - -func checkProcessCaps(pid int, wantCaps *specs.LinuxCapabilities) error { - curCaps, err := capability.NewPid2(pid) - if err != nil { - return fmt.Errorf("capability.NewPid2(%d) failed: %v", pid, err) - } - if err := curCaps.Load(); err != nil { - return fmt.Errorf("unable to load capabilities: %v", err) - } - fmt.Printf("Capabilities (PID: %d): %v\n", pid, curCaps) - - for _, c := range allCapTypes { - if err := checkCaps(c, curCaps, wantCaps); err != nil { - return err - } - } - return nil -} - -func checkCaps(which capability.CapType, curCaps capability.Capabilities, wantCaps *specs.LinuxCapabilities) error { - wantNames := getCaps(which, wantCaps) - for name, c := range capFromName { - want := specutils.ContainsStr(wantNames, name) - got := curCaps.Get(which, c) - if want != got { - if want { - return fmt.Errorf("capability %v:%s should be set", which, name) - } - return fmt.Errorf("capability %v:%s should NOT be set", which, name) - } - } - return nil -} - -func TestCapabilities(t *testing.T) { - stop := testutil.StartReaper() - defer stop() - - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - caps := []string{ - "CAP_CHOWN", - "CAP_SYS_PTRACE", // ptrace is added due to the platform choice. - } - spec.Process.Capabilities = &specs.LinuxCapabilities{ - Permitted: caps, - Bounding: caps, - Effective: caps, - Inheritable: caps, - } - - conf := testutil.TestConfig(t) - - // Use --network=host to make sandbox use spec's capabilities. - conf.Network = config.NetworkHost - - _, 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 := container.Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := container.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) - } - - // Check that sandbox and gofer have the proper capabilities. - if err := checkProcessCaps(c.Sandbox.Pid, spec.Process.Capabilities); err != nil { - t.Error(err) - } - if err := checkProcessCaps(c.GoferPid, goferCaps); err != nil { - t.Error(err) - } -} - -func TestMain(m *testing.M) { - flag.Parse() - specutils.MaybeRunAsRoot() - os.Exit(m.Run()) -} diff --git a/runsc/cmd/cmd_state_autogen.go b/runsc/cmd/cmd_state_autogen.go new file mode 100644 index 000000000..de8aa267b --- /dev/null +++ b/runsc/cmd/cmd_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package cmd diff --git a/runsc/cmd/delete_test.go b/runsc/cmd/delete_test.go deleted file mode 100644 index e2d994a05..000000000 --- a/runsc/cmd/delete_test.go +++ /dev/null @@ -1,41 +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 cmd - -import ( - "io/ioutil" - "testing" - - "gvisor.dev/gvisor/runsc/config" -) - -func TestNotFound(t *testing.T) { - ids := []string{"123"} - dir, err := ioutil.TempDir("", "metadata") - if err != nil { - t.Fatalf("error creating dir: %v", err) - } - conf := &config.Config{RootDir: dir} - - d := Delete{} - if err := d.execute(ids, conf); err == nil { - t.Error("Deleting non-existent container should have failed") - } - - d = Delete{force: true} - if err := d.execute(ids, conf); err != nil { - t.Errorf("Deleting non-existent container with --force should NOT have failed: %v", err) - } -} diff --git a/runsc/cmd/exec_test.go b/runsc/cmd/exec_test.go deleted file mode 100644 index a1e980d08..000000000 --- a/runsc/cmd/exec_test.go +++ /dev/null @@ -1,154 +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 cmd - -import ( - "os" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/urpc" -) - -func TestUser(t *testing.T) { - testCases := []struct { - input string - want user - wantErr bool - }{ - {input: "0", want: user{kuid: 0, kgid: 0}}, - {input: "7", want: user{kuid: 7, kgid: 0}}, - {input: "49:343", want: user{kuid: 49, kgid: 343}}, - {input: "0:2401", want: user{kuid: 0, kgid: 2401}}, - {input: "", wantErr: true}, - {input: "foo", wantErr: true}, - {input: ":123", wantErr: true}, - {input: "1:2:3", wantErr: true}, - } - - for _, tc := range testCases { - var u user - if err := u.Set(tc.input); err != nil && tc.wantErr { - // We got an error and wanted one. - continue - } else if err == nil && tc.wantErr { - t.Errorf("user.Set(%s): got no error, but wanted one", tc.input) - } else if err != nil && !tc.wantErr { - t.Errorf("user.Set(%s): got error %v, but wanted none", tc.input, err) - } else if u != tc.want { - t.Errorf("user.Set(%s): got %+v, but wanted %+v", tc.input, u, tc.want) - } - } -} - -func TestCLIArgs(t *testing.T) { - testCases := []struct { - ex Exec - argv []string - expected control.ExecArgs - }{ - { - ex: Exec{ - cwd: "/foo/bar", - user: user{kuid: 0, kgid: 0}, - extraKGIDs: []string{"1", "2", "3"}, - caps: []string{"CAP_DAC_OVERRIDE"}, - processPath: "", - }, - argv: []string{"ls", "/"}, - expected: control.ExecArgs{ - Argv: []string{"ls", "/"}, - WorkingDirectory: "/foo/bar", - FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}, - KUID: 0, - KGID: 0, - ExtraKGIDs: []auth.KGID{1, 2, 3}, - Capabilities: &auth.TaskCapabilities{ - BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - }, - }, - }, - } - - for _, tc := range testCases { - e, err := tc.ex.argsFromCLI(tc.argv, true) - if err != nil { - t.Errorf("argsFromCLI(%+v): got error: %+v", tc.ex, err) - } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) { - t.Errorf("argsFromCLI(%+v): got %+v, but expected %+v", tc.ex, *e, tc.expected) - } - } -} - -func TestJSONArgs(t *testing.T) { - testCases := []struct { - // ex is provided to make sure it is overridden by p. - ex Exec - p specs.Process - expected control.ExecArgs - }{ - { - ex: Exec{ - cwd: "/baz/quux", - user: user{kuid: 1, kgid: 1}, - extraKGIDs: []string{"4", "5", "6"}, - caps: []string{"CAP_SETGID"}, - processPath: "/bin/foo", - }, - p: specs.Process{ - User: specs.User{UID: 0, GID: 0, AdditionalGids: []uint32{1, 2, 3}}, - Args: []string{"ls", "/"}, - Cwd: "/foo/bar", - Capabilities: &specs.LinuxCapabilities{ - Bounding: []string{"CAP_DAC_OVERRIDE"}, - Effective: []string{"CAP_DAC_OVERRIDE"}, - Inheritable: []string{"CAP_DAC_OVERRIDE"}, - Permitted: []string{"CAP_DAC_OVERRIDE"}, - }, - }, - expected: control.ExecArgs{ - Argv: []string{"ls", "/"}, - WorkingDirectory: "/foo/bar", - FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}, - KUID: 0, - KGID: 0, - ExtraKGIDs: []auth.KGID{1, 2, 3}, - Capabilities: &auth.TaskCapabilities{ - BoundingCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - InheritableCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - PermittedCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - }, - }, - }, - } - - for _, tc := range testCases { - e, err := argsFromProcess(&tc.p, true) - if err != nil { - t.Errorf("argsFromProcess(%+v): got error: %+v", tc.p, err) - } else if !cmp.Equal(*e, tc.expected, cmpopts.IgnoreUnexported(os.File{})) { - t.Errorf("argsFromProcess(%+v): got %+v, but expected %+v", tc.p, *e, tc.expected) - } - } -} diff --git a/runsc/cmd/gofer_test.go b/runsc/cmd/gofer_test.go deleted file mode 100644 index fea62a4f4..000000000 --- a/runsc/cmd/gofer_test.go +++ /dev/null @@ -1,163 +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 cmd - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "testing" -) - -func tmpDir() string { - if dir, ok := os.LookupEnv("TEST_TMPDIR"); ok { - return dir - } - return "/tmp" -} - -type dir struct { - rel string - link string -} - -func construct(root string, dirs []dir) error { - for _, d := range dirs { - p := path.Join(root, d.rel) - if d.link == "" { - if err := os.MkdirAll(p, 0755); err != nil { - return fmt.Errorf("error creating dir: %v", err) - } - } else { - if err := os.MkdirAll(path.Dir(p), 0755); err != nil { - return fmt.Errorf("error creating dir: %v", err) - } - if err := os.Symlink(d.link, p); err != nil { - return fmt.Errorf("error creating symlink: %v", err) - } - } - } - return nil -} - -func TestResolveSymlinks(t *testing.T) { - root, err := ioutil.TempDir(tmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - dirs := []dir{ - {"dir1/dir11/dir111/dir1111", ""}, // Just a boring dir - {"dir1/lnk12", "dir11"}, // Link to sibling - {"dir1/lnk13", "./dir11"}, // Link to sibling through self - {"dir1/lnk14", "../dir1/dir11"}, // Link to sibling through parent - {"dir1/dir15/lnk151", ".."}, // Link to parent - {"dir1/lnk16", "dir11/dir111"}, // Link to child - {"dir1/lnk17", "."}, // Link to self - {"dir1/lnk18", "lnk13"}, // Link to link - {"lnk2", "dir1/lnk13"}, // Link to link to link - {"dir3/dir21/lnk211", "../.."}, // Link to root relative - {"dir3/lnk22", "/"}, // Link to root absolute - {"dir3/lnk23", "/dir1"}, // Link to dir absolute - {"dir3/lnk24", "/dir1/lnk12"}, // Link to link absolute - {"lnk5", "../../.."}, // Link outside root - } - if err := construct(root, dirs); err != nil { - t.Fatal("construct failed:", err) - } - - tests := []struct { - name string - rel string - want string - compareHost bool - }{ - {name: "root", rel: "/", want: "/", compareHost: true}, - {name: "basic dir", rel: "/dir1/dir11/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dot 1", rel: "/dir1/dir11/./dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dot 2", rel: "/dir1/././dir11/./././././dir111/.", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "dotdot 1", rel: "/dir1/dir11/../dir15", want: "/dir1/dir15", compareHost: true}, - {name: "dotdot 2", rel: "/dir1/dir11/dir1111/../..", want: "/dir1", compareHost: true}, - - {name: "link sibling", rel: "/dir1/lnk12", want: "/dir1/dir11", compareHost: true}, - {name: "link sibling + dir", rel: "/dir1/lnk12/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link sibling through self", rel: "/dir1/lnk13", want: "/dir1/dir11", compareHost: true}, - {name: "link sibling through parent", rel: "/dir1/lnk14", want: "/dir1/dir11", compareHost: true}, - - {name: "link parent", rel: "/dir1/dir15/lnk151", want: "/dir1", compareHost: true}, - {name: "link parent + dir", rel: "/dir1/dir15/lnk151/dir11", want: "/dir1/dir11", compareHost: true}, - {name: "link child", rel: "/dir1/lnk16", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link child + dir", rel: "/dir1/lnk16/dir1111", want: "/dir1/dir11/dir111/dir1111", compareHost: true}, - {name: "link self", rel: "/dir1/lnk17", want: "/dir1", compareHost: true}, - {name: "link self + dir", rel: "/dir1/lnk17/dir11", want: "/dir1/dir11", compareHost: true}, - - {name: "link^2", rel: "/dir1/lnk18", want: "/dir1/dir11", compareHost: true}, - {name: "link^2 + dir", rel: "/dir1/lnk18/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - {name: "link^3", rel: "/lnk2", want: "/dir1/dir11", compareHost: true}, - {name: "link^3 + dir", rel: "/lnk2/dir111", want: "/dir1/dir11/dir111", compareHost: true}, - - {name: "link abs", rel: "/dir3/lnk23", want: "/dir1"}, - {name: "link abs + dir", rel: "/dir3/lnk23/dir11", want: "/dir1/dir11"}, - {name: "link^2 abs", rel: "/dir3/lnk24", want: "/dir1/dir11"}, - {name: "link^2 abs + dir", rel: "/dir3/lnk24/dir111", want: "/dir1/dir11/dir111"}, - - {name: "root link rel", rel: "/dir3/dir21/lnk211", want: "/", compareHost: true}, - {name: "root link abs", rel: "/dir3/lnk22", want: "/"}, - {name: "root contain link", rel: "/lnk5/dir1", want: "/dir1"}, - {name: "root contain dotdot", rel: "/dir1/dir11/../../../../../../../..", want: "/"}, - - {name: "crazy", rel: "/dir3/dir21/lnk211/dir3/lnk22/dir1/dir11/../../lnk5/dir3/../dir3/lnk24/dir111/dir1111/..", want: "/dir1/dir11/dir111"}, - } - for _, tst := range tests { - t.Run(tst.name, func(t *testing.T) { - got, err := resolveSymlinks(root, tst.rel) - if err != nil { - t.Errorf("resolveSymlinks(root, %q) failed: %v", tst.rel, err) - } - want := path.Join(root, tst.want) - if got != want { - t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, got, want) - } - if tst.compareHost { - // Check that host got to the same end result. - host, err := filepath.EvalSymlinks(path.Join(root, tst.rel)) - if err != nil { - t.Errorf("path.EvalSymlinks(root, %q) failed: %v", tst.rel, err) - } - if host != got { - t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, host, got) - } - } - }) - } -} - -func TestResolveSymlinksLoop(t *testing.T) { - root, err := ioutil.TempDir(tmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - dirs := []dir{ - {"loop1", "loop2"}, - {"loop2", "loop1"}, - } - if err := construct(root, dirs); err != nil { - t.Fatal("construct failed:", err) - } - if _, err := resolveSymlinks(root, "loop1"); err == nil { - t.Errorf("resolveSymlinks() should have failed") - } -} diff --git a/runsc/config/BUILD b/runsc/config/BUILD deleted file mode 100644 index b1672bb9d..000000000 --- a/runsc/config/BUILD +++ /dev/null @@ -1,28 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "config", - srcs = [ - "config.go", - "flags.go", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/refs", - "//pkg/sentry/watchdog", - "//pkg/sync", - "//runsc/flag", - ], -) - -go_test( - name = "config_test", - size = "small", - srcs = [ - "config_test.go", - ], - library = ":config", - deps = ["//runsc/flag"], -) diff --git a/runsc/config/config_state_autogen.go b/runsc/config/config_state_autogen.go new file mode 100644 index 000000000..92fa90265 --- /dev/null +++ b/runsc/config/config_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package config diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go deleted file mode 100644 index fb162b7eb..000000000 --- a/runsc/config/config_test.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2020 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 config - -import ( - "strings" - "testing" - - "gvisor.dev/gvisor/runsc/flag" -) - -func init() { - RegisterFlags() -} - -func TestDefault(t *testing.T) { - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - // "--root" is always set to something different than the default. Reset it - // to make it easier to test that default values do not generate flags. - c.RootDir = "" - - // All defaults doesn't require setting flags. - flags := c.ToFlags() - if len(flags) > 0 { - t.Errorf("default flags not set correctly for: %s", flags) - } -} - -func setDefault(name string) { - fl := flag.CommandLine.Lookup(name) - fl.Value.Set(fl.DefValue) -} - -func TestFromFlags(t *testing.T) { - flag.CommandLine.Lookup("root").Value.Set("some-path") - flag.CommandLine.Lookup("debug").Value.Set("true") - flag.CommandLine.Lookup("num-network-channels").Value.Set("123") - flag.CommandLine.Lookup("network").Value.Set("none") - defer func() { - setDefault("root") - setDefault("debug") - setDefault("num-network-channels") - setDefault("network") - }() - - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - if want := "some-path"; c.RootDir != want { - t.Errorf("RootDir=%v, want: %v", c.RootDir, want) - } - if want := true; c.Debug != want { - t.Errorf("Debug=%v, want: %v", c.Debug, want) - } - if want := 123; c.NumNetworkChannels != want { - t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want) - } - if want := NetworkNone; c.Network != want { - t.Errorf("Network=%v, want: %v", c.Network, want) - } -} - -func TestToFlags(t *testing.T) { - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - c.RootDir = "some-path" - c.Debug = true - c.NumNetworkChannels = 123 - c.Network = NetworkNone - - flags := c.ToFlags() - if len(flags) != 4 { - t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags) - } - t.Logf("Flags: %s", flags) - fm := map[string]string{} - for _, f := range flags { - kv := strings.Split(f, "=") - fm[kv[0]] = kv[1] - } - for name, want := range map[string]string{ - "--root": "some-path", - "--debug": "true", - "--num-network-channels": "123", - "--network": "none", - } { - if got, ok := fm[name]; ok { - if got != want { - t.Errorf("flag %q, want: %q, got: %q", name, want, got) - } - } else { - t.Errorf("flag %q not set", name) - } - } -} - -// TestInvalidFlags checks that enum flags fail when value is not in enum set. -func TestInvalidFlags(t *testing.T) { - for _, tc := range []struct { - name string - error string - }{ - { - name: "file-access", - error: "invalid file access type", - }, - { - name: "network", - error: "invalid network type", - }, - { - name: "qdisc", - error: "invalid qdisc", - }, - { - name: "watchdog-action", - error: "invalid watchdog action", - }, - { - name: "ref-leak-mode", - error: "invalid ref leak mode", - }, - } { - t.Run(tc.name, func(t *testing.T) { - defer setDefault(tc.name) - if err := flag.CommandLine.Lookup(tc.name).Value.Set("invalid"); err == nil || !strings.Contains(err.Error(), tc.error) { - t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err) - } - }) - } -} - -func TestValidationFail(t *testing.T) { - for _, tc := range []struct { - name string - flags map[string]string - error string - }{ - { - name: "shared+overlay", - flags: map[string]string{ - "file-access": "shared", - "overlay": "true", - }, - error: "overlay flag is incompatible", - }, - { - name: "network-channels", - flags: map[string]string{ - "num-network-channels": "-1", - }, - error: "num_network_channels must be > 0", - }, - } { - t.Run(tc.name, func(t *testing.T) { - for name, val := range tc.flags { - defer setDefault(name) - if err := flag.CommandLine.Lookup(name).Value.Set(val); err != nil { - t.Errorf("%s=%q: %v", name, val, err) - } - } - if _, err := NewFromFlags(); err == nil || !strings.Contains(err.Error(), tc.error) { - t.Errorf("NewFromFlags() wrong error reported: %v", err) - } - }) - } -} - -func TestOverride(t *testing.T) { - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - c.AllowFlagOverride = true - - t.Run("string", func(t *testing.T) { - c.RootDir = "foobar" - if err := c.Override("root", "bar"); err != nil { - t.Fatalf("Override(root, bar) failed: %v", err) - } - defer setDefault("root") - if c.RootDir != "bar" { - t.Errorf("Override(root, bar) didn't work: %+v", c) - } - }) - - t.Run("bool", func(t *testing.T) { - c.Debug = true - if err := c.Override("debug", "false"); err != nil { - t.Fatalf("Override(debug, false) failed: %v", err) - } - defer setDefault("debug") - if c.Debug { - t.Errorf("Override(debug, false) didn't work: %+v", c) - } - }) - - t.Run("enum", func(t *testing.T) { - c.FileAccess = FileAccessShared - if err := c.Override("file-access", "exclusive"); err != nil { - t.Fatalf("Override(file-access, exclusive) failed: %v", err) - } - defer setDefault("file-access") - if c.FileAccess != FileAccessExclusive { - t.Errorf("Override(file-access, exclusive) didn't work: %+v", c) - } - }) -} - -func TestOverrideDisabled(t *testing.T) { - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - const errMsg = "flag override disabled" - if err := c.Override("root", "path"); err == nil || !strings.Contains(err.Error(), errMsg) { - t.Errorf("Override() wrong error: %v", err) - } -} - -func TestOverrideError(t *testing.T) { - c, err := NewFromFlags() - if err != nil { - t.Fatal(err) - } - c.AllowFlagOverride = true - for _, tc := range []struct { - name string - value string - error string - }{ - { - name: "invalid", - value: "valid", - error: `flag "invalid" not found`, - }, - { - name: "debug", - value: "invalid", - error: "error setting flag debug", - }, - { - name: "file-access", - value: "invalid", - error: "invalid file access type", - }, - } { - t.Run(tc.name, func(t *testing.T) { - if err := c.Override(tc.name, tc.value); err == nil || !strings.Contains(err.Error(), tc.error) { - t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err) - } - }) - } -} diff --git a/runsc/console/BUILD b/runsc/console/BUILD deleted file mode 100644 index 06924bccd..000000000 --- a/runsc/console/BUILD +++ /dev/null @@ -1,17 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "console", - srcs = [ - "console.go", - ], - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "@com_github_kr_pty//:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/console/console_state_autogen.go b/runsc/console/console_state_autogen.go new file mode 100644 index 000000000..80521cdb7 --- /dev/null +++ b/runsc/console/console_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package console diff --git a/runsc/container/BUILD b/runsc/container/BUILD deleted file mode 100644 index 8793c8916..000000000 --- a/runsc/container/BUILD +++ /dev/null @@ -1,77 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test", "more_shards") - -package(licenses = ["notice"]) - -go_library( - name = "container", - srcs = [ - "container.go", - "hook.go", - "state_file.go", - "status.go", - ], - visibility = [ - "//runsc:__subpackages__", - "//test:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/cleanup", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/sighandling", - "//pkg/sync", - "//runsc/boot", - "//runsc/cgroup", - "//runsc/config", - "//runsc/console", - "//runsc/sandbox", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_gofrs_flock//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - ], -) - -go_test( - name = "container_test", - size = "large", - srcs = [ - "console_test.go", - "container_norace_test.go", - "container_race_test.go", - "container_test.go", - "multi_container_test.go", - "shared_volume_test.go", - ], - data = [ - "//runsc", - "//test/cmd/test_app", - ], - library = ":container", - shard_count = more_shards, - tags = [ - "requires-kvm", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/bits", - "//pkg/cleanup", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/kernel", - "//pkg/sentry/kernel/auth", - "//pkg/sync", - "//pkg/test/testutil", - "//pkg/unet", - "//pkg/urpc", - "//runsc/boot", - "//runsc/boot/platforms", - "//runsc/config", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_kr_pty//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/container/console_test.go b/runsc/container/console_test.go deleted file mode 100644 index 7a3d5a523..000000000 --- a/runsc/container/console_test.go +++ /dev/null @@ -1,658 +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 container - -import ( - "bytes" - "fmt" - "io" - "math/rand" - "os" - "path/filepath" - "syscall" - "testing" - "time" - - "github.com/kr/pty" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/pkg/unet" - "gvisor.dev/gvisor/pkg/urpc" -) - -// socketPath creates a path inside bundleDir and ensures that the returned -// path is under 108 charactors (the unix socket path length limit), -// relativizing the path if necessary. -func socketPath(bundleDir string) (string, error) { - num := rand.Intn(10000) - path := filepath.Join(bundleDir, fmt.Sprintf("socket-%4d", num)) - const maxPathLen = 108 - if len(path) <= maxPathLen { - return path, nil - } - - // Path is too large, try to make it smaller. - cwd, err := os.Getwd() - if err != nil { - return "", fmt.Errorf("error getting cwd: %v", err) - } - path, err = filepath.Rel(cwd, path) - if err != nil { - return "", fmt.Errorf("error getting relative path for %q from cwd %q: %v", path, cwd, err) - } - if len(path) > maxPathLen { - return "", fmt.Errorf("could not get socket path under length limit %d: %s", maxPathLen, path) - } - return path, nil -} - -// createConsoleSocket creates a socket at the given path that will receive a -// console fd from the sandbox. If an error occurs, t.Fatalf will be called. -// The function returning should be deferred as cleanup. -func createConsoleSocket(t *testing.T, path string) (*unet.ServerSocket, func()) { - t.Helper() - srv, err := unet.BindAndListen(path, false) - if err != nil { - t.Fatalf("error binding and listening to socket %q: %v", path, err) - } - - cleanup := func() { - // Log errors; nothing can be done. - if err := srv.Close(); err != nil { - t.Logf("error closing socket %q: %v", path, err) - } - if err := os.Remove(path); err != nil { - t.Logf("error removing socket %q: %v", path, err) - } - } - - return srv, cleanup -} - -// receiveConsolePTY accepts a connection on the server socket and reads fds. -// It fails if more than one FD is received, or if the FD is not a PTY. It -// returns the PTY master file. -func receiveConsolePTY(srv *unet.ServerSocket) (*os.File, error) { - sock, err := srv.Accept() - if err != nil { - return nil, fmt.Errorf("error accepting socket connection: %v", err) - } - - // Allow 3 fds to be received. We only expect 1. - r := sock.Reader(true /* blocking */) - r.EnableFDs(1) - - // The socket is closed right after sending the FD, so EOF is - // an allowed error. - b := [][]byte{{}} - if _, err := r.ReadVec(b); err != nil && err != io.EOF { - return nil, fmt.Errorf("error reading from socket connection: %v", err) - } - - // We should have gotten a control message. - fds, err := r.ExtractFDs() - if err != nil { - return nil, fmt.Errorf("error extracting fds from socket connection: %v", err) - } - if len(fds) != 1 { - return nil, fmt.Errorf("got %d fds from socket, wanted 1", len(fds)) - } - - // Verify that the fd is a terminal. - if _, err := unix.IoctlGetTermios(fds[0], unix.TCGETS); err != nil { - return nil, fmt.Errorf("fd is not a terminal (ioctl TGGETS got %v)", err) - } - - return os.NewFile(uintptr(fds[0]), "pty_master"), nil -} - -// Test that an pty FD is sent over the console socket if one is provided. -func TestConsoleSocket(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - spec := testutil.NewSpecWithArgs("true") - spec.Process.Terminal = true - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup := createConsoleSocket(t, sock) - defer cleanup() - - // Create the container and pass the socket name. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - ConsoleSocket: sock, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - - // Make sure we get a console PTY. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - ptyMaster.Close() - }) - } -} - -// Test that an pty FD is sent over the console socket if one is provided. -func TestMultiContainerConsoleSocket(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - tru := []string{"true"} - testSpecs, ids := createSpecs(sleep, tru) - testSpecs[1].Process.Terminal = true - - bundleDir, cleanup, err := testutil.SetupBundleDir(testSpecs[0]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: ids[0], - Spec: testSpecs[0], - BundleDir: bundleDir, - } - rootCont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer rootCont.Destroy() - if err := rootCont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - bundleDir, cleanup, err = testutil.SetupBundleDir(testSpecs[0]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup := createConsoleSocket(t, sock) - defer cleanup() - - // Create the container and pass the socket name. - args = Args{ - ID: ids[1], - Spec: testSpecs[1], - BundleDir: bundleDir, - ConsoleSocket: sock, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Make sure we get a console PTY. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - ptyMaster.Close() - }) - } -} - -// Test that job control signals work on a console created with "exec -ti". -func TestJobControlSignalExec(t *testing.T) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - conf := testutil.TestConfig(t) - - _, 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) - } - - // Create a pty master/replica. The replica will be passed to the exec - // process. - ptyMaster, ptyReplica, err := pty.Open() - if err != nil { - t.Fatalf("error opening pty: %v", err) - } - defer ptyMaster.Close() - defer ptyReplica.Close() - - // Exec bash and attach a terminal. Note that occasionally /bin/sh - // may be a different shell or have a different configuration (such - // as disabling interactive mode and job control). Since we want to - // explicitly test interactive mode, use /bin/bash. See b/116981926. - execArgs := &control.ExecArgs{ - Filename: "/bin/bash", - // Don't let bash execute from profile or rc files, otherwise - // our PID counts get messed up. - Argv: []string{"/bin/bash", "--noprofile", "--norc"}, - // Pass the pty replica as FD 0, 1, and 2. - FilePayload: urpc.FilePayload{ - Files: []*os.File{ptyReplica, ptyReplica, ptyReplica}, - }, - StdioIsPty: true, - } - - pid, err := c.Execute(execArgs) - if err != nil { - t.Fatalf("error executing: %v", err) - } - if pid != 2 { - t.Fatalf("exec got pid %d, wanted %d", pid, 2) - } - - // Make sure all the processes are running. - expectedPL := []*control.Process{ - // Root container process. - newProcessBuilder().Cmd("sleep").Process(), - // Bash from exec process. - newProcessBuilder().PID(2).Cmd("bash").Process(), - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Execute sleep. - ptyMaster.Write([]byte("sleep 100\n")) - - // Wait for it to start. Sleep's PPID is bash's PID. - expectedPL = append(expectedPL, newProcessBuilder().PID(3).PPID(2).Cmd("sleep").Process()) - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Send a SIGTERM to the foreground process for the exec PID. Note that - // although we pass in the PID of "bash", it should actually terminate - // "sleep", since that is the foreground process. - if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGTERM, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Sleep process should be gone. - expectedPL = expectedPL[:len(expectedPL)-1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Sleep is dead, but it may take more time for bash to notice and - // change the foreground process back to itself. We know it is done - // when bash writes "Terminated" to the pty. - if err := testutil.WaitUntilRead(ptyMaster, "Terminated", 5*time.Second); err != nil { - t.Fatalf("bash did not take over pty: %v", err) - } - - // Send a SIGKILL to the foreground process again. This time "bash" - // should be killed. We use SIGKILL instead of SIGTERM or SIGINT - // because bash ignores those. - if err := c.Sandbox.SignalProcess(c.ID, pid, syscall.SIGKILL, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - expectedPL = expectedPL[:1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Make sure the process indicates it was killed by a SIGKILL. - ws, err := c.WaitPID(pid) - if err != nil { - t.Errorf("waiting on container failed: %v", err) - } - if !ws.Signaled() { - t.Error("ws.Signaled() got false, want true") - } - if got, want := ws.Signal(), syscall.SIGKILL; got != want { - t.Errorf("ws.Signal() got %v, want %v", got, want) - } -} - -// Test that job control signals work on a console created with "run -ti". -func TestJobControlSignalRootContainer(t *testing.T) { - conf := testutil.TestConfig(t) - // Don't let bash execute from profile or rc files, otherwise our PID - // counts get messed up. - spec := testutil.NewSpecWithArgs("/bin/bash", "--noprofile", "--norc") - spec.Process.Terminal = true - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup := createConsoleSocket(t, sock) - defer cleanup() - - // Create the container and pass the socket name. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - ConsoleSocket: sock, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer c.Destroy() - - // Get the PTY master. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - defer ptyMaster.Close() - - // Bash output as well as sandbox output will be written to the PTY - // file. Writes after a certain point will block unless we drain the - // PTY, so we must continually copy from it. - // - // We log the output to stderr for debugabilitly, and also to a buffer, - // since we wait on particular output from bash below. We use a custom - // blockingBuffer which is thread-safe and also blocks on Read calls, - // which makes this a suitable Reader for WaitUntilRead. - ptyBuf := newBlockingBuffer() - tee := io.TeeReader(ptyMaster, ptyBuf) - go io.Copy(os.Stderr, tee) - - // Start the container. - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Start waiting for the container to exit in a goroutine. We do this - // very early, otherwise it might exit before we have a chance to call - // Wait. - var ( - ws syscall.WaitStatus - wg sync.WaitGroup - ) - wg.Add(1) - go func() { - var err error - ws, err = c.Wait() - if err != nil { - t.Errorf("error waiting on container: %v", err) - } - wg.Done() - }() - - // Wait for bash to start. - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).Cmd("bash").Process(), - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - // Execute sleep via the terminal. - ptyMaster.Write([]byte("sleep 100\n")) - - // Wait for sleep to start. - expectedPL = append(expectedPL, newProcessBuilder().PID(2).PPID(1).Cmd("sleep").Process()) - if err := waitForProcessList(c, expectedPL); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - // Reset the pty buffer, so there is less output for us to scan later. - ptyBuf.Reset() - - // Send a SIGTERM to the foreground process. We pass PID=0, indicating - // that the root process should be killed. However, by setting - // fgProcess=true, the signal should actually be sent to sleep. - if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGTERM, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Sleep process should be gone. - expectedPL = expectedPL[:len(expectedPL)-1] - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Sleep is dead, but it may take more time for bash to notice and - // change the foreground process back to itself. We know it is done - // when bash writes "Terminated" to the pty. - if err := testutil.WaitUntilRead(ptyBuf, "Terminated", 5*time.Second); err != nil { - t.Fatalf("bash did not take over pty: %v", err) - } - - // Send a SIGKILL to the foreground process again. This time "bash" - // should be killed. We use SIGKILL instead of SIGTERM or SIGINT - // because bash ignores those. - if err := c.Sandbox.SignalProcess(c.ID, 0 /* PID */, syscall.SIGKILL, true /* fgProcess */); err != nil { - t.Fatalf("error signaling container: %v", err) - } - - // Wait for the sandbox to exit. It should exit with a SIGKILL status. - wg.Wait() - if !ws.Signaled() { - t.Error("ws.Signaled() got false, want true") - } - if got, want := ws.Signal(), syscall.SIGKILL; got != want { - t.Errorf("ws.Signal() got %v, want %v", got, want) - } -} - -// Test that terminal works with root and sub-containers. -func TestMultiContainerTerminal(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Don't let bash execute from profile or rc files, otherwise our PID - // counts get messed up. - bash := []string{"/bin/bash", "--noprofile", "--norc"} - testSpecs, ids := createSpecs(bash, bash) - - type termContainer struct { - container *Container - master *os.File - } - var containers []termContainer - for i, spec := range testSpecs { - bundleDir, cleanup, err := testutil.SetupBundleDir(spec) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - spec.Process.Terminal = true - sock, err := socketPath(bundleDir) - if err != nil { - t.Fatalf("error getting socket path: %v", err) - } - srv, cleanup := createConsoleSocket(t, sock) - defer cleanup() - - // Create the container and pass the socket name. - args := Args{ - ID: ids[i], - Spec: spec, - BundleDir: bundleDir, - ConsoleSocket: sock, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Make sure we get a console PTY. - ptyMaster, err := receiveConsolePTY(srv) - if err != nil { - t.Fatalf("error receiving console FD: %v", err) - } - defer ptyMaster.Close() - - containers = append(containers, termContainer{ - container: cont, - master: ptyMaster, - }) - } - - for _, tc := range containers { - // Bash output as well as sandbox output will be written to the PTY - // file. Writes after a certain point will block unless we drain the - // PTY, so we must continually copy from it. - // - // We log the output to stderr for debugabilitly, and also to a buffer, - // since we wait on particular output from bash below. We use a custom - // blockingBuffer which is thread-safe and also blocks on Read calls, - // which makes this a suitable Reader for WaitUntilRead. - ptyBuf := newBlockingBuffer() - tee := io.TeeReader(tc.master, ptyBuf) - go io.Copy(os.Stderr, tee) - - // Wait for bash to start. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("bash").Process(), - } - if err := waitForProcessList(tc.container, expectedPL); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - // Execute echo command and check that it was executed correctly. Use - // a variable to ensure it's not matching against command echo. - tc.master.Write([]byte("echo foo-${PWD}-123\n")) - if err := testutil.WaitUntilRead(ptyBuf, "foo-/-123", 5*time.Second); err != nil { - t.Fatalf("echo didn't execute: %v", err) - } - } - }) - } -} - -// blockingBuffer is a thread-safe buffer that blocks when reading if the -// buffer is empty. It implements io.ReadWriter. -type blockingBuffer struct { - // A send to readCh indicates that a previously empty buffer now has - // data for reading. - readCh chan struct{} - - // mu protects buf. - mu sync.Mutex - buf bytes.Buffer -} - -func newBlockingBuffer() *blockingBuffer { - return &blockingBuffer{ - readCh: make(chan struct{}, 1), - } -} - -// Write implements Writer.Write. -func (bb *blockingBuffer) Write(p []byte) (int, error) { - bb.mu.Lock() - defer bb.mu.Unlock() - l := bb.buf.Len() - n, err := bb.buf.Write(p) - if l == 0 && n > 0 { - // New data! - bb.readCh <- struct{}{} - } - return n, err -} - -// Read implements Reader.Read. It will block until data is available. -func (bb *blockingBuffer) Read(p []byte) (int, error) { - for { - bb.mu.Lock() - n, err := bb.buf.Read(p) - if n > 0 || err != io.EOF { - if bb.buf.Len() == 0 { - // Reset the readCh. - select { - case <-bb.readCh: - default: - } - } - bb.mu.Unlock() - return n, err - } - bb.mu.Unlock() - - // Wait for new data. - <-bb.readCh - } -} - -// Reset resets the buffer. -func (bb *blockingBuffer) Reset() { - bb.mu.Lock() - defer bb.mu.Unlock() - bb.buf.Reset() - // Reset the readCh. - select { - case <-bb.readCh: - default: - } -} diff --git a/runsc/container/container_norace_test.go b/runsc/container/container_norace_test.go deleted file mode 100644 index 838c1e20a..000000000 --- a/runsc/container/container_norace_test.go +++ /dev/null @@ -1,20 +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. - -// +build !race - -package container - -// Allow both kvm and ptrace for non-race builds. -var platformOptions = []configOption{ptrace, kvm} diff --git a/runsc/container/container_race_test.go b/runsc/container/container_race_test.go deleted file mode 100644 index 9fb4c4fc0..000000000 --- a/runsc/container/container_race_test.go +++ /dev/null @@ -1,20 +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. - -// +build race - -package container - -// Only enabled ptrace with race builds. -var platformOptions = []configOption{ptrace} diff --git a/runsc/container/container_state_autogen.go b/runsc/container/container_state_autogen.go new file mode 100644 index 000000000..5bc1c1aff --- /dev/null +++ b/runsc/container/container_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package container diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go deleted file mode 100644 index d50bbcd9f..000000000 --- a/runsc/container/container_test.go +++ /dev/null @@ -1,2447 +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 container - -import ( - "bytes" - "flag" - "fmt" - "io" - "io/ioutil" - "math" - "os" - "path" - "path/filepath" - "reflect" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "github.com/cenkalti/backoff" - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/bits" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sentry/kernel/auth" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/pkg/urpc" - "gvisor.dev/gvisor/runsc/boot/platforms" - "gvisor.dev/gvisor/runsc/config" - "gvisor.dev/gvisor/runsc/specutils" -) - -// waitForProcessList waits for the given process list to show up in the container. -func waitForProcessList(cont *Container, want []*control.Process) error { - cb := func() error { - got, err := cont.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %w", err) - return &backoff.PermanentError{Err: err} - } - if !procListsEqual(got, want) { - return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want)) - } - return nil - } - // Gives plenty of time as tests can run slow under --race. - return testutil.Poll(cb, 30*time.Second) -} - -// waitForProcess waits for the given process to show up in the container. -func waitForProcess(cont *Container, want *control.Process) error { - cb := func() error { - gots, err := cont.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %w", err) - return &backoff.PermanentError{Err: err} - } - for _, got := range gots { - if procEqual(got, want) { - return nil - } - } - return fmt.Errorf("container got process list: %s, want: %+v", procListToString(gots), want) - } - // Gives plenty of time as tests can run slow under --race. - return testutil.Poll(cb, 30*time.Second) -} - -func waitForProcessCount(cont *Container, want int) error { - cb := func() error { - pss, err := cont.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %w", err) - return &backoff.PermanentError{Err: err} - } - if got := len(pss); got != want { - log.Infof("Waiting for process count to reach %d. Current: %d", want, got) - return fmt.Errorf("wrong process count, got: %d, want: %d", got, want) - } - return nil - } - // Gives plenty of time as tests can run slow under --race. - return testutil.Poll(cb, 30*time.Second) -} - -func blockUntilWaitable(pid int) error { - _, _, err := specutils.RetryEintr(func() (uintptr, uintptr, error) { - var err error - _, _, err1 := syscall.Syscall6(syscall.SYS_WAITID, 1, uintptr(pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0) - if err1 != 0 { - err = err1 - } - return 0, 0, err - }) - return err -} - -// procListsEqual is used to check whether 2 Process lists are equal. Fields -// set to -1 in wants are ignored. Timestamp and threads fields are always -// ignored. -func procListsEqual(gots, wants []*control.Process) bool { - if len(gots) != len(wants) { - return false - } - for i := range gots { - if !procEqual(gots[i], wants[i]) { - return false - } - } - return true -} - -func procEqual(got, want *control.Process) bool { - if want.UID != math.MaxUint32 && want.UID != got.UID { - return false - } - if want.PID != -1 && want.PID != got.PID { - return false - } - if want.PPID != -1 && want.PPID != got.PPID { - return false - } - if len(want.TTY) != 0 && want.TTY != got.TTY { - return false - } - if len(want.Cmd) != 0 && want.Cmd != got.Cmd { - return false - } - return true -} - -type processBuilder struct { - process control.Process -} - -func newProcessBuilder() *processBuilder { - return &processBuilder{ - process: control.Process{ - UID: math.MaxUint32, - PID: -1, - PPID: -1, - }, - } -} - -func (p *processBuilder) Cmd(cmd string) *processBuilder { - p.process.Cmd = cmd - return p -} - -func (p *processBuilder) PID(pid kernel.ThreadID) *processBuilder { - p.process.PID = pid - return p -} - -func (p *processBuilder) PPID(ppid kernel.ThreadID) *processBuilder { - p.process.PPID = ppid - return p -} - -func (p *processBuilder) UID(uid auth.KUID) *processBuilder { - p.process.UID = uid - return p -} - -func (p *processBuilder) Process() *control.Process { - return &p.process -} - -func procListToString(pl []*control.Process) string { - strs := make([]string, 0, len(pl)) - for _, p := range pl { - strs = append(strs, fmt.Sprintf("%+v", p)) - } - return fmt.Sprintf("[%s]", strings.Join(strs, ",")) -} - -// createWriteableOutputFile creates an output file that can be read and -// written to in the sandbox. -func createWriteableOutputFile(path string) (*os.File, error) { - outputFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - return nil, fmt.Errorf("error creating file: %q, %v", path, err) - } - - // Chmod to allow writing after umask. - if err := outputFile.Chmod(0666); err != nil { - return nil, fmt.Errorf("error chmoding file: %q, %v", path, err) - } - return outputFile, nil -} - -func waitForFileNotEmpty(f *os.File) error { - op := func() error { - fi, err := f.Stat() - if err != nil { - return err - } - if fi.Size() == 0 { - return fmt.Errorf("file %q is empty", f.Name()) - } - return nil - } - - return testutil.Poll(op, 30*time.Second) -} - -func waitForFileExist(path string) error { - op := func() error { - if _, err := os.Stat(path); os.IsNotExist(err) { - return err - } - return nil - } - - return testutil.Poll(op, 30*time.Second) -} - -// readOutputNum reads a file at given filepath and returns the int at the -// requested position. -func readOutputNum(file string, position int) (int, error) { - f, err := os.Open(file) - if err != nil { - return 0, fmt.Errorf("error opening file: %q, %v", file, err) - } - - // Ensure that there is content in output file. - if err := waitForFileNotEmpty(f); err != nil { - return 0, fmt.Errorf("error waiting for output file: %v", err) - } - - b, err := ioutil.ReadAll(f) - if err != nil { - return 0, fmt.Errorf("error reading file: %v", err) - } - if len(b) == 0 { - return 0, fmt.Errorf("error no content was read") - } - - // Strip leading null bytes caused by file offset not being 0 upon restore. - b = bytes.Trim(b, "\x00") - nums := strings.Split(string(b), "\n") - - if position >= len(nums) { - return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums) - } - if position == -1 { - // Expectation of newline at the end of last position. - position = len(nums) - 2 - } - num, err := strconv.Atoi(nums[position]) - if err != nil { - return 0, fmt.Errorf("error getting number from file: %v", err) - } - return num, nil -} - -// run starts the sandbox and waits for it to exit, checking that the -// application succeeded. -func run(spec *specs.Spec, conf *config.Config) error { - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - return fmt.Errorf("error setting up container: %v", err) - } - defer cleanup() - - // Create, start and wait for the container. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - return fmt.Errorf("running container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - return fmt.Errorf("container failed, waitStatus: %v", ws) - } - return nil -} - -type configOption int - -const ( - overlay configOption = iota - ptrace - kvm - nonExclusiveFS -) - -var ( - noOverlay = append(platformOptions, nonExclusiveFS) - all = append(noOverlay, overlay) -) - -func configsHelper(t *testing.T, opts ...configOption) map[string]*config.Config { - // Always load the default config. - cs := make(map[string]*config.Config) - testutil.TestConfig(t) - for _, o := range opts { - c := testutil.TestConfig(t) - switch o { - case overlay: - c.Overlay = true - cs["overlay"] = c - case ptrace: - c.Platform = platforms.Ptrace - cs["ptrace"] = c - case kvm: - c.Platform = platforms.KVM - cs["kvm"] = c - case nonExclusiveFS: - c.FileAccess = config.FileAccessShared - cs["non-exclusive"] = c - default: - panic(fmt.Sprintf("unknown config option %v", o)) - } - } - return cs -} - -// configs generates different configurations to run tests. -// -// TODO(gvisor.dev/issue/1624): Remove VFS1 dimension. -func configs(t *testing.T, opts ...configOption) map[string]*config.Config { - all := configsHelper(t, opts...) - for key, value := range configsHelper(t, opts...) { - value.VFS2 = true - all[key+"VFS2"] = value - } - return all -} - -// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle. -// It verifies after each step that the container can be loaded from disk, and -// has the correct status. -func TestLifecycle(t *testing.T) { - // Start the child reaper. - childReaper := &testutil.Reaper{} - childReaper.Start() - defer childReaper.Stop() - - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - // The container will just sleep for a long time. We will kill it before - // it finishes sleeping. - spec := testutil.NewSpecWithArgs("sleep", "100") - - rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").Process(), - } - // Create 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() - - // Load the container from disk and check the status. - c, err = Load(rootDir, FullID{ContainerID: args.ID}, LoadOpts{}) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Created; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // List should return the container id. - ids, err := List(rootDir) - if err != nil { - t.Fatalf("error listing containers: %v", err) - } - fullID := FullID{ - SandboxID: args.ID, - ContainerID: args.ID, - } - if got, want := ids, []FullID{fullID}; !reflect.DeepEqual(got, want) { - t.Errorf("container list got %v, want %v", got, want) - } - - // Start the container. - if err := c.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Load the container from disk and check the status. - c, err = Load(rootDir, fullID, LoadOpts{Exact: true}) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Verify that "sleep 100" is running. - if err := waitForProcessList(c, expectedPL); err != nil { - t.Error(err) - } - - // Wait on the container. - ch := make(chan error) - go func() { - ws, err := c.Wait() - if err != nil { - ch <- err - } - if got, want := ws.Signal(), syscall.SIGTERM; got != want { - ch <- fmt.Errorf("got signal %v, want %v", got, want) - } - ch <- nil - }() - - // Wait a bit to ensure that we've started waiting on - // the container before we signal. - time.Sleep(time.Second) - - // Send the container a SIGTERM which will cause it to stop. - if err := c.SignalContainer(syscall.SIGTERM, false); err != nil { - t.Fatalf("error sending signal %v to container: %v", syscall.SIGTERM, err) - } - - // Wait for it to die. - if err := <-ch; err != nil { - t.Fatalf("error waiting for container: %v", err) - } - - // Load the container from disk and check the status. - c, err = Load(rootDir, fullID, LoadOpts{Exact: true}) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - if got, want := c.Status, Stopped; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Destroy the container. - if err := c.Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - - // List should not return the container id. - ids, err = List(rootDir) - if err != nil { - t.Fatalf("error listing containers: %v", err) - } - if len(ids) != 0 { - t.Errorf("expected container list to be empty, but got %v", ids) - } - - // Loading the container by id should fail. - if _, err = Load(rootDir, fullID, LoadOpts{Exact: true}); err == nil { - t.Errorf("expected loading destroyed container to fail, but it did not") - } - }) - } -} - -// Test the we can execute the application with different path formats. -func TestExePath(t *testing.T) { - // Create two directories that will be prepended to PATH. - firstPath, err := ioutil.TempDir(testutil.TmpDir(), "first") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(firstPath) - secondPath, err := ioutil.TempDir(testutil.TmpDir(), "second") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - defer os.RemoveAll(secondPath) - - // Create two minimal executables in the second path, two of which - // will be masked by files in first path. - for _, p := range []string{"unmasked", "masked1", "masked2"} { - path := filepath.Join(secondPath, p) - f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0777) - if err != nil { - t.Fatalf("error opening path: %v", err) - } - defer f.Close() - if _, err := io.WriteString(f, "#!/bin/true\n"); err != nil { - t.Fatalf("error writing contents: %v", err) - } - } - - // Create a non-executable file in the first path which masks a healthy - // executable in the second. - nonExecutable := filepath.Join(firstPath, "masked1") - f2, err := os.OpenFile(nonExecutable, os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - t.Fatalf("error opening file: %v", err) - } - f2.Close() - - // Create a non-regular file in the first path which masks a healthy - // executable in the second. - nonRegular := filepath.Join(firstPath, "masked2") - if err := os.Mkdir(nonRegular, 0777); err != nil { - t.Fatalf("error making directory: %v", err) - } - - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - for _, test := range []struct { - path string - success bool - }{ - {path: "true", success: true}, - {path: "bin/true", success: true}, - {path: "/bin/true", success: true}, - {path: "thisfiledoesntexit", success: false}, - {path: "bin/thisfiledoesntexit", success: false}, - {path: "/bin/thisfiledoesntexit", success: false}, - - {path: "unmasked", success: true}, - {path: filepath.Join(firstPath, "unmasked"), success: false}, - {path: filepath.Join(secondPath, "unmasked"), success: true}, - - {path: "masked1", success: true}, - {path: filepath.Join(firstPath, "masked1"), success: false}, - {path: filepath.Join(secondPath, "masked1"), success: true}, - - {path: "masked2", success: true}, - {path: filepath.Join(firstPath, "masked2"), success: false}, - {path: filepath.Join(secondPath, "masked2"), success: true}, - } { - t.Run(fmt.Sprintf("path=%s,success=%t", test.path, test.success), func(t *testing.T) { - spec := testutil.NewSpecWithArgs(test.path) - spec.Process.Env = []string{ - fmt.Sprintf("PATH=%s:%s:%s", firstPath, secondPath, os.Getenv("PATH")), - } - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("exec: error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - - if test.success { - if err != nil { - t.Errorf("exec: error running container: %v", err) - } - if ws.ExitStatus() != 0 { - t.Errorf("exec: got exit status %v want %v", ws.ExitStatus(), 0) - } - } else { - if err == nil { - t.Errorf("exec: got: no error, want: error") - } - } - }) - } - }) - } -} - -// Test the we can retrieve the application exit status from the container. -func TestAppExitStatus(t *testing.T) { - doAppExitStatus(t, false) -} - -// This is TestAppExitStatus for VFSv2. -func TestAppExitStatusVFS2(t *testing.T) { - doAppExitStatus(t, true) -} - -func doAppExitStatus(t *testing.T, vfs2 bool) { - // First container will succeed. - succSpec := testutil.NewSpecWithArgs("true") - conf := testutil.TestConfig(t) - conf.VFS2 = vfs2 - _, bundleDir, cleanup, err := testutil.SetupContainer(succSpec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: testutil.RandomContainerID(), - Spec: succSpec, - BundleDir: bundleDir, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if ws.ExitStatus() != 0 { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0) - } - - // Second container exits with non-zero status. - wantStatus := 123 - errSpec := testutil.NewSpecWithArgs("bash", "-c", fmt.Sprintf("exit %d", wantStatus)) - - _, bundleDir2, cleanup2, err := testutil.SetupContainer(errSpec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup2() - - args2 := Args{ - ID: testutil.RandomContainerID(), - Spec: errSpec, - BundleDir: bundleDir2, - Attached: true, - } - ws, err = Run(conf, args2) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if ws.ExitStatus() != wantStatus { - t.Errorf("got exit status %v want %v", ws.ExitStatus(), wantStatus) - } -} - -// TestExec verifies that a container can exec a new program. -func TestExec(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "exec-test") - if err != nil { - t.Fatalf("error creating temporary directory: %v", err) - } - // Note that some shells may exec the final command in a sequence as - // an optimization. We avoid this here by adding the exit 0. - cmd := fmt.Sprintf("ln -s /bin/true %q/symlink && sleep 100 && exit 0", dir) - spec := testutil.NewSpecWithArgs("sh", "-c", cmd) - - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Wait until sleep is running to ensure the symlink was created. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sh").Process(), - newProcessBuilder().Cmd("sleep").Process(), - } - if err := waitForProcessList(cont, expectedPL); err != nil { - t.Fatalf("waitForProcessList: %v", err) - } - - for _, tc := range []struct { - name string - args control.ExecArgs - }{ - { - name: "complete", - args: control.ExecArgs{ - Filename: "/bin/true", - Argv: []string{"/bin/true"}, - }, - }, - { - name: "filename", - args: control.ExecArgs{ - Filename: "/bin/true", - }, - }, - { - name: "argv", - args: control.ExecArgs{ - Argv: []string{"/bin/true"}, - }, - }, - { - name: "filename resolution", - args: control.ExecArgs{ - Filename: "true", - Envv: []string{"PATH=/bin"}, - }, - }, - { - name: "argv resolution", - args: control.ExecArgs{ - Argv: []string{"true"}, - Envv: []string{"PATH=/bin"}, - }, - }, - { - name: "argv symlink", - args: control.ExecArgs{ - Argv: []string{filepath.Join(dir, "symlink")}, - }, - }, - { - name: "working dir", - args: control.ExecArgs{ - Argv: []string{"/bin/sh", "-c", `if [[ "${PWD}" != "/tmp" ]]; then exit 1; fi`}, - WorkingDirectory: "/tmp", - }, - }, - { - name: "user", - args: control.ExecArgs{ - Argv: []string{"/bin/sh", "-c", `if [[ "$(id -u)" != "343" ]]; then exit 1; fi`}, - KUID: 343, - }, - }, - { - name: "group", - args: control.ExecArgs{ - Argv: []string{"/bin/sh", "-c", `if [[ "$(id -g)" != "343" ]]; then exit 1; fi`}, - KGID: 343, - }, - }, - { - name: "env", - args: control.ExecArgs{ - Argv: []string{"/bin/sh", "-c", `if [[ "${FOO}" != "123" ]]; then exit 1; fi`}, - Envv: []string{"FOO=123"}, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - // t.Parallel() - if ws, err := cont.executeSync(&tc.args); err != nil { - t.Fatalf("executeAsync(%+v): %v", tc.args, err) - } else if ws != 0 { - t.Fatalf("executeAsync(%+v) failed with exit: %v", tc.args, ws) - } - }) - } - }) - } -} - -// TestExecProcList verifies that a container can exec a new program and it -// shows correcly in the process list. -func TestExecProcList(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - const uid = 343 - spec := testutil.NewSpecWithArgs("sleep", "100") - - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - execArgs := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "5"}, - WorkingDirectory: "/", - KUID: uid, - } - - // Verify that "sleep 100" and "sleep 5" are running after exec. First, - // start running exec (which blocks). - ch := make(chan error) - go func() { - exitStatus, err := cont.executeSync(execArgs) - if err != nil { - ch <- err - } else if exitStatus != 0 { - ch <- fmt.Errorf("failed with exit status: %v", exitStatus) - } else { - ch <- nil - } - }() - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).PPID(0).Cmd("sleep").UID(0).Process(), - newProcessBuilder().PID(2).PPID(0).Cmd("sleep").UID(uid).Process(), - } - if err := waitForProcessList(cont, expectedPL); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - // Ensure that exec finished without error. - select { - case <-time.After(10 * time.Second): - t.Fatalf("container timed out waiting for exec to finish.") - case err := <-ch: - if err != nil { - t.Errorf("container failed to exec %v: %v", args, err) - } - } - }) - } -} - -// TestKillPid verifies that we can signal individual exec'd processes. -func TestKillPid(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - const nProcs = 4 - spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true") - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Verify that all processes are running. - if err := waitForProcessCount(cont, nProcs); err != nil { - t.Fatalf("timed out waiting for processes to start: %v", err) - } - - // Kill the child process with the largest PID. - procs, err := cont.Processes() - if err != nil { - t.Fatalf("failed to get process list: %v", err) - } - var pid int32 - for _, p := range procs { - if pid < int32(p.PID) { - pid = int32(p.PID) - } - } - if err := cont.SignalProcess(syscall.SIGKILL, pid); err != nil { - t.Fatalf("failed to signal process %d: %v", pid, err) - } - - // Verify that one process is gone. - if err := waitForProcessCount(cont, nProcs-1); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - procs, err = cont.Processes() - if err != nil { - t.Fatalf("failed to get process list: %v", err) - } - for _, p := range procs { - if pid == int32(p.PID) { - t.Fatalf("pid %d is still alive, which should be killed", pid) - } - } - }) - } -} - -// TestCheckpointRestore creates a container that continuously writes successive -// integers to a file. To test checkpoint and restore functionality, the -// container is checkpointed and the last number printed to the file is -// recorded. Then, it is restored in two new containers and the first number -// printed from these containers is checked. Both should be the next consecutive -// number after the last number from the checkpointed container. -func TestCheckpointRestore(t *testing.T) { - // Skip overlay because test requires writing to host file. - for name, conf := range configs(t, noOverlay...) { - t.Run(name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "checkpoint-test") - if err != nil { - t.Fatalf("ioutil.TempDir failed: %v", err) - } - defer os.RemoveAll(dir) - if err := os.Chmod(dir, 0777); err != nil { - t.Fatalf("error chmoding file: %q, %v", dir, err) - } - - outputPath := filepath.Join(dir, "output") - outputFile, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile.Close() - - script := fmt.Sprintf("for ((i=0; ;i++)); do echo $i >> %q; sleep 1; done", outputPath) - spec := testutil.NewSpecWithArgs("bash", "-c", script) - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Set the image path, which is where the checkpoint image will be saved. - imagePath := filepath.Join(dir, "test-image-file") - - // Create the image file and open for writing. - file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) - if err != nil { - t.Fatalf("error opening new file at imagePath: %v", err) - } - defer file.Close() - - // Wait until application has ran. - if err := waitForFileNotEmpty(outputFile); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Checkpoint running container; save state into new file. - if err := cont.Checkpoint(file); err != nil { - t.Fatalf("error checkpointing container to empty file: %v", err) - } - defer os.RemoveAll(imagePath) - - lastNum, err := readOutputNum(outputPath, -1) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile2, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile2.Close() - - // Restore into a new container. - args2 := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont2, err := New(conf, args2) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont2.Destroy() - - if err := cont2.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFileNotEmpty(outputFile2); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - firstNum, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum and that the container picks - // up from where it left off. - if lastNum+1 != firstNum { - t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum) - } - cont2.Destroy() - - // Restore into another container! - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile3, err := createWriteableOutputFile(outputPath) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile3.Close() - - // Restore into a new container. - args3 := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont3, err := New(conf, args3) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont3.Destroy() - - if err := cont3.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFileNotEmpty(outputFile3); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - firstNum2, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum and that the container picks - // up from where it left off. - if lastNum+1 != firstNum2 { - t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum2) - } - cont3.Destroy() - }) - } -} - -// TestUnixDomainSockets checks that Checkpoint/Restore works in cases -// with filesystem Unix Domain Socket use. -func TestUnixDomainSockets(t *testing.T) { - // Skip overlay because test requires writing to host file. - for name, conf := range configs(t, noOverlay...) { - t.Run(name, func(t *testing.T) { - // UDS path is limited to 108 chars for compatibility with older systems. - // Use '/tmp' (instead of testutil.TmpDir) to ensure the size limit is - // not exceeded. Assumes '/tmp' exists in the system. - dir, err := ioutil.TempDir("/tmp", "uds-test") - if err != nil { - t.Fatalf("ioutil.TempDir failed: %v", err) - } - defer os.RemoveAll(dir) - - outputPath := filepath.Join(dir, "uds_output") - outputFile, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile.Close() - - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - socketPath := filepath.Join(dir, "uds_socket") - defer os.Remove(socketPath) - - spec := testutil.NewSpecWithArgs(app, "uds", "--file", outputPath, "--socket", socketPath) - spec.Process.User = specs.User{ - UID: uint32(os.Getuid()), - GID: uint32(os.Getgid()), - } - spec.Mounts = []specs.Mount{{ - Type: "bind", - Destination: dir, - Source: dir, - }} - - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Set the image path, the location where the checkpoint image will be saved. - imagePath := filepath.Join(dir, "test-image-file") - - // Create the image file and open for writing. - file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) - if err != nil { - t.Fatalf("error opening new file at imagePath: %v", err) - } - defer file.Close() - defer os.RemoveAll(imagePath) - - // Wait until application has ran. - if err := waitForFileNotEmpty(outputFile); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Checkpoint running container; save state into new file. - if err := cont.Checkpoint(file); err != nil { - t.Fatalf("error checkpointing container to empty file: %v", err) - } - - // Read last number outputted before checkpoint. - lastNum, err := readOutputNum(outputPath, -1) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Delete and recreate file before restoring. - if err := os.Remove(outputPath); err != nil { - t.Fatalf("error removing file") - } - outputFile2, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) - if err != nil { - t.Fatalf("error creating output file: %v", err) - } - defer outputFile2.Close() - - // Restore into a new container. - argsRestore := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - contRestore, err := New(conf, argsRestore) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer contRestore.Destroy() - - if err := contRestore.Restore(spec, conf, imagePath); err != nil { - t.Fatalf("error restoring container: %v", err) - } - - // Wait until application has ran. - if err := waitForFileNotEmpty(outputFile2); err != nil { - t.Fatalf("Failed to wait for output file: %v", err) - } - - // Read first number outputted after restore. - firstNum, err := readOutputNum(outputPath, 0) - if err != nil { - t.Fatalf("error with outputFile: %v", err) - } - - // Check that lastNum is one less than firstNum. - if lastNum+1 != firstNum { - t.Errorf("error numbers not consecutive, previous: %d, next: %d", lastNum, firstNum) - } - contRestore.Destroy() - }) - } -} - -// TestPauseResume tests that we can successfully pause and resume a container. -// The container will keep touching a file to indicate it's running. The test -// pauses the container, removes the file, and checks that it doesn't get -// recreated. Then it resumes the container, verify that the file gets created -// again. -func TestPauseResume(t *testing.T) { - for name, conf := range configs(t, noOverlay...) { - t.Run(name, func(t *testing.T) { - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "lock") - if err != nil { - t.Fatalf("error creating temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) - - running := path.Join(tmpDir, "running") - script := fmt.Sprintf("while [[ true ]]; do touch %q; sleep 0.1; done", running) - spec := testutil.NewSpecWithArgs("/bin/bash", "-c", script) - - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Wait until container starts running, observed by the existence of running - // file. - if err := waitForFileExist(running); err != nil { - t.Errorf("error waiting for container to start: %v", err) - } - - // Pause the running container. - if err := cont.Pause(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - if err := os.Remove(running); err != nil { - t.Fatalf("os.Remove(%q) failed: %v", running, err) - } - // Script touches the file every 100ms. Give a bit a time for it to run to - // catch the case that pause didn't work. - time.Sleep(200 * time.Millisecond) - if _, err := os.Stat(running); !os.IsNotExist(err) { - t.Fatalf("container did not pause: file exist check: %v", err) - } - - // Resume the running container. - if err := cont.Resume(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Verify that the file is once again created by container. - if err := waitForFileExist(running); err != nil { - t.Fatalf("error resuming container: file exist check: %v", err) - } - }) - } -} - -// TestPauseResumeStatus makes sure that the statuses are set correctly -// with calls to pause and resume and that pausing and resuming only -// occurs given the correct state. -func TestPauseResumeStatus(t *testing.T) { - spec := testutil.NewSpecWithArgs("sleep", "20") - conf := testutil.TestConfig(t) - _, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Pause the running container. - if err := cont.Pause(); err != nil { - t.Errorf("error pausing container: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Try to Pause again. Should cause error. - if err := cont.Pause(); err == nil { - t.Errorf("error pausing container that was already paused: %v", err) - } - if got, want := cont.Status, Paused; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Resume the running container. - if err := cont.Resume(); err != nil { - t.Errorf("error resuming container: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } - - // Try to resume again. Should cause error. - if err := cont.Resume(); err == nil { - t.Errorf("error resuming container already running: %v", err) - } - if got, want := cont.Status, Running; got != want { - t.Errorf("container status got %v, want %v", got, want) - } -} - -// TestCapabilities verifies that: -// - Running exec as non-root UID and GID will result in an error (because the -// executable file can't be read). -// - Running exec as non-root with CAP_DAC_OVERRIDE succeeds because it skips -// this check. -func TestCapabilities(t *testing.T) { - // Pick uid/gid different than ours. - uid := auth.KUID(os.Getuid() + 1) - gid := auth.KGID(os.Getgid() + 1) - - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - spec := testutil.NewSpecWithArgs("sleep", "100") - rootDir, 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, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - if err := cont.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // expectedPL lists the expected process state of the container. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").Process(), - } - if err := waitForProcessList(cont, expectedPL); err != nil { - t.Fatalf("Failed to wait for sleep to start, err: %v", err) - } - - // Create an executable that can't be run with the specified UID:GID. - // This shouldn't be callable within the container until we add the - // CAP_DAC_OVERRIDE capability to skip the access check. - exePath := filepath.Join(rootDir, "exe") - if err := ioutil.WriteFile(exePath, []byte("#!/bin/sh\necho hello"), 0770); err != nil { - t.Fatalf("couldn't create executable: %v", err) - } - defer os.Remove(exePath) - - // Need to traverse the intermediate directory. - os.Chmod(rootDir, 0755) - - execArgs := &control.ExecArgs{ - Filename: exePath, - Argv: []string{exePath}, - WorkingDirectory: "/", - KUID: uid, - KGID: gid, - Capabilities: &auth.TaskCapabilities{}, - } - - // "exe" should fail because we don't have the necessary permissions. - if _, err := cont.executeSync(execArgs); err == nil { - t.Fatalf("container executed without error, but an error was expected") - } - - // Now we run with the capability enabled and should succeed. - execArgs.Capabilities = &auth.TaskCapabilities{ - EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), - } - // "exe" should not fail this time. - if _, err := cont.executeSync(execArgs); err != nil { - t.Fatalf("container failed to exec %v: %v", args, err) - } - }) - } -} - -// TestRunNonRoot checks that sandbox can be configured when running as -// non-privileged user. -func TestRunNonRoot(t *testing.T) { - for name, conf := range configs(t, noOverlay...) { - t.Run(name, func(t *testing.T) { - spec := testutil.NewSpecWithArgs("/bin/true") - - // Set a random user/group with no access to "blocked" dir. - spec.Process.User.UID = 343 - spec.Process.User.GID = 2401 - spec.Process.Capabilities = nil - - // User running inside container can't list '$TMP/blocked' and would fail to - // mount it. - dir, err := ioutil.TempDir(testutil.TmpDir(), "blocked") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - if err := os.Chmod(dir, 0700); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", dir, err) - } - dir = path.Join(dir, "test") - if err := os.Mkdir(dir, 0755); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", dir, err) - } - - src, err := ioutil.TempDir(testutil.TmpDir(), "src") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: dir, - Source: src, - Type: "bind", - }) - - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } - }) - } -} - -// TestMountNewDir checks that runsc will create destination directory if it -// doesn't exit. -func TestMountNewDir(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - root, err := ioutil.TempDir(testutil.TmpDir(), "root") - if err != nil { - t.Fatal("ioutil.TempDir() failed:", err) - } - - srcDir := path.Join(root, "src", "dir", "anotherdir") - if err := os.MkdirAll(srcDir, 0755); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", srcDir, err) - } - - mountDir := path.Join(root, "dir", "anotherdir") - - spec := testutil.NewSpecWithArgs("/bin/ls", mountDir) - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: mountDir, - Source: srcDir, - Type: "bind", - }) - // Extra points for creating the mount with a readonly root. - spec.Root.Readonly = true - - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } - }) - } -} - -func TestReadonlyRoot(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - spec := testutil.NewSpecWithArgs("sleep", "100") - spec.Root.Readonly = true - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - 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) - } - - // Read mounts to check that root is readonly. - out, ws, err := executeCombinedOutput(c, "/bin/sh", "-c", "mount | grep ' / '") - if err != nil || ws != 0 { - t.Fatalf("exec failed, ws: %v, err: %v", ws, err) - } - t.Logf("root mount: %q", out) - if !strings.Contains(string(out), "(ro)") { - t.Errorf("root not mounted readonly: %q", out) - } - - // Check that file cannot be created. - ws, err = execute(c, "/bin/touch", "/foo") - if err != nil { - t.Fatalf("touch file in ro mount: %v", err) - } - if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM { - t.Fatalf("wrong waitStatus: %v", ws) - } - }) - } -} - -func TestReadonlyMount(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "ro-mount") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - spec := testutil.NewSpecWithArgs("sleep", "100") - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: dir, - Source: dir, - Type: "bind", - Options: []string{"ro"}, - }) - spec.Root.Readonly = false - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - 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) - } - - // Read mounts to check that volume is readonly. - cmd := fmt.Sprintf("mount | grep ' %s '", dir) - out, ws, err := executeCombinedOutput(c, "/bin/sh", "-c", cmd) - if err != nil || ws != 0 { - t.Fatalf("exec failed, ws: %v, err: %v", ws, err) - } - t.Logf("mount: %q", out) - if !strings.Contains(string(out), "(ro)") { - t.Errorf("volume not mounted readonly: %q", out) - } - - // Check that file cannot be created. - ws, err = execute(c, "/bin/touch", path.Join(dir, "file")) - if err != nil { - t.Fatalf("touch file in ro mount: %v", err) - } - if !ws.Exited() || syscall.Errno(ws.ExitStatus()) != syscall.EPERM { - t.Fatalf("wrong WaitStatus: %v", ws) - } - }) - } -} - -func TestUIDMap(t *testing.T) { - for name, conf := range configs(t, noOverlay...) { - t.Run(name, func(t *testing.T) { - testDir, err := ioutil.TempDir(testutil.TmpDir(), "test-mount") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - defer os.RemoveAll(testDir) - testFile := path.Join(testDir, "testfile") - - spec := testutil.NewSpecWithArgs("touch", "/tmp/testfile") - uid := os.Getuid() - gid := os.Getgid() - spec.Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - {Type: specs.UserNamespace}, - {Type: specs.PIDNamespace}, - {Type: specs.MountNamespace}, - }, - UIDMappings: []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: uint32(uid), - Size: 1, - }, - }, - GIDMappings: []specs.LinuxIDMapping{ - { - ContainerID: 0, - HostID: uint32(gid), - Size: 1, - }, - }, - } - - spec.Mounts = append(spec.Mounts, specs.Mount{ - Destination: "/tmp", - Source: testDir, - Type: "bind", - }) - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Create, start and wait for 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) - } - - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %v", ws) - } - st := syscall.Stat_t{} - if err := syscall.Stat(testFile, &st); err != nil { - t.Fatalf("error stat /testfile: %v", err) - } - - if st.Uid != uint32(uid) || st.Gid != uint32(gid) { - t.Fatalf("UID: %d (%d) GID: %d (%d)", st.Uid, uid, st.Gid, gid) - } - }) - } -} - -// TestAbbreviatedIDs checks that runsc supports using abbreviated container -// IDs in place of full IDs. -func TestAbbreviatedIDs(t *testing.T) { - doAbbreviatedIDsTest(t, false) -} - -func TestAbbreviatedIDsVFS2(t *testing.T) { - doAbbreviatedIDsTest(t, true) -} - -func doAbbreviatedIDsTest(t *testing.T, vfs2 bool) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - conf.VFS2 = vfs2 - - cids := []string{ - "foo-" + testutil.RandomContainerID(), - "bar-" + testutil.RandomContainerID(), - "baz-" + testutil.RandomContainerID(), - } - for _, cid := range cids { - spec := testutil.NewSpecWithArgs("sleep", "100") - bundleDir, cleanup, err := testutil.SetupBundleDir(spec) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Create and start the container. - args := Args{ - ID: cid, - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - defer cont.Destroy() - } - - // These should all be unambigious. - unambiguous := map[string]string{ - "f": cids[0], - cids[0]: cids[0], - "bar": cids[1], - cids[1]: cids[1], - "baz": cids[2], - cids[2]: cids[2], - } - for shortid, longid := range unambiguous { - if _, err := Load(rootDir, FullID{ContainerID: shortid}, LoadOpts{}); err != nil { - t.Errorf("%q should resolve to %q: %v", shortid, longid, err) - } - } - - // These should be ambiguous. - ambiguous := []string{ - "b", - "ba", - } - for _, shortid := range ambiguous { - if s, err := Load(rootDir, FullID{ContainerID: shortid}, LoadOpts{}); err == nil { - t.Errorf("%q should be ambiguous, but resolved to %q", shortid, s.ID) - } - } -} - -func TestGoferExits(t *testing.T) { - doGoferExitTest(t, false) -} - -func TestGoferExitsVFS2(t *testing.T) { - doGoferExitTest(t, true) -} - -func doGoferExitTest(t *testing.T, vfs2 bool) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "10000") - conf := testutil.TestConfig(t) - conf.VFS2 = vfs2 - _, 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) - } - - // Kill sandbox and expect gofer to exit on its own. - sandboxProc, err := os.FindProcess(c.Sandbox.Pid) - if err != nil { - t.Fatalf("error finding sandbox process: %v", err) - } - if err := sandboxProc.Kill(); err != nil { - t.Fatalf("error killing sandbox process: %v", err) - } - - err = blockUntilWaitable(c.GoferPid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } -} - -func TestRootNotMount(t *testing.T) { - appSym, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - app, err := filepath.EvalSymlinks(appSym) - if err != nil { - t.Fatalf("error resolving %q symlink: %v", appSym, err) - } - log.Infof("App path %q is a symlink to %q", appSym, app) - - static, err := testutil.IsStatic(app) - if err != nil { - t.Fatalf("error reading application binary: %v", err) - } - if !static { - // This happens during race builds; we cannot map in shared - // libraries also, so we need to skip the test. - t.Skip() - } - - root := filepath.Dir(app) - exe := "/" + filepath.Base(app) - log.Infof("Executing %q in %q", exe, root) - - spec := testutil.NewSpecWithArgs(exe, "help") - spec.Root.Path = root - spec.Root.Readonly = true - spec.Mounts = nil - - conf := testutil.TestConfig(t) - if err := run(spec, conf); err != nil { - t.Fatalf("error running sandbox: %v", err) - } -} - -func TestUserLog(t *testing.T) { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // sched_rr_get_interval - not implemented in gvisor. - num := strconv.Itoa(syscall.SYS_SCHED_RR_GET_INTERVAL) - spec := testutil.NewSpecWithArgs(app, "syscall", "--syscall="+num) - conf := testutil.TestConfig(t) - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - dir, err := ioutil.TempDir(testutil.TmpDir(), "user_log_test") - if err != nil { - t.Fatalf("error creating tmp dir: %v", err) - } - userLog := filepath.Join(dir, "user.log") - - // Create, start and wait for the container. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - UserLog: userLog, - Attached: true, - } - ws, err := Run(conf, args) - if err != nil { - t.Fatalf("error running container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %v", ws) - } - - out, err := ioutil.ReadFile(userLog) - if err != nil { - t.Fatalf("error opening user log file %q: %v", userLog, err) - } - if want := "Unsupported syscall sched_rr_get_interval("; !strings.Contains(string(out), want) { - t.Errorf("user log file doesn't contain %q, out: %s", want, string(out)) - } -} - -func TestWaitOnExitedSandbox(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - // Run a shell that sleeps for 1 second and then exits with a - // non-zero code. - const wantExit = 17 - cmd := fmt.Sprintf("sleep 1; exit %d", wantExit) - spec := testutil.NewSpecWithArgs("/bin/sh", "-c", cmd) - _, 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) - } - - // Wait on the sandbox. This will make an RPC to the sandbox - // and get the actual exit status of the application. - ws, err := c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if got := ws.ExitStatus(); got != wantExit { - t.Errorf("got exit status %d, want %d", got, wantExit) - } - - // Now the sandbox has exited, but the zombie sandbox process - // still exists. Calling Wait() now will return the sandbox - // exit status. - ws, err = c.Wait() - if err != nil { - t.Fatalf("error waiting on container: %v", err) - } - if got := ws.ExitStatus(); got != wantExit { - t.Errorf("got exit status %d, want %d", got, wantExit) - } - }) - } -} - -func TestDestroyNotStarted(t *testing.T) { - doDestroyNotStartedTest(t, false) -} - -func TestDestroyNotStartedVFS2(t *testing.T) { - doDestroyNotStartedTest(t, true) -} - -func doDestroyNotStartedTest(t *testing.T, vfs2 bool) { - spec := testutil.NewSpecWithArgs("/bin/sleep", "100") - conf := testutil.TestConfig(t) - conf.VFS2 = vfs2 - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Create the container and check that it can be destroyed. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - if err := c.Destroy(); err != nil { - t.Fatalf("deleting non-started container failed: %v", err) - } -} - -// TestDestroyStarting attempts to force a race between start and destroy. -func TestDestroyStarting(t *testing.T) { - doDestroyStartingTest(t, false) -} - -func TestDestroyStartedVFS2(t *testing.T) { - doDestroyStartingTest(t, true) -} - -func doDestroyStartingTest(t *testing.T, vfs2 bool) { - for i := 0; i < 10; i++ { - spec := testutil.NewSpecWithArgs("/bin/sleep", "100") - conf := testutil.TestConfig(t) - conf.VFS2 = vfs2 - rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Create the container and check that it can be destroyed. - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - c, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Container is not thread safe, so load another instance to run in - // concurrently. - startCont, err := Load(rootDir, FullID{ContainerID: args.ID}, LoadOpts{}) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - // Ignore failures, start can fail if destroy runs first. - startCont.Start(conf) - }() - - wg.Add(1) - go func() { - defer wg.Done() - if err := c.Destroy(); err != nil { - t.Errorf("deleting non-started container failed: %v", err) - } - }() - wg.Wait() - } -} - -func TestCreateWorkingDir(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "cwd-create") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - dir := path.Join(tmpDir, "new/working/dir") - - // touch will fail if the directory doesn't exist. - spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file")) - spec.Process.Cwd = dir - spec.Root.Readonly = true - - if err := run(spec, conf); err != nil { - t.Fatalf("Error running container: %v", err) - } - }) - } -} - -// TestMountPropagation verifies that mount propagates to slave but not to -// private mounts. -func TestMountPropagation(t *testing.T) { - // Setup dir structure: - // - src: is mounted as shared and is used as source for both private and - // slave mounts - // - dir: will be bind mounted inside src and should propagate to slave - tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "mount") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - src := filepath.Join(tmpDir, "src") - srcMnt := filepath.Join(src, "mnt") - dir := filepath.Join(tmpDir, "dir") - for _, path := range []string{src, srcMnt, dir} { - if err := os.MkdirAll(path, 0777); err != nil { - t.Fatalf("MkdirAll(%q): %v", path, err) - } - } - dirFile := filepath.Join(dir, "file") - f, err := os.Create(dirFile) - if err != nil { - t.Fatalf("os.Create(%q): %v", dirFile, err) - } - f.Close() - - // Setup src as a shared mount. - if err := syscall.Mount(src, src, "bind", syscall.MS_BIND, ""); err != nil { - t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) - } - if err := syscall.Mount("", src, "", syscall.MS_SHARED, ""); err != nil { - t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err) - } - - spec := testutil.NewSpecWithArgs("sleep", "1000") - - priv := filepath.Join(tmpDir, "priv") - slave := filepath.Join(tmpDir, "slave") - spec.Mounts = []specs.Mount{ - { - Source: src, - Destination: priv, - Type: "bind", - Options: []string{"private"}, - }, - { - Source: src, - Destination: slave, - Type: "bind", - Options: []string{"slave"}, - }, - } - - conf := testutil.TestConfig(t) - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("creating container: %v", err) - } - defer cont.Destroy() - - if err := cont.Start(conf); err != nil { - t.Fatalf("starting container: %v", err) - } - - // After the container is started, mount dir inside source and check what - // happens to both destinations. - if err := syscall.Mount(dir, srcMnt, "bind", syscall.MS_BIND, ""); err != nil { - t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) - } - - // Check that mount didn't propagate to private mount. - privFile := filepath.Join(priv, "mnt", "file") - if ws, err := execute(cont, "/usr/bin/test", "!", "-f", privFile); err != nil || ws != 0 { - t.Fatalf("exec: test ! -f %q, ws: %v, err: %v", privFile, ws, err) - } - - // Check that mount propagated to slave mount. - slaveFile := filepath.Join(slave, "mnt", "file") - if ws, err := execute(cont, "/usr/bin/test", "-f", slaveFile); err != nil || ws != 0 { - t.Fatalf("exec: test -f %q, ws: %v, err: %v", privFile, ws, err) - } -} - -func TestMountSymlink(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - dir, err := ioutil.TempDir(testutil.TmpDir(), "mount-symlink") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - defer os.RemoveAll(dir) - - source := path.Join(dir, "source") - target := path.Join(dir, "target") - for _, path := range []string{source, target} { - if err := os.MkdirAll(path, 0777); err != nil { - t.Fatalf("os.MkdirAll(): %v", err) - } - } - f, err := os.Create(path.Join(source, "file")) - if err != nil { - t.Fatalf("os.Create(): %v", err) - } - f.Close() - - link := path.Join(dir, "link") - if err := os.Symlink(target, link); err != nil { - t.Fatalf("os.Symlink(%q, %q): %v", target, link, err) - } - - spec := testutil.NewSpecWithArgs("/bin/sleep", "1000") - - // Mount to a symlink to ensure the mount code will follow it and mount - // at the symlink target. - spec.Mounts = append(spec.Mounts, specs.Mount{ - Type: "bind", - Destination: link, - Source: source, - }) - - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("creating container: %v", err) - } - defer cont.Destroy() - - if err := cont.Start(conf); err != nil { - t.Fatalf("starting container: %v", err) - } - - // Check that symlink was resolved and mount was created where the symlink - // is pointing to. - file := path.Join(target, "file") - if ws, err := execute(cont, "/usr/bin/test", "-f", file); err != nil || ws != 0 { - t.Fatalf("exec: test -f %q, ws: %v, err: %v", file, ws, err) - } - }) - } -} - -// Check that --net-raw disables the CAP_NET_RAW capability. -func TestNetRaw(t *testing.T) { - capNetRaw := strconv.FormatUint(bits.MaskOf64(int(linux.CAP_NET_RAW)), 10) - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - for _, enableRaw := range []bool{true, false} { - conf := testutil.TestConfig(t) - conf.EnableRaw = enableRaw - - test := "--enabled" - if !enableRaw { - test = "--disabled" - } - - spec := testutil.NewSpecWithArgs(app, "capability", test, capNetRaw) - if err := run(spec, conf); err != nil { - t.Fatalf("Error running container: %v", err) - } - } -} - -// TestTTYField checks TTY field returned by container.Processes(). -func TestTTYField(t *testing.T) { - stop := testutil.StartReaper() - defer stop() - - testApp, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - testCases := []struct { - name string - useTTY bool - wantTTYField string - }{ - { - name: "no tty", - useTTY: false, - wantTTYField: "?", - }, - { - name: "tty used", - useTTY: true, - wantTTYField: "pts/0", - }, - } - - for _, test := range testCases { - for _, vfs2 := range []bool{false, true} { - name := test.name - if vfs2 { - name += "-vfs2" - } - t.Run(name, func(t *testing.T) { - conf := testutil.TestConfig(t) - conf.VFS2 = vfs2 - - // We will run /bin/sleep, possibly with an open TTY. - cmd := []string{"/bin/sleep", "10000"} - if test.useTTY { - // Run inside the "pty-runner". - cmd = append([]string{testApp, "pty-runner"}, cmd...) - } - - spec := testutil.NewSpecWithArgs(cmd...) - _, 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) - } - - // Wait for sleep to be running, and check the TTY - // field. - var gotTTYField string - cb := func() error { - ps, err := c.Processes() - if err != nil { - err = fmt.Errorf("error getting process data from container: %v", err) - return &backoff.PermanentError{Err: err} - } - for _, p := range ps { - if strings.Contains(p.Cmd, "sleep") { - gotTTYField = p.TTY - return nil - } - } - return fmt.Errorf("sleep not running") - } - if err := testutil.Poll(cb, 30*time.Second); err != nil { - t.Fatalf("error waiting for sleep process: %v", err) - } - - if gotTTYField != test.wantTTYField { - t.Errorf("tty field got %q, want %q", gotTTYField, test.wantTTYField) - } - }) - } - } -} - -// Test that container can run even when there are corrupt state files in the -// root directiry. -func TestCreateWithCorruptedStateFile(t *testing.T) { - conf := testutil.TestConfig(t) - spec := testutil.NewSpecWithArgs("/bin/true") - _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Create corrupted state file. - corruptID := testutil.RandomContainerID() - corruptState := buildPath(conf.RootDir, FullID{SandboxID: corruptID, ContainerID: corruptID}, stateFileExtension) - if err := ioutil.WriteFile(corruptState, []byte("this{file(is;not[valid.json"), 0777); err != nil { - t.Fatalf("createCorruptStateFile(): %v", err) - } - defer os.Remove(corruptState) - - if _, err := Load(conf.RootDir, FullID{ContainerID: corruptID}, LoadOpts{SkipCheck: true}); err == nil { - t.Fatalf("loading corrupted state file should have failed") - } - - args := Args{ - ID: testutil.RandomContainerID(), - Spec: spec, - BundleDir: bundleDir, - Attached: true, - } - if ws, err := Run(conf, args); err != nil { - t.Errorf("running container: %v", err) - } else if !ws.Exited() || ws.ExitStatus() != 0 { - t.Errorf("container failed, waitStatus: %v", ws) - } -} - -func execute(cont *Container, name string, arg ...string) (syscall.WaitStatus, error) { - args := &control.ExecArgs{ - Filename: name, - Argv: append([]string{name}, arg...), - } - return cont.executeSync(args) -} - -func executeCombinedOutput(cont *Container, name string, arg ...string) ([]byte, syscall.WaitStatus, error) { - r, w, err := os.Pipe() - if err != nil { - return nil, 0, err - } - defer r.Close() - - args := &control.ExecArgs{ - Filename: name, - Argv: append([]string{name}, arg...), - FilePayload: urpc.FilePayload{Files: []*os.File{os.Stdin, w, w}}, - } - ws, err := cont.executeSync(args) - w.Close() - if err != nil { - return nil, 0, err - } - out, err := ioutil.ReadAll(r) - return out, ws, err -} - -// executeSync synchronously executes a new process. -func (c *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) { - pid, err := c.Execute(args) - if err != nil { - return 0, fmt.Errorf("error executing: %v", err) - } - ws, err := c.WaitPID(pid) - if err != nil { - return 0, fmt.Errorf("error waiting: %v", err) - } - return ws, nil -} - -func TestMain(m *testing.M) { - log.SetLevel(log.Debug) - flag.Parse() - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } - specutils.MaybeRunAsRoot() - os.Exit(m.Run()) -} diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go deleted file mode 100644 index 173332cc2..000000000 --- a/runsc/container/multi_container_test.go +++ /dev/null @@ -1,1908 +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 container - -import ( - "fmt" - "io/ioutil" - "math" - "os" - "path" - "path/filepath" - "strings" - "syscall" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/cleanup" - "gvisor.dev/gvisor/pkg/sentry/control" - "gvisor.dev/gvisor/pkg/sentry/kernel" - "gvisor.dev/gvisor/pkg/sync" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/boot" - "gvisor.dev/gvisor/runsc/config" - "gvisor.dev/gvisor/runsc/specutils" -) - -func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) { - var specs []*specs.Spec - var ids []string - rootID := testutil.RandomContainerID() - - for i, cmd := range cmds { - spec := testutil.NewSpecWithArgs(cmd...) - if i == 0 { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox, - } - ids = append(ids, rootID) - } else { - spec.Annotations = map[string]string{ - specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer, - specutils.ContainerdSandboxIDAnnotation: rootID, - } - ids = append(ids, testutil.RandomContainerID()) - } - specs = append(specs, spec) - } - return specs, ids -} - -func startContainers(conf *config.Config, specs []*specs.Spec, ids []string) ([]*Container, func(), error) { - if len(conf.RootDir) == 0 { - panic("conf.RootDir not set. Call testutil.SetupRootDir() to set.") - } - - cu := cleanup.Cleanup{} - defer cu.Clean() - - var containers []*Container - for i, spec := range specs { - bundleDir, cleanup, err := testutil.SetupBundleDir(spec) - if err != nil { - return nil, nil, fmt.Errorf("error setting up container: %v", err) - } - cu.Add(cleanup) - - args := Args{ - ID: ids[i], - Spec: spec, - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - return nil, nil, fmt.Errorf("error creating container: %v", err) - } - cu.Add(func() { cont.Destroy() }) - containers = append(containers, cont) - - if err := cont.Start(conf); err != nil { - return nil, nil, fmt.Errorf("error starting container: %v", err) - } - } - - return containers, cu.Release(), nil -} - -type execDesc struct { - c *Container - cmd []string - want int - name string -} - -func execMany(t *testing.T, execs []execDesc) { - for _, exec := range execs { - t.Run(exec.name, func(t *testing.T) { - args := &control.ExecArgs{Argv: exec.cmd} - if ws, err := exec.c.executeSync(args); err != nil { - t.Errorf("error executing %+v: %v", args, err) - } else if ws.ExitStatus() != exec.want { - t.Errorf("%q: exec %q got exit status: %d, want: %d", exec.name, exec.cmd, ws.ExitStatus(), exec.want) - } - }) - } -} - -func createSharedMount(mount specs.Mount, name string, pod ...*specs.Spec) { - for _, spec := range pod { - spec.Annotations[boot.MountPrefix+name+".source"] = mount.Source - spec.Annotations[boot.MountPrefix+name+".type"] = mount.Type - spec.Annotations[boot.MountPrefix+name+".share"] = "pod" - if len(mount.Options) > 0 { - spec.Annotations[boot.MountPrefix+name+".options"] = strings.Join(mount.Options, ",") - } - } -} - -// TestMultiContainerSanity checks that it is possible to run 2 dead-simple -// containers in the same sandbox. -func TestMultiContainerSanity(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).PPID(0).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - expectedPL = []*control.Process{ - newProcessBuilder().PID(2).PPID(0).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - }) - } -} - -// TestMultiPIDNS checks that it is possible to run 2 dead-simple -// containers in the same sandbox with different pidns. -func TestMultiPIDNS(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - testSpecs, ids := createSpecs(sleep, sleep) - testSpecs[1].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - }, - }, - } - - containers, cleanup, err := startContainers(conf, testSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - expectedPL = []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - }) - } -} - -// TestMultiPIDNSPath checks the pidns path. -func TestMultiPIDNSPath(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - testSpecs, ids := createSpecs(sleep, sleep, sleep) - testSpecs[0].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/1/ns/pid", - }, - }, - } - testSpecs[1].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/1/ns/pid", - }, - }, - } - testSpecs[2].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{ - { - Type: "pid", - Path: "/proc/2/ns/pid", - }, - }, - } - - containers, cleanup, err := startContainers(conf, testSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that multiple processes are running. - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).PPID(0).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - if err := waitForProcessList(containers[2], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - expectedPL = []*control.Process{ - newProcessBuilder().PID(2).PPID(0).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - }) - } -} - -func TestMultiContainerWait(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // The first container should run the entire duration of the test. - cmd1 := []string{"sleep", "100"} - // We'll wait on the second container, which is much shorter lived. - cmd2 := []string{"sleep", "1"} - specs, ids := createSpecs(cmd1, cmd2) - - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check that we can wait for the sub-container. - c := containers[1] - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - if _, err := c.Wait(); err != nil { - t.Errorf("wait for stopped container %s shouldn't fail: %v", c.Spec.Process.Args, err) - } - - // After Wait returns, ensure that the root container is running and - // the child has finished. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").PID(1).Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for %q to start: %v", strings.Join(containers[0].Spec.Process.Args, " "), err) - } -} - -// TestExecWait ensures what we can wait on containers and individual processes -// in the sandbox that have already exited. -func TestExecWait(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // The first container should run the entire duration of the test. - cmd1 := []string{"sleep", "100"} - // We'll wait on the second container, which is much shorter lived. - cmd2 := []string{"sleep", "1"} - specs, ids := createSpecs(cmd1, cmd2) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that process is running. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Fatalf("failed to wait for sleep to start: %v", err) - } - - // Wait for the second container to finish. - if err := waitForProcessCount(containers[1], 0); err != nil { - t.Fatalf("failed to wait for second container to stop: %v", err) - } - - // Get the second container exit status. - if ws, err := containers[1].Wait(); err != nil { - t.Fatalf("failed to wait for process %s: %v", containers[1].Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Fatalf("process %s exited with non-zero status %d", containers[1].Spec.Process.Args, es) - } - if _, err := containers[1].Wait(); err != nil { - t.Fatalf("wait for stopped container %s shouldn't fail: %v", containers[1].Spec.Process.Args, err) - } - - // Execute another process in the first container. - args := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "1"}, - WorkingDirectory: "/", - KUID: 0, - } - pid, err := containers[0].Execute(args) - if err != nil { - t.Fatalf("error executing: %v", err) - } - - // Wait for the exec'd process to exit. - expectedPL = []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Fatalf("failed to wait for second container to stop: %v", err) - } - - // Get the exit status from the exec'd process. - if ws, err := containers[0].WaitPID(pid); err != nil { - t.Fatalf("failed to wait for process %+v with pid %d: %v", args, pid, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Fatalf("process %+v exited with non-zero status %d", args, es) - } - if _, err := containers[0].WaitPID(pid); err == nil { - t.Fatalf("wait for stopped process %+v should fail", args) - } -} - -// TestMultiContainerMount tests that bind mounts can be used with multiple -// containers. -func TestMultiContainerMount(t *testing.T) { - cmd1 := []string{"sleep", "100"} - - // 'src != dst' ensures that 'dst' doesn't exist in the host and must be - // properly mapped inside the container to work. - src, err := ioutil.TempDir(testutil.TmpDir(), "container") - if err != nil { - t.Fatal("ioutil.TempDir failed:", err) - } - dst := src + ".dst" - cmd2 := []string{"touch", filepath.Join(dst, "file")} - - sps, ids := createSpecs(cmd1, cmd2) - sps[1].Mounts = append(sps[1].Mounts, specs.Mount{ - Source: src, - Destination: dst, - Type: "bind", - }) - - // Setup the containers. - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - containers, cleanup, err := startContainers(conf, sps, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - ws, err := containers[1].Wait() - if err != nil { - t.Error("error waiting on container:", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Error("container failed, waitStatus:", ws) - } -} - -// TestMultiContainerSignal checks that it is possible to signal individual -// containers without killing the entire sandbox. -func TestMultiContainerSignal(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check via ps that container 1 process is running. - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Kill process 2. - if err := containers[1].SignalContainer(syscall.SIGKILL, false); err != nil { - t.Errorf("failed to kill process 2: %v", err) - } - - // Make sure process 1 is still running. - expectedPL = []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // goferPid is reset when container is destroyed. - goferPid := containers[1].GoferPid - - // Destroy container and ensure container's gofer process has exited. - if err := containers[1].Destroy(); err != nil { - t.Errorf("failed to destroy container: %v", err) - } - _, _, err = specutils.RetryEintr(func() (uintptr, uintptr, error) { - cpid, err := syscall.Wait4(goferPid, nil, 0, nil) - return uintptr(cpid), 0, err - }) - if err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } - // Make sure process 1 is still running. - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Now that process 2 is gone, ensure we get an error trying to - // signal it again. - if err := containers[1].SignalContainer(syscall.SIGKILL, false); err == nil { - t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID) - } - - // Kill process 1. - if err := containers[0].SignalContainer(syscall.SIGKILL, false); err != nil { - t.Errorf("failed to kill process 1: %v", err) - } - - // Ensure that container's gofer and sandbox process are no more. - err = blockUntilWaitable(containers[0].GoferPid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for gofer to exit: %v", err) - } - - err = blockUntilWaitable(containers[0].Sandbox.Pid) - if err != nil && err != syscall.ECHILD { - t.Errorf("error waiting for sandbox to exit: %v", err) - } - - // The sentry should be gone, so signaling should yield an error. - if err := containers[0].SignalContainer(syscall.SIGKILL, false); err == nil { - t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID) - } - - if err := containers[0].Destroy(); err != nil { - t.Errorf("failed to destroy container: %v", err) - } - }) - } -} - -// TestMultiContainerDestroy checks that container are properly cleaned-up when -// they are destroyed. -func TestMultiContainerDestroy(t *testing.T) { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // First container will remain intact while the second container is killed. - podSpecs, ids := createSpecs( - []string{"sleep", "100"}, - []string{app, "fork-bomb"}) - - // Run the fork bomb in a PID namespace to prevent processes to be - // re-parented to PID=1 in the root container. - podSpecs[1].Linux = &specs.Linux{ - Namespaces: []specs.LinuxNamespace{{Type: "pid"}}, - } - containers, cleanup, err := startContainers(conf, podSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Exec more processes to ensure signal all works for exec'd processes too. - args := &control.ExecArgs{ - Filename: app, - Argv: []string{app, "fork-bomb"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - - // Let it brew... - time.Sleep(500 * time.Millisecond) - - if err := containers[1].Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - - // Check that destroy killed all processes belonging to the container and - // waited for them to exit before returning. - pss, err := containers[0].Sandbox.Processes("") - if err != nil { - t.Fatalf("error getting process data from sandbox: %v", err) - } - expectedPL := []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if !procListsEqual(pss, expectedPL) { - t.Errorf("container got process list: %s, want: %s: error: %v", - procListToString(pss), procListToString(expectedPL), err) - } - - // Check that cont.Destroy is safe to call multiple times. - if err := containers[1].Destroy(); err != nil { - t.Errorf("error destroying container: %v", err) - } - }) - } -} - -func TestMultiContainerProcesses(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // Note: use curly braces to keep 'sh' process around. Otherwise, shell - // will just execve into 'sleep' and both containers will look the - // same. - specs, ids := createSpecs( - []string{"sleep", "100"}, - []string{"sh", "-c", "{ sleep 100; }"}) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Check root's container process list doesn't include other containers. - expectedPL0 := []*control.Process{ - newProcessBuilder().PID(1).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL0); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - - // Same for the other container. - expectedPL1 := []*control.Process{ - newProcessBuilder().PID(2).Cmd("sh").Process(), - newProcessBuilder().PID(3).PPID(2).Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[1], expectedPL1); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - - // Now exec into the second container and verify it shows up in the container. - args := &control.ExecArgs{ - Filename: "/bin/sleep", - Argv: []string{"/bin/sleep", "100"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - expectedPL1 = append(expectedPL1, newProcessBuilder().PID(4).Cmd("sleep").Process()) - if err := waitForProcessList(containers[1], expectedPL1); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } - // Root container should remain unchanged. - if err := waitForProcessList(containers[0], expectedPL0); err != nil { - t.Errorf("failed to wait for process to start: %v", err) - } -} - -// TestMultiContainerKillAll checks that all process that belong to a container -// are killed when SIGKILL is sent to *all* processes in that container. -func TestMultiContainerKillAll(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - for _, tc := range []struct { - killContainer bool - }{ - {killContainer: true}, - {killContainer: false}, - } { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // First container will remain intact while the second container is killed. - specs, ids := createSpecs( - []string{app, "task-tree", "--depth=2", "--width=2"}, - []string{app, "task-tree", "--depth=4", "--width=2"}) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Wait until all processes are created. - rootProcCount := int(math.Pow(2, 3) - 1) - if err := waitForProcessCount(containers[0], rootProcCount); err != nil { - t.Fatalf("error waitting for processes: %v", err) - } - procCount := int(math.Pow(2, 5) - 1) - if err := waitForProcessCount(containers[1], procCount); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - // Exec more processes to ensure signal works for exec'd processes too. - args := &control.ExecArgs{ - Filename: app, - Argv: []string{app, "task-tree", "--depth=2", "--width=2"}, - } - if _, err := containers[1].Execute(args); err != nil { - t.Fatalf("error exec'ing: %v", err) - } - // Wait for these new processes to start. - procCount += int(math.Pow(2, 3) - 1) - if err := waitForProcessCount(containers[1], procCount); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - - if tc.killContainer { - // First kill the init process to make the container be stopped with - // processes still running inside. - containers[1].SignalContainer(syscall.SIGKILL, false) - op := func() error { - c, err := Load(conf.RootDir, FullID{ContainerID: ids[1]}, LoadOpts{}) - if err != nil { - return err - } - if c.Status != Stopped { - return fmt.Errorf("container is not stopped") - } - return nil - } - if err := testutil.Poll(op, 5*time.Second); err != nil { - t.Fatalf("container did not stop %q: %v", containers[1].ID, err) - } - } - - c, err := Load(conf.RootDir, FullID{ContainerID: ids[1]}, LoadOpts{}) - if err != nil { - t.Fatalf("failed to load child container %q: %v", c.ID, err) - } - // Kill'Em All - if err := c.SignalContainer(syscall.SIGKILL, true); err != nil { - t.Fatalf("failed to send SIGKILL to container %q: %v", c.ID, err) - } - - // Check that all processes are gone. - if err := waitForProcessCount(containers[1], 0); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - // Check that root container was not affected. - if err := waitForProcessCount(containers[0], rootProcCount); err != nil { - t.Fatalf("error waiting for processes: %v", err) - } - } -} - -func TestMultiContainerDestroyNotStarted(t *testing.T) { - specs, ids := createSpecs( - []string{"/bin/sleep", "100"}, - []string{"/bin/sleep", "100"}) - - conf := testutil.TestConfig(t) - _, bundleDir, cleanup, err := testutil.SetupContainer(specs[0], conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - rootArgs := Args{ - ID: ids[0], - Spec: specs[0], - BundleDir: bundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - defer root.Destroy() - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - - // Create and destroy sub-container. - bundleDir, cleanupSub, err := testutil.SetupBundleDir(specs[1]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanupSub() - - args := Args{ - ID: ids[1], - Spec: specs[1], - BundleDir: bundleDir, - } - cont, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Check that container can be destroyed. - if err := cont.Destroy(); err != nil { - t.Fatalf("deleting non-started container failed: %v", err) - } -} - -// TestMultiContainerDestroyStarting attempts to force a race between start -// and destroy. -func TestMultiContainerDestroyStarting(t *testing.T) { - cmds := make([][]string, 10) - for i := range cmds { - cmds[i] = []string{"/bin/sleep", "100"} - } - specs, ids := createSpecs(cmds...) - - conf := testutil.TestConfig(t) - rootDir, bundleDir, cleanup, err := testutil.SetupContainer(specs[0], conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - rootArgs := Args{ - ID: ids[0], - Spec: specs[0], - BundleDir: bundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - defer root.Destroy() - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - - wg := sync.WaitGroup{} - for i := range cmds { - if i == 0 { - continue // skip root container - } - - bundleDir, cleanup, err := testutil.SetupBundleDir(specs[i]) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - rootArgs := Args{ - ID: ids[i], - Spec: specs[i], - BundleDir: bundleDir, - } - cont, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - - // Container is not thread safe, so load another instance to run in - // concurrently. - startCont, err := Load(rootDir, FullID{ContainerID: ids[i]}, LoadOpts{}) - if err != nil { - t.Fatalf("error loading container: %v", err) - } - wg.Add(1) - go func() { - defer wg.Done() - startCont.Start(conf) // ignore failures, start can fail if destroy runs first. - }() - - wg.Add(1) - go func() { - defer wg.Done() - if err := cont.Destroy(); err != nil { - t.Errorf("deleting non-started container failed: %v", err) - } - }() - } - wg.Wait() -} - -// TestMultiContainerDifferentFilesystems tests that different containers have -// different root filesystems. -func TestMultiContainerDifferentFilesystems(t *testing.T) { - filename := "/foo" - // Root container will create file and then sleep. - cmdRoot := []string{"sh", "-c", fmt.Sprintf("touch %q && sleep 100", filename)} - - // Child containers will assert that the file does not exist, and will - // then create it. - script := fmt.Sprintf("if [ -f %q ]; then exit 1; else touch %q; fi", filename, filename) - cmd := []string{"sh", "-c", script} - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // Make sure overlay is enabled, and none of the root filesystems are - // read-only, otherwise we won't be able to create the file. - conf.Overlay = true - specs, ids := createSpecs(cmdRoot, cmd, cmd) - for _, s := range specs { - s.Root.Readonly = false - } - - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Both child containers should exit successfully. - for i, c := range containers { - if i == 0 { - // Don't wait on the root. - continue - } - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - } -} - -// TestMultiContainerContainerDestroyStress tests that IO operations continue -// to work after containers have been stopped and gofers killed. -func TestMultiContainerContainerDestroyStress(t *testing.T) { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // Setup containers. Root container just reaps children, while the others - // perform some IOs. Children are executed in 3 batches of 10. Within the - // batch there is overlap between containers starting and being destroyed. In - // between batches all containers stop before starting another batch. - cmds := [][]string{{app, "reaper"}} - const batchSize = 10 - for i := 0; i < 3*batchSize; i++ { - dir, err := ioutil.TempDir(testutil.TmpDir(), "gofer-stop-test") - if err != nil { - t.Fatal("ioutil.TempDir failed:", err) - } - defer os.RemoveAll(dir) - - cmd := "find /bin -type f | head | xargs -I SRC cp SRC " + dir - cmds = append(cmds, []string{"sh", "-c", cmd}) - } - allSpecs, allIDs := createSpecs(cmds...) - - // Split up the specs and IDs. - rootSpec := allSpecs[0] - rootID := allIDs[0] - childrenSpecs := allSpecs[1:] - childrenIDs := allIDs[1:] - - conf := testutil.TestConfig(t) - _, bundleDir, cleanup, err := testutil.SetupContainer(rootSpec, conf) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - // Start root container. - rootArgs := Args{ - ID: rootID, - Spec: rootSpec, - BundleDir: bundleDir, - } - root, err := New(conf, rootArgs) - if err != nil { - t.Fatalf("error creating root container: %v", err) - } - if err := root.Start(conf); err != nil { - t.Fatalf("error starting root container: %v", err) - } - defer root.Destroy() - - // Run batches. Each batch starts containers in parallel, then wait and - // destroy them before starting another batch. - for i := 0; i < len(childrenSpecs); i += batchSize { - t.Logf("Starting batch from %d to %d", i, i+batchSize) - specs := childrenSpecs[i : i+batchSize] - ids := childrenIDs[i : i+batchSize] - - var children []*Container - for j, spec := range specs { - bundleDir, cleanup, err := testutil.SetupBundleDir(spec) - if err != nil { - t.Fatalf("error setting up container: %v", err) - } - defer cleanup() - - args := Args{ - ID: ids[j], - Spec: spec, - BundleDir: bundleDir, - } - child, err := New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - children = append(children, child) - - if err := child.Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - // Give a small gap between containers. - time.Sleep(50 * time.Millisecond) - } - for _, child := range children { - ws, err := child.Wait() - if err != nil { - t.Fatalf("waiting for container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("container failed, waitStatus: %x (%d)", ws, ws.ExitStatus()) - } - if err := child.Destroy(); err != nil { - t.Fatalf("error destroying container: %v", err) - } - } - } -} - -// Test that pod shared mounts are properly mounted in 2 containers and that -// changes from one container is reflected in the other. -func TestMultiContainerSharedMount(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: nil, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", mnt0.Destination}, - name: "directory is mounted in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", mnt1.Destination}, - name: "directory is mounted in container1", - }, - { - c: containers[0], - cmd: []string{"/bin/touch", file0}, - name: "create file in container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - name: "file appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - name: "file appears in container1", - }, - { - c: containers[1], - cmd: []string{"/bin/rm", file1}, - name: "remove file from container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-f", file0}, - name: "file removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-f", file1}, - name: "file removed from container1", - }, - { - c: containers[1], - cmd: []string{"/bin/mkdir", file1}, - name: "create directory in container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", file0}, - name: "dir appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", file1}, - name: "dir appears in container1", - }, - { - c: containers[0], - cmd: []string{"/bin/rmdir", file0}, - name: "remove directory from container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-d", file0}, - name: "dir removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-d", file1}, - name: "dir removed from container1", - }, - } - execMany(t, execs) - }) - } -} - -// Test that pod mounts are mounted as readonly when requested. -func TestMultiContainerSharedMountReadonly(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: []string{"ro"}, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", mnt0.Destination}, - name: "directory is mounted in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", mnt1.Destination}, - name: "directory is mounted in container1", - }, - { - c: containers[0], - cmd: []string{"/bin/touch", file0}, - want: 1, - name: "fails to write to container0", - }, - { - c: containers[1], - cmd: []string{"/bin/touch", file1}, - want: 1, - name: "fails to write to container1", - }, - } - execMany(t, execs) - }) - } -} - -// Test that shared pod mounts continue to work after container is restarted. -func TestMultiContainerSharedMountRestart(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: nil, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - file0 := path.Join(mnt0.Destination, "abc") - file1 := path.Join(mnt1.Destination, "abc") - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/bin/touch", file0}, - name: "create file in container0", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - name: "file appears in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - name: "file appears in container1", - }, - } - execMany(t, execs) - - containers[1].Destroy() - - bundleDir, cleanup, err := testutil.SetupBundleDir(podSpec[1]) - if err != nil { - t.Fatalf("error restarting container: %v", err) - } - defer cleanup() - - args := Args{ - ID: ids[1], - Spec: podSpec[1], - BundleDir: bundleDir, - } - containers[1], err = New(conf, args) - if err != nil { - t.Fatalf("error creating container: %v", err) - } - if err := containers[1].Start(conf); err != nil { - t.Fatalf("error starting container: %v", err) - } - - execs = []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-f", file0}, - name: "file is still in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-f", file1}, - name: "file is still in container1", - }, - { - c: containers[1], - cmd: []string{"/bin/rm", file1}, - name: "file removed from container1", - }, - { - c: containers[0], - cmd: []string{"/usr/bin/test", "!", "-f", file0}, - name: "file removed from container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "!", "-f", file1}, - name: "file removed from container1", - }, - } - execMany(t, execs) - }) - } -} - -// Test that unsupported pod mounts options are ignored when matching master and -// replica mounts. -func TestMultiContainerSharedMountUnsupportedOptions(t *testing.T) { - for name, conf := range configs(t, all...) { - t.Run(name, func(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"/bin/sleep", "100"} - podSpec, ids := createSpecs(sleep, sleep) - mnt0 := specs.Mount{ - Destination: "/mydir/test", - Source: "/some/dir", - Type: "tmpfs", - Options: []string{"rw", "rbind", "relatime"}, - } - podSpec[0].Mounts = append(podSpec[0].Mounts, mnt0) - - mnt1 := mnt0 - mnt1.Destination = "/mydir2/test2" - mnt1.Options = []string{"rw", "nosuid"} - podSpec[1].Mounts = append(podSpec[1].Mounts, mnt1) - - createSharedMount(mnt0, "test-mount", podSpec...) - - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - execs := []execDesc{ - { - c: containers[0], - cmd: []string{"/usr/bin/test", "-d", mnt0.Destination}, - name: "directory is mounted in container0", - }, - { - c: containers[1], - cmd: []string{"/usr/bin/test", "-d", mnt1.Destination}, - name: "directory is mounted in container1", - }, - } - execMany(t, execs) - }) - } -} - -// Test that one container can send an FD to another container, even though -// they have distinct MountNamespaces. -func TestMultiContainerMultiRootCanHandleFDs(t *testing.T) { - app, err := testutil.FindFile("test/cmd/test_app/test_app") - if err != nil { - t.Fatal("error finding test_app:", err) - } - - // We set up two containers with one shared mount that is used for a - // shared socket. The first container will send an FD over the socket - // to the second container. The FD corresponds to a file in the first - // container's mount namespace that is not part of the second - // container's mount namespace. However, the second container still - // should be able to read the FD. - - // Create a shared mount where we will put the socket. - sharedMnt := specs.Mount{ - Destination: "/mydir/test", - Type: "tmpfs", - // Shared mounts need a Source, even for tmpfs. It is only used - // to match up different shared mounts inside the pod. - Source: "/some/dir", - } - socketPath := filepath.Join(sharedMnt.Destination, "socket") - - // Create a writeable tmpfs mount where the FD sender app will create - // files to send. This will only be mounted in the FD sender. - writeableMnt := specs.Mount{ - Destination: "/tmp", - Type: "tmpfs", - } - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // Create the specs. - specs, ids := createSpecs( - []string{"sleep", "1000"}, - []string{app, "fd_sender", "--socket", socketPath}, - []string{app, "fd_receiver", "--socket", socketPath}, - ) - createSharedMount(sharedMnt, "shared-mount", specs...) - specs[1].Mounts = append(specs[2].Mounts, sharedMnt, writeableMnt) - specs[2].Mounts = append(specs[1].Mounts, sharedMnt) - - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Both containers should exit successfully. - for _, c := range containers[1:] { - if ws, err := c.Wait(); err != nil { - t.Errorf("failed to wait for process %s: %v", c.Spec.Process.Args, err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("process %s exited with non-zero status %d", c.Spec.Process.Args, es) - } - } -} - -// Test that container is destroyed when Gofer is killed. -func TestMultiContainerGoferKilled(t *testing.T) { - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep, sleep) - containers, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Ensure container is running - c := containers[2] - expectedPL := []*control.Process{ - newProcessBuilder().PID(3).Cmd("sleep").Process(), - } - if err := waitForProcessList(c, expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Kill container's gofer. - if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil { - t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err) - } - - // Wait until container stops. - if err := waitForProcessList(c, nil); err != nil { - t.Errorf("Container %q was not stopped after gofer death: %v", c.ID, err) - } - - // Check that container isn't running anymore. - if _, err := execute(c, "/bin/true"); err == nil { - t.Fatalf("Container %q was not stopped after gofer death", c.ID) - } - - // Check that other containers are unaffected. - for i, c := range containers { - if i == 2 { - continue // container[2] has been killed. - } - pl := []*control.Process{ - newProcessBuilder().PID(kernel.ThreadID(i + 1)).Cmd("sleep").Process(), - } - if err := waitForProcessList(c, pl); err != nil { - t.Errorf("Container %q was affected by another container: %v", c.ID, err) - } - if _, err := execute(c, "/bin/true"); err != nil { - t.Fatalf("Container %q was affected by another container: %v", c.ID, err) - } - } - - // Kill root container's gofer to bring entire sandbox down. - c = containers[0] - if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil { - t.Fatalf("syscall.Kill(%d, SIGKILL)=%v", c.GoferPid, err) - } - - // Wait until sandbox stops. waitForProcessList will loop until sandbox exits - // and RPC errors out. - impossiblePL := []*control.Process{ - newProcessBuilder().Cmd("non-existent-process").Process(), - } - if err := waitForProcessList(c, impossiblePL); err == nil { - t.Fatalf("Sandbox was not killed after gofer death") - } - - // Check that entire sandbox isn't running anymore. - for _, c := range containers { - if _, err := execute(c, "/bin/true"); err == nil { - t.Fatalf("Container %q was not stopped after gofer death", c.ID) - } - } -} - -func TestMultiContainerLoadSandbox(t *testing.T) { - sleep := []string{"sleep", "100"} - specs, ids := createSpecs(sleep, sleep, sleep) - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - // Create containers for the sandbox. - wants, cleanup, err := startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Then create unrelated containers. - for i := 0; i < 3; i++ { - specs, ids = createSpecs(sleep, sleep, sleep) - _, cleanup, err = startContainers(conf, specs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - } - - // Create an unrelated directory under root. - dir := filepath.Join(conf.RootDir, "not-a-container") - if err := os.MkdirAll(dir, 0755); err != nil { - t.Fatalf("os.MkdirAll(%q)=%v", dir, err) - } - - // Create a valid but empty container directory. - randomCID := testutil.RandomContainerID() - dir = filepath.Join(conf.RootDir, randomCID) - if err := os.MkdirAll(dir, 0755); err != nil { - t.Fatalf("os.MkdirAll(%q)=%v", dir, err) - } - - // Load the sandbox and check that the correct containers were returned. - id := wants[0].Sandbox.ID - gots, err := loadSandbox(conf.RootDir, id) - if err != nil { - t.Fatalf("loadSandbox()=%v", err) - } - wantIDs := make(map[string]struct{}) - for _, want := range wants { - wantIDs[want.ID] = struct{}{} - } - for _, got := range gots { - if got.Sandbox.ID != id { - t.Errorf("wrong sandbox ID, got: %v, want: %v", got.Sandbox.ID, id) - } - if _, ok := wantIDs[got.ID]; !ok { - t.Errorf("wrong container ID, got: %v, wants: %v", got.ID, wantIDs) - } - delete(wantIDs, got.ID) - } - if len(wantIDs) != 0 { - t.Errorf("containers not found: %v", wantIDs) - } -} - -// TestMultiContainerRunNonRoot checks that child container can be configured -// when running as non-privileged user. -func TestMultiContainerRunNonRoot(t *testing.T) { - cmdRoot := []string{"/bin/sleep", "100"} - cmdSub := []string{"/bin/true"} - podSpecs, ids := createSpecs(cmdRoot, cmdSub) - - // User running inside container can't list '$TMP/blocked' and would fail to - // mount it. - blocked, err := ioutil.TempDir(testutil.TmpDir(), "blocked") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - if err := os.Chmod(blocked, 0700); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", blocked, err) - } - dir := path.Join(blocked, "test") - if err := os.Mkdir(dir, 0755); err != nil { - t.Fatalf("os.MkDir(%q) failed: %v", dir, err) - } - - src, err := ioutil.TempDir(testutil.TmpDir(), "src") - if err != nil { - t.Fatalf("ioutil.TempDir() failed: %v", err) - } - - // Set a random user/group with no access to "blocked" dir. - podSpecs[1].Process.User.UID = 343 - podSpecs[1].Process.User.GID = 2401 - podSpecs[1].Process.Capabilities = nil - - podSpecs[1].Mounts = append(podSpecs[1].Mounts, specs.Mount{ - Destination: dir, - Source: src, - Type: "bind", - }) - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - - conf := testutil.TestConfig(t) - conf.RootDir = rootDir - - pod, cleanup, err := startContainers(conf, podSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Once all containers are started, wait for the child container to exit. - // This means that the volume was mounted properly. - ws, err := pod[1].Wait() - if err != nil { - t.Fatalf("running child container: %v", err) - } - if !ws.Exited() || ws.ExitStatus() != 0 { - t.Fatalf("child container failed, waitStatus: %v", ws) - } -} - -// TestMultiContainerHomeEnvDir tests that the HOME environment variable is set -// for root containers, sub-containers, and exec'ed processes. -func TestMultiContainerHomeEnvDir(t *testing.T) { - // NOTE: Don't use overlay since we need changes to persist to the temp dir - // outside the sandbox. - for testName, conf := range configs(t, noOverlay...) { - t.Run(testName, func(t *testing.T) { - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Create temp files we can write the value of $HOME to. - homeDirs := map[string]*os.File{} - for _, name := range []string{"root", "sub", "exec"} { - homeFile, err := ioutil.TempFile(testutil.TmpDir(), name) - if err != nil { - t.Fatalf("creating temp file: %v", err) - } - homeDirs[name] = homeFile - } - - // We will sleep in the root container in order to ensure that the root - //container doesn't terminate before sub containers can be created. - rootCmd := []string{"/bin/sh", "-c", fmt.Sprintf(`printf "$HOME" > %s; sleep 1000`, homeDirs["root"].Name())} - subCmd := []string{"/bin/sh", "-c", fmt.Sprintf(`printf "$HOME" > %s`, homeDirs["sub"].Name())} - execCmd := fmt.Sprintf(`printf "$HOME" > %s`, homeDirs["exec"].Name()) - - // Setup the containers, a root container and sub container. - specConfig, ids := createSpecs(rootCmd, subCmd) - containers, cleanup, err := startContainers(conf, specConfig, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Exec into the root container synchronously. - if _, err := execute(containers[0], "/bin/sh", "-c", execCmd); err != nil { - t.Errorf("error executing %+v: %v", execCmd, err) - } - - // Wait for the subcontainer to finish. - _, err = containers[1].Wait() - if err != nil { - t.Errorf("wait on child container: %v", err) - } - - // Wait until after `env` has executed. - expectedProc := newProcessBuilder().Cmd("sleep").Process() - if err := waitForProcess(containers[0], expectedProc); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - - // Check the written files. - for name, tmpFile := range homeDirs { - dirBytes, err := ioutil.ReadAll(tmpFile) - if err != nil { - t.Fatalf("reading %s temp file: %v", name, err) - } - got := string(dirBytes) - - want := "/" - if got != want { - t.Errorf("%s $HOME incorrect: got: %q, want: %q", name, got, want) - } - } - - }) - } -} - -func TestMultiContainerEvent(t *testing.T) { - conf := testutil.TestConfig(t) - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Setup the containers. - sleep := []string{"/bin/sleep", "100"} - busy := []string{"/bin/bash", "-c", "i=0 ; while true ; do (( i += 1 )) ; done"} - quick := []string{"/bin/true"} - podSpec, ids := createSpecs(sleep, busy, quick) - containers, cleanup, err := startContainers(conf, podSpec, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - for _, cont := range containers { - t.Logf("Running containerd %s", cont.ID) - } - - // Wait for last container to stabilize the process count that is - // checked further below. - if ws, err := containers[2].Wait(); err != nil || ws != 0 { - t.Fatalf("Container.Wait, status: %v, err: %v", ws, err) - } - expectedPL := []*control.Process{ - newProcessBuilder().Cmd("sleep").Process(), - } - if err := waitForProcessList(containers[0], expectedPL); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - expectedPL = []*control.Process{ - newProcessBuilder().Cmd("bash").Process(), - } - if err := waitForProcessList(containers[1], expectedPL); err != nil { - t.Errorf("failed to wait for bash to start: %v", err) - } - - // Check events for running containers. - var prevUsage uint64 - for _, cont := range containers[:2] { - ret, err := cont.Event() - if err != nil { - t.Errorf("Container.Events(): %v", err) - } - evt := ret.Event - if want := "stats"; evt.Type != want { - t.Errorf("Wrong event type, want: %s, got: %s", want, evt.Type) - } - if cont.ID != evt.ID { - t.Errorf("Wrong container ID, want: %s, got: %s", cont.ID, evt.ID) - } - // One process per remaining container. - if got, want := evt.Data.Pids.Current, uint64(2); got != want { - t.Errorf("Wrong number of PIDs, want: %d, got: %d", want, got) - } - - // Both remaining containers should have nonzero usage, and - // 'busy' should have higher usage than 'sleep'. - usage := evt.Data.CPU.Usage.Total - if usage == 0 { - t.Errorf("Running container should report nonzero CPU usage, but got %d", usage) - } - if usage <= prevUsage { - t.Errorf("Expected container %s to use more than %d ns of CPU, but used %d", cont.ID, prevUsage, usage) - } - t.Logf("Container %s usage: %d", cont.ID, usage) - prevUsage = usage - - // The exited container should have a usage of zero. - if exited := ret.ContainerUsage[containers[2].ID]; exited != 0 { - t.Errorf("Exited container should report 0 CPU usage, but got %d", exited) - } - } - - // Check that stop and destroyed containers return error. - if err := containers[1].Destroy(); err != nil { - t.Fatalf("container.Destroy: %v", err) - } - for _, cont := range containers[1:] { - _, err := cont.Event() - if err == nil { - t.Errorf("Container.Events() should have failed, cid:%s, state: %v", cont.ID, cont.Status) - } - } -} - -// Tests that duplicate variables in the spec are merged into a single one. -func TestDuplicateEnvVariable(t *testing.T) { - conf := testutil.TestConfig(t) - - rootDir, cleanup, err := testutil.SetupRootDir() - if err != nil { - t.Fatalf("error creating root dir: %v", err) - } - defer cleanup() - conf.RootDir = rootDir - - // Create files to dump `env` output. - files := [3]*os.File{} - for i := 0; i < len(files); i++ { - var err error - files[i], err = ioutil.TempFile(testutil.TmpDir(), "env-var-test") - if err != nil { - t.Fatalf("creating temp file: %v", err) - } - defer files[i].Close() - defer os.Remove(files[i].Name()) - } - - // Setup the containers. Use root container to test exec too. - cmd1 := fmt.Sprintf("env > %q; sleep 1000", files[0].Name()) - cmd2 := fmt.Sprintf("env > %q", files[1].Name()) - cmdExec := fmt.Sprintf("env > %q", files[2].Name()) - testSpecs, ids := createSpecs([]string{"/bin/sh", "-c", cmd1}, []string{"/bin/sh", "-c", cmd2}) - testSpecs[0].Process.Env = append(testSpecs[0].Process.Env, "VAR=foo", "VAR=bar") - testSpecs[1].Process.Env = append(testSpecs[1].Process.Env, "VAR=foo", "VAR=bar") - - containers, cleanup, err := startContainers(conf, testSpecs, ids) - if err != nil { - t.Fatalf("error starting containers: %v", err) - } - defer cleanup() - - // Wait until after `env` has executed. - expectedProc := newProcessBuilder().Cmd("sleep").Process() - if err := waitForProcess(containers[0], expectedProc); err != nil { - t.Errorf("failed to wait for sleep to start: %v", err) - } - if ws, err := containers[1].Wait(); err != nil { - t.Errorf("failed to wait container 1: %v", err) - } else if es := ws.ExitStatus(); es != 0 { - t.Errorf("container %s exited with non-zero status: %v", containers[1].ID, es) - } - - execArgs := &control.ExecArgs{ - Filename: "/bin/sh", - Argv: []string{"/bin/sh", "-c", cmdExec}, - Envv: []string{"VAR=foo", "VAR=bar"}, - } - if ws, err := containers[0].executeSync(execArgs); err != nil || ws.ExitStatus() != 0 { - t.Fatalf("exec failed, ws: %v, err: %v", ws, err) - } - - // Now read and check that none of the env has repeated values. - for _, file := range files { - out, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - t.Logf("Checking env %q:\n%s", file.Name(), out) - envs := make(map[string]string) - for _, line := range strings.Split(string(out), "\n") { - if len(line) == 0 { - continue - } - envVar := strings.SplitN(line, "=", 2) - if len(envVar) != 2 { - t.Fatalf("invalid env variable: %s", line) - } - key := envVar[0] - if val, ok := envs[key]; ok { - t.Errorf("env variable %q is duplicated: %q and %q", key, val, envVar[1]) - } - envs[key] = envVar[1] - } - if _, ok := envs["VAR"]; !ok { - t.Errorf("variable VAR missing: %v", envs) - } - } -} diff --git a/runsc/container/shared_volume_test.go b/runsc/container/shared_volume_test.go deleted file mode 100644 index cb5bffb89..000000000 --- a/runsc/container/shared_volume_test.go +++ /dev/null @@ -1,265 +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 container - -import ( - "bytes" - "fmt" - "io/ioutil" - "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.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 := ioutil.TempDir(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(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" - if _, err := execute(c, "/bin/cp", "-f", filename, cpy); 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(t) - 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 := ioutil.TempDir(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 := 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. - if _, err := execute(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(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()) - } -} diff --git a/runsc/flag/BUILD b/runsc/flag/BUILD deleted file mode 100644 index 5cb7604a8..000000000 --- a/runsc/flag/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "flag", - srcs = ["flag.go"], - visibility = ["//:sandbox"], -) diff --git a/runsc/flag/flag_state_autogen.go b/runsc/flag/flag_state_autogen.go new file mode 100644 index 000000000..933063e6c --- /dev/null +++ b/runsc/flag/flag_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package flag diff --git a/runsc/fsgofer/BUILD b/runsc/fsgofer/BUILD deleted file mode 100644 index 3280b74fe..000000000 --- a/runsc/fsgofer/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "fsgofer", - srcs = [ - "fsgofer.go", - "fsgofer_amd64_unsafe.go", - "fsgofer_arm64_unsafe.go", - "fsgofer_unsafe.go", - ], - visibility = ["//runsc:__subpackages__"], - deps = [ - "//pkg/cleanup", - "//pkg/fd", - "//pkg/log", - "//pkg/p9", - "//pkg/sync", - "//pkg/syserr", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "fsgofer_test", - size = "small", - srcs = ["fsgofer_test.go"], - library = ":fsgofer", - deps = [ - "//pkg/fd", - "//pkg/log", - "//pkg/p9", - "//pkg/test/testutil", - "//runsc/specutils", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/fsgofer/filter/BUILD b/runsc/fsgofer/filter/BUILD deleted file mode 100644 index 82b48ef32..000000000 --- a/runsc/fsgofer/filter/BUILD +++ /dev/null @@ -1,26 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "filter", - srcs = [ - "config.go", - "config_amd64.go", - "config_arm64.go", - "extra_filters.go", - "extra_filters_msan.go", - "extra_filters_race.go", - "filter.go", - ], - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/abi/linux", - "//pkg/flipcall", - "//pkg/log", - "//pkg/seccomp", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/fsgofer/filter/filter_amd64_state_autogen.go b/runsc/fsgofer/filter/filter_amd64_state_autogen.go new file mode 100644 index 000000000..0f27e5568 --- /dev/null +++ b/runsc/fsgofer/filter/filter_amd64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build amd64 + +package filter diff --git a/runsc/fsgofer/filter/filter_arm64_state_autogen.go b/runsc/fsgofer/filter/filter_arm64_state_autogen.go new file mode 100644 index 000000000..e87cf5af7 --- /dev/null +++ b/runsc/fsgofer/filter/filter_arm64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build arm64 + +package filter diff --git a/runsc/fsgofer/filter/filter_race_state_autogen.go b/runsc/fsgofer/filter/filter_race_state_autogen.go new file mode 100644 index 000000000..c4a858e80 --- /dev/null +++ b/runsc/fsgofer/filter/filter_race_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build race + +package filter diff --git a/runsc/fsgofer/filter/filter_state_autogen.go b/runsc/fsgofer/filter/filter_state_autogen.go new file mode 100644 index 000000000..41ff99424 --- /dev/null +++ b/runsc/fsgofer/filter/filter_state_autogen.go @@ -0,0 +1,6 @@ +// automatically generated by stateify. + +// +build !msan,!race +// +build msan + +package filter diff --git a/runsc/fsgofer/fsgofer_amd64_unsafe_state_autogen.go b/runsc/fsgofer/fsgofer_amd64_unsafe_state_autogen.go new file mode 100644 index 000000000..df6721aaa --- /dev/null +++ b/runsc/fsgofer/fsgofer_amd64_unsafe_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build amd64 + +package fsgofer diff --git a/runsc/fsgofer/fsgofer_arm64_unsafe_state_autogen.go b/runsc/fsgofer/fsgofer_arm64_unsafe_state_autogen.go new file mode 100644 index 000000000..d2a18c61c --- /dev/null +++ b/runsc/fsgofer/fsgofer_arm64_unsafe_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build arm64 + +package fsgofer diff --git a/runsc/fsgofer/fsgofer_state_autogen.go b/runsc/fsgofer/fsgofer_state_autogen.go new file mode 100644 index 000000000..d2f978fb9 --- /dev/null +++ b/runsc/fsgofer/fsgofer_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package fsgofer diff --git a/runsc/fsgofer/fsgofer_test.go b/runsc/fsgofer/fsgofer_test.go deleted file mode 100644 index 99ea9bd32..000000000 --- a/runsc/fsgofer/fsgofer_test.go +++ /dev/null @@ -1,1122 +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 fsgofer - -import ( - "fmt" - "io/ioutil" - "net" - "os" - "path" - "path/filepath" - "testing" - - "github.com/syndtr/gocapability/capability" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/fd" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/p9" - "gvisor.dev/gvisor/pkg/test/testutil" - "gvisor.dev/gvisor/runsc/specutils" -) - -// Nodoby is the standard UID/GID for the nobody user/group. -const nobody = 65534 - -var allOpenFlags = []p9.OpenFlags{p9.ReadOnly, p9.WriteOnly, p9.ReadWrite} - -var ( - allTypes = []uint32{unix.S_IFREG, unix.S_IFDIR, unix.S_IFLNK} - - // allConfs is set in init(). - allConfs []Config - - rwConfs = []Config{{ROMount: false}} - roConfs = []Config{{ROMount: true}} -) - -func init() { - log.SetLevel(log.Debug) - - allConfs = append(allConfs, rwConfs...) - allConfs = append(allConfs, roConfs...) - - if err := OpenProcSelfFD(); err != nil { - panic(err) - } -} - -func configTestName(conf *Config) string { - if conf.ROMount { - return "ROMount" - } - return "RWMount" -} - -func assertPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("function did not panic") - } - }() - f() -} - -func testReadWrite(f p9.File, flags p9.OpenFlags, content []byte) error { - want := make([]byte, len(content)) - copy(want, content) - - b := []byte("test-1-2-3") - w, err := f.WriteAt(b, uint64(len(content))) - if flags == p9.WriteOnly || flags == p9.ReadWrite { - if err != nil { - return fmt.Errorf("WriteAt(): %v", err) - } - if w != len(b) { - return fmt.Errorf("WriteAt() was partial, got: %d, want: %d", w, len(b)) - } - want = append(want, b...) - } else { - if e, ok := err.(unix.Errno); !ok || e != unix.EBADF { - return fmt.Errorf("WriteAt() should have failed, got: %d, want: EBADFD", err) - } - } - - rBuf := make([]byte, len(want)) - r, err := f.ReadAt(rBuf, 0) - if flags == p9.ReadOnly || flags == p9.ReadWrite { - if err != nil { - return fmt.Errorf("ReadAt(): %v", err) - } - if r != len(rBuf) { - return fmt.Errorf("ReadAt() was partial, got: %d, want: %d", r, len(rBuf)) - } - if string(rBuf) != string(want) { - return fmt.Errorf("ReadAt() wrong data, got: %s, want: %s", string(rBuf), want) - } - } else { - if e, ok := err.(unix.Errno); !ok || e != unix.EBADF { - return fmt.Errorf("ReadAt() should have failed, got: %d, want: EBADFD", err) - } - } - return nil -} - -type state struct { - root *localFile - file *localFile - conf Config - fileType uint32 -} - -func (s state) String() string { - return fmt.Sprintf("type(%v)", s.fileType) -} - -func typeName(fileType uint32) string { - switch fileType { - case unix.S_IFREG: - return "file" - case unix.S_IFDIR: - return "directory" - case unix.S_IFLNK: - return "symlink" - default: - panic(fmt.Sprintf("invalid file type for test: %d", fileType)) - } -} - -func runAll(t *testing.T, test func(*testing.T, state)) { - runCustom(t, allTypes, allConfs, test) -} - -func runCustom(t *testing.T, types []uint32, confs []Config, test func(*testing.T, state)) { - for _, c := range confs { - for _, ft := range types { - name := fmt.Sprintf("%s/%s", configTestName(&c), typeName(ft)) - t.Run(name, func(t *testing.T) { - path, name, err := setup(ft) - if err != nil { - t.Fatalf("%v", err) - } - defer os.RemoveAll(path) - - a, err := NewAttachPoint(path, c) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - t.Fatalf("Attach failed, err: %v", err) - } - - _, file, err := root.Walk([]string{name}) - if err != nil { - root.Close() - t.Fatalf("root.Walk({%q}) failed, err: %v", "symlink", err) - } - - st := state{ - root: root.(*localFile), - file: file.(*localFile), - conf: c, - fileType: ft, - } - test(t, st) - file.Close() - root.Close() - }) - } - } -} - -func setup(fileType uint32) (string, string, error) { - path, err := ioutil.TempDir(testutil.TmpDir(), "root-") - if err != nil { - return "", "", fmt.Errorf("ioutil.TempDir() failed, err: %v", err) - } - - // First attach with writable configuration to setup tree. - a, err := NewAttachPoint(path, Config{}) - if err != nil { - return "", "", err - } - root, err := a.Attach() - if err != nil { - return "", "", fmt.Errorf("Attach failed, err: %v", err) - } - defer root.Close() - - var name string - switch fileType { - case unix.S_IFREG: - name = "file" - fd, f, _, _, err := root.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - return "", "", fmt.Errorf("createFile(root, %q) failed, err: %v", "test", err) - } - if fd != nil { - fd.Close() - } - defer f.Close() - case unix.S_IFDIR: - name = "dir" - if _, err := root.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - return "", "", fmt.Errorf("root.MkDir(%q) failed, err: %v", name, err) - } - case unix.S_IFLNK: - name = "symlink" - if _, err := root.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - return "", "", fmt.Errorf("root.Symlink(%q) failed, err: %v", name, err) - } - default: - panic(fmt.Sprintf("unknown file type %v", fileType)) - } - return path, name, nil -} - -func createFile(dir *localFile, name string) (*localFile, error) { - _, f, _, _, err := dir.Create(name, p9.ReadWrite, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - return nil, err - } - return f.(*localFile), nil -} - -func TestReadWrite(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - child, err := createFile(s.file, "test") - if err != nil { - t.Fatalf("%v: createFile() failed, err: %v", s, err) - } - defer child.Close() - want := []byte("foobar") - w, err := child.WriteAt(want, 0) - if err != nil { - t.Fatalf("%v: Write() failed, err: %v", s, err) - } - if w != len(want) { - t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) - } - for _, flags := range allOpenFlags { - _, l, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) - } - fd, _, _, err := l.Open(flags) - if err != nil { - t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) - } - if fd != nil { - defer fd.Close() - } - if err := testReadWrite(l, flags, want); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) - } - } - }) -} - -func TestCreate(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - for i, flags := range allOpenFlags { - _, l, _, _, err := s.file.Create(fmt.Sprintf("test-%d", i), flags, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("%v, %v: WriteAt() failed, err: %v", s, flags, err) - } - - if err := testReadWrite(l, flags, nil); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, flags, err) - } - } - }) -} - -func checkIDs(f p9.File, uid, gid int) error { - _, _, stat, err := f.GetAttr(p9.AttrMask{UID: true, GID: true}) - if err != nil { - return fmt.Errorf("GetAttr() failed, err: %v", err) - } - if want := p9.UID(uid); stat.UID != want { - return fmt.Errorf("Wrong UID, want: %v, got: %v", want, stat.UID) - } - if want := p9.GID(gid); stat.GID != want { - return fmt.Errorf("Wrong GID, want: %v, got: %v", want, stat.GID) - } - return nil -} - -// TestCreateSetGID checks files/dirs/symlinks are created with the proper -// owner when the parent directory has setgid set, -func TestCreateSetGID(t *testing.T) { - if !specutils.HasCapabilities(capability.CAP_CHOWN) { - t.Skipf("Test requires CAP_CHOWN") - } - - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - // Change group and set setgid to the parent dir. - if err := unix.Chown(s.file.hostPath, os.Getuid(), nobody); err != nil { - t.Fatalf("Chown() failed: %v", err) - } - if err := unix.Chmod(s.file.hostPath, 02777); err != nil { - t.Fatalf("Chmod() failed: %v", err) - } - - t.Run("create", func(t *testing.T) { - _, l, _, _, err := s.file.Create("test", p9.ReadOnly, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("WriteAt() failed: %v", err) - } - defer l.Close() - if err := checkIDs(l, os.Getuid(), os.Getgid()); err != nil { - t.Error(err) - } - }) - - t.Run("mkdir", func(t *testing.T) { - _, err := s.file.Mkdir("test-dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("WriteAt() failed: %v", err) - } - _, l, err := s.file.Walk([]string{"test-dir"}) - if err != nil { - t.Fatalf("Walk() failed: %v", err) - } - defer l.Close() - if err := checkIDs(l, os.Getuid(), os.Getgid()); err != nil { - t.Error(err) - } - }) - - t.Run("symlink", func(t *testing.T) { - if _, err := s.file.Symlink("/some/target", "symlink", p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("Symlink() failed: %v", err) - } - _, l, err := s.file.Walk([]string{"symlink"}) - if err != nil { - t.Fatalf("Walk() failed, err: %v", err) - } - defer l.Close() - if err := checkIDs(l, os.Getuid(), os.Getgid()); err != nil { - t.Error(err) - } - }) - - t.Run("mknod", func(t *testing.T) { - if _, err := s.file.Mknod("nod", p9.ModeRegular|0777, 0, 0, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("Mknod() failed: %v", err) - } - _, l, err := s.file.Walk([]string{"nod"}) - if err != nil { - t.Fatalf("Walk() failed, err: %v", err) - } - defer l.Close() - if err := checkIDs(l, os.Getuid(), os.Getgid()); err != nil { - t.Error(err) - } - }) - }) -} - -// TestReadWriteDup tests that a file opened in any mode can be dup'ed and -// reopened in any other mode. -func TestReadWriteDup(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - child, err := createFile(s.file, "test") - if err != nil { - t.Fatalf("%v: createFile() failed, err: %v", s, err) - } - defer child.Close() - want := []byte("foobar") - w, err := child.WriteAt(want, 0) - if err != nil { - t.Fatalf("%v: Write() failed, err: %v", s, err) - } - if w != len(want) { - t.Fatalf("%v: Write() was partial, got: %d, expected: %d", s, w, len(want)) - } - for _, flags := range allOpenFlags { - _, l, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("%v: Walk(%s) failed, err: %v", s, "test", err) - } - defer l.Close() - if _, _, _, err := l.Open(flags); err != nil { - t.Fatalf("%v: Open(%v) failed, err: %v", s, flags, err) - } - for _, dupFlags := range allOpenFlags { - t.Logf("Original flags: %v, dup flags: %v", flags, dupFlags) - _, dup, err := l.Walk([]string{}) - if err != nil { - t.Fatalf("%v: Walk(<empty>) failed: %v", s, err) - } - defer dup.Close() - fd, _, _, err := dup.Open(dupFlags) - if err != nil { - t.Fatalf("%v: Open(%v) failed: %v", s, flags, err) - } - if fd != nil { - defer fd.Close() - } - if err := testReadWrite(dup, dupFlags, want); err != nil { - t.Fatalf("%v: testReadWrite(%v) failed: %v", s, dupFlags, err) - } - } - } - }) -} - -func TestUnopened(t *testing.T) { - runCustom(t, []uint32{unix.S_IFREG}, allConfs, func(t *testing.T, s state) { - b := []byte("foobar") - if _, err := s.file.WriteAt(b, 0); err != unix.EBADF { - t.Errorf("%v: WriteAt() should have failed, got: %v, expected: unix.EBADF", s, err) - } - if _, err := s.file.ReadAt(b, 0); err != unix.EBADF { - t.Errorf("%v: ReadAt() should have failed, got: %v, expected: unix.EBADF", s, err) - } - if _, err := s.file.Readdir(0, 100); err != unix.EBADF { - t.Errorf("%v: Readdir() should have failed, got: %v, expected: unix.EBADF", s, err) - } - if err := s.file.FSync(); err != unix.EBADF { - t.Errorf("%v: FSync() should have failed, got: %v, expected: unix.EBADF", s, err) - } - }) -} - -// TestOpenOPath is a regression test to ensure that a file that cannot be open -// for read is allowed to be open. This was happening because the control file -// was open with O_PATH, but Open() was not checking for it and allowing the -// control file to be reused. -func TestOpenOPath(t *testing.T) { - runCustom(t, []uint32{unix.S_IFREG}, rwConfs, func(t *testing.T, s state) { - // Fist remove all permissions on the file. - if err := s.file.SetAttr(p9.SetAttrMask{Permissions: true}, p9.SetAttr{Permissions: p9.FileMode(0)}); err != nil { - t.Fatalf("SetAttr(): %v", err) - } - // Then walk to the file again to open a new control file. - filename := filepath.Base(s.file.hostPath) - _, newFile, err := s.root.Walk([]string{filename}) - if err != nil { - t.Fatalf("root.Walk(%q): %v", filename, err) - } - - if newFile.(*localFile).controlReadable { - t.Fatalf("control file didn't open with O_PATH: %+v", newFile) - } - if _, _, _, err := newFile.Open(p9.ReadOnly); err != unix.EACCES { - t.Fatalf("Open() should have failed, got: %v, wanted: EACCES", err) - } - }) -} - -func SetGetAttr(l *localFile, valid p9.SetAttrMask, attr p9.SetAttr) (p9.Attr, error) { - if err := l.SetAttr(valid, attr); err != nil { - return p9.Attr{}, err - } - _, _, a, err := l.GetAttr(p9.AttrMask{}) - if err != nil { - return p9.Attr{}, err - } - return a, nil -} - -func TestSetAttrPerm(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - valid := p9.SetAttrMask{Permissions: true} - attr := p9.SetAttr{Permissions: 0777} - got, err := SetGetAttr(s.file, valid, attr) - if s.fileType == unix.S_IFLNK { - if err == nil { - t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) - } - } else { - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Permissions, err) - } - if got.Mode.Permissions() != attr.Permissions { - t.Errorf("%v: wrong permission, got: %v, expected: %v", s, got.Mode.Permissions(), attr.Permissions) - } - } - }) -} - -func TestSetAttrSize(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - for _, size := range []uint64{1024, 0, 1024 * 1024} { - valid := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: size} - got, err := SetGetAttr(s.file, valid, attr) - if s.fileType == unix.S_IFLNK || s.fileType == unix.S_IFDIR { - if err == nil { - t.Fatalf("%v: SetGetAttr(valid, %v) should have failed", s, attr.Permissions) - } - // Run for one size only, they will all fail the same way. - return - } - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.Size, err) - } - if got.Size != size { - t.Errorf("%v: wrong size, got: %v, expected: %v", s, got.Size, size) - } - } - }) -} - -func TestSetAttrTime(t *testing.T) { - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - valid := p9.SetAttrMask{ATime: true, ATimeNotSystemTime: true} - attr := p9.SetAttr{ATimeSeconds: 123, ATimeNanoSeconds: 456} - got, err := SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.ATimeSeconds, attr.ATimeNanoSeconds, err) - } - if got.ATimeSeconds != 123 { - t.Errorf("%v: wrong ATimeSeconds, got: %v, expected: %v", s, got.ATimeSeconds, 123) - } - if got.ATimeNanoSeconds != 456 { - t.Errorf("%v: wrong ATimeNanoSeconds, got: %v, expected: %v", s, got.ATimeNanoSeconds, 456) - } - - valid = p9.SetAttrMask{MTime: true, MTimeNotSystemTime: true} - attr = p9.SetAttr{MTimeSeconds: 789, MTimeNanoSeconds: 012} - got, err = SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v:%v) failed, err: %v", s, attr.MTimeSeconds, attr.MTimeNanoSeconds, err) - } - if got.MTimeSeconds != 789 { - t.Errorf("%v: wrong MTimeSeconds, got: %v, expected: %v", s, got.MTimeSeconds, 789) - } - if got.MTimeNanoSeconds != 012 { - t.Errorf("%v: wrong MTimeNanoSeconds, got: %v, expected: %v", s, got.MTimeNanoSeconds, 012) - } - }) -} - -func TestSetAttrOwner(t *testing.T) { - if !specutils.HasCapabilities(capability.CAP_CHOWN) { - t.Skipf("SetAttr(owner) test requires CAP_CHOWN, running as %d", os.Getuid()) - } - - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - newUID := os.Getuid() + 1 - valid := p9.SetAttrMask{UID: true} - attr := p9.SetAttr{UID: p9.UID(newUID)} - got, err := SetGetAttr(s.file, valid, attr) - if err != nil { - t.Fatalf("%v: SetGetAttr(valid, %v) failed, err: %v", s, attr.UID, err) - } - if got.UID != p9.UID(newUID) { - t.Errorf("%v: wrong uid, got: %v, expected: %v", s, got.UID, newUID) - } - }) -} - -func TestLink(t *testing.T) { - if !specutils.HasCapabilities(capability.CAP_DAC_READ_SEARCH) { - t.Skipf("Link test requires CAP_DAC_READ_SEARCH, running as %d", os.Getuid()) - } - runCustom(t, allTypes, rwConfs, func(t *testing.T, s state) { - const dirName = "linkdir" - const linkFile = "link" - if _, err := s.root.Mkdir(dirName, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: MkDir(%s) failed, err: %v", s, dirName, err) - } - _, dir, err := s.root.Walk([]string{dirName}) - if err != nil { - t.Fatalf("%v: Walk({%s}) failed, err: %v", s, dirName, err) - } - - err = dir.Link(s.file, linkFile) - if s.fileType == unix.S_IFDIR { - if err != unix.EPERM { - t.Errorf("%v: Link(target, %s) should have failed, got: %v, expected: unix.EPERM", s, linkFile, err) - } - return - } - if err != nil { - t.Errorf("%v: Link(target, %s) failed, err: %v", s, linkFile, err) - } - }) -} - -func TestROMountChecks(t *testing.T) { - const want = unix.EROFS - uid := p9.UID(os.Getuid()) - gid := p9.GID(os.Getgid()) - - runCustom(t, allTypes, roConfs, func(t *testing.T, s state) { - if s.fileType != unix.S_IFLNK { - if _, _, _, err := s.file.Open(p9.WriteOnly); err != want { - t.Errorf("Open() should have failed, got: %v, expected: %v", err, want) - } - if _, _, _, err := s.file.Open(p9.ReadWrite); err != want { - t.Errorf("Open() should have failed, got: %v, expected: %v", err, want) - } - if _, _, _, err := s.file.Open(p9.ReadOnly | p9.OpenTruncate); err != want { - t.Errorf("Open() should have failed, got: %v, expected: %v", err, want) - } - f, _, _, err := s.file.Open(p9.ReadOnly) - if err != nil { - t.Errorf("Open() failed: %v", err) - } - if f != nil { - _ = f.Close() - } - } - - if _, _, _, _, err := s.file.Create("some_file", p9.ReadWrite, 0777, uid, gid); err != want { - t.Errorf("Create() should have failed, got: %v, expected: %v", err, want) - } - if _, err := s.file.Mkdir("some_dir", 0777, uid, gid); err != want { - t.Errorf("MkDir() should have failed, got: %v, expected: %v", err, want) - } - if err := s.file.RenameAt("some_file", s.file, "other_file"); err != want { - t.Errorf("Rename() should have failed, got: %v, expected: %v", err, want) - } - if _, err := s.file.Symlink("some_place", "some_symlink", uid, gid); err != want { - t.Errorf("Symlink() should have failed, got: %v, expected: %v", err, want) - } - if err := s.file.UnlinkAt("some_file", 0); err != want { - t.Errorf("UnlinkAt() should have failed, got: %v, expected: %v", err, want) - } - if err := s.file.Link(s.file, "some_link"); err != want { - t.Errorf("Link() should have failed, got: %v, expected: %v", err, want) - } - if _, err := s.file.Mknod("some-nod", 0777, 1, 2, uid, gid); err != want { - t.Errorf("Mknod() should have failed, got: %v, expected: %v", err, want) - } - - valid := p9.SetAttrMask{Size: true} - attr := p9.SetAttr{Size: 0} - if err := s.file.SetAttr(valid, attr); err != want { - t.Errorf("SetAttr() should have failed, got: %v, expected: %v", err, want) - } - }) -} - -func TestWalkNotFound(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, allConfs, func(t *testing.T, s state) { - if _, _, err := s.file.Walk([]string{"nobody-here"}); err != unix.ENOENT { - t.Errorf("Walk(%q) should have failed, got: %v, expected: unix.ENOENT", "nobody-here", err) - } - if _, _, err := s.file.Walk([]string{"nobody", "here"}); err != unix.ENOENT { - t.Errorf("Walk(%q) should have failed, got: %v, expected: unix.ENOENT", "nobody/here", err) - } - if !s.conf.ROMount { - if _, err := s.file.Mkdir("dir", 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("MkDir(dir) failed, err: %v", err) - } - if _, _, err := s.file.Walk([]string{"dir", "nobody-here"}); err != unix.ENOENT { - t.Errorf("Walk(%q) should have failed, got: %v, expected: unix.ENOENT", "dir/nobody-here", err) - } - } - }) -} - -func TestWalkPanic(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, allConfs, func(t *testing.T, s state) { - for _, name := range []string{".", ".."} { - assertPanic(t, func() { - s.file.Walk([]string{name}) - }) - } - }) -} - -func TestWalkDup(t *testing.T) { - runAll(t, func(t *testing.T, s state) { - _, dup, err := s.file.Walk([]string{}) - if err != nil { - t.Fatalf("%v: Walk(nil) failed, err: %v", s, err) - } - // Check that 'dup' is usable. - if _, _, _, err := dup.GetAttr(p9.AttrMask{}); err != nil { - t.Errorf("%v: GetAttr() failed, err: %v", s, err) - } - }) -} - -func TestWalkMultiple(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - var names []string - var parent p9.File = s.file - for i := 0; i < 5; i++ { - name := fmt.Sprintf("dir%d", i) - names = append(names, name) - - if _, err := parent.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("MkDir(%q) failed, err: %v", name, err) - } - - var err error - _, parent, err = s.file.Walk(names) - if err != nil { - t.Errorf("Walk(%q): %v", name, err) - } - } - }) -} - -func TestReaddir(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - name := "dir" - if _, err := s.file.Mkdir(name, 0777, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: MkDir(%s) failed, err: %v", s, name, err) - } - name = "symlink" - if _, err := s.file.Symlink("/some/target", name, p9.UID(os.Getuid()), p9.GID(os.Getgid())); err != nil { - t.Fatalf("%v: Symlink(%q) failed, err: %v", s, name, err) - } - name = "file" - _, f, _, _, err := s.file.Create(name, p9.ReadWrite, 0555, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("%v: createFile(root, %q) failed, err: %v", s, name, err) - } - f.Close() - - if _, _, _, err := s.file.Open(p9.ReadOnly); err != nil { - t.Fatalf("%v: Open(ReadOnly) failed, err: %v", s, err) - } - - dirents, err := s.file.Readdir(0, 10) - if err != nil { - t.Fatalf("%v: Readdir(0, 10) failed, err: %v", s, err) - } - if len(dirents) != 3 { - t.Fatalf("%v: Readdir(0, 10) wrong number of items, got: %v, expected: 3", s, len(dirents)) - } - var dir, symlink, file bool - for _, d := range dirents { - switch d.Name { - case "dir": - if d.Type != p9.TypeDir { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeDir) - } - dir = true - case "symlink": - if d.Type != p9.TypeSymlink { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeSymlink) - } - symlink = true - case "file": - if d.Type != p9.TypeRegular { - t.Errorf("%v: dirent.Type got: %v, expected: %v", s, d.Type, p9.TypeRegular) - } - file = true - default: - t.Errorf("%v: dirent.Name got: %v", s, d.Name) - } - - _, f, err := s.file.Walk([]string{d.Name}) - if err != nil { - t.Fatalf("%v: Walk({%s}) failed, err: %v", s, d.Name, err) - } - _, _, a, err := f.GetAttr(p9.AttrMask{}) - if err != nil { - t.Fatalf("%v: GetAttr() failed, err: %v", s, err) - } - if d.Type != a.Mode.QIDType() { - t.Errorf("%v: dirent.Type different than GetAttr().Mode.QIDType(), got: %v, expected: %v", s, d.Type, a.Mode.QIDType()) - } - } - if !dir || !symlink || !file { - t.Errorf("%v: Readdir(0, 10) wrong files returned, dir: %v, symlink: %v, file: %v", s, dir, symlink, file) - } - }) -} - -// Test that attach point can be written to when it points to a file, e.g. -// /etc/hosts. -func TestAttachFile(t *testing.T) { - conf := Config{ROMount: false} - dir, err := ioutil.TempDir("", "root-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(dir) - - path := path.Join(dir, "test") - if _, err := os.Create(path); err != nil { - t.Fatalf("os.Create(%q) failed, err: %v", path, err) - } - - a, err := NewAttachPoint(path, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - t.Fatalf("Attach failed, err: %v", err) - } - - if _, _, _, err := root.Open(p9.ReadWrite); err != nil { - t.Fatalf("Open(ReadWrite) failed, err: %v", err) - } - defer root.Close() - - b := []byte("foobar") - w, err := root.WriteAt(b, 0) - if err != nil { - t.Fatalf("Write() failed, err: %v", err) - } - if w != len(b) { - t.Fatalf("Write() was partial, got: %d, expected: %d", w, len(b)) - } - rBuf := make([]byte, len(b)) - r, err := root.ReadAt(rBuf, 0) - if err != nil { - t.Fatalf("ReadAt() failed, err: %v", err) - } - if r != len(rBuf) { - t.Fatalf("ReadAt() was partial, got: %d, expected: %d", r, len(rBuf)) - } - if string(rBuf) != "foobar" { - t.Fatalf("ReadAt() wrong data, got: %s, expected: %s", string(rBuf), "foobar") - } -} - -func TestAttachInvalidType(t *testing.T) { - dir, err := ioutil.TempDir("", "attach-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(dir) - - fifo := filepath.Join(dir, "fifo") - if err := unix.Mkfifo(fifo, 0755); err != nil { - t.Fatalf("Mkfifo(%q): %v", fifo, err) - } - - dirFile, err := os.Open(dir) - if err != nil { - t.Fatalf("Open(%s): %v", dir, err) - } - defer dirFile.Close() - - // Bind a socket via /proc to be sure that a length of a socket path - // is less than UNIX_PATH_MAX. - socket := filepath.Join(fmt.Sprintf("/proc/self/fd/%d", dirFile.Fd()), "socket") - l, err := net.Listen("unix", socket) - if err != nil { - t.Fatalf("net.Listen(unix, %q): %v", socket, err) - } - defer l.Close() - - for _, tc := range []struct { - name string - path string - }{ - {name: "fifo", path: fifo}, - {name: "socket", path: socket}, - } { - t.Run(tc.name, func(t *testing.T) { - conf := Config{ROMount: false} - a, err := NewAttachPoint(tc.path, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - f, err := a.Attach() - if f != nil || err == nil { - t.Fatalf("Attach should have failed, got (%v, %v)", f, err) - } - }) - } -} - -func TestDoubleAttachError(t *testing.T) { - conf := Config{ROMount: false} - root, err := ioutil.TempDir("", "root-") - if err != nil { - t.Fatalf("ioutil.TempDir() failed, err: %v", err) - } - defer os.RemoveAll(root) - a, err := NewAttachPoint(root, conf) - if err != nil { - t.Fatalf("NewAttachPoint failed: %v", err) - } - - if _, err := a.Attach(); err != nil { - t.Fatalf("Attach failed: %v", err) - } - if _, err := a.Attach(); err == nil { - t.Fatalf("Attach should have failed, got %v want non-nil", err) - } -} - -func TestTruncate(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - child, err := createFile(s.file, "test") - if err != nil { - t.Fatalf("createFile() failed: %v", err) - } - defer child.Close() - want := []byte("foobar") - w, err := child.WriteAt(want, 0) - if err != nil { - t.Fatalf("Write() failed: %v", err) - } - if w != len(want) { - t.Fatalf("Write() was partial, got: %d, expected: %d", w, len(want)) - } - - _, l, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("Walk(%s) failed: %v", "test", err) - } - if _, _, _, err := l.Open(p9.ReadOnly | p9.OpenTruncate); err != nil { - t.Fatalf("Open() failed: %v", err) - } - _, mask, attr, err := l.GetAttr(p9.AttrMask{Size: true}) - if err != nil { - t.Fatalf("GetAttr() failed: %v", err) - } - if !mask.Size { - t.Fatalf("GetAttr() didn't return size: %+v", mask) - } - if attr.Size != 0 { - t.Fatalf("truncate didn't work, want: 0, got: %d", attr.Size) - } - }) -} - -func TestMknod(t *testing.T) { - runCustom(t, []uint32{unix.S_IFDIR}, rwConfs, func(t *testing.T, s state) { - _, err := s.file.Mknod("test", p9.ModeRegular|0777, 1, 2, p9.UID(os.Getuid()), p9.GID(os.Getgid())) - if err != nil { - t.Fatalf("Mknod() failed: %v", err) - } - - _, f, err := s.file.Walk([]string{"test"}) - if err != nil { - t.Fatalf("Walk() failed: %v", err) - } - fd, _, _, err := f.Open(p9.ReadWrite) - if err != nil { - t.Fatalf("Open() failed: %v", err) - } - if fd != nil { - defer fd.Close() - } - if err := testReadWrite(f, p9.ReadWrite, nil); err != nil { - t.Fatalf("testReadWrite() failed: %v", err) - } - }) -} - -func BenchmarkWalkOne(b *testing.B) { - path, name, err := setup(unix.S_IFDIR) - if err != nil { - b.Fatalf("%v", err) - } - defer os.RemoveAll(path) - - a, err := NewAttachPoint(path, Config{}) - if err != nil { - b.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - b.Fatalf("Attach failed, err: %v", err) - } - defer root.Close() - - names := []string{name} - files := make([]p9.File, 0, 1000) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, file, err := root.Walk(names) - if err != nil { - b.Fatalf("Walk(%q): %v", name, err) - } - files = append(files, file) - - // Avoid running out of FDs. - if len(files) == cap(files) { - b.StopTimer() - for _, file := range files { - file.Close() - } - files = files[:0] - b.StartTimer() - } - } - - b.StopTimer() - for _, file := range files { - file.Close() - } -} - -func BenchmarkCreate(b *testing.B) { - path, _, err := setup(unix.S_IFDIR) - if err != nil { - b.Fatalf("%v", err) - } - defer os.RemoveAll(path) - - a, err := NewAttachPoint(path, Config{}) - if err != nil { - b.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - b.Fatalf("Attach failed, err: %v", err) - } - defer root.Close() - - files := make([]p9.File, 0, 500) - fds := make([]*fd.FD, 0, 500) - uid := p9.UID(os.Getuid()) - gid := p9.GID(os.Getgid()) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - name := fmt.Sprintf("same-%d", i) - fd, file, _, _, err := root.Create(name, p9.ReadOnly, 0777, uid, gid) - if err != nil { - b.Fatalf("Create(%q): %v", name, err) - } - files = append(files, file) - if fd != nil { - fds = append(fds, fd) - } - - // Avoid running out of FDs. - if len(files) == cap(files) { - b.StopTimer() - for _, file := range files { - file.Close() - } - files = files[:0] - for _, fd := range fds { - fd.Close() - } - fds = fds[:0] - b.StartTimer() - } - } - - b.StopTimer() - for _, file := range files { - file.Close() - } - for _, fd := range fds { - fd.Close() - } -} - -func BenchmarkCreateDiffOwner(b *testing.B) { - if !specutils.HasCapabilities(capability.CAP_CHOWN) { - b.Skipf("Test requires CAP_CHOWN") - } - - path, _, err := setup(unix.S_IFDIR) - if err != nil { - b.Fatalf("%v", err) - } - defer os.RemoveAll(path) - - a, err := NewAttachPoint(path, Config{}) - if err != nil { - b.Fatalf("NewAttachPoint failed: %v", err) - } - root, err := a.Attach() - if err != nil { - b.Fatalf("Attach failed, err: %v", err) - } - defer root.Close() - - files := make([]p9.File, 0, 500) - fds := make([]*fd.FD, 0, 500) - gid := p9.GID(os.Getgid()) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - name := fmt.Sprintf("diff-%d", i) - fd, file, _, _, err := root.Create(name, p9.ReadOnly, 0777, nobody, gid) - if err != nil { - b.Fatalf("Create(%q): %v", name, err) - } - files = append(files, file) - if fd != nil { - fds = append(fds, fd) - } - - // Avoid running out of FDs. - if len(files) == cap(files) { - b.StopTimer() - for _, file := range files { - file.Close() - } - files = files[:0] - for _, fd := range fds { - fd.Close() - } - fds = fds[:0] - b.StartTimer() - } - } - - b.StopTimer() - for _, file := range files { - file.Close() - } - for _, fd := range fds { - fd.Close() - } -} diff --git a/runsc/fsgofer/fsgofer_unsafe_state_autogen.go b/runsc/fsgofer/fsgofer_unsafe_state_autogen.go new file mode 100644 index 000000000..d2f978fb9 --- /dev/null +++ b/runsc/fsgofer/fsgofer_unsafe_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package fsgofer diff --git a/runsc/mitigate/BUILD b/runsc/mitigate/BUILD deleted file mode 100644 index 3b0342d18..000000000 --- a/runsc/mitigate/BUILD +++ /dev/null @@ -1,20 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "mitigate", - srcs = [ - "cpu.go", - "mitigate.go", - ], - deps = ["@in_gopkg_yaml_v2//:go_default_library"], -) - -go_test( - name = "mitigate_test", - size = "small", - srcs = ["cpu_test.go"], - library = ":mitigate", - deps = ["@com_github_google_go_cmp//cmp:go_default_library"], -) diff --git a/runsc/mitigate/cpu.go b/runsc/mitigate/cpu.go deleted file mode 100644 index ae4ce9579..000000000 --- a/runsc/mitigate/cpu.go +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2021 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 mitigate - -import ( - "fmt" - "io/ioutil" - "regexp" - "strconv" - "strings" -) - -const ( - // constants of coomm - meltdown = "cpu_meltdown" - l1tf = "l1tf" - mds = "mds" - swapgs = "swapgs" - taa = "taa" -) - -const ( - processorKey = "processor" - vendorIDKey = "vendor_id" - cpuFamilyKey = "cpu family" - modelKey = "model" - physicalIDKey = "physical id" - coreIDKey = "core id" - bugsKey = "bugs" -) - -const ( - cpuOnlineTemplate = "/sys/devices/system/cpu/cpu%d/online" -) - -// cpuSet contains a map of all CPUs on the system, mapped -// by Physical ID and CoreIDs. threads with the same -// Core and Physical ID are Hyperthread pairs. -type cpuSet map[cpuID]*threadGroup - -// newCPUSet creates a CPUSet from data read from /proc/cpuinfo. -func newCPUSet(data []byte, vulnerable func(*thread) bool) (cpuSet, error) { - processors, err := getThreads(string(data)) - if err != nil { - return nil, err - } - - set := make(cpuSet) - for _, p := range processors { - // Each ID is of the form physicalID:coreID. Hyperthread pairs - // have identical physical and core IDs. We need to match - // Hyperthread pairs so that we can shutdown all but one per - // pair. - core, ok := set[p.id] - if !ok { - core = &threadGroup{} - set[p.id] = core - } - core.isVulnerable = core.isVulnerable || vulnerable(p) - core.threads = append(core.threads, p) - } - return set, nil -} - -// String implements the String method for CPUSet. -func (c cpuSet) String() string { - ret := "" - for _, tg := range c { - ret += fmt.Sprintf("%s\n", tg) - } - return ret -} - -// getRemainingList returns the list of threads that will remain active -// after mitigation. -func (c cpuSet) getRemainingList() []*thread { - threads := make([]*thread, 0, len(c)) - for _, core := range c { - // If we're vulnerable, take only one thread from the pair. - if core.isVulnerable { - threads = append(threads, core.threads[0]) - continue - } - // Otherwise don't shutdown anything. - threads = append(threads, core.threads...) - } - return threads -} - -// getShutdownList returns the list of threads that will be shutdown on -// mitigation. -func (c cpuSet) getShutdownList() []*thread { - threads := make([]*thread, 0) - for _, core := range c { - // Only if we're vulnerable do shutdown anything. In this case, - // shutdown all but the first entry. - if core.isVulnerable && len(core.threads) > 1 { - threads = append(threads, core.threads[1:]...) - } - } - return threads -} - -// threadGroup represents Hyperthread pairs on the same physical/core ID. -type threadGroup struct { - threads []*thread - isVulnerable bool -} - -// String implements the String method for threadGroup. -func (c *threadGroup) String() string { - ret := fmt.Sprintf("ThreadGroup:\nIsVulnerable: %t\n", c.isVulnerable) - for _, processor := range c.threads { - ret += fmt.Sprintf("%s\n", processor) - } - return ret -} - -// getThreads returns threads structs from reading /proc/cpuinfo. -func getThreads(data string) ([]*thread, error) { - // Each processor entry should start with the - // processor key. Find the beginings of each. - r := buildRegex(processorKey, `\d+`) - indices := r.FindAllStringIndex(data, -1) - if len(indices) < 1 { - return nil, fmt.Errorf("no cpus found for: %s", data) - } - - // Add the ending index for last entry. - indices = append(indices, []int{len(data), -1}) - - // Valid cpus are now defined by strings in between - // indexes (e.g. data[index[i], index[i+1]]). - // There should be len(indicies) - 1 CPUs - // since the last index is the end of the string. - var cpus = make([]*thread, 0, len(indices)-1) - // Find each string that represents a CPU. These begin "processor". - for i := 1; i < len(indices); i++ { - start := indices[i-1][0] - end := indices[i][0] - // Parse the CPU entry, which should be between start/end. - c, err := newThread(data[start:end]) - if err != nil { - return nil, err - } - cpus = append(cpus, c) - } - return cpus, nil -} - -// cpuID for each thread is defined by the physical and -// core IDs. If equal, two threads are Hyperthread pairs. -type cpuID struct { - physicalID int64 - coreID int64 -} - -// type cpu represents pertinent info about a cpu. -type thread struct { - processorNumber int64 // the processor number of this CPU. - vendorID string // the vendorID of CPU (e.g. AuthenticAMD). - cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake). - model int64 // CPU model number (e.g. 85 for CascadeLake/Skylake). - id cpuID // id for this thread - bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field. -} - -// newThread parses a CPU from a single cpu entry from /proc/cpuinfo. -func newThread(data string) (*thread, error) { - processor, err := parseProcessor(data) - if err != nil { - return nil, err - } - - vendorID, err := parseVendorID(data) - if err != nil { - return nil, err - } - - cpuFamily, err := parseCPUFamily(data) - if err != nil { - return nil, err - } - - model, err := parseModel(data) - if err != nil { - return nil, err - } - - physicalID, err := parsePhysicalID(data) - if err != nil { - return nil, err - } - - coreID, err := parseCoreID(data) - if err != nil { - return nil, err - } - - bugs, err := parseBugs(data) - if err != nil { - return nil, err - } - - return &thread{ - processorNumber: processor, - vendorID: vendorID, - cpuFamily: cpuFamily, - model: model, - id: cpuID{ - physicalID: physicalID, - coreID: coreID, - }, - bugs: bugs, - }, nil -} - -// String implements the String method for thread. -func (t *thread) String() string { - template := `CPU: %d -CPU ID: %+v -Vendor: %s -Family/Model: %d/%d -Bugs: %s -` - bugs := make([]string, 0) - for bug := range t.bugs { - bugs = append(bugs, bug) - } - - return fmt.Sprintf(template, t.processorNumber, t.id, t.vendorID, t.cpuFamily, t.model, strings.Join(bugs, ",")) -} - -// shutdown turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online. -func (t *thread) shutdown() error { - cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) - return ioutil.WriteFile(cpuPath, []byte{'0'}, 0644) -} - -// List of pertinent side channel vulnerablilites. -// For mds, see: https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html. -var vulnerabilities = []string{ - meltdown, - l1tf, - mds, - swapgs, - taa, -} - -// isVulnerable checks if a CPU is vulnerable to pertinent bugs. -func (t *thread) isVulnerable() bool { - for _, bug := range vulnerabilities { - if _, ok := t.bugs[bug]; ok { - return true - } - } - return false -} - -// isActive checks if a CPU is active from /sys/devices/system/cpu/cpu{N}/online -// If the file does not exist (ioutil returns in error), we assume the CPU is on. -func (t *thread) isActive() bool { - cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) - data, err := ioutil.ReadFile(cpuPath) - if err != nil { - return true - } - return len(data) > 0 && data[0] != '0' -} - -// similarTo checks family/model/bugs fields for equality of two -// processors. -func (t *thread) similarTo(other *thread) bool { - if t.vendorID != other.vendorID { - return false - } - - if other.cpuFamily != t.cpuFamily { - return false - } - - if other.model != t.model { - return false - } - - if len(other.bugs) != len(t.bugs) { - return false - } - - for bug := range t.bugs { - if _, ok := other.bugs[bug]; !ok { - return false - } - } - return true -} - -// parseProcessor grabs the processor field from /proc/cpuinfo output. -func parseProcessor(data string) (int64, error) { - return parseIntegerResult(data, processorKey) -} - -// parseVendorID grabs the vendor_id field from /proc/cpuinfo output. -func parseVendorID(data string) (string, error) { - return parseRegex(data, vendorIDKey, `[\w\d]+`) -} - -// parseCPUFamily grabs the cpu family field from /proc/cpuinfo output. -func parseCPUFamily(data string) (int64, error) { - return parseIntegerResult(data, cpuFamilyKey) -} - -// parseModel grabs the model field from /proc/cpuinfo output. -func parseModel(data string) (int64, error) { - return parseIntegerResult(data, modelKey) -} - -// parsePhysicalID parses the physical id field. -func parsePhysicalID(data string) (int64, error) { - return parseIntegerResult(data, physicalIDKey) -} - -// parseCoreID parses the core id field. -func parseCoreID(data string) (int64, error) { - return parseIntegerResult(data, coreIDKey) -} - -// parseBugs grabs the bugs field from /proc/cpuinfo output. -func parseBugs(data string) (map[string]struct{}, error) { - result, err := parseRegex(data, bugsKey, `[\d\w\s]*`) - if err != nil { - return nil, err - } - bugs := strings.Split(result, " ") - ret := make(map[string]struct{}, len(bugs)) - for _, bug := range bugs { - ret[bug] = struct{}{} - } - return ret, nil -} - -// parseIntegerResult parses fields expecting an integer. -func parseIntegerResult(data, key string) (int64, error) { - result, err := parseRegex(data, key, `\d+`) - if err != nil { - return 0, err - } - return strconv.ParseInt(result, 0, 64) -} - -// buildRegex builds a regex for parsing each CPU field. -func buildRegex(key, match string) *regexp.Regexp { - reg := fmt.Sprintf(`(?m)^%s\s*:\s*(.*)$`, key) - return regexp.MustCompile(reg) -} - -// parseRegex parses data with key inserted into a standard regex template. -func parseRegex(data, key, match string) (string, error) { - r := buildRegex(key, match) - matches := r.FindStringSubmatch(data) - if len(matches) < 2 { - return "", fmt.Errorf("failed to match key %s: %s", key, data) - } - return matches[1], nil -} diff --git a/runsc/mitigate/cpu_test.go b/runsc/mitigate/cpu_test.go deleted file mode 100644 index 21c12f586..000000000 --- a/runsc/mitigate/cpu_test.go +++ /dev/null @@ -1,504 +0,0 @@ -// Copyright 2021 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 mitigate - -import ( - "fmt" - "io/ioutil" - "strings" - "testing" -) - -// cpuTestCase represents data from CPUs that will be mitigated. -type cpuTestCase struct { - name string - vendorID string - family int - model int - modelName string - bugs string - physicalCores int - cores int - threadsPerCore int -} - -var cascadeLake4 = cpuTestCase{ - name: "CascadeLake", - vendorID: "GenuineIntel", - family: 6, - model: 85, - modelName: "Intel(R) Xeon(R) CPU", - bugs: "spectre_v1 spectre_v2 spec_store_bypass mds swapgs taa", - physicalCores: 1, - cores: 2, - threadsPerCore: 2, -} - -var haswell2 = cpuTestCase{ - name: "Haswell", - vendorID: "GenuineIntel", - family: 6, - model: 63, - modelName: "Intel(R) Xeon(R) CPU", - bugs: "cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs", - physicalCores: 1, - cores: 1, - threadsPerCore: 2, -} - -var haswell2core = cpuTestCase{ - name: "Haswell2Physical", - vendorID: "GenuineIntel", - family: 6, - model: 63, - modelName: "Intel(R) Xeon(R) CPU", - bugs: "cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs", - physicalCores: 2, - cores: 1, - threadsPerCore: 1, -} - -var amd8 = cpuTestCase{ - name: "AMD", - vendorID: "AuthenticAMD", - family: 23, - model: 49, - modelName: "AMD EPYC 7B12", - bugs: "sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass", - physicalCores: 4, - cores: 1, - threadsPerCore: 2, -} - -// makeCPUString makes a string formated like /proc/cpuinfo for each cpuTestCase -func (tc cpuTestCase) makeCPUString() string { - template := `processor : %d -vendor_id : %s -cpu family : %d -model : %d -model name : %s -physical id : %d -core id : %d -cpu cores : %d -bugs : %s -` - ret := `` - for i := 0; i < tc.physicalCores; i++ { - for j := 0; j < tc.cores; j++ { - for k := 0; k < tc.threadsPerCore; k++ { - processorNum := (i*tc.cores+j)*tc.threadsPerCore + k - ret += fmt.Sprintf(template, - processorNum, /*processor*/ - tc.vendorID, /*vendor_id*/ - tc.family, /*cpu family*/ - tc.model, /*model*/ - tc.modelName, /*model name*/ - i, /*physical id*/ - j, /*core id*/ - tc.cores*tc.physicalCores, /*cpu cores*/ - tc.bugs /*bugs*/) - } - } - } - return ret -} - -// TestMockCPUSet tests mock cpu test cases against the cpuSet functions. -func TestMockCPUSet(t *testing.T) { - for _, tc := range []struct { - testCase cpuTestCase - isVulnerable bool - }{ - { - testCase: amd8, - isVulnerable: false, - }, - { - testCase: haswell2, - isVulnerable: true, - }, - { - testCase: haswell2core, - isVulnerable: true, - }, - - { - testCase: cascadeLake4, - isVulnerable: true, - }, - } { - t.Run(tc.testCase.name, func(t *testing.T) { - data := tc.testCase.makeCPUString() - vulnerable := func(t *thread) bool { - return t.isVulnerable() - } - set, err := newCPUSet([]byte(data), vulnerable) - if err != nil { - t.Fatalf("Failed to ") - } - remaining := set.getRemainingList() - // In the non-vulnerable case, no cores should be shutdown so all should remain. - want := tc.testCase.physicalCores * tc.testCase.cores * tc.testCase.threadsPerCore - if tc.isVulnerable { - want = tc.testCase.physicalCores * tc.testCase.cores - } - - if want != len(remaining) { - t.Fatalf("Failed to shutdown the correct number of cores: want: %d got: %d", want, len(remaining)) - } - - if !tc.isVulnerable { - return - } - - // If the set is vulnerable, we expect only 1 thread per hyperthread pair. - for _, r := range remaining { - if _, ok := set[r.id]; !ok { - t.Fatalf("Entry %+v not in map, there must be two entries in the same thread group.", r) - } - delete(set, r.id) - } - }) - } -} - -// TestGetCPU tests basic parsing of single CPU strings from reading -// /proc/cpuinfo. -func TestGetCPU(t *testing.T) { - data := `processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 85 -physical id: 0 -core id : 0 -bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit -` - want := thread{ - processorNumber: 0, - vendorID: "GenuineIntel", - cpuFamily: 6, - model: 85, - id: cpuID{ - physicalID: 0, - coreID: 0, - }, - bugs: map[string]struct{}{ - "cpu_meltdown": struct{}{}, - "spectre_v1": struct{}{}, - "spectre_v2": struct{}{}, - "spec_store_bypass": struct{}{}, - "l1tf": struct{}{}, - "mds": struct{}{}, - "swapgs": struct{}{}, - "taa": struct{}{}, - "itlb_multihit": struct{}{}, - }, - } - - got, err := newThread(data) - if err != nil { - t.Fatalf("getCpu failed with error: %v", err) - } - - if !want.similarTo(got) { - t.Fatalf("Failed cpus not similar: got: %+v, want: %+v", got, want) - } - - if !got.isVulnerable() { - t.Fatalf("Failed: cpu should be vulnerable.") - } -} - -func TestInvalid(t *testing.T) { - result, err := getThreads(`something not a processor`) - if err == nil { - t.Fatalf("getCPU set didn't return an error: %+v", result) - } - - if !strings.Contains(err.Error(), "no cpus") { - t.Fatalf("Incorrect error returned: %v", err) - } -} - -// TestCPUSet tests getting the right number of CPUs from -// parsing full output of /proc/cpuinfo. -func TestCPUSet(t *testing.T) { - data := `processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 63 -model name : Intel(R) Xeon(R) CPU @ 2.30GHz -stepping : 0 -microcode : 0x1 -cpu MHz : 2299.998 -cache size : 46080 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities -bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs -bogomips : 4599.99 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: - -processor : 1 -vendor_id : GenuineIntel -cpu family : 6 -model : 63 -model name : Intel(R) Xeon(R) CPU @ 2.30GHz -stepping : 0 -microcode : 0x1 -cpu MHz : 2299.998 -cache size : 46080 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -apicid : 1 -initial apicid : 1 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities -bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs -bogomips : 4599.99 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management: -` - cpuSet, err := getThreads(data) - if err != nil { - t.Fatalf("getCPUSet failed: %v", err) - } - - wantCPULen := 2 - if len(cpuSet) != wantCPULen { - t.Fatalf("Num CPU mismatch: want: %d, got: %d", wantCPULen, len(cpuSet)) - } - - wantCPU := thread{ - vendorID: "GenuineIntel", - cpuFamily: 6, - model: 63, - bugs: map[string]struct{}{ - "cpu_meltdown": struct{}{}, - "spectre_v1": struct{}{}, - "spectre_v2": struct{}{}, - "spec_store_bypass": struct{}{}, - "l1tf": struct{}{}, - "mds": struct{}{}, - "swapgs": struct{}{}, - }, - } - - for _, c := range cpuSet { - if !wantCPU.similarTo(c) { - t.Fatalf("Failed cpus not equal: got: %+v, want: %+v", c, wantCPU) - } - } -} - -// TestReadFile is a smoke test for parsing methods. -func TestReadFile(t *testing.T) { - data, err := ioutil.ReadFile("/proc/cpuinfo") - if err != nil { - t.Fatalf("Failed to read cpuinfo: %v", err) - } - - vulnerable := func(t *thread) bool { - return t.isVulnerable() - } - - set, err := newCPUSet(data, vulnerable) - if err != nil { - t.Fatalf("Failed to parse CPU data %v\n%s", err, data) - } - - if len(set) < 1 { - t.Fatalf("Failed to parse any CPUs: %d", len(set)) - } - - t.Log(set) -} - -// TestVulnerable tests if the isVulnerable method is correct -// among known CPUs in GCP. -func TestVulnerable(t *testing.T) { - const haswell = `processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 63 -model name : Intel(R) Xeon(R) CPU @ 2.30GHz -stepping : 0 -microcode : 0x1 -cpu MHz : 2299.998 -cache size : 46080 KB -physical id : 0 -siblings : 4 -core id : 0 -cpu cores : 2 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities -bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs -bogomips : 4599.99 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management:` - - const skylake = `processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 85 -model name : Intel(R) Xeon(R) CPU @ 2.00GHz -stepping : 3 -microcode : 0x1 -cpu MHz : 2000.180 -cache size : 39424 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves arat md_clear arch_capabilities -bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa -bogomips : 4000.36 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management:` - - const cascade = `processor : 0 -vendor_id : GenuineIntel -cpu family : 6 -model : 85 -model name : Intel(R) Xeon(R) CPU -stepping : 7 -microcode : 0x1 -cpu MHz : 2800.198 -cache size : 33792 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 - ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmu -lqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr -efetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid r -tm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves a -rat avx512_vnni md_clear arch_capabilities -bugs : spectre_v1 spectre_v2 spec_store_bypass mds swapgs taa -bogomips : 5600.39 -clflush size : 64 -cache_alignment : 64 -address sizes : 46 bits physical, 48 bits virtual -power management:` - - const amd = `processor : 0 -vendor_id : AuthenticAMD -cpu family : 23 -model : 49 -model name : AMD EPYC 7B12 -stepping : 0 -microcode : 0x1000065 -cpu MHz : 2250.000 -cache size : 512 KB -physical id : 0 -siblings : 2 -core id : 0 -cpu cores : 1 -apicid : 0 -initial apicid : 0 -fpu : yes -fpu_exception : yes -cpuid level : 13 -wp : yes -flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext ssbd ibrs ibpb stibp vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 clzero xsaveerptr arat npt nrip_save umip rdpid -bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass -bogomips : 4500.00 -TLB size : 3072 4K pages -clflush size : 64 -cache_alignment : 64 -address sizes : 48 bits physical, 48 bits virtual -power management:` - - for _, tc := range []struct { - name string - cpuString string - vulnerable bool - }{ - { - name: "haswell", - cpuString: haswell, - vulnerable: true, - }, { - name: "skylake", - cpuString: skylake, - vulnerable: true, - }, { - name: "amd", - cpuString: amd, - vulnerable: false, - }, - } { - t.Run(tc.name, func(t *testing.T) { - set, err := getThreads(tc.cpuString) - if err != nil { - t.Fatalf("Failed to getCPUSet:%v\n %s", err, tc.cpuString) - } - - if len(set) < 1 { - t.Fatalf("Returned empty cpu set: %v", set) - } - - for _, c := range set { - got := func() bool { - return c.isVulnerable() - }() - - if got != tc.vulnerable { - t.Fatalf("Mismatch vulnerable for cpu %+s: got %t want: %t", tc.name, tc.vulnerable, got) - } - } - }) - } -} diff --git a/runsc/mitigate/mitigate.go b/runsc/mitigate/mitigate.go deleted file mode 100644 index 51d5449b6..000000000 --- a/runsc/mitigate/mitigate.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2021 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 mitigate provides libraries for the mitigate command. The -// mitigate command mitigates side channel attacks such as MDS. Mitigate -// shuts down CPUs via /sys/devices/system/cpu/cpu{N}/online. In addition, -// the mitigate also handles computing available CPU in kubernetes kube_config -// files. -package mitigate diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD deleted file mode 100644 index f0a551a1e..000000000 --- a/runsc/sandbox/BUILD +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defs.bzl", "go_library") - -package(licenses = ["notice"]) - -go_library( - name = "sandbox", - srcs = [ - "network.go", - "network_unsafe.go", - "sandbox.go", - ], - visibility = [ - "//runsc:__subpackages__", - ], - deps = [ - "//pkg/cleanup", - "//pkg/control/client", - "//pkg/control/server", - "//pkg/log", - "//pkg/sentry/control", - "//pkg/sentry/platform", - "//pkg/sync", - "//pkg/tcpip/header", - "//pkg/tcpip/stack", - "//pkg/urpc", - "//runsc/boot", - "//runsc/boot/platforms", - "//runsc/cgroup", - "//runsc/config", - "//runsc/console", - "//runsc/specutils", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@com_github_vishvananda_netlink//:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) diff --git a/runsc/sandbox/sandbox_state_autogen.go b/runsc/sandbox/sandbox_state_autogen.go new file mode 100644 index 000000000..79ebc2220 --- /dev/null +++ b/runsc/sandbox/sandbox_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package sandbox diff --git a/runsc/sandbox/sandbox_unsafe_state_autogen.go b/runsc/sandbox/sandbox_unsafe_state_autogen.go new file mode 100644 index 000000000..79ebc2220 --- /dev/null +++ b/runsc/sandbox/sandbox_unsafe_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package sandbox diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD deleted file mode 100644 index 679d8bc8e..000000000 --- a/runsc/specutils/BUILD +++ /dev/null @@ -1,34 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "specutils", - srcs = [ - "cri.go", - "fs.go", - "namespace.go", - "specutils.go", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/abi/linux", - "//pkg/bits", - "//pkg/log", - "//pkg/sentry/kernel/auth", - "//runsc/config", - "@com_github_cenkalti_backoff//:go_default_library", - "@com_github_mohae_deepcopy//:go_default_library", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", - "@org_golang_x_sys//unix:go_default_library", - ], -) - -go_test( - name = "specutils_test", - size = "small", - srcs = ["specutils_test.go"], - library = ":specutils", - deps = ["@com_github_opencontainers_runtime_spec//specs-go:go_default_library"], -) diff --git a/runsc/specutils/seccomp/BUILD b/runsc/specutils/seccomp/BUILD deleted file mode 100644 index 3520f2d6d..000000000 --- a/runsc/specutils/seccomp/BUILD +++ /dev/null @@ -1,34 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "seccomp", - srcs = [ - "audit_amd64.go", - "audit_arm64.go", - "seccomp.go", - ], - visibility = ["//:sandbox"], - deps = [ - "//pkg/abi/linux", - "//pkg/bpf", - "//pkg/log", - "//pkg/seccomp", - "//pkg/sentry/kernel", - "//pkg/sentry/syscalls/linux", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - ], -) - -go_test( - name = "seccomp_test", - size = "small", - srcs = ["seccomp_test.go"], - library = ":seccomp", - deps = [ - "//pkg/binary", - "//pkg/bpf", - "@com_github_opencontainers_runtime_spec//specs-go:go_default_library", - ], -) diff --git a/runsc/specutils/seccomp/seccomp_amd64_state_autogen.go b/runsc/specutils/seccomp/seccomp_amd64_state_autogen.go new file mode 100644 index 000000000..27a96018b --- /dev/null +++ b/runsc/specutils/seccomp/seccomp_amd64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build amd64 + +package seccomp diff --git a/runsc/specutils/seccomp/seccomp_arm64_state_autogen.go b/runsc/specutils/seccomp/seccomp_arm64_state_autogen.go new file mode 100644 index 000000000..96c64c23d --- /dev/null +++ b/runsc/specutils/seccomp/seccomp_arm64_state_autogen.go @@ -0,0 +1,5 @@ +// automatically generated by stateify. + +// +build arm64 + +package seccomp diff --git a/runsc/specutils/seccomp/seccomp_state_autogen.go b/runsc/specutils/seccomp/seccomp_state_autogen.go new file mode 100644 index 000000000..e16b5d7c2 --- /dev/null +++ b/runsc/specutils/seccomp/seccomp_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package seccomp diff --git a/runsc/specutils/seccomp/seccomp_test.go b/runsc/specutils/seccomp/seccomp_test.go deleted file mode 100644 index 850c237ba..000000000 --- a/runsc/specutils/seccomp/seccomp_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2020 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 seccomp - -import ( - "fmt" - "syscall" - "testing" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "gvisor.dev/gvisor/pkg/binary" - "gvisor.dev/gvisor/pkg/bpf" -) - -type seccompData struct { - nr uint32 - arch uint32 - instructionPointer uint64 - args [6]uint64 -} - -// asInput converts a seccompData to a bpf.Input. -func asInput(d seccompData) bpf.Input { - return bpf.InputBytes{binary.Marshal(nil, binary.LittleEndian, d), binary.LittleEndian} -} - -// testInput creates an Input struct with given seccomp input values. -func testInput(arch uint32, syscallName string, args *[6]uint64) bpf.Input { - syscallNo, err := lookupSyscallNo(arch, syscallName) - if err != nil { - // Assume tests set valid syscall names. - panic(err) - } - - if args == nil { - argArray := [6]uint64{0, 0, 0, 0, 0, 0} - args = &argArray - } - - data := seccompData{ - nr: syscallNo, - arch: arch, - args: *args, - } - - return asInput(data) -} - -// testCase holds a seccomp test case. -type testCase struct { - name string - config specs.LinuxSeccomp - input bpf.Input - expected uint32 -} - -var ( - // seccompTests is a list of speccomp test cases. - seccompTests = []testCase{ - { - name: "default_allow", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - }, - input: testInput(nativeArchAuditNo, "read", nil), - expected: uint32(allowAction), - }, - { - name: "default_deny", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActErrno, - }, - input: testInput(nativeArchAuditNo, "read", nil), - expected: uint32(errnoAction), - }, - { - name: "deny_arch", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getcwd", - }, - Action: specs.ActErrno, - }, - }, - }, - // Syscall matches but the arch is AUDIT_ARCH_X86 so the return - // value is the bad arch action. - input: asInput(seccompData{nr: 183, arch: 0x40000003}), // - expected: uint32(killThreadAction), - }, - { - name: "match_name_errno", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getcwd", - "chmod", - }, - Action: specs.ActErrno, - }, - { - Names: []string{ - "write", - }, - Action: specs.ActTrace, - }, - }, - }, - input: testInput(nativeArchAuditNo, "getcwd", nil), - expected: uint32(errnoAction), - }, - { - name: "match_name_trace", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getcwd", - "chmod", - }, - Action: specs.ActErrno, - }, - { - Names: []string{ - "write", - }, - Action: specs.ActTrace, - }, - }, - }, - input: testInput(nativeArchAuditNo, "write", nil), - expected: uint32(traceAction), - }, - { - name: "no_match_name_allow", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getcwd", - "chmod", - }, - Action: specs.ActErrno, - }, - { - Names: []string{ - "write", - }, - Action: specs.ActTrace, - }, - }, - }, - input: testInput(nativeArchAuditNo, "openat", nil), - expected: uint32(allowAction), - }, - { - name: "simple_match_args", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: syscall.CLONE_FS, - Op: specs.OpEqualTo, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}), - expected: uint32(errnoAction), - }, - { - name: "match_args_or", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: syscall.CLONE_FS, - Op: specs.OpEqualTo, - }, - { - Index: 0, - Value: syscall.CLONE_VM, - Op: specs.OpEqualTo, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}), - expected: uint32(errnoAction), - }, - { - name: "match_args_and", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getsockopt", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 1, - Value: syscall.SOL_SOCKET, - Op: specs.OpEqualTo, - }, - { - Index: 2, - Value: syscall.SO_PEERCRED, - Op: specs.OpEqualTo, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, syscall.SOL_SOCKET, syscall.SO_PEERCRED}), - expected: uint32(errnoAction), - }, - { - name: "no_match_args_and", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "getsockopt", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 1, - Value: syscall.SOL_SOCKET, - Op: specs.OpEqualTo, - }, - { - Index: 2, - Value: syscall.SO_PEERCRED, - Op: specs.OpEqualTo, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, syscall.SOL_SOCKET}), - expected: uint32(allowAction), - }, - { - name: "Simple args (no match)", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: syscall.CLONE_FS, - Op: specs.OpEqualTo, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_VM}), - expected: uint32(allowAction), - }, - { - name: "OpMaskedEqual (match)", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: syscall.CLONE_FS, - ValueTwo: syscall.CLONE_FS, - Op: specs.OpMaskedEqual, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS | syscall.CLONE_VM}), - expected: uint32(errnoAction), - }, - { - name: "OpMaskedEqual (no match)", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActAllow, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: syscall.CLONE_FS | syscall.CLONE_VM, - ValueTwo: syscall.CLONE_FS | syscall.CLONE_VM, - Op: specs.OpMaskedEqual, - }, - }, - Action: specs.ActErrno, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{syscall.CLONE_FS}), - expected: uint32(allowAction), - }, - { - name: "OpMaskedEqual (clone)", - config: specs.LinuxSeccomp{ - DefaultAction: specs.ActErrno, - Syscalls: []specs.LinuxSyscall{ - { - Names: []string{ - "clone", - }, - // This comes from the Docker default seccomp - // profile for clone. - Args: []specs.LinuxSeccompArg{ - { - Index: 0, - Value: 0x7e020000, - ValueTwo: 0x0, - Op: specs.OpMaskedEqual, - }, - }, - Action: specs.ActAllow, - }, - }, - }, - input: testInput(nativeArchAuditNo, "clone", &[6]uint64{0x50f00}), - expected: uint32(allowAction), - }, - } -) - -// TestRunscSeccomp generates seccomp programs from OCI config and executes -// them using runsc's library, comparing against expected results. -func TestRunscSeccomp(t *testing.T) { - for _, tc := range seccompTests { - t.Run(tc.name, func(t *testing.T) { - runscProgram, err := BuildProgram(&tc.config) - if err != nil { - t.Fatalf("generating runsc BPF: %v", err) - } - - if err := checkProgram(runscProgram, tc.input, tc.expected); err != nil { - t.Fatalf("running runsc BPF: %v", err) - } - }) - } -} - -// checkProgram runs the given program over the given input and checks the -// result against the expected output. -func checkProgram(p bpf.Program, in bpf.Input, expected uint32) error { - result, err := bpf.Exec(p, in) - if err != nil { - return err - } - - if result != expected { - // Include a decoded version of the program in output for debugging purposes. - decoded, _ := bpf.DecodeProgram(p) - return fmt.Errorf("Unexpected result: got: %d, expected: %d\nBPF Program\n%s", result, expected, decoded) - } - - return nil -} diff --git a/runsc/specutils/specutils_state_autogen.go b/runsc/specutils/specutils_state_autogen.go new file mode 100644 index 000000000..11eefbaa2 --- /dev/null +++ b/runsc/specutils/specutils_state_autogen.go @@ -0,0 +1,3 @@ +// automatically generated by stateify. + +package specutils diff --git a/runsc/specutils/specutils_test.go b/runsc/specutils/specutils_test.go deleted file mode 100644 index 2c86fffe8..000000000 --- a/runsc/specutils/specutils_test.go +++ /dev/null @@ -1,265 +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 specutils - -import ( - "fmt" - "os/exec" - "strings" - "testing" - "time" - - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -func TestWaitForReadyHappy(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - var count int - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - if count < 3 { - count++ - return false, nil - } - return true, nil - }) - if err != nil { - t.Errorf("ProcessWaitReady got: %v, expected: nil", err) - } - cmd.Process.Kill() -} - -func TestWaitForReadyFail(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - var count int - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - if count < 3 { - count++ - return false, nil - } - return false, fmt.Errorf("Fake error") - }) - if err == nil { - t.Errorf("ProcessWaitReady got: nil, expected: error") - } - cmd.Process.Kill() -} - -func TestWaitForReadyNotRunning(t *testing.T) { - cmd := exec.Command("/bin/true") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) { - return false, nil - }) - if err != nil && !strings.Contains(err.Error(), "terminated") { - t.Errorf("ProcessWaitReady got: %v, expected: process terminated", err) - } - if err == nil { - t.Errorf("ProcessWaitReady incorrectly succeeded") - } -} - -func TestWaitForReadyTimeout(t *testing.T) { - cmd := exec.Command("/bin/sleep", "1000") - if err := cmd.Start(); err != nil { - t.Fatalf("cmd.Start() failed, err: %v", err) - } - defer cmd.Wait() - - err := WaitForReady(cmd.Process.Pid, 50*time.Millisecond, func() (bool, error) { - return false, nil - }) - if !strings.Contains(err.Error(), "not running yet") { - t.Errorf("ProcessWaitReady got: %v, expected: not running yet", err) - } - cmd.Process.Kill() -} - -func TestSpecInvalid(t *testing.T) { - for _, test := range []struct { - name string - spec specs.Spec - error string - }{ - { - name: "valid", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "src", - Destination: "/dst", - }, - }, - }, - error: "", - }, - { - name: "valid+warning", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - // This is normally set by docker and will just cause warnings to be logged. - ApparmorProfile: "someprofile", - }, - // This is normally set by docker and will just cause warnings to be logged. - Linux: &specs.Linux{Seccomp: &specs.LinuxSeccomp{}}, - }, - error: "", - }, - { - name: "no root", - spec: specs.Spec{ - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - }, - error: "must be defined", - }, - { - name: "empty root", - spec: specs.Spec{ - Root: &specs.Root{}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - }, - error: "must be defined", - }, - { - name: "no process", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - }, - error: "must be defined", - }, - { - name: "empty args", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{}, - }, - error: "must be defined", - }, - { - name: "selinux", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - SelinuxLabel: "somelabel", - }, - }, - error: "is not supported", - }, - { - name: "solaris", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Solaris: &specs.Solaris{}, - }, - error: "is not supported", - }, - { - name: "windows", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Windows: &specs.Windows{}, - }, - error: "is not supported", - }, - { - name: "relative mount destination", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "src", - Destination: "dst", - }, - }, - }, - error: "must be an absolute path", - }, - { - name: "invalid mount option", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Mounts: []specs.Mount{ - { - Source: "/src", - Destination: "/dst", - Type: "bind", - Options: []string{"shared"}, - }, - }, - }, - error: "is not supported", - }, - { - name: "invalid rootfs propagation", - spec: specs.Spec{ - Root: &specs.Root{Path: "/"}, - Process: &specs.Process{ - Args: []string{"/bin/true"}, - }, - Linux: &specs.Linux{ - RootfsPropagation: "foo", - }, - }, - error: "root mount propagation option must specify private or slave", - }, - } { - err := ValidateSpec(&test.spec) - if len(test.error) == 0 { - if err != nil { - t.Errorf("ValidateSpec(%q) failed, err: %v", test.name, err) - } - } else { - if err == nil || !strings.Contains(err.Error(), test.error) { - t.Errorf("ValidateSpec(%q) wrong error, got: %v, want: .*%s.*", test.name, err, test.error) - } - } - } -} diff --git a/runsc/version_test.sh b/runsc/version_test.sh deleted file mode 100755 index 747350654..000000000 --- a/runsc/version_test.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# 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. - -set -euf -x -o pipefail - -readonly runsc="$1" -readonly version=$($runsc --version) - -# Version should should not match VERSION, which is the default and which will -# also appear if something is wrong with workspace_status.sh script. -if [[ $version =~ "VERSION" ]]; then - echo "FAIL: Got bad version $version" - exit 1 -fi - -# Version should contain at least one number. -if [[ ! $version =~ [0-9] ]]; then - echo "FAIL: Got bad version $version" - exit 1 -fi - -echo "PASS: Got OK version $version" -exit 0 |