internal/safepath: Adapt k8s openat2 fallback

Adapts the function source code to the Moby codebase.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2023-07-03 18:31:01 +02:00
parent 56bb143a4d
commit 5841ed4e5e
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A
2 changed files with 29 additions and 41 deletions

View file

@ -85,7 +85,7 @@ func safeOpenFd(path, subpath string) (int, error) {
switch {
case errors.Is(err, unix.ENOSYS):
// Openat2 is not available, fallback to Openat loop.
return softOpenat2(prevFd, subpath)
return kubernetesSafeOpen(path, subpath)
case errors.Is(err, unix.EXDEV):
return -1, &ErrEscapesBase{Base: path, Subpath: subpath}
case errors.Is(err, unix.ENOENT), errors.Is(err, unix.ELOOP):
@ -98,10 +98,6 @@ func safeOpenFd(path, subpath string) (int, error) {
return fd, nil
}
func softOpenat2(baseFd int, subpath string) (int, error) {
return -1, errors.New("temporary stub, will be removed in later commit")
}
// tempMountPoint creates a temporary file/directory to act as mount
// point for the file descriptor.
func tempMountPoint(sourceFd int) (string, error) {

View file

@ -17,48 +17,40 @@ limitations under the License.
*/
import (
"context"
"fmt"
"path/filepath"
"strings"
"syscall"
"github.com/containerd/log"
"github.com/docker/docker/internal/unix_noeintr"
"golang.org/x/sys/unix"
"k8s.io/klog/v2"
"k8s.io/mount-utils"
)
const (
// kubernetesSafeOpen open path formed by concatenation of the base directory
// and its subpath and return its fd.
// Symlinks are disallowed (pathname must already resolve symlinks) and the path
// path must be within the base directory.
// This is minimally modified code from https://github.com/kubernetes/kubernetes/blob/55fb1805a1217b91b36fa8fe8f2bf3a28af2454d/pkg/volume/util/subpath/subpath_linux.go#L530
func kubernetesSafeOpen(base, subpath string) (int, error) {
// syscall.Openat flags used to traverse directories not following symlinks
nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
const nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
// flags for getting file descriptor without following the symlink
openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
)
const openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
// This implementation is shared between Linux and NsEnterMounter
// Open path and return its fd.
// Symlinks are disallowed (pathname must already resolve symlinks),
// and the path must be within the base directory.
func doSafeOpen(pathname string, base string) (int, error) {
pathname = filepath.Clean(pathname)
base = filepath.Clean(base)
// Calculate segments to follow
subpath, err := filepath.Rel(base, pathname)
if err != nil {
return -1, err
}
pathname := filepath.Join(base, subpath)
segments := strings.Split(subpath, string(filepath.Separator))
// Assumption: base is the only directory that we have under control.
// Base dir is not allowed to be a symlink.
parentFD, err := syscall.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
parentFD, err := unix_noeintr.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
if err != nil {
return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
return -1, &ErrNotAccessible{Path: base, Cause: err}
}
defer func() {
if parentFD != -1 {
if err = syscall.Close(parentFD); err != nil {
klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
if err = unix_noeintr.Close(parentFD); err != nil {
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
}
}
}()
@ -66,8 +58,8 @@ func doSafeOpen(pathname string, base string) (int, error) {
childFD := -1
defer func() {
if childFD != -1 {
if err = syscall.Close(childFD); err != nil {
klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
if err = unix_noeintr.Close(childFD); err != nil {
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
}
}
}()
@ -80,31 +72,31 @@ func doSafeOpen(pathname string, base string) (int, error) {
var deviceStat unix.Stat_t
currentPath = filepath.Join(currentPath, seg)
if !mount.PathWithinBase(currentPath, base) {
return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
if !isLocalTo(currentPath, base) {
return -1, &ErrEscapesBase{Base: currentPath, Subpath: seg}
}
// Trigger auto mount if it's an auto-mounted directory, ignore error if not a directory.
// Notice the trailing slash is mandatory, see "automount" in openat(2) and open_by_handle_at(2).
unix.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
unix_noeintr.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
klog.V(5).Infof("Opening path %s", currentPath)
childFD, err = syscall.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
log.G(context.TODO()).Debugf("Opening path %s", currentPath)
childFD, err = unix_noeintr.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
if err != nil {
return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
return -1, &ErrNotAccessible{Path: currentPath, Cause: err}
}
err := unix.Fstat(childFD, &deviceStat)
err := unix_noeintr.Fstat(childFD, &deviceStat)
if err != nil {
return -1, fmt.Errorf("error running fstat on %s with %v", currentPath, err)
}
fileFmt := deviceStat.Mode & syscall.S_IFMT
if fileFmt == syscall.S_IFLNK {
fileFmt := deviceStat.Mode & unix.S_IFMT
if fileFmt == unix.S_IFLNK {
return -1, fmt.Errorf("unexpected symlink found %s", currentPath)
}
// Close parentFD
if err = syscall.Close(parentFD); err != nil {
if err = unix_noeintr.Close(parentFD); err != nil {
return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
}
// Set child to new parent