diff --git a/pkg/sentry/control/lifecycle.go b/pkg/sentry/control/lifecycle.go index 67abf497d..52b44babc 100644 --- a/pkg/sentry/control/lifecycle.go +++ b/pkg/sentry/control/lifecycle.go @@ -15,12 +15,152 @@ package control import ( + "fmt" + "strings" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/fd" + "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/fs/user" "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/limits" + "gvisor.dev/gvisor/pkg/sentry/vfs" + "gvisor.dev/gvisor/pkg/urpc" ) // Lifecycle provides functions related to starting and stopping tasks. type Lifecycle struct { + // Kernel is the kernel where the tasks belong to. Kernel *kernel.Kernel + + // Sends a message to the sentry that the task has been started. + StartedCh chan struct{} + + // TODO(b/202052732): Root mount namespace. When running multiple + // containers, create the mount namespace using the mount spec in + // the StartContainerArgs. + MountNamespaceVFS2 *vfs.MountNamespace +} + +// StartContainerArgs is the set of arguments to start a container. +type StartContainerArgs struct { + // Filename is the filename to load. + // + // If this is provided as "", then the file will be guessed via Argv[0]. + Filename string `json:"filename"` + + // Argv is a list of arguments. + Argv []string `json:"argv"` + + // Envv is a list of environment variables. + Envv []string `json:"envv"` + + // WorkingDirectory defines the working directory for the new process. + WorkingDirectory string `json:"wd"` + + // KUID is the UID to run with in the root user namespace. Defaults to + // root if not set explicitly. + KUID auth.KUID `json:"KUID"` + + // KGID is the GID to run with in the root user namespace. Defaults to + // the root group if not set explicitly. + KGID auth.KGID `json:"KGID"` + + // ExtraKGIDs is the list of additional groups to which the user belongs. + ExtraKGIDs []auth.KGID `json:"extraKGID"` + + // Capabilities is the list of capabilities to give to the process. + Capabilities *auth.TaskCapabilities `json:"capabilities"` + + // FilePayload determines the files to give to the new process. + urpc.FilePayload + + // ContainerID is the container for the process being executed. + ContainerID string `json:"containerID"` + + // Limits is the limit set for the process being executed. + Limits *limits.LimitSet `json:"limits"` +} + +// String prints the StartContainerArgs.argv as a string. +func (args StartContainerArgs) String() string { + if len(args.Argv) == 0 { + return args.Filename + } + a := make([]string, len(args.Argv)) + copy(a, args.Argv) + if args.Filename != "" { + a[0] = args.Filename + } + return strings.Join(a, " ") +} + +// StartContainer will start a new container in the sandbox. +func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error { + // Import file descriptors. + fdTable := l.Kernel.NewFDTable() + + creds := auth.NewUserCredentials( + args.KUID, + args.KGID, + args.ExtraKGIDs, + args.Capabilities, + l.Kernel.RootUserNamespace()) + + limitSet := args.Limits + if limitSet == nil { + limitSet = limits.NewLimitSet() + } + initArgs := kernel.CreateProcessArgs{ + Filename: args.Filename, + Argv: args.Argv, + Envv: args.Envv, + WorkingDirectory: args.WorkingDirectory, + Credentials: creds, + FDTable: fdTable, + Umask: 0022, + Limits: limitSet, + MaxSymlinkTraversals: linux.MaxSymlinkTraversals, + UTSNamespace: l.Kernel.RootUTSNamespace(), + IPCNamespace: l.Kernel.RootIPCNamespace(), + AbstractSocketNamespace: l.Kernel.RootAbstractSocketNamespace(), + ContainerID: args.ContainerID, + PIDNamespace: l.Kernel.RootPIDNamespace(), + MountNamespaceVFS2: l.MountNamespaceVFS2, + } + + ctx := initArgs.NewContext(l.Kernel) + defer fdTable.DecRef(ctx) + + resolved, err := user.ResolveExecutablePath(ctx, &initArgs) + if err != nil { + return err + } + initArgs.Filename = resolved + + fds, err := fd.NewFromFiles(args.Files) + if err != nil { + return fmt.Errorf("duplicating payload files: %w", err) + } + defer func() { + for _, fd := range fds { + _ = fd.Close() + } + }() + + tg, _, err := l.Kernel.CreateProcess(initArgs) + if err != nil { + return err + } + + // Start the newly created process. + l.Kernel.StartProcess(tg) + + log.Infof("Started the new container") + + l.StartedCh <- struct{}{} + return nil } // Pause pauses all tasks, blocking until they are stopped. diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index 395a39911..8b5af6a66 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -950,6 +950,9 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, if VFS2Enabled { mntnsVFS2 = args.MountNamespaceVFS2 if mntnsVFS2 == nil { + if k.globalInit == nil { + return nil, 0, fmt.Errorf("mount namespace is nil") + } // Add a reference to the namespace, which is transferred to the new process. mntnsVFS2 = k.globalInit.Leader().MountNamespaceVFS2() mntnsVFS2.IncRef() @@ -983,6 +986,9 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, } else { mntns = args.MountNamespace if mntns == nil { + if k.globalInit == nil { + return nil, 0, fmt.Errorf("mount namespace is nil") + } mntns = k.GlobalInit().Leader().MountNamespace() mntns.IncRef() } @@ -1100,9 +1106,6 @@ func (k *Kernel) Start() error { k.extMu.Lock() defer k.extMu.Unlock() - if k.globalInit == nil { - return fmt.Errorf("kernel contains no tasks") - } if k.started { return fmt.Errorf("kernel already started") }