diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index 6c4a65c73..2e19ed601 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -160,16 +160,37 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err } else if !os.IsNotExist(err) { return nil, err } + + cm, err := manager.New(config.Cgroups) + if err != nil { + return nil, err + } + + // Check that cgroup does not exist or empty (no processes). + // Note for cgroup v1 this check is not thorough, as there are multiple + // separate hierarchies, while both Exists() and GetAllPids() only use + // one for "devices" controller (assuming others are the same, which is + // probably true in almost all scenarios). Checking all the hierarchies + // would be too expensive. + if cm.Exists() { + pids, err := cm.GetAllPids() + // Reading PIDs can race with cgroups removal, so ignore ENOENT and ENODEV. + if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, unix.ENODEV) { + return nil, fmt.Errorf("unable to get cgroup PIDs: %w", err) + } + if len(pids) != 0 { + // TODO: return an error. + logrus.Warnf("container's cgroup is not empty: %d process(es) found", len(pids)) + logrus.Warn("DEPRECATED: running container in a non-empty cgroup won't be supported in runc 1.2; https://github.com/opencontainers/runc/issues/3132") + } + } + if err := os.MkdirAll(containerRoot, 0o711); err != nil { return nil, err } if err := os.Chown(containerRoot, unix.Geteuid(), unix.Getegid()); err != nil { return nil, err } - cm, err := manager.New(config.Cgroups) - if err != nil { - return nil, err - } c := &linuxContainer{ id: id, root: containerRoot, diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats index ded7b1260..feddb0b4e 100644 --- a/tests/integration/cgroups.bats +++ b/tests/integration/cgroups.bats @@ -347,3 +347,24 @@ function setup() { [ "$status" -eq 0 ] [ "$output" = "ok" ] } + +@test "runc run/create should warn about a non-empty cgroup" { + if [[ "$ROOTLESS" -ne 0 ]]; then + requires rootless_cgroup + fi + + set_cgroups_path + + runc run -d --console-socket "$CONSOLE_SOCKET" ct1 + [ "$status" -eq 0 ] + + # Run a second container sharing the cgroup with the first one. + runc --debug run -d --console-socket "$CONSOLE_SOCKET" ct2 + [ "$status" -eq 0 ] + [[ "$output" == *"container's cgroup is not empty"* ]] + + # Same but using runc create. + runc create --console-socket "$CONSOLE_SOCKET" ct3 + [ "$status" -eq 0 ] + [[ "$output" == *"container's cgroup is not empty"* ]] +}