github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/cmd/exec.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/google/subcommands"
    30  	specs "github.com/opencontainers/runtime-spec/specs-go"
    31  	"golang.org/x/sys/unix"
    32  	"github.com/SagerNet/gvisor/pkg/log"
    33  	"github.com/SagerNet/gvisor/pkg/sentry/control"
    34  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    35  	"github.com/SagerNet/gvisor/pkg/urpc"
    36  	"github.com/SagerNet/gvisor/runsc/config"
    37  	"github.com/SagerNet/gvisor/runsc/console"
    38  	"github.com/SagerNet/gvisor/runsc/container"
    39  	"github.com/SagerNet/gvisor/runsc/flag"
    40  	"github.com/SagerNet/gvisor/runsc/specutils"
    41  )
    42  
    43  // Exec implements subcommands.Command for the "exec" command.
    44  type Exec struct {
    45  	cwd string
    46  	env stringSlice
    47  	// user contains the UID and GID with which to run the new process.
    48  	user            user
    49  	extraKGIDs      stringSlice
    50  	caps            stringSlice
    51  	detach          bool
    52  	processPath     string
    53  	pidFile         string
    54  	internalPidFile string
    55  
    56  	// consoleSocket is the path to an AF_UNIX socket which will receive a
    57  	// file descriptor referencing the master end of the console's
    58  	// pseudoterminal.
    59  	consoleSocket string
    60  }
    61  
    62  // Name implements subcommands.Command.Name.
    63  func (*Exec) Name() string {
    64  	return "exec"
    65  }
    66  
    67  // Synopsis implements subcommands.Command.Synopsis.
    68  func (*Exec) Synopsis() string {
    69  	return "execute new process inside the container"
    70  }
    71  
    72  // Usage implements subcommands.Command.Usage.
    73  func (*Exec) Usage() string {
    74  	return `exec [command options] <container-id> <command> [command options] || --process process.json <container-id>
    75  
    76  
    77  Where "<container-id>" is the name for the instance of the container and
    78  "<command>" is the command to be executed in the container.
    79  "<command>" can't be empty unless a "-process" flag provided.
    80  
    81  EXAMPLE:
    82  If the container is configured to run /bin/ps the following will
    83  output a list of processes running in the container:
    84  
    85         # runc exec <container-id> ps
    86  
    87  OPTIONS:
    88  `
    89  }
    90  
    91  // SetFlags implements subcommands.Command.SetFlags.
    92  func (ex *Exec) SetFlags(f *flag.FlagSet) {
    93  	f.StringVar(&ex.cwd, "cwd", "", "current working directory")
    94  	f.Var(&ex.env, "env", "set environment variables (e.g. '-env PATH=/bin -env TERM=xterm')")
    95  	f.Var(&ex.user, "user", "UID (format: <uid>[:<gid>])")
    96  	f.Var(&ex.extraKGIDs, "additional-gids", "additional gids")
    97  	f.Var(&ex.caps, "cap", "add a capability to the bounding set for the process")
    98  	f.BoolVar(&ex.detach, "detach", false, "detach from the container's process")
    99  	f.StringVar(&ex.processPath, "process", "", "path to the process.json")
   100  	f.StringVar(&ex.pidFile, "pid-file", "", "filename that the container pid will be written to")
   101  	f.StringVar(&ex.internalPidFile, "internal-pid-file", "", "filename that the container-internal pid will be written to")
   102  	f.StringVar(&ex.consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal")
   103  }
   104  
   105  // Execute implements subcommands.Command.Execute. It starts a process in an
   106  // already created container.
   107  func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
   108  	conf := args[0].(*config.Config)
   109  	e, id, err := ex.parseArgs(f, conf.EnableRaw)
   110  	if err != nil {
   111  		Fatalf("parsing process spec: %v", err)
   112  	}
   113  	waitStatus := args[1].(*unix.WaitStatus)
   114  
   115  	c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
   116  	if err != nil {
   117  		Fatalf("loading sandbox: %v", err)
   118  	}
   119  
   120  	log.Debugf("Exec arguments: %+v", e)
   121  	log.Debugf("Exec capabilities: %+v", e.Capabilities)
   122  
   123  	// Replace empty settings with defaults from container.
   124  	if e.WorkingDirectory == "" {
   125  		e.WorkingDirectory = c.Spec.Process.Cwd
   126  	}
   127  	if e.Envv == nil {
   128  		e.Envv, err = specutils.ResolveEnvs(c.Spec.Process.Env, ex.env)
   129  		if err != nil {
   130  			Fatalf("getting environment variables: %v", err)
   131  		}
   132  	}
   133  
   134  	if e.Capabilities == nil {
   135  		e.Capabilities, err = specutils.Capabilities(conf.EnableRaw, c.Spec.Process.Capabilities)
   136  		if err != nil {
   137  			Fatalf("creating capabilities: %v", err)
   138  		}
   139  		log.Infof("Using exec capabilities from container: %+v", e.Capabilities)
   140  	}
   141  
   142  	// containerd expects an actual process to represent the container being
   143  	// executed. If detach was specified, starts a child in non-detach mode,
   144  	// write the child's PID to the pid file. So when the container returns, the
   145  	// child process will also return and signal containerd.
   146  	if ex.detach {
   147  		return ex.execChildAndWait(waitStatus)
   148  	}
   149  	return ex.exec(c, e, waitStatus)
   150  }
   151  
   152  func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *unix.WaitStatus) subcommands.ExitStatus {
   153  	// Start the new process and get its pid.
   154  	pid, err := c.Execute(e)
   155  	if err != nil {
   156  		return Errorf("executing processes for container: %v", err)
   157  	}
   158  
   159  	if e.StdioIsPty {
   160  		// Forward signals sent to this process to the foreground
   161  		// process in the sandbox.
   162  		stopForwarding := c.ForwardSignals(pid, true /* fgProcess */)
   163  		defer stopForwarding()
   164  	}
   165  
   166  	// Write the sandbox-internal pid if required.
   167  	if ex.internalPidFile != "" {
   168  		pidStr := []byte(strconv.Itoa(int(pid)))
   169  		if err := ioutil.WriteFile(ex.internalPidFile, pidStr, 0644); err != nil {
   170  			return Errorf("writing internal pid file %q: %v", ex.internalPidFile, err)
   171  		}
   172  	}
   173  
   174  	// Generate the pid file after the internal pid file is generated, so that
   175  	// users can safely assume that the internal pid file is ready after
   176  	// `runsc exec -d` returns.
   177  	if ex.pidFile != "" {
   178  		if err := ioutil.WriteFile(ex.pidFile, []byte(strconv.Itoa(os.Getpid())), 0644); err != nil {
   179  			return Errorf("writing pid file: %v", err)
   180  		}
   181  	}
   182  
   183  	// Wait for the process to exit.
   184  	ws, err := c.WaitPID(pid)
   185  	if err != nil {
   186  		return Errorf("waiting on pid %d: %v", pid, err)
   187  	}
   188  	*waitStatus = ws
   189  	return subcommands.ExitSuccess
   190  }
   191  
   192  func (ex *Exec) execChildAndWait(waitStatus *unix.WaitStatus) subcommands.ExitStatus {
   193  	var args []string
   194  	for _, a := range os.Args[1:] {
   195  		if !strings.Contains(a, "detach") {
   196  			args = append(args, a)
   197  		}
   198  	}
   199  
   200  	// The command needs to write a pid file so that execChildAndWait can tell
   201  	// when it has started. If no pid-file was provided, we should use a
   202  	// filename in a temp directory.
   203  	pidFile := ex.pidFile
   204  	if pidFile == "" {
   205  		tmpDir, err := ioutil.TempDir("", "exec-pid-")
   206  		if err != nil {
   207  			Fatalf("creating TempDir: %v", err)
   208  		}
   209  		defer os.RemoveAll(tmpDir)
   210  		pidFile = filepath.Join(tmpDir, "pid")
   211  		args = append(args, "--pid-file="+pidFile)
   212  	}
   213  
   214  	cmd := exec.Command(specutils.ExePath, args...)
   215  	cmd.Args[0] = "runsc-exec"
   216  
   217  	// Exec stdio defaults to current process stdio.
   218  	cmd.Stdin = os.Stdin
   219  	cmd.Stdout = os.Stdout
   220  	cmd.Stderr = os.Stderr
   221  
   222  	// If the console control socket file is provided, then create a new
   223  	// pty master/replica pair and set the TTY on the sandbox process.
   224  	if ex.consoleSocket != "" {
   225  		// Create a new TTY pair and send the master on the provided socket.
   226  		tty, err := console.NewWithSocket(ex.consoleSocket)
   227  		if err != nil {
   228  			Fatalf("setting up console with socket %q: %v", ex.consoleSocket, err)
   229  		}
   230  		defer tty.Close()
   231  
   232  		// Set stdio to the new TTY replica.
   233  		cmd.Stdin = tty
   234  		cmd.Stdout = tty
   235  		cmd.Stderr = tty
   236  		cmd.SysProcAttr = &unix.SysProcAttr{
   237  			Setsid:  true,
   238  			Setctty: true,
   239  			// The Ctty FD must be the FD in the child process's FD
   240  			// table. Since we set cmd.Stdin/Stdout/Stderr to the
   241  			// tty FD, we can use any of 0, 1, or 2 here.
   242  			// See https://github.com/golang/go/issues/29458.
   243  			Ctty: 0,
   244  		}
   245  	}
   246  
   247  	if err := cmd.Start(); err != nil {
   248  		Fatalf("failure to start child exec process, err: %v", err)
   249  	}
   250  
   251  	log.Infof("Started child (PID: %d) to exec and wait: %s %s", cmd.Process.Pid, specutils.ExePath, args)
   252  
   253  	// Wait for PID file to ensure that child process has started. Otherwise,
   254  	// '--process' file is deleted as soon as this process returns and the child
   255  	// may fail to read it.
   256  	ready := func() (bool, error) {
   257  		pidb, err := ioutil.ReadFile(pidFile)
   258  		if err == nil {
   259  			// File appeared, check whether pid is fully written.
   260  			pid, err := strconv.Atoi(string(pidb))
   261  			if err != nil {
   262  				return false, nil
   263  			}
   264  			return pid == cmd.Process.Pid, nil
   265  		}
   266  		if pe, ok := err.(*os.PathError); !ok || pe.Err != unix.ENOENT {
   267  			return false, err
   268  		}
   269  		// No file yet, continue to wait...
   270  		return false, nil
   271  	}
   272  	if err := specutils.WaitForReady(cmd.Process.Pid, 10*time.Second, ready); err != nil {
   273  		// Don't log fatal error here, otherwise it will override the error logged
   274  		// by the child process that has failed to start.
   275  		log.Warningf("Unexpected error waiting for PID file, err: %v", err)
   276  		return subcommands.ExitFailure
   277  	}
   278  
   279  	*waitStatus = 0
   280  	return subcommands.ExitSuccess
   281  }
   282  
   283  // parseArgs parses exec information from the command line or a JSON file
   284  // depending on whether the --process flag was used. Returns an ExecArgs and
   285  // the ID of the container to be used.
   286  func (ex *Exec) parseArgs(f *flag.FlagSet, enableRaw bool) (*control.ExecArgs, string, error) {
   287  	if ex.processPath == "" {
   288  		// Requires at least a container ID and command.
   289  		if f.NArg() < 2 {
   290  			f.Usage()
   291  			return nil, "", fmt.Errorf("both a container-id and command are required")
   292  		}
   293  		e, err := ex.argsFromCLI(f.Args()[1:], enableRaw)
   294  		return e, f.Arg(0), err
   295  	}
   296  	// Requires only the container ID.
   297  	if f.NArg() != 1 {
   298  		f.Usage()
   299  		return nil, "", fmt.Errorf("a container-id is required")
   300  	}
   301  	e, err := ex.argsFromProcessFile(enableRaw)
   302  	return e, f.Arg(0), err
   303  }
   304  
   305  func (ex *Exec) argsFromCLI(argv []string, enableRaw bool) (*control.ExecArgs, error) {
   306  	extraKGIDs := make([]auth.KGID, 0, len(ex.extraKGIDs))
   307  	for _, s := range ex.extraKGIDs {
   308  		kgid, err := strconv.Atoi(s)
   309  		if err != nil {
   310  			Fatalf("parsing GID: %s, %v", s, err)
   311  		}
   312  		extraKGIDs = append(extraKGIDs, auth.KGID(kgid))
   313  	}
   314  
   315  	var caps *auth.TaskCapabilities
   316  	if len(ex.caps) > 0 {
   317  		var err error
   318  		caps, err = capabilities(ex.caps, enableRaw)
   319  		if err != nil {
   320  			return nil, fmt.Errorf("capabilities error: %v", err)
   321  		}
   322  	}
   323  
   324  	return &control.ExecArgs{
   325  		Argv:             argv,
   326  		WorkingDirectory: ex.cwd,
   327  		KUID:             ex.user.kuid,
   328  		KGID:             ex.user.kgid,
   329  		ExtraKGIDs:       extraKGIDs,
   330  		Capabilities:     caps,
   331  		StdioIsPty:       ex.consoleSocket != "",
   332  		FilePayload:      urpc.FilePayload{[]*os.File{os.Stdin, os.Stdout, os.Stderr}},
   333  	}, nil
   334  }
   335  
   336  func (ex *Exec) argsFromProcessFile(enableRaw bool) (*control.ExecArgs, error) {
   337  	f, err := os.Open(ex.processPath)
   338  	if err != nil {
   339  		return nil, fmt.Errorf("error opening process file: %s, %v", ex.processPath, err)
   340  	}
   341  	defer f.Close()
   342  	var p specs.Process
   343  	if err := json.NewDecoder(f).Decode(&p); err != nil {
   344  		return nil, fmt.Errorf("error parsing process file: %s, %v", ex.processPath, err)
   345  	}
   346  	return argsFromProcess(&p, enableRaw)
   347  }
   348  
   349  // argsFromProcess performs all the non-IO conversion from the Process struct
   350  // to ExecArgs.
   351  func argsFromProcess(p *specs.Process, enableRaw bool) (*control.ExecArgs, error) {
   352  	// Create capabilities.
   353  	var caps *auth.TaskCapabilities
   354  	if p.Capabilities != nil {
   355  		var err error
   356  		// Starting from Docker 19, capabilities are explicitly set for exec (instead
   357  		// of nil like before). So we can't distinguish 'exec' from
   358  		// 'exec --privileged', as both specify CAP_NET_RAW. Therefore, filter
   359  		// CAP_NET_RAW in the same way as container start.
   360  		caps, err = specutils.Capabilities(enableRaw, p.Capabilities)
   361  		if err != nil {
   362  			return nil, fmt.Errorf("error creating capabilities: %v", err)
   363  		}
   364  	}
   365  
   366  	// Convert the spec's additional GIDs to KGIDs.
   367  	extraKGIDs := make([]auth.KGID, 0, len(p.User.AdditionalGids))
   368  	for _, GID := range p.User.AdditionalGids {
   369  		extraKGIDs = append(extraKGIDs, auth.KGID(GID))
   370  	}
   371  
   372  	return &control.ExecArgs{
   373  		Argv:             p.Args,
   374  		Envv:             p.Env,
   375  		WorkingDirectory: p.Cwd,
   376  		KUID:             auth.KUID(p.User.UID),
   377  		KGID:             auth.KGID(p.User.GID),
   378  		ExtraKGIDs:       extraKGIDs,
   379  		Capabilities:     caps,
   380  		StdioIsPty:       p.Terminal,
   381  		FilePayload:      urpc.FilePayload{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}},
   382  	}, nil
   383  }
   384  
   385  // capabilities takes a list of capabilities as strings and returns an
   386  // auth.TaskCapabilities struct with those capabilities in every capability set.
   387  // This mimics runc's behavior.
   388  func capabilities(cs []string, enableRaw bool) (*auth.TaskCapabilities, error) {
   389  	var specCaps specs.LinuxCapabilities
   390  	for _, cap := range cs {
   391  		specCaps.Ambient = append(specCaps.Ambient, cap)
   392  		specCaps.Bounding = append(specCaps.Bounding, cap)
   393  		specCaps.Effective = append(specCaps.Effective, cap)
   394  		specCaps.Inheritable = append(specCaps.Inheritable, cap)
   395  		specCaps.Permitted = append(specCaps.Permitted, cap)
   396  	}
   397  	// Starting from Docker 19, capabilities are explicitly set for exec (instead
   398  	// of nil like before). So we can't distinguish 'exec' from
   399  	// 'exec --privileged', as both specify CAP_NET_RAW. Therefore, filter
   400  	// CAP_NET_RAW in the same way as container start.
   401  	return specutils.Capabilities(enableRaw, &specCaps)
   402  }
   403  
   404  // stringSlice allows a flag to be used multiple times, where each occurrence
   405  // adds a value to the flag. For example, a flag called "x" could be invoked
   406  // via "runsc exec -x foo -x bar", and the corresponding stringSlice would be
   407  // {"x", "y"}.
   408  type stringSlice []string
   409  
   410  // String implements flag.Value.String.
   411  func (ss *stringSlice) String() string {
   412  	return fmt.Sprintf("%v", *ss)
   413  }
   414  
   415  // Get implements flag.Value.Get.
   416  func (ss *stringSlice) Get() interface{} {
   417  	return ss
   418  }
   419  
   420  // Set implements flag.Value.Set.
   421  func (ss *stringSlice) Set(s string) error {
   422  	*ss = append(*ss, s)
   423  	return nil
   424  }
   425  
   426  // user allows -user to convey a UID and, optionally, a GID separated by a
   427  // colon.
   428  type user struct {
   429  	kuid auth.KUID
   430  	kgid auth.KGID
   431  }
   432  
   433  func (u *user) String() string {
   434  	return fmt.Sprintf("%+v", *u)
   435  }
   436  
   437  func (u *user) Get() interface{} {
   438  	return u
   439  }
   440  
   441  func (u *user) Set(s string) error {
   442  	parts := strings.SplitN(s, ":", 2)
   443  	kuid, err := strconv.Atoi(parts[0])
   444  	if err != nil {
   445  		return fmt.Errorf("couldn't parse UID: %s", parts[0])
   446  	}
   447  	u.kuid = auth.KUID(kuid)
   448  	if len(parts) > 1 {
   449  		kgid, err := strconv.Atoi(parts[1])
   450  		if err != nil {
   451  			return fmt.Errorf("couldn't parse GID: %s", parts[1])
   452  		}
   453  		u.kgid = auth.KGID(kgid)
   454  	}
   455  	return nil
   456  }