Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .buildkite/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,22 @@ steps:
<<: *platform_specific_agents
arch: "amd64"

# Bwrap tests.
- <<: *common
<<: *docker
<<: *source_test
label: ":package: bwrap tests (AMD64)"
command: make bwrap-tests
agents:
arch: "amd64"
- <<: *common
<<: *docker
<<: *source_test
label: ":package: bwrap tests (ARM64)"
command: make bwrap-tests
agents:
arch: "arm64"

# Runtime tests (goferfs). Continuous only.
- <<: *common
<<: *docker
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ nftables-syscall-runc-tests: load-nftables
@$(call build_paths,//test/syscalls/linux:socket_netlink_netfilter_test,docker run $(DOCKER_RUN_OPTIONS) --user 0:0 --runtime runc --rm gvisor.dev/images/nftables {})
.PHONY: nftables-syscall-runc-tests

bwrap-tests: $(RUNTIME_BIN)
@$(call sudo,//runsc/cmd/alias/bwrap:bwrap_integration_test,-test.v -runsc=$(RUNTIME_BIN))
.PHONY: bwrap-tests

packetdrill-tests: load-packetdrill $(RUNTIME_BIN)
@$(call install_runtime,$(RUNTIME),) # Clear flags.
@$(call test_runtime,$(RUNTIME),//test/packetdrill:all_tests)
Expand Down
18 changes: 18 additions & 0 deletions runsc/cmd/alias/bwrap/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,21 @@ go_test(
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
],
)

go_test(
name = "bwrap_integration_test",
srcs = ["bwrap_integration_test.go"],
data = [
"//runsc",
],
library = ":bwrap",
tags = [
"local",
"manual",
],
deps = [
"//pkg/test/testutil",
"//runsc/specutils",
"@com_github_google_go_cmp//cmp:go_default_library",
],
)
123 changes: 122 additions & 1 deletion runsc/cmd/alias/bwrap/bwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"math/rand"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/google/subcommands"
Expand Down Expand Up @@ -107,6 +108,7 @@ func do(c *bwrapConfig, waitStatus *unix.WaitStatus) subcommands.ExitStatus {
c.WorkspaceDir = workspaceDir

// Build the runsc spec from the bwrap config.

spec, err := c.buildRunscSpec()
if err != nil {
return util.Errorf("failed to build runsc spec: %v", err)
Expand Down Expand Up @@ -169,6 +171,13 @@ type bwrapConfig struct {
Chdir string
WorkspaceDir string
runscConfig *config.Config
Env []string
UnsetEnv []string
UID int
GID int
UnshareUser bool
Hostname string
ProcDest map[string]struct{}
}

// String returns a string representation of the bwrapConfig.
Expand Down Expand Up @@ -251,6 +260,14 @@ func (c *bwrapConfig) mapCWD() (string, error) {
// TODO: b/508701483 - Use the causeway library when it is ready
// and update this function.
func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) {
if c.UID != -1 && !c.UnshareUser {
return nil, fmt.Errorf("bwrap: Specifying --uid requires --unshare-user")
}

if c.GID != -1 && !c.UnshareUser {
return nil, fmt.Errorf("bwrap: Specifying --gid requires --unshare-user")
}

spec := &specs.Spec{}
// Find what the current working directory should be in the sandbox.
cwd, err := c.mapCWD()
Expand All @@ -260,10 +277,12 @@ func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) {
spec.Process = &specs.Process{
Cwd: cwd,
Args: c.Args,
Env: os.Environ(),
Env: c.Env,
Capabilities: specutils.AllCapabilities(),
}

c.setupUserNamespace(spec)

rootMount, rootMountPresent := c.getRootMount()
if rootMountPresent {
// If a root mount is specified, use it as the root.
Expand Down Expand Up @@ -337,6 +356,10 @@ func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) {
}
}

if c.Hostname != "" {
spec.Hostname = c.Hostname
}

// TODO: b/508701483 - Fix support for network args.
if c.UnshareNet {
if spec.Linux == nil {
Expand All @@ -345,7 +368,105 @@ func (c *bwrapConfig) buildRunscSpec() (*specs.Spec, error) {
spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace})
}

// Add proc mounts.
for procDest := range c.ProcDest {
if procDest == "/proc" {
continue
}
spec.Mounts = append(spec.Mounts, specs.Mount{
Type: "proc",
Destination: procDest,
})
}

// Set the current working directory.
spec.Process.Cwd = cwd
return spec, nil
}

// setupUserNamespace configures the user namespace, UID/GID mappings, and process user in the Spec.
func (c *bwrapConfig) setupUserNamespace(spec *specs.Spec) {
if c.UnshareUser {
hostUID := os.Getuid()
targetUID := hostUID
if c.UID != -1 {
targetUID = c.UID
}
hostGID := os.Getgid()
targetGID := hostGID
if c.GID != -1 {
targetGID = c.GID
}
spec.Process.User = specs.User{
UID: uint32(targetUID),
GID: uint32(targetGID),
}
if spec.Linux == nil {
spec.Linux = &specs.Linux{}
}

// When running under sudo (e.g. in CI tests), SUDO_UID/SUDO_GID point to the true non-root workspace owner.
// When running without sudo, they gracefully fallback to the current host UID/GID.
sudoUID := getEnvInt("SUDO_UID", hostUID)
sudoGID := getEnvInt("SUDO_GID", hostGID)

spec.Linux.UIDMappings = []specs.LinuxIDMapping{
{ContainerID: uint32(targetUID), HostID: uint32(sudoUID), Size: 1},
}
spec.Linux.GIDMappings = []specs.LinuxIDMapping{
{ContainerID: uint32(targetGID), HostID: uint32(sudoGID), Size: 1},
}

// When runsc is executed by root (hostUID == 0), gVisor's Gofer initialization requires Container 0:0
// to be mapped to Host 0:0 so the Gofer process can successfully call setuid(0)/setgid(0).
if hostUID == 0 {
if targetUID != 0 {
spec.Linux.UIDMappings = append(spec.Linux.UIDMappings, specs.LinuxIDMapping{ContainerID: 0, HostID: 0, Size: 1})
}
if targetGID != 0 {
spec.Linux.GIDMappings = append(spec.Linux.GIDMappings, specs.LinuxIDMapping{ContainerID: 0, HostID: 0, Size: 1})
}
}

spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{Type: specs.UserNamespace})
return
}

if c.UID != -1 || c.GID != -1 {
spec.Process.User = specs.User{}
if c.UID != -1 {
spec.Process.User.UID = uint32(c.UID)
}
if c.GID != -1 {
spec.Process.User.GID = uint32(c.GID)
}
}
}

// getEnvInt parses an environment variable as an integer, returning fallback if empty or invalid.
func getEnvInt(key string, fallback int) int {
if s := os.Getenv(key); s != "" {
if v, err := strconv.Atoi(s); err == nil {
return v
}
}
return fallback
}

func (c *bwrapConfig) resolveEnv() {
var env []string
for _, e := range c.Env {
if strings.Contains(e, "=") {
env = append(env, e)
}
}
for _, e := range c.UnsetEnv {
for i, v := range env {
if strings.HasPrefix(v, e+"=") {
env = append(env[:i], env[i+1:]...)
break
}
}
}
c.Env = env
}
Loading
Loading