diff --git a/README.md b/README.md index 4ec83ab0c..d85948ce5 100644 --- a/README.md +++ b/README.md @@ -179,14 +179,15 @@ here: `https://storage.googleapis.com/gvisor/releases/nightly/${yyyy-mm-dd}/runsc.sha512` **It is important to copy this binary to some place that is accessible to all -users**, since `runsc` executes itself as user `nobody` to avoid unnecessary -privileges. The `/usr/local/bin` directory is a good choice. +users, and make is executable to all users**, since `runsc` executes itself as +user `nobody` to avoid unnecessary privileges. The `/usr/local/bin` directory is +a good place to put the `runsc` binary. ``` wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc wget https://storage.googleapis.com/gvisor/releases/nightly/latest/runsc.sha512 sha512sum -c runsc.sha512 -chmod +x runsc +chmod a+x runsc sudo mv runsc /usr/local/bin ``` diff --git a/runsc/boot/config.go b/runsc/boot/config.go index 212f5b003..87a47dd0b 100644 --- a/runsc/boot/config.go +++ b/runsc/boot/config.go @@ -213,6 +213,11 @@ type Config struct { // PanicSignal register signal handling that panics. Usually set to // SIGUSR2(12) to troubleshoot hangs. -1 disables it. PanicSignal int + + // TestOnlyAllowRunAsCurrentUser should only be used in tests. It + // allows runsc to start the sandbox process as the current user if we + // do not have capability to set uid/gid to another user. + TestOnlyAllowRunAsCurrentUser bool } // ToFlags returns a slice of flags that correspond to the given Config. diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index e0fadefcd..dd5a0aa56 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -373,8 +373,8 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund // User namespace depends on the following options: // - Host network/filesystem: requires to run inside the user namespace // specified in the spec or the current namespace if none is configured. - // - Gofer: when using a Gofer, the sandbox process can run isolated in an - // empty namespace. + // - Gofer: when using a Gofer, the sandbox process can run isolated in a + // new user namespace with only the "nobody" user and group. if conf.Network == boot.NetworkHost || conf.FileAccess == boot.FileAccessDirect { if userns, ok := specutils.GetNS(specs.UserNamespace, spec); ok { log.Infof("Sandbox will be started in container's user namespace: %+v", userns) @@ -391,6 +391,34 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund } else { log.Infof("Sandbox will be started in new user namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.UserNamespace}) + + if conf.TestOnlyAllowRunAsCurrentUser { + log.Warningf("Running sandbox in test mode as current user (uid=%d gid=%d). This is only safe in tests!", os.Getuid(), os.Getgid()) + } else if specutils.CanSetUIDGID() { + // If we have CAP_SETUID and CAP_SETGID, then we can also run + // as user nobody. + + // Map nobody in the new namespace to nobody in the parent namespace. + const nobody = 65534 + cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ + ContainerID: int(nobody), + HostID: int(nobody), + Size: int(1), + }} + cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ + ContainerID: int(nobody), + HostID: int(nobody), + Size: int(1), + }} + + // Set credentials to run as user and group nobody. + cmd.SysProcAttr.Credential = &syscall.Credential{ + Uid: nobody, + Gid: nobody, + } + } else { + return fmt.Errorf("can't run sandbox process as user nobody since we don't have CAP_SETUID or CAP_SETGID") + } } // Log the fds we are donating to the sandbox process. @@ -399,6 +427,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund } log.Debugf("Starting sandbox: %s %v", binPath, cmd.Args) + log.Debugf("SysProcAttr: %+v", cmd.SysProcAttr) if err := specutils.StartInNS(cmd, nss); err != nil { return err } diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD index 97a504b20..e73b2293f 100644 --- a/runsc/specutils/BUILD +++ b/runsc/specutils/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/sentry/kernel/auth", "@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", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/runsc/specutils/namespace.go b/runsc/specutils/namespace.go index 80eaad965..356943a65 100644 --- a/runsc/specutils/namespace.go +++ b/runsc/specutils/namespace.go @@ -23,6 +23,7 @@ import ( "syscall" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/syndtr/gocapability/capability" "golang.org/x/sys/unix" "gvisor.googlesource.com/gvisor/pkg/log" ) @@ -202,3 +203,16 @@ func SetUIDGIDMappings(cmd *exec.Cmd, s *specs.Spec) { }) } } + +// CanSetUIDGID returns true if the user has SETUID and SETGID capabilities. +func CanSetUIDGID() bool { + caps, err := capability.NewPid2(os.Getpid()) + if err != nil { + return false + } + if err := caps.Load(); err != nil { + return false + } + return caps.Get(capability.EFFECTIVE, capability.CAP_SETUID) && + caps.Get(capability.EFFECTIVE, capability.CAP_SETGID) +} diff --git a/runsc/test/testutil/testutil.go b/runsc/test/testutil/testutil.go index 4429b981b..77bd56912 100644 --- a/runsc/test/testutil/testutil.go +++ b/runsc/test/testutil/testutil.go @@ -104,13 +104,14 @@ func FindFile(path string) (string, error) { // TestConfig return the default configuration to use in tests. func TestConfig() *boot.Config { return &boot.Config{ - Debug: true, - LogFormat: "text", - LogPackets: true, - Network: boot.NetworkNone, - Strace: true, - MultiContainer: true, - FileAccess: boot.FileAccessProxyExclusive, + Debug: true, + LogFormat: "text", + LogPackets: true, + Network: boot.NetworkNone, + Strace: true, + MultiContainer: true, + FileAccess: boot.FileAccessProxyExclusive, + TestOnlyAllowRunAsCurrentUser: true, } }