Files
visor/runsc/cmd/alias/bwrap/bwrap_integration_test.go
Etienne Perot 5e7190b63b Implement shared "null netns" to avoid netns creation overhead on start
See issue #13764 for motivation and idea. This approach yields the
benefit without the security compromise of moving the fsgofer out of
a network-disabled netns, so it is a safe new default.

Benchmarks:

```
                            │ gofer-netns-new │          gofer-netns-null          │
                            │     sec/op      │   sec/op     vs base               │
OCIStartup/load=idle             118.6m ±  5%   119.3m ± 4%       ~ (p=0.756 n=40)
OCIStartup/load=netns-churn      236.5m ± 13%   215.2m ± 7%  -8.99% (p=0.033 n=20)
geomean                          167.5m         160.2m       -4.32%
```

`netns-churn` is simulated with this bash thingy in the background:

```
for j in $(seq 1 8); do
  ( while :; do unshare -n /bin/true; done ) &
done
```

The effect gets stronger at higher core counts on real servers.
2026-08-03 14:51:43 -07:00

339 lines
7.3 KiB
Go

// Copyright 2026 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 bwrap
import (
"bytes"
"fmt"
"os"
"os/exec"
"slices"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/test/testutil"
"gvisor.dev/gvisor/runsc/specutils"
)
// newRunRootDir returns a runtime root directory, deleted at test cleanup.
func newRunRootDir(t *testing.T) string {
t.Helper()
runRootDir := t.TempDir()
t.Cleanup(func() { specutils.UnmountNullNetNS(runRootDir) })
return runRootDir
}
func TestEnvVars(t *testing.T) {
if err := testutil.ConfigureExePath(); err != nil {
t.Fatalf("failed to configure exe path: %v", err)
}
stop := testutil.StartReaper()
defer stop()
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get cwd: %v", err)
}
hostEnvVar := "BWRAP_TEST_ENV_VAR"
hostEnvVal := "BWRAP_TEST_ENV_VALUE"
baseEnv := []string{
"PATH=/usr/sbin:/usr/bin:/sbin:/bin",
"HOME=/root",
}
tests := []struct {
name string
bwrapArgs []string
wantEnv []string
}{
{
name: "SetEnv",
bwrapArgs: []string{
"--ro-bind", "/", "/",
"--setenv", "FOO", "bar",
"--",
"/usr/bin/env",
},
wantEnv: append(baseEnv, hostEnvVar+"="+hostEnvVal, "FOO=bar"),
},
{
name: "ResetEnv",
bwrapArgs: []string{
"--ro-bind", "/", "/",
"--setenv", hostEnvVar, "bar",
"--",
"/usr/bin/env",
},
wantEnv: append(baseEnv, hostEnvVar+"=bar"),
},
{
name: "ClearEnv",
bwrapArgs: []string{
"--ro-bind", "/", "/",
"--clearenv",
"--setenv", "FOO", "bar",
"--",
"/usr/bin/env",
},
wantEnv: []string{"PWD=" + cwd, "FOO=bar", "HOME=/root"},
},
{
name: "UnsetEnv",
bwrapArgs: []string{
"--ro-bind", "/", "/",
"--unsetenv", hostEnvVar,
"--",
"/usr/bin/env",
},
wantEnv: baseEnv,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runRootDir := newRunRootDir(t)
args := append([]string{
"--root", runRootDir,
"bwrap",
}, tc.bwrapArgs...)
cmd := exec.Command(specutils.ExePath, args...)
cmd.Env = append(baseEnv, hostEnvVar+"="+hostEnvVal)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String())
}
actualEnv := strings.Split(strings.TrimSpace(stdout.String()), "\n")
// 2. Sort both slices alphabetically so order is 100% ignored!
slices.Sort(tc.wantEnv)
slices.Sort(actualEnv)
if diff := cmp.Diff(tc.wantEnv, actualEnv); diff != "" {
t.Errorf("runsc bwrap env mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestUserAndGroup(t *testing.T) {
if err := testutil.ConfigureExePath(); err != nil {
t.Fatalf("failed to configure exe path: %v", err)
}
stop := testutil.StartReaper()
defer stop()
tests := []struct {
name string
bwrapArgs []string
wantUID string
wantGID string
}{
{
name: "DefaultUser",
bwrapArgs: []string{
"--unshare-user",
"--ro-bind", "/", "/",
"--",
"/usr/bin/id", "-u",
},
wantUID: fmt.Sprintf("%d", os.Getuid()),
},
{
name: "CustomUID",
bwrapArgs: []string{
"--unshare-user",
"--uid", "12345",
"--ro-bind", "/", "/",
"--",
"/usr/bin/id", "-u",
},
wantUID: "12345",
},
{
name: "CustomGID",
bwrapArgs: []string{
"--unshare-user",
"--gid", "54321",
"--ro-bind", "/", "/",
"--",
"/usr/bin/id", "-g",
},
wantGID: "54321",
},
{
name: "CustomUIDAndGID",
bwrapArgs: []string{
"--unshare-user",
"--uid", "12345",
"--gid", "54321",
"--ro-bind", "/", "/",
"--",
"/usr/bin/id",
},
wantUID: "uid=12345",
wantGID: "gid=54321",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runRootDir := newRunRootDir(t)
args := append([]string{
"--root", runRootDir,
"bwrap",
}, tc.bwrapArgs...)
cmd := exec.Command(specutils.ExePath, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String())
}
output := strings.TrimSpace(stdout.String())
if tc.wantUID != "" && !strings.Contains(output, tc.wantUID) {
t.Errorf("output = %q, want UID %q", output, tc.wantUID)
}
if tc.wantGID != "" && !strings.Contains(output, tc.wantGID) {
t.Errorf("output = %q, want GID %q", output, tc.wantGID)
}
})
}
}
// TODO: b/518882196 - Support joining existing user namespaces.
func TestUserns(t *testing.T) {
t.Skip("Skipping userns joining due to runsc doesn't support joining existing user namespaces")
}
func TestHostname(t *testing.T) {
if err := testutil.ConfigureExePath(); err != nil {
t.Fatalf("failed to configure exe path: %v", err)
}
stop := testutil.StartReaper()
defer stop()
tests := []struct {
name string
bwrapArgs []string
wantOutput string
}{
{
name: "Hostname",
bwrapArgs: []string{
"--hostname", "test-host",
"--ro-bind", "/", "/",
"--",
"/bin/hostname",
},
wantOutput: "test-host",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runRootDir := newRunRootDir(t)
args := append([]string{
"--root", runRootDir,
"bwrap",
}, tc.bwrapArgs...)
cmd := exec.Command(specutils.ExePath, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String())
}
output := strings.TrimSpace(stdout.String())
if tc.wantOutput != "" && !strings.Contains(output, tc.wantOutput) {
t.Errorf("output = %q, want it to contain %q", output, tc.wantOutput)
}
})
}
}
func TestProc(t *testing.T) {
if err := testutil.ConfigureExePath(); err != nil {
t.Fatalf("failed to configure exe path: %v", err)
}
stop := testutil.StartReaper()
defer stop()
tests := []struct {
name string
bwrapArgs []string
wantOutput string
}{
{
name: "Proc",
bwrapArgs: []string{
"--unshare-user",
"--ro-bind", "/", "/",
"--proc", "/proc1",
"--",
"/bin/ls", "/proc1",
},
wantOutput: "cpuinfo",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
runRootDir := newRunRootDir(t)
args := append([]string{
"--root", runRootDir,
"bwrap",
}, tc.bwrapArgs...)
cmd := exec.Command(specutils.ExePath, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("runsc bwrap failed: %v\nStderr: %s", err, stderr.String())
}
output := strings.TrimSpace(stdout.String())
if tc.wantOutput != "" && !strings.Contains(output, tc.wantOutput) {
t.Errorf("output = %q, want it to contain %q", output, tc.wantOutput)
}
})
}
}