GeezerVison is showing github.com — crypto: harden WebCrypto against prototype pollution · nodejs/node@ae61cd6
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/diffiehellman.js +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 5 | 5 | FunctionPrototypeCall, | 6 | 6 | MathCeil, | 7 | 7 | ObjectDefineProperty, | 8 | | - PromisePrototypeThen, | 9 | 8 | SafeSet, | 10 | 9 | TypedArrayPrototypeGetBuffer, | 11 | 10 | Uint8Array, | @@ -65,6 +64,7 @@ const { | 65 | 64 | const { | 66 | 65 | getArrayBufferOrView, | 67 | 66 | jobPromise, | | 67 | + jobPromiseThen, | 68 | 68 | toBuf, | 69 | 69 | kHandle, | 70 | 70 | } = require('internal/crypto/util'); | @@ -405,7 +405,7 @@ function ecdhDeriveBits(algorithm, baseKey, length) { | 405 | 405 | if (length === null) | 406 | 406 | return bits; | 407 | 407 |
| 408 | | - return PromisePrototypeThen(bits, (bits) => { | | 408 | + return jobPromiseThen(bits, (bits) => { | 409 | 409 | // If the length is not a multiple of 8 the nearest ceiled | 410 | 410 | // multiple of 8 is sliced. | 411 | 411 | const sliceLength = MathCeil(length / 8); | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/crypto/util.js +70Lines changed: 70 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,7 +14,9 @@ const { | 14 | 14 | ObjectEntries, | 15 | 15 | ObjectKeys, | 16 | 16 | ObjectPrototypeHasOwnProperty, | | 17 | + PromisePrototypeThen, | 17 | 18 | PromiseReject, | | 19 | + PromiseWithResolvers, | 18 | 20 | SafeSet, | 19 | 21 | StringPrototypeToUpperCase, | 20 | 22 | Symbol, | @@ -76,6 +78,7 @@ const { | 76 | 78 | emitExperimentalWarning, | 77 | 79 | filterDuplicateStrings, | 78 | 80 | lazyDOMException, | | 81 | + setOwnProperty, | 79 | 82 | } = require('internal/util'); | 80 | 83 |
| 81 | 84 | const { | @@ -89,6 +92,7 @@ const { | 89 | 92 | isDataView, | 90 | 93 | isArrayBufferView, | 91 | 94 | isAnyArrayBuffer, | | 95 | + isPromise, | 92 | 96 | } = require('internal/util/types'); | 93 | 97 |
| 94 | 98 | const kHandle = Symbol('kHandle'); | @@ -669,6 +673,69 @@ function jobPromise(getJob) { | 669 | 673 | } | 670 | 674 | } | 671 | 675 |
| | 676 | +// Temporarily shadow inherited then accessors on WebCrypto result objects. | | 677 | +// Promise resolution reads "then" synchronously for thenable assimilation. | | 678 | +// Returning an own undefined data property keeps that lookup from reaching | | 679 | +// user-mutated prototypes. | | 680 | +function prepareWebCryptoResult(value) { | | 681 | + if ((value === null || typeof value !== 'object') && | | 682 | + typeof value !== 'function') { | | 683 | + return false; | | 684 | + } | | 685 | + if (isPromise(value) || ObjectPrototypeHasOwnProperty(value, 'then')) | | 686 | + return false; | | 687 | + setOwnProperty(value, 'then', undefined); | | 688 | + return true; | | 689 | +} | | 690 | + | | 691 | +// Remove the temporary then property installed by prepareWebCryptoResult(). | | 692 | +function cleanupWebCryptoResult(value) { | | 693 | + delete value.then; | | 694 | +} | | 695 | + | | 696 | +// Resolve a WebCrypto promise while inherited then accessors are shadowed. | | 697 | +function resolveWebCryptoResult(resolve, value) { | | 698 | + const shouldCleanupResult = prepareWebCryptoResult(value); | | 699 | + try { | | 700 | + resolve(value); | | 701 | + } finally { | | 702 | + if (shouldCleanupResult) | | 703 | + cleanupWebCryptoResult(value); | | 704 | + } | | 705 | +} | | 706 | + | | 707 | +// Run a WebCrypto promise reaction and settle the outer promise. | | 708 | +function settleJobPromise(handler, resolve, reject, value, isRejected) { | | 709 | + try { | | 710 | + if (typeof handler === 'function') { | | 711 | + resolveWebCryptoResult(resolve, handler(value)); | | 712 | + } else if (isRejected) { | | 713 | + reject(value); | | 714 | + } else { | | 715 | + resolveWebCryptoResult(resolve, value); | | 716 | + } | | 717 | + } catch (err) { | | 718 | + reject(err); | | 719 | + } | | 720 | +} | | 721 | + | | 722 | +// Promise.prototype.then gets promise.constructor to determine the result | | 723 | +// promise's species. These promises are internal WebCrypto intermediates, so | | 724 | +// make that lookup stay on the promise itself instead of user-mutated state. | | 725 | +function jobPromiseThen(promise, onFulfilled, onRejected) { | | 726 | + const { | | 727 | + promise: resultPromise, | | 728 | + resolve, | | 729 | + reject, | | 730 | + } = PromiseWithResolvers(); | | 731 | + setOwnProperty(promise, 'constructor', undefined); | | 732 | + PromisePrototypeThen( | | 733 | + promise, | | 734 | + (value) => settleJobPromise(onFulfilled, resolve, reject, value, false), | | 735 | + (value) => settleJobPromise(onRejected, resolve, reject, value, true)); | | 736 | + return resultPromise; | | 737 | +} | | 738 | + | 672 | 739 | // In WebCrypto, the publicExponent option in RSA is represented as a | 673 | 740 | // WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary | 674 | 741 | // number of leading zero bits. Our conventional APIs for reading | @@ -891,6 +958,9 @@ module.exports = { | 891 | 958 | validateByteSource, | 892 | 959 | validateKeyOps, | 893 | 960 | jobPromise, | | 961 | + jobPromiseThen, | | 962 | + cleanupWebCryptoResult, | | 963 | + prepareWebCryptoResult, | 894 | 964 | validateMaxBufferLength, | 895 | 965 | bigIntArrayToUnsignedBigInt, | 896 | 966 | bigIntArrayToUnsignedInt, | |
|