From e37598309fe15fb9b367250c9ec772dcbefe8429 Mon Sep 17 00:00:00 2001 From: "Michael Bradley, Jr" Date: Sun, 17 Mar 2019 18:14:54 -0500 Subject: [PATCH] refactor(embark): restrict path for fs ops only in VM2 context When testing production installs of embark 4.0.0-beta.1 (local registry), the `restrictPath` mechanism of `core/fs` was too restrictive, interfering with template generation (cause not completely determined). Also, it's known that `restrictPath` causes problems when resolving plugins, etc. from "higher up" `node_modules` paths, e.g. in the monorepo. Introduce `codeRunner/fs` which uses `restrictPath` as before, and in `core/fs` remove `restrictPath`. Revise the other `codeRunner` modules to use `codeRunner/fs`. --- packages/embark/src/lib/core/fs.js | 196 +++++------------- .../embark/src/lib/modules/codeRunner/fs.js | 195 +++++++++++++++++ .../src/lib/modules/codeRunner/index.ts | 20 +- .../embark/src/lib/modules/codeRunner/vm.ts | 4 +- packages/embark/src/test/{fs.js => vm_fs.js} | 7 +- 5 files changed, 261 insertions(+), 161 deletions(-) create mode 100644 packages/embark/src/lib/modules/codeRunner/fs.js rename packages/embark/src/test/{fs.js => vm_fs.js} (86%) diff --git a/packages/embark/src/lib/core/fs.js b/packages/embark/src/lib/core/fs.js index 1e0692d26..6277dfaa1 100644 --- a/packages/embark/src/lib/core/fs.js +++ b/packages/embark/src/lib/core/fs.js @@ -1,164 +1,90 @@ -const parseJson = require('parse-json'); +/* global module process require */ + +const {DAPP_PATH, + DIAGRAM_PATH, + EMBARK_PATH, + PKG_PATH, + anchoredValue} = require('./env'); +const fs = require('fs-extra'); const os = require('os'); -let path = require('path'); -let fs = require('fs-extra'); -let utils = require('../utils/utils.js'); -let env = require('./env.js'); +const parseJson = require('parse-json'); +const path = require('path'); +const utils = require('../utils/utils'); require('colors'); -function restrictPath(receiver, binding, count, args) { - const dapp = dappPath(); - let embark = embarkPath(); - const pkg = pkgPath(); +function mkdirpSync(...args) { return fs.mkdirpSync(...args); } - // In the monorepo, enable doing FS functions on all of embark (needed to access embark/node_modules) - embark = embark.replace(path.normalize('embark/packages/'), ''); +function mkdirp(...args) { return fs.mkdirp(...args); } - const allowedRoots = [ - dapp, - embark, - pkg, - os.tmpdir() - ]; +function readdir(...args) { return fs.readdir(...args); } - let allInsideRestricted = true; +function stat(...args) { return fs.stat(...args); } - for(let i = 0; i < count; i++) { - let resolved = path.resolve(dapp, args[i]); - allInsideRestricted = allowedRoots.some(p => { return resolved.indexOf(p) === 0; }); - if(!allInsideRestricted) break; - } +function remove(...args) { return fs.remove(...args); } - if(allInsideRestricted) return receiver.apply(binding, args); - throw new Error('EPERM: Operation not permitted'); -} +function copy(...args) { return fs.copy(...args); } -function mkdirpSync() { - return restrictPath(fs.mkdirpSync, fs.mkdirpSync, 1, arguments); -} +function copySync(...args) { return fs.copySync(...args); } -function mkdirp() { - return restrictPath(fs.mkdirp, fs.mkdirp, 1, arguments); -} +function move(...args) { return fs.move(...args); } -function readdir() { - return restrictPath(fs.readdir, fs.readdir, 1, arguments); -} +function moveSync(...args) { return fs.moveSync(...args); } -function stat() { - return restrictPath(fs.stat, fs.stat, 1, arguments); -} +function symlink(...args) { return fs.symlink(...args); } -function remove() { - return restrictPath(fs.remove, fs.remove, 1, arguments); -} +function appendFileSync(...args) { return fs.appendFileSync(...args); } -function copy() { - return restrictPath(fs.copy, fs.copy, 2, arguments); -} +function writeFile(...args) { return fs.writeFile(...args); } -function copySync() { - return restrictPath(fs.copySync, fs.copySync, 2, arguments); -} +function writeFileSync(...args) { return fs.writeFileSync(...args); } -function move(){ - return restrictPath(fs.move, fs.move, 2, arguments); -} +function readFile(...args) { return fs.readFile(...args); } -function moveSync() { - return restrictPath(fs.moveSync, fs.moveSync, 2, arguments); -} +function readFileSync(...args) { return fs.readFileSync(...args); } -function symlink() { - return restrictPath(fs.symlink, fs.symlink, 2, arguments); -} +function readdirSync(...args) { return fs.readdirSync(...args); } -function appendFileSync() { - return restrictPath(fs.appendFileSync, fs.writeFileSync, 1, arguments); -} +function statSync(...args) { return fs.statSync(...args); } -function writeFile() { - return restrictPath(fs.writeFile, fs.writeFileSync, 1, arguments); -} - -function writeFileSync() { - return restrictPath(fs.writeFileSync, fs.writeFileSync, 1, arguments); -} - -function readFile() { - return restrictPath(fs.readFile, fs.readFile, 1, arguments); -} - -function readFileSync() { - return restrictPath(fs.readFileSync, fs.readFileSync, 1, arguments); -} - -function readdirSync() { - return restrictPath(fs.readdirSync, fs.readdirSync, 1, arguments); -} - -function statSync() { - return restrictPath(fs.statSync, fs.statSync, 1, arguments); -} - -function readJSONSync() { - let content = readFileSync.apply(readFileSync, arguments); +function readJSONSync(...args) { + let json; try { - return parseJson(content); - } catch(e) { - console.error("error: ".red + arguments[0].green.underline + " " + e.message.green); - process.exit(0); + json = parseJson(readFileSync(...args)); + } catch (e) { + console.error('error: '.red + args[0].green.underline + ' ' + e.message.green); + process.exit(1); } + return json; } -function writeJSONSync() { - return restrictPath(fs.writeJSONSync, fs.writeJSONSync, 1, arguments); -} +function writeJSONSync(...args) { return fs.writeJSONSync(...args); } -function outputJSONSync() { - return restrictPath(fs.outputJSONSync, fs.outputJSONSync, 1, arguments); -} +function outputJSONSync(...args) { return fs.outputJSONSync(...args); } -function writeJson() { - return restrictPath(fs.writeJson, fs.writeJson, 1, arguments); -} +function writeJson(...args) { return fs.writeJson(...args); } -function existsSync() { - return restrictPath(fs.existsSync, fs.existsSync, 1, arguments); -} +function existsSync(...args) { return fs.existsSync(...args); } -function ensureFileSync() { - return restrictPath(fs.ensureFileSync, fs.ensureFileSync, 1, arguments); -} +function ensureFileSync(...args) { return fs.ensureFileSync(...args); } -function ensureDirSync() { - return restrictPath(fs.ensureDirSync, fs.ensureDirSync, 1, arguments); -} +function ensureDirSync(...args) { return fs.ensureDirSync(...args); } -function access() { - return restrictPath(fs.access, fs.access, 1, arguments); -} +function access(...args) { return fs.access(...args); } -function removeSync() { - return restrictPath(fs.removeSync, fs.removeSync, 1, arguments); -} +function removeSync(...args) { return fs.removeSync(...args); } function anchoredPath(anchor, ...args) { - args = args.map(path => path.replace(dappPath(), "")); - return utils.joinPath(env.anchoredValue(anchor), ...args); + return utils.joinPath( + anchoredValue(anchor), + ...args.map(path => path.replace(dappPath(), '')) + ); } -function embarkPath() { - return anchoredPath(env.EMBARK_PATH, ...arguments); -} +function embarkPath(...args) { return anchoredPath(EMBARK_PATH, ...args); } -function dappPath() { - return anchoredPath(env.DAPP_PATH, ...arguments); -} +function dappPath(...args) { return anchoredPath(DAPP_PATH, ...args); } -function diagramPath() { - return anchoredPath(env.DIAGRAM_PATH, ...arguments); -} +function diagramPath(...args) { return anchoredPath(DIAGRAM_PATH, ...args); } function ipcPath(basename, usePipePathOnWindows = false) { if (!(basename && typeof basename === 'string')) { @@ -173,26 +99,18 @@ function ipcPath(basename, usePipePathOnWindows = false) { ); } -function pkgPath() { - return anchoredPath(env.PKG_PATH, ...arguments); -} +function pkgPath(...args) { return anchoredPath(PKG_PATH, ...args); } -function createWriteStream() { - return restrictPath(fs.createWriteStream, fs.createWriteStream, 1, arguments); -} +function createWriteStream(...args) { return fs.createWriteStream(...args); } -function tmpDir() { - let os = require('os'); - return utils.joinPath(os.tmpdir(), ...arguments); -} +function tmpDir(...args) { return utils.joinPath(os.tmpdir(), ...args); } function copyPreserve(sourceFilePath, targetFilePath) { const implementation = (sourceFilePath, targetFilePath) => { - const path = require('path'); let ext = 1; let preserved = targetFilePath; while (fs.existsSync(preserved)) { - let extname = path.extname(targetFilePath); + const extname = path.extname(targetFilePath); preserved = utils.joinPath( path.dirname(targetFilePath), `${path.basename(targetFilePath, extname)}.${ext}${extname}` @@ -205,12 +123,10 @@ function copyPreserve(sourceFilePath, targetFilePath) { fs.copySync(sourceFilePath, targetFilePath); }; - return restrictPath(implementation, implementation, 2, [sourceFilePath, targetFilePath]); + return implementation(sourceFilePath, targetFilePath); } -function outputFileSync(){ - return restrictPath(fs.outputFileSync, fs.outputFile, 1, arguments); -} +function outputFileSync(...args) { return fs.outputFileSync(...args); } module.exports = { access, diff --git a/packages/embark/src/lib/modules/codeRunner/fs.js b/packages/embark/src/lib/modules/codeRunner/fs.js new file mode 100644 index 000000000..9a787eec9 --- /dev/null +++ b/packages/embark/src/lib/modules/codeRunner/fs.js @@ -0,0 +1,195 @@ +/* global module process require */ + +const {DAPP_PATH, + DIAGRAM_PATH, + EMBARK_PATH, + PKG_PATH, + anchoredValue} = require('../../core/env'); +const fs = require('fs-extra'); +const os = require('os'); +const parseJson = require('parse-json'); +const path = require('path'); +const utils = require('../../utils/utils'); +require('colors'); + +function restrictPath(receiver, binding, count, args) { + const dapp = dappPath(); + let embark = embarkPath(); + const pkg = pkgPath(); + + // In the monorepo, enable doing FS functions on all of embark (needed to access embark/node_modules) + embark = embark.replace(path.normalize('embark/packages/'), ''); + + const allowedRoots = [ + dapp, + embark, + pkg, + os.tmpdir() + ]; + + let allInsideRestricted = true; + + for (let i = 0; i < count; i++) { + const resolved = path.resolve(dapp, args[i]); + allInsideRestricted = allowedRoots.some(p => { return resolved.indexOf(p) === 0; }); + if (!allInsideRestricted) break; + } + + if (allInsideRestricted) return receiver.apply(binding, args); + throw new Error('EPERM: Operation not permitted'); +} + +function mkdirpSync(...args) { return restrictPath(fs.mkdirpSync, fs, 1, args); } + +function mkdirp(...args) { return restrictPath(fs.mkdirp, fs, 1, args); } + +function readdir(...args) { return restrictPath(fs.readdir, fs, 1, args); } + +function stat(...args) { return restrictPath(fs.stat, fs, 1, args); } + +function remove(...args) { return restrictPath(fs.remove, fs, 1, args); } + +function copy(...args) { return restrictPath(fs.copy, fs, 2, args); } + +function copySync(...args) { return restrictPath(fs.copySync, fs, 2, args); } + +function move(...args) { return restrictPath(fs.move, fs, 2, args); } + +function moveSync(...args) { return restrictPath(fs.moveSync, fs, 2, args); } + +function symlink(...args) { return restrictPath(fs.symlink, fs, 2, args); } + +function appendFileSync(...args) { return restrictPath(fs.appendFileSync, fs, 1, args); } + +function writeFile(...args) { return restrictPath(fs.writeFile, fs, 1, args); } + +function writeFileSync(...args) { return restrictPath(fs.writeFileSync, fs, 1, args); } + +function readFile(...args) { return restrictPath(fs.readFile, fs, 1, args); } + +function readFileSync(...args) { return restrictPath(fs.readFileSync, fs, 1, args); } + +function readdirSync(...args) { return restrictPath(fs.readdirSync, fs, 1, args); } + +function statSync(...args) { return restrictPath(fs.statSync, fs, 1, args); } + +function readJSONSync(...args) { + const content = readFileSync(...args); + let json; + try { + json = parseJson(content); + } catch(e) { + console.error('error: '.red + args[0].green.underline + ' ' + e.message.green); + process.exit(0); + } + return json; +} + +function writeJSONSync(...args) { return restrictPath(fs.writeJSONSync, fs, 1, args); } + +function outputJSONSync(...args) { return restrictPath(fs.outputJSONSync, fs, 1, args); } + +function writeJson(...args) { return restrictPath(fs.writeJson, fs, 1, args); } + +function existsSync(...args) { return restrictPath(fs.existsSync, fs, 1, args); } + +function ensureFileSync(...args) { return restrictPath(fs.ensureFileSync, fs, 1, args); } + +function ensureDirSync(...args) { return restrictPath(fs.ensureDirSync, fs, 1, args); } + +function access(...args) { return restrictPath(fs.access, fs, 1, args); } + +function removeSync(...args) { return restrictPath(fs.removeSync, fs, 1, args); } + +function anchoredPath(anchor, ...args) { + return utils.joinPath( + anchoredValue(anchor), + ...args.map(path => path.replace(dappPath(), '')) + ); +} + +function embarkPath(...args) { return anchoredPath(EMBARK_PATH, ...args); } + +function dappPath(...args) { return anchoredPath(DAPP_PATH, ...args); } + +function diagramPath(...args) { return anchoredPath(DIAGRAM_PATH, ...args); } + +function ipcPath(basename, usePipePathOnWindows = false) { + if (!(basename && typeof basename === 'string')) { + throw new TypeError('first argument must be a non-empty string'); + } + if (process.platform === 'win32' && usePipePathOnWindows) { + return `\\\\.\\pipe\\${basename}`; + } + return utils.joinPath( + tmpDir(`embark-${utils.sha512(dappPath()).slice(0, 8)}`), + basename + ); +} + +function pkgPath(...args) { return anchoredPath(PKG_PATH, ...args); } + +function createWriteStream(...args) { return restrictPath(fs.createWriteStream, fs, 1, args); } + +function tmpDir(...args) { return utils.joinPath(os.tmpdir(), ...args); } + +function copyPreserve(sourceFilePath, targetFilePath) { + const implementation = (sourceFilePath, targetFilePath) => { + let ext = 1; + let preserved = targetFilePath; + while (fs.existsSync(preserved)) { + const extname = path.extname(targetFilePath); + preserved = utils.joinPath( + path.dirname(targetFilePath), + `${path.basename(targetFilePath, extname)}.${ext}${extname}` + ); + ext++; + } + if (preserved !== targetFilePath) { + fs.copySync(targetFilePath, preserved); + } + fs.copySync(sourceFilePath, targetFilePath); + }; + + return restrictPath(implementation, {}, 2, [sourceFilePath, targetFilePath]); +} + +function outputFileSync(...args) { return restrictPath(fs.outputFileSync, fs, 1, args); } + +module.exports = { + access, + appendFileSync, + copy, + copyPreserve, + copySync, + createWriteStream, + dappPath, + diagramPath, + embarkPath, + existsSync, + ensureFileSync, + ensureDirSync, + ipcPath, + mkdirp, + mkdirpSync, + move, + moveSync, + outputFileSync, + outputJSONSync, + pkgPath, + readFile, + readFileSync, + readJSONSync, + readdir, + readdirSync, + remove, + removeSync, + stat, + statSync, + symlink, + tmpDir, + writeFile, + writeFileSync, + writeJSONSync, + writeJson +}; diff --git a/packages/embark/src/lib/modules/codeRunner/index.ts b/packages/embark/src/lib/modules/codeRunner/index.ts index 90f35935d..0dec78bbf 100644 --- a/packages/embark/src/lib/modules/codeRunner/index.ts +++ b/packages/embark/src/lib/modules/codeRunner/index.ts @@ -1,5 +1,5 @@ import VM from "./vm"; -const fs = require("../../core/fs"); +const fs = require("./fs"); import { Callback, Embark, Events, Logger } from "embark"; import Web3 from "web3"; const EmbarkJS = require("embarkjs"); @@ -22,23 +22,7 @@ class CodeRunner { this.vm = new VM({ require: { mock: { - fs: { - access: fs.access, - dappPath: fs.dappPath, - diagramPath: fs.diagramPath, - embarkPath: fs.embarkPath, - existsSync: fs.existsSync, - ipcPath: fs.ipcPath, - pkgPath: fs.pkgPath, - readFile: fs.readFile, - readFileSync: fs.readFileSync, - readJSONSync: fs.readJSONSync, - readdir: fs.readdir, - readdirSync: fs.readdirSync, - stat: fs.stat, - statSync: fs.statSync, - tmpDir: fs.tmpDir, - }, + fs, }, }, sandbox: { diff --git a/packages/embark/src/lib/modules/codeRunner/vm.ts b/packages/embark/src/lib/modules/codeRunner/vm.ts index 83ac98002..08e911f35 100644 --- a/packages/embark/src/lib/modules/codeRunner/vm.ts +++ b/packages/embark/src/lib/modules/codeRunner/vm.ts @@ -2,7 +2,7 @@ import { each } from "async"; import { Callback, Logger } from "embark"; import { NodeVM, NodeVMOptions } from "vm2"; -const fs = require("../../core/fs"); +const fs = require("./fs"); const path = require("path"); const { recursiveMerge, isEs6Module, compact } = require("../../utils/utils"); @@ -33,7 +33,7 @@ class VM { */ private _options: NodeVMOptions = { require: { - builtin: ["path", "rxjs", "util"], + builtin: ["path", "util"], external: [ "@babel/runtime-corejs2/helpers/interopRequireDefault", "@babel/runtime-corejs2/core-js/json/stringify", diff --git a/packages/embark/src/test/fs.js b/packages/embark/src/test/vm_fs.js similarity index 86% rename from packages/embark/src/test/fs.js rename to packages/embark/src/test/vm_fs.js index cca9c2aed..5b8a61376 100644 --- a/packages/embark/src/test/fs.js +++ b/packages/embark/src/test/vm_fs.js @@ -3,14 +3,16 @@ const {assert} = require('chai'); const os = require('os'); const path = require('path'); const underlyingFs = require('fs-extra'); -const fs = require('../lib/core/fs'); +const fs = require('../lib/modules/codeRunner/fs'); describe('fs', () => { let fsMethods = {}; + let oldConsoleError; let oldDappPath; let oldProcessExit; before(() => { + oldConsoleError = console.error; oldDappPath = process.env.DAPP_PATH; process.env.DAPP_PATH = fs.embarkPath(); oldProcessExit = process.exit; @@ -63,9 +65,12 @@ describe('fs', () => { it('should not throw exceptions on paths inside the temporary dir root', (done) => { assert.doesNotThrow(async () => { try { + if (func === 'readJSONSync') console.error = function() {}; await fs[func](path.join(os.tmpdir(), 'foo')); } catch(e) { if(e.message.indexOf('EPERM') === 0) throw e; + } finally { + if (func === 'readJSONSync') console.error = oldConsoleError; } }, /EPERM: Operation not permitted/);