Configure sandbox as superuser
Container user might not have enough priviledge to walk directories and mount filesystems. Instead, create superuser to perform these steps of the configuration. PiperOrigin-RevId: 197953667 Change-Id: I643650ab654e665408e2af1b8e2f2aa12d58d4fb
This commit is contained in:
parent
7996ae7ccf
commit
e48f707876
|
@ -51,21 +51,30 @@ func (f *fdDispenser) empty() bool {
|
||||||
return len(f.fds) == 0
|
return len(f.fds) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMountNamespace creates a mount manager containing the root filesystem
|
// createMountNamespace creates a mount namespace containing the root filesystem
|
||||||
// and all mounts.
|
// and all mounts. 'rootCtx' is used to walk directories to find mount points.
|
||||||
func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, ioFDs []int) (*fs.MountNamespace, error) {
|
func createMountNamespace(userCtx context.Context, rootCtx context.Context, spec *specs.Spec, conf *Config, ioFDs []int) (*fs.MountNamespace, error) {
|
||||||
fds := &fdDispenser{fds: ioFDs}
|
fds := &fdDispenser{fds: ioFDs}
|
||||||
|
rootInode, err := createRootMount(rootCtx, spec, conf, fds)
|
||||||
// Create the MountNamespace from the root.
|
|
||||||
rootInode, err := createRootMount(ctx, spec, conf, fds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create root overlay: %v", err)
|
return nil, fmt.Errorf("failed to create root mount: %v", err)
|
||||||
}
|
}
|
||||||
mns, err := fs.NewMountNamespace(ctx, rootInode)
|
mns, err := fs.NewMountNamespace(userCtx, rootInode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to construct MountNamespace: %v", err)
|
return nil, fmt.Errorf("failed to create root mount namespace: %v", err)
|
||||||
|
}
|
||||||
|
if err := configureMounts(rootCtx, spec, conf, mns, fds); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to configure mounts: %v", err)
|
||||||
|
}
|
||||||
|
if !fds.empty() {
|
||||||
|
return nil, fmt.Errorf("not all mount points were consumed, remaining: %v", fds)
|
||||||
|
}
|
||||||
|
return mns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// configureMounts iterates over Spec.Mounts and mounts them in the specified
|
||||||
|
// mount namespace.
|
||||||
|
func configureMounts(ctx context.Context, spec *specs.Spec, conf *Config, mns *fs.MountNamespace, fds *fdDispenser) error {
|
||||||
// Keep track of whether proc, sys, and tmp were mounted.
|
// Keep track of whether proc, sys, and tmp were mounted.
|
||||||
var procMounted, sysMounted, tmpMounted bool
|
var procMounted, sysMounted, tmpMounted bool
|
||||||
|
|
||||||
|
@ -88,7 +97,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mountSubmount(ctx, spec, conf, mns, fds, m); err != nil {
|
if err := mountSubmount(ctx, spec, conf, mns, fds, m); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +106,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
|
||||||
Type: "devtmpfs",
|
Type: "devtmpfs",
|
||||||
Destination: "/dev",
|
Destination: "/dev",
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount proc and sys even if the user did not ask for it, as the spec
|
// Mount proc and sys even if the user did not ask for it, as the spec
|
||||||
|
@ -107,7 +116,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
|
||||||
Type: "proc",
|
Type: "proc",
|
||||||
Destination: "/proc",
|
Destination: "/proc",
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !sysMounted {
|
if !sysMounted {
|
||||||
|
@ -115,7 +124,7 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
|
||||||
Type: "sysfs",
|
Type: "sysfs",
|
||||||
Destination: "/sys",
|
Destination: "/sys",
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,15 +136,11 @@ func createMountNamespace(ctx context.Context, spec *specs.Spec, conf *Config, i
|
||||||
Type: "tmpfs",
|
Type: "tmpfs",
|
||||||
Destination: "/tmp",
|
Destination: "/tmp",
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !fds.empty() {
|
return nil
|
||||||
return nil, fmt.Errorf("not all mount points were consumed, remaining: %v", fds)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mns, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRootMount creates the root filesystem.
|
// createRootMount creates the root filesystem.
|
||||||
|
|
|
@ -137,9 +137,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
|
||||||
extraKGIDs,
|
extraKGIDs,
|
||||||
caps,
|
caps,
|
||||||
auth.NewRootUserNamespace())
|
auth.NewRootUserNamespace())
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating credentials: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create user namespace.
|
// Create user namespace.
|
||||||
// TODO: Not clear what domain name should be here. It is
|
// TODO: Not clear what domain name should be here. It is
|
||||||
|
@ -159,22 +156,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
|
||||||
return nil, fmt.Errorf("error getting executable path: %v", err)
|
return nil, fmt.Errorf("error getting executable path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the process arguments.
|
|
||||||
procArgs := kernel.CreateProcessArgs{
|
|
||||||
Filename: exec,
|
|
||||||
Argv: spec.Process.Args,
|
|
||||||
Envv: spec.Process.Env,
|
|
||||||
WorkingDirectory: spec.Process.Cwd,
|
|
||||||
Credentials: creds,
|
|
||||||
// Creating the FDMap requires that we have kernel.Kernel.fdMapUids, so
|
|
||||||
// it must wait until we have a Kernel.
|
|
||||||
Umask: uint(syscall.Umask(0)),
|
|
||||||
Limits: ls,
|
|
||||||
MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
|
|
||||||
UTSNamespace: utsns,
|
|
||||||
IPCNamespace: ipcns,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an empty network stack because the network namespace may be empty at
|
// Create an empty network stack because the network namespace may be empty at
|
||||||
// this point. Netns is configured before Run() is called. Netstack is
|
// this point. Netns is configured before Run() is called. Netstack is
|
||||||
// configured using a control uRPC message. Host network is configured inside
|
// configured using a control uRPC message. Host network is configured inside
|
||||||
|
@ -219,14 +200,39 @@ func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console
|
||||||
return nil, fmt.Errorf("error creating control server: %v", err)
|
return nil, fmt.Errorf("error creating control server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the process arguments.
|
||||||
|
procArgs := kernel.CreateProcessArgs{
|
||||||
|
Filename: exec,
|
||||||
|
Argv: spec.Process.Args,
|
||||||
|
Envv: spec.Process.Env,
|
||||||
|
WorkingDirectory: spec.Process.Cwd,
|
||||||
|
Credentials: creds,
|
||||||
|
// Creating the FDMap requires that we have kernel.Kernel.fdMapUids, so
|
||||||
|
// it must wait until we have a Kernel.
|
||||||
|
Umask: uint(syscall.Umask(0)),
|
||||||
|
Limits: ls,
|
||||||
|
MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
|
||||||
|
UTSNamespace: utsns,
|
||||||
|
IPCNamespace: ipcns,
|
||||||
|
}
|
||||||
ctx := procArgs.NewContext(k)
|
ctx := procArgs.NewContext(k)
|
||||||
|
|
||||||
|
// Use root user to configure mounts. The current user might not have
|
||||||
|
// permission to do so.
|
||||||
|
rootProcArgs := kernel.CreateProcessArgs{
|
||||||
|
WorkingDirectory: "/",
|
||||||
|
Credentials: auth.NewRootCredentials(creds.UserNamespace),
|
||||||
|
Umask: uint(syscall.Umask(0022)),
|
||||||
|
MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
|
||||||
|
}
|
||||||
|
rootCtx := rootProcArgs.NewContext(k)
|
||||||
|
|
||||||
// Create the virtual filesystem.
|
// Create the virtual filesystem.
|
||||||
mm, err := createMountNamespace(ctx, spec, conf, ioFDs)
|
mns, err := createMountNamespace(ctx, rootCtx, spec, conf, ioFDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating mounts: %v", err)
|
return nil, fmt.Errorf("error creating mounts: %v", err)
|
||||||
}
|
}
|
||||||
k.SetRootMountNamespace(mm)
|
k.SetRootMountNamespace(mns)
|
||||||
|
|
||||||
// Create the FD map, which will set stdin, stdout, and stderr. If console
|
// Create the FD map, which will set stdin, stdout, and stderr. If console
|
||||||
// is true, then ioctl calls will be passed through to the host fd.
|
// is true, then ioctl calls will be passed through to the host fd.
|
||||||
|
|
|
@ -239,7 +239,7 @@ func TestCreateMountNamespace(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
ctx := contexttest.Context(t)
|
ctx := contexttest.Context(t)
|
||||||
mm, err := createMountNamespace(ctx, &tc.spec, conf, nil)
|
mm, err := createMountNamespace(ctx, ctx, &tc.spec, conf, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("createMountNamespace test case %q failed: %v", tc.name, err)
|
t.Fatalf("createMountNamespace test case %q failed: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -132,6 +133,34 @@ func waitForProcessList(s *container.Container, expected []*control.Process) err
|
||||||
return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(expected))
|
return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(expected))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// procListsEqual is used to check whether 2 Process lists are equal for all
|
||||||
|
// implemented fields.
|
||||||
|
func procListsEqual(got, want []*control.Process) bool {
|
||||||
|
if len(got) != len(want) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
pd1 := got[i]
|
||||||
|
pd2 := want[i]
|
||||||
|
// Zero out unimplemented and timing dependant fields.
|
||||||
|
pd1.Time, pd2.Time = "", ""
|
||||||
|
pd1.STime, pd2.STime = "", ""
|
||||||
|
pd1.C, pd2.C = 0, 0
|
||||||
|
if *pd1 != *pd2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func procListToString(pl []*control.Process) string {
|
||||||
|
strs := make([]string, 0, len(pl))
|
||||||
|
for _, p := range pl {
|
||||||
|
strs = append(strs, fmt.Sprintf("%+v", p))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%s]", strings.Join(strs, ","))
|
||||||
|
}
|
||||||
|
|
||||||
// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
|
// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
|
||||||
// It verifies after each step that the container can be loaded from disk, and
|
// It verifies after each step that the container can be loaded from disk, and
|
||||||
// has the correct status.
|
// has the correct status.
|
||||||
|
@ -434,17 +463,6 @@ func TestCapabilities(t *testing.T) {
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Capability below is needed to mount TempDir above in case the user doesn't
|
|
||||||
// have access to all parents that lead to TempDir.
|
|
||||||
caps := []string{"CAP_DAC_OVERRIDE"}
|
|
||||||
spec.Process.Capabilities = &specs.LinuxCapabilities{
|
|
||||||
Bounding: caps,
|
|
||||||
Effective: caps,
|
|
||||||
Inheritable: caps,
|
|
||||||
Permitted: caps,
|
|
||||||
Ambient: caps,
|
|
||||||
}
|
|
||||||
|
|
||||||
rootDir, bundleDir, conf, err := setupContainer(spec)
|
rootDir, bundleDir, conf, err := setupContainer(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error setting up container: %v", err)
|
t.Fatalf("error setting up container: %v", err)
|
||||||
|
@ -621,32 +639,54 @@ func TestSpecUnsupported(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// procListsEqual is used to check whether 2 Process lists are equal for all
|
// TestRunNonRoot checks that sandbox can be configured when running as
|
||||||
// implemented fields.
|
// non-priviledged user.
|
||||||
func procListsEqual(got, want []*control.Process) bool {
|
func TestRunNonRoot(t *testing.T) {
|
||||||
if len(got) != len(want) {
|
spec := newSpecWithArgs("/bin/true")
|
||||||
return false
|
spec.Process.User.UID = 343
|
||||||
|
spec.Process.User.GID = 2401
|
||||||
|
|
||||||
|
// User that container runs as can't list '$TMP/blocked' and would fail to
|
||||||
|
// mount it.
|
||||||
|
dir := path.Join(os.TempDir(), "blocked")
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
|
||||||
}
|
}
|
||||||
for i := range got {
|
dir = path.Join(dir, "test")
|
||||||
pd1 := got[i]
|
if err := os.Mkdir(dir, 0755); err != nil {
|
||||||
pd2 := want[i]
|
t.Fatalf("os.MkDir(%q) failed: %v", dir, err)
|
||||||
// Zero out unimplemented and timing dependant fields.
|
|
||||||
pd1.Time, pd2.Time = "", ""
|
|
||||||
pd1.STime, pd2.STime = "", ""
|
|
||||||
pd1.C, pd2.C = 0, 0
|
|
||||||
if *pd1 != *pd2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func procListToString(pl []*control.Process) string {
|
// We generate files in the host temporary directory.
|
||||||
strs := make([]string, 0, len(pl))
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
||||||
for _, p := range pl {
|
Destination: dir,
|
||||||
strs = append(strs, fmt.Sprintf("%+v", p))
|
Source: dir,
|
||||||
|
Type: "bind",
|
||||||
|
})
|
||||||
|
|
||||||
|
rootDir, bundleDir, conf, err := setupContainer(spec)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error setting up container: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(rootDir)
|
||||||
|
defer os.RemoveAll(bundleDir)
|
||||||
|
|
||||||
|
// Create, start and wait for the container.
|
||||||
|
s, err := container.Create(uniqueContainerID(), spec, conf, bundleDir, "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating container: %v", err)
|
||||||
|
}
|
||||||
|
defer s.Destroy()
|
||||||
|
if err := s.Start(conf); err != nil {
|
||||||
|
t.Fatalf("error starting container: %v", err)
|
||||||
|
}
|
||||||
|
ws, err := s.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error waiting on container: %v", err)
|
||||||
|
}
|
||||||
|
if !ws.Exited() || ws.ExitStatus() != 0 {
|
||||||
|
t.Errorf("container failed, waitStatus: %v", ws)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s]", strings.Join(strs, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMain acts like runsc if it is called with the "boot" argument, otherwise
|
// TestMain acts like runsc if it is called with the "boot" argument, otherwise
|
||||||
|
|
Loading…
Reference in New Issue