Skip to content

Commit

Permalink
Add test that runs runsc do inside a non-gVisor container.
Browse files Browse the repository at this point in the history
This is used in contexts such as Dangerzone:
https://gvisor.dev/blog/2024/09/23/safe-ride-into-the-dangerzone/

Updates issue #10944.

PiperOrigin-RevId: 681229280
  • Loading branch information
EtiennePerot authored and gvisor-bot committed Oct 2, 2024
1 parent ca8d05a commit cae035d
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ docker-tests: load-basic $(RUNTIME_BIN)
@$(call install_runtime,$(RUNTIME)-dcache,--fdlimit=2000 --dcache=100) # Used by TestDentryCacheLimit.
@$(call install_runtime,$(RUNTIME)-host-uds,--host-uds=all) # Used by TestHostSocketConnect.
@$(call install_runtime,$(RUNTIME)-overlay,--overlay2=all:self) # Used by TestOverlay*.
@$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS) //test/e2e:integration_runtime_test)
@$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS) //test/e2e:integration_runtime_test //test/e2e:runtime_in_docker_test)
.PHONY: docker-tests

plugin-network-tests: load-basic $(RUNTIME_BIN)
Expand Down
13 changes: 11 additions & 2 deletions images/basic/integrationtest/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ WORKDIR /root
COPY . .
RUN chmod +x *.sh

RUN apt-get update && apt-get install -y gcc iputils-ping iproute2
RUN apt-get update && apt-get install -y \
build-essential iputils-ping iproute2 iptables

# Compilation Steps.
RUN gcc -O2 -o test_copy_up test_copy_up.c
RUN gcc -O2 -o test_rewinddir test_rewinddir.c
RUN gcc -O2 -o link_test link_test.c
RUN gcc -O2 -o test_sticky test_sticky.c
RUN gcc -O2 -o host_fd host_fd.c
RUN gcc -O2 -o host_connect host_connect.c
RUN gcc -O2 -o host_connect host_connect.c

