Skip to content

Commit fa99c0d

Browse files
committed
buffer: disallow ArrayBuffer transfer on pooled buffer
1 parent 7804cb4 commit fa99c0d

File tree

6 files changed

+44
-0
lines changed

6 files changed

+44
-0
lines changed

doc/api/worker_threads.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ In particular, this makes sense for objects that can be cloned, rather than
256256
transferred, and which are used by other objects on the sending side.
257257
For example, Node.js marks the `ArrayBuffer`s it uses for its
258258
[`Buffer` pool][`Buffer.allocUnsafe()`] with this.
259+
`ArrayBuffer.prototype.transfer()` is disallowed on such array buffer
260+
instances.
259261
260262
This operation cannot be undone.
261263

lib/buffer.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ const {
143143
utf8Write,
144144
} = require('internal/buffer');
145145

146+
const {
147+
namespace: {
148+
addDeserializeCallback,
149+
isBuildingSnapshot,
150+
},
151+
} = require('internal/v8/startup_snapshot');
152+
146153
FastBuffer.prototype.constructor = Buffer;
147154
Buffer.prototype = FastBuffer.prototype;
148155
addBufferPrototypeMethods(Buffer.prototype);
@@ -173,6 +180,13 @@ function createPool() {
173180
poolOffset = 0;
174181
}
175182
createPool();
183+
if (isBuildingSnapshot()) {
184+
addDeserializeCallback(() => {
185+
// TODO(legendecas): ArrayBuffer.[[ArrayBufferDetachKey]] is not been serialized.
186+
// Remove this callback when snapshot serialization supports it.
187+
createPool();
188+
});
189+
}
176190

177191
function alignPool() {
178192
// Ensure aligned slices

lib/internal/buffer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
Float64Array,
77
MathFloor,
88
Number,
9+
Symbol,
910
Uint8Array,
1011
} = primordials;
1112

@@ -15,6 +16,8 @@ const {
1516
ERR_OUT_OF_RANGE,
1617
} = require('internal/errors').codes;
1718
const { validateNumber } = require('internal/validators');
19+
const { isArrayBuffer } = require('util/types');
20+
1821
const {
1922
asciiSlice,
2023
base64Slice,
@@ -31,6 +34,7 @@ const {
3134
ucs2Write,
3235
utf8WriteStatic,
3336
createUnsafeArrayBuffer,
37+
setDetachKey,
3438
} = internalBinding('buffer');
3539

3640
const {
@@ -1074,6 +1078,10 @@ function markAsUntransferable(obj) {
10741078
if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null)
10751079
return; // This object is a primitive and therefore already untransferable.
10761080
obj[untransferable_object_private_symbol] = true;
1081+
1082+
if (isArrayBuffer(obj)) {
1083+
setDetachKey(obj, Symbol('unique_detach_key_for_untransferable_arraybuffer'));
1084+
}
10771085
}
10781086

10791087
// This simply checks if the object is marked as untransferable and doesn't

src/node_buffer.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,15 @@ static void Atob(const FunctionCallbackInfo<Value>& args) {
13541354
args.GetReturnValue().Set(error_code);
13551355
}
13561356

1357+
static void SetDetachKey(const FunctionCallbackInfo<Value>& args) {
1358+
CHECK_EQ(args.Length(), 2);
1359+
CHECK(args[0]->IsArrayBuffer());
1360+
1361+
Local<ArrayBuffer> ab = args[0].As<ArrayBuffer>();
1362+
Local<Value> key = args[1];
1363+
ab->SetDetachKey(key);
1364+
}
1365+
13571366
namespace {
13581367

13591368
std::pair<void*, size_t> DecomposeBufferToParts(Local<Value> buffer) {
@@ -1638,6 +1647,8 @@ void Initialize(Local<Object> target,
16381647
"utf8WriteStatic",
16391648
SlowWriteString<UTF8>,
16401649
&fast_write_string_utf8);
1650+
1651+
SetMethod(context, target, "setDetachKey", SetDetachKey);
16411652
}
16421653

16431654
} // anonymous namespace
@@ -1692,6 +1703,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
16921703

16931704
registry->Register(Atob);
16941705
registry->Register(Btoa);
1706+
1707+
registry->Register(SetDetachKey);
16951708
}
16961709

16971710
} // namespace Buffer

test/parallel/test-bootstrap-modules.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ expected.beforePreExec = new Set([
5151
'NativeModule events',
5252
'Internal Binding buffer',
5353
'Internal Binding string_decoder',
54+
'NativeModule util/types',
5455
'NativeModule internal/buffer',
5556
'NativeModule buffer',
5657
'Internal Binding messaging',

test/parallel/test-buffer-pool-untransferable.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ assert.throws(() => port1.postMessage(a, [ a.buffer ]), {
2121
// Verify that the pool ArrayBuffer has not actually been transferred:
2222
assert.strictEqual(a.buffer, b.buffer);
2323
assert.strictEqual(a.length, length);
24+
25+
// Verify that ArrayBuffer.prototype.transfer() also throws.
26+
assert.throws(() => a.buffer.transfer(), {
27+
name: 'TypeError',
28+
});
29+
assert.strictEqual(a.buffer, b.buffer);

0 commit comments

Comments
 (0)