Repository: opencontainers/runc
Pull request #3030 (2021-06-15 03:20:29)

libct/cg/OpenFile: fix/improve openat2 handling
1. While looking into https://github.com/opencontainers/runc/issues/3026, I started writing a test case and found out that 
    `OpenFile("/sys/fs/cgroup", name)` falls back to using open and fstatfs.
    This is caused by a peculiarities of its `dir` and `name` arguments handling,
    and was never discovered before because of the fallback.

    Fix the bug, add the test cases.

2. Improve the error message for case like in the issue #3026, based on @cyphar suggestion.

See commit messages for more details.

kolyshkin: No need to backport it to 1.0 -- this is a bug, but the fallback works so the impact is pretty low 
kolyshkin: This is a real bug fix, can @opencontainers/runc-maintainers PTAL 🙏🏻 ?

Patch:
diff --git a/go.mod b/go.mod
index 4fc8d5a1..32748263 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
 	github.com/moby/sys/mountinfo v0.4.1
 	github.com/mrunalp/fileutils v0.5.0
 	github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
-	github.com/opencontainers/selinux v1.8.4
+	github.com/opencontainers/selinux v1.8.3
 	github.com/seccomp/libseccomp-golang v0.9.1
 	github.com/sirupsen/logrus v1.8.1
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
diff --git a/go.sum b/go.sum
index 97250392..19078580 100644
--- a/go.sum
+++ b/go.sum
@@ -46,8 +46,8 @@ github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/selinux v1.8.4 h1:krlgQ6/j9CkCXT5oW0yVXdQFOME3NjKuuAZXuR6O7P4=
-github.com/opencontainers/selinux v1.8.4/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo=
+github.com/opencontainers/selinux v1.8.3 h1:tzZR7AuKB5gU1+53uBkoG4XdIFGZzvJTOVoNbRQI8/4=
+github.com/opencontainers/selinux v1.8.3/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
diff --git a/libcontainer/cgroups/file.go b/libcontainer/cgroups/file.go
index b1888f7e..0cdaf747 100644
--- a/libcontainer/cgroups/file.go
+++ b/libcontainer/cgroups/file.go
@@ -5,6 +5,8 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -13,7 +15,11 @@ import (
 )
 
 // OpenFile opens a cgroup file in a given dir with given flags.
