runsc: Run sandbox as user nobody.

When starting a sandbox without direct file or network access, we create an
empty user namespace and run the sandbox in there.  However, the root user in
that namespace is still mapped to the root user in the parent namespace.

This CL maps the "nobody" user from the parent namespace into the child
namespace, and runs the sandbox process as user "nobody" inside the new
namespace.

PiperOrigin-RevId: 211572223
Change-Id: I1b1f9b1a86c0b4e7e5ca7bc93be7d4887678bab6
This commit is contained in:
Nicolas Lacasse 2018-09-04 20:31:52 -07:00 committed by Shentubot
parent ad8648c634
commit 0a9a40abcd
6 changed files with 63 additions and 12 deletions

View File

@ -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
```

View File

@ -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.

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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,
}
}