...

Source file src/github.com/opencontainers/runc/utils_linux.go

Documentation: github.com/opencontainers/runc

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strconv"
    11  
    12  	"github.com/coreos/go-systemd/v22/activation"
    13  	"github.com/opencontainers/runtime-spec/specs-go"
    14  	selinux "github.com/opencontainers/selinux/go-selinux"
    15  	"github.com/sirupsen/logrus"
    16  	"github.com/urfave/cli"
    17  	"golang.org/x/sys/unix"
    18  
    19  	"github.com/opencontainers/runc/libcontainer"
    20  	"github.com/opencontainers/runc/libcontainer/configs"
    21  	"github.com/opencontainers/runc/libcontainer/specconv"
    22  	"github.com/opencontainers/runc/libcontainer/utils"
    23  )
    24  
    25  var errEmptyID = errors.New("container id cannot be empty")
    26  
    27  // loadFactory returns the configured factory instance for execing containers.
    28  func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
    29  	root := context.GlobalString("root")
    30  	abs, err := filepath.Abs(root)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	// We resolve the paths for {newuidmap,newgidmap} from the context of runc,
    36  	// to avoid doing a path lookup in the nsexec context. TODO: The binary
    37  	// names are not currently configurable.
    38  	newuidmap, err := exec.LookPath("newuidmap")
    39  	if err != nil {
    40  		newuidmap = ""
    41  	}
    42  	newgidmap, err := exec.LookPath("newgidmap")
    43  	if err != nil {
    44  		newgidmap = ""
    45  	}
    46  
    47  	return libcontainer.New(abs,
    48  		libcontainer.CriuPath(context.GlobalString("criu")),
    49  		libcontainer.NewuidmapPath(newuidmap),
    50  		libcontainer.NewgidmapPath(newgidmap))
    51  }
    52  
    53  // getContainer returns the specified container instance by loading it from state
    54  // with the default factory.
    55  func getContainer(context *cli.Context) (libcontainer.Container, error) {
    56  	id := context.Args().First()
    57  	if id == "" {
    58  		return nil, errEmptyID
    59  	}
    60  	factory, err := loadFactory(context)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return factory.Load(id)
    65  }
    66  
    67  func getDefaultImagePath() string {
    68  	cwd, err := os.Getwd()
    69  	if err != nil {
    70  		panic(err)
    71  	}
    72  	return filepath.Join(cwd, "checkpoint")
    73  }
    74  
    75  // newProcess returns a new libcontainer Process with the arguments from the
    76  // spec and stdio from the current process.
    77  func newProcess(p specs.Process) (*libcontainer.Process, error) {
    78  	lp := &libcontainer.Process{
    79  		Args: p.Args,
    80  		Env:  p.Env,
    81  		// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
    82  		User:            fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
    83  		Cwd:             p.Cwd,
    84  		Label:           p.SelinuxLabel,
    85  		NoNewPrivileges: &p.NoNewPrivileges,
    86  		AppArmorProfile: p.ApparmorProfile,
    87  	}
    88  
    89  	if p.ConsoleSize != nil {
    90  		lp.ConsoleWidth = uint16(p.ConsoleSize.Width)
    91  		lp.ConsoleHeight = uint16(p.ConsoleSize.Height)
    92  	}
    93  
    94  	if p.Capabilities != nil {
    95  		lp.Capabilities = &configs.Capabilities{}
    96  		lp.Capabilities.Bounding = p.Capabilities.Bounding
    97  		lp.Capabilities.Effective = p.Capabilities.Effective
    98  		lp.Capabilities.Inheritable = p.Capabilities.Inheritable
    99  		lp.Capabilities.Permitted = p.Capabilities.Permitted
   100  		lp.Capabilities.Ambient = p.Capabilities.Ambient
   101  	}
   102  	for _, gid := range p.User.AdditionalGids {
   103  		lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10))
   104  	}
   105  	for _, rlimit := range p.Rlimits {
   106  		rl, err := createLibContainerRlimit(rlimit)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  		lp.Rlimits = append(lp.Rlimits, rl)
   111  	}
   112  	return lp, nil
   113  }
   114  
   115  func destroy(container libcontainer.Container) {
   116  	if err := container.Destroy(); err != nil {
   117  		logrus.Error(err)
   118  	}
   119  }
   120  
   121  // setupIO modifies the given process config according to the options.
   122  func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool, sockpath string) (*tty, error) {
   123  	if createTTY {
   124  		process.Stdin = nil
   125  		process.Stdout = nil
   126  		process.Stderr = nil
   127  		t := &tty{}
   128  		if !detach {
   129  			if err := t.initHostConsole(); err != nil {
   130  				return nil, err
   131  			}
   132  			parent, child, err := utils.NewSockPair("console")
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			process.ConsoleSocket = child
   137  			t.postStart = append(t.postStart, parent, child)
   138  			t.consoleC = make(chan error, 1)
   139  			go func() {
   140  				t.consoleC <- t.recvtty(parent)
   141  			}()
   142  		} else {
   143  			// the caller of runc will handle receiving the console master
   144  			conn, err := net.Dial("unix", sockpath)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  			uc, ok := conn.(*net.UnixConn)
   149  			if !ok {
   150  				return nil, errors.New("casting to UnixConn failed")
   151  			}
   152  			t.postStart = append(t.postStart, uc)
   153  			socket, err := uc.File()
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  			t.postStart = append(t.postStart, socket)
   158  			process.ConsoleSocket = socket
   159  		}
   160  		return t, nil
   161  	}
   162  	// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
   163  	// and the container's process inherits runc's stdio.
   164  	if detach {
   165  		inheritStdio(process)
   166  		return &tty{}, nil
   167  	}
   168  	return setupProcessPipes(process, rootuid, rootgid)
   169  }
   170  
   171  // createPidFile creates a file with the processes pid inside it atomically
   172  // it creates a temp file with the paths filename + '.' infront of it
   173  // then renames the file
   174  func createPidFile(path string, process *libcontainer.Process) error {
   175  	pid, err := process.Pid()
   176  	if err != nil {
   177  		return err
   178  	}
   179  	var (
   180  		tmpDir  = filepath.Dir(path)
   181  		tmpName = filepath.Join(tmpDir, "."+filepath.Base(path))
   182  	)
   183  	f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0o666)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	_, err = f.WriteString(strconv.Itoa(pid))
   188  	f.Close()
   189  	if err != nil {
   190  		return err
   191  	}
   192  	return os.Rename(tmpName, path)
   193  }
   194  
   195  func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
   196  	rootlessCg, err := shouldUseRootlessCgroupManager(context)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
   201  		CgroupName:       id,
   202  		UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
   203  		NoPivotRoot:      context.Bool("no-pivot"),
   204  		NoNewKeyring:     context.Bool("no-new-keyring"),
   205  		Spec:             spec,
   206  		RootlessEUID:     os.Geteuid() != 0,
   207  		RootlessCgroups:  rootlessCg,
   208  	})
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	factory, err := loadFactory(context)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	return factory.Create(id, config)
   218  }
   219  
   220  type runner struct {
   221  	init            bool
   222  	enableSubreaper bool
   223  	shouldDestroy   bool
   224  	detach          bool
   225  	listenFDs       []*os.File
   226  	preserveFDs     int
   227  	pidFile         string
   228  	consoleSocket   string
   229  	container       libcontainer.Container
   230  	action          CtAct
   231  	notifySocket    *notifySocket
   232  	criuOpts        *libcontainer.CriuOpts
   233  	subCgroupPaths  map[string]string
   234  }
   235  
   236  func (r *runner) run(config *specs.Process) (int, error) {
   237  	var err error
   238  	defer func() {
   239  		if err != nil {
   240  			r.destroy()
   241  		}
   242  	}()
   243  	if err = r.checkTerminal(config); err != nil {
   244  		return -1, err
   245  	}
   246  	process, err := newProcess(*config)
   247  	if err != nil {
   248  		return -1, err
   249  	}
   250  	process.LogLevel = strconv.Itoa(int(logrus.GetLevel()))
   251  	// Populate the fields that come from runner.
   252  	process.Init = r.init
   253  	process.SubCgroupPaths = r.subCgroupPaths
   254  	if len(r.listenFDs) > 0 {
   255  		process.Env = append(process.Env, "LISTEN_FDS="+strconv.Itoa(len(r.listenFDs)), "LISTEN_PID=1")
   256  		process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
   257  	}
   258  	baseFd := 3 + len(process.ExtraFiles)
   259  	for i := baseFd; i < baseFd+r.preserveFDs; i++ {
   260  		_, err = os.Stat("/proc/self/fd/" + strconv.Itoa(i))
   261  		if err != nil {
   262  			return -1, fmt.Errorf("unable to stat preserved-fd %d (of %d): %w", i-baseFd, r.preserveFDs, err)
   263  		}
   264  		process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
   265  	}
   266  	rootuid, err := r.container.Config().HostRootUID()
   267  	if err != nil {
   268  		return -1, err
   269  	}
   270  	rootgid, err := r.container.Config().HostRootGID()
   271  	if err != nil {
   272  		return -1, err
   273  	}
   274  	detach := r.detach || (r.action == CT_ACT_CREATE)
   275  	// Setting up IO is a two stage process. We need to modify process to deal
   276  	// with detaching containers, and then we get a tty after the container has
   277  	// started.
   278  	handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
   279  	tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket)
   280  	if err != nil {
   281  		return -1, err
   282  	}
   283  	defer tty.Close()
   284  
   285  	switch r.action {
   286  	case CT_ACT_CREATE:
   287  		err = r.container.Start(process)
   288  	case CT_ACT_RESTORE:
   289  		err = r.container.Restore(process, r.criuOpts)
   290  	case CT_ACT_RUN:
   291  		err = r.container.Run(process)
   292  	default:
   293  		panic("Unknown action")
   294  	}
   295  	if err != nil {
   296  		return -1, err
   297  	}
   298  	if err = tty.waitConsole(); err != nil {
   299  		r.terminate(process)
   300  		return -1, err
   301  	}
   302  	tty.ClosePostStart()
   303  	if r.pidFile != "" {
   304  		if err = createPidFile(r.pidFile, process); err != nil {
   305  			r.terminate(process)
   306  			return -1, err
   307  		}
   308  	}
   309  	status, err := handler.forward(process, tty, detach)
   310  	if err != nil {
   311  		r.terminate(process)
   312  	}
   313  	if detach {
   314  		return 0, nil
   315  	}
   316  	if err == nil {
   317  		r.destroy()
   318  	}
   319  	return status, err
   320  }
   321  
   322  func (r *runner) destroy() {
   323  	if r.shouldDestroy {
   324  		destroy(r.container)
   325  	}
   326  }
   327  
   328  func (r *runner) terminate(p *libcontainer.Process) {
   329  	_ = p.Signal(unix.SIGKILL)
   330  	_, _ = p.Wait()
   331  }
   332  
   333  func (r *runner) checkTerminal(config *specs.Process) error {
   334  	detach := r.detach || (r.action == CT_ACT_CREATE)
   335  	// Check command-line for sanity.
   336  	if detach && config.Terminal && r.consoleSocket == "" {
   337  		return errors.New("cannot allocate tty if runc will detach without setting console socket")
   338  	}
   339  	if (!detach || !config.Terminal) && r.consoleSocket != "" {
   340  		return errors.New("cannot use console socket if runc will not detach or allocate tty")
   341  	}
   342  	return nil
   343  }
   344  
   345  func validateProcessSpec(spec *specs.Process) error {
   346  	if spec == nil {
   347  		return errors.New("process property must not be empty")
   348  	}
   349  	if spec.Cwd == "" {
   350  		return errors.New("Cwd property must not be empty")
   351  	}
   352  	if !filepath.IsAbs(spec.Cwd) {
   353  		return errors.New("Cwd must be an absolute path")
   354  	}
   355  	if len(spec.Args) == 0 {
   356  		return errors.New("args must not be empty")
   357  	}
   358  	if spec.SelinuxLabel != "" && !selinux.GetEnabled() {
   359  		return errors.New("selinux label is specified in config, but selinux is disabled or not supported")
   360  	}
   361  	return nil
   362  }
   363  
   364  type CtAct uint8
   365  
   366  const (
   367  	CT_ACT_CREATE CtAct = iota + 1
   368  	CT_ACT_RUN
   369  	CT_ACT_RESTORE
   370  )
   371  
   372  func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
   373  	if err := revisePidFile(context); err != nil {
   374  		return -1, err
   375  	}
   376  	spec, err := setupSpec(context)
   377  	if err != nil {
   378  		return -1, err
   379  	}
   380  
   381  	id := context.Args().First()
   382  	if id == "" {
   383  		return -1, errEmptyID
   384  	}
   385  
   386  	notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
   387  	if notifySocket != nil {
   388  		notifySocket.setupSpec(spec)
   389  	}
   390  
   391  	container, err := createContainer(context, id, spec)
   392  	if err != nil {
   393  		return -1, err
   394  	}
   395  
   396  	if notifySocket != nil {
   397  		if err := notifySocket.setupSocketDirectory(); err != nil {
   398  			return -1, err
   399  		}
   400  		if action == CT_ACT_RUN {
   401  			if err := notifySocket.bindSocket(); err != nil {
   402  				return -1, err
   403  			}
   404  		}
   405  	}
   406  
   407  	// Support on-demand socket activation by passing file descriptors into the container init process.
   408  	listenFDs := []*os.File{}
   409  	if os.Getenv("LISTEN_FDS") != "" {
   410  		listenFDs = activation.Files(false)
   411  	}
   412  
   413  	r := &runner{
   414  		enableSubreaper: !context.Bool("no-subreaper"),
   415  		shouldDestroy:   !context.Bool("keep"),
   416  		container:       container,
   417  		listenFDs:       listenFDs,
   418  		notifySocket:    notifySocket,
   419  		consoleSocket:   context.String("console-socket"),
   420  		detach:          context.Bool("detach"),
   421  		pidFile:         context.String("pid-file"),
   422  		preserveFDs:     context.Int("preserve-fds"),
   423  		action:          action,
   424  		criuOpts:        criuOpts,
   425  		init:            true,
   426  	}
   427  	return r.run(spec.Process)
   428  }
   429  

View as plain text