-// It is supposed to be used for cgroup files only.
+// It is supposed to be used for cgroup files only, and returns
+// an error if the file is not a cgroup file.
+//
+// Arguments dir and file are joined together to form an absolute path
+// to a file being opened.
 func OpenFile(dir, file string, flags int) (*os.File, error) {
 	if dir == "" {
 		return nil, fmt.Errorf("no directory specified for %s", file)
@@ -109,8 +115,6 @@ func prepareOpenat2() error {
 	return prepErr
 }
 
-// OpenFile opens a cgroup file in a given dir with given flags.
-// It is supposed to be used for cgroup files only.
 func openFile(dir, file string, flags int) (*os.File, error) {
 	mode := os.FileMode(0)
 	if TestMode && flags&os.O_WRONLY != 0 {
@@ -118,34 +122,52 @@ func openFile(dir, file string, flags int) (*os.File, error) {
 		flags |= os.O_TRUNC | os.O_CREATE
 		mode = 0o600
 	}
+	path := path.Join(dir, file)
 	if prepareOpenat2() != nil {
-		return openFallback(dir, file, flags, mode)
+		return openFallback(path, flags, mode)
 	}
-	reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
-	if len(reldir) == len(dir) { // non-standard path, old system?
-		return openFallback(dir, file, flags, mode)
+	relPath := strings.TrimPrefix(path, cgroupfsPrefix)
+	if len(relPath) == len(path) { // non-standard path, old system?
+		return openFallback(path, flags, mode)
 	}
 
-	relname := reldir + "/" + file
-	fd, err := unix.Openat2(cgroupFd, relname,
+	fd, err := unix.Openat2(cgroupFd, relPath,
 		&unix.OpenHow{
 			Resolve: resolveFlags,
 			Flags:   uint64(flags) | unix.O_CLOEXEC,
 			Mode:    uint64(mode),
 		})
 	if err != nil {
-		return nil, &os.PathError{Op: "openat2", Path: dir + "/" + file, Err: err}
+		err = &os.PathError{Op: "openat2", Path: path, Err: err}
+		// Check if cgroupFd is still opened to cgroupfsDir
+		// (happens when this package is incorrectly used
+		// across the chroot/pivot_root/mntns boundary, or
+		// when /sys/fs/cgroup is remounted).
+		//
+		// TODO: if such usage will ever be common, amend this
+		// to reopen cgroupFd and retry openat2.
+		fdStr := strconv.Itoa(cgroupFd)
+		fdDest, _ := os.Readlink("/proc/self/fd/" + fdStr)
+		if fdDest != cgroupfsDir {
+			// Wrap the error so it is clear that cgroupFd
+			// is opened to an unexpected/wrong directory.
+			err = fmt.Errorf("cgroupFd %s unexpectedly opened to %s != %s: %w",
+				fdStr, fdDest, cgroupfsDir, err)
+		}
+		return nil, err
 	}
 
-	return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil
+	return os.NewFile(uintptr(fd), path), nil
 }
 
 var errNotCgroupfs = errors.New("not a cgroup file")
 
-// openFallback is used when openat2(2) is not available. It checks the opened
+// Can be changed by unit tests.
+var openFallback = openAndCheck
+
+// openAndCheck is used when openat2(2) is not available. It checks the opened
 // file is on cgroupfs, returning an error otherwise.
-func openFallback(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
-	path := dir + "/" + file
+func openAndCheck(path string, flags int, mode os.FileMode) (*os.File, error) {
 	fd, err := os.OpenFile(path, flags, mode)
 	if err != nil {
 		return nil, err
diff --git a/libcontainer/cgroups/getallpids.go b/libcontainer/cgroups/getallpids.go
deleted file mode 100644
index b218744e..00000000
--- a/libcontainer/cgroups/getallpids.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// +build linux,go1.16
-
-package cgroups
-
-import (
-	"io/fs"
-	"path/filepath"
-)
-
-// GetAllPids returns all pids from the cgroup identified by path, and all its
-// sub-cgroups.
-func GetAllPids(path string) ([]int, error) {
-	var pids []int
-	err := filepath.WalkDir(path, func(p string, d fs.DirEntry, iErr error) error {
-		if iErr != nil {
-			return iErr
-		}
-		if !d.IsDir() {
-			return nil
-		}
-		cPids, err := readProcsFile(p)
-		if err != nil {
-			return err
-		}
-		pids = append(pids, cPids...)
-		return nil
-	})
-	return pids, err
-}
diff --git a/libcontainer/cgroups/getallpids_go115.go b/libcontainer/cgroups/getallpids_go115.go
deleted file mode 100644
index 5d55a685..00000000
--- a/libcontainer/cgroups/getallpids_go115.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// +build linux,!go1.16
-
-package cgroups
-
-import (
-	"os"
-	"path/filepath"
-)
-
-// GetAllPids returns all pids, that were added to cgroup at path and to all its
-// subcgroups.
-func GetAllPids(path string) ([]int, error) {
-	var pids []int
-	// collect pids from all sub-cgroups
-	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
-		if iErr != nil {
-			return iErr
-		}
-		if !info.IsDir() {
-			return nil
-		}
-		cPids, err := readProcsFile(p)
-		if err != nil {
-			return err
-		}
-		pids = append(pids, cPids...)
-		return nil
-	})
-	return pids, err
-}
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index b31705be..75015d66 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -118,8 +118,8 @@ func GetAllSubsystems() ([]string, error) {
 	return subsystems, nil
 }
 
-func readProcsFile(dir string) ([]int, error) {
-	f, err := OpenFile(dir, CgroupProcesses, os.O_RDONLY)
+func readProcsFile(file string) ([]int, error) {
+	f, err := os.Open(file)
 	if err != nil {
 		return nil, err
 	}
@@ -336,7 +336,29 @@ func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
 
 // GetPids returns all pids, that were added to cgroup at path.
 func GetPids(dir string) ([]int, error) {
-	return readProcsFile(dir)
+	return readProcsFile(filepath.Join(dir, CgroupProcesses))
+}
+
+// GetAllPids returns all pids, that were added to cgroup at path and to all its
+// subcgroups.
+func GetAllPids(path string) ([]int, error) {
+	var pids []int
+	// collect pids from all sub-cgroups
+	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
+		if iErr != nil {
+			return iErr
+		}
+		if info.IsDir() || info.Name() != CgroupProcesses {
+			return nil
+		}
+		cPids, err := readProcsFile(p)
+		if err != nil {
+			return err
+		}
+		pids = append(pids, cPids...)
+		return nil
+	})
+	return pids, err
 }
 
 // WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file
diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c
index ee7b75b1..49e8f54b 100644
--- a/libcontainer/nsenter/nsexec.c
+++ b/libcontainer/nsenter/nsexec.c
@@ -136,7 +136,7 @@ int setns(int fd, int nstype)
 
 static void write_log(const char *level, const char *format, ...)
 {
-	char *message = NULL, *stage = NULL, *json = NULL;
+	char *message = NULL, *stage = NULL;
 	va_list args;
 	int ret;
 
@@ -158,18 +158,11 @@ static void write_log(const char *level, const char *format, ...)
 	if (ret < 0)
 		goto out;
 
-	ret = asprintf(&json, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", level, stage, getpid(), message);
-	if (ret < 0) {
-		json = NULL;
-		goto out;
-	}
-
-	write(logfd, json, ret);
+	dprintf(logfd, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", level, stage, getpid(), message);
 
 out:
 	free(message);
 	free(stage);
-	free(json);
 }
 
 /* XXX: This is ugly. */
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go b/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go
deleted file mode 100644
index 897ecbac..00000000
--- a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// +build linux,go1.16
-
-package selinux
-
-import (
-	"errors"
-	"io/fs"
-	"os"
-
-	"github.com/opencontainers/selinux/pkg/pwalkdir"
-)
-
-func rchcon(fpath, label string) error {
-	return pwalkdir.Walk(fpath, func(p string, _ fs.DirEntry, _ error) error {
-		e := setFileLabel(p, label)
-		// Walk a file tree can race with removal, so ignore ENOENT.
-		if errors.Is(e, os.ErrNotExist) {
-			return nil
-		}
-		return e
-	})
-}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go b/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go
deleted file mode 100644
index 2c8b033c..00000000
--- a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build linux,!go1.16
-
-package selinux
-
-import (
-	"errors"
-	"os"
-
-	"github.com/opencontainers/selinux/pkg/pwalk"
-)
-
-func rchcon(fpath, label string) error {
-	return pwalk.Walk(fpath, func(p string, _ os.FileInfo, _ error) error {
-		e := setFileLabel(p, label)
-		// Walk a file tree can race with removal, so ignore ENOENT.
-		if errors.Is(e, os.ErrNotExist) {
-			return nil
-		}
-		return e
-	})
-}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
index a804473e..62df82a3 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
@@ -18,6 +18,7 @@ import (
 	"sync"
 
 	"github.com/bits-and-blooms/bitset"
+	"github.com/opencontainers/selinux/pkg/pwalk"
 	"golang.org/x/sys/unix"
 )
 
@@ -1047,10 +1048,17 @@ func chcon(fpath string, label string, recurse bool) error {
 	}
 
 	if !recurse {
-		return setFileLabel(fpath, label)
+		return SetFileLabel(fpath, label)
 	}
 
-	return rchcon(fpath, label)
+	return pwalk.Walk(fpath, func(p string, info os.FileInfo, err error) error {
+		e := SetFileLabel(p, label)
+		// Walk a file tree can race with removal, so ignore ENOENT
+		if errors.Is(e, os.ErrNotExist) {
+			return nil
+		}
+		return e
+	})
 }
 
 // dupSecOpt takes an SELinux process label and returns security options that
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md b/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
index 7e78dce0..16c4dfd3 100644
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
+++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
@@ -8,12 +8,6 @@ By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
 This can be changed by using WalkN function which has the additional
 parameter, specifying the number of goroutines (concurrency).
 
-### pwalk vs pwalkdir
-
-This package is deprecated in favor of
-[pwalkdir](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
-which is faster, but requires at least Go 1.16.
-
 ### Caveats
 
 Please note the following limitations of this code:
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
index 011fe862..a8088a19 100644
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
+++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
@@ -19,7 +19,7 @@ type WalkFunc = filepath.WalkFunc
 //
 // Note that this implementation only supports primitive error handling:
 //
-// - no errors are ever passed to walkFn;
+// - no errors are ever passed to WalkFn;
 //
 // - once a walkFn returns any error, all further processing stops
 // and the error is returned to the caller of Walk;
@@ -95,7 +95,7 @@ func WalkN(root string, walkFn WalkFunc, num int) error {
 	return err
 }
 
-// walkArgs holds the arguments that were passed to the Walk or WalkN
+// walkArgs holds the arguments that were passed to the Walk or WalkLimit
 // functions.
 type walkArgs struct {
 	path string
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md
deleted file mode 100644
index 068ac400..00000000
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-## pwalkdir: parallel implementation of filepath.WalkDir
-
-This is a wrapper for [filepath.WalkDir](https://pkg.go.dev/path/filepath#WalkDir)
-which may speed it up by calling multiple callback functions (WalkDirFunc)
-in parallel, utilizing goroutines.
-
-By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
-This can be changed by using WalkN function which has the additional
-parameter, specifying the number of goroutines (concurrency).
-
-### pwalk vs pwalkdir
-
-This package is very similar to
-[pwalk](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
-but utilizes `filepath.WalkDir` (added to Go 1.16), which does not call stat(2)
-on every entry and is therefore faster (up to 3x, depending on usage scenario).
-
-Users who are OK with requiring Go 1.16+ should switch to this
-implementation.
-
-### Caveats
-
-Please note the following limitations of this code:
-
-* Unlike filepath.WalkDir, the order of calls is non-deterministic;
-
-* Only primitive error handling is supported:
-
-  * fs.SkipDir is not supported;
-
-  * no errors are ever passed to WalkDirFunc;
-
-  * once any error is returned from any walkDirFunc instance, no more calls
-    to WalkDirFunc are made, and the error is returned to the caller of WalkDir;
-
-  * if more than one WalkDirFunc instance will return an error, only one
-    of such errors will be propagated to and returned by WalkDir, others
-    will be silently discarded.
-
-### Documentation
-
-For the official documentation, see
-https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir
-
-### Benchmarks
-
-For a WalkDirFunc that consists solely of the return statement, this
-implementation is about 15% slower than the standard library's
-filepath.WalkDir.
-
-Otherwise (if a WalkDirFunc is actually doing something) this is usually
-faster, except when the WalkDirN(..., 1) is used. Run `go test -bench .`
-to see how different operations can benefit from it, as well as how the
-level of paralellism affects the speed.
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go
deleted file mode 100644
index 22282075..00000000
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go
+++ /dev/null
@@ -1,103 +0,0 @@
-// +build go1.16
-
-package pwalkdir
-
-import (
-	"fmt"
-	"io/fs"
-	"path/filepath"
-	"runtime"
-	"sync"
-)
-
-// Walk is a wrapper for filepath.WalkDir which can call multiple walkFn
-// in parallel, allowing to handle each item concurrently. A maximum of
-// twice the runtime.NumCPU() walkFn will be called at any one time.
-// If you want to change the maximum, use WalkN instead.
-//
-// The order of calls is non-deterministic.
-//
-// Note that this implementation only supports primitive error handling:
-//
-// - no errors are ever passed to walkFn;
-//
-// - once a walkFn returns any error, all further processing stops
-// and the error is returned to the caller of Walk;
-//
-// - filepath.SkipDir is not supported;
-//
-// - if more than one walkFn instance will return an error, only one
-// of such errors will be propagated and returned by Walk, others
-// will be silently discarded.
-func Walk(root string, walkFn fs.WalkDirFunc) error {
-	return WalkN(root, walkFn, runtime.NumCPU()*2)
-}
-
-// WalkN is a wrapper for filepath.WalkDir which can call multiple walkFn
-// in parallel, allowing to handle each item concurrently. A maximum of
-// num walkFn will be called at any one time.
-//
-// Please see Walk documentation for caveats of using this function.
-func WalkN(root string, walkFn fs.WalkDirFunc, num int) error {
-	// make sure limit is sensible
-	if num < 1 {
-		return fmt.Errorf("walk(%q): num must be > 0", root)
-	}
-
-	files := make(chan *walkArgs, 2*num)
-	errCh := make(chan error, 1) // Get the first error, ignore others.
-
-	// Start walking a tree asap.
-	var (
-		err error
-		wg  sync.WaitGroup
-	)
-	wg.Add(1)
-	go func() {
-		err = filepath.WalkDir(root, func(p string, entry fs.DirEntry, err error) error {
-			if err != nil {
-				close(files)
-				return err
-			}
-			// Add a file to the queue unless a callback sent an error.
-			select {
-			case e := <-errCh:
-				close(files)
-				return e
-			default:
-				files <- &walkArgs{path: p, entry: entry}
-				return nil
-			}
-		})
-		if err == nil {
-			close(files)
-		}
-		wg.Done()
-	}()
-
-	wg.Add(num)
-	for i := 0; i < num; i++ {
-		go func() {
-			for file := range files {
-				if e := walkFn(file.path, file.entry, nil); e != nil {
-					select {
-					case errCh <- e: // sent ok
-					default: // buffer full
-					}
-				}
-			}
-			wg.Done()
-		}()
-	}
-
-	wg.Wait()
-
-	return err
-}
-
-// walkArgs holds the arguments that were passed to the Walk or WalkN
-// functions.
-type walkArgs struct {
-	path  string
-	entry fs.DirEntry
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3c74fb52..5c77cf79 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -42,12 +42,11 @@ github.com/mrunalp/fileutils
 # github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
 ## explicit
 github.com/opencontainers/runtime-spec/specs-go
-# github.com/opencontainers/selinux v1.8.4
+# github.com/opencontainers/selinux v1.8.3
 ## explicit
 github.com/opencontainers/selinux/go-selinux
 github.com/opencontainers/selinux/go-selinux/label
 github.com/opencontainers/selinux/pkg/pwalk
-github.com/opencontainers/selinux/pkg/pwalkdir
 # github.com/pkg/errors v0.9.1
 github.com/pkg/errors
 # github.com/russross/blackfriday/v2 v2.0.1


Test patch:
diff --git a/libcontainer/cgroups/file_test.go b/libcontainer/cgroups/file_test.go
index 4b2cb895..9f7be0b8 100644
--- a/libcontainer/cgroups/file_test.go
+++ b/libcontainer/cgroups/file_test.go
@@ -3,6 +3,7 @@
 package cgroups
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -40,3 +41,35 @@ func TestWriteCgroupFileHandlesInterrupt(t *testing.T) {
 		}
 	}
 }
+
+func TestOpenat2(t *testing.T) {
+	if !IsCgroup2UnifiedMode() {
+		// The reason is many test cases below test opening files from
+		// the top-level directory, where cgroup v1 has no files.
+		t.Skip("test requires cgroup v2")
+	}
+
+	// Make sure we test openat2, not its fallback.
+	openFallback = func(_ string, _ int, _ os.FileMode) (*os.File, error) {
+		return nil, errors.New("fallback")
+	}
+	defer func() { openFallback = openAndCheck }()
+
+	for _, tc := range []struct{ dir, file string }{
+		{"/sys/fs/cgroup", "cgroup.controllers"},
+		{"/sys/fs/cgroup", "/cgroup.controllers"},
+		{"/sys/fs/cgroup/", "cgroup.controllers"},
+		{"/sys/fs/cgroup/", "/cgroup.controllers"},
+		{"/sys/fs/cgroup/user.slice", "cgroup.controllers"},
+		{"/sys/fs/cgroup/user.slice/", "/cgroup.controllers"},
+		{"/", "/sys/fs/cgroup/cgroup.controllers"},
+		{"/", "sys/fs/cgroup/cgroup.controllers"},
+		{"/sys/fs/cgroup/cgroup.controllers", ""},
+	} {
+		fd, err := OpenFile(tc.dir, tc.file, os.O_RDONLY)
+		if err != nil {
+			t.Errorf("case %+v: %v", tc, err)
+		}
+		fd.Close()
+	}
+}
diff --git a/libcontainer/cgroups/getallpids_test.go b/libcontainer/cgroups/getallpids_test.go
deleted file mode 100644
index b6100686..00000000
--- a/libcontainer/cgroups/getallpids_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// +build linux
-
-package cgroups
-
-import (
-	"testing"
-)
-
-func BenchmarkGetAllPids(b *testing.B) {
-	total := 0
-	for i := 0; i < b.N; i++ {
-		i, err := GetAllPids("/sys/fs/cgroup")
-		if err != nil {
-			b.Fatal(err)
-		}
-		total += len(i)
-	}
-	b.Logf("iter: %d, total: %d", b.N, total)
-}