4adc40ac40
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
112 lines
3.7 KiB
Go
112 lines
3.7 KiB
Go
package safepath
|
|
|
|
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/internal/unix_noeintr"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// 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 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
|
|
const nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
|
|
// flags for getting file descriptor without following the symlink
|
|
const openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
|
|
|
|
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 := unix_noeintr.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return -1, &ErrNotAccessible{Path: base, Cause: err}
|
|
}
|
|
defer func() {
|
|
if parentFD != -1 {
|
|
if err = unix_noeintr.Close(parentFD); err != nil {
|
|
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
childFD := -1
|
|
defer func() {
|
|
if childFD != -1 {
|
|
if err = unix_noeintr.Close(childFD); err != nil {
|
|
log.G(context.TODO()).Errorf("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
currentPath := base
|
|
|
|
// Follow the segments one by one using openat() to make
|
|
// sure the user cannot change already existing directories into symlinks.
|
|
for _, seg := range segments {
|
|
var deviceStat unix.Stat_t
|
|
|
|
currentPath = filepath.Join(currentPath, seg)
|
|
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_noeintr.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
|
|
|
|
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, &ErrNotAccessible{Path: currentPath, Cause: err}
|
|
}
|
|
|
|
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 & unix.S_IFMT
|
|
if fileFmt == unix.S_IFLNK {
|
|
return -1, fmt.Errorf("unexpected symlink found %s", currentPath)
|
|
}
|
|
|
|
// Close parentFD
|
|
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
|
|
parentFD = childFD
|
|
childFD = -1
|
|
}
|
|
|
|
// We made it to the end, return this fd, don't close it
|
|
finalFD := parentFD
|
|
parentFD = -1
|
|
|
|
return finalFD, nil
|
|
}
|