summaryrefslogtreecommitdiffhomepage
path: root/runsc/sandbox/sandbox.go
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/sandbox/sandbox.go')
-rw-r--r--runsc/sandbox/sandbox.go230
1 files changed, 132 insertions, 98 deletions
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index f14cc7229..5fb7dc834 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -65,6 +65,11 @@ type Sandbox struct {
// is not running.
Pid int `json:"pid"`
+ // UID is the user ID in the parent namespace that the sandbox is running as.
+ UID int `json:"uid"`
+ // GID is the group ID in the parent namespace that the sandbox is running as.
+ GID int `json:"gid"`
+
// Cgroup has the cgroup configuration for the sandbox.
Cgroup *cgroup.Cgroup `json:"cgroup"`
@@ -175,26 +180,30 @@ func New(conf *config.Config, args *Args) (*Sandbox, error) {
return s, nil
}
-// CreateContainer creates a non-root container inside the sandbox.
-func (s *Sandbox) CreateContainer(cid string, tty *os.File) error {
- log.Debugf("Create non-root container %q in sandbox %q, PID: %d", cid, s.ID, s.Pid)
- sandboxConn, err := s.sandboxConnect()
- if err != nil {
- return fmt.Errorf("couldn't connect to sandbox: %v", err)
- }
- defer sandboxConn.Close()
+// CreateSubcontainer creates a container inside the sandbox.
+func (s *Sandbox) CreateSubcontainer(conf *config.Config, cid string, tty *os.File) error {
+ log.Debugf("Create sub-container %q in sandbox %q, PID: %d", cid, s.ID, s.Pid)
var files []*os.File
if tty != nil {
files = []*os.File{tty}
}
+ if err := s.configureStdios(conf, files); err != nil {
+ return err
+ }
+
+ sandboxConn, err := s.sandboxConnect()
+ if err != nil {
+ return fmt.Errorf("couldn't connect to sandbox: %v", err)
+ }
+ defer sandboxConn.Close()
args := boot.CreateArgs{
CID: cid,
FilePayload: urpc.FilePayload{Files: files},
}
- if err := sandboxConn.Call(boot.ContainerCreate, &args, nil); err != nil {
- return fmt.Errorf("creating non-root container %q: %v", cid, err)
+ if err := sandboxConn.Call(boot.ContMgrCreateSubcontainer, &args, nil); err != nil {
+ return fmt.Errorf("creating sub-container %q: %v", cid, err)
}
return nil
}
@@ -209,22 +218,27 @@ func (s *Sandbox) StartRoot(spec *specs.Spec, conf *config.Config) error {
defer conn.Close()
// Configure the network.
- if err := setupNetwork(conn, s.Pid, spec, conf); err != nil {
+ if err := setupNetwork(conn, s.Pid, conf); err != nil {
return fmt.Errorf("setting up network: %v", err)
}
// Send a message to the sandbox control server to start the root
// container.
- if err := conn.Call(boot.RootContainerStart, &s.ID, nil); err != nil {
+ if err := conn.Call(boot.ContMgrRootContainerStart, &s.ID, nil); err != nil {
return fmt.Errorf("starting root container: %v", err)
}
return nil
}
-// StartContainer starts running a non-root container inside the sandbox.
-func (s *Sandbox) StartContainer(spec *specs.Spec, conf *config.Config, cid string, stdios, goferFiles []*os.File) error {
- log.Debugf("Start non-root container %q in sandbox %q, PID: %d", cid, s.ID, s.Pid)
+// StartSubcontainer starts running a sub-container inside the sandbox.
+func (s *Sandbox) StartSubcontainer(spec *specs.Spec, conf *config.Config, cid string, stdios, goferFiles []*os.File) error {
+ log.Debugf("Start sub-container %q in sandbox %q, PID: %d", cid, s.ID, s.Pid)
+
+ if err := s.configureStdios(conf, stdios); err != nil {
+ return err
+ }
+
sandboxConn, err := s.sandboxConnect()
if err != nil {
return fmt.Errorf("couldn't connect to sandbox: %v", err)
@@ -244,8 +258,8 @@ func (s *Sandbox) StartContainer(spec *specs.Spec, conf *config.Config, cid stri
CID: cid,
FilePayload: payload,
}
- if err := sandboxConn.Call(boot.ContainerStart, &args, nil); err != nil {
- return fmt.Errorf("starting non-root container %v: %v", spec.Process.Args, err)
+ if err := sandboxConn.Call(boot.ContMgrStartSubcontainer, &args, nil); err != nil {
+ return fmt.Errorf("starting sub-container %v: %v", spec.Process.Args, err)
}
return nil
}
@@ -282,12 +296,12 @@ func (s *Sandbox) Restore(cid string, spec *specs.Spec, conf *config.Config, fil
defer conn.Close()
// Configure the network.
- if err := setupNetwork(conn, s.Pid, spec, conf); err != nil {
+ if err := setupNetwork(conn, s.Pid, conf); err != nil {
return fmt.Errorf("setting up network: %v", err)
}
// Restore the container and start the root container.
- if err := conn.Call(boot.ContainerRestore, &opt, nil); err != nil {
+ if err := conn.Call(boot.ContMgrRestore, &opt, nil); err != nil {
return fmt.Errorf("restoring container %q: %v", cid, err)
}
@@ -305,7 +319,7 @@ func (s *Sandbox) Processes(cid string) ([]*control.Process, error) {
defer conn.Close()
var pl []*control.Process
- if err := conn.Call(boot.ContainerProcesses, &cid, &pl); err != nil {
+ if err := conn.Call(boot.ContMgrProcesses, &cid, &pl); err != nil {
return nil, fmt.Errorf("retrieving process data from sandbox: %v", err)
}
return pl, nil
@@ -318,8 +332,13 @@ func (s *Sandbox) NewCGroup() (*cgroup.Cgroup, error) {
// Execute runs the specified command in the container. It returns the PID of
// the newly created process.
-func (s *Sandbox) Execute(args *control.ExecArgs) (int32, error) {
+func (s *Sandbox) Execute(conf *config.Config, args *control.ExecArgs) (int32, error) {
log.Debugf("Executing new process in container %q in sandbox %q", args.ContainerID, s.ID)
+
+ if err := s.configureStdios(conf, args.Files); err != nil {
+ return 0, err
+ }
+
conn, err := s.sandboxConnect()
if err != nil {
return 0, s.connError(err)
@@ -328,7 +347,7 @@ func (s *Sandbox) Execute(args *control.ExecArgs) (int32, error) {
// Send a message to the sandbox control server to start the container.
var pid int32
- if err := conn.Call(boot.ContainerExecuteAsync, args, &pid); err != nil {
+ if err := conn.Call(boot.ContMgrExecuteAsync, args, &pid); err != nil {
return 0, fmt.Errorf("executing command %q in sandbox: %v", args, err)
}
return pid, nil
@@ -346,7 +365,7 @@ func (s *Sandbox) Event(cid string) (*boot.EventOut, error) {
var e boot.EventOut
// TODO(b/129292330): Pass in the container id (cid) here. The sandbox
// should return events only for that container.
- if err := conn.Call(boot.ContainerEvent, nil, &e); err != nil {
+ if err := conn.Call(boot.ContMgrEvent, nil, &e); err != nil {
return nil, fmt.Errorf("retrieving event data from sandbox: %v", err)
}
e.Event.ID = cid
@@ -505,6 +524,7 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil
+ var stdios [3]*os.File
// If the console control socket file is provided, then create a new
// pty master/replica pair and set the TTY on the sandbox process.
@@ -525,11 +545,9 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
cmd.SysProcAttr.Ctty = nextFD
// Pass the tty as all stdio fds to sandbox.
- for i := 0; i < 3; i++ {
- cmd.ExtraFiles = append(cmd.ExtraFiles, tty)
- cmd.Args = append(cmd.Args, "--stdio-fds="+strconv.Itoa(nextFD))
- nextFD++
- }
+ stdios[0] = tty
+ stdios[1] = tty
+ stdios[2] = tty
if conf.Debug {
// If debugging, send the boot process stdio to the
@@ -541,11 +559,9 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
} else {
// If not using a console, pass our current stdio as the
// container stdio via flags.
- for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
- cmd.ExtraFiles = append(cmd.ExtraFiles, f)
- cmd.Args = append(cmd.Args, "--stdio-fds="+strconv.Itoa(nextFD))
- nextFD++
- }
+ stdios[0] = os.Stdin
+ stdios[1] = os.Stdout
+ stdios[2] = os.Stderr
if conf.Debug {
// If debugging, send the boot process stdio to the
@@ -595,6 +611,10 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
nss = append(nss, specs.LinuxNamespace{Type: specs.NetworkNamespace})
}
+ // These are set to the uid/gid that the sandbox process will use.
+ s.UID = os.Getuid()
+ s.GID = os.Getgid()
+
// User namespace depends on the network type. Host network requires to run
// inside the user namespace specified in the spec or the current namespace
// if none is configured.
@@ -636,51 +656,49 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
const nobody = 65534
if conf.Rootless {
log.Infof("Rootless mode: sandbox will run as nobody inside user namespace, mapped to the current user, uid: %d, gid: %d", os.Getuid(), os.Getgid())
- cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{
- {
- ContainerID: nobody,
- HostID: os.Getuid(),
- Size: 1,
- },
- }
- cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{
- {
- ContainerID: nobody,
- HostID: os.Getgid(),
- Size: 1,
- },
- }
-
} else {
// Map nobody in the new namespace to nobody in the parent namespace.
- //
- // A sandbox process will construct an empty
- // root for itself, so it has to have
- // CAP_SYS_ADMIN and CAP_SYS_CHROOT capabilities.
- cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{
- {
- ContainerID: nobody,
- HostID: nobody,
- Size: 1,
- },
- }
- cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{
- {
- ContainerID: nobody,
- HostID: nobody,
- Size: 1,
- },
- }
+ s.UID = nobody
+ s.GID = nobody
}
// Set credentials to run as user and group nobody.
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: nobody, Gid: nobody}
+ cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{
+ {
+ ContainerID: nobody,
+ HostID: s.UID,
+ Size: 1,
+ },
+ }
+ cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{
+ {
+ ContainerID: nobody,
+ HostID: s.GID,
+ Size: 1,
+ },
+ }
+
+ // A sandbox process will construct an empty root for itself, so it has
+ // to have CAP_SYS_ADMIN and CAP_SYS_CHROOT capabilities.
cmd.SysProcAttr.AmbientCaps = append(cmd.SysProcAttr.AmbientCaps, uintptr(capability.CAP_SYS_ADMIN), uintptr(capability.CAP_SYS_CHROOT))
+
} else {
return fmt.Errorf("can't run sandbox process as user nobody since we don't have CAP_SETUID or CAP_SETGID")
}
}
+ if err := s.configureStdios(conf, stdios[:]); err != nil {
+ return fmt.Errorf("configuring stdios: %w", err)
+ }
+ for _, file := range stdios {
+ cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+ cmd.Args = append(cmd.Args, "--stdio-fds="+strconv.Itoa(nextFD))
+ nextFD++
+ }
+
+ // Set Args[0] to make easier to spot the sandbox process. Otherwise it's
+ // shown as `exe`.
cmd.Args[0] = "runsc-sandbox"
if s.Cgroup != nil {
@@ -796,7 +814,7 @@ func (s *Sandbox) Wait(cid string) (unix.WaitStatus, error) {
// Try the Wait RPC to the sandbox.
var ws unix.WaitStatus
- err = conn.Call(boot.ContainerWait, &cid, &ws)
+ err = conn.Call(boot.ContMgrWait, &cid, &ws)
conn.Close()
if err == nil {
if s.IsRootContainer(cid) {
@@ -847,7 +865,7 @@ func (s *Sandbox) WaitPID(cid string, pid int32) (unix.WaitStatus, error) {
PID: pid,
CID: cid,
}
- if err := conn.Call(boot.ContainerWaitPID, args, &ws); err != nil {
+ if err := conn.Call(boot.ContMgrWaitPID, args, &ws); err != nil {
return ws, fmt.Errorf("waiting on PID %d in sandbox %q: %v", pid, s.ID, err)
}
return ws, nil
@@ -897,7 +915,7 @@ func (s *Sandbox) SignalContainer(cid string, sig unix.Signal, all bool) error {
Signo: int32(sig),
Mode: mode,
}
- if err := conn.Call(boot.ContainerSignal, &args, nil); err != nil {
+ if err := conn.Call(boot.ContMgrSignal, &args, nil); err != nil {
return fmt.Errorf("signaling container %q: %v", cid, err)
}
return nil
@@ -926,7 +944,7 @@ func (s *Sandbox) SignalProcess(cid string, pid int32, sig unix.Signal, fgProces
PID: pid,
Mode: mode,
}
- if err := conn.Call(boot.ContainerSignal, &args, nil); err != nil {
+ if err := conn.Call(boot.ContMgrSignal, &args, nil); err != nil {
return fmt.Errorf("signaling container %q PID %d: %v", cid, pid, err)
}
return nil
@@ -948,7 +966,7 @@ func (s *Sandbox) Checkpoint(cid string, f *os.File) error {
},
}
- if err := conn.Call(boot.ContainerCheckpoint, &opt, nil); err != nil {
+ if err := conn.Call(boot.ContMgrCheckpoint, &opt, nil); err != nil {
return fmt.Errorf("checkpointing container %q: %v", cid, err)
}
return nil
@@ -963,7 +981,7 @@ func (s *Sandbox) Pause(cid string) error {
}
defer conn.Close()
- if err := conn.Call(boot.ContainerPause, nil, nil); err != nil {
+ if err := conn.Call(boot.ContMgrPause, nil, nil); err != nil {
return fmt.Errorf("pausing container %q: %v", cid, err)
}
return nil
@@ -978,7 +996,7 @@ func (s *Sandbox) Resume(cid string) error {
}
defer conn.Close()
- if err := conn.Call(boot.ContainerResume, nil, nil); err != nil {
+ if err := conn.Call(boot.ContMgrResume, nil, nil); err != nil {
return fmt.Errorf("resuming container %q: %v", cid, err)
}
return nil
@@ -1006,7 +1024,7 @@ func (s *Sandbox) Stacks() (string, error) {
defer conn.Close()
var stacks string
- if err := conn.Call(boot.SandboxStacks, nil, &stacks); err != nil {
+ if err := conn.Call(boot.DebugStacks, nil, &stacks); err != nil {
return "", fmt.Errorf("getting sandbox %q stacks: %v", s.ID, err)
}
return stacks, nil
@@ -1025,7 +1043,7 @@ func (s *Sandbox) HeapProfile(f *os.File, delay time.Duration) error {
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
Delay: delay,
}
- return conn.Call(boot.HeapProfile, &opts, nil)
+ return conn.Call(boot.ProfileHeap, &opts, nil)
}
// CPUProfile collects a CPU profile.
@@ -1041,7 +1059,7 @@ func (s *Sandbox) CPUProfile(f *os.File, duration time.Duration) error {
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
Duration: duration,
}
- return conn.Call(boot.CPUProfile, &opts, nil)
+ return conn.Call(boot.ProfileCPU, &opts, nil)
}
// BlockProfile writes a block profile to the given file.
@@ -1057,7 +1075,7 @@ func (s *Sandbox) BlockProfile(f *os.File, duration time.Duration) error {
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
Duration: duration,
}
- return conn.Call(boot.BlockProfile, &opts, nil)
+ return conn.Call(boot.ProfileBlock, &opts, nil)
}
// MutexProfile writes a mutex profile to the given file.
@@ -1073,7 +1091,7 @@ func (s *Sandbox) MutexProfile(f *os.File, duration time.Duration) error {
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
Duration: duration,
}
- return conn.Call(boot.MutexProfile, &opts, nil)
+ return conn.Call(boot.ProfileMutex, &opts, nil)
}
// Trace collects an execution trace.
@@ -1089,7 +1107,7 @@ func (s *Sandbox) Trace(f *os.File, duration time.Duration) error {
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
Duration: duration,
}
- return conn.Call(boot.Trace, &opts, nil)
+ return conn.Call(boot.ProfileTrace, &opts, nil)
}
// ChangeLogging changes logging options.
@@ -1101,7 +1119,7 @@ func (s *Sandbox) ChangeLogging(args control.LoggingArgs) error {
}
defer conn.Close()
- if err := conn.Call(boot.ChangeLogging, &args, nil); err != nil {
+ if err := conn.Call(boot.LoggingChange, &args, nil); err != nil {
return fmt.Errorf("changing sandbox %q logging: %v", s.ID, err)
}
return nil
@@ -1132,34 +1150,33 @@ func (s *Sandbox) destroyContainer(cid string) error {
return err
}
defer conn.Close()
- if err := conn.Call(boot.ContainerDestroy, &cid, nil); err != nil {
+ if err := conn.Call(boot.ContMgrDestroySubcontainer, &cid, nil); err != nil {
return fmt.Errorf("destroying container %q: %v", cid, err)
}
return nil
}
func (s *Sandbox) waitForStopped() error {
+ if s.child {
+ s.statusMu.Lock()
+ defer s.statusMu.Unlock()
+ if s.Pid == 0 {
+ return nil
+ }
+ // The sandbox process is a child of the current process,
+ // so we can wait it and collect its zombie.
+ if _, err := unix.Wait4(int(s.Pid), &s.status, 0, nil); err != nil {
+ return fmt.Errorf("error waiting the sandbox process: %v", err)
+ }
+ s.Pid = 0
+ return nil
+ }
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
op := func() error {
- if s.child {
- s.statusMu.Lock()
- defer s.statusMu.Unlock()
- if s.Pid == 0 {
- return nil
- }
- // The sandbox process is a child of the current process,
- // so we can wait it and collect its zombie.
- wpid, err := unix.Wait4(int(s.Pid), &s.status, unix.WNOHANG, nil)
- if err != nil {
- return fmt.Errorf("error waiting the sandbox process: %v", err)
- }
- if wpid == 0 {
- return fmt.Errorf("sandbox is still running")
- }
- s.Pid = 0
- } else if s.IsRunning() {
+ if s.IsRunning() {
return fmt.Errorf("sandbox is still running")
}
return nil
@@ -1167,6 +1184,23 @@ func (s *Sandbox) waitForStopped() error {
return backoff.Retry(op, b)
}
+// configureStdios change stdios ownership to give access to the sandbox
+// process. This may be skipped depending on the configuration.
+func (s *Sandbox) configureStdios(conf *config.Config, stdios []*os.File) error {
+ if conf.Rootless || conf.TestOnlyAllowRunAsCurrentUserWithoutChroot {
+ // Cannot change ownership without CAP_CHOWN.
+ return nil
+ }
+
+ for _, file := range stdios {
+ log.Debugf("Changing %q ownership to %d/%d", file.Name(), s.UID, s.GID)
+ if err := file.Chown(s.UID, s.GID); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// deviceFileForPlatform opens the device file for the given platform. If the
// platform does not need a device file, then nil is returned.
func deviceFileForPlatform(name string) (*os.File, error) {