diff --git a/cmd/urunc/create.go b/cmd/urunc/create.go index 5a9e2f94d..75118af98 100644 --- a/cmd/urunc/create.go +++ b/cmd/urunc/create.go @@ -82,16 +82,15 @@ var createCommand = &cli.Command{ }, } -// createUnikontainer creates a Unikernel struct from bundle data, -// initializes it's base dir and state.json, -// setups terminal if required and spawns reexec process, -// waits for reexec process to notify, executes CreateRuntime hooks, -// sends ACK to reexec process -func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (err error) { - err = nil +// newUnikontainer parses the bundle and performs the host-side preparation for +// the monitor execution environment (Unikontainer, base directory, state and +// monitor resources). It never returns for a container that is not a urunc +// container: those are handed over to the real runc with an execve. +func newUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (*unikontainers.Unikontainer, error) { containerID := cmd.Args().First() - if err = validateID(containerID); err != nil { - return err + err := validateID(containerID) + if err != nil { + return nil, err } metrics.SetLoggerContainerID(containerID) metrics.Capture(m.TS00) @@ -105,7 +104,7 @@ func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) ( if bundlePath == "" { bundlePath, err = os.Getwd() if err != nil { - return err + return nil, err } } @@ -114,21 +113,33 @@ func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) ( if err != nil { if errors.Is(err, unikontainers.ErrQueueProxy) || errors.Is(err, unikontainers.ErrNotUnikernel) { - // Exec runc to handle non unikernel containers - err = runcExec() - return err + // Exec runc to handle non urunc containers. + // It should never return. + return nil, runcExec() } - return err + return nil, err } metrics.Capture(m.TS01) err = unikontainer.InitialSetup() if err != nil { - return err + return nil, err } - metrics.Capture(m.TS02) + return unikontainer, nil +} + +// createUnikontainer creates a Unikernel struct from bundle data, initializes +// it's base dir and state.json, setups terminal if required and spawns reexec +// process, waits for reexec process to notify, executes CreateRuntime hooks, +// sends ACK to reexec process +func createUnikontainer(cmd *cli.Command, uruncCfg *unikontainers.UruncConfig) (err error) { + unikontainer, err := newUnikontainer(cmd, uruncCfg) + if err != nil { + return err + } + // Create socket for nsenter initSockParent, initSockChild, err := newSockPair("init") if err != nil { diff --git a/pkg/unikontainers/block.go b/pkg/unikontainers/block.go index 69db4629a..aaac2446b 100644 --- a/pkg/unikontainers/block.go +++ b/pkg/unikontainers/block.go @@ -426,7 +426,7 @@ func (b blockRootfs) getSharedDirs() (types.SharedfsParams, error) { return types.SharedfsParams{}, nil } -func (b blockRootfs) preStart() error { +func (b blockRootfs) preStartCmd() []string { return nil } diff --git a/pkg/unikontainers/initrd_rootfs.go b/pkg/unikontainers/initrd_rootfs.go index ee70d9da2..136b77365 100644 --- a/pkg/unikontainers/initrd_rootfs.go +++ b/pkg/unikontainers/initrd_rootfs.go @@ -57,6 +57,6 @@ func (i initrdRootfs) getSharedDirs() (types.SharedfsParams, error) { return types.SharedfsParams{}, nil } -func (i initrdRootfs) preStart() error { +func (i initrdRootfs) preStartCmd() []string { return nil } diff --git a/pkg/unikontainers/rootfs.go b/pkg/unikontainers/rootfs.go index bd1c800f0..40dd5b963 100644 --- a/pkg/unikontainers/rootfs.go +++ b/pkg/unikontainers/rootfs.go @@ -30,17 +30,13 @@ import ( // TODO: Find and set the correct size for the tmpfs in the host const tmpfsSizeForNoRootfs = "65536k" -// annotRootfsParams holds JSON RootfsParams after shim chooseGuestRootfs. -// When present in bundle config.json, Exec reuses it; otherwise Exec runs ChooseRootfs. -const annotRootfsParams = "com.urunc.internal.rootfs.params" - type rootfsBuilder interface { preSetup() error postSetup() error getMounts() ([]specs.Mount, error) getBlockDevs() ([]types.BlockDevParams, error) getSharedDirs() (types.SharedfsParams, error) - preStart() error + preStartCmd() []string } // tmpfsMount creates a mount for a tmpfs in the form of "/tmp" at target @@ -162,7 +158,7 @@ func (n noRootfs) getSharedDirs() (types.SharedfsParams, error) { return types.SharedfsParams{}, nil } -func (n noRootfs) preStart() error { +func (n noRootfs) preStartCmd() []string { return nil } diff --git a/pkg/unikontainers/shared_fs.go b/pkg/unikontainers/shared_fs.go index 9441f39a7..875d9dc6f 100644 --- a/pkg/unikontainers/shared_fs.go +++ b/pkg/unikontainers/shared_fs.go @@ -15,7 +15,6 @@ package unikontainers import ( - "fmt" "path/filepath" "strings" @@ -73,26 +72,24 @@ func (s sharedfsRootfs) getSharedDirs() (types.SharedfsParams, error) { }, nil } -func (s sharedfsRootfs) preStart() error { +func (s sharedfsRootfs) preStartCmd() []string { if s.sfsType == "9pfs" { return nil } - // Start the virtiofsd process - args := []string{ + // The virtiofsd argv, with the binary itself as the first element so it can be + // both stored in the monitor spec and spawned later by spawnProcess. + argv := []string{ + s.vfsdConfig.Path, "--socket-path=/tmp/vhostqemu", "--shared-dir", s.sharedPath, } if s.vfsdConfig.Options != "" { - args = append(args, strings.Fields(s.vfsdConfig.Options)...) + argv = append(argv, strings.Fields(s.vfsdConfig.Options)...) } - err := spawnProcess(s.vfsdConfig.Path, args) - if err != nil { - err = fmt.Errorf("failed to start virtiofsd: %w", err) - } - return err + return argv } func chooseTmpfsSize(sfsType string, mem uint64) string { diff --git a/pkg/unikontainers/types/types.go b/pkg/unikontainers/types/types.go index f5d668b0d..8950d9388 100644 --- a/pkg/unikontainers/types/types.go +++ b/pkg/unikontainers/types/types.go @@ -142,3 +142,14 @@ type MonitorConfig struct { DataPath string `toml:"data_path,omitempty"` // Optional path to the hypervisor data files (e.g. qemu bios stuff) Vhost bool `toml:"vhost,omitempty"` // Optional: enable vhost for network performance optimization } + +// MonitorSpec is everything the post-pivot urunc process needs in order to +// finalize the monitor's process execution environment and exec the monitor. +type MonitorSpec struct { + ContainerID string `json:"containerID"` + UnikernelType string `json:"unikernelType"` + MonitorType string `json:"monitorType"` + MonitorCfg MonitorConfig `json:"monitorCfg"` + ExecArgs ExecArgs `json:"execArgs"` + GuestParams UnikernelParams `json:"guestParams"` +} diff --git a/pkg/unikontainers/unikontainers.go b/pkg/unikontainers/unikontainers.go index ad0bf2897..5b3db8f24 100644 --- a/pkg/unikontainers/unikontainers.go +++ b/pkg/unikontainers/unikontainers.go @@ -169,26 +169,10 @@ func (u *Unikontainer) InitialSetup() error { // if the respective annotation is set then, depending on the guest // (supports block or 9pfs), it will use the supported option. In case // both ae supported, then the block option will be used by default. - var rootfsParams types.RootfsParams - - // Read the rootfs choice written by the shim. - if rootfsParamsJSON := u.Spec.Annotations[annotRootfsParams]; rootfsParamsJSON != "" { - if err := json.Unmarshal([]byte(rootfsParamsJSON), &rootfsParams); err != nil { - return fmt.Errorf("could not decode guest rootfs params: %w", err) - } - } - - if rootfsParams.MonRootfs == "" { - rootfsParams, err = ChooseRootfs(bundleDir, rootfsDir, u.State.Annotations, u.UruncCfg) - if err != nil { - uniklog.Errorf("could not choose guest rootfs: %v", err) - return err - } - encoded, err := json.Marshal(rootfsParams) - if err != nil { - return err - } - u.State.Annotations[annotRootfsParams] = string(encoded) + rootfsParams, err := ChooseRootfs(bundleDir, rootfsDir, u.State.Annotations, u.UruncCfg) + if err != nil { + uniklog.Errorf("could not choose guest rootfs: %v", err) + return err } uniklog.WithFields(logrus.Fields{ "rootfs_type": rootfsParams.Type, @@ -216,6 +200,12 @@ func (u *Unikontainer) InitialSetup() error { if err != nil { return err } + monRes.Rootfs = rootfsParams + + err = rfsBuilder.postSetup() + if err != nil { + return fmt.Errorf("post setup step for rootfs failed: %w", err) + } u.State.Status = specs.StateCreating // FIXME: should we really create this base dir @@ -255,8 +245,9 @@ func (u *Unikontainer) SetRunningState() error { return u.saveContainerState() } -func (u *Unikontainer) SetupNet() (types.NetDevParams, error) { - networkType := u.getNetworkType() +// SetupNet creates the sandbox's network device (tap) in the current network +// namespace and returns its parameters; uid and gid own the tap device. +func SetupNet(networkType string, uid, gid uint32) (types.NetDevParams, error) { uniklog.WithField("network type", networkType).Debug("Retrieved network type") netArgs := types.NetDevParams{} netManager, err := network.NewNetworkManager(networkType) @@ -264,7 +255,7 @@ func (u *Unikontainer) SetupNet() (types.NetDevParams, error) { return netArgs, fmt.Errorf("failed to create network manager for %s type: %v", networkType, err) } - networkInfo, err := netManager.NetworkSetup(u.Spec.Process.User.UID, u.Spec.Process.User.GID) + networkInfo, err := netManager.NetworkSetup(uid, gid) if err != nil { // TODO: Handle this case better. We do not need to show an error // since there was no network in the container. Therefore, we @@ -388,6 +379,13 @@ func getMonitorResources(rfs rootfsBuilder, rootfsParams types.RootfsParams, vmm } res.Devices = append(res.Devices, blockDevs...) + res.Sharedfs, err = rfs.getSharedDirs() + if err != nil { + return res, fmt.Errorf("failed to get directories to share with sandbox: %w", err) + } + + res.PreStartCmd = rfs.preStartCmd() + return res, nil } @@ -446,45 +444,16 @@ func monitorMemoryBytes(defaultMem uint, resources *specs.LinuxResources) uint64 return mem } -// nolint:gocyclo -func (u *Unikontainer) Exec(metrics m.Writer) error { - metrics.Capture(m.TS15) +func (u *Unikontainer) buildMonitorSpec(rootfsParams types.RootfsParams, monRes monitorResources) types.MonitorSpec { + var mSpec types.MonitorSpec - // container Paths - // Make sure paths are clean - bundleDir := filepath.Clean(u.State.Bundle) - rootfsDir := filepath.Clean(u.Spec.Root.Path) - rootfsDir, err := resolveAgainstBase(bundleDir, rootfsDir) - if err != nil { - uniklog.Errorf("could not resolve rootfs directory %s: %v", rootfsDir, err) - return err - } - - // unikernel unikernelType := u.State.Annotations[annotType] - unikernel, err := unikernels.New(unikernelType) - if err != nil { - return err - } - - // Vmm vmmType := u.State.Annotations[annotHypervisor] - vmm, err := hypervisors.NewVMM(hypervisors.VmmType(vmmType), u.UruncCfg.Monitors) - if err != nil { - return err - } - - // unikernelParams unikernelVersion := u.State.Annotations[annotVersion] - - // ExecArgs unikernelPath := u.State.Annotations[annotBinary] initrdPath := u.State.Annotations[annotInitrd] - // debug uniklog.WithFields(logrus.Fields{ - "bundle directory": bundleDir, - "rootfs directory": rootfsDir, "vmm type": vmmType, "unikernel type": unikernelType, "unikernel version": unikernelVersion, @@ -492,14 +461,12 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { "initrd Path": initrdPath, }).Debug("Initialization values") - // ExecArgs defaultVCPUs := u.UruncCfg.Monitors[vmmType].DefaultVCPUs if defaultVCPUs < 1 { defaultVCPUs = 1 } defaultMemSizeMB := u.UruncCfg.Monitors[vmmType].DefaultMemoryMB - // ExecArgs vmmArgs := types.ExecArgs{ ContainerID: u.State.ID, UnikernelPath: unikernelPath, @@ -510,127 +477,139 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { Environment: os.Environ(), } - // ExecArgs // Check if container is set to unconfined -- disable seccomp if u.Spec.Linux.Seccomp == nil { uniklog.Warn("Seccomp is disabled") vmmArgs.Seccomp = false } - procAttrs := types.ProcessConfig{ - UID: u.Spec.Process.User.UID, - GID: u.Spec.Process.User.GID, - WorkDir: u.Spec.Process.Cwd, - } - // UnikernelParams - // populate unikernel params - unikernelParams := types.UnikernelParams{ - CmdLine: u.Spec.Process.Args, - EnvVars: u.Spec.Process.Env, - Monitor: vmmType, - Version: unikernelVersion, - ProcConf: procAttrs, + guest := types.UnikernelParams{ + CmdLine: u.Spec.Process.Args, + EnvVars: u.Spec.Process.Env, + Monitor: vmmType, + Version: unikernelVersion, + ProcConf: types.ProcessConfig{ + UID: u.Spec.Process.User.UID, + GID: u.Spec.Process.User.GID, + WorkDir: u.Spec.Process.Cwd, + }, NetDevName: u.State.Annotations[annotNetDev], BlkDevName: u.State.Annotations[annotBlkDev], + Rootfs: rootfsParams, + Block: monRes.BlockArgs, } - if len(unikernelParams.CmdLine) == 0 { - unikernelParams.CmdLine = strings.Fields(u.State.Annotations[annotCmdLine]) + if len(guest.CmdLine) == 0 { + guest.CmdLine = strings.Fields(u.State.Annotations[annotCmdLine]) } - // handle network - netArgs, err := u.SetupNet() - if err != nil { - uniklog.Errorf("failed to setup network: %v", err) - return err + if rootfsParams.Type == "virtiofs" || rootfsParams.Type == "9pfs" { + // Update the paths of the files we need to pass in the monitor process. + vmmArgs.UnikernelPath = adjustPathsForSharedfs(vmmArgs.UnikernelPath) + vmmArgs.InitrdPath = adjustPathsForSharedfs(vmmArgs.InitrdPath) } - metrics.Capture(m.TS16) - withTUNTAP := netArgs.IP != "" - - // UnikernelParams - unikernelParams.Net = netArgs + vmmArgs.Sharedfs = monRes.Sharedfs - // ExecArgs - vmmArgs.Net = netArgs + mSpec.ContainerID = u.State.ID + mSpec.UnikernelType = unikernelType + mSpec.MonitorType = vmmType + mSpec.MonitorCfg = u.UruncCfg.Monitors[vmmType] + mSpec.ExecArgs = vmmArgs + mSpec.GuestParams = guest - // guest rootfs - // block - // handle guest's rootfs. - // There are three options: - // 1. No rootfs for guest - // 2. Use the devmapper snapshot as a block device for the guest's rootfs - // 3. Use 9pfs to share the container's rootfs as the guest's rootfs - // By default, urunc will not set any rootfs for the guest. However, - // if the respective annotation is set then, depending on the guest - // (supports block or 9pfs), it will use the supported option. In case - // both ae supported, then the block option will be used by default. - var rootfsParams types.RootfsParams + return mSpec +} - // Read the rootfs choice written by the shim. - if rootfsParamsJSON := u.State.Annotations[annotRootfsParams]; rootfsParamsJSON != "" { - if err := json.Unmarshal([]byte(rootfsParamsJSON), &rootfsParams); err != nil { - return fmt.Errorf("could not decode guest rootfs params: %w", err) - } +// setupMonitorRootfs prepares the monitor rootfs: it makes sure the directory +// exists and is mounted with a propagation flag that allows a later pivot, then +// replicates the gathered mounts and devices inside it and gives the monitor a +// console. +func (u *Unikontainer) setupMonitorRootfs(monRootfs string, monRes monitorResources, withTUNTAP bool) error { + err := os.MkdirAll(monRootfs, 0o755) + if err != nil { + return fmt.Errorf("failed to create monitor rootfs directory %s: %w", monRootfs, err) } - if rootfsParams.MonRootfs == "" { - uniklog.Errorf("missing annotations from selected rootfs") - return fmt.Errorf("missing metadata for rootfs preparation") + // Make sure that rootfs is mounted with the correct propagation + // flags so we can later pivot if needed. + err = prepareRoot(monRootfs, u.Spec.Linux.RootfsPropagation) + if err != nil { + return err } - uniklog.WithFields(logrus.Fields{ - "rootfs_type": rootfsParams.Type, - "rootfs_path": rootfsParams.Path, - "mon_rootfs": rootfsParams.MonRootfs, - }).Debug("guest rootfs params") - rfsBuilder := u.newRootfsBuilder(rootfsParams, unikernel, unikernelPath, initrdPath, vmmArgs.MemSizeB) - if rootfsParams.Type == "virtiofs" || rootfsParams.Type == "9pfs" { - // Update the paths of the files we need to pass in the monitor process. - vmmArgs.UnikernelPath = adjustPathsForSharedfs(vmmArgs.UnikernelPath) - vmmArgs.InitrdPath = adjustPathsForSharedfs(vmmArgs.InitrdPath) + err = applyMounts(monRootfs, monRes.Mounts) + if err != nil { + return fmt.Errorf("failed to apply rootfs mounts: %w", err) } - if err = os.MkdirAll(rootfsParams.MonRootfs, 0o755); err != nil { - return fmt.Errorf("failed to create monitor rootfs directory %s: %w", rootfsParams.MonRootfs, err) + // setupDevices decides whether to create the TUN/TAP device based on the + // container's network configuration. + err = setupDevices(monRootfs, monRes.Devices, withTUNTAP) + if err != nil { + return fmt.Errorf("failed to create devices in monitor rootfs: %w", err) } - // Prepare Monitor rootfs - // Make sure that rootfs is mounted with the correct propagation - // flags so we can later pivot if needed. - err = prepareRoot(rootfsParams.MonRootfs, u.Spec.Linux.RootfsPropagation) + err = setupConsole(monRootfs) if err != nil { - return err + return fmt.Errorf("failed to setup console: %w", err) } - // The monitor mounts and devices were gathered and stored in a file during - // InitialSetup; here we just apply them. postSetup applies the container's own - // bind mounts on top of the shared rootfs, and setupDevices decides whether to - // create the TUN/TAP device based on the container's network configuration. + return nil +} + +// nolint:gocyclo +func (u *Unikontainer) Exec(metrics m.Writer) error { + metrics.Capture(m.TS15) + + // The chosen guest rootfs params, together with the monitor mounts, devices + // and block args, were gathered and stored in monitor.json during + // InitialSetup. Load them back here. monRes, err := loadMonitorResources(u.BaseDir) if err != nil { return fmt.Errorf("failed to load monitor resources: %w", err) } + rootfsParams := monRes.Rootfs + if rootfsParams.MonRootfs == "" { + uniklog.Errorf("missing metadata for selected rootfs") + return fmt.Errorf("missing metadata for rootfs preparation") + } + uniklog.WithFields(logrus.Fields{ + "rootfs_type": rootfsParams.Type, + "rootfs_path": rootfsParams.Path, + "mon_rootfs": rootfsParams.MonRootfs, + }).Debug("guest rootfs params") - err = applyMounts(rootfsParams.MonRootfs, monRes.Mounts) + ms := u.buildMonitorSpec(rootfsParams, monRes) + vmmArgs := ms.ExecArgs + unikernelParams := ms.GuestParams + + // The spec carries the monitor and unikernel by type; rebuild the behaviour + // objects it refers to, exactly as the monitor process does. + unikernel, err := unikernels.New(ms.UnikernelType) if err != nil { - return fmt.Errorf("failed to apply rootfs mounts: %w", err) + return err } - - err = rfsBuilder.postSetup() + vmm, err := hypervisors.NewVMM(hypervisors.VmmType(ms.MonitorType), u.UruncCfg.Monitors) if err != nil { - return fmt.Errorf("post setup step for rootfs failed: %w", err) + return err } - err = setupDevices(rootfsParams.MonRootfs, monRes.Devices, withTUNTAP) + // handle network + netArgs, err := SetupNet(u.getNetworkType(), u.Spec.Process.User.UID, u.Spec.Process.User.GID) if err != nil { - return fmt.Errorf("failed to create devices in monitor rootfs: %w", err) + uniklog.Errorf("failed to setup network: %v", err) + return err } + metrics.Capture(m.TS16) + withTUNTAP := netArgs.IP != "" + unikernelParams.Net = netArgs + vmmArgs.Net = netArgs - err = setupConsole(rootfsParams.MonRootfs) + err = u.setupMonitorRootfs(rootfsParams.MonRootfs, monRes, withTUNTAP) if err != nil { return err } - metrics.Capture(m.TS17) + // vAccel setup vAccelType, vsockSocketPath, rpcAddress, err := resolveVAccelConfig(u.State.Annotations[annotHypervisor], u.Spec.Annotations) if err != nil { @@ -664,39 +643,13 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { vmmArgs.VSockDevID = idToGuestCID(u.State.ID) } - unikernelParams.Rootfs = rootfsParams - - // unikernelParams - // The block parameters were gathered in InitialSetup and stored in the - // monitor resources file. - unikernelParams.Block = monRes.BlockArgs - - // ExecArgs - sharedfsArgs, err := rfsBuilder.getSharedDirs() - if err != nil { - return fmt.Errorf("failed to get directories to share with sandbox: %w", err) - } - vmmArgs.Sharedfs = sharedfsArgs - - // unikernel - err = unikernel.Init(unikernelParams) - if errors.Is(err, unikernels.ErrUndefinedVersion) || - errors.Is(err, unikernels.ErrVersionParsing) { - uniklog.WithError(err).Error("an error occurred while initializing the unikernel") - } else if err != nil { - return err - } - // unikernel // build the unikernel command - unikernelCmd, err := unikernel.CommandString() + vmmArgs.Command, err = buildUnikernelCommand(unikernel, unikernelParams) if err != nil { return err } - // ExecArgs - vmmArgs.Command = unikernelCmd - // pivot _, err = findNS(u.Spec.Linux.Namespaces, specs.MountNamespace) // Only pivot if a mount namespace entry is actually present in the @@ -728,35 +681,54 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { return err } - err = rfsBuilder.preStart() + err = spawnProcess(monRes.PreStartCmd) if err != nil { return err } - uniklog.Debug("calling vmm execve") - metrics.Capture(m.TS18) - - // Build the VMM command once and verify it can be constructed successfully. - // This ensures we don't report the container as started if command building fails. + // Build the VMM command once and verify it can be constructed successfully, so + // we do not report the container as started if command building fails. execCmd, err := vmm.BuildExecCmd(vmmArgs, unikernel) if err != nil { uniklog.WithError(err).Error("failed to build VMM command") return err } - // Notify urunc start that the monitor is ready to execute. - // We send this after BuildExecCmd succeeds to avoid reporting a container - // as started when the VMM command cannot be built. - // TODO: The container can still be reported as running if the PreExec step - // (e.g., BPF/seccomp filter setup) fails after this point. We should find - // a way to handle that case as well. + // Notify urunc start that the monitor is ready to execute, only after the + // command builds so a container is never reported started when it cannot be. err = u.SendMessage(StartSuccess) if err != nil { return err } + return execMonitor(metrics, vmm, vmmArgs, execCmd) +} + +// buildUnikernelCommand initializes the unikernel with the collected parameters +// and returns its command line. +func buildUnikernelCommand(unikernel types.Unikernel, params types.UnikernelParams) (string, error) { + err := unikernel.Init(params) + if errors.Is(err, unikernels.ErrUndefinedVersion) || + errors.Is(err, unikernels.ErrVersionParsing) { + uniklog.WithError(err).Error("an error occurred while initializing the unikernel") + } else if err != nil { + return "", err + } + + return unikernel.CommandString() +} + +// execMonitor runs the monitor's pre-exec setup and finally execve's the monitor. +// It does not return on success: +// +// TODO: The container can still be reported as running if the PreExec step +// (e.g., BPF/seccomp filter setup) fails after the caller reported success. We +// should find a way to handle that case as well. +func execMonitor(metrics m.Writer, vmm types.VMM, execArgs types.ExecArgs, execCmd []string) error { + uniklog.Debug("calling vmm execve") + metrics.Capture(m.TS18) // Perform any monitor-specific pre-exec setup (e.g., seccomp filters for HVT). - err = vmm.PreExec(vmmArgs) + err := vmm.PreExec(execArgs) if err != nil { uniklog.WithError(err).Error("failed to perform pre-exec setup") return err @@ -764,7 +736,7 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { // Execute the VMM using the command we built earlier. uniklog.WithField("command", execCmd).Debug("Ready to execve VMM") - return syscall.Exec(vmm.Path(), execCmd, vmmArgs.Environment) //nolint: gosec + return syscall.Exec(vmm.Path(), execCmd, execArgs.Environment) //nolint: gosec } func setupUser(user specs.User) error { diff --git a/pkg/unikontainers/utils.go b/pkg/unikontainers/utils.go index 90b9500cf..68977dd0d 100644 --- a/pkg/unikontainers/utils.go +++ b/pkg/unikontainers/utils.go @@ -44,12 +44,16 @@ const ( rootfsDirName = "rootfs" ) -// monitorResources holds the mounts and devices that must be replicated inside -// the monitor's rootfs, along with the block parameters the guest needs. +// monitorResources holds the chosen guest rootfs params, the mounts and devices +// that must be replicated inside the monitor's rootfs, and the block parameters +// the guest needs. type monitorResources struct { - Mounts []specs.Mount `json:"mounts"` - Devices []specs.LinuxDevice `json:"devices"` - BlockArgs []types.BlockDevParams `json:"blockArgs"` + Rootfs types.RootfsParams `json:"rootfs"` + Mounts []specs.Mount `json:"mounts"` + Devices []specs.LinuxDevice `json:"devices"` + BlockArgs []types.BlockDevParams `json:"blockArgs"` + Sharedfs types.SharedfsParams `json:"sharedfs"` + PreStartCmd []string `json:"preStartCmd,omitempty"` } // saveMonitorResources stores the monitorResources passed as an argument in a JSON @@ -243,14 +247,20 @@ func convertUint32ToIntSlice(valSlice []uint32, size int) []int { // return data.Bytes(), nil // } -func spawnProcess(binaryPath string, args []string) error { - cmd := exec.Command(binaryPath, args...) +// spawnProcess starts the process described by argv, whose first element is the +// binary. An empty argv is a no-op. +func spawnProcess(argv []string) error { + if len(argv) == 0 { + return nil + } + // argv is built by urunc, based on its config, not untrusted input + cmd := exec.Command(argv[0], argv[1:]...) //nolint:gosec cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err != nil { - return err + return fmt.Errorf("failed to start %s: %w", argv[0], err) } return nil