Skip to content

Commit 56bb143

Browse files
committed
internal/safepath: Import k8s safeopen function
For use as a soft fallback if Openat2 is not available. Source: https://github.com/kubernetes/kubernetes/blob/55fb1805a1217b91b36fa8fe8f2bf3a28af2454d/pkg/volume/util/subpath/subpath_linux.go Signed-off-by: Paweł Gronowski <[email protected]>
1 parent 3784316 commit 56bb143

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package safepath
2+
3+
/*
4+
Copyright 2014 The Kubernetes Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
import (
20+
"fmt"
21+
"path/filepath"
22+
"strings"
23+
"syscall"
24+
25+
"golang.org/x/sys/unix"
26+
"k8s.io/klog/v2"
27+
"k8s.io/mount-utils"
28+
)
29+
30+
const (
31+
// syscall.Openat flags used to traverse directories not following symlinks
32+
nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
33+
// flags for getting file descriptor without following the symlink
34+
openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
35+
)
36+
37+
// This implementation is shared between Linux and NsEnterMounter
38+
// Open path and return its fd.
39+
// Symlinks are disallowed (pathname must already resolve symlinks),
40+
// and the path must be within the base directory.
41+
func doSafeOpen(pathname string, base string) (int, error) {
42+
pathname = filepath.Clean(pathname)
43+
base = filepath.Clean(base)
44+
45+
// Calculate segments to follow
46+
subpath, err := filepath.Rel(base, pathname)
47+
if err != nil {
48+
return -1, err
49+
}
50+
segments := strings.Split(subpath, string(filepath.Separator))
51+
52+
// Assumption: base is the only directory that we have under control.
53+
// Base dir is not allowed to be a symlink.
54+
parentFD, err := syscall.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
55+
if err != nil {
56+
return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
57+
}
58+
defer func() {
59+
if parentFD != -1 {
60+
if err = syscall.Close(parentFD); err != nil {
61+
klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
62+
}
63+
}
64+
}()
65+
66+
childFD := -1
67+
defer func() {
68+
if childFD != -1 {
69+
if err = syscall.Close(childFD); err != nil {
70+
klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
71+
}
72+
}
73+
}()
74+
75+
currentPath := base
76+
77+
// Follow the segments one by one using openat() to make
78+
// sure the user cannot change already existing directories into symlinks.
79+
for _, seg := range segments {
80+
var deviceStat unix.Stat_t
81+
82+
currentPath = filepath.Join(currentPath, seg)
83+
if !mount.PathWithinBase(currentPath, base) {
84+
return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
85+
}
86+
87+
// Trigger auto mount if it's an auto-mounted directory, ignore error if not a directory.
88+
// Notice the trailing slash is mandatory, see "automount" in openat(2) and open_by_handle_at(2).
89+
unix.Fstatat(parentFD, seg+"/", &deviceStat, unix.AT_SYMLINK_NOFOLLOW)
90+
91+
klog.V(5).Infof("Opening path %s", currentPath)
92+
childFD, err = syscall.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
93+
if err != nil {
94+
return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
95+
}
96+
97+
err := unix.Fstat(childFD, &deviceStat)
98+
if err != nil {
99+
return -1, fmt.Errorf("error running fstat on %s with %v", currentPath, err)
100+
}
101+
fileFmt := deviceStat.Mode & syscall.S_IFMT
102+
if fileFmt == syscall.S_IFLNK {
103+
return -1, fmt.Errorf("unexpected symlink found %s", currentPath)
104+
}
105+
106+
// Close parentFD
107+
if err = syscall.Close(parentFD); err != nil {
108+
return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
109+
}
110+
// Set child to new parent
111+
parentFD = childFD
112+
childFD = -1
113+
}
114+
115+
// We made it to the end, return this fd, don't close it
116+
finalFD := parentFD
117+
parentFD = -1
118+
119+
return finalFD, nil
120+
}

0 commit comments

Comments
 (0)