GeezerVison is showing github.com — child_process: harden against prototype pollution · nodejs/node@1f35c0c
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/child_process.js +5-2Lines changed: 5 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -138,7 +138,7 @@ function fork(modulePath, args = [], options) { | 138 | 138 | if (options != null) { | 139 | 139 | validateObject(options, 'options'); | 140 | 140 | } | 141 | | - options = { ...options, shell: false }; | | 141 | + options = { __proto__: null, ...options, shell: false }; | 142 | 142 | options.execPath = options.execPath || process.execPath; | 143 | 143 | validateArgumentNullCheck(options.execPath, 'options.execPath'); | 144 | 144 |
| @@ -196,7 +196,7 @@ function normalizeExecArgs(command, options, callback) { | 196 | 196 | } | 197 | 197 |
| 198 | 198 | // Make a shallow copy so we don't clobber the user's options object. | 199 | | - options = { ...options }; | | 199 | + options = { __proto__: null, ...options }; | 200 | 200 | options.shell = typeof options.shell === 'string' ? options.shell : true; | 201 | 201 |
| 202 | 202 | return { | @@ -329,6 +329,7 @@ function execFile(file, args, options, callback) { | 329 | 329 | ({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback)); | 330 | 330 |
| 331 | 331 | options = { | | 332 | + __proto__: null, | 332 | 333 | encoding: 'utf8', | 333 | 334 | timeout: 0, | 334 | 335 | maxBuffer: MAX_BUFFER, | @@ -703,6 +704,7 @@ function normalizeSpawnArguments(file, args, options) { | 703 | 704 |
| 704 | 705 | return { | 705 | 706 | // Make a shallow copy so we don't clobber the user's options object. | | 707 | + __proto__: null, | 706 | 708 | ...options, | 707 | 709 | args, | 708 | 710 | cwd, | @@ -828,6 +830,7 @@ function spawn(file, args, options) { | 828 | 830 | */ | 829 | 831 | function spawnSync(file, args, options) { | 830 | 832 | options = { | | 833 | + __proto__: null, | 831 | 834 | maxBuffer: MAX_BUFFER, | 832 | 835 | ...normalizeSpawnArguments(file, args, options), | 833 | 836 | }; | |
Collapse file Copy file name to clipboard +59Lines changed: 59 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import * as common from '../common/index.mjs'; | | 2 | +import * as fixtures from '../common/fixtures.mjs'; | | 3 | +import { EOL } from 'node:os'; | | 4 | +import { strictEqual } from 'node:assert'; | | 5 | +import cp from 'node:child_process'; | | 6 | + | | 7 | +// TODO(LiviaMedeiros): test on different platforms | | 8 | +if (!common.isLinux) | | 9 | + common.skip(); | | 10 | + | | 11 | +const expectedCWD = process.cwd(); | | 12 | +const expectedUID = process.getuid(); | | 13 | + | | 14 | +for (const tamperedCwd of ['', '/tmp', '/not/existing/malicious/path', 42n]) { | | 15 | + Object.prototype.cwd = tamperedCwd; | | 16 | + | | 17 | + cp.exec('pwd', common.mustSucceed((out) => { | | 18 | + strictEqual(`${out}`, `${expectedCWD}${EOL}`); | | 19 | + })); | | 20 | + strictEqual(`${cp.execSync('pwd')}`, `${expectedCWD}${EOL}`); | | 21 | + cp.execFile('pwd', common.mustSucceed((out) => { | | 22 | + strictEqual(`${out}`, `${expectedCWD}${EOL}`); | | 23 | + })); | | 24 | + strictEqual(`${cp.execFileSync('pwd')}`, `${expectedCWD}${EOL}`); | | 25 | + cp.spawn('pwd').stdout.on('data', common.mustCall((out) => { | | 26 | + strictEqual(`${out}`, `${expectedCWD}${EOL}`); | | 27 | + })); | | 28 | + strictEqual(`${cp.spawnSync('pwd').stdout}`, `${expectedCWD}${EOL}`); | | 29 | + | | 30 | + delete Object.prototype.cwd; | | 31 | +} | | 32 | + | | 33 | +for (const tamperedUID of [0, 1, 999, 1000, 0n, 'gwak']) { | | 34 | + Object.prototype.uid = tamperedUID; | | 35 | + | | 36 | + cp.exec('id -u', common.mustSucceed((out) => { | | 37 | + strictEqual(`${out}`, `${expectedUID}${EOL}`); | | 38 | + })); | | 39 | + strictEqual(`${cp.execSync('id -u')}`, `${expectedUID}${EOL}`); | | 40 | + cp.execFile('id', ['-u'], common.mustSucceed((out) => { | | 41 | + strictEqual(`${out}`, `${expectedUID}${EOL}`); | | 42 | + })); | | 43 | + strictEqual(`${cp.execFileSync('id', ['-u'])}`, `${expectedUID}${EOL}`); | | 44 | + cp.spawn('id', ['-u']).stdout.on('data', common.mustCall((out) => { | | 45 | + strictEqual(`${out}`, `${expectedUID}${EOL}`); | | 46 | + })); | | 47 | + strictEqual(`${cp.spawnSync('id', ['-u']).stdout}`, `${expectedUID}${EOL}`); | | 48 | + | | 49 | + delete Object.prototype.uid; | | 50 | +} | | 51 | + | | 52 | +{ | | 53 | + Object.prototype.execPath = '/not/existing/malicious/path'; | | 54 | + | | 55 | + // Does not throw ENOENT | | 56 | + cp.fork(fixtures.path('empty.js')); | | 57 | + | | 58 | + delete Object.prototype.execPath; | | 59 | +} |
|