Files
Shailend ChandandgVisor bot 46e35291a0 Support writing to cgroup v2 control files without an open file descriptor
Fix a panic when writing to cgroup v2 control files from outside the sandbox
(e.g., via Cgroups.WriteControlFiles / WriteControl), where writes occur
without an open in-sandbox VFS file descriptor (fd == nil). cgroup.procs now
safely falls back to context credentials with unconfined namespace migration,
and checkNSDelegateWrite skips namespace delegation checks. Add an integration
test for runsc cgroup v2 control file writes.

Also:
- Export cgroup2fs.InitHierarchy to initialize sandbox-wide cgroup2 state
  and root controller enablement.
- Plumb CgroupNamespace through sentry lifecycle and task context to derive
  initial container cgroup2 placements.
PiperOrigin-RevId: 978087817
2026-09-08 13:36:37 -07:00

2478 lines
79 KiB
Go

// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package kernel provides an emulation of the Linux kernel.
//
// See README.md for a detailed overview.
//
// Lock order (outermost locks must be taken first):
//
// Kernel.extMu
// TTY.mu
// ThreadGroup.timerMu
// Locks acquired by ktime.Timer methods
// TaskSet.mu
// SignalHandlers.mu
// Task.mu
// FSContext.mu
// runningTasksMu
// Timekeeper.updateMu
//
// Locking SignalHandlers.mu in multiple SignalHandlers requires locking
// TaskSet.mu exclusively first. Locking Task.mu in multiple Tasks at the same
// time requires locking all of their signal mutexes first.
package kernel
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"time"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/devutil"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/eventchannel"
"gvisor.dev/gvisor/pkg/fdnotifier"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/checkpoint"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy/nvconf"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/nsfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/pipefs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/timerfd"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/hostcpu"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/kernel/futex"
"gvisor.dev/gvisor/pkg/sentry/kernel/ipc"
"gvisor.dev/gvisor/pkg/sentry/kernel/sched"
"gvisor.dev/gvisor/pkg/sentry/ktime"
"gvisor.dev/gvisor/pkg/sentry/limits"
"gvisor.dev/gvisor/pkg/sentry/loader"
"gvisor.dev/gvisor/pkg/sentry/mm"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/port"
"gvisor.dev/gvisor/pkg/sentry/state/stateio"
sentrytime "gvisor.dev/gvisor/pkg/sentry/time"
"gvisor.dev/gvisor/pkg/sentry/unimpl"
"gvisor.dev/gvisor/pkg/sentry/uniqueid"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/state"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/timing"
uspb "gvisor.dev/gvisor/pkg/sentry/unimpl/unimplemented_syscall_go_proto"
)
// UserCounters is a set of user counters.
//
// +stateify savable
type UserCounters struct {
uid auth.KUID
rlimitNProc atomicbitops.Uint64
}
// incRLimitNProc increments the rlimitNProc counter.
func (uc *UserCounters) incRLimitNProc(ctx context.Context) error {
lim := limits.FromContext(ctx).Get(limits.ProcessCount)
creds := auth.CredentialsFromContext(ctx)
nproc := uc.rlimitNProc.Add(1)
if nproc > lim.Cur && !creds.HasRootCapability(linux.CAP_SYS_ADMIN) && !creds.HasRootCapability(linux.CAP_SYS_RESOURCE) {
uc.rlimitNProc.Add(^uint64(0))
return linuxerr.EAGAIN
}
return nil
}
// decRLimitNProc decrements the rlimitNProc counter.
func (uc *UserCounters) decRLimitNProc() {
uc.rlimitNProc.Add(^uint64(0))
}
// CgroupMount contains the cgroup mount. These mounts are created for the root
// container by default and are stored in the kernel.
//
// +stateify savable
type CgroupMount struct {
Fs *vfs.Filesystem
Root *vfs.Dentry
Mount *vfs.Mount
}
// SaveRestoreExecConfig contains the configuration for the save/restore binary.
//
// +stateify savable
type SaveRestoreExecConfig struct {
// Argv is the argv to the save/restore binary. The binary path is expected to
// be argv[0]. The specified binary is executed with an environment variable
// (GVISOR_SAVE_RESTORE_AUTO_EXEC_MODE) set to "save" before the kernel is
// saved, "restore" after the kernel is restored and restarted, and "resume"
// after the kernel is saved and resumed.
Argv []string
// Timeout is the timeout for the save/restore binary. If the binary fails to
// exit within this timeout the save/restore operation will fail.
Timeout time.Duration
// LeaderTask is the task in the kernel that the save/restore binary will run
// under.
LeaderTask *Task
}
// Kernel represents an emulated Linux kernel. It must be initialized by calling
// Init() or LoadFrom().
//
// +stateify savable
type Kernel struct {
// extMu serializes external changes to the Kernel with calls to
// Kernel.SaveTo. (Kernel.SaveTo requires that the state of the Kernel
// remains frozen for the duration of the call; it requires that the Kernel
// is paused as a precondition, which ensures that none of the tasks
// running within the Kernel can affect its state, but extMu is required to
// ensure that concurrent users of the Kernel *outside* the Kernel's
// control cannot affect its state by calling e.g.
// Kernel.SendExternalSignal.)
extMu sync.Mutex `state:"nosave"`
// started is true if Start has been called. Unless otherwise specified,
// all Kernel fields become immutable once started becomes true.
started bool `state:"nosave"`
// All of the following fields are immutable unless otherwise specified.
// Platform is the platform that is used to execute tasks in the created
// Kernel.
platform.Platform `state:"nosave"`
// mf provides application memory.
mf *pgalloc.MemoryFile `state:"nosave"`
// See InitKernelArgs for the meaning of these fields.
featureSet cpuid.FeatureSet
timekeeper *Timekeeper
tasks *TaskSet
rootUserNamespace *auth.UserNamespace
rootNetworkNamespace *inet.Namespace
applicationCores uint
useHostCores bool
extraAuxv []arch.AuxEntry
vdso *loader.VDSO
vdsoParams *VDSOParamPage
rootUTSNamespace *UTSNamespace
rootIPCNamespace *IPCNamespace
rootCgroupNamespace *CgroupNamespace
// futexes is the "root" futex.Manager, from which all others are forked.
// This is necessary to ensure that shared futexes are coherent across all
// tasks, including those created by CreateProcess.
futexes *futex.Manager
// globalInit is the thread group whose leader has ID 1 in the root PID
// namespace. globalInit is stored separately so that it is accessible even
// after all tasks in the thread group have exited, such that ID 1 is no
// longer mapped.
//
// globalInit is mutable until it is assigned by the first successful call
// to CreateProcess, and is protected by extMu.
globalInit *ThreadGroup
// syslog is the kernel log.
syslog syslog
runningTasksMu runningTasksMutex `state:"nosave"`
// runningTasks is the total count of tasks currently in
// TaskGoroutineRunningSys or TaskGoroutineRunningApp. i.e., they are
// not blocked or stopped.
//
// runningTasks must be accessed atomically. Increments from 0 to 1 are
// further protected by runningTasksMu (see incRunningTasks).
runningTasks atomicbitops.Int64
// blockedTasks is the total count of tasks currently in
// TaskGoroutineBlockedUninterruptible, i.e. uninterruptible sleep. It is
// used to implement procs_blocked in /proc/stat.
//
// blockedTasks must be accessed atomically. It is not saved; on restore it
// is repopulated as tasks re-enter uninterruptible sleep.
blockedTasks atomicbitops.Int64 `state:"nosave"`
// runningTasksCond is signaled when runningTasks is incremented from 0 to 1.
//
// Invariant: runningTasksCond.L == &runningTasksMu.
runningTasksCond sync.Cond `state:"nosave"`
// taskActivityCh is closed (and replaced with a fresh channel) when
// runningTasks is incremented from 0 to 1, to wake goroutines that park
// while the kernel is idle (currently the watchdog; see
// Kernel.WaitForTaskActivity()). Unlike runningTasksCond, which only matters
// when the CPU clock ticker has actually parked, this fires on every real
// 0->1 transition, since a waiter may have parked during the slack tick in
// which runningTasks is 0 but cpuClockTickerRunning is still true.
//
// activeNotifyCh is protected by runningTasksMu.
taskActivityCh chan struct{} `state:"nosave"`
// cpuClockTickTimer drives increments of cpuClock.
cpuClockTickTimer *time.Timer `state:"nosave"`
// cpuClockTickerRunning is true if the goroutine that increments cpuClock is
// running, and false if it is blocked in runningTasksCond.Wait() or if it
// never started.
//
// cpuClockTickerRunning is protected by runningTasksMu.
cpuClockTickerRunning bool
// cpuClockTickerWakeCh is sent to to wake the goroutine that increments
// cpuClock if it's sleeping between ticks.
cpuClockTickerWakeCh chan struct{} `state:"nosave"`
// cpuClockTickerStopCond is broadcast when cpuClockTickerRunning transitions
// from true to false.
//
// Invariant: cpuClockTickerStopCond.L == &runningTasksMu.
cpuClockTickerStopCond sync.Cond `state:"nosave"`
// cpuClock is a coarse monotonic clock that is advanced by the CPU clock
// ticker and thus approximates wall time when tasks are running (but is
// strictly slower due to CPU clock ticker goroutine wakeup latency). This
// does not use ktime.SyntheticClock since this clock currently does not
// need to support timers.
cpuClock atomicbitops.Int64
// userCPUClock and userSysCPUClock are kernel-wide cumulative CPU time
// accumulators, in nanoseconds, advanced by the CPU clock ticker as it
// accounts ticks to running tasks. userCPUClock mirrors the sum of all
// tasks' Task.appCPUClock (i.e. application/user time), while
// userSysCPUClock mirrors the sum of all tasks' Task.appSysCPUClock (i.e.
// application+sentry time). System time is the difference of the two. Like
// the per-task clocks, these are never decremented, so they also include
// the CPU time of exited tasks. They are used to implement the aggregate
// CPU line in /proc/stat (see Kernel.CPUStats).
//
// userCPUClock and userSysCPUClock must be accessed atomically.
userCPUClock atomicbitops.Int64
userSysCPUClock atomicbitops.Int64
// uniqueID is used to generate unique identifiers.
//
// uniqueID is mutable, and is accessed using atomic memory operations.
uniqueID atomicbitops.Uint64
// nextInotifyCookie is a monotonically increasing counter used for
// generating unique inotify event cookies.
//
// nextInotifyCookie is mutable.
nextInotifyCookie atomicbitops.Uint32
// netlinkPorts manages allocation of netlink socket port IDs.
netlinkPorts *port.Manager
// saveStatus is nil if the sandbox has not been saved, errSaved or
// errAutoSaved if it has been saved successfully, or the error causing the
// sandbox to exit during save.
// It is protected by extMu.
saveStatus error `state:"nosave"`
// danglingEndpoints is used to save / restore tcpip.DanglingEndpoints.
danglingEndpoints struct{} `state:".([]tcpip.Endpoint)"`
// sockets records all network sockets in the system. Protected by extMu.
sockets map[*vfs.FileDescription]*SocketRecord
// nextSocketRecord is the next entry number to use in sockets. Protected
// by extMu.
nextSocketRecord uint64
// unimplementedSyscallEmitterOnce is used in the initialization of
// unimplementedSyscallEmitter.
unimplementedSyscallEmitterOnce sync.Once `state:"nosave"`
// unimplementedSyscallEmitter is used to emit unimplemented syscall
// events. This is initialized lazily on the first unimplemented
// syscall.
unimplementedSyscallEmitter eventchannel.Emitter `state:"nosave"`
// SpecialOpts contains special kernel options.
SpecialOpts
// vfs keeps the filesystem state used across the kernel.
vfs vfs.VirtualFilesystem
// hostMount is the Mount used for file descriptors that were imported
// from the host.
hostMount *vfs.Mount
// pipeMount is the Mount used for pipes created by the pipe() and pipe2()
// syscalls (as opposed to named pipes created by mknod()).
pipeMount *vfs.Mount
// nsfsMount is the Mount used for namespaces.
nsfsMount *vfs.Mount
// shmMount is the Mount used for anonymous files created by the
// memfd_create() syscalls. It is analogous to Linux's shm_mnt.
shmMount *vfs.Mount
// socketMount is the Mount used for sockets created by the socket() and
// socketpair() syscalls. There are several cases where a socket dentry will
// not be contained in socketMount:
// 1. Socket files created by mknod()
// 2. Socket fds imported from the host (Kernel.hostMount is used for these)
// 3. Socket files created by binding Unix sockets to a file path
socketMount *vfs.Mount
// sysVShmDevID is the device number used by SysV shm segments. In Linux,
// SysV shm uses shmem_file_setup() and thus uses shm_mnt's device number.
// In gVisor, the shm implementation does not use shmMount, extracting
// shmMount's device number is inconvenient, applications accept a
// different device number in practice, and using a distinct device number
// avoids the possibility of inode number collisions due to the hack
// described in shm.Shm.InodeID().
sysVShmDevID uint32
// If set to true, report address space activation waits as if the task is in
// external wait so that the watchdog doesn't report the task stuck.
SleepForAddressSpaceActivation bool
// Exceptions to YAMA ptrace restrictions. Each key-value pair represents a
// tracee-tracer relationship. The key is a process (technically, the thread
// group leader) that can be traced by any thread that is a descendant of the
// value. If the value is nil, then anyone can trace the process represented by
// the key.
//
// ptraceExceptions is protected by the TaskSet mutex.
ptraceExceptions map[*Task]*Task
// YAMAPtraceScope is the current level of YAMA ptrace restrictions.
YAMAPtraceScope atomicbitops.Int32
// cgroupRegistry contains the set of active cgroup controllers on the
// system. It is controller by cgroupfs. Nil if cgroupfs is unavailable on
// the system.
cgroupRegistry *CgroupRegistry
// cgroupMountsMap maps the cgroup controller names to the cgroup mounts
// created for the root container. These mounts are then bind mounted
// for other application containers by creating their own container
// directories.
cgroupMountsMap map[string]*CgroupMount
cgroupMountsMapMu cgroupMountsMutex `state:"nosave"`
// userCountersMap maps auth.KUID into a set of user counters.
userCountersMap map[auth.KUID]*UserCounters
userCountersMapMu userCountersMutex `state:"nosave"`
// MaxFDLimit specifies the maximum file descriptor number that can be
// used by processes.
MaxFDLimit atomicbitops.Int32
// devGofers maps containers (using its name) to its device gofer client.
devGofers map[string]*devutil.GoferClient `state:"nosave"`
devGofersMu sync.Mutex `state:"nosave"`
// containerNames store the container name based on their container ID.
// Names are preserved between save/restore session, while IDs can change.
//
// Mapping: cid -> name.
// It's protected by extMu.
containerNames map[string]string
// checkpointMu is used to protect the checkpointing related fields below.
checkpointMu sync.Mutex `state:"nosave"`
// additionalCheckpointState stores additional state that needs
// to be checkpointed. It's protected by checkpointMu.
additionalCheckpointState map[any]any
// saver implements the Saver interface, which (as of writing) supports
// asynchronous checkpointing. It's protected by checkpointMu.
saver Saver `state:"nosave"`
// CheckpointWait is used to wait for a checkpoint to complete.
CheckpointWait CheckpointWaitable
// checkpointGen aims to track the number of times the kernel has been
// successfully checkpointed. Callers of checkpoint must notify the kernel
// when checkpoint/restore are done. It's protected by checkpointMu.
checkpointGen CheckpointGeneration
// SaveRestoreExecConfig stores configuration options for the save/restore
// exec binary.
SaveRestoreExecConfig *SaveRestoreExecConfig
// NvidiaDriverVersion is the NVIDIA driver version configured for this
// sandbox.
NvidiaDriverVersion nvconf.DriverVersion
// AllowSUID determines if the SUID/SGID bits are honored during execve.
AllowSUID bool
// IOUringEnabled determines if io_uring is enabled.
IOUringEnabled bool
// MaxKeySetSize is the maximum number of keys in a key set.
MaxKeySetSize atomicbitops.Int32
// fsSaveWaiters holds waiters for Kernel.WaitForFSSave. fsSaveWaiters is
// protected by fsSaveMu.
fsSaveMu fsSaveMutex `state:"nosave"`
fsSaveWaiters []chan error `state:"nosave"`
// HostNamePoller is notified when the system hostname changes in *any*
// UTS namespace.
HostNamePoller vfs.DynamicBytesPoller
// DomainNamePoller is notified when the system domainname changes in *any*
// UTS namespace.
DomainNamePoller vfs.DynamicBytesPoller
}
// InitKernelArgs holds arguments to Init.
type InitKernelArgs struct {
// FeatureSet is the emulated CPU feature set.
FeatureSet cpuid.FeatureSet
// Timekeeper manages time for all tasks in the system.
Timekeeper *Timekeeper
// RootUserNamespace is the root user namespace.
RootUserNamespace *auth.UserNamespace
// RootNetworkNamespace is the root network namespace. If nil, no networking
// will be available.
RootNetworkNamespace *inet.Namespace
// ApplicationCores is the number of logical CPUs visible to sandboxed
// applications. The set of logical CPU IDs is [0, ApplicationCores); thus
// ApplicationCores is analogous to Linux's nr_cpu_ids, the index of the
// most significant bit in cpu_possible_mask + 1.
ApplicationCores uint
// If UseHostCores is true, Task.CPU() returns the task goroutine's CPU
// instead of a virtualized CPU number, and Task.CopyToCPUMask() is a
// no-op. If ApplicationCores is less than hostcpu.MaxPossibleCPU(), it
// will be overridden.
UseHostCores bool
// ExtraAuxv contains additional auxiliary vector entries that are added to
// each process by the ELF loader.
ExtraAuxv []arch.AuxEntry
// Vdso holds the VDSO and its parameter page.
Vdso *loader.VDSO
// VdsoParams is the VDSO parameter page manager.
VdsoParams *VDSOParamPage
// RootUTSNamespace is the root UTS namespace.
RootUTSNamespace *UTSNamespace
// RootIPCNamespace is the root IPC namespace.
RootIPCNamespace *IPCNamespace
// RootPIDNamespace is the root PID namespace.
RootPIDNamespace *PIDNamespace
// MaxFDLimit specifies the maximum file descriptor number that can be
// used by processes. If it is zero, the limit will be set to
// unlimited.
MaxFDLimit int32
// Cgroup2FSInit initializes the cgroup2fs filesystem singleton.
Cgroup2FSInit func(ctx context.Context, k *Kernel, vfsObj *vfs.VirtualFilesystem) (*vfs.Filesystem, error)
}
// Init initialize the Kernel with no tasks.
//
// Callers must manually set Kernel.Platform and call Kernel.SetMemoryFile
// before calling Init.
func (k *Kernel) Init(args InitKernelArgs) error {
if args.Timekeeper == nil {
return fmt.Errorf("args.Timekeeper is nil")
}
if args.Timekeeper.clocks == nil {
return fmt.Errorf("must call Timekeeper.SetClocks() before Kernel.Init()")
}
if args.RootUserNamespace == nil {
return fmt.Errorf("args.RootUserNamespace is nil")
}
if args.ApplicationCores == 0 {
return fmt.Errorf("args.ApplicationCores is 0")
}
k.featureSet = args.FeatureSet
k.timekeeper = args.Timekeeper
k.tasks = newTaskSet(args.RootPIDNamespace)
k.rootUserNamespace = args.RootUserNamespace
k.rootUTSNamespace = args.RootUTSNamespace
k.rootIPCNamespace = args.RootIPCNamespace
k.rootNetworkNamespace = args.RootNetworkNamespace
if k.rootNetworkNamespace == nil {
k.rootNetworkNamespace = inet.NewRootNamespace(nil, nil, args.RootUserNamespace)
}
k.runningTasksCond.L = &k.runningTasksMu
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
k.cpuClockTickerStopCond.L = &k.runningTasksMu
k.taskActivityCh = make(chan struct{})
k.applicationCores = args.ApplicationCores
if args.UseHostCores && k.HasCPUNumbers() {
args.UseHostCores = false
log.Infof("UseHostCores enabled but the platform implements HasCPUNumbers(): setting UseHostCores to false")
}
if args.UseHostCores {
k.useHostCores = true
maxCPU, err := hostcpu.MaxPossibleCPU()
if err != nil {
return fmt.Errorf("failed to get maximum CPU number: %v", err)
}
minAppCores := uint(maxCPU) + 1
if k.applicationCores < minAppCores {
log.Infof("UseHostCores enabled: increasing ApplicationCores from %d to %d", k.applicationCores, minAppCores)
k.applicationCores = minAppCores
}
}
if k.HasCPUNumbers() {
numCPUs := uint(k.NumCPUs())
if k.applicationCores < numCPUs {
log.Infof("ApplicationCores is less than NumCPUs: %d < %d", k.applicationCores, numCPUs)
log.Infof("Setting applicationCores to NumCPUs: %d", numCPUs)
k.applicationCores = numCPUs
}
}
k.extraAuxv = args.ExtraAuxv
k.vdso = args.Vdso
k.vdsoParams = args.VdsoParams
k.futexes = futex.NewManager()
k.netlinkPorts = port.New()
k.ptraceExceptions = make(map[*Task]*Task)
k.YAMAPtraceScope = atomicbitops.FromInt32(linux.YAMA_SCOPE_RELATIONAL)
k.userCountersMap = make(map[auth.KUID]*UserCounters)
if args.MaxFDLimit == 0 {
args.MaxFDLimit = MaxFdLimit
}
k.MaxFDLimit.Store(args.MaxFDLimit)
k.containerNames = make(map[string]string)
k.CheckpointWait.k = k
ctx := k.SupervisorContext()
if err := k.vfs.Init(ctx); err != nil {
return fmt.Errorf("failed to initialize VFS: %v", err)
}
err := k.rootIPCNamespace.InitPosixQueues(ctx, &k.vfs, auth.CredentialsFromContext(ctx))
if err != nil {
return fmt.Errorf("failed to create mqfs filesystem: %v", err)
}
pipeFilesystem, err := pipefs.NewFilesystem(&k.vfs)
if err != nil {
return fmt.Errorf("failed to create pipefs filesystem: %v", err)
}
defer pipeFilesystem.DecRef(ctx)
pipeMount := k.vfs.NewDisconnectedMount(pipeFilesystem, nil, &vfs.MountOptions{})
k.pipeMount = pipeMount
nsfsFilesystem, err := nsfs.NewFilesystem(&k.vfs)
if err != nil {
return fmt.Errorf("failed to create nsfs filesystem: %v", err)
}
defer nsfsFilesystem.DecRef(ctx)
k.nsfsMount = k.vfs.NewDisconnectedMount(nsfsFilesystem, nil, &vfs.MountOptions{})
k.rootUserNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootUserNamespace))
k.rootNetworkNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootNetworkNamespace))
k.rootIPCNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootIPCNamespace))
k.rootUTSNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootUTSNamespace))
args.RootPIDNamespace.InitInode(ctx, k)
tmpfsOpts := vfs.GetFilesystemOptions{
InternalData: tmpfs.FilesystemOpts{
// See mm/shmem.c:shmem_init() => vfs_kern_mount(flags=SB_KERNMOUNT).
// Note how mm/shmem.c:shmem_fill_super() does not provide a default
// value for sbinfo->max_blocks when SB_KERNMOUNT is set.
DisableDefaultSizeLimit: true,
},
InternalMount: true,
}
tmpfsFilesystem, tmpfsRoot, err := tmpfs.FilesystemType{}.GetFilesystem(ctx, &k.vfs, auth.NewRootCredentials(k.rootUserNamespace), "", tmpfsOpts)
if err != nil {
return fmt.Errorf("failed to create tmpfs filesystem: %v", err)
}
defer tmpfsFilesystem.DecRef(ctx)
defer tmpfsRoot.DecRef(ctx)
k.shmMount = k.vfs.NewDisconnectedMount(tmpfsFilesystem, tmpfsRoot, &vfs.MountOptions{})
socketFilesystem, err := sockfs.NewFilesystem(&k.vfs)
if err != nil {
return fmt.Errorf("failed to create sockfs filesystem: %v", err)
}
defer socketFilesystem.DecRef(ctx)
k.socketMount = k.vfs.NewDisconnectedMount(socketFilesystem, nil, &vfs.MountOptions{})
sysVShmDevMinor, err := k.vfs.GetAnonBlockDevMinor()
if err != nil {
return fmt.Errorf("failed to get device number for SysV shm: %v", err)
}
k.sysVShmDevID = linux.MakeDeviceID(linux.UNNAMED_MAJOR, sysVShmDevMinor)
k.sockets = make(map[*vfs.FileDescription]*SocketRecord)
k.cgroupRegistry = newCgroupRegistry()
if args.Cgroup2FSInit == nil {
return fmt.Errorf("cgroup2fs initializer is required")
}
cfs, err := args.Cgroup2FSInit(ctx, k, &k.vfs)
if err != nil {
return fmt.Errorf("failed to initialize cgroup2fs: %v", err)
}
k.cgroupRegistry.v2fs = cfs
// The root cgroup namespace is rooted at the root cgroup of the cgroup2
// filesystem singleton. It must be created after the cgroup2fs singleton,
// and after the nsfs mount.
k.rootCgroupNamespace = newCgroupNamespace(k.Cgroup2FS().RootCgroup(), k.rootUserNamespace)
k.rootCgroupNamespace.SetInode(nsfs.NewInode(ctx, k.nsfsMount, k.rootCgroupNamespace))
k.MaxKeySetSize = atomicbitops.FromInt32(auth.MaxSetSize)
return nil
}
func (k *Kernel) quiescePausedAnd(ctx context.Context, f func() error) error {
// Do not allow other Kernel methods to affect it while it's being saved.
k.extMu.Lock()
defer k.extMu.Unlock()
// Suspend fdnotifier notifications.
fdnotifier.Pause()
defer fdnotifier.Resume()
// Stop time.
k.pauseTimeLocked(ctx)
defer k.resumeTimeLocked(ctx)
// Evict all evictable MemoryFile allocations.
k.mf.StartEvictions()
k.mf.WaitForEvictions()
return f()
}
// +stateify savable
type privateMemoryFileMetadata struct {
owners []checkpoint.ResourceID
}
func savePrivateMFs(ctx context.Context, w io.Writer, mfsToSave map[checkpoint.ResourceID]*pgalloc.MemoryFile, mfOpts *pgalloc.SaveOpts) error {
var meta privateMemoryFileMetadata
// Generate the order in which private memory files are saved.
for fsID := range mfsToSave {
meta.owners = append(meta.owners, fsID)
}
// Save the metadata.
if _, err := state.Save(ctx, w, &meta); err != nil {
return err
}
// Followed by the private memory files in order.
for _, fsID := range meta.owners {
if err := mfsToSave[fsID].SaveTo(ctx, w, mfOpts); err != nil {
return err
}
}
return nil
}
// SaveTo saves the state of k to stateFile. It takes ownership of stateFile,
// pagesMetadata, and pagesFile, even if it returns a non-nil error.
//
// Preconditions: The kernel must be paused throughout the call to SaveTo.
func (k *Kernel) SaveTo(ctx context.Context, stateFile, pagesMetadata io.WriteCloser, pagesFile stateio.AsyncWriter, appMFExcludeCommittedZeroPages, resume bool) error {
if hostarch.PageSize != 4096 {
return fmt.Errorf("save is not supported with %dK page size", hostarch.PageSize/1024)
}
saveStart := time.Now()
stateFileCleanup := cleanup.Make(func() { stateFile.Close() })
defer stateFileCleanup.Clean()
parallelMFSave := pagesMetadata != nil
var pagesCleanup cleanup.Cleanup
if parallelMFSave {
pagesCleanup.Add(func() {
pagesMetadata.Close()
pagesFile.Close()
})
}
defer pagesCleanup.Clean()
return k.quiescePausedAnd(ctx, func() error {
// Discard unsavable mappings, such as those for host file descriptors.
if err := k.invalidateUnsavableMappings(ctx); err != nil {
return fmt.Errorf("failed to invalidate unsavable mappings: %v", err)
}
// Capture all private memory files.
mfsToSave := make(map[checkpoint.ResourceID]*pgalloc.MemoryFile)
vfsCtx := context.WithValue(ctx, pgalloc.CtxMemoryFileMap, mfsToSave)
// Prepare filesystems for saving. This must be done after
// invalidateUnsavableMappings(), since dropping memory mappings may
// affect filesystem state (e.g. page cache reference counts).
if err := k.vfs.PrepareSave(vfsCtx); err != nil {
return err
}
// Mark all to-be-saved MemoryFiles as savable to inform kernel save below.
k.mf.MarkSavable()
for _, mf := range mfsToSave {
mf.MarkSavable()
}
var (
mfSaveWg sync.WaitGroup
mfSaveErr error
)
if parallelMFSave {
// Parallelize MemoryFile save and kernel save. Both are independent.
mfSaveWg.Add(1)
go func() {
defer mfSaveWg.Done()
mfSaveErr = k.saveMemoryFiles(ctx, nil, pagesMetadata, pagesFile, mfsToSave, appMFExcludeCommittedZeroPages) // transfers ownership
}()
pagesCleanup.Release()
// Defer a Wait() so we wait for k.saveMemoryFiles() to complete even if we
// error out without reaching the other Wait() below.
defer mfSaveWg.Wait()
}
// Save the CPUID FeatureSet before the rest of the kernel so we can
// verify its compatibility on restore before attempting to restore the
// entire kernel, which may fail on an incompatible machine.
//
// N.B. This will also be saved along with the full kernel save below.
cpuidStart := time.Now()
if _, err := state.Save(ctx, stateFile, &k.featureSet); err != nil {
return err
}
log.Infof("CPUID save took [%s].", time.Since(cpuidStart))
// Save the timekeeper's state.
if rootNS := k.rootNetworkNamespace; rootNS != nil && rootNS.Stack() != nil {
// Pause the network stack.
netstackPauseStart := time.Now()
// Stack.removeConf should be true when resume=false and vice versa.
k.rootNetworkNamespace.Stack().SetRemoveConf(!resume)
log.Infof("Pausing root network namespace")
k.rootNetworkNamespace.Stack().Pause()
defer k.rootNetworkNamespace.Stack().Resume()
log.Infof("Pausing root network namespace took [%s].", time.Since(netstackPauseStart))
}
// Save the kernel state.
kernelStart := time.Now()
stats, err := state.Save(ctx, stateFile, k)
if err != nil {
return err
}
log.Infof("Kernel save stats: %s", stats.String())
log.Infof("Kernel save took [%s].", time.Since(kernelStart))
if parallelMFSave {
// Close stateFile while MemoryFile saving is in progress to overlap
// their latencies.
err := stateFile.Close()
stateFileCleanup.Release()
if err != nil {
return fmt.Errorf("closing state file failed: %w", err)
}
mfSaveWg.Wait()
if mfSaveErr != nil {
return mfSaveErr
}
} else {
mfSaveErr = k.saveMemoryFiles(ctx, stateFile, nil, nil, mfsToSave, appMFExcludeCommittedZeroPages)
if mfSaveErr != nil {
return mfSaveErr
}
// Can't close stateFile until k.saveMemoryFiles() finishes writing to
// it.
err := stateFile.Close()
stateFileCleanup.Release()
if err != nil {
return fmt.Errorf("closing state file failed: %w", err)
}
}
log.Infof("Overall save took [%s].", time.Since(saveStart))
return nil
})
}
// BeforeResume is called before the kernel is resumed after save.
func (k *Kernel) BeforeResume(ctx context.Context) {
k.vfs.BeforeResume(ctx)
}
// saveMemoryFiles saves both the application memory file and all MemoryFiles
// in mfsToSave. If w is non-nil, then pagesMetadata and pagesFile must be nil,
// and MemoryFile state will be saved to w. Otherwise, pagesMetadata and
// pagesFile must be non-nil, saveMemoryFiles takes ownership of both
// pagesMetadata and pagesFile (even if it returns a non-nil error), and
// MemoryFile state will be saved to pagesMetadata and pagesFile.
func (k *Kernel) saveMemoryFiles(ctx context.Context, w io.Writer, pagesMetadata io.WriteCloser, pagesFile stateio.AsyncWriter, mfsToSave map[checkpoint.ResourceID]*pgalloc.MemoryFile, appMFExcludeCommittedZeroPages bool) error {
memoryStart := time.Now()
pmw := w
var pmwCleanup cleanup.Cleanup
if pagesMetadata != nil {
pmw = pagesMetadata
pmwCleanup.Add(func() { pagesMetadata.Close() })
}
defer pmwCleanup.Clean()
mfOpts := pgalloc.SaveOpts{
ExcludeCommittedZeroPages: appMFExcludeCommittedZeroPages,
}
var (
asyncPageSaveWg sync.WaitGroup
asyncPageSaveErr error
asyncPageSaveCleanup cleanup.Cleanup
)
defer asyncPageSaveCleanup.Clean()
if pagesFile != nil {
asyncPageSaveWg.Add(1)
apfs, err := pgalloc.StartAsyncPagesFileSave(pagesFile, func(err error) {
defer asyncPageSaveWg.Done()
asyncPageSaveErr = err
}) // transfers ownership
if err != nil {
return fmt.Errorf("failed to start async pages file saving: %w", err)
}
asyncPageSaveCleanup.Add(func() {
apfs.MemoryFilesDone()
asyncPageSaveWg.Wait()
})
mfOpts.PagesFile = apfs
}
if err := k.mf.SaveTo(ctx, pmw, &mfOpts); err != nil {
return err
}
// appMFExcludeCommittedZeroPages is expected to reflect application memory
// usage behavior, but not necessarily usage of private MemoryFiles.
mfOpts.ExcludeCommittedZeroPages = false
if err := savePrivateMFs(ctx, pmw, mfsToSave, &mfOpts); err != nil {
return err
}
if pagesMetadata != nil {
// Close pagesMetadata while async MemoryFile saving is in progress to
// overlap their latencies.
err := pagesMetadata.Close()
pmwCleanup.Release()
if err != nil {
return fmt.Errorf("failed to close pages metadata file: %w", err)
}
}
// Wait for page saving to complete and report errors.
asyncPageSaveCleanup.Release()()
if asyncPageSaveErr != nil {
return fmt.Errorf("failed to save MemoryFile pages: %w", asyncPageSaveErr)
}
log.Infof("Memory files save took [%s].", time.Since(memoryStart))
return nil
}
// Preconditions: The kernel must be paused.
func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error {
invalidated := make(map[*mm.MemoryManager]struct{})
k.tasks.mu.RLock()
defer k.tasks.mu.RUnlock()
for t := range k.tasks.Root.tids {
// We can skip locking Task.mu here since the kernel is paused.
if memMgr := t.image.MemoryManager; memMgr != nil {
if _, ok := invalidated[memMgr]; !ok {
if err := memMgr.InvalidateUnsavable(ctx); err != nil {
return err
}
invalidated[memMgr] = struct{}{}
}
}
// I really wish we just had a sync.Map of all MMs...
if r, ok := t.runState.(*runExecveAfterSiblingExitStop); ok {
if err := r.image.MemoryManager.InvalidateUnsavable(ctx); err != nil {
return err
}
}
}
return nil
}
// LoadFrom returns a new Kernel loaded from args.
func (k *Kernel) LoadFrom(ctx context.Context, r io.Reader, asyncMFLoader *AsyncMFLoader, timeReady chan struct{}, networkArgs inet.NetworkArgs, clocks sentrytime.Clocks, vfsOpts *vfs.CompleteRestoreOptions, timeline *timing.Timeline) error {
defer timeline.End()
if hostarch.PageSize != 4096 {
return fmt.Errorf("restore is not supported with %dK page size", hostarch.PageSize/1024)
}
loadStart := time.Now()
k.runningTasksCond.L = &k.runningTasksMu
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
k.cpuClockTickerStopCond.L = &k.runningTasksMu
k.taskActivityCh = make(chan struct{})
initAppCores := k.applicationCores
// Load the pre-saved CPUID FeatureSet.
//
// N.B. This was also saved along with the full kernel below, so we
// don't need to explicitly install it in the Kernel.
cpuidStart := time.Now()
if _, err := state.Load(ctx, r, &k.featureSet); err != nil {
return err
}
log.Infof("CPUID load took [%s].", time.Since(cpuidStart))
timeline.Reached("CPUID loaded")
// Verify that the FeatureSet is usable on this host. We do this before
// Kernel load so that the explicit CPUID mismatch error has priority
// over floating point state restore errors that may occur on load on
// an incompatible machine.
if err := k.featureSet.CheckHostCompatible(); err != nil {
return err
}
timeline.Reached("CPUID checked")
// Suspend fdnotifier notifications.
fdnotifier.Pause()
defer fdnotifier.Resume()
// Load the kernel state.
kernelStart := time.Now()
stats, err := state.Load(ctx, r, k)
if err != nil {
return err
}
log.Infof("Kernel load stats: %s", stats.String())
log.Infof("Kernel load took [%s].", time.Since(kernelStart))
timeline.Reached("Kernel loaded")
if asyncMFLoader == nil {
mfStart := time.Now()
if err := k.loadMemoryFiles(ctx, r); err != nil {
return fmt.Errorf("failed to load memory files: %w", err)
}
log.Infof("Memory files load took [%s].", time.Since(mfStart))
timeline.Reached("Memory files loaded")
} else {
// Timekeeper restore below tries writing to the main MF. If async page
// loading is being used, we need to make sure that the main MF loading has
// started before we try to write to it.
if err := asyncMFLoader.WaitMainMFStart(); err != nil {
return fmt.Errorf("main MF start failed: %w", err)
}
timeline.Reached("Main MemoryFile loading started")
}
k.Timekeeper().SetClocks(clocks, k.vdsoParams)
if timeReady != nil {
close(timeReady)
}
if s := k.rootNetworkNamespace.Stack(); s != nil {
if networkArgs == nil {
return fmt.Errorf("network configuration cannot be nil during restore")
}
log.Infof("Reconfiguring network for restore")
s.ResetConfig()
if err := networkArgs.ConfigureNetwork(s); err != nil {
return fmt.Errorf("configuring network: %w", err)
}
s.Restore()
timeline.Reached("Network stack restored")
}
if err := k.vfs.CompleteRestore(ctx, vfsOpts); err != nil {
return vfs.PrependErrMsg("vfs.CompleteRestore() failed", err)
}
timeline.Reached("VFS restored")
log.Infof("Overall load took [%s] after async work", time.Since(loadStart))
// Applications may size per-cpu structures based on k.applicationCores, so
// it can't change across save/restore. When we are virtualizing CPU
// numbers, this isn't a problem. However, when we are exposing host CPU
// assignments, we can't tolerate an increase in the number of host CPUs,
// which could result in getcpu(2) returning CPUs that applications expect
// not to exist.
if k.useHostCores && initAppCores > k.applicationCores {
return fmt.Errorf("UseHostCores enabled: can't increase ApplicationCores from %d to %d after restore", k.applicationCores, initAppCores)
}
return nil
}
// ExtractRootfsUpperLayer partially restores the kernel state and extracts the
// rootfs upper layer to the provided file.
func (k *Kernel) ExtractRootfsUpperLayer(ctx context.Context, r io.Reader, asyncMFLoader *AsyncMFLoader, timeReady chan struct{}, clocks sentrytime.Clocks, outFD *os.File) error {
if hostarch.PageSize != 4096 {
return fmt.Errorf("restore is not supported with %dK page size", hostarch.PageSize/1024)
}
loadStart := time.Now()
k.runningTasksCond.L = &k.runningTasksMu
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
k.cpuClockTickerStopCond.L = &k.runningTasksMu
k.taskActivityCh = make(chan struct{})
// Load the pre-saved CPUID FeatureSet.
cpuidStart := time.Now()
if _, err := state.Load(ctx, r, &k.featureSet); err != nil {
return err
}
log.Infof("CPUID load took [%s].", time.Since(cpuidStart))
fdnotifier.Pause()
defer fdnotifier.Resume()
kernelStart := time.Now()
stats, err := state.Load(ctx, r, k)
if err != nil {
return err
}
log.Infof("Kernel load stats: %s", stats.String())
log.Infof("Kernel load took [%s].", time.Since(kernelStart))
if asyncMFLoader == nil {
mfStart := time.Now()
if err := k.loadMemoryFiles(ctx, r); err != nil {
return fmt.Errorf("failed to load memory files: %w", err)
}
log.Infof("Memory files load took [%s].", time.Since(mfStart))
} else {
if err := asyncMFLoader.WaitMainMFStart(); err != nil {
return fmt.Errorf("main MF start failed: %w", err)
}
}
k.Timekeeper().SetClocks(clocks, k.vdsoParams)
if timeReady != nil {
close(timeReady)
}
log.Infof("Overall load took [%s] after async work", time.Since(loadStart))
// Now call TarRootfsUpperLayer on the root filesystem
root := k.GlobalInit().Leader().MountNamespace().Root(ctx)
defer root.DecRef(ctx)
ts, ok := root.Mount().Filesystem().Impl().(vfs.TarSerializer)
if !ok {
return fmt.Errorf("rootfs is not an overlayfs")
}
if err := ts.TarUpperLayer(ctx, outFD); err != nil {
return fmt.Errorf("failed to serialize rootfs upper layer to tar: %v", err)
}
return nil
}
// UniqueID returns a unique identifier.
func (k *Kernel) UniqueID() uint64 {
id := k.uniqueID.Add(1)
if id == 0 {
panic("unique identifier generator wrapped around")
}
return id
}
// CreateProcessArgs holds arguments to kernel.CreateProcess.
type CreateProcessArgs struct {
// Filename is the filename to load as the init binary.
//
// If this is provided as "", File will be checked, then the file will be
// guessed via Argv[0].
Filename string
// File is a passed host FD pointing to a file to load as the init binary.
//
// This is checked if and only if Filename is "".
File *vfs.FileDescription
// Argv is a list of arguments.
Argv []string
// Envv is a list of environment variables.
Envv []string
// WorkingDirectory is the initial working directory.
//
// This defaults to the root if empty.
WorkingDirectory string
// Credentials is the initial credentials.
Credentials *auth.Credentials
// NoNewPrivs is the initial prctl NO_NEW_PRIVS state.
NoNewPrivs bool
// FDTable is the initial set of file descriptors. If CreateProcess succeeds,
// it takes a reference on FDTable.
FDTable *FDTable
// Umask is the initial umask.
Umask uint
// Limits are the initial resource limits.
Limits *limits.LimitSet
// MaxSymlinkTraversals is the maximum number of symlinks to follow
// during resolution.
MaxSymlinkTraversals uint
// UTSNamespace is the initial UTS namespace.
UTSNamespace *UTSNamespace
// IPCNamespace is the initial IPC namespace.
IPCNamespace *IPCNamespace
// PIDNamespace is the initial PID Namespace.
PIDNamespace *PIDNamespace
// MountNamespace optionally contains the mount namespace for this
// process. If nil, the init process's mount namespace is used.
//
// Anyone setting MountNamespace must donate a reference (i.e.
// increment it).
MountNamespace *vfs.MountNamespace
// ContainerID is the container that the process belongs to.
ContainerID string
// InitialCgroups are the cgroups the container is initialized to.
InitialCgroups map[Cgroup]struct{}
// InitialCgroupV2 is the cgroup2 node the new process starts in. If nil,
// the process starts in the root cgroup.
InitialCgroupV2 Cgroup2
// CgroupNamespace is the cgroup namespace of the new process. If nil, the
// root cgroup namespace is used.
CgroupNamespace *CgroupNamespace
// Origin indicates how the task was first created.
Origin TaskOrigin
// TTY is the optional controlling TTY to associate with this process.
TTY *TTY
// StartupTimeline tracks the creation of this process as part of overall
// sandbox startup. CreateProcess records midpoints on it, but ownership
// remains with its caller.
StartupTimeline *timing.Timeline
}
// NewContext returns a context.Context that represents the task that will be
// created by args.NewContext(k).
func (args *CreateProcessArgs) NewContext(k *Kernel) context.Context {
return &createProcessContext{
Context: context.Background(),
kernel: k,
args: args,
}
}
// createProcessContext is a context.Context that represents the context
// associated with a task that is being created.
type createProcessContext struct {
context.Context
kernel *Kernel
args *CreateProcessArgs
}
// Value implements context.Context.Value.
func (ctx *createProcessContext) Value(key any) any {
switch key {
case CtxKernel:
return ctx.kernel
case CtxPIDNamespace:
return ctx.args.PIDNamespace
case CtxCgroupNamespace:
if ctx.args.CgroupNamespace == nil {
return nil
}
return ctx.args.CgroupNamespace
case CtxUTSNamespace:
utsns := ctx.args.UTSNamespace
utsns.IncRef()
return utsns
case ipc.CtxIPCNamespace:
ipcns := ctx.args.IPCNamespace
ipcns.IncRef()
return ipcns
case auth.CtxCredentials:
return ctx.args.Credentials
case vfs.CtxRoot:
if ctx.args.MountNamespace == nil {
return nil
}
root := ctx.args.MountNamespace.Root(ctx)
return root
case vfs.CtxMountNamespace:
if ctx.kernel.globalInit == nil {
return nil
}
mntns := ctx.kernel.GlobalInit().Leader().MountNamespace()
mntns.IncRef()
return mntns
case devutil.CtxDevGoferClient:
return ctx.kernel.GetDevGoferClient(ctx.kernel.ContainerName(ctx.args.ContainerID))
case inet.CtxStack:
return ctx.kernel.RootNetworkNamespace().Stack()
case ktime.CtxRealtimeClock:
return ctx.kernel.RealtimeClock()
case limits.CtxLimits:
return ctx.args.Limits
case pgalloc.CtxMemoryCgroupID:
return ctx.getMemoryCgroupID()
case pgalloc.CtxMemoryFile:
return ctx.kernel.mf
case platform.CtxPlatform:
return ctx.kernel
case uniqueid.CtxGlobalUniqueID:
return ctx.kernel.UniqueID()
case uniqueid.CtxGlobalUniqueIDProvider:
return ctx.kernel
case uniqueid.CtxInotifyCookie:
return ctx.kernel.GenerateInotifyCookie()
case unimpl.CtxEvents:
return ctx.kernel
default:
return nil
}
}
func (ctx *createProcessContext) getMemoryCgroupID() uint32 {
for cg := range ctx.args.InitialCgroups {
for _, ctl := range cg.Controllers() {
if ctl.Type() == CgroupControllerMemory {
return cg.ID()
}
}
}
return InvalidCgroupID
}
// CreateProcess creates a new task in a new thread group with the given
// options. The new task has no parent and is in the root PID namespace.
//
// If k.Start() has already been called, then the created process must be
// started by calling kernel.StartProcess(tg).
//
// If k.Start() has not yet been called, then the created task will begin
// running when k.Start() is called.
//
// CreateProcess has no analogue in Linux; it is used to create the initial
// application task, as well as processes started by the control server.
//
// Precondition: Caller must take a ref on args.MountNamespace, which is
// transferred to CreateProcess.
func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, error) {
k.extMu.Lock()
defer k.extMu.Unlock()
log.Infof("EXEC: %#v", args.Argv)
ctx := args.NewContext(k)
mntns := args.MountNamespace
if mntns == 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.
mntns = k.globalInit.Leader().MountNamespace()
mntns.IncRef()
}
// Get the root directory from the MountNamespace.
root := mntns.Root(ctx)
defer root.DecRef(ctx)
refcountCu := cleanup.Make(func() { mntns.DecRef(ctx) })
defer refcountCu.Clean()
// Grab the working directory.
wd := root // Default.
if args.WorkingDirectory != "" {
pop := vfs.PathOperation{
Root: root,
Start: wd,
Path: fspath.Parse(args.WorkingDirectory),
FollowFinalSymlink: true,
}
// NOTE(b/236028361): Do not set CheckSearchable flag to true.
// Application is allowed to start with a working directory that it can
// not access/search. This is consistent with Docker and VFS1. Runc
// explicitly allows for this in 6ce2d63a5db6 ("libct/init_linux: retry
// chdir to fix EPERM"). As described in the commit, runc unintentionally
// allowed this behavior in a couple of releases and applications started
// relying on it. So they decided to allow it for backward compatibility.
var err error
wd, err = k.VFS().GetDentryAt(ctx, args.Credentials, &pop, &vfs.GetDentryOptions{})
if err != nil {
return nil, 0, fmt.Errorf("failed to find initial working directory %q: %v", args.WorkingDirectory, err)
}
defer wd.DecRef(ctx)
}
fsContext := NewFSContext(root, wd, args.Umask)
refcountCu.Add(func() { fsContext.DecRef(ctx) })
args.StartupTimeline.Reached("FS context created")
tg := k.NewThreadGroup(args.PIDNamespace, NewSignalHandlers(), linux.SIGCHLD, args.Limits)
cu := cleanup.Make(func() {
tg.Release(ctx)
})
defer cu.Clean()
// Check which file to start from.
switch {
case args.Filename != "":
// If a filename is given, take that.
// Set File to nil so we resolve the path in LoadTaskImage.
args.File = nil
case args.File != nil:
// If File is set, take the File provided directly.
args.Filename = args.File.MappedName(ctx)
default:
// Otherwise look at Argv and see if the first argument is a valid path.
if len(args.Argv) == 0 {
return nil, 0, fmt.Errorf("no filename or command provided")
}
if !filepath.IsAbs(args.Argv[0]) {
return nil, 0, fmt.Errorf("'%s' is not an absolute path", args.Argv[0])
}
args.Filename = args.Argv[0]
}
// Create a fresh task context.
remainingTraversals := args.MaxSymlinkTraversals
loadArgs := loader.LoadArgs{
Root: root,
WorkingDir: wd,
RemainingTraversals: &remainingTraversals,
ResolveFinal: true,
Filename: args.Filename,
File: args.File,
CloseOnExec: false,
Argv: args.Argv,
Envv: args.Envv,
Features: k.featureSet,
NoNewPrivs: args.NoNewPrivs,
StopPrivGain: false,
AllowSUID: k.AllowSUID,
StartupTimeline: args.StartupTimeline,
}
image, newCreds, _, se := k.LoadTaskImage(ctx, loadArgs)
if se != nil {
return nil, 0, errors.New(se.String())
}
args.StartupTimeline.Reached("task image loaded")
args.FDTable.IncRef()
cgroupns := args.CgroupNamespace
if cgroupns == nil {
cgroupns = k.rootCgroupNamespace
}
// Create the task.
config := &TaskConfig{
Kernel: k,
ThreadGroup: tg,
TaskImage: image,
FSContext: fsContext,
FDTable: args.FDTable,
Credentials: newCreds,
NoNewPrivs: args.NoNewPrivs,
NetworkNamespace: k.RootNetworkNamespace(),
AllowedCPUMask: sched.NewFullCPUSet(k.applicationCores),
UTSNamespace: args.UTSNamespace,
IPCNamespace: args.IPCNamespace,
CgroupNamespace: cgroupns,
MountNamespace: mntns,
ContainerID: args.ContainerID,
InitialCgroups: args.InitialCgroups,
InitialCgroupV2: args.InitialCgroupV2,
UserCounters: k.GetUserCounters(args.Credentials.RealKUID),
Origin: args.Origin,
Personality: linux.PER_LINUX,
// A task with no parent starts out with no session keyring.
SessionKeyring: nil,
}
config.UTSNamespace.IncRef()
config.IPCNamespace.IncRef()
config.CgroupNamespace.IncRef()
config.NetworkNamespace.IncRef()
config.Credentials.UserNamespace.IncRef()
refcountCu.Release() // refs(mntns, fsContext) are transferred to NewTask()
t, err := k.tasks.NewTask(ctx, config)
if err != nil {
return nil, 0, err
}
args.StartupTimeline.Reached("init task created")
t.traceExecEvent(image) // Simulate exec for tracing.
// Set TTY if configured.
if args.TTY != nil {
if err := t.tg.SetControllingTTY(ctx, args.TTY, false /* steal */, true /* isReadable */); err != nil {
return nil, 0, fmt.Errorf("setting controlling tty: %w", err)
}
}
// Success.
cu.Release()
tgid := k.tasks.Root.IDOfThreadGroup(tg)
if k.globalInit == nil {
k.globalInit = tg
}
return tg, tgid, nil
}
// StartProcess starts running a process that was created with CreateProcess.
func (k *Kernel) StartProcess(tg *ThreadGroup) {
t := tg.Leader()
tid := k.tasks.Root.IDOfTask(t)
t.Start(tid)
}
// Start starts execution of all tasks in k.
//
// Preconditions: Start may be called exactly once.
func (k *Kernel) Start() error {
k.extMu.Lock()
defer k.extMu.Unlock()
if k.started {
return fmt.Errorf("kernel already started")
}
k.started = true
k.cpuClockTickTimer = time.NewTimer(linux.ClockTick)
k.runningTasksMu.Lock()
k.cpuClockTickerRunning = true
k.runningTasksMu.Unlock()
go k.runCPUClockTicker()
// If k was created by LoadKernelFrom, timers were stopped during
// Kernel.SaveTo and need to be resumed. If k was created by NewKernel,
// this is a no-op.
k.resumeTimeLocked(k.SupervisorContext())
k.tasks.mu.RLock()
ts := make([]*Task, 0, len(k.tasks.Root.tids))
for t := range k.tasks.Root.tids {
ts = append(ts, t)
}
k.tasks.mu.RUnlock()
// Start task goroutines.
// NOTE(b/235349091): We don't actually need the TaskSet mutex, we just
// need to make sure we only call t.Start() once for each task. Holding the
// mutex for each task start may cause a nested locking error.
for _, t := range ts {
t.Start(t.ThreadID())
}
return nil
}
// pauseTimeLocked pauses all Timers and Timekeeper updates.
//
// Preconditions:
// - Any task goroutines running in k must be stopped.
// - k.extMu must be locked.
func (k *Kernel) pauseTimeLocked(ctx context.Context) {
// Since all task goroutines have been stopped by precondition, the CPU clock
// ticker should stop on its own; wait for it to do so, waking it up from
// sleeping between ticks if necessary.
k.runningTasksMu.Lock()
for k.cpuClockTickerRunning {
select {
case k.cpuClockTickerWakeCh <- struct{}{}:
default:
}
k.cpuClockTickerStopCond.Wait()
}
k.runningTasksMu.Unlock()
// By precondition, nothing else can be interacting with PIDNamespace.tids
// or FDTable.files, so we can iterate them without synchronization. (We
// can't hold the TaskSet mutex when pausing thread group timers because
// thread group timers call ThreadGroup.SendSignal, which takes the TaskSet
// mutex, while holding the Timer mutex.)
for t := range k.tasks.Root.tids {
if t == t.tg.leader {
t.tg.itimerRealTimer.Pause()
for _, it := range t.tg.timers {
it.PauseTimer()
}
}
// This means we'll iterate FDTables shared by multiple tasks repeatedly,
// but ktime.Timer.Pause is idempotent so this is harmless.
if t.fdTable != nil {
t.fdTable.ForEach(ctx, func(_ int32, fd *vfs.FileDescription, _ FDFlags) bool {
if tfd, ok := fd.Impl().(*timerfd.TimerFileDescription); ok {
tfd.PauseTimer()
}
return true
})
}
}
k.timekeeper.Pause()
}
// resumeTimeLocked resumes all Timers and Timekeeper updates. If
// pauseTimeLocked has not been previously called, resumeTimeLocked has no
// effect.
//
// Preconditions:
// - Any task goroutines running in k must be stopped.
// - k.extMu must be locked.
func (k *Kernel) resumeTimeLocked(ctx context.Context) {
// The CPU clock ticker will automatically resume as task goroutines resume
// execution.
k.timekeeper.Resume(k.vdsoParams)
for t := range k.tasks.Root.tids {
if t == t.tg.leader {
t.tg.itimerRealTimer.Resume()
for _, it := range t.tg.timers {
it.ResumeTimer()
}
}
if t.fdTable != nil {
t.fdTable.ForEach(ctx, func(_ int32, fd *vfs.FileDescription, _ FDFlags) bool {
if tfd, ok := fd.Impl().(*timerfd.TimerFileDescription); ok {
tfd.ResumeTimer()
}
return true
})
}
}
}
func (k *Kernel) incRunningTasks() {
for {
tasks := k.runningTasks.Load()
if tasks != 0 {
// Standard case. Simply increment.
if !k.runningTasks.CompareAndSwap(tasks, tasks+1) {
continue
}
return
}
// Transition from 0 -> 1.
k.runningTasksMu.Lock()
if tasks := k.runningTasks.Load(); tasks != 0 {
// Raced with another transition and lost.
if !k.runningTasks.CompareAndSwap(tasks, tasks+1) {
k.runningTasksMu.Unlock()
continue
}
k.runningTasksMu.Unlock()
return
}
if !k.cpuClockTickerRunning {
select {
case tickTime := <-k.cpuClockTickTimer.C:
// Rearm the timer since we consumed the wakeup. Estimate how much time
// remains on the current tick so that periodic workloads interact with
// the (periodic) CPU clock ticker in the same way that they would
// without the optimization of putting the ticker to sleep.
missedNS := time.Since(tickTime).Nanoseconds()
missedTicks := missedNS / linux.ClockTick.Nanoseconds()
thisTickNS := missedNS - missedTicks*linux.ClockTick.Nanoseconds()
k.cpuClockTickTimer.Reset(time.Duration(linux.ClockTick.Nanoseconds() - thisTickNS))
// Increment k.cpuClock on the CPU clock ticker goroutine's behalf.
// (Whole missed ticks don't matter, and adding them to k.cpuClock will
// just confuse the watchdog.) At the time the tick occurred, all task
// goroutines were asleep, so there's nothing else to do. This ensures
// that our caller (Task.accountTaskGoroutineLeave()) records an
// updated k.cpuClock in Task.gosched.Timestamp, so that it's correctly
// accounted as having resumed execution in the sentry during this tick
// instead of at the end of the previous one.
k.cpuClock.Add(1)
default:
}
// We are transitioning from idle to active. Set k.cpuClockTickerRunning
// = true here so that if we transition to idle and then active again
// before the CPU clock ticker goroutine has a chance to run, the first
// call to k.incRunningTasks() at the end of that cycle does not try to
// steal k.cpuClockTickTimer.C again, as this would allow workloads that
// rapidly cycle between idle and active to starve the CPU clock ticker
// of chances to observe task goroutines in a running state and account
// their CPU usage.
k.cpuClockTickerRunning = true
k.runningTasksCond.Signal()
}
// Wake anything parked while the kernel was idle (currently the
// watchdog; see Kernel.WaitForTaskActivity()). This is done on every
// real 0->1 transition, even when the block above is skipped because the
// CPU clock ticker had not yet parked, since a waiter may have parked
// during the slack tick in which runningTasks is 0 but the ticker is
// still running. Closing the channel broadcasts to all waiters; a fresh
// one takes its place for the next idle period.
close(k.taskActivityCh)
k.taskActivityCh = make(chan struct{})
// take a timekeeper reference that says alive until we transition
// back from 1 to 0 tasks so vDSO stays updated; this also updates
// the vDSO before returning if it was parked
k.timekeeper.addRef()
// This store must happen after the increment of k.cpuClock above to ensure
// that concurrent calls to Task.accountTaskGoroutineLeave() also observe
// the updated k.cpuClock.
k.runningTasks.Store(1)
k.runningTasksMu.Unlock()
return
}
}
func (k *Kernel) decRunningTasks() {
tasks := k.runningTasks.Add(-1)
if tasks < 0 {
panic(fmt.Sprintf("Invalid running count %d", tasks))
}
if tasks == 0 {
k.timekeeper.release()
}
// Nothing to do. The next CPU clock tick will disable the timer if
// there is still nothing running. This provides approximately one tick
// of slack in which we can switch back and forth between idle and
// active without an expensive transition.
}
// RunningTasks returns the number of tasks currently in
// TaskGoroutineRunningSys or TaskGoroutineRunningApp, i.e. the number of
// runnable tasks. It is used to implement procs_running in /proc/stat.
func (k *Kernel) RunningTasks() int64 {
return k.runningTasks.Load()
}
// BlockedTasks returns the number of tasks currently in
// TaskGoroutineBlockedUninterruptible, i.e. uninterruptible sleep. It is used
// to implement procs_blocked in /proc/stat.
func (k *Kernel) BlockedTasks() int64 {
return k.blockedTasks.Load()
}
// WaitForTaskActivity blocks until at least one task is running (i.e. the
// kernel is not idle) and returns true, or until stop is signaled and returns
// false. It returns true immediately if a task is already running.
//
// It lets a caller park while the kernel is idle instead of polling: while
// runningTasks is 0, no task can be in TaskGoroutineRunningSys (such a task
// counts as running), and the CPU clock is frozen, so a sentry-activity monitor
// like the watchdog has nothing to observe until a task becomes runnable again.
func (k *Kernel) WaitForTaskActivity(stop <-chan struct{}) bool {
k.runningTasksMu.Lock()
if k.runningTasks.Load() != 0 {
k.runningTasksMu.Unlock()
return true
}
// Read the current notification channel under the lock so we cannot miss a
// 0->1 transition: incRunningTasks closes this exact channel (also under the
// lock) before installing a replacement, so any transition after this point
// wakes us.
ch := k.taskActivityCh
k.runningTasksMu.Unlock()
select {
case <-ch:
return true
case <-stop:
return false
}
}
// WaitExited blocks until all tasks in k have exited. No tasks can be created
// after WaitExited returns.
func (k *Kernel) WaitExited() {
k.tasks.mu.Lock()
defer k.tasks.mu.Unlock()
k.tasks.noNewTasksIfZeroLive = true
for k.tasks.liveTasks != 0 {
k.tasks.zeroLiveTasksCond.Wait()
}
}
// Kill requests that all tasks in k immediately exit as if group exiting with
// status ws. Kill does not wait for tasks to exit.
func (k *Kernel) Kill(ws linux.WaitStatus) {
k.extMu.Lock()
defer k.extMu.Unlock()
k.tasks.Kill(ws)
}
// Pause requests that all tasks in k temporarily stop executing, and blocks
// until all tasks and asynchronous I/O operations in k have stopped. Multiple
// calls to Pause nest and require an equal number of calls to Unpause to
// resume execution.
func (k *Kernel) Pause() {
k.extMu.Lock()
k.tasks.BeginExternalStop()
k.extMu.Unlock()
k.tasks.runningGoroutines.Wait()
k.tasks.aioGoroutines.Wait()
}
// IsPaused returns true if the kernel is currently paused.
func (k *Kernel) IsPaused() bool {
return k.tasks.isExternallyStopped()
}
// ReceiveTaskStates receives full states for all tasks.
//
// Precondition: The kernel must be paused.
func (k *Kernel) ReceiveTaskStates() {
k.extMu.Lock()
k.tasks.PullFullState()
k.extMu.Unlock()
}
// Unpause ends the effect of a previous call to Pause. If Unpause is called
// without a matching preceding call to Pause, Unpause may panic.
func (k *Kernel) Unpause() {
k.extMu.Lock()
defer k.extMu.Unlock()
k.tasks.EndExternalStop()
}
// SendExternalSignal injects a signal into the kernel.
//
// context is used only for debugging to describe how the signal was received.
//
// Preconditions: Kernel must have an init process.
func (k *Kernel) SendExternalSignal(info *linux.SignalInfo, context string) {
k.extMu.Lock()
defer k.extMu.Unlock()
k.sendExternalSignal(info, context)
}
// SendExternalSignalThreadGroup injects a signal into an specific ThreadGroup.
//
// This function doesn't skip signals like SendExternalSignal does.
func (k *Kernel) SendExternalSignalThreadGroup(tg *ThreadGroup, info *linux.SignalInfo) error {
k.extMu.Lock()
defer k.extMu.Unlock()
return tg.SendSignal(info)
}
// SendExternalSignalProcessGroup sends a signal to all ThreadGroups in the
// given process group.
//
// This function doesn't skip signals like SendExternalSignal does.
func (k *Kernel) SendExternalSignalProcessGroup(pg *ProcessGroup, info *linux.SignalInfo) error {
k.extMu.Lock()
defer k.extMu.Unlock()
// If anything goes wrong, we'll return the error, but still try our
// best to deliver to other processes in the group.
var firstErr error
for _, tg := range k.TaskSet().Root.ThreadGroups() {
if tg.ProcessGroup() != pg {
continue
}
if err := tg.SendSignal(info); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
// SendContainerSignal sends the given signal to all processes inside the
// namespace that match the given container ID.
func (k *Kernel) SendContainerSignal(cid string, info *linux.SignalInfo) error {
k.extMu.Lock()
defer k.extMu.Unlock()
k.tasks.mu.RLock()
defer k.tasks.mu.RUnlock()
var lastErr error
for tg := range k.tasks.Root.tgids {
if tg.leader.ContainerID() == cid {
tg.signalHandlers.mu.Lock()
infoCopy := *info
if err := tg.leader.sendSignalLocked(&infoCopy, true /*group*/); err != nil {
lastErr = err
}
tg.signalHandlers.mu.Unlock()
}
}
return lastErr
}
// RebuildTraceContexts rebuilds the trace context for all tasks.
//
// Unfortunately, if these are built while tracing is not enabled, then we will
// not have meaningful trace data. Rebuilding here ensures that we can do so
// after tracing has been enabled.
func (k *Kernel) RebuildTraceContexts() {
// We need to pause all task goroutines because Task.rebuildTraceContext()
// replaces Task.traceContext and Task.traceTask, which are
// task-goroutine-exclusive (i.e. the task goroutine assumes that it can
// access them without synchronization) for performance.
k.Pause()
defer k.Unpause()
k.extMu.Lock()
defer k.extMu.Unlock()
k.tasks.mu.RLock()
defer k.tasks.mu.RUnlock()
for t, tid := range k.tasks.Root.tids {
t.rebuildTraceContext(tid)
}
}
// FeatureSet returns the FeatureSet.
func (k *Kernel) FeatureSet() cpuid.FeatureSet {
return k.featureSet
}
// Timekeeper returns the Timekeeper.
func (k *Kernel) Timekeeper() *Timekeeper {
return k.timekeeper
}
// TaskSet returns the TaskSet.
func (k *Kernel) TaskSet() *TaskSet {
return k.tasks
}
// RootUserNamespace returns the root UserNamespace.
func (k *Kernel) RootUserNamespace() *auth.UserNamespace {
return k.rootUserNamespace
}
// RootUTSNamespace returns the root UTSNamespace.
func (k *Kernel) RootUTSNamespace() *UTSNamespace {
return k.rootUTSNamespace
}
// RootCgroupNamespace returns the root (initial) CgroupNamespace.
func (k *Kernel) RootCgroupNamespace() *CgroupNamespace {
return k.rootCgroupNamespace
}
// RootIPCNamespace takes a reference and returns the root IPCNamespace.
func (k *Kernel) RootIPCNamespace() *IPCNamespace {
return k.rootIPCNamespace
}
// RootPIDNamespace returns the root PIDNamespace.
func (k *Kernel) RootPIDNamespace() *PIDNamespace {
return k.tasks.Root
}
// RootNetworkNamespace returns the root network namespace, always non-nil.
func (k *Kernel) RootNetworkNamespace() *inet.Namespace {
return k.rootNetworkNamespace
}
// GlobalInit returns the thread group with ID 1 in the root PID namespace, or
// nil if no such thread group exists. GlobalInit may return a thread group
// containing no tasks if the thread group has already exited.
func (k *Kernel) GlobalInit() *ThreadGroup {
k.extMu.Lock()
defer k.extMu.Unlock()
return k.globalInit
}
// TestOnlySetGlobalInit sets the thread group with ID 1 in the root PID namespace.
func (k *Kernel) TestOnlySetGlobalInit(tg *ThreadGroup) {
k.globalInit = tg
}
// ApplicationCores returns the number of CPUs visible to sandboxed
// applications.
func (k *Kernel) ApplicationCores() uint {
return k.applicationCores
}
// RealtimeClock returns the application CLOCK_REALTIME clock.
func (k *Kernel) RealtimeClock() ktime.SampledClock {
return k.timekeeper.realtimeClock
}
// MonotonicClock returns the application CLOCK_MONOTONIC clock.
func (k *Kernel) MonotonicClock() ktime.SampledClock {
return k.timekeeper.monotonicClock
}
// MonotonicRawClock returns the system CLOCK_MONOTONIC_RAW clock. When it is
// not enabled as a distinct clock this is the same as MonotonicClock.
func (k *Kernel) MonotonicRawClock() ktime.SampledClock {
if k.timekeeper.monotonicRawClock != nil {
return k.timekeeper.monotonicRawClock
}
return k.timekeeper.monotonicClock
}
// Syslog returns the syslog.
func (k *Kernel) Syslog() *syslog {
return &k.syslog
}
// GenerateInotifyCookie generates a unique inotify event cookie.
//
// Returned values may overlap with previously returned values if the value
// space is exhausted. 0 is not a valid cookie value, all other values
// representable in a uint32 are allowed.
func (k *Kernel) GenerateInotifyCookie() uint32 {
id := k.nextInotifyCookie.Add(1)
// Wrap-around is explicitly allowed for inotify event cookies.
if id == 0 {
id = k.nextInotifyCookie.Add(1)
}
return id
}
// NetlinkPorts returns the netlink port manager.
func (k *Kernel) NetlinkPorts() *port.Manager {
return k.netlinkPorts
}
var (
errSaved = errors.New("sandbox has been successfully saved")
errAutoSaved = errors.New("sandbox has been successfully auto-saved")
)
// SaveStatus returns the sandbox save status. If it was saved successfully,
// autosaved indicates whether save was triggered by autosave. If it was not
// saved successfully, err indicates the sandbox error that caused the kernel to
// exit during save.
func (k *Kernel) SaveStatus() (saved, autosaved bool, err error) {
k.extMu.Lock()
defer k.extMu.Unlock()
switch k.saveStatus {
case nil:
return false, false, nil
case errSaved:
return true, false, nil
case errAutoSaved:
return true, true, nil
default:
return false, false, k.saveStatus
}
}
// SetSaveSuccess sets the flag indicating that save completed successfully, if
// no status was already set.
func (k *Kernel) SetSaveSuccess(autosave bool) {
k.extMu.Lock()
defer k.extMu.Unlock()
if k.saveStatus == nil {
if autosave {
k.saveStatus = errAutoSaved
} else {
k.saveStatus = errSaved
}
}
}
// SetSaveError sets the sandbox error that caused the kernel to exit during
// save, if one is not already set.
func (k *Kernel) SetSaveError(err error) {
k.extMu.Lock()
defer k.extMu.Unlock()
if k.saveStatus == nil {
k.saveStatus = err
}
}
// SetMemoryFile sets Kernel.mf. SetMemoryFile must be called before Init or
// LoadFrom.
func (k *Kernel) SetMemoryFile(mf *pgalloc.MemoryFile) {
k.mf = mf
}
// MemoryFile returns the MemoryFile that provides application memory.
func (k *Kernel) MemoryFile() *pgalloc.MemoryFile {
return k.mf
}
// SupervisorContext returns a Context with maximum privileges in k. It should
// only be used by goroutines outside the control of the emulated kernel
// defined by e.
//
// Callers are responsible for ensuring that the returned Context is not used
// concurrently with changes to the Kernel.
func (k *Kernel) SupervisorContext() context.Context {
return &supervisorContext{
Kernel: k,
Logger: log.Log(),
}
}
// SocketRecord represents a socket recorded in Kernel.sockets.
//
// +stateify savable
type SocketRecord struct {
k *Kernel
Sock *vfs.FileDescription
ID uint64 // Socket table entry number.
}
// RecordSocket adds a socket to the system-wide socket table for
// tracking.
//
// Precondition: Caller must hold a reference to sock.
//
// Note that the socket table will not hold a reference on the
// vfs.FileDescription.
func (k *Kernel) RecordSocket(sock *vfs.FileDescription) {
k.extMu.Lock()
if _, ok := k.sockets[sock]; ok {
panic(fmt.Sprintf("Socket %p added twice", sock))
}
id := k.nextSocketRecord
k.nextSocketRecord++
s := &SocketRecord{
k: k,
ID: id,
Sock: sock,
}
k.sockets[sock] = s
k.extMu.Unlock()
}
// DeleteSocket removes a socket from the system-wide socket table.
func (k *Kernel) DeleteSocket(sock *vfs.FileDescription) {
k.extMu.Lock()
delete(k.sockets, sock)
k.extMu.Unlock()
}
// ListSockets returns a snapshot of all sockets.
//
// Callers of ListSockets() should use SocketRecord.Sock.TryIncRef()
// to get a reference on a socket in the table.
func (k *Kernel) ListSockets() []*SocketRecord {
k.extMu.Lock()
var socks []*SocketRecord
for _, s := range k.sockets {
socks = append(socks, s)
}
k.extMu.Unlock()
return socks
}
// supervisorContext is a privileged context.
type supervisorContext struct {
context.NoTask
log.Logger
*Kernel
}
// Deadline implements context.Context.Deadline.
func (*Kernel) Deadline() (time.Time, bool) {
return time.Time{}, false
}
// Done implements context.Context.Done.
func (*Kernel) Done() <-chan struct{} {
return nil
}
// Err implements context.Context.Err.
func (*Kernel) Err() error {
return nil
}
// Value implements context.Context.
func (ctx *supervisorContext) Value(key any) any {
switch key {
case CtxCanTrace:
// The supervisor context can trace anything. (None of
// supervisorContext's users are expected to invoke ptrace, but ptrace
// permissions are required for certain file accesses.)
return func(*Task, bool) bool { return true }
case CtxKernel:
return ctx.Kernel
case CtxPIDNamespace:
return ctx.Kernel.tasks.Root
case CtxUTSNamespace:
utsns := ctx.Kernel.rootUTSNamespace
utsns.IncRef()
return utsns
case ipc.CtxIPCNamespace:
ipcns := ctx.Kernel.rootIPCNamespace
ipcns.IncRef()
return ipcns
case auth.CtxCredentials:
// The supervisor context is global root.
return auth.NewRootCredentials(ctx.Kernel.rootUserNamespace)
case vfs.CtxRoot:
if ctx.Kernel.globalInit == nil || ctx.Kernel.globalInit.Leader() == nil {
return vfs.VirtualDentry{}
}
root := ctx.Kernel.GlobalInit().Leader().MountNamespace().Root(ctx)
return root
case vfs.CtxMountNamespace:
if ctx.Kernel.globalInit == nil || ctx.Kernel.globalInit.Leader() == nil {
return nil
}
mntns := ctx.Kernel.GlobalInit().Leader().MountNamespace()
mntns.IncRef()
return mntns
case inet.CtxStack:
return ctx.Kernel.RootNetworkNamespace().Stack()
case ktime.CtxRealtimeClock:
return ctx.Kernel.RealtimeClock()
case limits.CtxLimits:
// No limits apply.
return limits.NewLimitSet()
case pgalloc.CtxMemoryFile:
return ctx.Kernel.mf
case platform.CtxPlatform:
return ctx.Kernel
case uniqueid.CtxGlobalUniqueID:
return ctx.Kernel.UniqueID()
case uniqueid.CtxGlobalUniqueIDProvider:
return ctx.Kernel
case uniqueid.CtxInotifyCookie:
return ctx.Kernel.GenerateInotifyCookie()
case unimpl.CtxEvents:
return ctx.Kernel
case cpuid.CtxFeatureSet:
return ctx.Kernel.featureSet
default:
return nil
}
}
// Rate limits for the number of unimplemented syscall events.
const (
unimplementedSyscallsMaxRate = 100 // events per second
unimplementedSyscallBurst = 1000 // events
)
// EmitUnimplementedEvent emits an UnimplementedSyscall event via the event
// channel.
func (k *Kernel) EmitUnimplementedEvent(ctx context.Context, sysno uintptr) {
k.unimplementedSyscallEmitterOnce.Do(func() {
k.unimplementedSyscallEmitter = eventchannel.RateLimitedEmitterFrom(eventchannel.DefaultEmitter, unimplementedSyscallsMaxRate, unimplementedSyscallBurst)
})
t := TaskFromContext(ctx)
IncrementUnimplementedSyscallCounter(sysno)
_, _ = k.unimplementedSyscallEmitter.Emit(&uspb.UnimplementedSyscall{
Tid: int32(t.ThreadID()),
Registers: t.Arch().StateData().Proto(),
})
}
// VFS returns the virtual filesystem for the kernel.
func (k *Kernel) VFS() *vfs.VirtualFilesystem {
return &k.vfs
}
// SetHostMount sets the hostfs mount.
func (k *Kernel) SetHostMount(mnt *vfs.Mount) {
if k.hostMount != nil {
panic("Kernel.hostMount cannot be set more than once")
}
k.hostMount = mnt
}
// HostMount returns the hostfs mount.
func (k *Kernel) HostMount() *vfs.Mount {
return k.hostMount
}
// PipeMount returns the pipefs mount.
func (k *Kernel) PipeMount() *vfs.Mount {
return k.pipeMount
}
// GetNamespaceInode returns a new nsfs inode which serves as a reference counter for the namespace.
func (k *Kernel) GetNamespaceInode(ctx context.Context, ns vfs.Namespace) refs.TryRefCounter {
return nsfs.NewInode(ctx, k.nsfsMount, ns)
}
// ShmMount returns the tmpfs mount.
func (k *Kernel) ShmMount() *vfs.Mount {
return k.shmMount
}
// SocketMount returns the sockfs mount.
func (k *Kernel) SocketMount() *vfs.Mount {
return k.socketMount
}
// CgroupRegistry returns the cgroup registry.
func (k *Kernel) CgroupRegistry() *CgroupRegistry {
return k.cgroupRegistry
}
// AddCgroupMount adds the cgroup mounts to the cgroupMountsMap. These cgroup
// mounts are created during the creation of root container process and the
// reference ownership is transferred to the kernel.
func (k *Kernel) AddCgroupMount(ctl string, mnt *CgroupMount) {
k.cgroupMountsMapMu.Lock()
defer k.cgroupMountsMapMu.Unlock()
if k.cgroupMountsMap == nil {
k.cgroupMountsMap = make(map[string]*CgroupMount)
}
k.cgroupMountsMap[ctl] = mnt
}
// GetCgroupMount returns the cgroup mount for the given cgroup controller.
func (k *Kernel) GetCgroupMount(ctl string) *CgroupMount {
k.cgroupMountsMapMu.Lock()
defer k.cgroupMountsMapMu.Unlock()
return k.cgroupMountsMap[ctl]
}
// releaseCgroupMounts releases the cgroup mounts.
func (k *Kernel) releaseCgroupMounts(ctx context.Context) {
k.cgroupMountsMapMu.Lock()
defer k.cgroupMountsMapMu.Unlock()
for _, m := range k.cgroupMountsMap {
m.Mount.DecRef(ctx)
m.Root.DecRef(ctx)
m.Fs.DecRef(ctx)
}
}
// Release releases resources owned by k.
//
// Precondition: This should only be called after the kernel is fully
// initialized, e.g. after k.Start() has been called.
func (k *Kernel) Release() {
ctx := k.SupervisorContext()
k.releaseCgroupMounts(ctx)
k.hostMount.DecRef(ctx)
k.pipeMount.DecRef(ctx)
k.nsfsMount.DecRef(ctx)
k.shmMount.DecRef(ctx)
k.socketMount.DecRef(ctx)
k.vfs.Release(ctx)
if k.cgroupRegistry != nil {
k.cgroupRegistry.v2fs.DecRef(ctx)
k.cgroupRegistry.v2fs = nil
}
k.timekeeper.Destroy()
k.vdso.Release(ctx)
k.RootNetworkNamespace().DecRef(ctx)
k.rootIPCNamespace.DecRef(ctx)
k.rootUTSNamespace.DecRef(ctx)
k.rootCgroupNamespace.DecRef(ctx)
k.cleaupDevGofers()
k.mf.Destroy()
k.RootPIDNamespace().DecRef(ctx)
k.rootUserNamespace.DecRef(ctx)
}
// PopulateNewCgroupHierarchy moves all tasks into a newly created cgroup
// hierarchy.
//
// Precondition: root must be a new cgroup with no tasks. This implies the
// controllers for root are also new and currently manage no task, which in turn
// implies the new cgroup can be populated without migrating tasks between
// cgroups.
func (k *Kernel) PopulateNewCgroupHierarchy(root Cgroup) {
k.tasks.mu.RLock()
k.tasks.forEachTaskLocked(func(t *Task) {
if t.exitStateLocked() != TaskExitNone {
return
}
t.mu.Lock()
// A task can be in the cgroup if it has been created after the
// cgroup hierarchy was registered.
t.enterCgroupIfNotYetLocked(root)
t.mu.Unlock()
})
k.tasks.mu.RUnlock()
}
// ReleaseCgroupHierarchy moves all tasks out of all cgroups belonging to the
// hierarchy with the provided id. This is intended for use during hierarchy
// teardown, as otherwise the tasks would be orphaned w.r.t to some controllers.
func (k *Kernel) ReleaseCgroupHierarchy(hid uint32) {
var releasedCGs []Cgroup
k.tasks.mu.RLock()
// We'll have one cgroup per hierarchy per task.
releasedCGs = make([]Cgroup, 0, len(k.tasks.Root.tids))
k.tasks.forEachTaskLocked(func(t *Task) {
if t.exitStateLocked() != TaskExitNone {
return
}
t.mu.Lock()
for cg := range t.cgroups {
if cg.HierarchyID() == hid {
cg.Leave(t)
t.ResetMemCgIDFromCgroup(cg)
delete(t.cgroups, cg)
releasedCGs = append(releasedCGs, cg)
// A task can't be part of multiple cgroups from the same
// hierarchy, so we can skip checking the rest once we find a
// match.
break
}
}
t.mu.Unlock()
})
k.tasks.mu.RUnlock()
for _, c := range releasedCGs {
c.decRef()
}
}
// ReplaceFSContextRoots updates root and cwd to `newRoot` in the FSContext
// across all tasks whose old root or cwd were `oldRoot`.
func (k *Kernel) ReplaceFSContextRoots(ctx context.Context, oldRoot vfs.VirtualDentry, newRoot vfs.VirtualDentry) {
k.tasks.mu.RLock()
oldRootDecRefs := 0
k.tasks.forEachTaskLocked(func(t *Task) {
t.mu.Lock() // To prevent t's task goroutine from unsharing its fsContext from under us.
defer t.mu.Unlock()
if fsc := t.FSContext(); fsc != nil {
fsc.mu.Lock()
defer fsc.mu.Unlock()
if fsc.root == oldRoot {
newRoot.IncRef()
oldRootDecRefs++
fsc.root = newRoot
}
if fsc.cwd == oldRoot {
newRoot.IncRef()
oldRootDecRefs++
fsc.cwd = newRoot
}
}
})
k.tasks.mu.RUnlock()
for i := 0; i < oldRootDecRefs; i++ {
oldRoot.DecRef(ctx)
}
}
// GetUserCounters returns the user counters for the given KUID.
func (k *Kernel) GetUserCounters(uid auth.KUID) *UserCounters {
k.userCountersMapMu.Lock()
defer k.userCountersMapMu.Unlock()
if uc, ok := k.userCountersMap[uid]; ok {
return uc
}
uc := &UserCounters{}
k.userCountersMap[uid] = uc
return uc
}
// AddDevGofer initializes the dev gofer connection and starts tracking it.
// It takes ownership of goferFD.
func (k *Kernel) AddDevGofer(contName string, goferFD int) error {
client, err := devutil.NewGoferClient(k.SupervisorContext(), contName, goferFD)
if err != nil {
return err
}
k.devGofersMu.Lock()
defer k.devGofersMu.Unlock()
if k.devGofers == nil {
k.devGofers = make(map[string]*devutil.GoferClient)
}
k.devGofers[contName] = client
return nil
}
// RemoveDevGofer closes the dev gofer connection, if one exists, and stops
// tracking it.
func (k *Kernel) RemoveDevGofer(contName string) {
k.devGofersMu.Lock()
defer k.devGofersMu.Unlock()
client, ok := k.devGofers[contName]
if !ok {
return
}
client.Close()
delete(k.devGofers, contName)
}
// GetDevGoferClient implements
// devutil.GoferClientProviderFromContext.GetDevGoferClient.
func (k *Kernel) GetDevGoferClient(contName string) *devutil.GoferClient {
k.devGofersMu.Lock()
defer k.devGofersMu.Unlock()
return k.devGofers[contName]
}
func (k *Kernel) cleaupDevGofers() {
k.devGofersMu.Lock()
defer k.devGofersMu.Unlock()
for _, client := range k.devGofers {
client.Close()
}
k.devGofers = nil
}
// RegisterContainerName registers a container name for a given container ID.
func (k *Kernel) RegisterContainerName(cid, containerName string) {
k.extMu.Lock()
defer k.extMu.Unlock()
k.containerNames[cid] = containerName
}
// RestoreContainerMapping remaps old container IDs to new ones after a restore.
// containerIDs maps "name -> new container ID". Note that container names remain
// constant between restore sessions.
func (k *Kernel) RestoreContainerMapping(containerIDs map[string]string) {
k.extMu.Lock()
defer k.extMu.Unlock()
// Delete mapping from old session and replace with new values.
k.containerNames = make(map[string]string)
for name, cid := range containerIDs {
k.containerNames[cid] = name
}
}
// ContainerName returns the container name for a given container ID.
func (k *Kernel) ContainerName(cid string) string {
k.extMu.Lock()
defer k.extMu.Unlock()
return k.containerNames[cid]
}