diff --git a/backend/local/local.go b/backend/local/local.go index 2133279d0..c4f526f8e 100644 --- a/backend/local/local.go +++ b/backend/local/local.go @@ -1726,9 +1726,18 @@ func cleanRootPath(s string, noUNC bool, enc encoder.MultiEncoder) string { if runtime.GOOS == "windows" { s = vol + s } - s2, err := filepath.Abs(s) - if err == nil { - s = s2 + // UNC paths on Windows must be absolute, so make the path absolute + // there. On other platforms filepath.Abs would prepend the current + // directory, but the resulting absolute string is not guaranteed to + // refer to the same directory as the original relative path - for + // example when the current directory is shadowed by a mount or has been + // removed - so just clean it lexically instead. + if runtime.GOOS == "windows" { + if s2, err := filepath.Abs(s); err == nil { + s = s2 + } + } else { + s = filepath.Clean(s) } if !noUNC { // Convert to UNC. It does nothing on non windows platforms. diff --git a/backend/local/tests_test.go b/backend/local/tests_test.go index f85ea0f15..ba66676fd 100644 --- a/backend/local/tests_test.go +++ b/backend/local/tests_test.go @@ -30,3 +30,29 @@ func TestCleanWindows(t *testing.T) { } } } + +// Relative roots must stay relative so the OS resolves them against the +// live working directory rather than a canonicalised string that may no +// longer refer to the same directory (#9510). +var testsRelative = [][2]string{ + {".", "."}, + {"./", "."}, + {"sub/dir", "sub/dir"}, + {"sub/dir/", "sub/dir"}, + {"./sub/dir", "sub/dir"}, + {"sub/../dir", "dir"}, + {"..", ".."}, +} + +func TestCleanRootPathRelative(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skipf("non-windows only") + } + for _, test := range testsRelative { + got := cleanRootPath(test[0], true, encoder.OS) + expect := test[1] + if got != expect { + t.Fatalf("got %q, expected %q", got, expect) + } + } +}