Skip to content

Commit e04663a

Browse files
panvanodejs-github-bot
authored andcommitted
crypto: read WebCrypto inputs through primordials
BufferSource conversion hands over the caller's own object uncopied, so byteLength, byteOffset, buffer and length reads on it run user-replaceable prototype accessors. Internal lookup tables are indexed with computed keys, so a polluted %Object.prototype% key answers a miss. The %Set% constructor iterates its argument through the user-mutable %Array.prototype% iterator. The algorithm registry and the hash name tables are detached from %Object.prototype% after construction rather than declared `__proto__: null`, which V8 places in dictionary mode. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: #65115 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Aviv Keller <me@aviv.sh>
1 parent 4758184 commit e04663a

15 files changed

Lines changed: 619 additions & 69 deletions

File tree

lib/internal/crypto/aes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const {
2424
const {
2525
getUsagesMask,
2626
jobPromise,
27+
getBufferSourceByteLength,
2728
} = require('internal/crypto/util');
2829

2930
const {
@@ -218,7 +219,7 @@ function aesImportKey(
218219
if (format === 'raw' && name === 'AES-OCB') {
219220
return undefined;
220221
}
221-
length = keyData.byteLength * 8;
222+
length = getBufferSourceByteLength(keyData) * 8;
222223
validateKeyLength(length);
223224
handle = importSecretKey(keyData);
224225
break;

lib/internal/crypto/cfrg.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const {
4-
SafeSet,
54
StringPrototypeToLowerCase,
65
TypedArrayPrototypeGetBuffer,
76
} = primordials;
@@ -27,6 +26,7 @@ const {
2726
const {
2827
getUsagesMask,
2928
jobPromise,
29+
toUsagesSet,
3030
} = require('internal/crypto/util');
3131

3232
const {
@@ -124,7 +124,7 @@ function cfrgImportKey(
124124
const { name } = algorithm;
125125
let handle;
126126
const allowedUsages = kUsages[name];
127-
const usagesSet = new SafeSet(usages);
127+
const usagesSet = toUsagesSet(usages);
128128
switch (format) {
129129
case 'KeyObjectHandle':
130130
verifyAcceptableKeyUse(

lib/internal/crypto/diffiehellman.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayBufferPrototypeGetByteLength,
45
ArrayBufferPrototypeSlice,
56
FunctionPrototypeCall,
67
ObjectDefineProperty,
@@ -373,7 +374,7 @@ function ecdhDeriveBits(algorithm, baseKey, length) {
373374
return jobPromiseThen(bits, (bits) => {
374375
const sliceLength = numBitsToBytes(length);
375376

376-
const { byteLength } = bits;
377+
const byteLength = ArrayBufferPrototypeGetByteLength(bits);
377378
// If the length is larger than the derived secret, throw.
378379
if (byteLength < sliceLength)
379380
throw lazyDOMException('derived bit length is too small', 'OperationError');

lib/internal/crypto/ec.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const {
4-
SafeSet,
54
TypedArrayPrototypeGetBuffer,
65
TypedArrayPrototypeGetByteLength,
76
} = primordials;
@@ -33,6 +32,7 @@ const {
3332
jobPromise,
3433
normalizeHashName,
3534
kNamedCurveAliases,
35+
toUsagesSet,
3636
} = require('internal/crypto/util');
3737

3838
const {
@@ -142,7 +142,7 @@ function ecImportKey(
142142

143143
let handle;
144144
const allowedUsages = kUsages[name];
145-
const usagesSet = new SafeSet(usages);
145+
const usagesSet = toUsagesSet(usages);
146146
switch (format) {
147147
case 'KeyObjectHandle':
148148
verifyAcceptableKeyUse(
@@ -215,7 +215,8 @@ function ecImportKey(
215215
throw lazyDOMException('Invalid keyData', 'DataError');
216216
}
217217

218-
if (kNamedCurveAliases[namedCurve] !== handle.keyDetail({}).namedCurve)
218+
if (kNamedCurveAliases[namedCurve] !==
219+
handle.keyDetail({ __proto__: null }).namedCurve)
219220
throw lazyDOMException('Named curve mismatch', 'DataError');
220221

221222
return new InternalCryptoKey(

lib/internal/crypto/hash.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const {
3030
kHandle,
3131
getCachedHashId,
3232
getHashCache,
33+
getOptionalByteLength,
3334
} = require('internal/crypto/util');
3435

3536
const {
@@ -217,8 +218,8 @@ function asyncDigest(algorithm, data) {
217218
// Fall through
218219
case 'cSHAKE256': {
219220
const outputLength = algorithm.outputLength;
220-
if (algorithm.functionName?.byteLength ||
221-
algorithm.customization?.byteLength) {
221+
if (getOptionalByteLength(algorithm.functionName) ||
222+
getOptionalByteLength(algorithm.customization)) {
222223
if (CShakeJob === undefined) {
223224
throw lazyDOMException(
224225
'Non-empty CShakeParams functionName or customization is not supported',

lib/internal/crypto/hashnames.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ObjectKeys,
5+
ObjectSetPrototypeOf,
56
} = primordials;
67

78
const kHashContextNode = 1;
@@ -71,15 +72,22 @@ const kHashNames = {
7172
},
7273
};
7374

75+
// Both tables are indexed with computed keys, so a polluted %Object.prototype%
76+
// key must not answer a miss. Detached here rather than declared
77+
// `__proto__: null`: V8 puts that literal form in dictionary mode.
78+
ObjectSetPrototypeOf(kHashNames, null);
79+
7480
{
7581
// Index the aliases
7682
const keys = ObjectKeys(kHashNames);
7783
for (let n = 0; n < keys.length; n++) {
78-
const contexts = ObjectKeys(kHashNames[keys[n]]);
84+
const entry = kHashNames[keys[n]];
85+
ObjectSetPrototypeOf(entry, null);
86+
const contexts = ObjectKeys(entry);
7987
for (let i = 0; i < contexts.length; i++) {
80-
const alias = kHashNames[keys[n]][contexts[i]];
88+
const alias = entry[contexts[i]];
8189
if (kHashNames[alias] === undefined)
82-
kHashNames[alias] = kHashNames[keys[n]];
90+
kHashNames[alias] = entry;
8391
}
8492
}
8593
}

lib/internal/crypto/keys.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
ObjectDefineProperties,
66
ObjectPrototypeHasOwnProperty,
77
ObjectSetPrototypeOf,
8-
SafeSet,
98
StringPrototypeIncludes,
109
StringPrototypeStartsWith,
1110
SymbolToStringTag,
@@ -68,6 +67,7 @@ const {
6867
getUsagesMask,
6968
getUsagesFromMask,
7069
hasUsage,
70+
toUsagesSet,
7171
} = require('internal/crypto/util');
7272

7373
const {
@@ -1306,7 +1306,7 @@ function importGenericSecretKey(
13061306
extractable,
13071307
keyUsages,
13081308
) {
1309-
const usagesSet = new SafeSet(keyUsages);
1309+
const usagesSet = toUsagesSet(keyUsages);
13101310
const { name } = algorithm;
13111311
if (extractable)
13121312
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');

lib/internal/crypto/ml_dsa.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const {
4-
SafeSet,
54
StringPrototypeToLowerCase,
65
TypedArrayPrototypeGetBuffer,
76
TypedArrayPrototypeGetByteLength,
@@ -28,6 +27,8 @@ const {
2827
const {
2928
getUsagesMask,
3029
jobPromise,
30+
toUsagesSet,
31+
getBufferSourceByteLength,
3132
} = require('internal/crypto/util');
3233

3334
const {
@@ -122,7 +123,7 @@ function mlDsaImportKey(
122123

123124
const { name } = algorithm;
124125
let handle;
125-
const usagesSet = new SafeSet(usages);
126+
const usagesSet = toUsagesSet(usages);
126127
switch (format) {
127128
case 'KeyObjectHandle':
128129
verifyAcceptableKeyUse(
@@ -147,7 +148,7 @@ function mlDsaImportKey(
147148
'ML-DSA-65': 4060,
148149
'ML-DSA-87': 4924,
149150
};
150-
if (keyData.byteLength === privOnlyLengths[name]) {
151+
if (getBufferSourceByteLength(keyData) === privOnlyLengths[name]) {
151152
throw lazyDOMException(
152153
'Importing an ML-DSA PKCS#8 key without a seed is not supported',
153154
'NotSupportedError');

lib/internal/crypto/ml_kem.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const {
4-
SafeSet,
54
StringPrototypeToLowerCase,
65
TypedArrayPrototypeGetBuffer,
76
TypedArrayPrototypeGetByteLength,
@@ -27,6 +26,8 @@ const {
2726
const {
2827
getUsagesMask,
2928
jobPromise,
29+
toUsagesSet,
30+
getBufferSourceByteLength,
3031
} = require('internal/crypto/util');
3132

3233
const {
@@ -123,7 +124,7 @@ function mlKemImportKey(
123124

124125
const { name } = algorithm;
125126
let handle;
126-
const usagesSet = new SafeSet(usages);
127+
const usagesSet = toUsagesSet(usages);
127128
switch (format) {
128129
case 'KeyObjectHandle':
129130
verifyAcceptableKeyUse(
@@ -148,7 +149,7 @@ function mlKemImportKey(
148149
'ML-KEM-768': 2428,
149150
'ML-KEM-1024': 3196,
150151
};
151-
if (keyData.byteLength === privOnlyLengths[name]) {
152+
if (getBufferSourceByteLength(keyData) === privOnlyLengths[name]) {
152153
throw lazyDOMException(
153154
'Importing an ML-KEM PKCS#8 key without a seed is not supported',
154155
'NotSupportedError');

lib/internal/crypto/rsa.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const {
44
MathCeil,
5-
SafeSet,
65
TypedArrayPrototypeGetBuffer,
76
Uint8Array,
87
} = primordials;
@@ -35,6 +34,7 @@ const {
3534
jobPromise,
3635
normalizeHashName,
3736
validateMaxBufferLength,
37+
toUsagesSet,
3838
} = require('internal/crypto/util');
3939

4040
const {
@@ -174,7 +174,7 @@ function rsaImportKey(
174174
extractable,
175175
usages) {
176176
const allowedUsages = kUsages[algorithm.name];
177-
const usagesSet = new SafeSet(usages);
177+
const usagesSet = toUsagesSet(usages);
178178
let handle;
179179
switch (format) {
180180
case 'KeyObjectHandle':
@@ -234,7 +234,7 @@ function rsaImportKey(
234234
const {
235235
modulusLength,
236236
publicExponent,
237-
} = handle.keyDetail({});
237+
} = handle.keyDetail({ __proto__: null });
238238

239239
return new InternalCryptoKey(handle, {
240240
name: algorithm.name,

0 commit comments

Comments
 (0)