diff --git a/libcontainer/cgroups/fscommon/open.go b/libcontainer/cgroups/fscommon/open.go index 90560f548d3..0680bd0f98b 100644 --- a/libcontainer/cgroups/fscommon/open.go +++ b/libcontainer/cgroups/fscommon/open.go @@ -2,16 +2,56 @@ package fscommon import ( "os" + "strings" + "sync" securejoin "github.com/cyphar/filepath-securejoin" "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +const ( + cgroupfsDir = "/sys/fs/cgroup" + cgroupfsPrefix = cgroupfsDir + "/" ) var ( // Set to true by fs unit tests TestMode bool + + cgroupFd int = -1 + prepOnce sync.Once + prepErr error + resolveFlags uint64 ) +func prepareOpenat2() error { + prepOnce.Do(func() { + fd, err := unix.Openat2(-1, cgroupfsDir, &unix.OpenHow{Flags: unix.O_DIRECTORY | unix.O_PATH}) + if err != nil { + prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err} + return + } + var st unix.Statfs_t + if err = unix.Fstatfs(fd, &st); err != nil { + // XXX: make unix.ENOSYS a special case? + prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err} + return + } + + cgroupFd = fd + + if st.Type == unix.CGROUP2_SUPER_MAGIC { + resolveFlags = unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS + } else { + // cgroupfs v1 is a set of separate mounts + resolveFlags = unix.RESOLVE_BENEATH + } + }) + + 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) { @@ -24,6 +64,29 @@ func OpenFile(dir, file string, flags int) (*os.File, error) { flags |= os.O_TRUNC | os.O_CREATE mode = 0o600 } + reldir := strings.TrimPrefix(dir, cgroupfsPrefix) + if len(reldir) == len(dir) { // non-standard path, old system? + return openWithSecureJoin(dir, file, flags, mode) + } + if prepareOpenat2() != nil { + return openWithSecureJoin(dir, file, flags, mode) + } + + relname := reldir + "/" + file + fd, err := unix.Openat2(cgroupFd, relname, + &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} + } + + return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil +} + +func openWithSecureJoin(dir, file string, flags int, mode os.FileMode) (*os.File, error) { path, err := securejoin.SecureJoin(dir, file) if err != nil { return nil, err