Skip to content

Commit 0bd9b7e

Browse files
author
Greg Magolan
authored
fix(builtin): fix node patches subprocess sandbox propogation (#2017)
Two fixes here. Set --preserve-symlinks --preserve-symlinks-main in the generated _node_bin/node which comes from ${process.env.NODE_REPOSITORY_ARGS}. This will prevent the --require /path/to/node_patches.js from being resolved to its location outside of the sandbox. However, if preserve_symlinks=false, which is still an option, the 2nd fix is to honour the value of NP_SUBPROCESS_NODE_DIR when running node_patches.js for the subprocess. Even if node_patches.js is resolved to outside of the sandbox for the subprocess and run from there, it will still look for _node_bin/node within the sandbox.
1 parent 17c0244 commit 0bd9b7e

File tree

7 files changed

+74
-83
lines changed

7 files changed

+74
-83
lines changed

internal/node/launcher.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ readonly lcov_merger_script=$(rlocation "TEMPLATED_lcov_merger_script")
165165
source $repository_args
166166

167167
ARGS=()
168-
LAUNCHER_NODE_OPTIONS=()
168+
LAUNCHER_NODE_OPTIONS=($NODE_REPOSITORY_ARGS)
169169
USER_NODE_OPTIONS=()
170-
ALL_ARGS=(TEMPLATED_args $NODE_REPOSITORY_ARGS "$@")
170+
ALL_ARGS=(TEMPLATED_args "$@")
171171
STDOUT_CAPTURE=""
172172
STDERR_CAPTURE=""
173173
EXIT_CODE_CAPTURE=""
@@ -176,7 +176,7 @@ RUN_LINKER=true
176176
NODE_PATCHES=true
177177
# TODO(alex): change the default to false
178178
PATCH_REQUIRE=true
179-
for ARG in "${ALL_ARGS[@]:-}"; do
179+
for ARG in ${ALL_ARGS[@]+"${ALL_ARGS[@]}"}; do
180180
case "$ARG" in
181181
# Supply custom linker arguments for first-party dependencies
182182
--bazel_node_modules_manifest=*) MODULES_MANIFEST="${ARG#--bazel_node_modules_manifest=}" ;;
@@ -316,13 +316,13 @@ _int() {
316316
set +e
317317

318318
if [[ -n "${STDOUT_CAPTURE}" ]] && [[ -n "${STDERR_CAPTURE}" ]]; then
319-
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE 2>$STDERR_CAPTURE &
319+
"${node}" ${LAUNCHER_NODE_OPTIONS[@]+"${LAUNCHER_NODE_OPTIONS[@]}"} ${USER_NODE_OPTIONS[@]+"${USER_NODE_OPTIONS[@]}"} "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE 2>$STDERR_CAPTURE &
320320
elif [[ -n "${STDOUT_CAPTURE}" ]]; then
321-
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE &
321+
"${node}" ${LAUNCHER_NODE_OPTIONS[@]+"${LAUNCHER_NODE_OPTIONS[@]}"} ${USER_NODE_OPTIONS[@]+"${USER_NODE_OPTIONS[@]}"} "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE &
322322
elif [[ -n "${STDERR_CAPTURE}" ]]; then
323-
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 2>$STDERR_CAPTURE &
323+
"${node}" ${LAUNCHER_NODE_OPTIONS[@]+"${LAUNCHER_NODE_OPTIONS[@]}"} ${USER_NODE_OPTIONS[@]+"${USER_NODE_OPTIONS[@]}"} "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 2>$STDERR_CAPTURE &
324324
else
325-
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 &
325+
"${node}" ${LAUNCHER_NODE_OPTIONS[@]+"${LAUNCHER_NODE_OPTIONS[@]}"} ${USER_NODE_OPTIONS[@]+"${USER_NODE_OPTIONS[@]}"} "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 &
326326
fi
327327

328328
readonly child=$!

internal/node/node_patches.js

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -551,39 +551,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
551551
// but adds support to ensure the registered loader is included in all nested executions of nodejs.
552552

553553

554-
exports.patcher = (requireScriptName, binDir) => {
554+
exports.patcher = (requireScriptName, nodeDir) => {
555555
requireScriptName = path.resolve(requireScriptName);
556-
const dir = path.dirname(requireScriptName);
556+
nodeDir = nodeDir || path.join(path.dirname(requireScriptName), '_node_bin');
557557
const file = path.basename(requireScriptName);
558-
const nodeDir = path.join(binDir || dir, '_node_bin');
559-
if (!process.env.NP_PATCHED_NODEJS) {
560-
// TODO: WINDOWS.
561-
try {
562-
fs$1.mkdirSync(nodeDir, { recursive: true });
563-
}
564-
catch (e) {
565-
// with node versions that don't have recursive mkdir this may throw an error.
566-
if (e.code !== 'EEXIST') {
567-
throw e;
568-
}
558+
try {
559+
fs$1.mkdirSync(nodeDir, { recursive: true });
560+
}
561+
catch (e) {
562+
// with node versions that don't have recursive mkdir this may throw an error.
563+
if (e.code !== 'EEXIST') {
564+
throw e;
569565
}
570-
if (process.platform == 'win32') {
571-
fs$1.writeFileSync(path.join(nodeDir, 'node.bat'), `@if not defined DEBUG_HELPER @ECHO OFF
572-
set NP_PATCHED_NODEJS=${nodeDir}
566+
}
567+
if (process.platform == 'win32') {
568+
const nodeEntry = path.join(nodeDir, 'node.bat');
569+
if (!fs$1.existsSync(nodeEntry)) {
570+
fs$1.writeFileSync(nodeEntry, `@if not defined DEBUG_HELPER @ECHO OFF
571+
set NP_SUBPROCESS_NODE_DIR=${nodeDir}
573572
set Path=${nodeDir};%Path%
574-
"${process.execPath}" --require "${requireScriptName}" %*
575-
`);
573+
"${process.execPath}" ${process.env.NODE_REPOSITORY_ARGS} --require "${requireScriptName}" %*
574+
`);
576575
}
577-
else {
578-
fs$1.writeFileSync(path.join(nodeDir, 'node'), `#!/bin/bash
579-
export NP_PATCHED_NODEJS="${nodeDir}"
576+
}
577+
else {
578+
const nodeEntry = path.join(nodeDir, 'node');
579+
if (!fs$1.existsSync(nodeEntry)) {
580+
fs$1.writeFileSync(nodeEntry, `#!/bin/bash
581+
export NP_SUBPROCESS_NODE_DIR="${nodeDir}"
580582
export PATH="${nodeDir}":\$PATH
581583
if [[ ! "\${@}" =~ "${file}" ]]; then
582-
exec ${process.execPath} --require "${requireScriptName}" "$@"
584+
exec ${process.execPath} ${process.env.NODE_REPOSITORY_ARGS} --require "${requireScriptName}" "$@"
583585
else
584-
exec ${process.execPath} "$@"
586+
exec ${process.execPath} ${process.env.NODE_REPOSITORY_ARGS} "$@"
585587
fi
586-
`, { mode: 0o777 });
588+
`, { mode: 0o777 });
587589
}
588590
}
589591
if (!process.env.PATH) {
@@ -659,8 +661,7 @@ var src_2 = src.subprocess;
659661
* @fileoverview Description of this file.
660662
*/
661663

662-
// todo auto detect bazel env vars instead of adding a new one.
663-
const { BAZEL_PATCH_ROOT, BAZEL_PATCH_GUARDS, NP_SUBPROCESS_BIN_DIR, VERBOSE_LOGS } = process.env;
664+
const { BAZEL_PATCH_ROOT, BAZEL_PATCH_GUARDS, NP_SUBPROCESS_NODE_DIR, VERBOSE_LOGS } = process.env;
664665
if (BAZEL_PATCH_ROOT) {
665666
const guards = BAZEL_PATCH_GUARDS ? BAZEL_PATCH_GUARDS.split(',') : [];
666667
if (VERBOSE_LOGS)
@@ -671,7 +672,7 @@ if (BAZEL_PATCH_ROOT) {
671672
else if (VERBOSE_LOGS) {
672673
console.error(`bazel node patches disabled. set environment BAZEL_PATCH_ROOT`);
673674
}
674-
src.subprocess(__filename, NP_SUBPROCESS_BIN_DIR);
675+
src.subprocess(__filename, NP_SUBPROCESS_NODE_DIR);
675676

676677
var register = {
677678

internal/node/node_repositories.bzl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,13 +439,10 @@ def _prepare_node(repository_ctx):
439439
preserve_symlinks_main_support = check_version(repository_ctx.attr.node_version, "10.2.0")
440440
if preserve_symlinks_main_support:
441441
node_args = "--preserve-symlinks --preserve-symlinks-main"
442-
node_repo_args = "\"--node_options=--preserve-symlinks --node_options=--preserve-symlinks-main\""
443442
else:
444443
node_args = "--preserve-symlinks"
445-
node_repo_args = "--node_options=--preserve-symlinks"
446444
else:
447445
node_args = ""
448-
node_repo_args = ""
449446

450447
# The entry points for node for osx/linux and windows
451448
if not is_windows:
@@ -476,8 +473,8 @@ CALL "%SCRIPT_DIR%\\{node}" {args} %*
476473
# Immediately exit if any command fails.
477474
set -e
478475
# Generated by node_repositories.bzl
479-
export NODE_REPOSITORY_ARGS={args}
480-
""".format(args = node_repo_args), executable = True)
476+
export NODE_REPOSITORY_ARGS="{args}"
477+
""".format(args = node_args), executable = True)
481478

482479
# The entry points for npm for osx/linux and windows
483480
# Runs npm using appropriate node entry point

internal/node/test/empty_args_fail.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ var theArgs = process.argv.slice(2);
66

77
if (theArgs.length != 0) {
88
// Non-zero exit code if the argument list is not empty
9+
console.error(theArgs)
910
process.exit(42)
1011
}

packages/node-patches/register.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
* @fileoverview Description of this file.
1919
*/
2020
const patcher = require('./src');
21-
// todo auto detect bazel env vars instead of adding a new one.
22-
const {BAZEL_PATCH_ROOT, BAZEL_PATCH_GUARDS, NP_SUBPROCESS_BIN_DIR, VERBOSE_LOGS} = process.env;
21+
const {BAZEL_PATCH_ROOT, BAZEL_PATCH_GUARDS, NP_SUBPROCESS_NODE_DIR, VERBOSE_LOGS} = process.env;
2322

2423
if (BAZEL_PATCH_ROOT) {
2524
const guards = BAZEL_PATCH_GUARDS ? BAZEL_PATCH_GUARDS.split(',') : [];
@@ -32,4 +31,4 @@ if (BAZEL_PATCH_ROOT) {
3231
console.error(`bazel node patches disabled. set environment BAZEL_PATCH_ROOT`);
3332
}
3433

35-
patcher.subprocess(__filename, NP_SUBPROCESS_BIN_DIR);
34+
patcher.subprocess(__filename, NP_SUBPROCESS_NODE_DIR);

packages/node-patches/src/subprocess.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,41 @@
33
const fs = require('fs');
44
const path = require('path');
55

6-
export const patcher = (requireScriptName: string, binDir?: string) => {
6+
export const patcher = (requireScriptName: string, nodeDir?: string) => {
77
requireScriptName = path.resolve(requireScriptName);
8-
const dir = path.dirname(requireScriptName);
8+
nodeDir = nodeDir || path.join(path.dirname(requireScriptName), '_node_bin');
99
const file = path.basename(requireScriptName);
10-
const nodeDir = path.join(binDir || dir, '_node_bin');
1110

12-
if (!process.env.NP_PATCHED_NODEJS) {
13-
// TODO: WINDOWS.
14-
try {
15-
fs.mkdirSync(nodeDir, {recursive: true});
16-
} catch (e) {
17-
// with node versions that don't have recursive mkdir this may throw an error.
18-
if (e.code !== 'EEXIST') {
19-
throw e;
20-
}
11+
try {
12+
fs.mkdirSync(nodeDir, {recursive: true});
13+
} catch (e) {
14+
// with node versions that don't have recursive mkdir this may throw an error.
15+
if (e.code !== 'EEXIST') {
16+
throw e;
2117
}
22-
if (process.platform == 'win32') {
23-
fs.writeFileSync(path.join(nodeDir, 'node.bat'), `@if not defined DEBUG_HELPER @ECHO OFF
24-
set NP_PATCHED_NODEJS=${nodeDir}
18+
}
19+
if (process.platform == 'win32') {
20+
const nodeEntry = path.join(nodeDir, 'node.bat');
21+
if (!fs.existsSync(nodeEntry)) {
22+
fs.writeFileSync(nodeEntry, `@if not defined DEBUG_HELPER @ECHO OFF
23+
set NP_SUBPROCESS_NODE_DIR=${nodeDir}
2524
set Path=${nodeDir};%Path%
26-
"${process.execPath}" --require "${requireScriptName}" %*
27-
`)
28-
} else {
25+
"${process.execPath}" ${process.env.NODE_REPOSITORY_ARGS} --require "${requireScriptName}" %*
26+
`);
27+
}
28+
} else {
29+
const nodeEntry = path.join(nodeDir, 'node');
30+
if (!fs.existsSync(nodeEntry)) {
2931
fs.writeFileSync(
30-
path.join(nodeDir, 'node'), `#!/bin/bash
31-
export NP_PATCHED_NODEJS="${nodeDir}"
32+
nodeEntry, `#!/bin/bash
33+
export NP_SUBPROCESS_NODE_DIR="${nodeDir}"
3234
export PATH="${nodeDir}":\$PATH
3335
if [[ ! "\${@}" =~ "${file}" ]]; then
34-
exec ${process.execPath} --require "${requireScriptName}" "$@"
36+
exec ${process.execPath} ${process.env.NODE_REPOSITORY_ARGS} --require "${requireScriptName}" "$@"
3537
else
36-
exec ${process.execPath} "$@"
38+
exec ${process.execPath} ${process.env.NODE_REPOSITORY_ARGS} "$@"
3739
fi
38-
`,
40+
`,
3941
{mode: 0o777});
4042
}
4143
}
Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
11
import * as assert from 'assert';
22
import * as cp from 'child_process';
3-
import * as fs from 'fs';
43
import * as os from 'os';
54
import * as path from 'path';
65
import * as rimraf from 'rimraf';
76

8-
import {patcher} from '../../src/subprocess';
9-
107
const requireScript = path.resolve(path.join(__dirname, '..', '..', 'register.js'));
118

12-
const tmp = path.join(os.tmpdir(), 'node-patches-test-tmp');
9+
const nodeDir = path.join(os.tmpdir(), 'node-patches-test-tmp', '_node_bin');
1310

1411
// tslint:disable-next-line:no-any
1512
function assertPatched(prefix: string, arr: any) {
16-
const binDir = path.join(tmp, '_node_bin');
17-
1813
const [execPath, execArgv, argv, pathEnv] = arr;
1914

2015
assert.deepStrictEqual(
21-
path.join(binDir, 'node'), execPath,
16+
path.join(nodeDir, 'node'), execPath,
2217
prefix + ' exec path has been rewritten to subprocess proxy');
2318
assert.deepStrictEqual(
24-
path.join(binDir, 'node'), argv[0],
19+
path.join(nodeDir, 'node'), argv[0],
2520
prefix + ' argv[0] has been rewritten to subprocess proxy');
2621

2722
let found = false;
@@ -38,23 +33,19 @@ function assertPatched(prefix: string, arr: any) {
3833
// something.
3934

4035
assert.deepStrictEqual(
41-
binDir, pathEnv.split(path.delimiter)[0],
36+
nodeDir, pathEnv.split(path.delimiter)[0],
4237
prefix + ' the highest priority directory in the PATH must be the node shim dir.');
4338
}
4439

4540
describe('spawning child processes', () => {
46-
before(() => {
47-
fs.mkdirSync(tmp, {recursive: true});
48-
});
49-
5041
it('get patched if run as shell script.', () => {
51-
const res = cp.execSync(`NP_SUBPROCESS_BIN_DIR=${tmp} node -r ${
42+
const res = cp.execSync(`NP_SUBPROCESS_NODE_DIR=${nodeDir} node -r ${
5243
requireScript} -e 'console.log(JSON.stringify([process.execPath,process.execArgv,process.argv,process.env.PATH]))'`);
5344
assertPatched('', JSON.parse(res + ''));
5445
});
5546

5647
it('overwrites spawn related variables correctly.', () => {
57-
const res = cp.execSync(`NP_SUBPROCESS_BIN_DIR=${tmp} node -r ${requireScript} ${
48+
const res = cp.execSync(`NP_SUBPROCESS_NODE_DIR=${nodeDir} node -r ${requireScript} ${
5849
path.join(__dirname, 'worker-threads-script.js')}`);
5950

6051
const {mainThread, worker} = JSON.parse(res + '');
@@ -64,7 +55,7 @@ describe('spawning child processes', () => {
6455
});
6556

6657
it('can spawn node from the shell', () => {
67-
const res = cp.execSync(`NP_SUBPROCESS_BIN_DIR=${tmp} node -r ${requireScript} ${
58+
const res = cp.execSync(`NP_SUBPROCESS_NODE_DIR=${nodeDir} node -r ${requireScript} ${
6859
path.join(__dirname, 'shell-script.js')}`);
6960
// TODO: this is broken if no environment is passed and a new bash is executed
7061
// reading only the rc files to build the environment.
@@ -77,7 +68,7 @@ describe('spawning child processes', () => {
7768

7869
it('can spawn node from spawn', () => {
7970
const res = cp.execSync(
80-
`NP_SUBPROCESS_BIN_DIR=${tmp} node -r ${requireScript} ${
71+
`NP_SUBPROCESS_NODE_DIR=${nodeDir} node -r ${requireScript} ${
8172
path.join(__dirname, 'spawn-script.js')}`,
8273
{env: process.env});
8374

@@ -86,6 +77,6 @@ describe('spawning child processes', () => {
8677
});
8778

8879
after(() => {
89-
rimraf.sync(tmp);
80+
rimraf.sync(nodeDir);
9081
});
9182
});

0 commit comments

Comments
 (0)