| 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 | |
| } |