Commit 8a899d12315 for nodejs
commit 8a899d123159d50c60dd9445365654159c555767
Author: Filip Skokan <panva.ip@gmail.com>
Date: Tue Sep 22 22:16:43 2026 +0200
crypto: copy detached parameters as empty
Treat detached BufferSource parameters as empty byte sequences during
algorithm normalization, including detached DataViews.
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index 05da8442ba2..b045696bd11 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -3,6 +3,7 @@
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
+ ArrayBufferPrototypeGetDetached,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSlice,
@@ -107,6 +108,7 @@ const {
const {
isDataView,
+ isArrayBuffer,
isArrayBufferView,
isAnyArrayBuffer,
isPromise,
@@ -874,19 +876,43 @@ function getDataViewOrTypedArrayByteLength(V) {
}
function getBufferSourceByteLength(V) {
- return ArrayBufferIsView(V) ?
- getDataViewOrTypedArrayByteLength(V) :
- ArrayBufferPrototypeGetByteLength(V);
+ // ArrayBuffer and typed array lengths already become zero when detached.
+ if (!ArrayBufferIsView(V))
+ return ArrayBufferPrototypeGetByteLength(V);
+ if (!isDataView(V))
+ return TypedArrayPrototypeGetByteLength(V);
+ const buffer = DataViewPrototypeGetBuffer(V);
+ if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
+ return 0;
+ return DataViewPrototypeGetByteLength(V);
}
function getBufferSourceBytes(V) {
- return ArrayBufferIsView(V) ?
- new Uint8Array(
- getDataViewOrTypedArrayBuffer(V),
- getDataViewOrTypedArrayByteOffset(V),
- getDataViewOrTypedArrayByteLength(V),
- ) :
- new Uint8Array(V, 0, ArrayBufferPrototypeGetByteLength(V));
+ if (!ArrayBufferIsView(V)) {
+ const length = ArrayBufferPrototypeGetByteLength(V);
+ if (length === 0 && ArrayBufferPrototypeGetDetached(V))
+ return new Uint8Array(0);
+ return new Uint8Array(V, 0, length);
+ }
+
+ let buffer;
+ let offset;
+ let length;
+ if (isDataView(V)) {
+ buffer = DataViewPrototypeGetBuffer(V);
+ if (isArrayBuffer(buffer) && ArrayBufferPrototypeGetDetached(buffer))
+ return new Uint8Array(0);
+ offset = DataViewPrototypeGetByteOffset(V);
+ length = DataViewPrototypeGetByteLength(V);
+ } else {
+ buffer = TypedArrayPrototypeGetBuffer(V);
+ offset = TypedArrayPrototypeGetByteOffset(V);
+ length = TypedArrayPrototypeGetByteLength(V);
+ if (length === 0 && isArrayBuffer(buffer) &&
+ ArrayBufferPrototypeGetDetached(buffer))
+ return new Uint8Array(0);
+ }
+ return new Uint8Array(buffer, offset, length);
}
function getOptionalByteLength(V) {
diff --git a/test/parallel/test-webcrypto-detached-parameters.js b/test/parallel/test-webcrypto-detached-parameters.js
new file mode 100644
index 00000000000..cf8b34cc9dc
--- /dev/null
+++ b/test/parallel/test-webcrypto-detached-parameters.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const { subtle } = globalThis.crypto;
+
+function detached(kind) {
+ const buffer = new ArrayBuffer(16);
+ const value = kind === 'ArrayBuffer' ? buffer :
+ kind === 'DataView' ? new DataView(buffer) : new Uint8Array(buffer);
+ buffer.transfer();
+ return value;
+}
+
+(async () => {
+ const empty = new Uint8Array(0);
+ const input = new Uint8Array(16);
+ const key = await subtle.importKey('raw', input, 'HKDF', false, ['deriveBits']);
+ const algorithm = { name: 'HKDF', hash: 'SHA-256', salt: empty, info: empty };
+ const expected = await subtle.deriveBits(algorithm, key, 256);
+ const digest = await subtle.digest('SHA-256', empty);
+ for (const kind of ['ArrayBuffer', 'Uint8Array', 'DataView']) {
+ for (const member of ['salt', 'info']) {
+ assert.deepStrictEqual(await subtle.deriveBits({
+ ...algorithm, [member]: detached(kind),
+ }, key, 256), expected);
+ }
+ assert.deepStrictEqual(await subtle.digest('SHA-256', detached(kind)), digest);
+ }
+})().then(common.mustCall());