From 2a86c35768b6d9179ed3c3e83df44a5cd42d433f Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 7 Jan 2025 19:15:33 -0800 Subject: [PATCH] libct: document initConfig and friends This is one of the dark corners of runc / libcontainer, so let's shed some light on it. initConfig is a structure which is filled in [mostly] by newInitConfig, and one of its hidden aspects is it contains a process config which is the result of merge between the container and the process configs. Let's document how all this happens, where the fields are coming from, which one has a preference, and how it all works. Signed-off-by: Kir Kolyshkin --- libcontainer/container_linux.go | 9 ++++ libcontainer/init_linux.go | 74 +++++++++++++++++++++++---------- libcontainer/process.go | 43 ++++++++++++------- 3 files changed, 88 insertions(+), 38 deletions(-) diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index a411d4081..15090fe2e 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -689,6 +689,9 @@ func (c *Container) newSetnsProcess(p *Process, cmd *exec.Cmd, comm *processComm } func (c *Container) newInitConfig(process *Process) *initConfig { + // Set initial properties. For those properties that exist + // both in the container config and the process, use the ones + // from the container config first, and override them later. cfg := &initConfig{ Config: c.config, Args: process.Args, @@ -710,6 +713,9 @@ func (c *Container) newInitConfig(process *Process) *initConfig { ConsoleWidth: process.ConsoleWidth, ConsoleHeight: process.ConsoleHeight, } + + // Overwrite config properties with ones from process. + if process.NoNewPrivileges != nil { cfg.NoNewPrivileges = *process.NoNewPrivileges } @@ -722,6 +728,9 @@ func (c *Container) newInitConfig(process *Process) *initConfig { if len(process.Rlimits) > 0 { cfg.Rlimits = process.Rlimits } + + // Set misc properties. + if cgroups.IsCgroup2UnifiedMode() { cfg.Cgroup2Path = c.cgroupManager.Path("") } diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index cff21e1bc..b2cae3a84 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -47,30 +47,58 @@ type network struct { TempVethPeerName string `json:"temp_veth_peer_name"` } -// initConfig is used for transferring parameters from Exec() to Init() +// initConfig is used for transferring parameters from Exec() to Init(). +// It contains: +// - original container config; +// - some [Process] properties; +// - set of properties merged from the container config ([configs.Config]) +// and the process ([Process]); +// - some properties that come from the container. +// +// When adding new fields, please make sure they go into the relevant section. type initConfig struct { - Args []string `json:"args"` - Env []string `json:"env"` - Cwd string `json:"cwd"` - Capabilities *configs.Capabilities `json:"capabilities"` - ProcessLabel string `json:"process_label"` - AppArmorProfile string `json:"apparmor_profile"` - NoNewPrivileges bool `json:"no_new_privileges"` - UID int `json:"uid"` - GID int `json:"gid"` - AdditionalGroups []int `json:"additional_groups"` - Config *configs.Config `json:"config"` - Networks []*network `json:"network"` - PassedFilesCount int `json:"passed_files_count"` - ContainerID string `json:"containerid"` - Rlimits []configs.Rlimit `json:"rlimits"` - CreateConsole bool `json:"create_console"` - ConsoleWidth uint16 `json:"console_width"` - ConsoleHeight uint16 `json:"console_height"` - RootlessEUID bool `json:"rootless_euid,omitempty"` - RootlessCgroups bool `json:"rootless_cgroups,omitempty"` - SpecState *specs.State `json:"spec_state,omitempty"` - Cgroup2Path string `json:"cgroup2_path,omitempty"` + // Config is the original container config. + Config *configs.Config `json:"config"` + + // Properties that are unique to and come from [Process]. + + Args []string `json:"args"` + Env []string `json:"env"` + UID int `json:"uid"` + GID int `json:"gid"` + AdditionalGroups []int `json:"additional_groups"` + Cwd string `json:"cwd"` + CreateConsole bool `json:"create_console"` + ConsoleWidth uint16 `json:"console_width"` + ConsoleHeight uint16 `json:"console_height"` + PassedFilesCount int `json:"passed_files_count"` + + // Properties that exists both in the container config and the process, + // as merged by [Container.newInitConfig] (process properties has preference). + + AppArmorProfile string `json:"apparmor_profile"` + Capabilities *configs.Capabilities `json:"capabilities"` + NoNewPrivileges bool `json:"no_new_privileges"` + ProcessLabel string `json:"process_label"` + Rlimits []configs.Rlimit `json:"rlimits"` + + // Properties that only exist in container config. + // FIXME: they are also passed in Config above. + + RootlessEUID bool `json:"rootless_euid,omitempty"` + RootlessCgroups bool `json:"rootless_cgroups,omitempty"` + + // Miscellaneous properties, filled in by [Container.newInitConfig] + // unless documented otherwise. + + ContainerID string `json:"containerid"` + Cgroup2Path string `json:"cgroup2_path,omitempty"` + + // Networks is filled in from container config by [initProcess.createNetworkInterfaces]. + Networks []*network `json:"network"` + + // SpecState is filled in by [initProcess.Start]. + SpecState *specs.State `json:"spec_state,omitempty"` } // Init is part of "runc init" implementation. diff --git a/libcontainer/process.go b/libcontainer/process.go index 09162be9a..5b2928be4 100644 --- a/libcontainer/process.go +++ b/libcontainer/process.go @@ -17,8 +17,11 @@ type processOperations interface { pid() int } -// Process specifies the configuration and IO for a process inside -// a container. +// Process defines the configuration and IO for a process inside a container. +// +// Note that some Process properties are also present in container configuration +// ([configs.Config]). In all such cases, Process properties take precedence +// over container configuration ones. type Process struct { // The command to be run followed by any arguments. Args []string @@ -34,44 +37,54 @@ type Process struct { // in addition to those that the user belongs to. AdditionalGroups []int - // Cwd will change the processes current working directory inside the container's rootfs. + // Cwd will change the process's current working directory inside the container's rootfs. Cwd string - // Stdin is a pointer to a reader which provides the standard input stream. + // Stdin is a reader which provides the standard input stream. Stdin io.Reader - // Stdout is a pointer to a writer which receives the standard output stream. + // Stdout is a writer which receives the standard output stream. Stdout io.Writer - // Stderr is a pointer to a writer which receives the standard error stream. + // Stderr is a writer which receives the standard error stream. Stderr io.Writer - // ExtraFiles specifies additional open files to be inherited by the container + // ExtraFiles specifies additional open files to be inherited by the process. ExtraFiles []*os.File - // open handles to cloned binaries -- see dmz.CloneSelfExe for more details + // Open handles to cloned binaries -- see dmz.CloneSelfExe for more details. clonedExes []*os.File - // Initial sizings for the console + // Initial size for the console. ConsoleWidth uint16 ConsoleHeight uint16 - // Capabilities specify the capabilities to keep when executing the process inside the container - // All capabilities not specified will be dropped from the processes capability mask + // Capabilities specify the capabilities to keep when executing the process. + // All capabilities not specified will be dropped from the processes capability mask. + // + // If not nil, takes precedence over container's [configs.Config.Capabilities]. Capabilities *configs.Capabilities // AppArmorProfile specifies the profile to apply to the process and is - // changed at the time the process is execed + // changed at the time the process is executed. + // + // If not empty, takes precedence over container's [configs.Config.AppArmorProfile]. AppArmorProfile string - // Label specifies the label to apply to the process. It is commonly used by selinux + // Label specifies the label to apply to the process. It is commonly used by selinux. + // + // If not empty, takes precedence over container's [configs.Config.ProcessLabel]. Label string // NoNewPrivileges controls whether processes can gain additional privileges. + // + // If not nil, takes precedence over container's [configs.Config.NoNewPrivileges]. NoNewPrivileges *bool - // Rlimits specifies the resource limits, such as max open files, to set in the container - // If Rlimits are not set, the container will inherit rlimits from the parent process + // Rlimits specifies the resource limits, such as max open files, to set for the process. + // If unset, the process will inherit rlimits from the parent process. + // + // If not empty, takes precedence over container's [configs.Config.Rlimit]. Rlimits []configs.Rlimit // ConsoleSocket provides the masterfd console.