github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/integration-cli/docker_cli_exec_test.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "os" 8 "os/exec" 9 "reflect" 10 "runtime" 11 "sort" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/docker/docker/client" 18 "github.com/docker/docker/integration-cli/cli" 19 "github.com/docker/docker/integration-cli/cli/build" 20 "gotest.tools/v3/assert" 21 is "gotest.tools/v3/assert/cmp" 22 "gotest.tools/v3/icmd" 23 ) 24 25 func (s *DockerSuite) TestExec(c *testing.T) { 26 testRequires(c, DaemonIsLinux) 27 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 28 assert.NilError(c, waitRun(strings.TrimSpace(out))) 29 30 out, _ = dockerCmd(c, "exec", "testing", "cat", "/tmp/file") 31 assert.Equal(c, strings.Trim(out, "\r\n"), "test") 32 } 33 34 func (s *DockerSuite) TestExecInteractive(c *testing.T) { 35 testRequires(c, DaemonIsLinux) 36 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") 37 38 execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") 39 stdin, err := execCmd.StdinPipe() 40 assert.NilError(c, err) 41 stdout, err := execCmd.StdoutPipe() 42 assert.NilError(c, err) 43 44 err = execCmd.Start() 45 assert.NilError(c, err) 46 _, err = stdin.Write([]byte("cat /tmp/file\n")) 47 assert.NilError(c, err) 48 49 r := bufio.NewReader(stdout) 50 line, err := r.ReadString('\n') 51 assert.NilError(c, err) 52 line = strings.TrimSpace(line) 53 assert.Equal(c, line, "test") 54 err = stdin.Close() 55 assert.NilError(c, err) 56 errChan := make(chan error, 1) 57 go func() { 58 errChan <- execCmd.Wait() 59 close(errChan) 60 }() 61 select { 62 case err := <-errChan: 63 assert.NilError(c, err) 64 case <-time.After(1 * time.Second): 65 c.Fatal("docker exec failed to exit on stdin close") 66 } 67 68 } 69 70 func (s *DockerSuite) TestExecAfterContainerRestart(c *testing.T) { 71 out := runSleepingContainer(c) 72 cleanedContainerID := strings.TrimSpace(out) 73 assert.NilError(c, waitRun(cleanedContainerID)) 74 dockerCmd(c, "restart", cleanedContainerID) 75 assert.NilError(c, waitRun(cleanedContainerID)) 76 77 out, _ = dockerCmd(c, "exec", cleanedContainerID, "echo", "hello") 78 assert.Equal(c, strings.TrimSpace(out), "hello") 79 } 80 81 func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *testing.T) { 82 // TODO Windows CI: DockerDaemonSuite doesn't run on Windows, and requires a little work to get this ported. 83 s.d.StartWithBusybox(c) 84 85 out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top") 86 assert.NilError(c, err, "Could not run top: %s", out) 87 88 s.d.Restart(c) 89 90 out, err = s.d.Cmd("start", "top") 91 assert.NilError(c, err, "Could not start top after daemon restart: %s", out) 92 93 out, err = s.d.Cmd("exec", "top", "echo", "hello") 94 assert.NilError(c, err, "Could not exec on container top: %s", out) 95 assert.Equal(c, strings.TrimSpace(out), "hello") 96 } 97 98 // Regression test for #9155, #9044 99 func (s *DockerSuite) TestExecEnv(c *testing.T) { 100 // TODO Windows CI: This one is interesting and may just end up being a feature 101 // difference between Windows and Linux. On Windows, the environment is passed 102 // into the process that is launched, not into the machine environment. Hence 103 // a subsequent exec will not have LALA set/ 104 testRequires(c, DaemonIsLinux) 105 runSleepingContainer(c, "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing") 106 assert.NilError(c, waitRun("testing")) 107 108 out, _ := dockerCmd(c, "exec", "testing", "env") 109 assert.Check(c, !strings.Contains(out, "LALA=value1")) 110 assert.Check(c, strings.Contains(out, "LALA=value2")) 111 assert.Check(c, strings.Contains(out, "HOME=/root")) 112 } 113 114 func (s *DockerSuite) TestExecSetEnv(c *testing.T) { 115 testRequires(c, DaemonIsLinux) 116 runSleepingContainer(c, "-e", "HOME=/root", "-d", "--name", "testing") 117 assert.NilError(c, waitRun("testing")) 118 119 out, _ := dockerCmd(c, "exec", "-e", "HOME=/another", "-e", "ABC=xyz", "testing", "env") 120 assert.Check(c, !strings.Contains(out, "HOME=/root")) 121 assert.Check(c, strings.Contains(out, "HOME=/another")) 122 assert.Check(c, strings.Contains(out, "ABC=xyz")) 123 } 124 125 func (s *DockerSuite) TestExecExitStatus(c *testing.T) { 126 runSleepingContainer(c, "-d", "--name", "top") 127 128 result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") 129 result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) 130 } 131 132 func (s *DockerSuite) TestExecPausedContainer(c *testing.T) { 133 testRequires(c, IsPausable) 134 135 out := runSleepingContainer(c, "-d", "--name", "testing") 136 ContainerID := strings.TrimSpace(out) 137 138 dockerCmd(c, "pause", "testing") 139 out, _, err := dockerCmdWithError("exec", ContainerID, "echo", "hello") 140 assert.ErrorContains(c, err, "", "container should fail to exec new command if it is paused") 141 142 expected := ContainerID + " is paused, unpause the container before exec" 143 assert.Assert(c, is.Contains(out, expected), "container should not exec new command if it is paused") 144 } 145 146 // regression test for #9476 147 func (s *DockerSuite) TestExecTTYCloseStdin(c *testing.T) { 148 // TODO Windows CI: This requires some work to port to Windows. 149 testRequires(c, DaemonIsLinux) 150 dockerCmd(c, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") 151 152 cmd := exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") 153 stdinRw, err := cmd.StdinPipe() 154 assert.NilError(c, err) 155 156 stdinRw.Write([]byte("test")) 157 stdinRw.Close() 158 159 out, _, err := runCommandWithOutput(cmd) 160 assert.NilError(c, err, out) 161 162 out, _ = dockerCmd(c, "top", "exec_tty_stdin") 163 outArr := strings.Split(out, "\n") 164 assert.Assert(c, len(outArr) <= 3, "exec process left running") 165 assert.Assert(c, !strings.Contains(out, "nsenter-exec")) 166 } 167 168 func (s *DockerSuite) TestExecTTYWithoutStdin(c *testing.T) { 169 out, _ := dockerCmd(c, "run", "-d", "-ti", "busybox") 170 id := strings.TrimSpace(out) 171 assert.NilError(c, waitRun(id)) 172 173 errChan := make(chan error, 1) 174 go func() { 175 defer close(errChan) 176 177 cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") 178 if _, err := cmd.StdinPipe(); err != nil { 179 errChan <- err 180 return 181 } 182 183 expected := "the input device is not a TTY" 184 if "linux" == "windows" { 185 expected += ". If you are using mintty, try prefixing the command with 'winpty'" 186 } 187 if out, _, err := runCommandWithOutput(cmd); err == nil { 188 errChan <- fmt.Errorf("exec should have failed") 189 return 190 } else if !strings.Contains(out, expected) { 191 errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) 192 return 193 } 194 }() 195 196 select { 197 case err := <-errChan: 198 assert.NilError(c, err) 199 case <-time.After(3 * time.Second): 200 c.Fatal("exec is running but should have failed") 201 } 202 } 203 204 // FIXME(vdemeester) this should be a unit tests on cli/command/container package 205 func (s *DockerSuite) TestExecParseError(c *testing.T) { 206 // TODO Windows CI: Requires some extra work. Consider copying the 207 // runSleepingContainer helper to have an exec version. 208 testRequires(c, DaemonIsLinux) 209 dockerCmd(c, "run", "-d", "--name", "top", "busybox", "top") 210 211 // Test normal (non-detached) case first 212 icmd.RunCommand(dockerBinary, "exec", "top").Assert(c, icmd.Expected{ 213 ExitCode: 1, 214 Error: "exit status 1", 215 Err: "See 'docker exec --help'", 216 }) 217 } 218 219 func (s *DockerSuite) TestExecStopNotHanging(c *testing.T) { 220 // TODO Windows CI: Requires some extra work. Consider copying the 221 // runSleepingContainer helper to have an exec version. 222 testRequires(c, DaemonIsLinux) 223 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 224 225 result := icmd.StartCmd(icmd.Command(dockerBinary, "exec", "testing", "top")) 226 result.Assert(c, icmd.Success) 227 go icmd.WaitOnCmd(0, result) 228 229 type dstop struct { 230 out string 231 err error 232 } 233 ch := make(chan dstop, 1) 234 go func() { 235 result := icmd.RunCommand(dockerBinary, "stop", "testing") 236 ch <- dstop{result.Combined(), result.Error} 237 close(ch) 238 }() 239 select { 240 case <-time.After(3 * time.Second): 241 c.Fatal("Container stop timed out") 242 case s := <-ch: 243 assert.NilError(c, s.err) 244 } 245 } 246 247 func (s *DockerSuite) TestExecCgroup(c *testing.T) { 248 // Not applicable on Windows - using Linux specific functionality 249 testRequires(c, NotUserNamespace) 250 testRequires(c, DaemonIsLinux) 251 dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") 252 253 out, _ := dockerCmd(c, "exec", "testing", "cat", "/proc/1/cgroup") 254 containerCgroups := sort.StringSlice(strings.Split(out, "\n")) 255 256 var wg sync.WaitGroup 257 var mu sync.Mutex 258 var execCgroups []sort.StringSlice 259 errChan := make(chan error, 5) 260 // exec a few times concurrently to get consistent failure 261 for i := 0; i < 5; i++ { 262 wg.Add(1) 263 go func() { 264 defer wg.Done() 265 out, _, err := dockerCmdWithError("exec", "testing", "cat", "/proc/self/cgroup") 266 if err != nil { 267 errChan <- err 268 return 269 } 270 cg := sort.StringSlice(strings.Split(out, "\n")) 271 272 mu.Lock() 273 execCgroups = append(execCgroups, cg) 274 mu.Unlock() 275 }() 276 } 277 wg.Wait() 278 close(errChan) 279 280 for err := range errChan { 281 assert.NilError(c, err) 282 } 283 284 for _, cg := range execCgroups { 285 if !reflect.DeepEqual(cg, containerCgroups) { 286 fmt.Println("exec cgroups:") 287 for _, name := range cg { 288 fmt.Printf(" %s\n", name) 289 } 290 291 fmt.Println("container cgroups:") 292 for _, name := range containerCgroups { 293 fmt.Printf(" %s\n", name) 294 } 295 c.Fatal("cgroups mismatched") 296 } 297 } 298 } 299 300 func (s *DockerSuite) TestExecInspectID(c *testing.T) { 301 out := runSleepingContainer(c, "-d") 302 id := strings.TrimSuffix(out, "\n") 303 304 out = inspectField(c, id, "ExecIDs") 305 assert.Equal(c, out, "[]", "ExecIDs should be empty, got: %s", out) 306 307 // Start an exec, have it block waiting so we can do some checking 308 cmd := exec.Command(dockerBinary, "exec", id, "sh", "-c", 309 "while ! test -e /execid1; do sleep 1; done") 310 311 err := cmd.Start() 312 assert.NilError(c, err, "failed to start the exec cmd") 313 314 // Give the exec 10 chances/seconds to start then give up and stop the test 315 tries := 10 316 for i := 0; i < tries; i++ { 317 // Since its still running we should see exec as part of the container 318 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 319 320 if out != "[]" && out != "<no value>" { 321 break 322 } 323 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 324 time.Sleep(1 * time.Second) 325 } 326 327 // Save execID for later 328 execID, err := inspectFilter(id, "index .ExecIDs 0") 329 assert.NilError(c, err, "failed to get the exec id") 330 331 // End the exec by creating the missing file 332 err = exec.Command(dockerBinary, "exec", id, "sh", "-c", "touch /execid1").Run() 333 assert.NilError(c, err, "failed to run the 2nd exec cmd") 334 335 // Wait for 1st exec to complete 336 cmd.Wait() 337 338 // Give the exec 10 chances/seconds to stop then give up and stop the test 339 for i := 0; i < tries; i++ { 340 // Since its still running we should see exec as part of the container 341 out = strings.TrimSpace(inspectField(c, id, "ExecIDs")) 342 343 if out == "[]" { 344 break 345 } 346 assert.Check(c, i+1 != tries, "ExecIDs still empty after 10 second") 347 time.Sleep(1 * time.Second) 348 } 349 350 // But we should still be able to query the execID 351 cli, err := client.NewClientWithOpts(client.FromEnv) 352 assert.NilError(c, err) 353 defer cli.Close() 354 355 _, err = cli.ContainerExecInspect(context.Background(), execID) 356 assert.NilError(c, err) 357 358 // Now delete the container and then an 'inspect' on the exec should 359 // result in a 404 (not 'container not running') 360 out, ec := dockerCmd(c, "rm", "-f", id) 361 assert.Equal(c, ec, 0, "error removing container: %s", out) 362 363 _, err = cli.ContainerExecInspect(context.Background(), execID) 364 assert.ErrorContains(c, err, "No such exec instance") 365 } 366 367 func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *testing.T) { 368 // Problematic on Windows as Windows does not support links 369 testRequires(c, DaemonIsLinux) 370 var out string 371 out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") 372 idA := strings.TrimSpace(out) 373 assert.Assert(c, idA != "", "%s, id should not be nil", out) 374 out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") 375 idB := strings.TrimSpace(out) 376 assert.Assert(c, idB != "", "%s, id should not be nil", out) 377 378 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 379 dockerCmd(c, "rename", "container1", "container_new") 380 dockerCmd(c, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") 381 } 382 383 func (s *DockerSuite) TestRunMutableNetworkFiles(c *testing.T) { 384 // Not applicable on Windows to Windows CI. 385 testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux) 386 for _, fn := range []string{"resolv.conf", "hosts"} { 387 containers := cli.DockerCmd(c, "ps", "-q", "-a").Combined() 388 if containers != "" { 389 cli.DockerCmd(c, append([]string{"rm", "-fv"}, strings.Split(strings.TrimSpace(containers), "\n")...)...) 390 } 391 392 content := runCommandAndReadContainerFile(c, fn, dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn)) 393 394 assert.Equal(c, strings.TrimSpace(string(content)), "success", "Content was not what was modified in the container", string(content)) 395 396 out, _ := dockerCmd(c, "run", "-d", "--name", "c2", "busybox", "top") 397 contID := strings.TrimSpace(out) 398 netFilePath := containerStorageFile(contID, fn) 399 400 f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) 401 assert.NilError(c, err) 402 403 if _, err := f.Seek(0, 0); err != nil { 404 f.Close() 405 c.Fatal(err) 406 } 407 408 if err := f.Truncate(0); err != nil { 409 f.Close() 410 c.Fatal(err) 411 } 412 413 if _, err := f.Write([]byte("success2\n")); err != nil { 414 f.Close() 415 c.Fatal(err) 416 } 417 f.Close() 418 419 res, _ := dockerCmd(c, "exec", contID, "cat", "/etc/"+fn) 420 assert.Equal(c, res, "success2\n") 421 } 422 } 423 424 func (s *DockerSuite) TestExecWithUser(c *testing.T) { 425 // TODO Windows CI: This may be fixable in the future once Windows 426 // supports users 427 testRequires(c, DaemonIsLinux) 428 dockerCmd(c, "run", "-d", "--name", "parent", "busybox", "top") 429 430 out, _ := dockerCmd(c, "exec", "-u", "1", "parent", "id") 431 assert.Assert(c, strings.Contains(out, "uid=1(daemon) gid=1(daemon)")) 432 433 out, _ = dockerCmd(c, "exec", "-u", "root", "parent", "id") 434 assert.Assert(c, strings.Contains(out, "uid=0(root) gid=0(root)"), "exec with user by id expected daemon user got %s", out) 435 } 436 437 func (s *DockerSuite) TestExecWithPrivileged(c *testing.T) { 438 // Not applicable on Windows 439 testRequires(c, DaemonIsLinux, NotUserNamespace) 440 // Start main loop which attempts mknod repeatedly 441 dockerCmd(c, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "sh", "-c", `while (true); do if [ -e /exec_priv ]; then cat /exec_priv && mknod /tmp/sda b 8 0 && echo "Success"; else echo "Privileged exec has not run yet"; fi; usleep 10000; done`) 442 443 // Check exec mknod doesn't work 444 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdb b 8 16").Assert(c, icmd.Expected{ 445 ExitCode: 1, 446 Err: "Operation not permitted", 447 }) 448 449 // Check exec mknod does work with --privileged 450 result := icmd.RunCommand(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", `echo "Running exec --privileged" > /exec_priv && mknod /tmp/sdb b 8 16 && usleep 50000 && echo "Finished exec --privileged" > /exec_priv && echo ok`) 451 result.Assert(c, icmd.Success) 452 453 actual := strings.TrimSpace(result.Combined()) 454 assert.Equal(c, actual, "ok", "exec mknod in --cap-drop=ALL container with --privileged failed, output: %q", result.Combined()) 455 456 // Check subsequent unprivileged exec cannot mknod 457 icmd.RunCommand(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sdc b 8 32").Assert(c, icmd.Expected{ 458 ExitCode: 1, 459 Err: "Operation not permitted", 460 }) 461 // Confirm at no point was mknod allowed 462 result = icmd.RunCommand(dockerBinary, "logs", "parent") 463 result.Assert(c, icmd.Success) 464 assert.Assert(c, !strings.Contains(result.Combined(), "Success")) 465 } 466 467 func (s *DockerSuite) TestExecWithImageUser(c *testing.T) { 468 // Not applicable on Windows 469 testRequires(c, DaemonIsLinux) 470 name := "testbuilduser" 471 buildImageSuccessfully(c, name, build.WithDockerfile(`FROM busybox 472 RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd 473 USER dockerio`)) 474 dockerCmd(c, "run", "-d", "--name", "dockerioexec", name, "top") 475 476 out, _ := dockerCmd(c, "exec", "dockerioexec", "whoami") 477 assert.Assert(c, strings.Contains(out, "dockerio"), "exec with user by id expected dockerio user got %s", out) 478 } 479 480 func (s *DockerSuite) TestExecOnReadonlyContainer(c *testing.T) { 481 // Windows does not support read-only 482 // --read-only + userns has remount issues 483 testRequires(c, DaemonIsLinux, NotUserNamespace) 484 dockerCmd(c, "run", "-d", "--read-only", "--name", "parent", "busybox", "top") 485 dockerCmd(c, "exec", "parent", "true") 486 } 487 488 func (s *DockerSuite) TestExecUlimits(c *testing.T) { 489 testRequires(c, DaemonIsLinux) 490 name := "testexeculimits" 491 runSleepingContainer(c, "-d", "--ulimit", "nofile=511:511", "--name", name) 492 assert.NilError(c, waitRun(name)) 493 494 out, _, err := dockerCmdWithError("exec", name, "sh", "-c", "ulimit -n") 495 assert.NilError(c, err) 496 assert.Equal(c, strings.TrimSpace(out), "511") 497 } 498 499 // #15750 500 func (s *DockerSuite) TestExecStartFails(c *testing.T) { 501 // TODO Windows CI. This test should be portable. Figure out why it fails 502 // currently. 503 testRequires(c, DaemonIsLinux) 504 name := "exec-15750" 505 runSleepingContainer(c, "-d", "--name", name) 506 assert.NilError(c, waitRun(name)) 507 508 out, _, err := dockerCmdWithError("exec", name, "no-such-cmd") 509 assert.ErrorContains(c, err, "", out) 510 assert.Assert(c, strings.Contains(out, "executable file not found")) 511 } 512 513 // Fix regression in https://github.com/docker/docker/pull/26461#issuecomment-250287297 514 func (s *DockerSuite) TestExecWindowsPathNotWiped(c *testing.T) { 515 testRequires(c, DaemonIsWindows) 516 out, _ := dockerCmd(c, "run", "-d", "--name", "testing", minimalBaseImage(), "powershell", "start-sleep", "60") 517 assert.NilError(c, waitRun(strings.TrimSpace(out))) 518 519 out, _ = dockerCmd(c, "exec", "testing", "powershell", "write-host", "$env:PATH") 520 out = strings.ToLower(strings.Trim(out, "\r\n")) 521 assert.Assert(c, strings.Contains(out, `windowspowershell\v1.0`)) 522 } 523 524 func (s *DockerSuite) TestExecEnvLinksHost(c *testing.T) { 525 testRequires(c, DaemonIsLinux) 526 runSleepingContainer(c, "-d", "--name", "foo") 527 runSleepingContainer(c, "-d", "--link", "foo:db", "--hostname", "myhost", "--name", "bar") 528 out, _ := dockerCmd(c, "exec", "bar", "env") 529 assert.Check(c, is.Contains(out, "HOSTNAME=myhost")) 530 assert.Check(c, is.Contains(out, "DB_NAME=/bar/db")) 531 }