Skip to content

Commit ce30dd5

Browse files
authored
Ban network paths wholesale (#64832)
1 parent 0ee0284 commit ce30dd5

27 files changed

Lines changed: 663 additions & 23 deletions

‎tsc/internal/compiler/fileloader.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/microsoft/TypeScript/tsc/internal/core"
1616
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
1717
"github.com/microsoft/TypeScript/tsc/internal/module"
18+
"github.com/microsoft/TypeScript/tsc/internal/nativepath"
1819
"github.com/microsoft/TypeScript/tsc/internal/parser"
1920
"github.com/microsoft/TypeScript/tsc/internal/spanmap"
2021
"github.com/microsoft/TypeScript/tsc/internal/tracing"
@@ -687,6 +688,10 @@ func (p *fileLoader) getSourceFileFromReference(
687688
allowNonTsExtensions := options.AllowNonTsExtensions.IsTrue()
688689
diagnosticFileName := tspath.NormalizeSlashes(referenceText)
689690

691+
if nativepath.IsNetworkPath(fileName.AsString()) {
692+
return "", "", &sourceFileFromReferenceDiagnostic{message: diagnostics.Network_path_0_is_not_supported, args: []string{diagnosticFileName}}
693+
}
694+
690695
if fileName.HasExtension() {
691696
canonicalFileName := p.caseSensitivity.PathKey(tspath.RootedPath(fileName))
692697
if !allowNonTsExtensions && !p.isSupportedExtension(canonicalFileName) {

‎tsc/internal/compiler/program.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/microsoft/TypeScript/tsc/internal/locale"
2222
"github.com/microsoft/TypeScript/tsc/internal/module"
2323
"github.com/microsoft/TypeScript/tsc/internal/modulespecifiers"
24+
"github.com/microsoft/TypeScript/tsc/internal/nativepath"
2425
"github.com/microsoft/TypeScript/tsc/internal/outputpaths"
2526
"github.com/microsoft/TypeScript/tsc/internal/packagejson"
2627
"github.com/microsoft/TypeScript/tsc/internal/parser"
@@ -1181,6 +1182,8 @@ func (p *Program) verifyCompilerOptions() {
11811182
}
11821183
if !tspath.PathIsRelative(subst) && !tspath.PathIsAbsolute(subst) {
11831184
createDiagnosticForOptionPathKeyValue(key, i, diagnostics.Non_relative_paths_are_not_allowed_Did_you_forget_a_leading_Slash)
1185+
} else if nativepath.IsNetworkPath(tspath.GetNormalizedAbsolutePath(subst, options.GetPathsBasePath(p.GetCurrentDirectory()))) {
1186+
createDiagnosticForOptionPathKeyValue(key, i, diagnostics.Network_path_0_is_not_supported, subst)
11841187
}
11851188
}
11861189
}

‎tsc/internal/diagnostics/diagnosticMessages.generated.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,7 @@
11941194
"_0_is_only_assignable_to_the_non_distributed_1_but_1_has_been_distributed_here_5113": "'{0}' is only assignable to the non-distributed '{1}', but '{1}' has been distributed here.",
11951195
"Instantiations_of_type_0_appear_infinitely_circular_5114": "Instantiations of type '{0}' appear infinitely circular.",
11961196
"Instantiations_of_the_following_types_appear_infinitely_circular_Colon_0_5115": "Instantiations of the following types appear infinitely circular: {0}.",
1197+
"Network_path_0_is_not_supported_5116": "Network path '{0}' is not supported.",
11971198
"Generates_a_sourcemap_for_each_corresponding_d_ts_file_6000": "Generates a sourcemap for each corresponding '.d.ts' file.",
11981199
"Concatenate_and_emit_output_to_single_file_6001": "Concatenate and emit output to single file.",
11991200
"Generates_corresponding_d_ts_file_6002": "Generates corresponding '.d.ts' file.",

‎tsc/internal/diagnostics/diagnosticMessages.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4792,6 +4792,10 @@
47924792
"category": "Error",
47934793
"code": 5115
47944794
},
4795+
"Network path '{0}' is not supported.": {
4796+
"category": "Error",
4797+
"code": 5116
4798+
},
47954799

47964800
"Generates a sourcemap for each corresponding '.d.ts' file.": {
47974801
"category": "Message",

‎tsc/internal/diagnostics/diagnostics_generated.go‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tsc/internal/fswatch/watcher.go‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ var errRootPath = errors.New("fswatch: cannot watch a root path")
2424
// [Watcher.WatchFile] when the supplied path is not absolute.
2525
var errNotAbsolute = errors.New("fswatch: path must be absolute")
2626

27+
// errNetworkPath is returned by [Watcher.WatchDirectory] and
28+
// [Watcher.WatchFile] when the supplied path may refer to a network
29+
// location. See [nativepath.IsNetworkPath].
30+
var errNetworkPath = errors.New("fswatch: network paths are not supported")
31+
2732
// ErrOverflow indicates that the kernel event queue overflowed and
2833
// some filesystem changes were missed. The watch remains
2934
// active; further events will continue to be delivered. Callers
@@ -523,6 +528,10 @@ func (w *watcher) WatchDirectories(requests []WatchDirectoryRequest) ([]Watch, e
523528
rollback()
524529
return nil, errNilCallback
525530
}
531+
if nativepath.IsNetworkPath(dir) {
532+
rollback()
533+
return nil, errNetworkPath
534+
}
526535
dir = filepath.Clean(dir)
527536
if !filepath.IsAbs(dir) {
528537
rollback()
@@ -595,6 +604,9 @@ func (w *watcher) WatchFile(path string, fn WatchCallback) (Watch, error) {
595604
if !w.Available() {
596605
return nil, ErrUnavailable
597606
}
607+
if nativepath.IsNetworkPath(path) {
608+
return nil, errNetworkPath
609+
}
598610
path = filepath.Clean(path)
599611
if !filepath.IsAbs(path) {
600612
return nil, errNotAbsolute
@@ -856,7 +868,9 @@ func physicalDirFor(dir string) string {
856868
if err != nil {
857869
return dir
858870
}
859-
if realpath == dir {
871+
// A mapped drive resolves to the network share behind it; keep watching
872+
// it through the drive letter rather than opening the share directly.
873+
if realpath == dir || nativepath.IsNetworkPath(realpath) {
860874
return dir
861875
}
862876
return canonicalizePath(filepath.Clean(realpath))

‎tsc/internal/fswatch/watcher_test.go‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,35 @@ func TestWatchDirectoriesBatch(t *testing.T) {
15271527
})
15281528
}
15291529

1530+
func TestWatchRejectsNetworkPaths(t *testing.T) {
1531+
t.Parallel()
1532+
runForEachWatcher(t, func(t testingT, watcherImpl Watcher) {
1533+
dir := newTmpDir(t)
1534+
r := newRecorder(t)
1535+
r.watcher = watcherImpl
1536+
1537+
networkPaths := []string{`\\server\share`, `\??\UNC\server\share`}
1538+
if runtime.GOOS != "windows" {
1539+
// On POSIX systems, //dir resolves to /dir, so this is an existing
1540+
// directory that must still be refused.
1541+
networkPaths = append(networkPaths, "/"+dir)
1542+
}
1543+
for _, networkPath := range networkPaths {
1544+
_, err := watcherImpl.WatchDirectories([]WatchDirectoryRequest{
1545+
{Dir: dir, Callback: r.callback},
1546+
{Dir: networkPath, Callback: r.callback},
1547+
})
1548+
if !errors.Is(err, errNetworkPath) {
1549+
t.Fatalf("WatchDirectories(%q) error = %v, want %v", networkPath, err, errNetworkPath)
1550+
}
1551+
_, err = watcherImpl.WatchFile(networkPath+"/file.ts", r.callback)
1552+
if !errors.Is(err, errNetworkPath) {
1553+
t.Fatalf("WatchFile(%q) error = %v, want %v", networkPath, err, errNetworkPath)
1554+
}
1555+
}
1556+
})
1557+
}
1558+
15301559
type countingWatcherImpl struct {
15311560
watcherBase
15321561
subscribed []*dirWatch

‎tsc/internal/nativepath/network.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package nativepath
2+
3+
// IsNetworkPath reports whether Windows may resolve path over the network.
4+
// This includes UNC paths (`//server/share`), Win32 device paths (`//?/...`,
5+
// `//./...`), and NT object paths (`/??/...`), with either separator. Device
6+
// and object paths are included even when they name local volumes, because
7+
// their `UNC` and `GLOBALROOT` forms reach the network redirector.
8+
//
9+
// Accessing such a path can make Windows authenticate to an arbitrary host,
10+
// leaking credentials, so these paths are never accessed on any platform.
11+
func IsNetworkPath(path string) bool {
12+
if len(path) < 2 || !isAnyDirectorySeparator(path[0]) {
13+
return false
14+
}
15+
if isAnyDirectorySeparator(path[1]) {
16+
return true
17+
}
18+
return len(path) >= 3 && path[1] == '?' && path[2] == '?' && (len(path) == 3 || isAnyDirectorySeparator(path[3]))
19+
}
20+
21+
func isAnyDirectorySeparator(char byte) bool {
22+
return char == '/' || char == '\\'
23+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package nativepath
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
)
8+
9+
func TestIsNetworkPath(t *testing.T) {
10+
t.Parallel()
11+
for _, path := range []string{
12+
"//server",
13+
"//server/share/file.ts",
14+
"\\\\server\\share\\file.ts",
15+
"/\\server/share",
16+
"\\/server/share",
17+
"//?/UNC/server/share",
18+
"\\\\?\\UNC\\server\\share",
19+
"//./UNC/server/share",
20+
"\\\\.\\GLOBALROOT\\Device\\Mup\\server\\share",
21+
"\\\\?\\c:\\file.ts",
22+
"\\\\wsl$\\Ubuntu\\home",
23+
"//wsl.localhost/Ubuntu/home",
24+
"/??",
25+
"/??/UNC/server/share",
26+
"\\??\\UNC\\server\\share",
27+
"\\??\\GLOBALROOT\\Device\\Mup\\server\\share",
28+
} {
29+
assert.Assert(t, IsNetworkPath(path), path)
30+
}
31+
for _, path := range []string{
32+
"",
33+
"/",
34+
"\\",
35+
"a",
36+
"a//b",
37+
"./a",
38+
"../a",
39+
"/a",
40+
"/a//b",
41+
"/?",
42+
"/???",
43+
"/??a",
44+
"/a/??/b",
45+
"c:",
46+
"c:/",
47+
"c:\\file.ts",
48+
"c:file.ts",
49+
"file:///c:/file.ts",
50+
"file://server/share",
51+
"^/untitled/ts-nul-authority/Untitled-1",
52+
"bundled:///libs/lib.d.ts",
53+
} {
54+
assert.Assert(t, !IsNetworkPath(path), path)
55+
}
56+
}

‎tsc/internal/pprof/pprof.go‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,22 @@ import (
1010
"runtime/pprof"
1111
"sync"
1212
"time"
13+
14+
"github.com/microsoft/TypeScript/tsc/internal/nativepath"
1315
)
1416

17+
// makeProfileDir creates profileDir, refusing network paths so that
18+
// profiling requests cannot trigger connections to remote hosts.
19+
func makeProfileDir(profileDir string) error {
20+
if nativepath.IsNetworkPath(profileDir) {
21+
return errors.New("network paths are not supported: " + profileDir)
22+
}
23+
if err := os.MkdirAll(profileDir, 0o755); err != nil {
24+
return fmt.Errorf("failed to create profile directory: %w", err)
25+
}
26+
return nil
27+
}
28+
1529
type ProfileSession struct {
1630
cpuFilePath string
1731
memFilePath string
@@ -21,7 +35,7 @@ type ProfileSession struct {
2135

2236
// BeginProfiling starts CPU and memory profiling, writing the profiles to the specified directory.
2337
func BeginProfiling(profileDir string, logWriter io.Writer) *ProfileSession {
24-
if err := os.MkdirAll(profileDir, 0o755); err != nil {
38+
if err := makeProfileDir(profileDir); err != nil {
2539
panic(err)
2640
}
2741

@@ -80,8 +94,8 @@ func (c *CPUProfiler) StartCPUProfile(profileDir string) error {
8094
return errors.New("CPU profiling already in progress")
8195
}
8296

83-
if err := os.MkdirAll(profileDir, 0o755); err != nil {
84-
return fmt.Errorf("failed to create profile directory: %w", err)
97+
if err := makeProfileDir(profileDir); err != nil {
98+
return err
8599
}
86100

87101
cpuProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-%d-cpuprofile.pb.gz", os.Getpid(), time.Now().UnixMilli()))
@@ -122,8 +136,8 @@ func (c *CPUProfiler) StopCPUProfile() (string, error) {
122136

123137
// SaveHeapProfile saves a heap profile to the specified directory.
124138
func SaveHeapProfile(profileDir string) (string, error) {
125-
if err := os.MkdirAll(profileDir, 0o755); err != nil {
126-
return "", fmt.Errorf("failed to create profile directory: %w", err)
139+
if err := makeProfileDir(profileDir); err != nil {
140+
return "", err
127141
}
128142

129143
heapProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-%d-heapprofile.pb.gz", os.Getpid(), time.Now().UnixMilli()))
@@ -144,8 +158,8 @@ func SaveHeapProfile(profileDir string) (string, error) {
144158

145159
// SaveAllocProfile saves an allocation profile to the specified directory.
146160
func SaveAllocProfile(profileDir string) (string, error) {
147-
if err := os.MkdirAll(profileDir, 0o755); err != nil {
148-
return "", fmt.Errorf("failed to create profile directory: %w", err)
161+
if err := makeProfileDir(profileDir); err != nil {
162+
return "", err
149163
}
150164

151165
allocProfilePath := filepath.Join(profileDir, fmt.Sprintf("%d-%d-allocprofile.pb.gz", os.Getpid(), time.Now().UnixMilli()))

0 commit comments

Comments
 (0)