From 83b428a9548ee3b92d8ad9bda1dd01e2b6d36a88 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 29 Dec 2020 15:59:44 +0100 Subject: [PATCH] domain: make node resilient to Array prototype tempering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/nodejs/node/issues/36669 PR-URL: https://github.com/nodejs/node/pull/36676 Reviewed-By: Rich Trott Reviewed-By: Michaƫl Zasso Reviewed-By: James M Snell --- lib/domain.js | 2 +- .../test-repl-array-prototype-tempering.js | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-repl-array-prototype-tempering.js diff --git a/lib/domain.js b/lib/domain.js index 5c96cb43790..4a018c52f84 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -319,7 +319,7 @@ Domain.prototype.exit = function() { // Exit all domains until this one. ArrayPrototypeSplice(stack, index); - exports.active = stack[stack.length - 1]; + exports.active = stack.length === 0 ? undefined : stack[stack.length - 1]; process.domain = exports.active; updateExceptionCapture(); }; diff --git a/test/parallel/test-repl-array-prototype-tempering.js b/test/parallel/test-repl-array-prototype-tempering.js new file mode 100644 index 00000000000..907a6396ebe --- /dev/null +++ b/test/parallel/test-repl-array-prototype-tempering.js @@ -0,0 +1,66 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { spawn } = require('child_process'); + +const replProcess = spawn(process.argv0, ['--interactive'], { + stdio: ['pipe', 'pipe', 'inherit'], + windowsHide: true, +}); + +replProcess.on('error', common.mustNotCall()); + +const replReadyState = (async function* () { + let ready; + const SPACE = ' '.charCodeAt(); + const BRACKET = '>'.charCodeAt(); + const DOT = '.'.charCodeAt(); + replProcess.stdout.on('data', (data) => { + ready = data[data.length - 1] === SPACE && ( + data[data.length - 2] === BRACKET || ( + data[data.length - 2] === DOT && + data[data.length - 3] === DOT && + data[data.length - 4] === DOT + )); + }); + + const processCrashed = new Promise((resolve, reject) => + replProcess.on('exit', reject) + ); + while (true) { + await Promise.race([new Promise(setImmediate), processCrashed]); + if (ready) { + ready = false; + yield; + } + } +})(); +async function writeLn(data, expectedOutput) { + await replReadyState.next(); + if (expectedOutput) { + replProcess.stdout.once('data', common.mustCall((data) => + assert.match(data.toString('utf8'), expectedOutput) + )); + } + await new Promise((resolve, reject) => replProcess.stdin.write( + `${data}\n`, + (err) => (err ? reject(err) : resolve()) + )); +} + +async function main() { + await writeLn( + 'Object.defineProperty(Array.prototype, "-1", ' + + '{ get() { return this[this.length - 1]; } });' + ); + + await writeLn( + '[3, 2, 1][-1];', + /^1\n(>\s)?$/ + ); + await writeLn('.exit'); + + assert(!replProcess.connected); +} + +main().then(common.mustCall());