Handle more arguments in StartContainer.

PiperOrigin-RevId: 477234682
This commit is contained in:
Nicolas Lacasse
2022-09-27 11:58:04 -07:00
committed by gVisor bot
parent 6ac829ca1e
commit 564ff73c18
6 changed files with 136 additions and 71 deletions
+95 -39
View File
@@ -15,12 +15,13 @@
package control
import (
"encoding/json"
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fdimport"
"gvisor.dev/gvisor/pkg/sentry/fs/user"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -90,6 +91,11 @@ type StartContainerArgs struct {
// Envv is a list of environment variables.
Envv []string `json:"envv"`
// Secret_envv is a list of secret environment variables.
//
// NOTE: This field must never be logged!
SecretEnvv []string `json:"secret_envv"`
// WorkingDirectory defines the working directory for the new process.
WorkingDirectory string `json:"wd"`
@@ -101,33 +107,44 @@ type StartContainerArgs struct {
// 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"`
// ContainerID is the container for the process being executed.
ContainerID string `json:"container_id"`
// Capabilities is the list of capabilities to give to the process.
Capabilities *auth.TaskCapabilities `json:"capabilities"`
// Limits is the limit set for the process being executed.
Limits map[string]limits.Limit `json:"limits"`
// If HOME environment variable is not provided, and this flag is set,
// then the HOME environment variable will be set inside the container
// based on the user's home directory in /etc/passwd.
ResolveHome bool `json:"resolve_home"`
// If set, attempt to resolve the binary_path via the following procedure:
// 1) If binary_path is absolute, it is used directly.
// 2) If binary_path contains a slash, then it is resolved relative to the
// working_directory (or the root it working_directory is not set).
// 3) Otherwise, search the PATH environment variable for the first directory
// that contains an executable file with name in binary_path.
ResolveBinaryPath bool `json:"resolve_binary_path"`
// DonatedFDs is the list of sentry-intrenal file descriptors that will
// donated. They correspond to the donated files in FilePayload.
DonatedFDs []int `json:"donated_fds"`
// 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
// String formats the StartContainerArgs without the SecretEnvv field.
func (sca StartContainerArgs) String() string {
sca.SecretEnvv = make([]string, len(sca.SecretEnvv))
for i := range sca.SecretEnvv {
sca.SecretEnvv[i] = "(hidden)"
}
a := make([]string, len(args.Argv))
copy(a, args.Argv)
if args.Filename != "" {
a[0] = args.Filename
b, err := json.Marshal(sca)
if err != nil {
return fmt.Sprintf("error marshaling: %s", err)
}
return strings.Join(a, " ")
return string(b)
}
func (l *Lifecycle) updateContainerState(containerID string, newState containerState) error {
@@ -164,19 +181,28 @@ func (l *Lifecycle) updateContainerState(containerID string, newState containerS
// StartContainer will start a new container in the sandbox.
func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
// Import file descriptors.
fdTable := l.Kernel.NewFDTable()
log.Infof("StartContainer: %v", args)
if len(args.Files) != len(args.DonatedFDs) {
return fmt.Errorf("FilePayload.Files and DonatedFDs must have same number of elements (%d != %d)", len(args.Files), len(args.DonatedFDs))
}
creds := auth.NewUserCredentials(
args.KUID,
args.KGID,
args.ExtraKGIDs,
args.Capabilities,
nil, /* extraKGIDs */
nil, /* capabilities */
l.Kernel.RootUserNamespace())
limitSet := args.Limits
if limitSet == nil {
limitSet = limits.NewLimitSet()
ls, err := limits.NewLinuxDistroLimitSet()
if err != nil {
return fmt.Errorf("error creating default limit set: %w", err)
}
for name, limit := range args.Limits {
lt, ok := limits.FromLinuxResourceName[name]
if !ok {
return fmt.Errorf("unknown limit %q", name)
}
ls.SetUnchecked(lt, limit)
}
// Create a new pid namespace for the container. Each container must run
@@ -184,14 +210,14 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
pidNs := l.Kernel.RootPIDNamespace().NewChild(l.Kernel.RootUserNamespace())
initArgs := kernel.CreateProcessArgs{
Filename: args.Filename,
Argv: args.Argv,
Envv: args.Envv,
Filename: args.Filename,
Argv: args.Argv,
// Order Envv before SecretEnvv.
Envv: append(args.Envv, args.SecretEnvv...),
WorkingDirectory: args.WorkingDirectory,
Credentials: creds,
FDTable: fdTable,
Umask: 0022,
Limits: limitSet,
Limits: ls,
MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
UTSNamespace: l.Kernel.RootUTSNamespace(),
IPCNamespace: l.Kernel.RootIPCNamespace(),
@@ -201,7 +227,43 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
}
ctx := initArgs.NewContext(l.Kernel)
// Import file descriptors.
fdTable := l.Kernel.NewFDTable()
defer fdTable.DecRef(ctx)
hostFDs, err := fd.NewFromFiles(args.Files)
if err != nil {
return fmt.Errorf("error donating host files: %w", err)
}
defer func() {
for _, hfd := range hostFDs {
_ = hfd.Close()
}
}()
fdMap := make(map[int]*fd.FD, len(args.DonatedFDs))
for i, appFD := range args.DonatedFDs {
fdMap[appFD] = hostFDs[i]
}
if _, _, err := fdimport.Import(ctx, fdTable, false, args.KUID, args.KGID, fdMap); err != nil {
return fmt.Errorf("error importing host files: %w", err)
}
initArgs.FDTable = fdTable
if args.ResolveBinaryPath {
resolved, err := user.ResolveExecutablePath(ctx, &initArgs)
if err != nil {
return fmt.Errorf("failed to resolve binary path: %w", err)
}
initArgs.Filename = resolved
}
if args.ResolveHome {
envVars, err := user.MaybeAddExecUserHomeVFS2(ctx, initArgs.MountNamespaceVFS2, creds.RealKUID, initArgs.Envv)
if err != nil {
return fmt.Errorf("failed to get user home dir: %w", err)
}
initArgs.Envv = envVars
}
// VFS2 is supported in multi-container mode by default.
l.mu.RLock()
@@ -214,12 +276,6 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
l.mu.RUnlock()
initArgs.MountNamespaceVFS2.IncRef()
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)
@@ -289,7 +345,7 @@ func (l *Lifecycle) getInitContainerProcess(containerID string) (*kernel.ThreadG
// starting the container.
type ContainerArgs struct {
// ContainerID.
ContainerID string `json:"containerID"`
ContainerID string `json:"container_id"`
Signo int32 `json:"signo"`
SignalAll bool `json:"signalAll"`
}
+5 -1
View File
@@ -223,7 +223,11 @@ func (proc *Proc) execAsync(args *ExecArgs) (*kernel.ThreadGroup, kernel.ThreadI
_ = fd.Close()
}
}()
ttyFile, ttyFileVFS2, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fds)
fdMap := make(map[int]*fd.FD, len(fds))
for appFD, hostFD := range fds {
fdMap[appFD] = hostFD
}
ttyFile, ttyFileVFS2, err := fdimport.Import(ctx, fdTable, args.StdioIsPty, args.KUID, args.KGID, fdMap)
if err != nil {
return nil, 0, nil, nil, err
}
+7 -7
View File
@@ -28,11 +28,11 @@ import (
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// Import imports a slice of FDs into the given FDTable. If console is true,
// sets up TTY for the first 3 FDs in the slice representing stdin, stdout,
// stderr. Used FDs are either closed or released. It's safe for the caller to
// close any remaining files upon return.
func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, fds []*fd.FD) (*host.TTYFileOperations, *hostvfs2.TTYFileDescription, error) {
// Import imports a map of FDs into the given FDTable. If console is true,
// sets up TTY for sentry stdin, stdout, and stderr FDs. Used FDs are either
// closed or released. It's safe for the caller to close any remaining files
// upon return.
func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, fds map[int]*fd.FD) (*host.TTYFileOperations, *hostvfs2.TTYFileDescription, error) {
if kernel.VFS2Enabled {
ttyFile, err := importVFS2(ctx, fdTable, console, uid, gid, fds)
return nil, ttyFile, err
@@ -41,7 +41,7 @@ func Import(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth
return ttyFile, nil, err
}
func importFS(ctx context.Context, fdTable *kernel.FDTable, console bool, fds []*fd.FD) (*host.TTYFileOperations, error) {
func importFS(ctx context.Context, fdTable *kernel.FDTable, console bool, fds map[int]*fd.FD) (*host.TTYFileOperations, error) {
var ttyFile *fs.File
for appFD, hostFD := range fds {
var appFile *fs.File
@@ -90,7 +90,7 @@ func importFS(ctx context.Context, fdTable *kernel.FDTable, console bool, fds []
return ttyFile.FileOperations.(*host.TTYFileOperations), nil
}
func importVFS2(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, stdioFDs []*fd.FD) (*hostvfs2.TTYFileDescription, error) {
func importVFS2(ctx context.Context, fdTable *kernel.FDTable, console bool, uid auth.KUID, gid auth.KGID, stdioFDs map[int]*fd.FD) (*hostvfs2.TTYFileDescription, error) {
k := kernel.KernelFromContext(ctx)
if k == nil {
return nil, fmt.Errorf("cannot find kernel from context")
+21 -1
View File
@@ -20,7 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
)
// FromLinuxResource maps linux resources to sentry LimitTypes.
// FromLinuxResource maps linux resources to LimitTypes.
var FromLinuxResource = map[int]LimitType{
linux.RLIMIT_CPU: CPU,
linux.RLIMIT_FSIZE: FileSize,
@@ -40,6 +40,26 @@ var FromLinuxResource = map[int]LimitType{
linux.RLIMIT_RTTIME: Rttime,
}
// FromLinuxResourceName maps from linux resource names to LimitTypes.
var FromLinuxResourceName = map[string]LimitType{
"RLIMIT_AS": AS,
"RLIMIT_CORE": Core,
"RLIMIT_CPU": CPU,
"RLIMIT_DATA": Data,
"RLIMIT_FSIZE": FileSize,
"RLIMIT_LOCKS": Locks,
"RLIMIT_MEMLOCK": MemoryLocked,
"RLIMIT_MSGQUEUE": MessageQueueBytes,
"RLIMIT_NICE": Nice,
"RLIMIT_NOFILE": NumberOfFiles,
"RLIMIT_NPROC": ProcessCount,
"RLIMIT_RSS": Rss,
"RLIMIT_RTPRIO": RealTimePriority,
"RLIMIT_RTTIME": Rttime,
"RLIMIT_SIGPENDING": SignalsPending,
"RLIMIT_STACK": Stack,
}
// FromLinux maps linux rlimit values to sentry Limits, being careful to handle
// infinities.
func FromLinux(rl uint64) uint64 {
+2 -22
View File
@@ -24,28 +24,8 @@ import (
"gvisor.dev/gvisor/pkg/sync"
)
// Mapping from linux resource names to limits.LimitType.
var fromLinuxResource = map[string]limits.LimitType{
"RLIMIT_AS": limits.AS,
"RLIMIT_CORE": limits.Core,
"RLIMIT_CPU": limits.CPU,
"RLIMIT_DATA": limits.Data,
"RLIMIT_FSIZE": limits.FileSize,
"RLIMIT_LOCKS": limits.Locks,
"RLIMIT_MEMLOCK": limits.MemoryLocked,
"RLIMIT_MSGQUEUE": limits.MessageQueueBytes,
"RLIMIT_NICE": limits.Nice,
"RLIMIT_NOFILE": limits.NumberOfFiles,
"RLIMIT_NPROC": limits.ProcessCount,
"RLIMIT_RSS": limits.Rss,
"RLIMIT_RTPRIO": limits.RealTimePriority,
"RLIMIT_RTTIME": limits.Rttime,
"RLIMIT_SIGPENDING": limits.SignalsPending,
"RLIMIT_STACK": limits.Stack,
}
func findName(lt limits.LimitType) string {
for k, v := range fromLinuxResource {
for k, v := range limits.FromLinuxResourceName {
if v == lt {
return k
}
@@ -141,7 +121,7 @@ func createLimitSet(spec *specs.Spec) (*limits.LimitSet, error) {
// Then apply overwrites on top of defaults.
for _, rl := range spec.Process.Rlimits {
lt, ok := fromLinuxResource[rl.Type]
lt, ok := limits.FromLinuxResourceName[rl.Type]
if !ok {
return nil, fmt.Errorf("unknown resource %q", rl.Type)
}
+6 -1
View File
@@ -1362,10 +1362,15 @@ func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, user sp
if len(stdioFDs) != 3 {
return nil, nil, fmt.Errorf("stdioFDs should contain exactly 3 FDs (stdin, stdout, and stderr), but %d FDs received", len(stdioFDs))
}
fdMap := map[int]*fd.FD{
0: stdioFDs[0],
1: stdioFDs[1],
2: stdioFDs[2],
}
k := kernel.KernelFromContext(ctx)
fdTable := k.NewFDTable()
_, ttyFile, err := fdimport.Import(ctx, fdTable, console, auth.KUID(user.UID), auth.KGID(user.GID), stdioFDs)
_, ttyFile, err := fdimport.Import(ctx, fdTable, console, auth.KUID(user.UID), auth.KGID(user.GID), fdMap)
if err != nil {
fdTable.DecRef(ctx)
return nil, nil, err