diff options
4 files changed, 142 insertions, 83 deletions
diff --git a/chromium/third_party/blink/renderer/core/streams/CommonOperations.js b/chromium/third_party/blink/renderer/core/streams/CommonOperations.js index 4f04dacf2d5..1d213772ae3 100644 --- a/chromium/third_party/blink/renderer/core/streams/CommonOperations.js +++ b/chromium/third_party/blink/renderer/core/streams/CommonOperations.js @@ -64,12 +64,27 @@ throw new RangeError('Stream API Internal Error'); } + // For safety, this must always be used in place of calling v8.createPromise() + // directly. + function createPromise() { + const p = v8.createPromise(); + p[_isSettled] = false; + return p; + } + + // Calling v8.rejectPromise() directly is very dangerous. Always use this + // wrapper. function rejectPromise(p, reason) { if (!v8.isPromise(p)) { streamInternalError(); } - if (p[_isSettled]) { + // assert(typeof p[_isSettled] === 'boolean', + // 'Type(p.[[isSettled]]) is `"boolean"`'); + + // Note that this makes the function a no-op for promises that were not + // created via createPromise(). This is critical for security. + if (p[_isSettled] !== false) { return; } p[_isSettled] = true; @@ -77,12 +92,27 @@ v8.rejectPromise(p, reason); } + // This must always be used instead of Promise.reject(). + function createRejectedPromise(reason) { + const p = createPromise(); + rejectPromise(p, reason); + return p; + } + + // Calling v8.resolvePromise() directly is very dangerous. Always use this + // wrapper. If |value| is an object this will look up Object.prototype.then + // and so may be re-entrant. function resolvePromise(p, value) { if (!v8.isPromise(p)) { streamInternalError(); } - if (p[_isSettled]) { + // assert(typeof p[_isSettled] === 'boolean', + // 'Type(p.[[isSettled]]) is `"boolean"`'); + + // Note that this makes the function a no-op for promises that were not + // created via createPromise(). This is critical for security. + if (p[_isSettled] !== false) { return; } p[_isSettled] = true; @@ -90,6 +120,20 @@ v8.resolvePromise(p, value); } + // This must always be used instead of Promise.resolve(). If |value| is an + // object this will look up Object.prototype.then and so may be re-entrant. + function createResolvedPromise(value) { + if (v8.isPromise(value)) { + // This case applies when an underlying method returns a promise. Promises + // that are passed through in this way are not used with resolvePromise() + // or rejectPromise(). + return value; + } + const p = createPromise(); + resolvePromise(p, value); + return p; + } + function markPromiseAsHandled(p) { if (!v8.isPromise(p)) { streamInternalError(); @@ -198,8 +242,6 @@ const callFunction = v8.uncurryThis(global.Function.prototype.call); const errTmplMustBeFunctionOrUndefined = name => `${name} must be a function or undefined`; - const Promise_resolve = Promise.resolve.bind(Promise); - const Promise_reject = Promise.reject.bind(Promise); const Function_bind = v8.uncurryThis(global.Function.prototype.bind); function resolveMethod(O, P, nameForError) { @@ -228,7 +270,7 @@ // The implementation uses bound functions rather than lambdas where // possible to give the compiler the maximum opportunity to optimise. if (method === undefined) { - return () => Promise_resolve(); + return () => createResolvedPromise(); } if (algoArgCount === 0) { @@ -254,7 +296,7 @@ const method = resolveMethod(underlyingObject, methodName, methodNameForError); if (method === undefined) { - return () => Promise_resolve(); + return () => createResolvedPromise(); } if (algoArgCount === 0) { @@ -279,9 +321,9 @@ // assert(typeof F === 'function', 'IsCallable(F) is true.'); // assert(V !== undefined, 'V is not undefined.'); try { - return Promise_resolve(callFunction(F, V)); + return createResolvedPromise(callFunction(F, V)); } catch (e) { - return Promise_reject(e); + return createRejectedPromise(e); } } @@ -289,9 +331,9 @@ // assert(typeof F === 'function', 'IsCallable(F) is true.'); // assert(V !== undefined, 'V is not undefined.'); try { - return Promise_resolve(callFunction(F, V, arg0)); + return createResolvedPromise(callFunction(F, V, arg0)); } catch (e) { - return Promise_reject(e); + return createRejectedPromise(e); } } @@ -299,15 +341,18 @@ // assert(typeof F === 'function', 'IsCallable(F) is true.'); // assert(V !== undefined, 'V is not undefined.'); try { - return Promise_resolve(callFunction(F, V, arg0, arg1)); + return createResolvedPromise(callFunction(F, V, arg0, arg1)); } catch (e) { - return Promise_reject(e); + return createRejectedPromise(e); } } binding.streamOperations = { _queue, _queueTotalSize, + createPromise, + createRejectedPromise, + createResolvedPromise, hasOwnPropertyNoThrow, rejectPromise, resolvePromise, diff --git a/chromium/third_party/blink/renderer/core/streams/ReadableStream.js b/chromium/third_party/blink/renderer/core/streams/ReadableStream.js index a5ec08c1abf..36f11dcf76e 100644 --- a/chromium/third_party/blink/renderer/core/streams/ReadableStream.js +++ b/chromium/third_party/blink/renderer/core/streams/ReadableStream.js @@ -63,13 +63,14 @@ const Promise = global.Promise; const thenPromise = v8.uncurryThis(Promise.prototype.then); - const Promise_resolve = Promise.resolve.bind(Promise); - const Promise_reject = Promise.reject.bind(Promise); // From CommonOperations.js const { _queue, _queueTotalSize, + createPromise, + createRejectedPromise, + createResolvedPromise, hasOwnPropertyNoThrow, rejectPromise, resolvePromise, @@ -181,11 +182,11 @@ cancel(reason) { if (IsReadableStream(this) === false) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise(new TypeError(streamErrors.illegalInvocation)); } if (IsReadableStreamLocked(this) === true) { - return Promise_reject(new TypeError(errCancelLockedStream)); + return createRejectedPromise(new TypeError(errCancelLockedStream)); } return ReadableStreamCancel(this, reason); @@ -228,12 +229,12 @@ pipeTo(dest, {preventClose, preventAbort, preventCancel} = {}) { if (!IsReadableStream(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise(new TypeError(streamErrors.illegalInvocation)); } if (!binding.IsWritableStream(dest)) { // TODO(ricea): Think about having a better error message. - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise(new TypeError(streamErrors.illegalInvocation)); } preventClose = Boolean(preventClose); @@ -241,11 +242,11 @@ preventCancel = Boolean(preventCancel); if (IsReadableStreamLocked(this)) { - return Promise_reject(new TypeError(errCannotPipeLockedStream)); + return createRejectedPromise(new TypeError(errCannotPipeLockedStream)); } if (binding.IsWritableStreamLocked(dest)) { - return Promise_reject(new TypeError(errCannotPipeToALockedStream)); + return createRejectedPromise(new TypeError(errCannotPipeToALockedStream)); } return ReadableStreamPipeTo( @@ -275,7 +276,7 @@ const reader = AcquireReadableStreamDefaultReader(readable); const writer = binding.AcquireWritableStreamDefaultWriter(dest); let shuttingDown = false; - const promise = v8.createPromise(); + const promise = createPromise(); let reading = false; if (checkInitialState()) { @@ -506,7 +507,7 @@ let canceled2 = false; let reason1; let reason2; - const cancelPromise = v8.createPromise(); + const cancelPromise = createPromise(); function pullAlgorithm() { return thenPromise( @@ -591,7 +592,7 @@ // function ReadableStreamAddReadRequest(stream) { - const promise = v8.createPromise(); + const promise = createPromise(); stream[_reader][_readRequests].push(promise); return promise; } @@ -601,10 +602,10 @@ const state = ReadableStreamGetState(stream); if (state === STATE_CLOSED) { - return Promise_resolve(undefined); + return createResolvedPromise(undefined); } if (state === STATE_ERRORED) { - return Promise_reject(stream[_storedError]); + return createRejectedPromise(stream[_storedError]); } ReadableStreamClose(stream); @@ -681,7 +682,8 @@ get closed() { if (IsReadableStreamDefaultReader(this) === false) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } return this[_closedPromise]; @@ -689,11 +691,12 @@ cancel(reason) { if (IsReadableStreamDefaultReader(this) === false) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } if (this[_ownerReadableStream] === undefined) { - return Promise_reject(new TypeError(errCancelReleasedReader)); + return createRejectedPromise(new TypeError(errCancelReleasedReader)); } return ReadableStreamReaderGenericCancel(this, reason); @@ -701,11 +704,11 @@ read() { if (IsReadableStreamDefaultReader(this) === false) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise(new TypeError(streamErrors.illegalInvocation)); } if (this[_ownerReadableStream] === undefined) { - return Promise_reject(new TypeError(errReadReleasedReader)); + return createRejectedPromise(new TypeError(errReadReleasedReader)); } return ReadableStreamDefaultReaderRead(this); @@ -757,13 +760,13 @@ switch (ReadableStreamGetState(stream)) { case STATE_READABLE: - reader[_closedPromise] = v8.createPromise(); + reader[_closedPromise] = createPromise(); break; case STATE_CLOSED: - reader[_closedPromise] = Promise_resolve(undefined); + reader[_closedPromise] = createResolvedPromise(undefined); break; case STATE_ERRORED: - reader[_closedPromise] = Promise_reject(stream[_storedError]); + reader[_closedPromise] = createRejectedPromise(stream[_storedError]); markPromiseAsHandled(reader[_closedPromise]); break; } @@ -788,7 +791,7 @@ new TypeError(errReleasedReaderClosedPromise)); } else { reader[_closedPromise] = - Promise_reject(new TypeError(errReleasedReaderClosedPromise)); + createRejectedPromise(new TypeError(errReleasedReaderClosedPromise)); } markPromiseAsHandled(reader[_closedPromise]); @@ -802,10 +805,10 @@ switch (ReadableStreamGetState(stream)) { case STATE_CLOSED: - return Promise_resolve(CreateIterResultObject(undefined, true)); + return createResolvedPromise(CreateIterResultObject(undefined, true)); case STATE_ERRORED: - return Promise_reject(stream[_storedError]); + return createRejectedPromise(stream[_storedError]); default: return ReadableStreamDefaultControllerPull(stream[_controller]); @@ -902,7 +905,7 @@ ReadableStreamDefaultControllerCallPullIfNeeded(controller); } - return Promise_resolve(CreateIterResultObject(chunk, false)); + return createResolvedPromise(CreateIterResultObject(chunk, false)); } const pendingPromise = ReadableStreamAddReadRequest(stream); @@ -1054,7 +1057,7 @@ controller[_cancelAlgorithm] = cancelAlgorithm; stream[_controller] = controller; - thenPromise(Promise_resolve(startAlgorithm()), () => { + thenPromise(createResolvedPromise(startAlgorithm()), () => { controller[_readableStreamDefaultControllerBits] |= STARTED; ReadableStreamDefaultControllerCallPullIfNeeded(controller); }, r => ReadableStreamDefaultControllerError(controller, r)); diff --git a/chromium/third_party/blink/renderer/core/streams/TransformStream.js b/chromium/third_party/blink/renderer/core/streams/TransformStream.js index fc8f9695ce9..cc6b7b674c5 100644 --- a/chromium/third_party/blink/renderer/core/streams/TransformStream.js +++ b/chromium/third_party/blink/renderer/core/streams/TransformStream.js @@ -38,11 +38,12 @@ const Promise = global.Promise; const thenPromise = v8.uncurryThis(Promise.prototype.then); - const Promise_resolve = Promise.resolve.bind(Promise); - const Promise_reject = Promise.reject.bind(Promise); // From CommonOperations.js const { + createPromise, + createRejectedPromise, + createResolvedPromise, hasOwnPropertyNoThrow, resolvePromise, CreateAlgorithmFromUnderlyingMethodPassingController, @@ -97,7 +98,7 @@ readableHighWaterMark = ValidateAndNormalizeHighWaterMark(readableHighWaterMark); - const startPromise = v8.createPromise(); + const startPromise = createPromise(); InitializeTransformStream( this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm); @@ -151,7 +152,7 @@ // readableHighWaterMark >= 0, // '! IsNonNegativeNumber(_readableHighWaterMark_) is true'); const stream = ObjectCreate(TransformStream_prototype); - const startPromise = v8.createPromise(); + const startPromise = createPromise(); InitializeTransformStream( stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm); @@ -180,7 +181,7 @@ TransformStreamDefaultSourcePullAlgorithm(stream); const cancelAlgorithm = reason => { TransformStreamErrorWritableAndUnblockWrite(stream, reason); - return Promise_resolve(undefined); + return createResolvedPromise(undefined); }; stream[_readable] = binding.CreateReadableStream( startAlgorithm, pullAlgorithm, cancelAlgorithm, readableHighWaterMark, @@ -224,7 +225,7 @@ resolvePromise(stream[_backpressureChangePromise], undefined); } - stream[_backpressureChangePromise] = v8.createPromise(); + stream[_backpressureChangePromise] = createPromise(); stream[_backpressure] = backpressure; } @@ -312,9 +313,9 @@ transformAlgorithm = chunk => { try { TransformStreamDefaultControllerEnqueue(controller, chunk); - return Promise_resolve(); + return createResolvedPromise(); } catch (resultValue) { - return Promise_reject(resultValue); + return createRejectedPromise(resultValue); } }; } @@ -397,7 +398,7 @@ function TransformStreamDefaultSinkAbortAlgorithm(stream, reason) { TransformStreamError(stream, reason); - return Promise_resolve(); + return createResolvedPromise(); } function TransformStreamDefaultSinkCloseAlgorithm(stream) { diff --git a/chromium/third_party/blink/renderer/core/streams/WritableStream.js b/chromium/third_party/blink/renderer/core/streams/WritableStream.js index 56805f1a5d0..5cdc20f8b8a 100644 --- a/chromium/third_party/blink/renderer/core/streams/WritableStream.js +++ b/chromium/third_party/blink/renderer/core/streams/WritableStream.js @@ -72,13 +72,14 @@ const Promise = global.Promise; const thenPromise = v8.uncurryThis(Promise.prototype.then); - const Promise_resolve = Promise.resolve.bind(Promise); - const Promise_reject = Promise.reject.bind(Promise); // From CommonOperations.js const { _queue, _queueTotalSize, + createPromise, + createRejectedPromise, + createResolvedPromise, hasOwnPropertyNoThrow, rejectPromise, resolvePromise, @@ -237,7 +238,7 @@ function WritableStreamAbort(stream, reason) { const state = stream[_stateAndFlags] & STATE_MASK; if (state === CLOSED || state === ERRORED) { - return Promise_resolve(undefined); + return createResolvedPromise(undefined); } if (stream[_pendingAbortRequest] !== undefined) { return stream[_pendingAbortRequest].promise; @@ -251,7 +252,7 @@ reason = undefined; } - const promise = v8.createPromise(); + const promise = createPromise(); stream[_pendingAbortRequest] = {promise, reason, wasAlreadyErroring}; if (!wasAlreadyErroring) { @@ -267,7 +268,7 @@ // '! IsWritableStreamLocked(writer) is true.'); // assert((stream[_stateAndFlags] & STATE_MASK) === WRITABLE, // 'stream.[[state]] is "writable".'); - const promise = v8.createPromise(); + const promise = createPromise(); stream[_writeRequests].push(promise); return promise; } @@ -474,7 +475,7 @@ if (writer !== undefined && backpressure !== Boolean(stream[_stateAndFlags] & BACKPRESSURE_FLAG)) { if (backpressure) { - writer[_readyPromise] = v8.createPromise(); + writer[_readyPromise] = createPromise(); } else { // assert(!backpressure, '_backpressure_ is *false*.'); resolvePromise(writer[_readyPromise], undefined); @@ -542,33 +543,33 @@ case WRITABLE: { if (!WritableStreamCloseQueuedOrInFlight(stream) && stream[_stateAndFlags] & BACKPRESSURE_FLAG) { - this[_readyPromise] = v8.createPromise(); + this[_readyPromise] = createPromise(); } else { - this[_readyPromise] = Promise_resolve(undefined); + this[_readyPromise] = createResolvedPromise(undefined); } - this[_closedPromise] = v8.createPromise(); + this[_closedPromise] = createPromise(); break; } case ERRORING: { - this[_readyPromise] = Promise_reject(stream[_storedError]); + this[_readyPromise] = createRejectedPromise(stream[_storedError]); markPromiseAsHandled(this[_readyPromise]); - this[_closedPromise] = v8.createPromise(); + this[_closedPromise] = createPromise(); break; } case CLOSED: { - this[_readyPromise] = Promise_resolve(undefined); - this[_closedPromise] = Promise_resolve(undefined); + this[_readyPromise] = createResolvedPromise(undefined); + this[_closedPromise] = createResolvedPromise(undefined); break; } default: { // assert(state === ERRORED, '_state_ is `"errored"`.'); const storedError = stream[_storedError]; - this[_readyPromise] = Promise_reject(storedError); + this[_readyPromise] = createRejectedPromise(storedError); markPromiseAsHandled(this[_readyPromise]); - this[_closedPromise] = Promise_reject(storedError); + this[_closedPromise] = createRejectedPromise(storedError); markPromiseAsHandled(this[_closedPromise]); break; } @@ -577,7 +578,8 @@ get closed() { if (!IsWritableStreamDefaultWriter(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } return this[_closedPromise]; } @@ -594,31 +596,36 @@ get ready() { if (!IsWritableStreamDefaultWriter(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } return this[_readyPromise]; } abort(reason) { if (!IsWritableStreamDefaultWriter(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } if (this[_ownerWritableStream] === undefined) { - return Promise_reject(createWriterLockReleasedError(verbAborted)); + return createRejectedPromise( + createWriterLockReleasedError(verbAborted)); } return WritableStreamDefaultWriterAbort(this, reason); } close() { if (!IsWritableStreamDefaultWriter(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } const stream = this[_ownerWritableStream]; if (stream === undefined) { - return Promise_reject(createWriterLockReleasedError(verbClosed)); + return createRejectedPromise(createWriterLockReleasedError(verbClosed)); } if (WritableStreamCloseQueuedOrInFlight(stream)) { - return Promise_reject(new TypeError(errCloseCloseRequestedStream)); + return createRejectedPromise( + new TypeError(errCloseCloseRequestedStream)); } return WritableStreamDefaultWriterClose(this); } @@ -638,10 +645,12 @@ write(chunk) { if (!IsWritableStreamDefaultWriter(this)) { - return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + return createRejectedPromise( + new TypeError(streamErrors.illegalInvocation)); } if (this[_ownerWritableStream] === undefined) { - return Promise_reject(createWriterLockReleasedError(verbWrittenTo)); + return createRejectedPromise( + createWriterLockReleasedError(verbWrittenTo)); } return WritableStreamDefaultWriterWrite(this, chunk); } @@ -665,7 +674,7 @@ // assert(stream !== undefined, 'stream is not undefined.'); const state = stream[_stateAndFlags] & STATE_MASK; if (state === CLOSED || state === ERRORED) { - return Promise_reject( + return createRejectedPromise( createCannotActionOnStateStreamError('close', state)); } @@ -673,7 +682,7 @@ // '_state_ is `"writable"` or `"erroring"`.'); // assert(!WritableStreamCloseQueuedOrInFlight(stream), // '! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.'); - const promise = v8.createPromise(); + const promise = createPromise(); stream[_closeRequest] = promise; if ((stream[_stateAndFlags] & BACKPRESSURE_FLAG) && state === WRITABLE) { @@ -688,10 +697,10 @@ // assert(stream !== undefined, 'stream is not undefined.'); const state = stream[_stateAndFlags] & STATE_MASK; if (WritableStreamCloseQueuedOrInFlight(stream) || state === CLOSED) { - return Promise_resolve(undefined); + return createResolvedPromise(undefined); } if (state === ERRORED) { - return Promise_reject(stream[_storedError]); + return createRejectedPromise(stream[_storedError]); } // assert(state === WRITABLE || state === ERRORING, @@ -705,7 +714,7 @@ if (promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) { rejectPromise(writer[_closedPromise], error); } else { - writer[_closedPromise] = Promise_reject(error); + writer[_closedPromise] = createRejectedPromise(error); } markPromiseAsHandled(writer[_closedPromise]); } @@ -716,7 +725,7 @@ if (promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) { rejectPromise(writer[_readyPromise], error); } else { - writer[_readyPromise] = Promise_reject(error); + writer[_readyPromise] = createRejectedPromise(error); } markPromiseAsHandled(writer[_readyPromise]); } @@ -756,22 +765,23 @@ const chunkSize = WritableStreamDefaultControllerGetChunkSize(controller, chunk); if (stream !== writer[_ownerWritableStream]) { - return Promise_reject(createWriterLockReleasedError(verbWrittenTo)); + return createRejectedPromise( + createWriterLockReleasedError(verbWrittenTo)); } const state = stream[_stateAndFlags] & STATE_MASK; if (state === ERRORED) { - return Promise_reject(stream[_storedError]); + return createRejectedPromise(stream[_storedError]); } if (WritableStreamCloseQueuedOrInFlight(stream)) { - return Promise_reject(new TypeError( + return createRejectedPromise(new TypeError( templateErrorCannotActionOnStateStream('write to', 'closing'))); } if (state === CLOSED) { - return Promise_reject( + return createRejectedPromise( createCannotActionOnStateStreamError('write to', CLOSED)); } if (state === ERRORING) { - return Promise_reject(stream[_storedError]); + return createRejectedPromise(stream[_storedError]); } // assert(state === WRITABLE, '_state_ is `"writable"`'); const promise = WritableStreamAddWriteRequest(stream); @@ -859,7 +869,7 @@ WritableStreamDefaultControllerGetBackpressure(controller); WritableStreamUpdateBackpressure(stream, backpressure); const startResult = startAlgorithm(); - const startPromise = Promise_resolve(startResult); + const startPromise = createResolvedPromise(startResult); thenPromise( startPromise, () => { |