GeezerVison is showing github.com — lib: handle windows reserved device names on UNC · nodejs/node@47543a7
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/path.js +37Lines changed: 37 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -400,6 +400,13 @@ const win32 = { | 400 | 400 | // We matched a device root (e.g. \\\\.\\PHYSICALDRIVE0) | 401 | 401 | device = `\\\\${firstPart}`; | 402 | 402 | rootEnd = 4; | | 403 | + const colonIndex = StringPrototypeIndexOf(path, ':'); | | 404 | + // Special case: handle \\?\COM1: or similar reserved device paths | | 405 | + const possibleDevice = StringPrototypeSlice(path, 4, colonIndex + 1); | | 406 | + if (isWindowsReservedName(possibleDevice, possibleDevice.length - 1)) { | | 407 | + device = `\\\\?\\${possibleDevice}`; | | 408 | + rootEnd = 4 + possibleDevice.length; | | 409 | + } | 403 | 410 | } else if (j === len) { | 404 | 411 | // We matched a UNC root only | 405 | 412 | // Return the normalized version of the UNC root since there | @@ -558,6 +565,36 @@ const win32 = { | 558 | 565 | joined = `\\${StringPrototypeSlice(joined, slashCount)}`; | 559 | 566 | } | 560 | 567 |
| | 568 | + // Skip normalization when reserved device names are present | | 569 | + const parts = []; | | 570 | + let part = ''; | | 571 | + | | 572 | + for (let i = 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 | + const colonIndex = StringPrototypeIndexOf(p, ':'); | | 588 | + return colonIndex !== -1 && isWindowsReservedName(p, colonIndex); | | 589 | + })) { | | 590 | + // Replace forward slashes with backslashes | | 591 | + let result = ''; | | 592 | + for (let i = 0; i < joined.length; i++) { | | 593 | + result += joined[i] === '/' ? '\\' : joined[i]; | | 594 | + } | | 595 | + return result; | | 596 | + } | | 597 | + | 561 | 598 | return win32.normalize(joined); | 562 | 599 | }, | 563 | 600 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-path-join.js +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -110,6 +110,14 @@ joinTests.push([ | 110 | 110 | [['c:.', 'file'], 'c:file'], | 111 | 111 | [['c:', '/'], 'c:\\'], | 112 | 112 | [['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 | + | 113 | 121 | // Path traversal in previous versions of Node.js. | 114 | 122 | [['./upload', '/../C:/Windows'], '.\\C:\\Windows'], | 115 | 123 | [['upload', '../', 'C:foo'], '.\\C:foo'], | |
Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -9,6 +9,19 @@ if (!common.isWindows) { | 9 | 9 | } | 10 | 10 |
| 11 | 11 | 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 | + | 12 | 25 | { input: 'CON', expected: 'CON' }, | 13 | 26 | { input: 'con', expected: 'con' }, | 14 | 27 | { input: 'CON:', expected: '.\\CON:.' }, | @@ -81,6 +94,8 @@ const normalizeDeviceNameTests = [ | 81 | 94 | // Test cases from original vulnerability reports or similar scenarios | 82 | 95 | { input: 'COM1:.\\..\\..\\foo.js', expected: '.\\COM1:..\\..\\foo.js' }, | 83 | 96 | { input: 'LPT1:.\\..\\..\\another.txt', expected: '.\\LPT1:..\\..\\another.txt' }, | | 97 | + // UNC paths | | 98 | + { input: '\\\\?\\COM1:.\\..\\..\\foo2.js', expected: '\\\\?\\COM1:\\foo2.js' }, | 84 | 99 |
| 85 | 100 | // Paths with device names not at the beginning | 86 | 101 | { input: 'C:\\CON', expected: 'C:\\CON' }, | |
|