mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
buffer: disallow ArrayBuffer transfer on pooled buffer
PR-URL: https://github.com/nodejs/node/pull/61372 Fixes: https://github.com/nodejs/node/issues/61362 Refs: https://github.com/v8/v8/commit/c5ff7c4d6cdea06d0319609875c41eaa77b70db0 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
This commit is contained in:
1 parent
859332bf76
commit
6cc2ed56da
6 files changed
+45
No files matched your search
@@ -256,6 +256,8 @@ In particular, this makes sense for objects that can be cloned, rather than
|
||||
transferred, and which are used by other objects on the sending side.
|
||||
For example, Node.js marks the `ArrayBuffer`s it uses for its
|
||||
[`Buffer` pool][`Buffer.allocUnsafe()`] with this.
|
||||
`ArrayBuffer.prototype.transfer()` is disallowed on such array buffer
|
||||
instances.
|
||||
|
||||
This operation cannot be undone.
|
||||
|
||||
|
||||
@@ -143,6 +143,13 @@ const {
|
||||
utf8Write,
|
||||
} = require('internal/buffer');
|
||||
|
||||
const {
|
||||
namespace: {
|
||||
addDeserializeCallback,
|
||||
isBuildingSnapshot,
|
||||
},
|
||||
} = require('internal/v8/startup_snapshot');
|
||||
|
||||
FastBuffer.prototype.constructor = Buffer;
|
||||
Buffer.prototype = FastBuffer.prototype;
|
||||
addBufferPrototypeMethods(Buffer.prototype);
|
||||
@@ -173,6 +180,13 @@ function createPool() {
|
||||
poolOffset = 0;
|
||||
}
|
||||
createPool();
|
||||
if (isBuildingSnapshot()) {
|
||||
addDeserializeCallback(() => {
|
||||
// TODO(legendecas): ArrayBuffer.[[ArrayBufferDetachKey]] is not been serialized.
|
||||
// Remove this callback when snapshot serialization supports it.
|
||||
createPool();
|
||||
});
|
||||
}
|
||||
|
||||
function alignPool() {
|
||||
// Ensure aligned slices
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
Float64Array,
|
||||
MathFloor,
|
||||
Number,
|
||||
Symbol,
|
||||
Uint8Array,
|
||||
} = primordials;
|
||||
|
||||
@@ -15,6 +16,8 @@ const {
|
||||
ERR_OUT_OF_RANGE,
|
||||
} = require('internal/errors').codes;
|
||||
const { validateNumber } = require('internal/validators');
|
||||
const { isArrayBuffer } = require('util/types');
|
||||
|
||||
const {
|
||||
asciiSlice,
|
||||
base64Slice,
|
||||
@@ -31,6 +34,7 @@ const {
|
||||
ucs2Write,
|
||||
utf8WriteStatic,
|
||||
createUnsafeArrayBuffer,
|
||||
setDetachKey,
|
||||
} = internalBinding('buffer');
|
||||
|
||||
const {
|
||||
@@ -1068,12 +1072,17 @@ function addBufferPrototypeMethods(proto) {
|
||||
proto.utf8Write = function(string, offset, length) { return utf8Write(this, string, offset, length); };
|
||||
}
|
||||
|
||||
const kDetachKey = Symbol('detach_key_for_untransferable_arraybuffer');
|
||||
// This would better be placed in internal/worker/io.js, but that doesn't work
|
||||
// because Buffer needs this and that would introduce a cyclic dependency.
|
||||
function markAsUntransferable(obj) {
|
||||
if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null)
|
||||
return; // This object is a primitive and therefore already untransferable.
|
||||
obj[untransferable_object_private_symbol] = true;
|
||||
|
||||
if (isArrayBuffer(obj)) {
|
||||
setDetachKey(obj, kDetachKey);
|
||||
}
|
||||
}
|
||||
|
||||
// This simply checks if the object is marked as untransferable and doesn't
|
||||
|
||||
@@ -1377,6 +1377,15 @@ static void Atob(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(error_code);
|
||||
}
|
||||
|
||||
static void SetDetachKey(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK_EQ(args.Length(), 2);
|
||||
CHECK(args[0]->IsArrayBuffer());
|
||||
|
||||
Local<ArrayBuffer> ab = args[0].As<ArrayBuffer>();
|
||||
Local<Value> key = args[1];
|
||||
ab->SetDetachKey(key);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::pair<void*, size_t> DecomposeBufferToParts(Local<Value> buffer) {
|
||||
@@ -1661,6 +1670,8 @@ void Initialize(Local<Object> target,
|
||||
"utf8WriteStatic",
|
||||
SlowWriteString<UTF8>,
|
||||
&fast_write_string_utf8);
|
||||
|
||||
SetMethod(context, target, "setDetachKey", SetDetachKey);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
@@ -1715,6 +1726,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
|
||||
registry->Register(Atob);
|
||||
registry->Register(Btoa);
|
||||
|
||||
registry->Register(SetDetachKey);
|
||||
}
|
||||
|
||||
} // namespace Buffer
|
||||
|
||||
@@ -51,6 +51,7 @@ expected.beforePreExec = new Set([
|
||||
'NativeModule events',
|
||||
'Internal Binding buffer',
|
||||
'Internal Binding string_decoder',
|
||||
'NativeModule util/types',
|
||||
'NativeModule internal/buffer',
|
||||
'NativeModule buffer',
|
||||
'Internal Binding messaging',
|
||||
|
||||
@@ -21,3 +21,9 @@ assert.throws(() => port1.postMessage(a, [ a.buffer ]), {
|
||||
// Verify that the pool ArrayBuffer has not actually been transferred:
|
||||
assert.strictEqual(a.buffer, b.buffer);
|
||||
assert.strictEqual(a.length, length);
|
||||
|
||||
// Verify that ArrayBuffer.prototype.transfer() also throws.
|
||||
assert.throws(() => a.buffer.transfer(), {
|
||||
name: 'TypeError',
|
||||
});
|
||||
assert.strictEqual(a.buffer, b.buffer);
|
||||
Reference in new issue
Block a user