Skip to content

Commit ebbbc57

Browse files
authored
Merge pull request kubernetes#93707 from jingxu97/Aug/subpath
Fix issue in evaluating symlink path for subpath
2 parents 8f9f2fb + 5aa817a commit ebbbc57

File tree

2 files changed

+124
-25
lines changed

2 files changed

+124
-25
lines changed

pkg/volume/util/subpath/subpath_windows.go

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ import (
3131
"k8s.io/utils/nsenter"
3232
)
3333

34+
// MaxPathLength is the maximum length of Windows path. Normally, it is 260, but if long path is enable,
35+
// the max number is 32,767
36+
const MaxPathLength = 32767
37+
3438
type subpath struct{}
3539

3640
// New returns a subpath.Interface for the current system
@@ -44,34 +48,91 @@ func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) In
4448
return nil
4549
}
4650

47-
// evalPath returns the path name after the evaluation of any symbolic links.
48-
// If the path after evaluation starts with Volume or \??\Volume, it means that it was a symlink from
49-
// volume (represented by volumeID) to the given path. In this case, the given path is returned.
50-
func evalPath(path string) (linkedPath string, err error) {
51-
cmd := fmt.Sprintf("Get-Item -Path %s | Select-Object -ExpandProperty Target", path)
51+
// isDriveLetterPath returns true if the given path is empty or it ends with ":" or ":\" or ":\\"
52+
func isDriveLetterorEmptyPath(path string) bool {
53+
if path == "" || strings.HasSuffix(path, ":\\\\") || strings.HasSuffix(path, ":") || strings.HasSuffix(path, ":\\") {
54+
return true
55+
}
56+
return false
57+
}
58+
59+
// isVolumePrefix returns true if the given path name starts with "Volume" or volume prefix including
60+
// "\\.\", "\\?\" for device path or "UNC" or "\\" for UNC path. Otherwise, it returns false.
61+
func isDeviceOrUncPath(path string) bool {
62+
if strings.HasPrefix(path, "Volume") || strings.HasPrefix(path, "\\\\?\\") || strings.HasPrefix(path, "\\\\.\\") || strings.HasPrefix(path, "UNC") {
63+
return true
64+
}
65+
return false
66+
}
67+
68+
// getUpperPath removes the last level of directory.
69+
func getUpperPath(path string) string {
70+
sep := fmt.Sprintf("%c", filepath.Separator)
71+
upperpath := strings.TrimSuffix(path, sep)
72+
return filepath.Dir(upperpath)
73+
}
74+
75+
// Check whether a directory/file is a link type or not
76+
// LinkType could be SymbolicLink, Junction, or HardLink
77+
func isLinkPath(path string) (bool, error) {
78+
cmd := fmt.Sprintf("(Get-Item -Path %s).LinkType", path)
79+
output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
80+
if err != nil {
81+
return false, err
82+
}
83+
if strings.TrimSpace(string(output)) != "" {
84+
return true, nil
85+
}
86+
return false, nil
87+
}
88+
89+
// evalSymlink returns the path name after the evaluation of any symbolic links.
90+
// If the path after evaluation is a device path or network connection, the original path is returned
91+
func evalSymlink(path string) (string, error) {
92+
path = mount.NormalizeWindowsPath(path)
93+
if isDeviceOrUncPath(path) || isDriveLetterorEmptyPath(path) {
94+
klog.V(4).Infof("Path '%s' is not a symlink, return its original form.", path)
95+
return path, nil
96+
}
97+
upperpath := path
98+
base := ""
99+
for i := 0; i < MaxPathLength; i++ {
100+
isLink, err := isLinkPath(upperpath)
101+
if err != nil {
102+
return "", err
103+
}
104+
if isLink {
105+
break
106+
}
107+
// continue to check next layer
108+
base = filepath.Join(filepath.Base(upperpath), base)
109+
upperpath = getUpperPath(upperpath)
110+
if isDriveLetterorEmptyPath(upperpath) {
111+
klog.V(4).Infof("Path '%s' is not a symlink, return its original form.", path)
112+
return path, nil
113+
}
114+
}
115+
// This command will give the target path of a given symlink
116+
cmd := fmt.Sprintf("(Get-Item -Path %s).Target", upperpath)
52117
output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
53-
klog.V(4).Infof("evaluate symlink from %s: %s %v", path, string(output), err)
54118
if err != nil {
55119
return "", err
56120
}
57-
linkedPath = strings.TrimSpace(string(output))
58-
if linkedPath == "" {
59-
klog.V(4).Infof("Path '%s' has no target. Consiering it as evaluated.", path)
121+
klog.V(4).Infof("evaluate path %s: symlink from %s to %s", path, upperpath, string(output))
122+
linkedPath := strings.TrimSpace(string(output))
123+
if linkedPath == "" || isDeviceOrUncPath(linkedPath) {
124+
klog.V(4).Infof("Path '%s' has a target %s. Return its original form.", path, linkedPath)
60125
return path, nil
61126
}
62-
if isVolumePrefix(linkedPath) {
63-
return path, err
127+
// If the target is not an absoluate path, join iit with the current upperpath
128+
if !filepath.IsAbs(linkedPath) {
129+
linkedPath = filepath.Join(getUpperPath(upperpath), linkedPath)
64130
}
65-
return linkedPath, err
66-
}
67-
68-
// isVolumePrefix returns true if the given path name starts with "Volume" or volume prefix including
69-
// "\\.\" or "\\?\". Otherwise, it returns false.
70-
func isVolumePrefix(path string) bool {
71-
if strings.HasPrefix(path, "Volume") || strings.HasPrefix(path, "\\\\?\\") || strings.HasPrefix(path, "\\\\.\\") {
72-
return true
131+
nextLink, err := evalSymlink(linkedPath)
132+
if err != nil {
133+
return path, err
73134
}
74-
return false
135+
return filepath.Join(nextLink, base), nil
75136
}
76137

77138
// check whether hostPath is within volume path
@@ -81,12 +142,12 @@ func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
81142
return []uintptr{}, nil
82143
}
83144

84-
finalSubPath, err := evalPath(hostPath)
145+
finalSubPath, err := evalSymlink(hostPath)
85146
if err != nil {
86147
return []uintptr{}, fmt.Errorf("cannot evaluate link %s: %s", hostPath, err)
87148
}
88149

89-
finalVolumePath, err := evalPath(volumePath)
150+
finalVolumePath, err := evalSymlink(volumePath)
90151
if err != nil {
91152
return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
92153
}
@@ -194,7 +255,7 @@ func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
194255

195256
// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
196257
func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
197-
realBase, err := evalPath(base)
258+
realBase, err := evalSymlink(base)
198259
if err != nil {
199260
return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
200261
}
@@ -233,11 +294,11 @@ func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
233294
}
234295

235296
// Ensure the existing directory is inside allowed base
236-
fullExistingPath, err := evalPath(existingPath)
297+
fullExistingPath, err := evalSymlink(existingPath)
237298
if err != nil {
238299
return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
239300
}
240-
fullBasePath, err := evalPath(base)
301+
fullBasePath, err := evalSymlink(base)
241302
if err != nil {
242303
return fmt.Errorf("cannot read link %s: %s", base, err)
243304
}

pkg/volume/util/subpath/subpath_windows_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,41 @@ func TestFindExistingPrefix(t *testing.T) {
438438
// remove dir will happen after closing all file handles
439439
assert.Nil(t, os.RemoveAll(testingVolumePath), "Expect no error during remove dir %s", testingVolumePath)
440440
}
441+
442+
func TestIsDriveLetterorEmptyPath(t *testing.T) {
443+
tests := []struct {
444+
path string
445+
expectedResult bool
446+
}{
447+
{
448+
path: ``,
449+
expectedResult: true,
450+
},
451+
{
452+
path: `\tmp`,
453+
expectedResult: false,
454+
},
455+
{
456+
path: `c:\tmp`,
457+
expectedResult: false,
458+
},
459+
{
460+
path: `c:\\`,
461+
expectedResult: true,
462+
},
463+
{
464+
path: `c:\`,
465+
expectedResult: true,
466+
},
467+
{
468+
path: `c:`,
469+
expectedResult: true,
470+
},
471+
}
472+
473+
for _, test := range tests {
474+
result := isDriveLetterorEmptyPath(test.path)
475+
assert.Equal(t, test.expectedResult, result, "Expect result not equal with isDriveLetterorEmptyPath(%s) return: %t, expected: %t",
476+
test.path, result, test.expectedResult)
477+
}
478+
}

0 commit comments

Comments
 (0)