fix: strict type check for cross realms (#21669)

Deno v1.39 introduces `vm.runInNewContext`. This may cause problems when
using `Object.prototype.isPrototypeOf` to check built-in types.

```js
import vm from "node:vm";

const err = new Error();
const crossErr = vm.runInNewContext(`new Error()`);

console.assert( !(crossErr instanceof Error) );
console.assert( Object.getPrototypeOf(err) !== Object.getPrototypeOf(crossErr) );
```

This PR changes to check using internal slots solves them.

---

current: 

```
> import vm from "node:vm";
undefined
> vm.runInNewContext(`new Error("message")`)
Error {}
> vm.runInNewContext(`new Date("2018-12-10T02:26:59.002Z")`)
Date {}
```

this PR:

```
> import vm from "node:vm";
undefined
> vm.runInNewContext(`new Error("message")`)
Error: message
    at <anonymous>:1:1
> vm.runInNewContext(`new Date("2018-12-10T02:26:59.002Z")`)
2018-12-10T02:26:59.002Z
```

---------

Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Kenta Moriuchi 2024-01-04 13:12:38 +09:00 committed by GitHub
parent 4855674857
commit b2cd254c35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 532 additions and 906 deletions

View file

@ -36,7 +36,6 @@ import {
const {
ArrayBuffer,
ArrayBufferIsView,
ArrayBufferPrototype,
ArrayBufferPrototypeGetByteLength,
ArrayBufferPrototypeSlice,
ArrayPrototypeMap,
@ -96,6 +95,12 @@ const {
WeakMapPrototypeSet,
queueMicrotask,
} = primordials;
const {
isAnyArrayBuffer,
isArrayBuffer,
isSharedArrayBuffer,
isTypedArray,
} = core;
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { assert, AssertionError } from "ext:deno_web/00_infra.js";
@ -272,8 +277,7 @@ class Queue {
* @returns {boolean}
*/
function isDetachedBuffer(O) {
// deno-lint-ignore prefer-primordials
if (ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O)) {
if (isSharedArrayBuffer(O)) {
return false;
}
return ArrayBufferPrototypeGetByteLength(O) === 0 &&
@ -286,11 +290,7 @@ function isDetachedBuffer(O) {
*/
function canTransferArrayBuffer(O) {
assert(typeof O === "object");
assert(
ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, O) ||
// deno-lint-ignore prefer-primordials
ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O),
);
assert(isAnyArrayBuffer(O));
if (isDetachedBuffer(O)) {
return false;
}
@ -311,8 +311,7 @@ function transferArrayBuffer(O) {
* @returns {number}
*/
function getArrayBufferByteLength(O) {
// deno-lint-ignore prefer-primordials
if (ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O)) {
if (isSharedArrayBuffer(O)) {
// TODO(petamoriken): use primordials
// deno-lint-ignore prefer-primordials
return O.byteLength;
@ -328,8 +327,7 @@ function getArrayBufferByteLength(O) {
function cloneAsUint8Array(O) {
assert(typeof O === "object");
assert(ArrayBufferIsView(O));
if (TypedArrayPrototypeGetSymbolToStringTag(O) !== undefined) {
// TypedArray
if (isTypedArray(O)) {
return TypedArrayPrototypeSlice(
new Uint8Array(
TypedArrayPrototypeGetBuffer(/** @type {Uint8Array} */ (O)),
@ -338,7 +336,6 @@ function cloneAsUint8Array(O) {
),
);
} else {
// DataView
return TypedArrayPrototypeSlice(
new Uint8Array(
DataViewPrototypeGetBuffer(/** @type {DataView} */ (O)),
@ -1340,15 +1337,7 @@ function readableByteStreamControllerEnqueue(controller, chunk) {
}
let buffer, byteLength, byteOffset;
if (TypedArrayPrototypeGetSymbolToStringTag(chunk) === undefined) {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (chunk));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
byteOffset = DataViewPrototypeGetByteOffset(
/** @type {DataView} */ (chunk),
);
} else {
if (isTypedArray(chunk)) {
buffer = TypedArrayPrototypeGetBuffer(/** @type {Uint8Array}} */ (chunk));
byteLength = TypedArrayPrototypeGetByteLength(
/** @type {Uint8Array} */ (chunk),
@ -1356,6 +1345,14 @@ function readableByteStreamControllerEnqueue(controller, chunk) {
byteOffset = TypedArrayPrototypeGetByteOffset(
/** @type {Uint8Array} */ (chunk),
);
} else {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (chunk));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
byteOffset = DataViewPrototypeGetByteOffset(
/** @type {DataView} */ (chunk),
);
}
if (isDetachedBuffer(buffer)) {
@ -1461,7 +1458,7 @@ function readableByteStreamControllerEnqueueClonedChunkToQueue(
) {
let cloneResult;
try {
if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, buffer)) {
if (isArrayBuffer(buffer)) {
cloneResult = ArrayBufferPrototypeSlice(
buffer,
byteOffset,
@ -2292,11 +2289,7 @@ function readableByteStreamControllerRespondWithNewView(controller, view) {
assert(controller[_pendingPullIntos].length !== 0);
let buffer, byteLength, byteOffset;
if (TypedArrayPrototypeGetSymbolToStringTag(view) === undefined) {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (view));
byteLength = DataViewPrototypeGetByteLength(/** @type {DataView} */ (view));
byteOffset = DataViewPrototypeGetByteOffset(/** @type {DataView} */ (view));
} else {
if (isTypedArray(view)) {
buffer = TypedArrayPrototypeGetBuffer(/** @type {Uint8Array}} */ (view));
byteLength = TypedArrayPrototypeGetByteLength(
/** @type {Uint8Array} */ (view),
@ -2304,7 +2297,12 @@ function readableByteStreamControllerRespondWithNewView(controller, view) {
byteOffset = TypedArrayPrototypeGetByteOffset(
/** @type {Uint8Array} */ (view),
);
} else {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (view));
byteLength = DataViewPrototypeGetByteLength(/** @type {DataView} */ (view));
byteOffset = DataViewPrototypeGetByteOffset(/** @type {DataView} */ (view));
}
assert(!isDetachedBuffer(buffer));
const firstDescriptor = controller[_pendingPullIntos][0];
const state = controller[_stream][_state];
@ -3364,14 +3362,14 @@ function readableByteStreamTee(stream) {
}
if (chunk !== undefined) {
let byteLength;
if (TypedArrayPrototypeGetSymbolToStringTag(chunk) === undefined) {
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
} else {
if (isTypedArray(chunk)) {
byteLength = TypedArrayPrototypeGetByteLength(
/** @type {Uint8Array} */ (chunk),
);
} else {
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
}
assert(byteLength === 0);
if (!byobCanceled) {
@ -5581,16 +5579,16 @@ class ReadableStreamBYOBReader {
}
let buffer, byteLength;
if (TypedArrayPrototypeGetSymbolToStringTag(view) === undefined) {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (view));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (view),
);
} else {
if (isTypedArray(view)) {
buffer = TypedArrayPrototypeGetBuffer(/** @type {Uint8Array} */ (view));
byteLength = TypedArrayPrototypeGetByteLength(
/** @type {Uint8Array} */ (view),
);
} else {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (view));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (view),
);
}
if (byteLength === 0) {
return PromiseReject(
@ -5613,7 +5611,7 @@ class ReadableStreamBYOBReader {
if (options.min === 0) {
return PromiseReject(new TypeError("options.min must be non-zero"));
}
if (TypedArrayPrototypeGetSymbolToStringTag(view) !== undefined) {
if (isTypedArray(view)) {
if (options.min > TypedArrayPrototypeGetLength(view)) {
return PromiseReject(
new RangeError("options.min must be smaller or equal to view's size"),
@ -5745,12 +5743,12 @@ class ReadableStreamBYOBRequest {
}
let buffer, byteLength;
if (TypedArrayPrototypeGetSymbolToStringTag(this[_view]) === undefined) {
buffer = DataViewPrototypeGetBuffer(this[_view]);
byteLength = DataViewPrototypeGetByteLength(this[_view]);
} else {
if (isTypedArray(this[_view])) {
buffer = TypedArrayPrototypeGetBuffer(this[_view]);
byteLength = TypedArrayPrototypeGetByteLength(this[_view]);
} else {
buffer = DataViewPrototypeGetBuffer(this[_view]);
byteLength = DataViewPrototypeGetByteLength(this[_view]);
}
if (isDetachedBuffer(buffer)) {
throw new TypeError(
@ -5774,10 +5772,10 @@ class ReadableStreamBYOBRequest {
}
let buffer;
if (TypedArrayPrototypeGetSymbolToStringTag(view) === undefined) {
buffer = DataViewPrototypeGetBuffer(view);
} else {
if (isTypedArray(view)) {
buffer = TypedArrayPrototypeGetBuffer(view);
} else {
buffer = DataViewPrototypeGetBuffer(view);
}
if (isDetachedBuffer(buffer)) {
throw new TypeError(
@ -5864,16 +5862,16 @@ class ReadableByteStreamController {
const arg1 = "Argument 1";
chunk = webidl.converters.ArrayBufferView(chunk, prefix, arg1);
let buffer, byteLength;
if (TypedArrayPrototypeGetSymbolToStringTag(chunk) === undefined) {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (chunk));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
} else {
if (isTypedArray(chunk)) {
buffer = TypedArrayPrototypeGetBuffer(/** @type {Uint8Array} */ (chunk));
byteLength = TypedArrayPrototypeGetByteLength(
/** @type {Uint8Array} */ (chunk),
);
} else {
buffer = DataViewPrototypeGetBuffer(/** @type {DataView} */ (chunk));
byteLength = DataViewPrototypeGetByteLength(
/** @type {DataView} */ (chunk),
);
}
if (byteLength === 0) {
throw webidl.makeException(