From 4470caec4e2fea10f5d116894ca6b3fc9d78789b Mon Sep 17 00:00:00 2001 From: Etienne Perot Date: Thu, 24 Jun 2021 17:45:51 -0700 Subject: Run `:socket_inet_loopback_isolated_test_linux` tests in a container. This creates new user and network namespaces for all tests in `:socket_inet_loopback_isolated_test_linux`. PiperOrigin-RevId: 381374120 --- test/runner/BUILD | 1 + test/runner/defs.bzl | 3 + test/runner/main.go | 50 +++++++++++--- test/runner/setup_container/BUILD | 19 ++++++ test/runner/setup_container/setup_container.cc | 79 ++++++++++++++++++++++ test/syscalls/BUILD | 1 + test/syscalls/linux/BUILD | 2 + .../linux/socket_inet_loopback_isolated.cc | 1 + 8 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 test/runner/setup_container/BUILD create mode 100644 test/runner/setup_container/setup_container.cc (limited to 'test') diff --git a/test/runner/BUILD b/test/runner/BUILD index f9f788726..2d93aa6af 100644 --- a/test/runner/BUILD +++ b/test/runner/BUILD @@ -8,6 +8,7 @@ go_binary( srcs = ["main.go"], data = [ "//runsc", + "//test/runner/setup_container", ], visibility = ["//:sandbox"], deps = [ diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 416f51935..405e03832 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -103,6 +103,8 @@ def _syscall_test( if platform == "native": tags.append("nogotsan") + container = "container" in tags + runner_args = [ # Arguments are passed directly to runner binary. "--platform=" + platform, @@ -115,6 +117,7 @@ def _syscall_test( "--fuse=" + str(fuse), "--strace=" + str(debug), "--debug=" + str(debug), + "--container=" + str(container), ] # Call the rule above. diff --git a/test/runner/main.go b/test/runner/main.go index 7e8e88ba2..34e9c6279 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -40,16 +40,18 @@ import ( ) var ( - debug = flag.Bool("debug", false, "enable debug logs") - strace = flag.Bool("strace", false, "enable strace logs") - platform = flag.String("platform", "ptrace", "platform to run on") - network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") - useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") - fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") - overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") - vfs2 = flag.Bool("vfs2", false, "enable VFS2") - fuse = flag.Bool("fuse", false, "enable FUSE") - runscPath = flag.String("runsc", "", "path to runsc binary") + debug = flag.Bool("debug", false, "enable debug logs") + strace = flag.Bool("strace", false, "enable strace logs") + platform = flag.String("platform", "ptrace", "platform to run on") + network = flag.String("network", "none", "network stack to run on (sandbox, host, none)") + useTmpfs = flag.Bool("use-tmpfs", false, "mounts tmpfs for /tmp") + fileAccess = flag.String("file-access", "exclusive", "mounts root in exclusive or shared mode") + overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable tmpfs overlay") + vfs2 = flag.Bool("vfs2", false, "enable VFS2") + fuse = flag.Bool("fuse", false, "enable FUSE") + container = flag.Bool("container", false, "run tests in their own namespaces (user ns, network ns, etc), pretending to be root") + setupContainerPath = flag.String("setup-container", "", "path to setup_container binary (for use with --container)") + runscPath = flag.String("runsc", "", "path to runsc binary") addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests") // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and @@ -105,6 +107,27 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { cmd.Stderr = os.Stderr cmd.SysProcAttr = &unix.SysProcAttr{} + if *container { + // setup_container takes in its target argv as positional arguments. + cmd.Path = *setupContainerPath + cmd.Args = append([]string{cmd.Path}, cmd.Args...) + cmd.SysProcAttr = &unix.SysProcAttr{ + Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS, + // Set current user/group as root inside the namespace. + UidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getuid(), Size: 1}, + }, + GidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getgid(), Size: 1}, + }, + GidMappingsEnableSetgroups: false, + Credential: &syscall.Credential{ + Uid: 0, + Gid: 0, + }, + } + } + if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS } @@ -454,6 +477,13 @@ func main() { } *runscPath = specutils.ExePath } + if *container && *setupContainerPath == "" { + setupContainer, err := testutil.FindFile("test/runner/setup_container/setup_container") + if err != nil { + fatalf("cannot find setup_container: %v", err) + } + *setupContainerPath = setupContainer + } // Make sure stdout and stderr are opened with O_APPEND, otherwise logs // from outside the sandbox can (and will) stomp on logs from inside diff --git a/test/runner/setup_container/BUILD b/test/runner/setup_container/BUILD new file mode 100644 index 000000000..5b99d1de9 --- /dev/null +++ b/test/runner/setup_container/BUILD @@ -0,0 +1,19 @@ +# setup_container contains a shim binary that runs within the test container +# for syscall tests with container=True. + +load("//tools:defs.bzl", "cc_binary") + +package(licenses = ["notice"]) + +cc_binary( + name = "setup_container", + testonly = 1, + srcs = ["setup_container.cc"], + visibility = ["//test/runner:__subpackages__"], + deps = [ + "//test/syscalls/linux:socket_netlink_util", + "//test/syscalls/linux:socket_test_util", + "//test/util:capability_util", + "//test/util:posix_error", + ], +) diff --git a/test/runner/setup_container/setup_container.cc b/test/runner/setup_container/setup_container.cc new file mode 100644 index 000000000..9a4e3fb8b --- /dev/null +++ b/test/runner/setup_container/setup_container.cc @@ -0,0 +1,79 @@ +// 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. + +#include +#include +#include + +#include "test/syscalls/linux/socket_netlink_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/capability_util.h" +#include "test/util/posix_error.h" + +namespace gvisor { +namespace testing { + +// SetupContainer sets up the networking settings in the current container. +PosixError SetupContainer() { + const PosixErrorOr have_net_admin = HaveCapability(CAP_NET_ADMIN); + if (!have_net_admin.ok()) { + std::cerr << "Cannot determine if we have CAP_NET_ADMIN." << std::endl; + return have_net_admin.error(); + } + if (have_net_admin.ValueOrDie() && !IsRunningOnGvisor()) { + PosixErrorOr sockfd = Socket(AF_INET, SOCK_DGRAM, 0); + if (!sockfd.ok()) { + std::cerr << "Cannot open socket." << std::endl; + return sockfd.error(); + } + int sock = sockfd.ValueOrDie().get(); + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, "lo", IFNAMSIZ); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) { + std::cerr << "Cannot get 'lo' flags: " << strerror(errno) << std::endl; + return PosixError(errno); + } + if ((ifr.ifr_flags & IFF_UP) == 0) { + ifr.ifr_flags |= IFF_UP; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) == -1) { + std::cerr << "Cannot set 'lo' as UP: " << strerror(errno) << std::endl; + return PosixError(errno); + } + } + } + return NoError(); +} + +} // namespace testing +} // namespace gvisor + +using ::gvisor::testing::SetupContainer; + +// Binary setup_container initializes the container environment in which tests +// with container=True will run, then execs the actual test binary. +// Usage: +// ./setup_container test_binary [arguments forwarded to test_binary...] +int main(int argc, char *argv[], char *envp[]) { + if (!SetupContainer().ok()) { + return 1; + } + if (argc < 2) { + std::cerr << "Must provide arguments to exec." << std::endl; + return 2; + } + if (execve(argv[1], &argv[1], envp) == -1) { + std::cerr << "execv returned errno " << errno << std::endl; + return 1; + } +} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 8fc96d7ba..1257c0553 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -648,6 +648,7 @@ syscall_test( syscall_test( size = "large", shard_count = most_shards, + tags = ["container"], test = "//test/syscalls/linux:socket_inet_loopback_isolated_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index c7991cfaa..2bf685524 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -3196,9 +3196,11 @@ cc_binary( linkstatic = 1, deps = [ ":socket_inet_loopback_test_params", + ":socket_netlink_util", ":socket_test_util", gtest, "//test/util:test_main", + "//test/util:test_util", "@com_google_absl//absl/time", ], ) diff --git a/test/syscalls/linux/socket_inet_loopback_isolated.cc b/test/syscalls/linux/socket_inet_loopback_isolated.cc index ccb016726..ab2259b55 100644 --- a/test/syscalls/linux/socket_inet_loopback_isolated.cc +++ b/test/syscalls/linux/socket_inet_loopback_isolated.cc @@ -19,6 +19,7 @@ #include "absl/time/time.h" #include "test/syscalls/linux/socket_inet_loopback_test_params.h" #include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" // Unit tests in this file will run in their own network namespace. -- cgit v1.2.3