| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f0a8916 commit 26be208
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,7 +59,6 @@ const { | |||
| 59 | 59 | } = constants; | |
| 60 | 60 | ||
| 61 | 61 | const pathModule = require('path'); | |
| 62 | - const { isAbsolute } = pathModule; | ||
| 63 | 62 | const { isArrayBufferView } = require('internal/util/types'); | |
| 64 | 63 | ||
| 65 | 64 | const binding = internalBinding('fs'); | |
@@ -1777,18 +1776,12 @@ function symlink(target, path, type, callback) { | |||
| 1777 | 1776 | validateOneOf(type, 'type', ['dir', 'file', 'junction', null, undefined]); | |
| 1778 | 1777 | } | |
| 1779 | 1778 | ||
| 1780 | - if (permission.isEnabled()) { | ||
| 1781 | - // The permission model's security guarantees fall apart in the presence of | ||
| 1782 | - // relative symbolic links. Thus, we have to prevent their creation. | ||
| 1783 | - if (BufferIsBuffer(target)) { | ||
| 1784 | - if (!isAbsolute(BufferToString(target))) { | ||
| 1785 | - callback(new ERR_ACCESS_DENIED('relative symbolic link target')); | ||
| 1786 | - return; | ||
| 1787 | - } | ||
| 1788 | - } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { | ||
| 1789 | - callback(new ERR_ACCESS_DENIED('relative symbolic link target')); | ||
| 1790 | - return; | ||
| 1791 | - } | ||
| 1779 | + // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass | ||
| 1780 | + // the permission model security guarantees. Thus, this API is disabled unless fs.read | ||
| 1781 | + // and fs.write permission has been given. | ||
| 1782 | + if (permission.isEnabled() && !permission.has('fs')) { | ||
| 1783 | + callback(new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.')); | ||
| 1784 | + return; | ||
| 1792 | 1785 | } | |
| 1793 | 1786 | ||
| 1794 | 1787 | target = getValidatedPath(target, 'target'); | |
@@ -1852,16 +1845,11 @@ function symlinkSync(target, path, type) { | |||
| 1852 | 1845 | } | |
| 1853 | 1846 | } | |
| 1854 | 1847 | ||
| 1855 | - if (permission.isEnabled()) { | ||
| 1856 | - // The permission model's security guarantees fall apart in the presence of | ||
| 1857 | - // relative symbolic links. Thus, we have to prevent their creation. | ||
| 1858 | - if (BufferIsBuffer(target)) { | ||
| 1859 | - if (!isAbsolute(BufferToString(target))) { | ||
| 1860 | - throw new ERR_ACCESS_DENIED('relative symbolic link target'); | ||
| 1861 | - } | ||
| 1862 | - } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { | ||
| 1863 | - throw new ERR_ACCESS_DENIED('relative symbolic link target'); | ||
| 1864 | - } | ||
| 1848 | + // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass | ||
| 1849 | + // the permission model security guarantees. Thus, this API is disabled unless fs.read | ||
| 1850 | + // and fs.write permission has been given. | ||
| 1851 | + if (permission.isEnabled() && !permission.has('fs')) { | ||
| 1852 | + throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.'); | ||
| 1865 | 1853 | } | |
| 1866 | 1854 | ||
| 1867 | 1855 | target = getValidatedPath(target, 'target'); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,7 +17,6 @@ const { | |||
| 17 | 17 | Symbol, | |
| 18 | 18 | SymbolAsyncDispose, | |
| 19 | 19 | Uint8Array, | |
| 20 | - uncurryThis, | ||
| 21 | 20 | } = primordials; | |
| 22 | 21 | ||
| 23 | 22 | const { fs: constants } = internalBinding('constants'); | |
@@ -31,8 +30,6 @@ const { | |||
| 31 | 30 | ||
| 32 | 31 | const binding = internalBinding('fs'); | |
| 33 | 32 | const { Buffer } = require('buffer'); | |
| 34 | - const { isBuffer: BufferIsBuffer } = Buffer; | ||
| 35 | - const BufferToString = uncurryThis(Buffer.prototype.toString); | ||
| 36 | 33 | ||
| 37 | 34 | const { | |
| 38 | 35 | AbortError, | |
@@ -88,8 +85,6 @@ const { | |||
| 88 | 85 | kValidateObjectAllowNullable, | |
| 89 | 86 | } = require('internal/validators'); | |
| 90 | 87 | const pathModule = require('path'); | |
| 91 | - const { isAbsolute } = pathModule; | ||
| 92 | - const { toPathIfFileURL } = require('internal/url'); | ||
| 93 | 88 | const { | |
| 94 | 89 | getLazy, | |
| 95 | 90 | kEmptyObject, | |
@@ -992,16 +987,11 @@ async function symlink(target, path, type) { | |||
| 992 | 987 | } | |
| 993 | 988 | } | |
| 994 | 989 | ||
| 995 | - if (permission.isEnabled()) { | ||
| 996 | - // The permission model's security guarantees fall apart in the presence of | ||
| 997 | - // relative symbolic links. Thus, we have to prevent their creation. | ||
| 998 | - if (BufferIsBuffer(target)) { | ||
| 999 | - if (!isAbsolute(BufferToString(target))) { | ||
| 1000 | - throw new ERR_ACCESS_DENIED('relative symbolic link target'); | ||
| 1001 | - } | ||
| 1002 | - } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { | ||
| 1003 | - throw new ERR_ACCESS_DENIED('relative symbolic link target'); | ||
| 1004 | - } | ||
| 990 | + // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass | ||
| 991 | + // the permission model security guarantees. Thus, this API is disabled unless fs.read | ||
| 992 | + // and fs.write permission has been given. | ||
| 993 | + if (permission.isEnabled() && !permission.has('fs')) { | ||
| 994 | + throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.'); | ||
| 1005 | 995 | } | |
| 1006 | 996 | ||
| 1007 | 997 | target = getValidatedPath(target, 'target'); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,8 +26,7 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; | |||
| 26 | 26 | fs.symlinkSync(path.join(readOnlyFolder, 'file'), path.join(readWriteFolder, 'link-to-read-only'), 'file'); | |
| 27 | 27 | }, common.expectsError({ | |
| 28 | 28 | code: 'ERR_ACCESS_DENIED', | |
| 29 | - permission: 'FileSystemWrite', | ||
| 30 | - resource: path.toNamespacedPath(path.join(readOnlyFolder, 'file')), | ||
| 29 | + message: 'fs.symlink API requires full fs.read and fs.write permissions.', | ||
| 31 | 30 | })); | |
| 32 | 31 | assert.throws(() => { | |
| 33 | 32 | fs.linkSync(path.join(readOnlyFolder, 'file'), path.join(readWriteFolder, 'link-to-read-only')); | |
@@ -37,18 +36,6 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; | |||
| 37 | 36 | resource: path.toNamespacedPath(path.join(readOnlyFolder, 'file')), | |
| 38 | 37 | })); | |
| 39 | 38 | ||
| 40 | - // App will be able to symlink to a writeOnlyFolder | ||
| 41 | - fs.symlink(path.join(readWriteFolder, 'file'), path.join(writeOnlyFolder, 'link-to-read-write'), 'file', (err) => { | ||
| 42 | - assert.ifError(err); | ||
| 43 | - // App will won't be able to read the symlink | ||
| 44 | - fs.readFile(path.join(writeOnlyFolder, 'link-to-read-write'), common.expectsError({ | ||
| 45 | - code: 'ERR_ACCESS_DENIED', | ||
| 46 | - permission: 'FileSystemRead', | ||
| 47 | - })); | ||
| 48 | - | ||
| 49 | - // App will be able to write to the symlink | ||
| 50 | - fs.writeFile(path.join(writeOnlyFolder, 'link-to-read-write'), 'some content', common.mustSucceed()); | ||
| 51 | - }); | ||
| 52 | 39 | fs.link(path.join(readWriteFolder, 'file'), path.join(writeOnlyFolder, 'link-to-read-write2'), (err) => { | |
| 53 | 40 | assert.ifError(err); | |
| 54 | 41 | // App will won't be able to read the link | |
@@ -66,8 +53,7 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; | |||
| 66 | 53 | fs.symlinkSync(path.join(readWriteFolder, 'file'), path.join(readOnlyFolder, 'link-to-read-only'), 'file'); | |
| 67 | 54 | }, common.expectsError({ | |
| 68 | 55 | code: 'ERR_ACCESS_DENIED', | |
| 69 | - permission: 'FileSystemWrite', | ||
| 70 | - resource: path.toNamespacedPath(path.join(readOnlyFolder, 'link-to-read-only')), | ||
| 56 | + message: 'fs.symlink API requires full fs.read and fs.write permissions.', | ||
| 71 | 57 | })); | |
| 72 | 58 | assert.throws(() => { | |
| 73 | 59 | fs.linkSync(path.join(readWriteFolder, 'file'), path.join(readOnlyFolder, 'link-to-read-only')); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -54,7 +54,6 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; | |||
| 54 | 54 | fs.readFileSync(blockedFile); | |
| 55 | 55 | }, common.expectsError({ | |
| 56 | 56 | code: 'ERR_ACCESS_DENIED', | |
| 57 | - permission: 'FileSystemRead', | ||
| 58 | 57 | })); | |
| 59 | 58 | assert.throws(() => { | |
| 60 | 59 | fs.appendFileSync(blockedFile, 'data'); | |
@@ -68,7 +67,6 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; | |||
| 68 | 67 | fs.symlinkSync(regularFile, blockedFolder + '/asdf', 'file'); | |
| 69 | 68 | }, common.expectsError({ | |
| 70 | 69 | code: 'ERR_ACCESS_DENIED', | |
| 71 | - permission: 'FileSystemWrite', | ||
| 72 | 70 | })); | |
| 73 | 71 | assert.throws(() => { | |
| 74 | 72 | fs.linkSync(regularFile, blockedFolder + '/asdf'); | |
@@ -82,12 +80,26 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; | |||
| 82 | 80 | fs.symlinkSync(blockedFile, path.join(__dirname, '/asdf'), 'file'); | |
| 83 | 81 | }, common.expectsError({ | |
| 84 | 82 | code: 'ERR_ACCESS_DENIED', | |
| 85 | - permission: 'FileSystemRead', | ||
| 86 | 83 | })); | |
| 87 | 84 | assert.throws(() => { | |
| 88 | 85 | fs.linkSync(blockedFile, path.join(__dirname, '/asdf')); | |
| 89 | 86 | }, common.expectsError({ | |
| 90 | 87 | code: 'ERR_ACCESS_DENIED', | |
| 91 | 88 | permission: 'FileSystemRead', | |
| 92 | 89 | })); | |
| 90 | + } | ||
| 91 | + | ||
| 92 | + // fs.symlink API is blocked by default | ||
| 93 | + { | ||
| 94 | + assert.throws(() => { | ||
| 95 | + fs.symlinkSync(regularFile, regularFile); | ||
| 96 | + }, common.expectsError({ | ||
| 97 | + message: 'fs.symlink API requires full fs.read and fs.write permissions.', | ||
| 98 | + code: 'ERR_ACCESS_DENIED', | ||
| 99 | + })); | ||
| 100 | + | ||
| 101 | + fs.symlink(regularFile, regularFile, common.expectsError({ | ||
| 102 | + message: 'fs.symlink API requires full fs.read and fs.write permissions.', | ||
| 103 | + code: 'ERR_ACCESS_DENIED', | ||
| 104 | + })); | ||
| 93 | 105 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,4 @@ | |||
| 1 | - // Flags: --permission --allow-fs-read=* --allow-fs-write=* | ||
| 1 | + // Flags: --permission --allow-fs-read=* | ||
| 2 | 2 | 'use strict'; | |
| 3 | 3 | ||
| 4 | 4 | const common = require('../common'); | |
@@ -15,7 +15,7 @@ const { symlinkSync, symlink, promises: { symlink: symlinkAsync } } = require('f | |||
| 15 | 15 | ||
| 16 | 16 | const error = { | |
| 17 | 17 | code: 'ERR_ACCESS_DENIED', | |
| 18 | - message: /relative symbolic link target/, | ||
| 18 | + message: /symlink API requires full fs\.read and fs\.write permissions/, | ||
| 19 | 19 | }; | |
| 20 | 20 | ||
| 21 | 21 | for (const targetString of ['a', './b/c', '../d', 'e/../f', 'C:drive-relative', 'ntfs:alternate']) { | |
@@ -32,14 +32,14 @@ for (const targetString of ['a', './b/c', '../d', 'e/../f', 'C:drive-relative', | |||
| 32 | 32 | } | |
| 33 | 33 | } | |
| 34 | 34 | ||
| 35 | - // Absolute should not throw | ||
| 35 | + // Absolute should throw too | ||
| 36 | 36 | for (const targetString of [path.resolve('.')]) { | |
| 37 | 37 | for (const target of [targetString, Buffer.from(targetString)]) { | |
| 38 | 38 | for (const path of [__filename]) { | |
| 39 | 39 | symlink(target, path, common.mustCall((err) => { | |
| 40 | 40 | assert(err); | |
| 41 | - assert.strictEqual(err.code, 'EEXIST'); | ||
| 42 | - assert.match(err.message, /file already exists/); | ||
| 41 | + assert.strictEqual(err.code, error.code); | ||
| 42 | + assert.match(err.message, error.message); | ||
| 43 | 43 | })); | |
| 44 | 44 | } | |
| 45 | 45 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,15 +27,26 @@ const commonPathWildcard = path.join(__filename, '../../common*'); | |||
| 27 | 27 | const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md'); | |
| 28 | 28 | const blockedFolder = tmpdir.resolve('subdirectory'); | |
| 29 | 29 | const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); | |
| 30 | + const allowedFolder = tmpdir.resolve('allowed-folder'); | ||
| 31 | + const traversalSymlink = path.join(allowedFolder, 'deep1', 'deep2', 'deep3', 'gotcha'); | ||
| 30 | 32 | ||
| 31 | 33 | { | |
| 32 | 34 | tmpdir.refresh(); | |
| 33 | 35 | fs.mkdirSync(blockedFolder); | |
| 36 | + // Create deep directory structure for path traversal test | ||
| 37 | + fs.mkdirSync(allowedFolder); | ||
| 38 | + fs.writeFileSync(path.resolve(allowedFolder, '../protected-file.md'), 'protected'); | ||
| 39 | + fs.mkdirSync(path.join(allowedFolder, 'deep1')); | ||
| 40 | + fs.mkdirSync(path.join(allowedFolder, 'deep1', 'deep2')); | ||
| 41 | + fs.mkdirSync(path.join(allowedFolder, 'deep1', 'deep2', 'deep3')); | ||
| 34 | 42 | } | |
| 35 | 43 | ||
| 36 | 44 | { | |
| 37 | 45 | // Symlink previously created | |
| 46 | + // fs.symlink API is allowed when full-read and full-write access | ||
| 38 | 47 | fs.symlinkSync(blockedFile, symlinkFromBlockedFile); | |
| 48 | + // Create symlink for path traversal test - symlink points to parent directory | ||
| 49 | + fs.symlinkSync(allowedFolder, traversalSymlink); | ||
| 39 | 50 | } | |
| 40 | 51 | ||
| 41 | 52 | { | |
@@ -44,6 +55,7 @@ const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); | |||
| 44 | 55 | [ | |
| 45 | 56 | '--permission', | |
| 46 | 57 | `--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, `--allow-fs-read=${symlinkFromBlockedFile}`, | |
| 58 | + `--allow-fs-read=${allowedFolder}`, | ||
| 47 | 59 | `--allow-fs-write=${symlinkFromBlockedFile}`, | |
| 48 | 60 | file, | |
| 49 | 61 | ], | |
@@ -53,6 +65,8 @@ const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); | |||
| 53 | 65 | BLOCKEDFOLDER: blockedFolder, | |
| 54 | 66 | BLOCKEDFILE: blockedFile, | |
| 55 | 67 | EXISTINGSYMLINK: symlinkFromBlockedFile, | |
| 68 | + TRAVERSALSYMLINK: traversalSymlink, | ||
| 69 | + ALLOWEDFOLDER: allowedFolder, | ||
| 56 | 70 | }, | |
| 57 | 71 | } | |
| 58 | 72 | ); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments