Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0b631bb

Browse filesBrowse files
RafaelGSSrichardlau
authored andcommitted
lib: handle windows reserved device names on UNC
We have found that UNC paths weren't covered when .join/.normalize windows reserved device names (COM1, LPT1). PR-URL: #59286 Backport-PR-URL: #59831 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 9cc89f5 commit 0b631bb
Copy full SHA for 0b631bb

File tree

Expand file treeCollapse file tree

3 files changed

+60
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+60
-0
lines changed
Open diff view settings
Collapse file

‎lib/path.js‎

Copy file name to clipboardExpand all lines: lib/path.js
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,13 @@ const win32 = {
415415
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
416416
device = `\\\\${firstPart}`;
417417
rootEnd = 4;
418+
const colonIndex = StringPrototypeIndexOf(path, ':');
419+
// Special case: handle \\?\COM1: or similar reserved device paths
420+
const possibleDevice = StringPrototypeSlice(path, 4, colonIndex + 1);
421+
if (isWindowsReservedName(possibleDevice, possibleDevice.length - 1)) {
422+
device = `\\\\?\\${possibleDevice}`;
423+
rootEnd = 4 + possibleDevice.length;
424+
}
418425
} else if (j === len) {
419426
// We matched a UNC root only
420427
// Return the normalized version of the UNC root since there
@@ -573,6 +580,36 @@ const win32 = {
573580
joined = `\\${StringPrototypeSlice(joined, slashCount)}`;
574581
}
575582

583+
// Skip normalization when reserved device names are present
584+
const parts = [];
585+
let part = '';
586+
587+
for (let i = 0; i < joined.length; i++) {
588+
if (joined[i] === '\\') {
589+
if (part) parts.push(part);
590+
part = '';
591+
// Skip consecutive backslashes
592+
while (i + 1 < joined.length && joined[i + 1] === '\\') i++;
593+
} else {
594+
part += joined[i];
595+
}
596+
}
597+
// Add the final part if any
598+
if (part) parts.push(part);
599+
600+
// Check if any part has a Windows reserved name
601+
if (parts.some((p) => {
602+
const colonIndex = StringPrototypeIndexOf(p, ':');
603+
return colonIndex !== -1 && isWindowsReservedName(p, colonIndex);
604+
})) {
605+
// Replace forward slashes with backslashes
606+
let result = '';
607+
for (let i = 0; i < joined.length; i++) {
608+
result += joined[i] === '/' ? '\\' : joined[i];
609+
}
610+
return result;
611+
}
612+
576613
return win32.normalize(joined);
577614
},
578615

Collapse file

‎test/parallel/test-path-join.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-path-join.js
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ joinTests.push([
110110
[['c:.', 'file'], 'c:file'],
111111
[['c:', '/'], 'c:\\'],
112112
[['c:', 'file'], 'c:\\file'],
113+
// UNC path join tests (Windows)
114+
[['\\server\\share', 'file.txt'], '\\server\\share\\file.txt'],
115+
[['\\server\\share', 'folder', 'another.txt'], '\\server\\share\\folder\\another.txt'],
116+
[['\\server\\share', 'COM1:'], '\\server\\share\\COM1:'],
117+
[['\\server\\share', 'path', 'LPT1:'], '\\server\\share\\path\\LPT1:'],
118+
[['\\fileserver\\public\\uploads', 'CON:..\\..\\..\\private\\db.conf'],
119+
'\\fileserver\\public\\uploads\\CON:..\\..\\..\\private\\db.conf'],
120+
113121
// Path traversal in previous versions of Node.js.
114122
[['./upload', '/../C:/Windows'], '.\\C:\\Windows'],
115123
[['upload', '../', 'C:foo'], '.\\C:foo'],
Collapse file

‎test/parallel/test-path-win32-normalize-device-names.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-path-win32-normalize-device-names.js
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ if (!common.isWindows) {
99
}
1010

1111
const normalizeDeviceNameTests = [
12+
// UNC paths: \\server\share\... is a Windows UNC path, where 'server' is the network server name and 'share'
13+
// is the shared folder. These are used for network file access and are subject to reserved device name
14+
// checks after the share.
15+
{ input: '\\\\server\\share\\COM1:', expected: '\\\\server\\share\\COM1:' },
16+
{ input: '\\\\server\\share\\PRN:', expected: '\\\\server\\share\\PRN:' },
17+
{ input: '\\\\server\\share\\AUX:', expected: '\\\\server\\share\\AUX:' },
18+
{ input: '\\\\server\\share\\LPT1:', expected: '\\\\server\\share\\LPT1:' },
19+
{ input: '\\\\server\\share\\COM1:\\foo\\bar', expected: '\\\\server\\share\\COM1:\\foo\\bar' },
20+
{ input: '\\\\server\\share\\path\\COM1:', expected: '\\\\server\\share\\path\\COM1:' },
21+
{ input: '\\\\server\\share\\COM1:..\\..\\..\\..\\Windows', expected: '\\\\server\\share\\Windows' },
22+
{ input: '\\\\server\\share\\path\\to\\LPT9:..\\..\\..\\..\\..\\..\\..\\..\\..\\file.txt',
23+
expected: '\\\\server\\share\\file.txt' },
24+
1225
{ input: 'CON', expected: 'CON' },
1326
{ input: 'con', expected: 'con' },
1427
{ input: 'CON:', expected: '.\\CON:.' },
@@ -81,6 +94,8 @@ const normalizeDeviceNameTests = [
8194
// Test cases from original vulnerability reports or similar scenarios
8295
{ input: 'COM1:.\\..\\..\\foo.js', expected: '.\\COM1:..\\..\\foo.js' },
8396
{ input: 'LPT1:.\\..\\..\\another.txt', expected: '.\\LPT1:..\\..\\another.txt' },
97+
// UNC paths
98+
{ input: '\\\\?\\COM1:.\\..\\..\\foo2.js', expected: '\\\\?\\COM1:\\foo2.js' },
8499

85100
// Paths with device names not at the beginning
86101
{ input: 'C:\\CON', expected: 'C:\\CON' },

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.