From a4045c8c0d8c9d5c264014d774ed1d32de3918e4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 11 Mar 2021 23:25:45 +0100 Subject: [PATCH] lib: add brand checks to AbortController and AbortSignal PR-URL: https://github.com/nodejs/node/pull/37720 Reviewed-By: James M Snell Reviewed-By: Antoine du Hamel --- lib/internal/abort_controller.js | 32 ++++++++++++-- test/parallel/test-abortcontroller.js | 60 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 32fe2ef0126f36..95584feeeecb84 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -23,6 +23,11 @@ const { emitExperimentalWarning } = require('internal/util'); const { inspect } = require('internal/util/inspect'); +const { + codes: { + ERR_INVALID_THIS, + } +} = require('internal/errors'); const kAborted = Symbol('kAborted'); @@ -37,13 +42,21 @@ function customInspect(self, obj, depth, options) { return `${self.constructor.name} ${inspect(obj, opts)}`; } +function validateAbortSignal(obj) { + if (obj?.[kAborted] === undefined) + throw new ERR_INVALID_THIS('AbortSignal'); +} + class AbortSignal extends EventTarget { constructor() { // eslint-disable-next-line no-restricted-syntax throw new TypeError('Illegal constructor'); } - get aborted() { return !!this[kAborted]; } + get aborted() { + validateAbortSignal(this); + return !!this[kAborted]; + } [customInspectSymbol](depth, options) { return customInspect(this, { @@ -89,14 +102,27 @@ function abortSignal(signal) { // initializers for now: // https://bugs.chromium.org/p/v8/issues/detail?id=10704 const kSignal = Symbol('signal'); + +function validateAbortController(obj) { + if (obj?.[kSignal] === undefined) + throw new ERR_INVALID_THIS('AbortController'); +} + class AbortController { constructor() { this[kSignal] = createAbortSignal(); emitExperimentalWarning('AbortController'); } - get signal() { return this[kSignal]; } - abort() { abortSignal(this[kSignal]); } + get signal() { + validateAbortController(this); + return this[kSignal]; + } + + abort() { + validateAbortController(this); + abortSignal(this[kSignal]); + } [customInspectSymbol](depth, options) { return customInspect(this, { diff --git a/test/parallel/test-abortcontroller.js b/test/parallel/test-abortcontroller.js index 13bdfc766afc51..9df6f78b6165cc 100644 --- a/test/parallel/test-abortcontroller.js +++ b/test/parallel/test-abortcontroller.js @@ -73,3 +73,63 @@ const { Event } = require('internal/event_target'); const signal = AbortSignal.abort(); ok(signal.aborted); } + +{ + // Test that AbortController properties and methods validate the receiver + const acSignalGet = Object.getOwnPropertyDescriptor( + AbortController.prototype, + 'signal' + ).get; + const acAbort = AbortController.prototype.abort; + + const goodController = new AbortController(); + ok(acSignalGet.call(goodController)); + acAbort.call(goodController); + + const badAbortControllers = [ + null, + undefined, + 0, + NaN, + true, + 'AbortController', + Object.create(AbortController.prototype) + ]; + for (const badController of badAbortControllers) { + throws( + () => acSignalGet.call(badController), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + throws( + () => acAbort.call(badController), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + } +} + +{ + // Test that AbortSignal properties validate the receiver + const signalAbortedGet = Object.getOwnPropertyDescriptor( + AbortSignal.prototype, + 'aborted' + ).get; + + const goodSignal = new AbortController().signal; + strictEqual(signalAbortedGet.call(goodSignal), false); + + const badAbortSignals = [ + null, + undefined, + 0, + NaN, + true, + 'AbortSignal', + Object.create(AbortSignal.prototype) + ]; + for (const badSignal of badAbortSignals) { + throws( + () => signalAbortedGet.call(badSignal), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + } +}