From 6bda4600003e145f5aff4dd8d9d2d5866fc44ce9 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 24 Sep 2020 17:23:05 -0700 Subject: [PATCH 1/2] libcontainer/cgroups/fscommon: add openat2 support In case openat2 is available, it will be used to guarantee that we're not accessing anything other than cgroupfs[2] files. In cases when openat2 is not available, or when cgroup has a non-standard prefix (not "/sys/fs/cgroup", which might theoretically be the case on some very old installs and/or very custom systems), fall back to using securejoin + os.Open like we did before. Signed-off-by: Kir Kolyshkin --- libcontainer/cgroups/fscommon/open.go | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/libcontainer/cgroups/fscommon/open.go b/libcontainer/cgroups/fscommon/open.go index 90560f548d3..3858269caa6 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 { + prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err} + return + } + + cgroupFd = fd + + resolveFlags = unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS + if st.Type == unix.CGROUP2_SUPER_MAGIC { + // cgroupv2 has a single mountpoint and no "cpu,cpuacct" symlinks + resolveFlags |= unix.RESOLVE_NO_XDEV | unix.RESOLVE_NO_SYMLINKS + } + + }) + + 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 From 3c9b03fd73f5f57c13701c7e7f0122cdb5a8b42c Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Mon, 9 Nov 2020 15:56:48 -0800 Subject: [PATCH 2/2] libct/cg/fscommon: log openat2 init failures In case we get ENOSYS from openat2(2), this is expected, so log that we're falling back to using securejoin as debug. Otherwise, log it as a warning (as the error is unexpected, but we're still good to go). Signed-off-by: Kir Kolyshkin --- libcontainer/cgroups/fscommon/open.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libcontainer/cgroups/fscommon/open.go b/libcontainer/cgroups/fscommon/open.go index 3858269caa6..0a7e3d95282 100644 --- a/libcontainer/cgroups/fscommon/open.go +++ b/libcontainer/cgroups/fscommon/open.go @@ -7,6 +7,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -31,11 +32,17 @@ func prepareOpenat2() error { Flags: unix.O_DIRECTORY | unix.O_PATH}) if err != nil { prepErr = &os.PathError{Op: "openat2", Path: cgroupfsDir, Err: err} + if err != unix.ENOSYS { + logrus.Warnf("falling back to securejoin: %s", prepErr) + } else { + logrus.Debug("openat2 not available, falling back to securejoin") + } return } var st unix.Statfs_t if err = unix.Fstatfs(fd, &st); err != nil { prepErr = &os.PathError{Op: "statfs", Path: cgroupfsDir, Err: err} + logrus.Warnf("falling back to securejoin: %s", prepErr) return }