mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
850b2c47b2
Commit88e8350de2, among the other things, replaced filepath.Join with securejoin.SecureJoin for both reads and writes to cgroupfs. Commitse76ac1c054and31f0f5b7e0switched more code to use fscommon.ReadFile (and thus securejoin). Commit0228226e6dintroduced fscommon.OpenFile (which uses securejoin as the fallback if openat2(2) is not available, which is the case for older kernels), and commitc95e69007cswitched most of cgroup/fs[2] code to use it. As a result, fs.GetStats() method became noticeable slower, mostly due to securejoin calling os.Lstat and filepath.Clean. Using securejoin as a security measure for cgroupfs files is not well justified, as cgroupfs do not contain symlinks, and none of the code using it have uncleaned paths. In particular, fs/fs2/systemd managers do check and sanitize their paths. This commit modifies the code to not use securejoin. Instead, it checks that the opened file is indeed on cgroupfs. Using BenchmarkGetStats on a CentOS 8 VM, I see the following improvement: Before: > BenchmarkGetStats-8 8376 625135 ns/op After: > BenchmarkGetStats-8 12226 485015 ns/op An intermediate version, with no fstatfs to check fstype: > BenchmarkGetStats-8 13162 452281 ns/op Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
121 lines
3.1 KiB
Go
121 lines
3.1 KiB
Go
package fscommon
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
cgroupfsDir = "/sys/fs/cgroup"
|
|
cgroupfsPrefix = cgroupfsDir + "/"
|
|
)
|
|
|
|
var (
|
|
// TestMode is set to true by unit tests that need "fake" cgroupfs.
|
|
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}
|
|
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
|
|
}
|
|
|
|
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) {
|
|
if dir == "" {
|
|
return nil, errors.Errorf("no directory specified for %s", file)
|
|
}
|
|
mode := os.FileMode(0)
|
|
if TestMode && flags&os.O_WRONLY != 0 {
|
|
// "emulate" cgroup fs for unit tests
|
|
flags |= os.O_TRUNC | os.O_CREATE
|
|
mode = 0o600
|
|
}
|
|
if prepareOpenat2() != nil {
|
|
return openFallback(dir, file, flags, mode)
|
|
}
|
|
reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
|
|
if len(reldir) == len(dir) { // non-standard path, old system?
|
|
return openFallback(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
|
|
}
|
|
|
|
var errNotCgroupfs = errors.New("not a cgroup file")
|
|
|
|
// openFallback 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
|
|
fd, err := os.OpenFile(path, flags, mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if TestMode {
|
|
return fd, nil
|
|
}
|
|
// Check this is a cgroupfs file.
|
|
var st unix.Statfs_t
|
|
if err := unix.Fstatfs(int(fd.Fd()), &st); err != nil {
|
|
_ = fd.Close()
|
|
return nil, &os.PathError{Op: "statfs", Path: path, Err: err}
|
|
}
|
|
if st.Type != unix.CGROUP_SUPER_MAGIC && st.Type != unix.CGROUP2_SUPER_MAGIC {
|
|
_ = fd.Close()
|
|
return nil, &os.PathError{Op: "open", Path: path, Err: errNotCgroupfs}
|
|
}
|
|
|
|
return fd, nil
|
|
}
|