# Add nonprivileged regular user named "nonroot".
RUN groupadd --gid 1337 nonroot && \
useradd --uid 1337 --gid 1337 \
--create-home \
--shell $(which bash) \
--password '' \
nonroot
4 changes: 4 additions & 0 deletions pkg/test/dockerutil/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type RunOpts struct {

Devices []container.DeviceMapping

// SecurityOpts are security options to set on the container.
SecurityOpts []string

// sniffGPUOpts, if set, sets the rules for GPU sniffing during this test.
// Must be set via `RunOpts.SniffGPU`.
sniffGPUOpts *SniffGPUOpts
Expand Down Expand Up @@ -347,6 +350,7 @@ func (c *Container) hostConfig(r RunOpts) *container.HostConfig {
CapAdd: r.CapAdd,
CapDrop: r.CapDrop,
Privileged: r.Privileged,
SecurityOpt: r.SecurityOpts,
ReadonlyRootfs: r.ReadOnly,
NetworkMode: container.NetworkMode(r.NetworkMode),
Resources: container.Resources{
Expand Down
19 changes: 19 additions & 0 deletions test/e2e/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ go_test(
],
)

go_test(
name = "runtime_in_docker_test",
size = "large",
srcs = [
"runtime_in_docker_test.go",
],
library = ":integration",
tags = [
# Requires docker and runsc to be configured before the test runs.
"local",
"manual",
],
visibility = ["//:sandbox"],
deps = [
"//pkg/test/dockerutil",
"@com_github_docker_docker//api/types/mount:go_default_library",
],
)

go_library(
name = "integration",
srcs = ["integration.go"],
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/integration_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestDentryCacheLimit(t *testing.T) {
}
}

// Run the container. Open a bunch of files simutaneously and sleep a bit
// Run the container. Open a bunch of files simultaneously and sleep a bit
// to give time for everything to start. We shouldn't hit the FD limit
// because the dentry cache is small.
cmd := `for file in /tmp/foo/*; do (cat > "${file}") & done && sleep 10`
Expand Down
179 changes: 179 additions & 0 deletions test/e2e/runtime_in_docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright 2024 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 integration provides end-to-end integration tests for runsc.
package integration

import (
"context"
"strings"
"testing"

"github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
)

// TestGVisorInDocker runs `runsc` inside a non-gVisor container.
// This is used in contexts such as Dangerzone:
// https://gvisor.dev/blog/2024/09/23/safe-ride-into-the-dangerzone/
func TestGVisorInDocker(t *testing.T) {
ctx := context.Background()
runscPath, err := dockerutil.RuntimePath()
if err != nil {
t.Fatalf("Cannot locate runtime path: %v", err)
}
for _, test := range []struct {
Name string
User string
WorkDir string
CapAdd []string
Args []string
MountCgroupfs bool
}{
{
Name: "Rootful",
User: "root",
CapAdd: []string{
// Necessary to set up networking (creating veth devices).
"NET_ADMIN",
// Necessary to set up networking, which calls `ip netns add` which
// calls `mount(2)`.
"SYS_ADMIN",
},
// Mount cgroupfs as writable, otherwise the runtime won't be able to
// set up cgroups.
MountCgroupfs: true,
},
{
Name: "Rootful without networking",
User: "root",
CapAdd: []string{
// "Can't run sandbox process in minimal chroot since we don't have CAP_SYS_ADMIN"
"SYS_ADMIN",
},
Args: []string{
"--network=none",
},
MountCgroupfs: true,
},
{
Name: "Rootful with host networking",
User: "root",
CapAdd: []string{
// Necessary to set up networking (creating veth devices).
"NET_ADMIN",
// Necessary to set up networking, which calls `ip netns add` which
// calls `mount(2)`.
"SYS_ADMIN",
},
Args: []string{
"--network=host",
},
MountCgroupfs: true,
},
{
Name: "Rootful without networking and cgroupfs",
User: "root",
CapAdd: []string{
// "Can't run sandbox process in minimal chroot since we don't have CAP_SYS_ADMIN"
"SYS_ADMIN",
},
Args: []string{
"--network=none",
"--ignore-cgroups=true",
},
},
{
Name: "Rootless",
User: "nonroot",
WorkDir: "/home/nonroot",
Args: []string{
"--rootless=true",
},
},
{
Name: "Rootless without networking",
User: "nonroot",
WorkDir: "/home/nonroot",
Args: []string{
"--rootless=true",
"--network=none",
},
},
} {
t.Run(test.Name, func(t *testing.T) {
d := dockerutil.MakeNativeContainer(ctx, t)
defer d.CleanUp(ctx)
opts := dockerutil.RunOpts{
Image: "basic/integrationtest",
User: test.User,
WorkDir: test.WorkDir,
SecurityOpts: []string{
// Disable default seccomp filter which blocks `mount(2)` and others.
"seccomp=unconfined",

// Disable AppArmor which also blocks mounts.
"apparmor=unconfined",

// Set correct SELinux label; this allows ptrace.
"label=type:container_engine_t",
},
CapAdd: test.CapAdd,
Mounts: []mount.Mount{
// Mount the runtime binary.
{
Type: mount.TypeBind,
Source: runscPath,
Target: "/runtime",
ReadOnly: true,
},
},
}
if test.MountCgroupfs {
opts.Mounts = append(opts.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: "/sys/fs/cgroup",
Target: "/sys/fs/cgroup",
ReadOnly: false,
})
}
// Mount an unobstructed view of procfs at /proc2 so that the runtime
// can mount a fresh procfs.
// TODO(gvisor.dev/issue/10944): Remove this once issue is fixed.
opts.Mounts = append(opts.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: "/proc",
Target: "/proc2",
ReadOnly: false,
BindOptions: &mount.BindOptions{
NonRecursive: true,
},
})
wantMessage := "It became a jumble of words, a litany, almost a kind of glossolalia."
args := []string{
"/runtime",
"--debug=true",
"--debug-log=/dev/stderr",
}
args = append(args, test.Args...)
args = append(args, "do", "/bin/echo", wantMessage)
t.Logf("Running: %v", args)
if got, err := d.Run(ctx, opts, args...); err != nil {
t.Fatalf("Error: %v\nLogs:\n%s", err, strings.TrimSpace(got))
} else if !strings.Contains(got, wantMessage) {
t.Fatalf("Did not observe substring %q in logs:\n%s", wantMessage, strings.TrimSpace(got))
}
})
}
}

0 comments on commit cae035d

Please sign in to comment.