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 }