@@ -31,6 +31,10 @@ import (
31
31
"k8s.io/utils/nsenter"
32
32
)
33
33
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
+
34
38
type subpath struct {}
35
39
36
40
// New returns a subpath.Interface for the current system
@@ -44,34 +48,91 @@ func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) In
44
48
return nil
45
49
}
46
50
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 )
52
117
output , err := exec .Command ("powershell" , "/c" , cmd ).CombinedOutput ()
53
- klog .V (4 ).Infof ("evaluate symlink from %s: %s %v" , path , string (output ), err )
54
118
if err != nil {
55
119
return "" , err
56
120
}
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 )
60
125
return path , nil
61
126
}
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 )
64
130
}
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
73
134
}
74
- return false
135
+ return filepath . Join ( nextLink , base ), nil
75
136
}
76
137
77
138
// check whether hostPath is within volume path
@@ -81,12 +142,12 @@ func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
81
142
return []uintptr {}, nil
82
143
}
83
144
84
- finalSubPath , err := evalPath (hostPath )
145
+ finalSubPath , err := evalSymlink (hostPath )
85
146
if err != nil {
86
147
return []uintptr {}, fmt .Errorf ("cannot evaluate link %s: %s" , hostPath , err )
87
148
}
88
149
89
- finalVolumePath , err := evalPath (volumePath )
150
+ finalVolumePath , err := evalSymlink (volumePath )
90
151
if err != nil {
91
152
return []uintptr {}, fmt .Errorf ("cannot read link %s: %s" , volumePath , err )
92
153
}
@@ -194,7 +255,7 @@ func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
194
255
195
256
// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
196
257
func (sp * subpath ) SafeMakeDir (subdir string , base string , perm os.FileMode ) error {
197
- realBase , err := evalPath (base )
258
+ realBase , err := evalSymlink (base )
198
259
if err != nil {
199
260
return fmt .Errorf ("error resolving symlinks in %s: %s" , base , err )
200
261
}
@@ -233,11 +294,11 @@ func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
233
294
}
234
295
235
296
// Ensure the existing directory is inside allowed base
236
- fullExistingPath , err := evalPath (existingPath )
297
+ fullExistingPath , err := evalSymlink (existingPath )
237
298
if err != nil {
238
299
return fmt .Errorf ("error opening existing directory %s: %s" , existingPath , err )
239
300
}
240
- fullBasePath , err := evalPath (base )
301
+ fullBasePath , err := evalSymlink (base )
241
302
if err != nil {
242
303
return fmt .Errorf ("cannot read link %s: %s" , base , err )
243
304
}
0 commit comments