-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix --preserve-symlinks, Enhancement to exploit #10132
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ const path = require('path'); | |
const internalModuleReadFile = process.binding('fs').internalModuleReadFile; | ||
const internalModuleStat = process.binding('fs').internalModuleStat; | ||
const preserveSymlinks = !!process.binding('config').preserveSymlinks; | ||
const adjacentNodeModules = !!process.binding('config').adjacentNodeModules; | ||
|
||
// If obj.hasOwnProperty has been overridden, then calling | ||
// obj.hasOwnProperty(prop) will break. | ||
|
@@ -98,15 +99,15 @@ function readPackage(requestPath) { | |
return pkg; | ||
} | ||
|
||
function tryPackage(requestPath, exts, isMain) { | ||
function tryPackage(requestPath, exts) { | ||
var pkg = readPackage(requestPath); | ||
|
||
if (!pkg) return false; | ||
|
||
var filename = path.resolve(requestPath, pkg); | ||
return tryFile(filename, isMain) || | ||
tryExtensions(filename, exts, isMain) || | ||
tryExtensions(path.resolve(filename, 'index'), exts, isMain); | ||
return tryFile(filename) || | ||
tryExtensions(filename, exts) || | ||
tryExtensions(path.resolve(filename, 'index'), exts); | ||
} | ||
|
||
// In order to minimize unnecessary lstat() calls, | ||
|
@@ -118,9 +119,9 @@ const realpathCache = new Map(); | |
// if using --preserve-symlinks and isMain is false, | ||
// keep symlinks intact, otherwise resolve to the | ||
// absolute realpath. | ||
function tryFile(requestPath, isMain) { | ||
function tryFile(requestPath) { | ||
const rc = stat(requestPath); | ||
if (preserveSymlinks && !isMain) { | ||
if (preserveSymlinks) { | ||
return rc === 0 && path.resolve(requestPath); | ||
} | ||
return rc === 0 && toRealPath(requestPath); | ||
|
@@ -133,9 +134,9 @@ function toRealPath(requestPath) { | |
} | ||
|
||
// given a path check a the file exists with any of the set extensions | ||
function tryExtensions(p, exts, isMain) { | ||
function tryExtensions(p, exts) { | ||
for (var i = 0; i < exts.length; i++) { | ||
const filename = tryFile(p + exts[i], isMain); | ||
const filename = tryFile(p + exts[i]); | ||
|
||
if (filename) { | ||
return filename; | ||
|
@@ -145,7 +146,7 @@ function tryExtensions(p, exts, isMain) { | |
} | ||
|
||
var warned = false; | ||
Module._findPath = function(request, paths, isMain) { | ||
Module._findPath = function(request, paths) { | ||
if (path.isAbsolute(request)) { | ||
paths = ['']; | ||
} else if (!paths || paths.length === 0) { | ||
|
@@ -172,36 +173,36 @@ Module._findPath = function(request, paths, isMain) { | |
const rc = stat(basePath); | ||
if (!trailingSlash) { | ||
if (rc === 0) { // File. | ||
if (preserveSymlinks && !isMain) { | ||
if (preserveSymlinks) { | ||
filename = path.resolve(basePath); | ||
} else { | ||
filename = toRealPath(basePath); | ||
} | ||
} else if (rc === 1) { // Directory. | ||
if (exts === undefined) | ||
exts = Object.keys(Module._extensions); | ||
filename = tryPackage(basePath, exts, isMain); | ||
filename = tryPackage(basePath, exts); | ||
} | ||
|
||
if (!filename) { | ||
// try it with each of the extensions | ||
if (exts === undefined) | ||
exts = Object.keys(Module._extensions); | ||
filename = tryExtensions(basePath, exts, isMain); | ||
filename = tryExtensions(basePath, exts); | ||
} | ||
} | ||
|
||
if (!filename && rc === 1) { // Directory. | ||
if (exts === undefined) | ||
exts = Object.keys(Module._extensions); | ||
filename = tryPackage(basePath, exts, isMain); | ||
filename = tryPackage(basePath, exts); | ||
} | ||
|
||
if (!filename && rc === 1) { // Directory. | ||
// try it with each of the extensions at "index" | ||
if (exts === undefined) | ||
exts = Object.keys(Module._extensions); | ||
filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); | ||
filename = tryExtensions(path.resolve(basePath, 'index'), exts); | ||
} | ||
|
||
if (filename) { | ||
|
@@ -253,9 +254,22 @@ if (process.platform === 'win32') { | |
// Use colon as an extra condition since we can get node_modules | ||
// path for dirver root like 'C:\node_modules' and don't need to | ||
// parse driver name. | ||
if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/) { | ||
if (p !== nmLen) | ||
paths.push(from.slice(0, last) + '\\node_modules'); | ||
if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/ | ||
|| (adjacentNodeModules && code === 46/*.*/)) { | ||
if (p !== nmLen) { | ||
var parent = from.slice(0, last); | ||
paths.push(parent + '\\node_modules'); | ||
|
||
if (adjacentNodeModules) { | ||
paths.push(parent + '+node_modules'); | ||
if (code === 46/*.*/) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Braces are missing for this conditional (even if they are syntactically optional, they're required for the project's style). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
while (i > 1) { | ||
const code = from.charCodeAt(--i); | ||
if (code === 92/*\*/ || code === 47/*/*/ || code === 58/*:*/) | ||
break; | ||
} | ||
} | ||
} | ||
last = i; | ||
p = 0; | ||
} else if (p !== -1) { | ||
|
@@ -267,6 +281,9 @@ if (process.platform === 'win32') { | |
} | ||
} | ||
|
||
// superfluous "/.node_modules" | ||
if (adjacentNodeModules) paths.pop(); | ||
|
||
return paths; | ||
}; | ||
} else { // posix | ||
|
@@ -287,9 +304,17 @@ if (process.platform === 'win32') { | |
var last = from.length; | ||
for (var i = from.length - 1; i >= 0; --i) { | ||
const code = from.charCodeAt(i); | ||
if (code === 47/*/*/) { | ||
if (p !== nmLen) | ||
paths.push(from.slice(0, last) + '/node_modules'); | ||
if (code === 47/*/*/ || (adjacentNodeModules && code === 46/*.*/)) { | ||
if (p !== nmLen) { | ||
var parent = from.slice(0, last); | ||
paths.push(parent + '/node_modules'); | ||
|
||
if (adjacentNodeModules) { | ||
paths.push(parent + '+node_modules'); | ||
if (code === 46/*.*/) | ||
while (i > 1 && from.charCodeAt(--i) !== 47/*/*/); | ||
} | ||
} | ||
last = i; | ||
p = 0; | ||
} else if (p !== -1) { | ||
|
@@ -402,6 +427,34 @@ Module._resolveLookupPaths = function(request, parent) { | |
return [id, [path.dirname(parent.filename)]]; | ||
}; | ||
|
||
// when preserving symlinks, if the main file is both a file symlink AND | ||
// has execute permission (on windows, its extension is in PATHEXT), then | ||
// its target is simply read and resolved, potentially relative to the | ||
// symlink's dirname. in all other cases the original path is preserved. | ||
Module._resolveMainRequest = function(request) { | ||
if(!preserveSymlinks) return request | ||
|
||
request = Module._resolveFilename(request, null) | ||
if(!fs.lstatSync(request).isSymbolicLink()) return request | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a space between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
let isExecutable = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing semicolon here and all other instances. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
if(process.platform === 'win32') { | ||
const execExts = (process.env.PATHEXT || '').split(path.delimiter) | ||
const requestExt = path.extname(request).toLowerCase() | ||
isExecutable = | ||
execExts.some(execExt => requestExt === execExt.trim().toLowerCase()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Braces should be included for arrow functions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} else try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is confusing, please just include the try-catch inside a normal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
fs.accessSync(request, fs.constants.X_OK) | ||
isExecutable = true | ||
} catch(e) {} | ||
|
||
if(!isExecutable) return request | ||
|
||
const targetPath = fs.readlinkSync(request) | ||
return path.isAbsolute(targetPath) | ||
? targetPath | ||
: path.resolve(path.dirname(request), targetPath) | ||
} | ||
|
||
// Check the cache for the requested file. | ||
// 1. If a module already exists in the cache: return its exports object. | ||
|
@@ -415,14 +468,23 @@ Module._load = function(request, parent, isMain) { | |
debug('Module._load REQUEST %s parent: %s', request, parent.id); | ||
} | ||
|
||
var filename = Module._resolveFilename(request, parent, isMain); | ||
var requestPath = isMain ? Module._resolveMainRequest(request) : request; | ||
var filename = Module._resolveFilename(requestPath, parent); | ||
var doesNonInternalExist = NativeModule.nonInternalExists(filename); | ||
|
||
// when supporting symlinks, always cache by realpath to fix | ||
// memory bloat and addon crashing. module's __dirname will be that | ||
// of first requestPath that require()d it, which could be a symlink. | ||
var cachePath = preserveSymlinks && !doesNonInternalExist | ||
? toRealPath(filename) | ||
: filename; | ||
|
||
var cachedModule = Module._cache[filename]; | ||
var cachedModule = Module._cache[cachePath]; | ||
if (cachedModule) { | ||
return cachedModule.exports; | ||
} | ||
|
||
if (NativeModule.nonInternalExists(filename)) { | ||
if (doesNonInternalExist) { | ||
debug('load native module %s', request); | ||
return NativeModule.require(filename); | ||
} | ||
|
@@ -434,26 +496,26 @@ Module._load = function(request, parent, isMain) { | |
module.id = '.'; | ||
} | ||
|
||
Module._cache[filename] = module; | ||
Module._cache[cachePath] = module; | ||
|
||
tryModuleLoad(module, filename); | ||
tryModuleLoad(module, filename, cachePath); | ||
|
||
return module.exports; | ||
}; | ||
|
||
function tryModuleLoad(module, filename) { | ||
function tryModuleLoad(module, filename, cachePath) { | ||
var threw = true; | ||
try { | ||
module.load(filename); | ||
threw = false; | ||
} finally { | ||
if (threw) { | ||
delete Module._cache[filename]; | ||
delete Module._cache[cachePath]; | ||
} | ||
} | ||
} | ||
|
||
Module._resolveFilename = function(request, parent, isMain) { | ||
Module._resolveFilename = function(request, parent) { | ||
if (NativeModule.nonInternalExists(request)) { | ||
return request; | ||
} | ||
|
@@ -465,7 +527,7 @@ Module._resolveFilename = function(request, parent, isMain) { | |
// look up the filename first, since that's the cache key. | ||
debug('looking for %j in %j', id, paths); | ||
|
||
var filename = Module._findPath(request, paths, isMain); | ||
var filename = Module._findPath(request, paths); | ||
if (!filename) { | ||
var err = new Error("Cannot find module '" + request + "'"); | ||
err.code = 'MODULE_NOT_FOUND'; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Operators go at the end of the previous line. Also, the next line should match the indentation of the start of the first argument on the previous lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done