Skip to content

Commit 47543a7

Browse files
committed
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 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 e4ca30e commit 47543a7

3 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎lib/path.js‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,13 @@ const win32 = {
400400
// We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0)
401401
device=`\\\\${firstPart}`;
402402
rootEnd=4;
403+
constcolonIndex=StringPrototypeIndexOf(path,':');
404+
// Special case: handle \\?\COM1: or similar reserved device paths
405+
constpossibleDevice=StringPrototypeSlice(path,4,colonIndex+1);
406+
if(isWindowsReservedName(possibleDevice,possibleDevice.length-1)){
407+
device=`\\\\?\\${possibleDevice}`;
408+
rootEnd=4+possibleDevice.length;
409+
}
403410
}elseif(j===len){
404411
// We matched a UNC root only
405412
// Return the normalized version of the UNC root since there
@@ -558,6 +565,36 @@ const win32 = {
558565
joined=`\\${StringPrototypeSlice(joined,slashCount)}`;
559566
}
560567

568+
// Skip normalization when reserved device names are present
569+
constparts=[];
570+
letpart='';
571+
572+
for(leti=0;i<joined.length;i++){
573+
if(joined[i]==='\\'){
574+
if(part)parts.push(part);
575+
part='';
576+
// Skip consecutive backslashes
577+
while(i+1<joined.length&&joined[i+1]==='\\')i++;
578+
}else{
579+
part+=joined[i];
580+
}
581+
}
582+
// Add the final part if any
583+
if(part)parts.push(part);
584+
585+
// Check if any part has a Windows reserved name
586+
if(parts.some((p)=>{
587+
constcolonIndex=StringPrototypeIndexOf(p,':');
588+
returncolonIndex!==-1&&isWindowsReservedName(p,colonIndex);
589+
})){
590+
// Replace forward slashes with backslashes
591+
letresult='';
592+
for(leti=0;i<joined.length;i++){
593+
result+=joined[i]==='/' ? '\\' : joined[i];
594+
}
595+
returnresult;
596+
}
597+
561598
returnwin32.normalize(joined);
562599
},
563600

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

Lines 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'],

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ if (!common.isWindows) {
99
}
1010

1111
constnormalizeDeviceNameTests=[
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)