Fix privilege elevation for standalone zip package on Windows (windosu)

* Add pkgExec internal command
* Patch windosu to be aware of process.pkg and use pkgExec

Change-type: patch
Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
Paulo Castro 2019-10-15 01:05:40 +01:00
parent 69c97fed09
commit 02b888f7c1
2 changed files with 88 additions and 4 deletions

View File

@ -15,17 +15,63 @@
* limitations under the License.
*/
import { globalInit } from './app-common';
import { AppOptions, routeCliFramework } from './preparser';
/**
* CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which
* call this function.
*/
export async function run(cliArgs = process.argv, options: AppOptions = {}) {
export async function run(
cliArgs = process.argv,
options: import('./preparser').AppOptions = {},
) {
// The 'pkgExec' special/internal command provides a Node.js interpreter
// for use of the standalone zip package. See pkgExec function.
if (cliArgs.length > 3 && cliArgs[2] === 'pkgExec') {
return pkgExec(cliArgs[3], cliArgs.slice(4));
}
const { globalInit } = await import('./app-common');
const { routeCliFramework } = await import('./preparser');
// globalInit() must be called very early on (before other imports) because
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
// shared options, and performs node version requirement checks.
globalInit();
await routeCliFramework(cliArgs, options);
}
/**
* Implements the 'pkgExec' command, used as a way to provide a Node.js
* interpreter for child_process.spawn()-like operations when the CLI is
* executing as a standalone zip package (built-in Node interpreter) and
* the system may not have a separate Node.js installation. A present use
* case is a patched version of the 'windosu' package that requires a
* Node.js interpreter to spawn a privileged child process.
*
* @param modFunc Path to a JS module that will be executed via require().
* The modFunc argument may optionally contain a function name separated
* by '::', for example '::main' in:
* 'C:\\snapshot\\balena-cli\\node_modules\\windosu\\lib\\pipe.js::main'
* in which case that function is executed in the require'd module.
* @param args Optional arguments to passed through process.argv and as
* arguments to the function specified via modFunc.
*/
async function pkgExec(modFunc: string, args: string[]) {
const [modPath, funcName] = modFunc.split('::');
let replacedModPath = modPath;
const match = modPath
.replace(/\\/g, '/')
.match(/\/snapshot\/balena-cli\/(.+)/);
if (match) {
replacedModPath = `../${match[1]}`;
}
process.argv = [process.argv[0], process.argv[1], ...args];
try {
const mod: any = await import(replacedModPath);
if (funcName) {
await mod[funcName](...args);
}
} catch (err) {
console.error(`Error executing pkgExec "${modFunc}" [${args.join()}]`);
console.error(err);
}
}

View File

@ -0,0 +1,38 @@
diff --git a/node_modules/windosu/lib/pipe.js b/node_modules/windosu/lib/pipe.js
index dc81fa5..a381cc7 100644
--- a/node_modules/windosu/lib/pipe.js
+++ b/node_modules/windosu/lib/pipe.js
@@ -42,7 +42,8 @@ function pipe(path, options) {
return d.promise;
}
module.exports = pipe;
-if (module === require.main) {
+
+function main() {
if (!process.argv[4]) {
console.error('Incorrect arguments!');
process.exit(-1);
@@ -52,3 +53,8 @@ if (module === require.main) {
serve: process.argv[3] == 'server'
});
}
+module.exports.main = main;
+
+if (module === require.main) {
+ main();
+}
diff --git a/node_modules/windosu/lib/windosu.js b/node_modules/windosu/lib/windosu.js
index 6502812..dd0391a 100644
--- a/node_modules/windosu/lib/windosu.js
+++ b/node_modules/windosu/lib/windosu.js
@@ -16,7 +16,9 @@ module.exports.exec = function (command, options, callback) {
temp: temp,
command: command,
cliWidth: cliWidth(),
- pipe: '"' + process.execPath + '" "' + path.join(__dirname, 'pipe.js') + '"',
+ pipe: process.pkg
+ ? '"' + process.execPath + '" pkgExec "' + path.join(__dirname, 'pipe.js') + '::main"'
+ : '"' + process.execPath + '" "' + path.join(__dirname, 'pipe.js') + '"',
input: inputName = id + '-in',
output: outputName = id + '-out',
stderr_redir: process.stdout.isTTY ? '2>&1' : '2> %ERROR%'