From dd696235a4b0bb668ad99e3cfdf08e53500bc851 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 17 Aug 2021 16:54:04 -0700 Subject: [PATCH] runc exec: reject paused container unless --ignore-paused Currently, if a container is paused (i.e. its cgroup is frozen), runc exec just hangs, and it is not obvious why. Refuse to exec in a paused container. Add a test case. In case runc exec in a paused container is a legit use case, add --ignore-paused option to override the check. Document it, add a test case. Signed-off-by: Kir Kolyshkin --- contrib/completions/bash/runc | 1 + exec.go | 9 ++++++- man/runc-exec.8.md | 5 ++++ tests/integration/cgroups.bats | 44 ++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/contrib/completions/bash/runc b/contrib/completions/bash/runc index 403637df8..a4cd89937 100644 --- a/contrib/completions/bash/runc +++ b/contrib/completions/bash/runc @@ -173,6 +173,7 @@ _runc_exec() { --apparmor --cap, -c --preserve-fds + --ignore-paused " local all_options="$options_with_args $boolean_options" diff --git a/exec.go b/exec.go index 0d3670551..18c6bffde 100644 --- a/exec.go +++ b/exec.go @@ -91,6 +91,10 @@ following will output a list of processes running in the container: Name: "cgroup", Usage: "run the process in an (existing) sub-cgroup(s). Format is [:].", }, + cli.BoolFlag{ + Name: "ignore-paused", + Usage: "allow exec in a paused container", + }, }, Action: func(context *cli.Context) error { if err := checkArgs(context, 1, minArgs); err != nil { @@ -145,7 +149,10 @@ func execProcess(context *cli.Context) (int, error) { return -1, err } if status == libcontainer.Stopped { - return -1, errors.New("cannot exec a container that has stopped") + return -1, errors.New("cannot exec in a stopped container") + } + if status == libcontainer.Paused && !context.Bool("ignore-paused") { + return -1, errors.New("cannot exec in a paused container (use --ignore-paused to override)") } path := context.String("process") if path == "" && len(context.Args()) == 1 { diff --git a/man/runc-exec.8.md b/man/runc-exec.8.md index 24e9ff3c9..c0ca41110 100644 --- a/man/runc-exec.8.md +++ b/man/runc-exec.8.md @@ -59,6 +59,11 @@ multiple times. : Pass _N_ additional file descriptors to the container (**stdio** + **$LISTEN_FDS** + _N_ in total). Default is **0**. +**--ignore-paused** +: Allow exec in a paused container. By default, if a container is paused, +**runc exec** errors out; this option can be used to override it. +A paused container needs to be resumed for the exec to complete. + **--cgroup** _path_ | _controller_[,_controller_...]:_path_ : Execute a process in a sub-cgroup. If the specified cgroup does not exist, an error is returned. Default is empty path, which means to use container's top diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats index 882e59296..ded7b1260 100644 --- a/tests/integration/cgroups.bats +++ b/tests/integration/cgroups.bats @@ -303,3 +303,47 @@ function setup() { # check that the cgroups v2 path is the same for both processes [[ "$run_cgroup" == "$exec_cgroup" ]] } + +@test "runc exec should refuse a paused container" { + if [[ "$ROOTLESS" -ne 0 ]]; then + requires rootless_cgroup + fi + requires cgroups_freezer + + set_cgroups_path + + runc run -d --console-socket "$CONSOLE_SOCKET" ct1 + [ "$status" -eq 0 ] + runc pause ct1 + [ "$status" -eq 0 ] + + # Exec should not timeout or succeed. + runc exec ct1 echo ok + [ "$status" -eq 255 ] + [[ "$output" == *"cannot exec in a paused container"* ]] +} + +@test "runc exec --ignore-paused" { + if [[ "$ROOTLESS" -ne 0 ]]; then + requires rootless_cgroup + fi + requires cgroups_freezer + + set_cgroups_path + + runc run -d --console-socket "$CONSOLE_SOCKET" ct1 + [ "$status" -eq 0 ] + runc pause ct1 + [ "$status" -eq 0 ] + + # Resume the container a bit later. + ( + sleep 2 + runc resume ct1 + ) & + + # Exec should not timeout or succeed. + runc exec --ignore-paused ct1 echo ok + [ "$status" -eq 0 ] + [ "$output" = "ok" ] +}