Skip to content

Commit be4b717

Browse files
mxpvclaudiubelu
andauthored
Fix Abs path validation on Windows (kubernetes#124084)
* Windows: Consider slash-prefixed paths as absolute filepath.IsAbs does not consider "/" or "\" as absolute paths, even though files can be addressed as such. [1][2] Currently, there are some unit tests that are failing on Windows due to this reason. [1] https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths [2] https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#fully-qualified-vs-relative-paths * Add test to verify IsAbs for windows Signed-off-by: Maksym Pavlenko <[email protected]> * Fix abs path validation on windows Signed-off-by: Maksym Pavlenko <[email protected]> * Skipp path clean check for podLogDir on windows Signed-off-by: Maksym Pavlenko <[email protected]> * Implement IsPathClean to validate path Signed-off-by: Maksym Pavlenko <[email protected]> * Add warn comment for IsAbs Signed-off-by: Maksym Pavlenko <[email protected]> --------- Signed-off-by: Maksym Pavlenko <[email protected]> Co-authored-by: Claudiu Belu <[email protected]>
1 parent 9791f0d commit be4b717

File tree

8 files changed

+108
-7
lines changed

8 files changed

+108
-7
lines changed

pkg/kubelet/apis/config/validation/validation.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package validation
1818

1919
import (
2020
"fmt"
21-
"path/filepath"
2221
"time"
2322
"unicode"
2423

@@ -33,6 +32,7 @@ import (
3332
"k8s.io/kubernetes/pkg/features"
3433
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
3534
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
35+
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
3636
utiltaints "k8s.io/kubernetes/pkg/util/taints"
3737
"k8s.io/utils/cpuset"
3838
)
@@ -293,11 +293,11 @@ func ValidateKubeletConfiguration(kc *kubeletconfig.KubeletConfiguration, featur
293293
allErrors = append(allErrors, fmt.Errorf("invalid configuration: podLogsDir was not specified"))
294294
}
295295

296-
if !filepath.IsAbs(kc.PodLogsDir) {
296+
if !utilfs.IsAbs(kc.PodLogsDir) {
297297
allErrors = append(allErrors, fmt.Errorf("invalid configuration: pod logs path %q must be absolute path", kc.PodLogsDir))
298298
}
299299

300-
if filepath.Clean(kc.PodLogsDir) != kc.PodLogsDir {
300+
if !utilfs.IsPathClean(kc.PodLogsDir) {
301301
allErrors = append(allErrors, fmt.Errorf("invalid configuration: pod logs path %q must be normalized", kc.PodLogsDir))
302302
}
303303

pkg/kubelet/kubelet_pods.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import (
6161
"k8s.io/kubernetes/pkg/kubelet/status"
6262
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
6363
"k8s.io/kubernetes/pkg/kubelet/util"
64+
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
6465
utilpod "k8s.io/kubernetes/pkg/util/pod"
6566
volumeutil "k8s.io/kubernetes/pkg/volume/util"
6667
"k8s.io/kubernetes/pkg/volume/util/hostutil"
@@ -199,7 +200,7 @@ func (kl *Kubelet) makeBlockVolumes(pod *v1.Pod, container *v1.Container, podVol
199200
var devices []kubecontainer.DeviceInfo
200201
for _, device := range container.VolumeDevices {
201202
// check path is absolute
202-
if !filepath.IsAbs(device.DevicePath) {
203+
if !utilfs.IsAbs(device.DevicePath) {
203204
return nil, fmt.Errorf("error DevicePath `%s` must be an absolute path", device.DevicePath)
204205
}
205206
vol, ok := podVolumes[device.Name]
@@ -280,7 +281,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
280281
}
281282

282283
if subPath != "" {
283-
if filepath.IsAbs(subPath) {
284+
if utilfs.IsAbs(subPath) {
284285
return nil, cleanupAction, fmt.Errorf("error SubPath `%s` must not be an absolute path", subPath)
285286
}
286287

@@ -332,7 +333,7 @@ func makeMounts(pod *v1.Pod, podDir string, container *v1.Container, hostName, h
332333

333334
containerPath := mount.MountPath
334335
// IsAbs returns false for UNC path/SMB shares/named pipes in Windows. So check for those specifically and skip MakeAbsolutePath
335-
if !volumeutil.IsWindowsUNCPath(runtime.GOOS, containerPath) && !filepath.IsAbs(containerPath) {
336+
if !volumeutil.IsWindowsUNCPath(runtime.GOOS, containerPath) && !utilfs.IsAbs(containerPath) {
336337
containerPath = volumeutil.MakeAbsolutePath(runtime.GOOS, containerPath)
337338
}
338339

pkg/kubelet/kubeletconfig/configfiles/configfiles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func resolveRelativePaths(paths []*string, root string) {
100100
for _, path := range paths {
101101
// leave empty paths alone, "no path" is a valid input
102102
// do not attempt to resolve paths that are already absolute
103-
if len(*path) > 0 && !filepath.IsAbs(*path) {
103+
if len(*path) > 0 && !utilfs.IsAbs(*path) {
104104
*path = filepath.Join(root, *path)
105105
}
106106
}

pkg/util/filesystem/util.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package filesystem
18+
19+
import (
20+
"path/filepath"
21+
)
22+
23+
// IsPathClean will replace slashes to Separator (which is OS-specific).
24+
// This will make sure that all slashes are the same before comparing.
25+
func IsPathClean(path string) bool {
26+
return filepath.ToSlash(filepath.Clean(path)) == filepath.ToSlash(path)
27+
}

pkg/util/filesystem/util_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package filesystem
1919
import (
2020
"net"
2121
"os"
22+
"runtime"
2223
"testing"
2324

2425
"github.com/stretchr/testify/assert"
@@ -84,3 +85,48 @@ func TestIsUnixDomainSocket(t *testing.T) {
8485
assert.Equal(t, result, test.expectSocket, "Unexpected result from IsUnixDomainSocket: %v for %s", result, test.label)
8586
}
8687
}
88+
89+
func TestIsCleanPath(t *testing.T) {
90+
type Case struct {
91+
path string
92+
expected bool
93+
}
94+
95+
// Credits https://github.com/kubernetes/kubernetes/pull/124084/files#r1557566941
96+
cases := []Case{
97+
{path: "/logs", expected: true},
98+
{path: "/var/lib/something", expected: true},
99+
{path: "var/lib/something", expected: true},
100+
{path: "var\\lib\\something", expected: true},
101+
{path: "/", expected: true},
102+
{path: ".", expected: true},
103+
{path: "/var/../something", expected: false},
104+
{path: "/var//lib/something", expected: false},
105+
{path: "/var/./lib/something", expected: false},
106+
}
107+
108+
// Additional cases applicable on Windows
109+
if runtime.GOOS == "windows" {
110+
cases = append(cases, []Case{
111+
{path: "\\", expected: true},
112+
{path: "C:/var/lib/something", expected: true},
113+
{path: "C:\\var\\lib\\something", expected: true},
114+
{path: "C:/", expected: true},
115+
{path: "C:\\", expected: true},
116+
{path: "C:/var//lib/something", expected: false},
117+
{path: "\\var\\\\lib\\something", expected: false},
118+
{path: "C:\\var\\\\lib\\something", expected: false},
119+
{path: "C:\\var\\..\\something", expected: false},
120+
{path: "\\var\\.\\lib\\something", expected: false},
121+
{path: "C:\\var\\.\\lib\\something", expected: false},
122+
}...)
123+
}
124+
125+
for _, tc := range cases {
126+
actual := IsPathClean(tc.path)
127+
if actual != tc.expected {
128+
t.Errorf("actual: %t, expected: %t, for path: %s\n", actual, tc.expected, tc.path)
129+
}
130+
}
131+
132+
}

pkg/util/filesystem/util_unix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package filesystem
2222
import (
2323
"fmt"
2424
"os"
25+
"path/filepath"
2526
)
2627

2728
// IsUnixDomainSocket returns whether a given file is a AF_UNIX socket file
@@ -35,3 +36,8 @@ func IsUnixDomainSocket(filePath string) (bool, error) {
3536
}
3637
return true, nil
3738
}
39+
40+
// IsAbs is same as filepath.IsAbs on Unix.
41+
func IsAbs(path string) bool {
42+
return filepath.IsAbs(path)
43+
}

pkg/util/filesystem/util_windows.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"fmt"
2424
"net"
2525
"os"
26+
"path/filepath"
27+
"strings"
2628
"time"
2729

2830
"k8s.io/apimachinery/pkg/util/wait"
@@ -85,3 +87,13 @@ func IsUnixDomainSocket(filePath string) (bool, error) {
8587
}
8688
return true, nil
8789
}
90+
91+
// IsAbs returns whether the given path is absolute or not.
92+
// On Windows, filepath.IsAbs will not return True for paths prefixed with a slash, even
93+
// though they can be used as absolute paths (https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats).
94+
//
95+
// WARN: It isn't safe to use this for API values which will propagate across systems (e.g. REST API values
96+
// that get validated on Unix, persisted, then consumed by Windows, etc).
97+
func IsAbs(path string) bool {
98+
return filepath.IsAbs(path) || strings.HasPrefix(path, `\`) || strings.HasPrefix(path, `/`)
99+
}

pkg/util/filesystem/util_windows_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,12 @@ func TestPendingUnixDomainSocket(t *testing.T) {
8989
wg.Wait()
9090
unixln.Close()
9191
}
92+
93+
func TestAbsWithSlash(t *testing.T) {
94+
// On Windows, filepath.IsAbs will not return True for paths prefixed with a slash
95+
assert.True(t, IsAbs("/test"))
96+
assert.True(t, IsAbs("\\test"))
97+
98+
assert.False(t, IsAbs("./local"))
99+
assert.False(t, IsAbs("local"))
100+
}

0 commit comments

Comments
 (0)