diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 1a9c4979c26..6ad25c9a4df 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -198,6 +198,13 @@ func (l *linuxStandardInit) Init() error { if err != nil { return err } + // exec.LookPath might return no error for an executable residing on a + // file system mounted with noexec flag, so perform this extra check + // now while we can still return a proper error. + if err := system.Eaccess(name); err != nil { + return &os.PathError{Op: "exec", Path: name, Err: err} + } + // Set seccomp as close to execve as possible, so as few syscalls take // place afterward (reducing the amount of syscalls that users need to // enable in their seccomp profiles). However, this needs to be done diff --git a/libcontainer/system/linux.go b/libcontainer/system/linux.go index e1d6eb18034..039059a444c 100644 --- a/libcontainer/system/linux.go +++ b/libcontainer/system/linux.go @@ -31,6 +31,25 @@ func (p ParentDeathSignal) Set() error { return SetParentDeathSignal(uintptr(p)) } +// Eaccess is similar to unix.Access except for setuid/setgid binaries +// it checks against the effective (rather than real) uid and gid. +func Eaccess(path string) error { + err := unix.Faccessat2(unix.AT_FDCWD, path, unix.X_OK, unix.AT_EACCESS) + if err != unix.ENOSYS && err != unix.EPERM { //nolint:errorlint // unix errors are bare + return err + } + + // Faccessat2() not available; check if we are a set[ug]id binary. + if os.Getuid() == os.Geteuid() && os.Getgid() == os.Getegid() { + // For a non-set[ug]id binary, use access(2). + return unix.Access(path, unix.X_OK) + } + + // For a setuid/setgid binary, there is no fallback way + // so assume we can execute the binary. + return nil +} + func Execv(cmd string, args []string, env []string) error { name, err := exec.LookPath(cmd) if err != nil {