Compare commits
10 Commits
3818eb1d5d
...
41b5165b76
Author | SHA256 | Date | |
---|---|---|---|
41b5165b76 | |||
14afe8561b | |||
26e986c7a5 | |||
1d51fd3bc7 | |||
0091714e09 | |||
d3dec2361e | |||
9061f7712c | |||
9b56d5b8bf | |||
a10c843bae | |||
3a6764d3d4 |
34
CVE-2022-25881.patch
Normal file
34
CVE-2022-25881.patch
Normal file
@@ -0,0 +1,34 @@
|
||||
Index: node-v14.21.3/deps/npm/node_modules/http-cache-semantics/node4/index.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/npm/node_modules/http-cache-semantics/node4/index.js
|
||||
+++ node-v14.21.3/deps/npm/node_modules/http-cache-semantics/node4/index.js
|
||||
@@ -21,7 +21,7 @@ function parseCacheControl(header) {
|
||||
|
||||
// TODO: When there is more than one value present for a given directive (e.g., two Expires header fields, multiple Cache-Control: max-age directives),
|
||||
// the directive's value is considered invalid. Caches are encouraged to consider responses that have invalid freshness information to be stale
|
||||
- var parts = header.trim().split(/\s*,\s*/); // TODO: lame parsing
|
||||
+ var parts = header.trim().split(/,/); // TODO: lame parsing
|
||||
for (var _iterator = parts, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
|
||||
var _ref;
|
||||
|
||||
@@ -36,11 +36,11 @@ function parseCacheControl(header) {
|
||||
|
||||
var part = _ref;
|
||||
|
||||
- var _part$split = part.split(/\s*=\s*/, 2),
|
||||
+ var _part$split = part.split(/=/, 2),
|
||||
k = _part$split[0],
|
||||
v = _part$split[1];
|
||||
|
||||
- cc[k] = v === undefined ? true : v.replace(/^"|"$/g, ''); // TODO: lame unquoting
|
||||
+ cc[k.trim()] = v === undefined ? true : v.trim().replace(/^"|"$/g, ''); // TODO: lame unquoting
|
||||
}
|
||||
|
||||
return cc;
|
||||
@@ -556,4 +556,4 @@ module.exports = function () {
|
||||
};
|
||||
|
||||
return CachePolicy;
|
||||
-}();
|
||||
\ No newline at end of file
|
||||
+}();
|
64
CVE-2023-30581.patch
Normal file
64
CVE-2023-30581.patch
Normal file
@@ -0,0 +1,64 @@
|
||||
commit a6f4e87bc913ff18c1859b8a350c24f744355e66
|
||||
Author: RafaelGSS <rafael.nunu@hotmail.com>
|
||||
Date: Mon May 29 16:40:15 2023 -0300
|
||||
|
||||
policy: handle mainModule.__proto__ bypass
|
||||
|
||||
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/418
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/416
|
||||
Fixes: https://hackerone.com/bugs?subject=nodejs&report_id=1877919
|
||||
Reviewed-By: Rich Trott <rtrott@gmail.com>
|
||||
CVE-ID: CVE-2023-30581
|
||||
|
||||
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
|
||||
index 93681ea243..97bb6e5b13 100644
|
||||
--- a/lib/internal/modules/cjs/loader.js
|
||||
+++ b/lib/internal/modules/cjs/loader.js
|
||||
@@ -226,6 +226,8 @@ function Module(id = '', parent) {
|
||||
redirects = policy.manifest.getDependencyMapper(moduleURL);
|
||||
// TODO(rafaelgss): remove the necessity of this branch
|
||||
setOwnProperty(this, 'require', makeRequireFunction(this, redirects));
|
||||
+ // eslint-disable-next-line no-proto
|
||||
+ setOwnProperty(this.__proto__, 'require', makeRequireFunction(this, redirects));
|
||||
}
|
||||
this[require_private_symbol] = internalRequire;
|
||||
}
|
||||
@@ -892,7 +894,7 @@ Module._load = function(request, parent, isMain) {
|
||||
const module = cachedModule || new Module(filename, parent);
|
||||
|
||||
if (isMain) {
|
||||
- process.mainModule = module;
|
||||
+ setOwnProperty(process, 'mainModule', module);
|
||||
setOwnProperty(module.require, 'main', process.mainModule);
|
||||
module.id = '.';
|
||||
}
|
||||
diff --git a/test/fixtures/policy-manifest/main-module-proto-bypass.js b/test/fixtures/policy-manifest/main-module-proto-bypass.js
|
||||
new file mode 100644
|
||||
index 0000000000..6111aae140
|
||||
--- /dev/null
|
||||
+++ b/test/fixtures/policy-manifest/main-module-proto-bypass.js
|
||||
@@ -0,0 +1 @@
|
||||
+process.mainModule.__proto__.require("os")
|
||||
diff --git a/test/parallel/test-policy-manifest.js b/test/parallel/test-policy-manifest.js
|
||||
index f8bebdf4cf..5dfadb3631 100644
|
||||
--- a/test/parallel/test-policy-manifest.js
|
||||
+++ b/test/parallel/test-policy-manifest.js
|
||||
@@ -66,3 +66,18 @@ const fixtures = require('../common/fixtures.js');
|
||||
|
||||
assert.strictEqual(result.status, 0);
|
||||
}
|
||||
+
|
||||
+{
|
||||
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
|
||||
+ const mainModuleBypass = fixtures.path('policy-manifest', 'main-module-proto-bypass.js');
|
||||
+ const result = spawnSync(process.execPath, [
|
||||
+ '--experimental-policy',
|
||||
+ policyFilepath,
|
||||
+ mainModuleBypass,
|
||||
+ ]);
|
||||
+
|
||||
+ assert.notStrictEqual(result.status, 0);
|
||||
+ const stderr = result.stderr.toString();
|
||||
+ assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/);
|
||||
+ assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/);
|
||||
+}
|
80
CVE-2023-30588.patch
Normal file
80
CVE-2023-30588.patch
Normal file
@@ -0,0 +1,80 @@
|
||||
commit 5a92ea7a3b6210f04c902e177f9dc673ae866393
|
||||
Author: Tobias Nießen <tniessen@tnie.de>
|
||||
Date: Thu Feb 23 15:13:16 2023 +0000
|
||||
|
||||
crypto: handle cert with invalid SPKI gracefully
|
||||
|
||||
When attempting to convert the SPKI of a X509Certificate to a KeyObject,
|
||||
throw an error if the subjectPublicKey cannot be parsed instead of
|
||||
aborting the process.
|
||||
|
||||
Fixes: https://hackerone.com/bugs?report_id=1884159
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/393/
|
||||
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
|
||||
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
|
||||
Reviewed-By: Robert Nagy <ronagy@icloud.com>
|
||||
CVE-ID: CVE-2023-30588
|
||||
|
||||
diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc
|
||||
index a2f6ed8d8c..5fb9d5d1b3 100644
|
||||
--- a/src/crypto/crypto_x509.cc
|
||||
+++ b/src/crypto/crypto_x509.cc
|
||||
@@ -318,7 +318,11 @@ void X509Certificate::PublicKey(const FunctionCallbackInfo<Value>& args) {
|
||||
X509Certificate* cert;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());
|
||||
|
||||
+ // TODO(tniessen): consider checking X509_get_pubkey() when the
|
||||
+ // X509Certificate object is being created.
|
||||
+ ClearErrorOnReturn clear_error_on_return;
|
||||
EVPKeyPointer pkey(X509_get_pubkey(cert->get()));
|
||||
+ if (!pkey) return ThrowCryptoError(env, ERR_get_error());
|
||||
ManagedEVPPKey epkey(std::move(pkey));
|
||||
std::shared_ptr<KeyObjectData> key_data =
|
||||
KeyObjectData::CreateAsymmetric(kKeyTypePublic, epkey);
|
||||
diff --git a/test/parallel/test-crypto-x509.js b/test/parallel/test-crypto-x509.js
|
||||
index 70aaaea9c3..b7caa2c393 100644
|
||||
--- a/test/parallel/test-crypto-x509.js
|
||||
+++ b/test/parallel/test-crypto-x509.js
|
||||
@@ -317,3 +317,42 @@ oans248kpal88CGqsN2so/wZKxVnpiXlPHMdiNL7hRSUqlHkUi07FrP2Htg8kjI=
|
||||
legacyObject.serialNumber,
|
||||
legacyObjectCheck.serialNumber);
|
||||
}
|
||||
+
|
||||
+{
|
||||
+ // This X.509 Certificate can be parsed by OpenSSL because it contains a
|
||||
+ // structurally sound TBSCertificate structure. However, the SPKI field of the
|
||||
+ // TBSCertificate contains the subjectPublicKey as a BIT STRING, and this bit
|
||||
+ // sequence is not a valid public key. Ensure that X509Certificate.publicKey
|
||||
+ // does not abort in this case.
|
||||
+
|
||||
+ const certPem = `-----BEGIN CERTIFICATE-----
|
||||
+MIIDpDCCAw0CFEc1OZ8g17q+PZnna3iQ/gfoZ7f3MA0GCSqGSIb3DQEBBQUAMIHX
|
||||
+MRMwEQYLKwYBBAGCNzwCAQMTAkdJMR0wGwYDVQQPExRQcml2YXRlIE9yZ2FuaXph
|
||||
+dGlvbjEOMAwGA1UEBRMFOTkxOTExCzAJBgNVBAYTAkdJMRIwEAYDVQQIFAlHaWJy
|
||||
+YWx0YXIxEjAQBgNVBAcUCUdpYnJhbHRhcjEgMB4GA1UEChQXV0hHIChJbnRlcm5h
|
||||
+dGlvbmFsKSBMdGQxHDAaBgNVBAsUE0ludGVyYWN0aXZlIEJldHRpbmcxHDAaBgNV
|
||||
+BAMUE3d3dy53aWxsaWFtaGlsbC5jb20wIhgPMjAxNDAyMDcwMDAwMDBaGA8yMDE1
|
||||
+MDIyMTIzNTk1OVowgbAxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21lMRAwDgYD
|
||||
+VQQHEwdQb21lemlhMRYwFAYDVQQKEw1UZWxlY29taXRhbGlhMRIwEAYDVQQrEwlB
|
||||
+RE0uQVAuUE0xHTAbBgNVBAMTFHd3dy50ZWxlY29taXRhbGlhLml0MTUwMwYJKoZI
|
||||
+hvcNAQkBFiZ2YXNlc2VyY2l6aW9wb3J0YWxpY29AdGVsZWNvbWl0YWxpYS5pdDCB
|
||||
+nzANBgkqhkiG9w0BAQEFAAOBjQA4gYkCgYEA5m/Vf7PevH+inMfUJOc8GeR7WVhM
|
||||
+CQwcMM5k46MSZo7kCk7VZuaq5G2JHGAGnLPaPUkeXlrf5qLpTxXXxHNtz+WrDlFt
|
||||
+boAdnTcqpX3+72uBGOaT6Wi/9YRKuCs5D5/cAxAc3XjHfpRXMoXObj9Vy7mLndfV
|
||||
+/wsnTfU9QVeBkgsCAwEAAaOBkjCBjzAdBgNVHQ4EFgQUfLjAjEiC83A+NupGrx5+
|
||||
+Qe6nhRMwbgYIKwYBBQUHAQwEYjBgoV6gXDBaMFgwVhYJaW1hZ2UvZ2lmMCEwHzAH
|
||||
+BgUrDgMCGgQUS2u5KJYGDLvQUjibKaxLB4shBRgwJhYkaHR0cDovL2xvZ28udmVy
|
||||
+aXNpZ24uY29tL3ZzbG9nbzEuZ2lmMA0GCSqGSIb3DQEBBQUAA4GBALLiAMX0cIMp
|
||||
++V/JgMRhMEUKbrt5lYKfv9dil/f22ezZaFafb070jGMMPVy9O3/PavDOkHtTv3vd
|
||||
+tAt3hIKFD1bJt6c6WtMH2Su3syosWxmdmGk5ihslB00lvLpfj/wed8i3bkcB1doq
|
||||
+UcXd/5qu2GhokrKU2cPttU+XAN2Om6a0
|
||||
+-----END CERTIFICATE-----`;
|
||||
+
|
||||
+ const cert = new X509Certificate(certPem);
|
||||
+ assert.throws(() => cert.publicKey, {
|
||||
+ message: common.hasOpenSSL3 ? /decode error/ : /wrong tag/,
|
||||
+ name: 'Error'
|
||||
+ });
|
||||
+
|
||||
+ assert.strictEqual(cert.checkIssued(cert), false);
|
||||
+}
|
2700
CVE-2023-30589.patch
Normal file
2700
CVE-2023-30589.patch
Normal file
File diff suppressed because it is too large
Load Diff
165
CVE-2023-30590.patch
Normal file
165
CVE-2023-30590.patch
Normal file
@@ -0,0 +1,165 @@
|
||||
commit 1a5c9284ebce5cd71cf7a3c29759a748c373ac85
|
||||
Author: Tobias Nießen <tobias.niessen@tuwien.ac.at>
|
||||
Date: Mon Jun 12 19:44:48 2023 +0200
|
||||
|
||||
doc,test: clarify behavior of DH generateKeys
|
||||
|
||||
The DiffieHellman class is an old and thin wrapper around certain
|
||||
OpenSSL functions, many of which are deprecated in OpenSSL 3.0. Because
|
||||
the Node.js API mirrors the OpenSSL API, it adopts some of its
|
||||
peculiarities, but the Node.js documentation does not properly reflect
|
||||
these. Most importantly, despite the documentation saying otherwise,
|
||||
diffieHellman.generateKeys() does not generate a new private key when
|
||||
one has already been set or generated. Based on the documentation alone,
|
||||
users may be led to misuse the API in a way that results in key reuse,
|
||||
which can have drastic negative consequences for subsequent operations
|
||||
that consume the shared secret.
|
||||
|
||||
These design issues in this old API have been around for many years, and
|
||||
we are not currently aware of any misuse in the ecosystem that falls
|
||||
into the above scenario. Changing the behavior of the API would be a
|
||||
significant breaking change and is thus not appropriate for a security
|
||||
release (nor is it a goal.) The reported issue is treated as CWE-1068
|
||||
(after a vast amount of uncertainty whether to treat it as a
|
||||
vulnerability at all), therefore, this change only updates the
|
||||
documentation to match the actual behavior. Tests are also added that
|
||||
demonstrate this particular oddity.
|
||||
|
||||
Newer APIs exist that can be used for some, but not all, Diffie-Hellman
|
||||
operations (e.g., crypto.diffieHellman() that was added in 2020). We
|
||||
should keep modernizing crypto APIs, but that is a non-goal for this
|
||||
security release.
|
||||
|
||||
The ECDH class mirrors the DiffieHellman class in many ways, but it does
|
||||
not appear to be affected by this particular peculiarity. In particular,
|
||||
ecdh.generateKeys() does appear to always generate a new private key.
|
||||
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/426
|
||||
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
|
||||
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
|
||||
CVE-ID: CVE-2023-30590
|
||||
|
||||
Index: node-v14.21.3/doc/api/crypto.md
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/doc/api/crypto.md
|
||||
+++ node-v14.21.3/doc/api/crypto.md
|
||||
@@ -634,12 +634,17 @@ added: v0.5.0
|
||||
* `encoding` {string} The [encoding][] of the return value.
|
||||
* Returns: {Buffer | string}
|
||||
|
||||
-Generates private and public Diffie-Hellman key values, and returns
|
||||
+Generates private and public Diffie-Hellman key values unless they have been
|
||||
+generated or computed already, and returns
|
||||
the public key in the specified `encoding`. This key should be
|
||||
transferred to the other party.
|
||||
If `encoding` is provided a string is returned; otherwise a
|
||||
[`Buffer`][] is returned.
|
||||
|
||||
+This function is a thin wrapper around [`DH_generate_key()`][]. In particular,
|
||||
+once a private key has been generated or set, calling this function only updates
|
||||
+the public key but does not generate a new private key.
|
||||
+
|
||||
### `diffieHellman.getGenerator([encoding])`
|
||||
<!-- YAML
|
||||
added: v0.5.0
|
||||
@@ -701,6 +706,10 @@ Sets the Diffie-Hellman private key. If
|
||||
to be a string. If no `encoding` is provided, `privateKey` is expected
|
||||
to be a [`Buffer`][], `TypedArray`, or `DataView`.
|
||||
|
||||
+This function does not automatically compute the associated public key. Either
|
||||
+[`diffieHellman.setPublicKey()`][] or [`diffieHellman.generateKeys()`][] can be
|
||||
+used to manually provide the public key or to automatically derive it.
|
||||
+
|
||||
### `diffieHellman.setPublicKey(publicKey[, encoding])`
|
||||
<!-- YAML
|
||||
added: v0.5.0
|
||||
@@ -3606,6 +3615,7 @@ See the [list of SSL OP Flags][] for det
|
||||
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
|
||||
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
|
||||
[`Buffer`]: buffer.md
|
||||
+[`DH_generate_key()`]: https://www.openssl.org/docs/man3.0/man3/DH_generate_key.html
|
||||
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
|
||||
[`KeyObject`]: #crypto_class_keyobject
|
||||
[`Sign`]: #crypto_class_sign
|
||||
@@ -3638,6 +3648,7 @@ See the [list of SSL OP Flags][] for det
|
||||
[`crypto.scrypt()`]: #crypto_crypto_scrypt_password_salt_keylen_options_callback
|
||||
[`decipher.final()`]: #crypto_decipher_final_outputencoding
|
||||
[`decipher.update()`]: #crypto_decipher_update_data_inputencoding_outputencoding
|
||||
+[`diffieHellman.generateKeys()`]: #diffiehellmangeneratekeysencoding
|
||||
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_publickey_encoding
|
||||
[`ecdh.generateKeys()`]: #crypto_ecdh_generatekeys_encoding_format
|
||||
[`ecdh.setPrivateKey()`]: #crypto_ecdh_setprivatekey_privatekey_encoding
|
||||
Index: node-v14.21.3/test/parallel/test-crypto-dh.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/test/parallel/test-crypto-dh.js
|
||||
+++ node-v14.21.3/test/parallel/test-crypto-dh.js
|
||||
@@ -8,7 +8,8 @@ const crypto = require('crypto');
|
||||
|
||||
// Test Diffie-Hellman with two parties sharing a secret,
|
||||
// using various encodings as we go along
|
||||
-const dh1 = crypto.createDiffieHellman(common.hasFipsCrypto ? 1024 : 256);
|
||||
+const size = common.hasFipsCrypto ? 1024 : 256;
|
||||
+const dh1 = crypto.createDiffieHellman(size);
|
||||
const p1 = dh1.getPrime('buffer');
|
||||
const dh2 = crypto.createDiffieHellman(p1, 'buffer');
|
||||
let key1 = dh1.generateKeys();
|
||||
@@ -477,3 +478,59 @@ assert.throws(
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
}
|
||||
);
|
||||
+
|
||||
+{
|
||||
+ function unlessInvalidState(f) {
|
||||
+ try {
|
||||
+ return f();
|
||||
+ } catch (err) {
|
||||
+ // all errors thrown here are invalid state about missing keys
|
||||
+// if (err.code !== 'ERR_CRYPTO_INVALID_STATE') {
|
||||
+//console.log(err);
|
||||
+// throw err;
|
||||
+// }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ function testGenerateKeysChangesKeys(setup, expected) {
|
||||
+ const dh = crypto.createDiffieHellman(size);
|
||||
+ setup(dh);
|
||||
+ const firstPublicKey = unlessInvalidState(() => dh.getPublicKey());
|
||||
+ const firstPrivateKey = unlessInvalidState(() => dh.getPrivateKey());
|
||||
+ dh.generateKeys();
|
||||
+ const secondPublicKey = dh.getPublicKey();
|
||||
+ const secondPrivateKey = dh.getPrivateKey();
|
||||
+ function changed(shouldChange, first, second) {
|
||||
+ if (shouldChange) {
|
||||
+ assert.notDeepStrictEqual(first, second);
|
||||
+ } else {
|
||||
+ assert.deepStrictEqual(first, second);
|
||||
+ }
|
||||
+ }
|
||||
+ changed(expected.includes('public'), firstPublicKey, secondPublicKey);
|
||||
+ changed(expected.includes('private'), firstPrivateKey, secondPrivateKey);
|
||||
+ }
|
||||
+
|
||||
+ // Both the private and the public key are missing: generateKeys() generates both.
|
||||
+ testGenerateKeysChangesKeys(() => {
|
||||
+ // No setup.
|
||||
+ }, ['public', 'private']);
|
||||
+
|
||||
+ // Neither key is missing: generateKeys() does nothing.
|
||||
+ testGenerateKeysChangesKeys((dh) => {
|
||||
+ dh.generateKeys();
|
||||
+ }, []);
|
||||
+
|
||||
+ // Only the public key is missing: generateKeys() generates only the public key.
|
||||
+ testGenerateKeysChangesKeys((dh) => {
|
||||
+ dh.setPrivateKey(Buffer.from('01020304', 'hex'));
|
||||
+ }, ['public']);
|
||||
+
|
||||
+ // The public key is outdated: generateKeys() generates only the public key.
|
||||
+ testGenerateKeysChangesKeys((dh) => {
|
||||
+ const oldPublicKey = dh.generateKeys();
|
||||
+ dh.setPrivateKey(Buffer.from('01020304', 'hex'));
|
||||
+ assert.deepStrictEqual(dh.getPublicKey(), oldPublicKey);
|
||||
+ }, ['public']);
|
||||
+}
|
||||
+
|
187
CVE-2023-32002.patch
Normal file
187
CVE-2023-32002.patch
Normal file
@@ -0,0 +1,187 @@
|
||||
commit d8ccfe9ad4dce9da900cff9dd2b934dfa3600b8b
|
||||
Author: RafaelGSS <rafael.nunu@hotmail.com>
|
||||
Date: Mon May 29 19:45:33 2023 -0300
|
||||
|
||||
policy: handle Module.constructor and main.extensions bypass
|
||||
|
||||
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/445
|
||||
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1960870
|
||||
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=2043807
|
||||
CVE-ID: CVE-2023-32002,CVE-2023-32006
|
||||
|
||||
Index: node-v14.21.3/lib/internal/modules/cjs/helpers.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/internal/modules/cjs/helpers.js
|
||||
+++ node-v14.21.3/lib/internal/modules/cjs/helpers.js
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
StringPrototypeStartsWith,
|
||||
} = primordials;
|
||||
const {
|
||||
+ ERR_INVALID_ARG_TYPE,
|
||||
ERR_MANIFEST_DEPENDENCY_MISSING,
|
||||
ERR_UNKNOWN_BUILTIN_MODULE
|
||||
} = require('internal/errors').codes;
|
||||
@@ -55,12 +56,22 @@ function loadNativeModule(filename, requ
|
||||
}
|
||||
}
|
||||
|
||||
+let $Module = null;
|
||||
+function lazyModule() {
|
||||
+ $Module = $Module || require('internal/modules/cjs/loader').Module;
|
||||
+ return $Module;
|
||||
+}
|
||||
+
|
||||
// Invoke with makeRequireFunction(module) where |module| is the Module object
|
||||
// to use as the context for the require() function.
|
||||
// Use redirects to set up a mapping from a policy and restrict dependencies
|
||||
const urlToFileCache = new SafeMap();
|
||||
function makeRequireFunction(mod, redirects) {
|
||||
- const Module = mod.constructor;
|
||||
+ // lazy due to cycle
|
||||
+ const Module = lazyModule();
|
||||
+ if (mod instanceof Module !== true) {
|
||||
+ throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod);
|
||||
+ }
|
||||
|
||||
let require;
|
||||
if (redirects) {
|
||||
Index: node-v14.21.3/lib/internal/modules/cjs/loader.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/internal/modules/cjs/loader.js
|
||||
+++ node-v14.21.3/lib/internal/modules/cjs/loader.js
|
||||
@@ -147,8 +147,8 @@ const isWindows = process.platform === '
|
||||
const relativeResolveCache = ObjectCreate(null);
|
||||
|
||||
let requireDepth = 0;
|
||||
-let statCache = null;
|
||||
let isPreloading = false;
|
||||
+let statCache = null;
|
||||
|
||||
function internalRequire(module, id) {
|
||||
validateString(id, 'id');
|
||||
@@ -1284,5 +1284,14 @@ Module.syncBuiltinESMExports = function
|
||||
}
|
||||
};
|
||||
|
||||
+ObjectDefineProperty(Module.prototype, 'constructor', {
|
||||
+ __proto__: null,
|
||||
+ get: function() {
|
||||
+ return policy ? undefined : Module;
|
||||
+ },
|
||||
+ configurable: false,
|
||||
+ enumerable: false,
|
||||
+});
|
||||
+
|
||||
// Backwards compatibility
|
||||
Module.Module = Module;
|
||||
Index: node-v14.21.3/test/fixtures/policy-manifest/createRequire-bypass.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy-manifest/createRequire-bypass.js
|
||||
@@ -0,0 +1,2 @@
|
||||
+const os = module.constructor.createRequire('file:///os-access-module.js')('os')
|
||||
+os.cpus()
|
||||
\ No newline at end of file
|
||||
Index: node-v14.21.3/test/fixtures/policy-manifest/main-constructor-bypass.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy-manifest/main-constructor-bypass.js
|
||||
@@ -0,0 +1,2 @@
|
||||
+const m = new require.main.constructor();
|
||||
+m.require('./invalid-module')
|
||||
Index: node-v14.21.3/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js
|
||||
@@ -0,0 +1,2 @@
|
||||
+const m = new require.main.constructor();
|
||||
+require.extensions['.js'](m, './invalid-module')
|
||||
Index: node-v14.21.3/test/fixtures/policy-manifest/manifest-impersonate.json
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy-manifest/manifest-impersonate.json
|
||||
@@ -0,0 +1,13 @@
|
||||
+{
|
||||
+ "resources": {
|
||||
+ "./createRequire-bypass.js": {
|
||||
+ "integrity": true
|
||||
+ },
|
||||
+ "/os-access-module.js": {
|
||||
+ "integrity": true,
|
||||
+ "dependencies": {
|
||||
+ "os": true
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
\ No newline at end of file
|
||||
Index: node-v14.21.3/test/fixtures/policy-manifest/module-constructor-bypass.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy-manifest/module-constructor-bypass.js
|
||||
@@ -0,0 +1 @@
|
||||
+module.constructor._load('node:child_process');
|
||||
Index: node-v14.21.3/test/parallel/test-policy-manifest.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/test/parallel/test-policy-manifest.js
|
||||
+++ node-v14.21.3/test/parallel/test-policy-manifest.js
|
||||
@@ -76,3 +76,58 @@ const fixtures = require('../common/fixt
|
||||
assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/);
|
||||
assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/);
|
||||
}
|
||||
+
|
||||
+{
|
||||
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
|
||||
+ const mainModuleBypass = fixtures.path('policy-manifest', 'module-constructor-bypass.js');
|
||||
+ const result = spawnSync(process.execPath, [
|
||||
+ '--experimental-policy',
|
||||
+ policyFilepath,
|
||||
+ mainModuleBypass,
|
||||
+ ]);
|
||||
+ assert.notStrictEqual(result.status, 0);
|
||||
+ const stderr = result.stderr.toString();
|
||||
+ assert.match(stderr, /TypeError/);
|
||||
+}
|
||||
+
|
||||
+{
|
||||
+ const policyFilepath = fixtures.path('policy-manifest', 'manifest-impersonate.json');
|
||||
+ const createRequireBypass = fixtures.path('policy-manifest', 'createRequire-bypass.js');
|
||||
+ const result = spawnSync(process.execPath, [
|
||||
+ '--experimental-policy',
|
||||
+ policyFilepath,
|
||||
+ createRequireBypass,
|
||||
+ ]);
|
||||
+
|
||||
+ assert.notStrictEqual(result.status, 0);
|
||||
+ const stderr = result.stderr.toString();
|
||||
+ assert.match(stderr, /TypeError/);
|
||||
+}
|
||||
+
|
||||
+{
|
||||
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
|
||||
+ const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-bypass.js');
|
||||
+ const result = spawnSync(process.execPath, [
|
||||
+ '--experimental-policy',
|
||||
+ policyFilepath,
|
||||
+ mainModuleBypass,
|
||||
+ ]);
|
||||
+
|
||||
+ assert.notStrictEqual(result.status, 0);
|
||||
+ const stderr = result.stderr.toString();
|
||||
+ assert.match(stderr, /TypeError/);
|
||||
+}
|
||||
+
|
||||
+{
|
||||
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
|
||||
+ const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-extensions-bypass.js');
|
||||
+ const result = spawnSync(process.execPath, [
|
||||
+ '--experimental-policy',
|
||||
+ policyFilepath,
|
||||
+ mainModuleBypass,
|
||||
+ ]);
|
||||
+
|
||||
+ assert.notStrictEqual(result.status, 0);
|
||||
+ const stderr = result.stderr.toString();
|
||||
+ assert.match(stderr, /TypeError/);
|
||||
+}
|
160
CVE-2023-32559.patch
Normal file
160
CVE-2023-32559.patch
Normal file
@@ -0,0 +1,160 @@
|
||||
commit 242aaa0caaf0c15109067b598d58fdeae603c5fd
|
||||
Author: Tobias Nießen <tobias.niessen@tuwien.ac.at>
|
||||
Date: Sun Apr 16 22:26:47 2023 +0200
|
||||
|
||||
policy: disable process.binding() when enabled
|
||||
|
||||
process.binding() can be used to trivially bypass restrictions imposed
|
||||
through a policy. Since the function is deprecated already, simply
|
||||
replace it with a stub when a policy is being enabled.
|
||||
|
||||
Fixes: https://hackerone.com/bugs?report_id=1946470
|
||||
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
|
||||
CVE-ID: CVE-2023-32559
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/459
|
||||
|
||||
Index: node-v14.21.3/doc/api/deprecations.md
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/doc/api/deprecations.md
|
||||
+++ node-v14.21.3/doc/api/deprecations.md
|
||||
@@ -2072,6 +2072,9 @@ Type: Documentation-only (supports [`--p
|
||||
|
||||
`process.binding()` is for use by Node.js internal code only.
|
||||
|
||||
+While `process.binding()` has not reached End-of-Life status in general, it is
|
||||
+unavailable when [policies][] are enabled.
|
||||
+
|
||||
### DEP0112: `dgram` private APIs
|
||||
<!-- YAML
|
||||
changes:
|
||||
@@ -2794,4 +2797,5 @@ an explicit [`"exports"` or `"main"` ent
|
||||
[from_arraybuffer]: buffer.md#buffer_static_method_buffer_from_arraybuffer_byteoffset_length
|
||||
[from_string_encoding]: buffer.md#buffer_static_method_buffer_from_string_encoding
|
||||
[legacy `urlObject`]: url.md#url_legacy_urlobject
|
||||
+[policies]: permissions.md#policies
|
||||
[static methods of `crypto.Certificate()`]: crypto.md#crypto_class_certificate
|
||||
Index: node-v14.21.3/doc/api/errors.md
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/doc/api/errors.md
|
||||
+++ node-v14.21.3/doc/api/errors.md
|
||||
@@ -624,6 +624,14 @@ APIs _not_ using `AbortSignal`s typicall
|
||||
This code does not use the regular `ERR_*` convention Node.js errors use in
|
||||
order to be compatible with the web platform's `AbortError`.
|
||||
|
||||
+<a id="ERR_ACCESS_DENIED"></a>
|
||||
+
|
||||
+### `ERR_ACCESS_DENIED`
|
||||
+
|
||||
+A special type of error that is triggered whenever Node.js tries to get access
|
||||
+to a resource restricted by the [policy][] manifest.
|
||||
+For example, `process.binding`.
|
||||
+
|
||||
<a id="ERR_AMBIGUOUS_ARGUMENT"></a>
|
||||
### `ERR_AMBIGUOUS_ARGUMENT`
|
||||
|
||||
Index: node-v14.21.3/lib/internal/errors.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/internal/errors.js
|
||||
+++ node-v14.21.3/lib/internal/errors.js
|
||||
@@ -788,6 +788,10 @@ module.exports = {
|
||||
// Note: Please try to keep these in alphabetical order
|
||||
//
|
||||
// Note: Node.js specific errors must begin with the prefix ERR_
|
||||
+
|
||||
+E('ERR_ACCESS_DENIED',
|
||||
+ 'Access to this API has been restricted. Permission: %s',
|
||||
+ Error);
|
||||
E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError);
|
||||
E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError);
|
||||
E('ERR_ASSERTION', '%s', Error);
|
||||
Index: node-v14.21.3/lib/internal/process/policy.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/internal/process/policy.js
|
||||
+++ node-v14.21.3/lib/internal/process/policy.js
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
+ ERR_ACCESS_DENIED,
|
||||
ERR_MANIFEST_TDZ,
|
||||
} = require('internal/errors').codes;
|
||||
const { Manifest } = require('internal/policy/manifest');
|
||||
@@ -32,6 +33,15 @@ module.exports = ObjectFreeze({
|
||||
return o;
|
||||
});
|
||||
manifest = new Manifest(json, url);
|
||||
+
|
||||
+ // process.binding() is deprecated (DEP0111) and trivially allows bypassing
|
||||
+ // policies, so if policies are enabled, make this API unavailable.
|
||||
+ process.binding = function binding(_module) {
|
||||
+ throw new ERR_ACCESS_DENIED('process.binding');
|
||||
+ };
|
||||
+ process._linkedBinding = function _linkedBinding(_module) {
|
||||
+ throw new ERR_ACCESS_DENIED('process._linkedBinding');
|
||||
+ };
|
||||
},
|
||||
|
||||
get manifest() {
|
||||
Index: node-v14.21.3/test/fixtures/policy/process-binding/app.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/process-binding/app.js
|
||||
@@ -0,0 +1,10 @@
|
||||
+'use strict';
|
||||
+
|
||||
+const assert = require('assert');
|
||||
+
|
||||
+assert.throws(() => { process.binding(); }, {
|
||||
+ code: 'ERR_ACCESS_DENIED'
|
||||
+});
|
||||
+assert.throws(() => { process._linkedBinding(); }, {
|
||||
+ code: 'ERR_ACCESS_DENIED'
|
||||
+});
|
||||
Index: node-v14.21.3/test/fixtures/policy/process-binding/policy.json
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/process-binding/policy.json
|
||||
@@ -0,0 +1,10 @@
|
||||
+{
|
||||
+ "resources": {
|
||||
+ "./app.js": {
|
||||
+ "integrity": true,
|
||||
+ "dependencies": {
|
||||
+ "assert": true
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
Index: node-v14.21.3/test/parallel/test-policy-process-binding.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/parallel/test-policy-process-binding.js
|
||||
@@ -0,0 +1,28 @@
|
||||
+'use strict';
|
||||
+
|
||||
+const common = require('../common');
|
||||
+common.requireNoPackageJSONAbove();
|
||||
+
|
||||
+if (!common.hasCrypto)
|
||||
+ common.skip('missing crypto');
|
||||
+
|
||||
+const fixtures = require('../common/fixtures');
|
||||
+
|
||||
+const assert = require('node:assert');
|
||||
+const { spawnSync } = require('node:child_process');
|
||||
+
|
||||
+const dep = fixtures.path('policy', 'process-binding', 'app.js');
|
||||
+const depPolicy = fixtures.path(
|
||||
+ 'policy',
|
||||
+ 'process-binding',
|
||||
+ 'policy.json');
|
||||
+const { status } = spawnSync(
|
||||
+ process.execPath,
|
||||
+ [
|
||||
+ '--experimental-policy', depPolicy, dep,
|
||||
+ ],
|
||||
+ {
|
||||
+ stdio: 'inherit'
|
||||
+ },
|
||||
+);
|
||||
+assert.strictEqual(status, 0);
|
204
CVE-2023-38552.patch
Normal file
204
CVE-2023-38552.patch
Normal file
@@ -0,0 +1,204 @@
|
||||
commit 1c538938ccadfd35fbc699d8e85102736cd5945c
|
||||
Author: Tobias Nießen <tniessen@tnie.de>
|
||||
Date: Sun Aug 6 12:56:02 2023 +0000
|
||||
|
||||
policy: use tamper-proof integrity check function
|
||||
|
||||
Using the JavaScript Hash class is unsafe because its internals can be
|
||||
tampered with. In particular, an application can cause
|
||||
Hash.prototype.digest() to return arbitrary values, thus allowing to
|
||||
circumvent the integrity verification that policies are supposed to
|
||||
guarantee.
|
||||
|
||||
Add and use a new C++ binding internalVerifyIntegrity() that (hopefully)
|
||||
cannot be tampered with from JavaScript.
|
||||
|
||||
PR-URL: https://github.com/nodejs-private/node-private/pull/462
|
||||
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/493
|
||||
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
|
||||
CVE-ID: CVE-2023-38552
|
||||
|
||||
Index: node-v14.21.3/lib/internal/policy/manifest.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/internal/policy/manifest.js
|
||||
+++ node-v14.21.3/lib/internal/policy/manifest.js
|
||||
@@ -16,7 +16,6 @@ const {
|
||||
StringPrototypeEndsWith,
|
||||
StringPrototypeReplace,
|
||||
Symbol,
|
||||
- uncurryThis,
|
||||
} = primordials;
|
||||
const {
|
||||
ERR_MANIFEST_ASSERT_INTEGRITY,
|
||||
@@ -28,13 +27,8 @@ let debug = require('internal/util/debug
|
||||
debug = fn;
|
||||
});
|
||||
const SRI = require('internal/policy/sri');
|
||||
-const crypto = require('crypto');
|
||||
-const { Buffer } = require('buffer');
|
||||
const { URL } = require('internal/url');
|
||||
-const { createHash, timingSafeEqual } = crypto;
|
||||
-const HashUpdate = uncurryThis(crypto.Hash.prototype.update);
|
||||
-const HashDigest = uncurryThis(crypto.Hash.prototype.digest);
|
||||
-const BufferToString = uncurryThis(Buffer.prototype.toString);
|
||||
+const { internalVerifyIntegrity } = internalBinding('crypto');
|
||||
const kRelativeURLStringPattern = /^\.{0,2}\//;
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const shouldAbortOnUncaughtException = getOptionValue(
|
||||
@@ -553,16 +547,13 @@ class Manifest {
|
||||
// Avoid clobbered Symbol.iterator
|
||||
for (let i = 0; i < integrityEntries.length; i++) {
|
||||
const { algorithm, value: expected } = integrityEntries[i];
|
||||
- const hash = createHash(algorithm);
|
||||
// TODO(tniessen): the content should not be passed as a string in the
|
||||
// first place, see https://github.com/nodejs/node/issues/39707
|
||||
- HashUpdate(hash, content, 'utf8');
|
||||
- const digest = HashDigest(hash, 'buffer');
|
||||
- if (digest.length === expected.length &&
|
||||
- timingSafeEqual(digest, expected)) {
|
||||
+ const mismatchedIntegrity = internalVerifyIntegrity(algorithm, content, expected);
|
||||
+ if (mismatchedIntegrity === undefined) {
|
||||
return true;
|
||||
}
|
||||
- realIntegrities.set(algorithm, BufferToString(digest, 'base64'));
|
||||
+ realIntegrities.set(algorithm, mismatchedIntegrity);
|
||||
}
|
||||
}
|
||||
|
||||
Index: node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/.gitattributes
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/.gitattributes
|
||||
@@ -0,0 +1 @@
|
||||
+*.js text eol=lf
|
||||
Index: node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/main.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/main.js
|
||||
@@ -0,0 +1,8 @@
|
||||
+const h = require('crypto').createHash('sha384');
|
||||
+const fakeDigest = h.digest();
|
||||
+
|
||||
+const kHandle = Object.getOwnPropertySymbols(h)
|
||||
+ .find((s) => s.description === 'kHandle');
|
||||
+h[kHandle].constructor.prototype.digest = () => fakeDigest;
|
||||
+
|
||||
+require('./protected.js');
|
||||
Index: node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/policy.json
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/policy.json
|
||||
@@ -0,0 +1,15 @@
|
||||
+{
|
||||
+ "resources": {
|
||||
+ "./main.js": {
|
||||
+ "integrity": true,
|
||||
+ "dependencies": {
|
||||
+ "./protected.js": true,
|
||||
+ "crypto": true
|
||||
+ }
|
||||
+ },
|
||||
+ "./protected.js": {
|
||||
+ "integrity": "sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb",
|
||||
+ "dependencies": true
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
Index: node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/protected.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/fixtures/policy/crypto-hash-tampering/protected.js
|
||||
@@ -0,0 +1 @@
|
||||
+console.log(require('fs').readFileSync('/etc/passwd').length);
|
||||
Index: node-v14.21.3/test/parallel/test-policy-crypto-hash-tampering.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/parallel/test-policy-crypto-hash-tampering.js
|
||||
@@ -0,0 +1,21 @@
|
||||
+'use strict';
|
||||
+
|
||||
+const common = require('../common');
|
||||
+if (!common.hasCrypto)
|
||||
+ common.skip('missing crypto');
|
||||
+common.requireNoPackageJSONAbove();
|
||||
+
|
||||
+const fixtures = require('../common/fixtures');
|
||||
+
|
||||
+const assert = require('assert');
|
||||
+const { spawnSync } = require('child_process');
|
||||
+
|
||||
+const mainPath = fixtures.path('policy', 'crypto-hash-tampering', 'main.js');
|
||||
+const policyPath = fixtures.path(
|
||||
+ 'policy',
|
||||
+ 'crypto-hash-tampering',
|
||||
+ 'policy.json');
|
||||
+const { status, stderr } =
|
||||
+ spawnSync(process.execPath, ['--experimental-policy', policyPath, mainPath], { encoding: 'utf8' });
|
||||
+assert.strictEqual(status, 1);
|
||||
+assert(stderr.includes('sha384-Bnp/T8gFNzT9mHj2G/AeuMH8LcAQ4mljw15nxBNl5yaGM7VgbMzDT7O4+dXZTJJn'));
|
||||
Index: node-v14.21.3/src/node_crypto.cc
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/src/node_crypto.cc
|
||||
+++ node-v14.21.3/src/node_crypto.cc
|
||||
@@ -6642,6 +6642,53 @@ void GetCurves(const FunctionCallbackInf
|
||||
}
|
||||
|
||||
|
||||
+void InternalVerifyIntegrity(const FunctionCallbackInfo<Value>& args) {
|
||||
+ Environment* env = Environment::GetCurrent(args);
|
||||
+
|
||||
+ //CHECK_EQ(args.Length(), 3);
|
||||
+
|
||||
+ //CHECK(args[0]->IsString());
|
||||
+ Utf8Value algorithm(env->isolate(), args[0]);
|
||||
+
|
||||
+ //CHECK(args[1]->IsString() || IsAnyByteSource(args[1]));
|
||||
+ ByteSource content = ByteSource::FromStringOrBuffer(env, args[1]);
|
||||
+
|
||||
+ //CHECK(args[2]->IsArrayBufferView());
|
||||
+ ArrayBufferViewContents<unsigned char> expected(args[2]);
|
||||
+ //ArrayBufferOrViewContents<unsigned char> expected(args[2]);
|
||||
+
|
||||
+ const EVP_MD* md_type = EVP_get_digestbyname(*algorithm);
|
||||
+ unsigned char digest[EVP_MAX_MD_SIZE];
|
||||
+ unsigned int digest_size;
|
||||
+ if (md_type == nullptr || EVP_Digest(content.get(),
|
||||
+ content.size(),
|
||||
+ digest,
|
||||
+ &digest_size,
|
||||
+ md_type,
|
||||
+ nullptr) != 1) {
|
||||
+ return ThrowCryptoError(
|
||||
+ env, ERR_get_error(), "Digest method not supported");
|
||||
+ }
|
||||
+
|
||||
+ if (digest_size != expected.length() ||
|
||||
+ CRYPTO_memcmp(digest, expected.data(), digest_size) != 0) {
|
||||
+ Local<Value> error;
|
||||
+ MaybeLocal<Value> rc =
|
||||
+ StringBytes::Encode(env->isolate(),
|
||||
+ reinterpret_cast<const char*>(digest),
|
||||
+ digest_size,
|
||||
+ BASE64,
|
||||
+ &error);
|
||||
+ if (rc.IsEmpty()) {
|
||||
+ CHECK(!error.IsEmpty());
|
||||
+ env->isolate()->ThrowException(error);
|
||||
+ return;
|
||||
+ }
|
||||
+ args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+
|
||||
bool VerifySpkac(const char* data, unsigned int len) {
|
||||
NetscapeSPKIPointer spki(NETSCAPE_SPKI_b64_decode(data, len));
|
||||
if (!spki)
|
||||
@@ -7061,6 +7108,7 @@ void Initialize(Local<Object> target,
|
||||
env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
|
||||
env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
|
||||
env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
|
||||
+ env->SetMethodNoSideEffect(target, "internalVerifyIntegrity", InternalVerifyIntegrity);
|
||||
env->SetMethod(target, "publicEncrypt",
|
||||
PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
|
||||
EVP_PKEY_encrypt_init,
|
503
CVE-2023-44487.patch
Normal file
503
CVE-2023-44487.patch
Normal file
@@ -0,0 +1,503 @@
|
||||
commit d19f98f619771ce0ffe4f173bc96f8e823460de4
|
||||
Author: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
|
||||
Date: Sun Oct 1 00:05:01 2023 +0900
|
||||
|
||||
Rework session management
|
||||
|
||||
index a02a534b..03f6030b 100644
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/CMakeLists.txt
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/CMakeLists.txt
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/CMakeLists.txt
|
||||
@@ -23,6 +23,8 @@ set(NGHTTP2_SOURCES
|
||||
nghttp2_mem.c
|
||||
nghttp2_http.c
|
||||
nghttp2_rcbuf.c
|
||||
+ nghttp2_ratelim.c
|
||||
+ nghttp2_time.c
|
||||
nghttp2_debug.c
|
||||
nghttp2_ksl.c
|
||||
)
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/Makefile.am
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/Makefile.am
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/Makefile.am
|
||||
@@ -49,6 +49,9 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c ngh
|
||||
nghttp2_mem.c \
|
||||
nghttp2_http.c \
|
||||
nghttp2_rcbuf.c \
|
||||
+ nghttp2_extpri.c \
|
||||
+ nghttp2_ratelim.c \
|
||||
+ nghttp2_time.c \
|
||||
nghttp2_debug.c \
|
||||
nghttp2_ksl.c
|
||||
|
||||
@@ -66,6 +69,9 @@ HFILES = nghttp2_pq.h nghttp2_int.h nght
|
||||
nghttp2_mem.h \
|
||||
nghttp2_http.h \
|
||||
nghttp2_rcbuf.h \
|
||||
+ nghttp2_extpri.h \
|
||||
+ nghttp2_ratelim.h \
|
||||
+ nghttp2_time.h \
|
||||
nghttp2_debug.h \
|
||||
nghttp2_ksl.h
|
||||
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
|
||||
@@ -2674,6 +2674,24 @@ NGHTTP2_EXTERN void nghttp2_option_set_m
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
+ * This function sets the rate limit for the incoming stream reset
|
||||
+ * (RST_STREAM frame). It is server use only. It is a token-bucket
|
||||
+ * based rate limiter. |burst| specifies the number of tokens that is
|
||||
+ * initially available. The maximum number of tokens is capped to
|
||||
+ * this value. |rate| specifies the number of tokens that are
|
||||
+ * regenerated per second. An incoming RST_STREAM consumes one token.
|
||||
+ * If there is no token available, GOAWAY is sent to tear down the
|
||||
+ * connection. |burst| and |rate| default to 1000 and 33
|
||||
+ * respectively.
|
||||
+ */
|
||||
+NGHTTP2_EXTERN void
|
||||
+nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
|
||||
+ uint64_t burst, uint64_t rate);
|
||||
+
|
||||
+
|
||||
+/**
|
||||
+ * @function
|
||||
+ *
|
||||
* This function sets the maximum number of SETTINGS entries per
|
||||
* SETTINGS frame that will be accepted. If more than those entries
|
||||
* are received, the peer is considered to be misbehaving and session
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_option.c
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/nghttp2_option.c
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_option.c
|
||||
@@ -126,3 +126,10 @@ void nghttp2_option_set_max_settings(ngh
|
||||
option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
|
||||
option->max_settings = val;
|
||||
}
|
||||
+
|
||||
+void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
|
||||
+ uint64_t burst, uint64_t rate) {
|
||||
+ option->opt_set_mask |= NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT;
|
||||
+ option->stream_reset_burst = burst;
|
||||
+ option->stream_reset_rate = rate;
|
||||
+}
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_option.h
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/nghttp2_option.h
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_option.h
|
||||
@@ -68,6 +68,9 @@ typedef enum {
|
||||
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
|
||||
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
|
||||
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
|
||||
+ NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
|
||||
+ NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
|
||||
+ NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15,
|
||||
} nghttp2_option_flag;
|
||||
|
||||
/**
|
||||
@@ -75,6 +78,11 @@ typedef enum {
|
||||
*/
|
||||
struct nghttp2_option {
|
||||
/**
|
||||
+ * NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT
|
||||
+ */
|
||||
+ uint64_t stream_reset_burst;
|
||||
+ uint64_t stream_reset_rate;
|
||||
+ /**
|
||||
* NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH
|
||||
*/
|
||||
size_t max_send_header_block_length;
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_ratelim.c
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_ratelim.c
|
||||
@@ -0,0 +1,75 @@
|
||||
+/*
|
||||
+ * nghttp2 - HTTP/2 C Library
|
||||
+ *
|
||||
+ * Copyright (c) 2023 nghttp2 contributors
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining
|
||||
+ * a copy of this software and associated documentation files (the
|
||||
+ * "Software"), to deal in the Software without restriction, including
|
||||
+ * without limitation the rights to use, copy, modify, merge, publish,
|
||||
+ * distribute, sublicense, and/or sell copies of the Software, and to
|
||||
+ * permit persons to whom the Software is furnished to do so, subject to
|
||||
+ * the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be
|
||||
+ * included in all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+ */
|
||||
+#include "nghttp2_ratelim.h"
|
||||
+#include "nghttp2_helper.h"
|
||||
+
|
||||
+void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate) {
|
||||
+ rl->val = rl->burst = burst;
|
||||
+ rl->rate = rate;
|
||||
+ rl->tstamp = 0;
|
||||
+}
|
||||
+
|
||||
+void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp) {
|
||||
+ uint64_t d, gain;
|
||||
+
|
||||
+ if (tstamp == rl->tstamp) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (tstamp > rl->tstamp) {
|
||||
+ d = tstamp - rl->tstamp;
|
||||
+ } else {
|
||||
+ d = 1;
|
||||
+ }
|
||||
+
|
||||
+ rl->tstamp = tstamp;
|
||||
+
|
||||
+ if (UINT64_MAX / d < rl->rate) {
|
||||
+ rl->val = rl->burst;
|
||||
+
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ gain = rl->rate * d;
|
||||
+
|
||||
+ if (UINT64_MAX - gain < rl->val) {
|
||||
+ rl->val = rl->burst;
|
||||
+
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ rl->val += gain;
|
||||
+ rl->val = nghttp2_min(rl->val, rl->burst);
|
||||
+}
|
||||
+
|
||||
+int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n) {
|
||||
+ if (rl->val < n) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ rl->val -= n;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_ratelim.h
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_ratelim.h
|
||||
@@ -0,0 +1,57 @@
|
||||
+/*
|
||||
+ * nghttp2 - HTTP/2 C Library
|
||||
+ *
|
||||
+ * Copyright (c) 2023 nghttp2 contributors
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining
|
||||
+ * a copy of this software and associated documentation files (the
|
||||
+ * "Software"), to deal in the Software without restriction, including
|
||||
+ * without limitation the rights to use, copy, modify, merge, publish,
|
||||
+ * distribute, sublicense, and/or sell copies of the Software, and to
|
||||
+ * permit persons to whom the Software is furnished to do so, subject to
|
||||
+ * the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be
|
||||
+ * included in all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+ */
|
||||
+#ifndef NGHTTP2_RATELIM_H
|
||||
+#define NGHTTP2_RATELIM_H
|
||||
+
|
||||
+#ifdef HAVE_CONFIG_H
|
||||
+# include <config.h>
|
||||
+#endif /* HAVE_CONFIG_H */
|
||||
+
|
||||
+#include <nghttp2/nghttp2.h>
|
||||
+
|
||||
+typedef struct nghttp2_ratelim {
|
||||
+ /* burst is the maximum value of val. */
|
||||
+ uint64_t burst;
|
||||
+ /* rate is the amount of value that is regenerated per 1 tstamp. */
|
||||
+ uint64_t rate;
|
||||
+ /* val is the amount of value available to drain. */
|
||||
+ uint64_t val;
|
||||
+ /* tstamp is the last timestamp in second resolution that is known
|
||||
+ to this object. */
|
||||
+ uint64_t tstamp;
|
||||
+} nghttp2_ratelim;
|
||||
+
|
||||
+/* nghttp2_ratelim_init initializes |rl| with the given parameters. */
|
||||
+void nghttp2_ratelim_init(nghttp2_ratelim *rl, uint64_t burst, uint64_t rate);
|
||||
+
|
||||
+/* nghttp2_ratelim_update updates rl->val with the current |tstamp|
|
||||
+ given in second resolution. */
|
||||
+void nghttp2_ratelim_update(nghttp2_ratelim *rl, uint64_t tstamp);
|
||||
+
|
||||
+/* nghttp2_ratelim_drain drains |n| from rl->val. It returns 0 if it
|
||||
+ succeeds, or -1. */
|
||||
+int nghttp2_ratelim_drain(nghttp2_ratelim *rl, uint64_t n);
|
||||
+
|
||||
+#endif /* NGHTTP2_RATELIM_H */
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_session.c
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/nghttp2_session.c
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_session.c
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "nghttp2_option.h"
|
||||
#include "nghttp2_http.h"
|
||||
#include "nghttp2_pq.h"
|
||||
+#include "nghttp2_time.h"
|
||||
#include "nghttp2_debug.h"
|
||||
|
||||
/*
|
||||
@@ -443,6 +444,10 @@ static int session_new(nghttp2_session *
|
||||
NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
|
||||
(*session_ptr)->pending_enable_push = 1;
|
||||
|
||||
+ nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim,
|
||||
+ NGHTTP2_DEFAULT_STREAM_RESET_BURST,
|
||||
+ NGHTTP2_DEFAULT_STREAM_RESET_RATE);
|
||||
+
|
||||
if (server) {
|
||||
(*session_ptr)->server = 1;
|
||||
}
|
||||
@@ -527,6 +532,12 @@ static int session_new(nghttp2_session *
|
||||
option->max_settings) {
|
||||
(*session_ptr)->max_settings = option->max_settings;
|
||||
}
|
||||
+
|
||||
+ if (option->opt_set_mask & NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT) {
|
||||
+ nghttp2_ratelim_init(&(*session_ptr)->stream_reset_ratelim,
|
||||
+ option->stream_reset_burst,
|
||||
+ option->stream_reset_rate);
|
||||
+ }
|
||||
}
|
||||
|
||||
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
|
||||
@@ -4153,6 +4164,23 @@ static int session_process_priority_fram
|
||||
return nghttp2_session_on_priority_received(session, frame);
|
||||
}
|
||||
|
||||
+static int session_update_stream_reset_ratelim(nghttp2_session *session) {
|
||||
+ if (!session->server || (session->goaway_flags & NGHTTP2_GOAWAY_SUBMITTED)) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ nghttp2_ratelim_update(&session->stream_reset_ratelim,
|
||||
+ nghttp2_time_now_sec());
|
||||
+
|
||||
+ if (nghttp2_ratelim_drain(&session->stream_reset_ratelim, 1) == 0) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ return nghttp2_session_add_goaway(session, session->last_recv_stream_id,
|
||||
+ NGHTTP2_INTERNAL_ERROR, NULL, 0,
|
||||
+ NGHTTP2_GOAWAY_AUX_NONE);
|
||||
+}
|
||||
+
|
||||
int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame) {
|
||||
int rv;
|
||||
@@ -4182,7 +4210,8 @@ int nghttp2_session_on_rst_stream_receiv
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
- return 0;
|
||||
+
|
||||
+ return session_update_stream_reset_ratelim(session);
|
||||
}
|
||||
|
||||
static int session_process_rst_stream_frame(nghttp2_session *session) {
|
||||
@@ -6963,6 +6992,9 @@ int nghttp2_session_add_goaway(nghttp2_s
|
||||
nghttp2_mem_free(mem, item);
|
||||
return rv;
|
||||
}
|
||||
+
|
||||
+ session->goaway_flags |= NGHTTP2_GOAWAY_SUBMITTED;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_session.h
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/lib/nghttp2_session.h
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_session.h
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "nghttp2_buf.h"
|
||||
#include "nghttp2_callbacks.h"
|
||||
#include "nghttp2_mem.h"
|
||||
+#include "nghttp2_ratelim.h"
|
||||
|
||||
/* The global variable for tests where we want to disable strict
|
||||
preface handling. */
|
||||
@@ -102,6 +103,10 @@ typedef struct {
|
||||
/* The default value of maximum number of concurrent streams. */
|
||||
#define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu
|
||||
|
||||
+/* The default values for stream reset rate limiter. */
|
||||
+#define NGHTTP2_DEFAULT_STREAM_RESET_BURST 1000
|
||||
+#define NGHTTP2_DEFAULT_STREAM_RESET_RATE 33
|
||||
+
|
||||
/* Internal state when receiving incoming frame */
|
||||
typedef enum {
|
||||
/* Receiving frame header */
|
||||
@@ -176,7 +181,9 @@ typedef enum {
|
||||
/* Flag means GOAWAY was sent */
|
||||
NGHTTP2_GOAWAY_SENT = 0x4,
|
||||
/* Flag means GOAWAY was received */
|
||||
- NGHTTP2_GOAWAY_RECV = 0x8
|
||||
+ NGHTTP2_GOAWAY_RECV = 0x8,
|
||||
+ /* Flag means GOAWAY has been submitted at least once */
|
||||
+ NGHTTP2_GOAWAY_SUBMITTED = 0x10
|
||||
} nghttp2_goaway_flag;
|
||||
|
||||
/* nghttp2_inflight_settings stores the SETTINGS entries which local
|
||||
@@ -227,6 +234,9 @@ struct nghttp2_session {
|
||||
/* Queue of In-flight SETTINGS values. SETTINGS bearing ACK is not
|
||||
considered as in-flight. */
|
||||
nghttp2_inflight_settings *inflight_settings_head;
|
||||
+ /* Stream reset rate limiter. If receiving excessive amount of
|
||||
+ stream resets, GOAWAY will be sent. */
|
||||
+ nghttp2_ratelim stream_reset_ratelim;
|
||||
/* The number of outgoing streams. This will be capped by
|
||||
remote_settings.max_concurrent_streams. */
|
||||
size_t num_outgoing_streams;
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_time.c
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_time.c
|
||||
@@ -0,0 +1,62 @@
|
||||
+/*
|
||||
+ * nghttp2 - HTTP/2 C Library
|
||||
+ *
|
||||
+ * Copyright (c) 2023 nghttp2 contributors
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining
|
||||
+ * a copy of this software and associated documentation files (the
|
||||
+ * "Software"), to deal in the Software without restriction, including
|
||||
+ * without limitation the rights to use, copy, modify, merge, publish,
|
||||
+ * distribute, sublicense, and/or sell copies of the Software, and to
|
||||
+ * permit persons to whom the Software is furnished to do so, subject to
|
||||
+ * the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be
|
||||
+ * included in all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+ */
|
||||
+#include "nghttp2_time.h"
|
||||
+
|
||||
+#ifdef HAVE_TIME_H
|
||||
+# include <time.h>
|
||||
+#endif /* HAVE_TIME_H */
|
||||
+
|
||||
+#ifdef HAVE_SYSINFOAPI_H
|
||||
+# include <sysinfoapi.h>
|
||||
+#endif /* HAVE_SYSINFOAPI_H */
|
||||
+
|
||||
+#ifndef HAVE_GETTICKCOUNT64
|
||||
+static uint64_t time_now_sec(void) {
|
||||
+ time_t t = time(NULL);
|
||||
+
|
||||
+ if (t == -1) {
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ return (uint64_t)t;
|
||||
+}
|
||||
+#endif /* HAVE_GETTICKCOUNT64 */
|
||||
+
|
||||
+#ifdef HAVE_CLOCK_GETTIME
|
||||
+uint64_t nghttp2_time_now_sec(void) {
|
||||
+ struct timespec tp;
|
||||
+ int rv = clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
+
|
||||
+ if (rv == -1) {
|
||||
+ return time_now_sec();
|
||||
+ }
|
||||
+
|
||||
+ return (uint64_t)tp.tv_sec;
|
||||
+}
|
||||
+#elif defined(HAVE_GETTICKCOUNT64)
|
||||
+uint64_t nghttp2_time_now_sec(void) { return GetTickCount64() / 1000; }
|
||||
+#else /* !HAVE_CLOCK_GETTIME && !HAVE_GETTICKCOUNT64 */
|
||||
+uint64_t nghttp2_time_now_sec(void) { return time_now_sec(); }
|
||||
+#endif /* !HAVE_CLOCK_GETTIME && !HAVE_GETTICKCOUNT64 */
|
||||
Index: node-v14.21.3/deps/nghttp2/lib/nghttp2_time.h
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/deps/nghttp2/lib/nghttp2_time.h
|
||||
@@ -0,0 +1,38 @@
|
||||
+/*
|
||||
+ * nghttp2 - HTTP/2 C Library
|
||||
+ *
|
||||
+ * Copyright (c) 2023 nghttp2 contributors
|
||||
+ *
|
||||
+ * Permission is hereby granted, free of charge, to any person obtaining
|
||||
+ * a copy of this software and associated documentation files (the
|
||||
+ * "Software"), to deal in the Software without restriction, including
|
||||
+ * without limitation the rights to use, copy, modify, merge, publish,
|
||||
+ * distribute, sublicense, and/or sell copies of the Software, and to
|
||||
+ * permit persons to whom the Software is furnished to do so, subject to
|
||||
+ * the following conditions:
|
||||
+ *
|
||||
+ * The above copyright notice and this permission notice shall be
|
||||
+ * included in all copies or substantial portions of the Software.
|
||||
+ *
|
||||
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+ */
|
||||
+#ifndef NGHTTP2_TIME_H
|
||||
+#define NGHTTP2_TIME_H
|
||||
+
|
||||
+#ifdef HAVE_CONFIG_H
|
||||
+# include <config.h>
|
||||
+#endif /* HAVE_CONFIG_H */
|
||||
+
|
||||
+#include <nghttp2/nghttp2.h>
|
||||
+
|
||||
+/* nghttp2_time_now_sec returns seconds from implementation-specific
|
||||
+ timepoint. If it is unable to get seconds, it returns 0. */
|
||||
+uint64_t nghttp2_time_now_sec(void);
|
||||
+
|
||||
+#endif /* NGHTTP2_TIME_H */
|
||||
Index: node-v14.21.3/deps/nghttp2/nghttp2.gyp
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/nghttp2/nghttp2.gyp
|
||||
+++ node-v14.21.3/deps/nghttp2/nghttp2.gyp
|
||||
@@ -52,6 +52,8 @@
|
||||
'lib/nghttp2_outbound_item.c',
|
||||
'lib/nghttp2_pq.c',
|
||||
'lib/nghttp2_priority_spec.c',
|
||||
+ 'lib/nghttp2_ratelim.c',
|
||||
+ 'lib/nghttp2_time.c',
|
||||
'lib/nghttp2_queue.c',
|
||||
'lib/nghttp2_rcbuf.c',
|
||||
'lib/nghttp2_session.c',
|
584
CVE-2023-46809.patch
Normal file
584
CVE-2023-46809.patch
Normal file
@@ -0,0 +1,584 @@
|
||||
Index: node-v14.21.3/test/parallel/test-crypto-rsa-dsa-revert.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/parallel/test-crypto-rsa-dsa-revert.js
|
||||
@@ -0,0 +1,466 @@
|
||||
+'use strict';
|
||||
+// Flags: --security-revert=CVE-2023-46809
|
||||
+const common = require('../common');
|
||||
+if (!common.hasCrypto)
|
||||
+ common.skip('missing crypto');
|
||||
+
|
||||
+const assert = require('assert');
|
||||
+const crypto = require('crypto');
|
||||
+
|
||||
+const constants = crypto.constants;
|
||||
+
|
||||
+const fixtures = require('../common/fixtures');
|
||||
+
|
||||
+// Test certificates
|
||||
+const certPem = fixtures.readKey('rsa_cert.crt');
|
||||
+const keyPem = fixtures.readKey('rsa_private.pem');
|
||||
+const rsaKeySize = 2048;
|
||||
+const rsaPubPem = fixtures.readKey('rsa_public.pem', 'ascii');
|
||||
+const rsaKeyPem = fixtures.readKey('rsa_private.pem', 'ascii');
|
||||
+const rsaKeyPemEncrypted = fixtures.readKey('rsa_private_encrypted.pem',
|
||||
+ 'ascii');
|
||||
+const dsaPubPem = fixtures.readKey('dsa_public.pem', 'ascii');
|
||||
+const dsaKeyPem = fixtures.readKey('dsa_private.pem', 'ascii');
|
||||
+const dsaKeyPemEncrypted = fixtures.readKey('dsa_private_encrypted.pem',
|
||||
+ 'ascii');
|
||||
+const rsaPkcs8KeyPem = fixtures.readKey('rsa_private_pkcs8.pem');
|
||||
+const dsaPkcs8KeyPem = fixtures.readKey('dsa_private_pkcs8.pem');
|
||||
+
|
||||
+const ec = new TextEncoder();
|
||||
+
|
||||
+const openssl1DecryptError = {
|
||||
+ message: 'error:06065064:digital envelope routines:EVP_DecryptFinal_ex:' +
|
||||
+ 'bad decrypt',
|
||||
+ code: 'ERR_OSSL_EVP_BAD_DECRYPT',
|
||||
+ reason: 'bad decrypt',
|
||||
+ function: 'EVP_DecryptFinal_ex',
|
||||
+ library: 'digital envelope routines',
|
||||
+};
|
||||
+
|
||||
+const decryptError = common.hasOpenSSL3 ?
|
||||
+ { message: 'error:1C800064:Provider routines::bad decrypt' } :
|
||||
+ openssl1DecryptError;
|
||||
+
|
||||
+const decryptPrivateKeyError = common.hasOpenSSL3 ? {
|
||||
+ message: 'error:1C800064:Provider routines::bad decrypt',
|
||||
+} : openssl1DecryptError;
|
||||
+
|
||||
+function getBufferCopy(buf) {
|
||||
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
+}
|
||||
+
|
||||
+// Test RSA encryption/decryption
|
||||
+{
|
||||
+ const input = 'I AM THE WALRUS';
|
||||
+ const bufferToEncrypt = Buffer.from(input);
|
||||
+ const bufferPassword = Buffer.from('password');
|
||||
+
|
||||
+ let encryptedBuffer = crypto.publicEncrypt(rsaPubPem, bufferToEncrypt);
|
||||
+
|
||||
+ // Test other input types
|
||||
+ let otherEncrypted;
|
||||
+ {
|
||||
+ const ab = getBufferCopy(ec.encode(rsaPubPem));
|
||||
+ const ab2enc = getBufferCopy(bufferToEncrypt);
|
||||
+
|
||||
+ crypto.publicEncrypt(ec.encode(rsaPubPem), bufferToEncrypt);
|
||||
+ crypto.publicEncrypt(new Uint8Array(ab), new Uint8Array(ab2enc));
|
||||
+ crypto.publicEncrypt(new DataView(ab), new DataView(ab2enc));
|
||||
+ otherEncrypted = crypto.publicEncrypt({
|
||||
+ key: Buffer.from(ab)
|
||||
+ }, Buffer.from(ab2enc));
|
||||
+ }
|
||||
+
|
||||
+ let decryptedBuffer = crypto.privateDecrypt(rsaKeyPem, encryptedBuffer);
|
||||
+ const otherDecrypted = crypto.privateDecrypt(rsaKeyPem, otherEncrypted);
|
||||
+ assert.strictEqual(decryptedBuffer.toString(), input);
|
||||
+ assert.strictEqual(otherDecrypted.toString(), input);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt(rsaPkcs8KeyPem, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBuffer.toString(), input);
|
||||
+
|
||||
+ let decryptedBufferWithPassword = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'password'
|
||||
+ }, encryptedBuffer);
|
||||
+
|
||||
+ const otherDecryptedBufferWithPassword = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: ec.encode('password')
|
||||
+ }, encryptedBuffer);
|
||||
+
|
||||
+ assert.strictEqual(
|
||||
+ otherDecryptedBufferWithPassword.toString(),
|
||||
+ decryptedBufferWithPassword.toString());
|
||||
+
|
||||
+ decryptedBufferWithPassword = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'password'
|
||||
+ }, encryptedBuffer);
|
||||
+
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), input);
|
||||
+
|
||||
+ encryptedBuffer = crypto.publicEncrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'password'
|
||||
+ }, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBufferWithPassword = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'password'
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), input);
|
||||
+
|
||||
+ encryptedBuffer = crypto.privateEncrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBufferWithPassword = crypto.publicDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), input);
|
||||
+
|
||||
+ // Now with explicit RSA_PKCS1_PADDING.
|
||||
+ encryptedBuffer = crypto.privateEncrypt({
|
||||
+ padding: crypto.constants.RSA_PKCS1_PADDING,
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBufferWithPassword = crypto.publicDecrypt({
|
||||
+ padding: crypto.constants.RSA_PKCS1_PADDING,
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), input);
|
||||
+
|
||||
+ // Omitting padding should be okay because RSA_PKCS1_PADDING is the default.
|
||||
+ decryptedBufferWithPassword = crypto.publicDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), input);
|
||||
+
|
||||
+ // Now with RSA_NO_PADDING. Plaintext needs to match key size.
|
||||
+ // OpenSSL 3.x has a rsa_check_padding that will cause an error if
|
||||
+ // RSA_NO_PADDING is used.
|
||||
+ if (!common.hasOpenSSL3) {
|
||||
+ {
|
||||
+ const plaintext = 'x'.repeat(rsaKeySize / 8);
|
||||
+ encryptedBuffer = crypto.privateEncrypt({
|
||||
+ padding: crypto.constants.RSA_NO_PADDING,
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, Buffer.from(plaintext));
|
||||
+
|
||||
+ decryptedBufferWithPassword = crypto.publicDecrypt({
|
||||
+ padding: crypto.constants.RSA_NO_PADDING,
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: bufferPassword
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBufferWithPassword.toString(), plaintext);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ encryptedBuffer = crypto.publicEncrypt(certPem, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBuffer.toString(), input);
|
||||
+
|
||||
+ encryptedBuffer = crypto.publicEncrypt(keyPem, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBuffer.toString(), input);
|
||||
+
|
||||
+ encryptedBuffer = crypto.privateEncrypt(keyPem, bufferToEncrypt);
|
||||
+
|
||||
+ decryptedBuffer = crypto.publicDecrypt(keyPem, encryptedBuffer);
|
||||
+ assert.strictEqual(decryptedBuffer.toString(), input);
|
||||
+
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'wrong'
|
||||
+ }, bufferToEncrypt);
|
||||
+ }, decryptError);
|
||||
+
|
||||
+ assert.throws(() => {
|
||||
+ crypto.publicEncrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: 'wrong'
|
||||
+ }, encryptedBuffer);
|
||||
+ }, decryptError);
|
||||
+
|
||||
+ encryptedBuffer = crypto.privateEncrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: Buffer.from('password')
|
||||
+ }, bufferToEncrypt);
|
||||
+
|
||||
+ assert.throws(() => {
|
||||
+ crypto.publicDecrypt({
|
||||
+ key: rsaKeyPemEncrypted,
|
||||
+ passphrase: Buffer.from('wrong')
|
||||
+ }, encryptedBuffer);
|
||||
+ }, decryptError);
|
||||
+}
|
||||
+
|
||||
+function test_rsa(padding, encryptOaepHash, decryptOaepHash) {
|
||||
+ const size = (padding === 'RSA_NO_PADDING') ? rsaKeySize / 8 : 32;
|
||||
+ const input = Buffer.allocUnsafe(size);
|
||||
+ for (let i = 0; i < input.length; i++)
|
||||
+ input[i] = (i * 7 + 11) & 0xff;
|
||||
+ const bufferToEncrypt = Buffer.from(input);
|
||||
+
|
||||
+ padding = constants[padding];
|
||||
+
|
||||
+ const encryptedBuffer = crypto.publicEncrypt({
|
||||
+ key: rsaPubPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: encryptOaepHash
|
||||
+ }, bufferToEncrypt);
|
||||
+
|
||||
+ let decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+}
|
||||
+
|
||||
+test_rsa('RSA_NO_PADDING');
|
||||
+test_rsa('RSA_PKCS1_PADDING');
|
||||
+test_rsa('RSA_PKCS1_OAEP_PADDING');
|
||||
+
|
||||
+// Test OAEP with different hash functions.
|
||||
+test_rsa('RSA_PKCS1_OAEP_PADDING', undefined, 'sha1');
|
||||
+test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha1', undefined);
|
||||
+test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha256');
|
||||
+test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha512', 'sha512');
|
||||
+assert.throws(() => {
|
||||
+ test_rsa('RSA_PKCS1_OAEP_PADDING', 'sha256', 'sha512');
|
||||
+}, {
|
||||
+ code: 'ERR_OSSL_RSA_OAEP_DECODING_ERROR'
|
||||
+});
|
||||
+
|
||||
+// The following RSA-OAEP test cases were created using the WebCrypto API to
|
||||
+// ensure compatibility when using non-SHA1 hash functions.
|
||||
+{
|
||||
+ const { decryptionTests } =
|
||||
+ JSON.parse(fixtures.readSync('rsa-oaep-test-vectors.js', 'utf8'));
|
||||
+
|
||||
+ for (const { ct, oaepHash, oaepLabel } of decryptionTests) {
|
||||
+ const label = oaepLabel ? Buffer.from(oaepLabel, 'hex') : undefined;
|
||||
+ const copiedLabel = oaepLabel ? getBufferCopy(label) : undefined;
|
||||
+
|
||||
+ const decrypted = crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ oaepHash,
|
||||
+ oaepLabel: oaepLabel ? label : undefined
|
||||
+ }, Buffer.from(ct, 'hex'));
|
||||
+
|
||||
+ assert.strictEqual(decrypted.toString('utf8'), 'Hello Node.js');
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// Test invalid oaepHash and oaepLabel options.
|
||||
+for (const fn of [crypto.publicEncrypt, crypto.privateDecrypt]) {
|
||||
+ assert.throws(() => {
|
||||
+ fn({
|
||||
+ key: rsaPubPem,
|
||||
+ oaepHash: 'Hello world'
|
||||
+ }, Buffer.alloc(10));
|
||||
+ }, {
|
||||
+ code: 'ERR_OSSL_EVP_INVALID_DIGEST'
|
||||
+ });
|
||||
+
|
||||
+ for (const oaepHash of [0, false, null, Symbol(), () => {}]) {
|
||||
+ assert.throws(() => {
|
||||
+ fn({
|
||||
+ key: rsaPubPem,
|
||||
+ oaepHash
|
||||
+ }, Buffer.alloc(10));
|
||||
+ }, {
|
||||
+ code: 'ERR_INVALID_ARG_TYPE'
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ for (const oaepLabel of [0, false, null, Symbol(), () => {}, {}]) {
|
||||
+ assert.throws(() => {
|
||||
+ fn({
|
||||
+ key: rsaPubPem,
|
||||
+ oaepLabel
|
||||
+ }, Buffer.alloc(10));
|
||||
+ }, {
|
||||
+ code: 'ERR_INVALID_ARG_TYPE'
|
||||
+ });
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// Test RSA key signing/verification
|
||||
+let rsaSign = crypto.createSign('SHA1');
|
||||
+let rsaVerify = crypto.createVerify('SHA1');
|
||||
+assert.ok(rsaSign);
|
||||
+assert.ok(rsaVerify);
|
||||
+
|
||||
+const expectedSignature = fixtures.readKey(
|
||||
+ 'rsa_public_sha1_signature_signedby_rsa_private_pkcs8.sha1',
|
||||
+ 'hex'
|
||||
+);
|
||||
+
|
||||
+rsaSign.update(rsaPubPem);
|
||||
+let rsaSignature = rsaSign.sign(rsaKeyPem, 'hex');
|
||||
+assert.strictEqual(rsaSignature, expectedSignature);
|
||||
+
|
||||
+rsaVerify.update(rsaPubPem);
|
||||
+assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
||||
+
|
||||
+// Test RSA PKCS#8 key signing/verification
|
||||
+rsaSign = crypto.createSign('SHA1');
|
||||
+rsaSign.update(rsaPubPem);
|
||||
+rsaSignature = rsaSign.sign(rsaPkcs8KeyPem, 'hex');
|
||||
+assert.strictEqual(rsaSignature, expectedSignature);
|
||||
+
|
||||
+rsaVerify = crypto.createVerify('SHA1');
|
||||
+rsaVerify.update(rsaPubPem);
|
||||
+assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
||||
+
|
||||
+// Test RSA key signing/verification with encrypted key
|
||||
+rsaSign = crypto.createSign('SHA1');
|
||||
+rsaSign.update(rsaPubPem);
|
||||
+const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'password' };
|
||||
+rsaSignature = rsaSign.sign(signOptions, 'hex');
|
||||
+assert.strictEqual(rsaSignature, expectedSignature);
|
||||
+
|
||||
+rsaVerify = crypto.createVerify('SHA1');
|
||||
+rsaVerify.update(rsaPubPem);
|
||||
+assert.strictEqual(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), true);
|
||||
+
|
||||
+rsaSign = crypto.createSign('SHA1');
|
||||
+rsaSign.update(rsaPubPem);
|
||||
+assert.throws(() => {
|
||||
+ const signOptions = { key: rsaKeyPemEncrypted, passphrase: 'wrong' };
|
||||
+ rsaSign.sign(signOptions, 'hex');
|
||||
+}, decryptPrivateKeyError);
|
||||
+
|
||||
+//
|
||||
+// Test RSA signing and verification
|
||||
+//
|
||||
+{
|
||||
+ const privateKey = fixtures.readKey('rsa_private_b.pem');
|
||||
+ const publicKey = fixtures.readKey('rsa_public_b.pem');
|
||||
+
|
||||
+ const input = 'I AM THE WALRUS';
|
||||
+
|
||||
+ const signature = fixtures.readKey(
|
||||
+ 'I_AM_THE_WALRUS_sha256_signature_signedby_rsa_private_b.sha256',
|
||||
+ 'hex'
|
||||
+ );
|
||||
+
|
||||
+ const sign = crypto.createSign('SHA256');
|
||||
+ sign.update(input);
|
||||
+
|
||||
+ const output = sign.sign(privateKey, 'hex');
|
||||
+ assert.strictEqual(output, signature);
|
||||
+
|
||||
+ const verify = crypto.createVerify('SHA256');
|
||||
+ verify.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify.verify(publicKey, signature, 'hex'), true);
|
||||
+
|
||||
+ // Test the legacy signature algorithm name.
|
||||
+ const sign2 = crypto.createSign('RSA-SHA256');
|
||||
+ sign2.update(input);
|
||||
+
|
||||
+ const output2 = sign2.sign(privateKey, 'hex');
|
||||
+ assert.strictEqual(output2, signature);
|
||||
+
|
||||
+ const verify2 = crypto.createVerify('SHA256');
|
||||
+ verify2.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify2.verify(publicKey, signature, 'hex'), true);
|
||||
+}
|
||||
+
|
||||
+
|
||||
+//
|
||||
+// Test DSA signing and verification
|
||||
+//
|
||||
+{
|
||||
+ const input = 'I AM THE WALRUS';
|
||||
+
|
||||
+ // DSA signatures vary across runs so there is no static string to verify
|
||||
+ // against.
|
||||
+ const sign = crypto.createSign('SHA1');
|
||||
+ sign.update(input);
|
||||
+ const signature = sign.sign(dsaKeyPem, 'hex');
|
||||
+
|
||||
+ const verify = crypto.createVerify('SHA1');
|
||||
+ verify.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
|
||||
+
|
||||
+ // Test the legacy 'DSS1' name.
|
||||
+ const sign2 = crypto.createSign('DSS1');
|
||||
+ sign2.update(input);
|
||||
+ const signature2 = sign2.sign(dsaKeyPem, 'hex');
|
||||
+
|
||||
+ const verify2 = crypto.createVerify('DSS1');
|
||||
+ verify2.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify2.verify(dsaPubPem, signature2, 'hex'), true);
|
||||
+}
|
||||
+
|
||||
+
|
||||
+//
|
||||
+// Test DSA signing and verification with PKCS#8 private key
|
||||
+//
|
||||
+{
|
||||
+ const input = 'I AM THE WALRUS';
|
||||
+
|
||||
+ // DSA signatures vary across runs so there is no static string to verify
|
||||
+ // against.
|
||||
+ const sign = crypto.createSign('SHA1');
|
||||
+ sign.update(input);
|
||||
+ const signature = sign.sign(dsaPkcs8KeyPem, 'hex');
|
||||
+
|
||||
+ const verify = crypto.createVerify('SHA1');
|
||||
+ verify.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
|
||||
+}
|
||||
+
|
||||
+
|
||||
+//
|
||||
+// Test DSA signing and verification with encrypted key
|
||||
+//
|
||||
+const input = 'I AM THE WALRUS';
|
||||
+
|
||||
+{
|
||||
+ const sign = crypto.createSign('SHA1');
|
||||
+ sign.update(input);
|
||||
+ assert.throws(() => {
|
||||
+ sign.sign({ key: dsaKeyPemEncrypted, passphrase: 'wrong' }, 'hex');
|
||||
+ }, decryptPrivateKeyError);
|
||||
+}
|
||||
+
|
||||
+{
|
||||
+ // DSA signatures vary across runs so there is no static string to verify
|
||||
+ // against.
|
||||
+ const sign = crypto.createSign('SHA1');
|
||||
+ sign.update(input);
|
||||
+ const signOptions = { key: dsaKeyPemEncrypted, passphrase: 'password' };
|
||||
+ const signature = sign.sign(signOptions, 'hex');
|
||||
+
|
||||
+ const verify = crypto.createVerify('SHA1');
|
||||
+ verify.update(input);
|
||||
+
|
||||
+ assert.strictEqual(verify.verify(dsaPubPem, signature, 'hex'), true);
|
||||
+}
|
||||
Index: node-v14.21.3/test/parallel/test-crypto-rsa-dsa.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/test/parallel/test-crypto-rsa-dsa.js
|
||||
+++ node-v14.21.3/test/parallel/test-crypto-rsa-dsa.js
|
||||
@@ -169,19 +169,37 @@ function test_rsa(padding, encryptOaepHa
|
||||
oaepHash: encryptOaepHash
|
||||
}, bufferToEncrypt);
|
||||
|
||||
- let decryptedBuffer = crypto.privateDecrypt({
|
||||
- key: rsaKeyPem,
|
||||
- padding: padding,
|
||||
- oaepHash: decryptOaepHash
|
||||
- }, encryptedBuffer);
|
||||
- assert.deepStrictEqual(decryptedBuffer, input);
|
||||
|
||||
- decryptedBuffer = crypto.privateDecrypt({
|
||||
- key: rsaPkcs8KeyPem,
|
||||
- padding: padding,
|
||||
- oaepHash: decryptOaepHash
|
||||
- }, encryptedBuffer);
|
||||
- assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+ if (padding === constants.RSA_PKCS1_PADDING) {
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ } else {
|
||||
+ let decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+ }
|
||||
}
|
||||
|
||||
test_rsa('RSA_NO_PADDING');
|
||||
Index: node-v14.21.3/src/node_crypto.cc
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/src/node_crypto.cc
|
||||
+++ node-v14.21.3/src/node_crypto.cc
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "string_bytes.h"
|
||||
#include "threadpoolwork-inl.h"
|
||||
#include "util-inl.h"
|
||||
+#include "node_revert.h"
|
||||
#include "v8.h"
|
||||
|
||||
#include <openssl/ec.h>
|
||||
@@ -5131,6 +5132,33 @@ void PublicKeyCipher::Cipher(const Funct
|
||||
uint32_t padding;
|
||||
if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return;
|
||||
|
||||
+ if (EVP_PKEY_cipher == EVP_PKEY_decrypt &&
|
||||
+ operation == PublicKeyCipher::kPrivate && padding == RSA_PKCS1_PADDING &&
|
||||
+ !IsReverted(SECURITY_REVERT_CVE_2023_46809)) {
|
||||
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
|
||||
+ CHECK(ctx);
|
||||
+
|
||||
+ if (EVP_PKEY_decrypt_init(ctx.get()) <= 0) {
|
||||
+ return ThrowCryptoError(env, ERR_get_error());
|
||||
+ }
|
||||
+
|
||||
+ int rsa_pkcs1_implicit_rejection =
|
||||
+ EVP_PKEY_CTX_ctrl_str(ctx.get(), "rsa_pkcs1_implicit_rejection", "1");
|
||||
+ // From the doc -2 means that the option is not supported.
|
||||
+ // The default for the option is enabled and if it has been
|
||||
+ // specifically disabled we want to respect that so we will
|
||||
+ // not throw an error if the option is supported regardless
|
||||
+ // of how it is set. The call to set the value
|
||||
+ // will not affect what is used since a different context is
|
||||
+ // used in the call if the option is supported
|
||||
+ if (rsa_pkcs1_implicit_rejection <= 0) {
|
||||
+ return THROW_ERR_INVALID_ARG_VALUE(
|
||||
+ env,
|
||||
+ "RSA_PKCS1_PADDING is no longer supported for private decryption,"
|
||||
+ " this can be reverted with --security-revert=CVE-2023-46809");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
const node::Utf8Value oaep_str(env->isolate(), args[offset + 2]);
|
||||
const char* oaep_hash = args[offset + 2]->IsString() ? *oaep_str : nullptr;
|
||||
const EVP_MD* digest = nullptr;
|
||||
Index: node-v14.21.3/src/node_revert.h
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/src/node_revert.h
|
||||
+++ node-v14.21.3/src/node_revert.h
|
||||
@@ -18,7 +18,7 @@ namespace node {
|
||||
#define SECURITY_REVERSIONS(XX) \
|
||||
XX(CVE_2021_44531, "CVE-2021-44531", "Cert Verif Bypass via URI SAN") \
|
||||
XX(CVE_2021_44532, "CVE-2021-44532", "Cert Verif Bypass via Str Inject") \
|
||||
-// XX(CVE_2016_PEND, "CVE-2016-PEND", "Vulnerability Title")
|
||||
+ XX(CVE_2023_46809, "CVE-2023-46809", "Marvin attack on PKCS#1 padding")
|
||||
|
||||
enum reversion {
|
||||
#define V(code, ...) SECURITY_REVERT_##code,
|
515
CVE-2024-22019.patch
Normal file
515
CVE-2024-22019.patch
Normal file
@@ -0,0 +1,515 @@
|
||||
Index: node-v14.21.3/deps/llhttp/include/llhttp.h
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/llhttp/include/llhttp.h
|
||||
+++ node-v14.21.3/deps/llhttp/include/llhttp.h
|
||||
@@ -255,6 +255,10 @@ struct llhttp_settings_s {
|
||||
*/
|
||||
llhttp_cb on_headers_complete;
|
||||
|
||||
+ /* Possible return values 0, -1, HPE_USER */
|
||||
+ llhttp_data_cb on_chunk_parameters;
|
||||
+
|
||||
+ /* Possible return values 0, -1, HPE_USER */
|
||||
llhttp_data_cb on_body;
|
||||
|
||||
/* Possible return values 0, -1, `HPE_PAUSED` */
|
||||
Index: node-v14.21.3/deps/llhttp/src/api.c
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/llhttp/src/api.c
|
||||
+++ node-v14.21.3/deps/llhttp/src/api.c
|
||||
@@ -15,6 +15,21 @@
|
||||
err = settings->NAME(__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
+#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \
|
||||
+ do { \
|
||||
+ const llhttp_settings_t* settings; \
|
||||
+ settings = (const llhttp_settings_t*) (PARSER)->settings; \
|
||||
+ if (settings == NULL || settings->NAME == NULL) { \
|
||||
+ err = 0; \
|
||||
+ break; \
|
||||
+ } \
|
||||
+ err = settings->NAME((PARSER), (START), (LEN)); \
|
||||
+ if (err == -1) { \
|
||||
+ err = HPE_USER; \
|
||||
+ llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \
|
||||
+ } \
|
||||
+ } while (0)
|
||||
+
|
||||
void llhttp_init(llhttp_t* parser, llhttp_type_t type,
|
||||
const llhttp_settings_t* settings) {
|
||||
llhttp__internal_init(parser);
|
||||
@@ -201,6 +216,13 @@ int llhttp__on_chunk_header(llhttp_t* s,
|
||||
return err;
|
||||
}
|
||||
|
||||
+
|
||||
+int llhttp__on_chunk_parameters(llhttp_t* s, const char* p, const char* endp) {
|
||||
+ int err;
|
||||
+ SPAN_CALLBACK_MAYBE(s, on_chunk_parameters, p, endp - p);
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
|
||||
int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
|
||||
int err;
|
||||
Index: node-v14.21.3/deps/llhttp/src/llhttp.c
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/deps/llhttp/src/llhttp.c
|
||||
+++ node-v14.21.3/deps/llhttp/src/llhttp.c
|
||||
@@ -307,6 +307,8 @@ enum llparse_state_e {
|
||||
s_n_llhttp__internal__n_invoke_is_equal_content_length,
|
||||
s_n_llhttp__internal__n_chunk_size_almost_done,
|
||||
s_n_llhttp__internal__n_chunk_parameters,
|
||||
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
|
||||
+ s_n_llhttp__internal__n_chunk_parameters_ows,
|
||||
s_n_llhttp__internal__n_chunk_size_otherwise,
|
||||
s_n_llhttp__internal__n_chunk_size,
|
||||
s_n_llhttp__internal__n_chunk_size_digit,
|
||||
@@ -482,6 +484,10 @@ int llhttp__on_body(
|
||||
llhttp__internal_t* s, const unsigned char* p,
|
||||
const unsigned char* endp);
|
||||
|
||||
+int llhttp__on_chunk_parameters(
|
||||
+ llhttp__internal_t* s, const unsigned char* p,
|
||||
+ const unsigned char* endp);
|
||||
+
|
||||
int llhttp__on_status(
|
||||
llhttp__internal_t* s, const unsigned char* p,
|
||||
const unsigned char* endp);
|
||||
@@ -1118,8 +1124,7 @@ static llparse_state_t llhttp__internal_
|
||||
goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
}
|
||||
case 2: {
|
||||
- p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
|
||||
}
|
||||
default: {
|
||||
goto s_n_llhttp__internal__n_error_10;
|
||||
@@ -1128,6 +1133,34 @@ static llparse_state_t llhttp__internal_
|
||||
/* UNREACHABLE */;
|
||||
abort();
|
||||
}
|
||||
+ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
|
||||
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
|
||||
+ if (p == endp) {
|
||||
+ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
|
||||
+ }
|
||||
+ state->_span_pos0 = (void*) p;
|
||||
+ state->_span_cb0 = llhttp__on_chunk_parameters;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
+ case s_n_llhttp__internal__n_chunk_parameters_ows:
|
||||
+ s_n_llhttp__internal__n_chunk_parameters_ows: {
|
||||
+ if (p == endp) {
|
||||
+ return s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
+ }
|
||||
+ switch (*p) {
|
||||
+ case ' ': {
|
||||
+ p++;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
+ }
|
||||
+ default: {
|
||||
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
|
||||
+ }
|
||||
+ }
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
case s_n_llhttp__internal__n_chunk_size_otherwise:
|
||||
s_n_llhttp__internal__n_chunk_size_otherwise: {
|
||||
if (p == endp) {
|
||||
@@ -1138,13 +1171,9 @@ static llparse_state_t llhttp__internal_
|
||||
p++;
|
||||
goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
}
|
||||
- case ' ': {
|
||||
- p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
- }
|
||||
case ';': {
|
||||
p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
}
|
||||
default: {
|
||||
goto s_n_llhttp__internal__n_error_11;
|
||||
@@ -5449,6 +5478,24 @@ static llparse_state_t llhttp__internal_
|
||||
/* UNREACHABLE */;
|
||||
abort();
|
||||
}
|
||||
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
|
||||
+ const unsigned char* start;
|
||||
+ int err;
|
||||
+
|
||||
+ start = state->_span_pos0;
|
||||
+ state->_span_pos0 = NULL;
|
||||
+ err = llhttp__on_chunk_parameters(state, start, p);
|
||||
+ if (err != 0) {
|
||||
+ state->error = err;
|
||||
+ state->error_pos = (const char*) (p + 1);
|
||||
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ return s_error;
|
||||
+ }
|
||||
+ p++;
|
||||
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
s_n_llhttp__internal__n_error_10: {
|
||||
state->error = 0x2;
|
||||
state->reason = "Invalid character in chunk parameters";
|
||||
@@ -7414,6 +7461,8 @@ enum llparse_state_e {
|
||||
s_n_llhttp__internal__n_invoke_is_equal_content_length,
|
||||
s_n_llhttp__internal__n_chunk_size_almost_done,
|
||||
s_n_llhttp__internal__n_chunk_parameters,
|
||||
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
|
||||
+ s_n_llhttp__internal__n_chunk_parameters_ows,
|
||||
s_n_llhttp__internal__n_chunk_size_otherwise,
|
||||
s_n_llhttp__internal__n_chunk_size,
|
||||
s_n_llhttp__internal__n_chunk_size_digit,
|
||||
@@ -7584,6 +7633,10 @@ int llhttp__on_body(
|
||||
llhttp__internal_t* s, const unsigned char* p,
|
||||
const unsigned char* endp);
|
||||
|
||||
+int llhttp__on_chunk_parameters(
|
||||
+ llhttp__internal_t* s, const unsigned char* p,
|
||||
+ const unsigned char* endp);
|
||||
+
|
||||
int llhttp__on_status(
|
||||
llhttp__internal_t* s, const unsigned char* p,
|
||||
const unsigned char* endp);
|
||||
@@ -8185,8 +8238,7 @@ static llparse_state_t llhttp__internal_
|
||||
goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
}
|
||||
case 2: {
|
||||
- p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
|
||||
}
|
||||
default: {
|
||||
goto s_n_llhttp__internal__n_error_6;
|
||||
@@ -8195,6 +8247,34 @@ static llparse_state_t llhttp__internal_
|
||||
/* UNREACHABLE */;
|
||||
abort();
|
||||
}
|
||||
+ case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
|
||||
+ s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
|
||||
+ if (p == endp) {
|
||||
+ return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
|
||||
+ }
|
||||
+ state->_span_pos0 = (void*) p;
|
||||
+ state->_span_cb0 = llhttp__on_chunk_parameters;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
+ case s_n_llhttp__internal__n_chunk_parameters_ows:
|
||||
+ s_n_llhttp__internal__n_chunk_parameters_ows: {
|
||||
+ if (p == endp) {
|
||||
+ return s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
+ }
|
||||
+ switch (*p) {
|
||||
+ case ' ': {
|
||||
+ p++;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
+ }
|
||||
+ default: {
|
||||
+ goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
|
||||
+ }
|
||||
+ }
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
case s_n_llhttp__internal__n_chunk_size_otherwise:
|
||||
s_n_llhttp__internal__n_chunk_size_otherwise: {
|
||||
if (p == endp) {
|
||||
@@ -8205,13 +8285,9 @@ static llparse_state_t llhttp__internal_
|
||||
p++;
|
||||
goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
}
|
||||
- case ' ': {
|
||||
- p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
- }
|
||||
case ';': {
|
||||
p++;
|
||||
- goto s_n_llhttp__internal__n_chunk_parameters;
|
||||
+ goto s_n_llhttp__internal__n_chunk_parameters_ows;
|
||||
}
|
||||
default: {
|
||||
goto s_n_llhttp__internal__n_error_7;
|
||||
@@ -12312,6 +12388,24 @@ static llparse_state_t llhttp__internal_
|
||||
/* UNREACHABLE */;
|
||||
abort();
|
||||
}
|
||||
+ s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
|
||||
+ const unsigned char* start;
|
||||
+ int err;
|
||||
+
|
||||
+ start = state->_span_pos0;
|
||||
+ state->_span_pos0 = NULL;
|
||||
+ err = llhttp__on_chunk_parameters(state, start, p);
|
||||
+ if (err != 0) {
|
||||
+ state->error = err;
|
||||
+ state->error_pos = (const char*) (p + 1);
|
||||
+ state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ return s_error;
|
||||
+ }
|
||||
+ p++;
|
||||
+ goto s_n_llhttp__internal__n_chunk_size_almost_done;
|
||||
+ /* UNREACHABLE */;
|
||||
+ abort();
|
||||
+ }
|
||||
s_n_llhttp__internal__n_error_6: {
|
||||
state->error = 0x2;
|
||||
state->reason = "Invalid character in chunk parameters";
|
||||
Index: node-v14.21.3/doc/api/errors.md
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/doc/api/errors.md
|
||||
+++ node-v14.21.3/doc/api/errors.md
|
||||
@@ -2326,6 +2326,18 @@ malconfigured clients, if more than 8KB
|
||||
HTTP parsing will abort without a request or response object being created, and
|
||||
an `Error` with this code will be emitted.
|
||||
|
||||
+<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>
|
||||
+
|
||||
+### `HPE_CHUNK_EXTENSIONS_OVERFLOW`
|
||||
+
|
||||
+<!-- YAML
|
||||
+added: REPLACEME
|
||||
+-->
|
||||
+
|
||||
+Too much data was received for a chunk extensions. In order to protect against
|
||||
+malicious or malconfigured clients, if more than 16 KiB of data is received
|
||||
+then an `Error` with this code will be emitted.
|
||||
+
|
||||
<a id="HPE_UNEXPECTED_CONTENT_LENGTH"></a>
|
||||
### `HPE_UNEXPECTED_CONTENT_LENGTH`
|
||||
|
||||
Index: node-v14.21.3/test/parallel/test-http-chunk-extensions-limit.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v14.21.3/test/parallel/test-http-chunk-extensions-limit.js
|
||||
@@ -0,0 +1,131 @@
|
||||
+'use strict';
|
||||
+
|
||||
+const common = require('../common');
|
||||
+const http = require('http');
|
||||
+const net = require('net');
|
||||
+const assert = require('assert');
|
||||
+
|
||||
+// Verify that chunk extensions are limited in size when sent all together.
|
||||
+{
|
||||
+ const server = http.createServer((req, res) => {
|
||||
+ req.on('end', () => {
|
||||
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
+ res.end('bye');
|
||||
+ });
|
||||
+
|
||||
+ req.resume();
|
||||
+ });
|
||||
+
|
||||
+ server.listen(0, () => {
|
||||
+ const sock = net.connect(server.address().port);
|
||||
+ let data = '';
|
||||
+
|
||||
+ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
|
||||
+
|
||||
+ sock.on('end', common.mustCall(function() {
|
||||
+ assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
|
||||
+ server.close();
|
||||
+ }));
|
||||
+
|
||||
+ sock.end('' +
|
||||
+ 'GET / HTTP/1.1\r\n' +
|
||||
+ 'Host: localhost:8080\r\n' +
|
||||
+ 'Transfer-Encoding: chunked\r\n\r\n' +
|
||||
+ '2;' + 'A'.repeat(20000) + '=bar\r\nAA\r\n' +
|
||||
+ '0\r\n\r\n'
|
||||
+ );
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+// Verify that chunk extensions are limited in size when sent in intervals.
|
||||
+{
|
||||
+ const server = http.createServer((req, res) => {
|
||||
+ req.on('end', () => {
|
||||
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
+ res.end('bye');
|
||||
+ });
|
||||
+
|
||||
+ req.resume();
|
||||
+ });
|
||||
+
|
||||
+ server.listen(0, () => {
|
||||
+ const sock = net.connect(server.address().port);
|
||||
+ let remaining = 20000;
|
||||
+ let data = '';
|
||||
+
|
||||
+ const interval = setInterval(
|
||||
+ () => {
|
||||
+ if (remaining > 0) {
|
||||
+ sock.write('A'.repeat(1000));
|
||||
+ } else {
|
||||
+ sock.write('=bar\r\nAA\r\n0\r\n\r\n');
|
||||
+ clearInterval(interval);
|
||||
+ }
|
||||
+
|
||||
+ remaining -= 1000;
|
||||
+ },
|
||||
+ common.platformTimeout(20),
|
||||
+ ).unref();
|
||||
+
|
||||
+ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
|
||||
+
|
||||
+ sock.on('end', common.mustCall(function() {
|
||||
+ assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
|
||||
+ server.close();
|
||||
+ }));
|
||||
+
|
||||
+ sock.write('' +
|
||||
+ 'GET / HTTP/1.1\r\n' +
|
||||
+ 'Host: localhost:8080\r\n' +
|
||||
+ 'Transfer-Encoding: chunked\r\n\r\n' +
|
||||
+ '2;'
|
||||
+ );
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+// Verify the chunk extensions is correctly reset after a chunk
|
||||
+{
|
||||
+ const server = http.createServer((req, res) => {
|
||||
+ req.on('end', () => {
|
||||
+ res.writeHead(200, { 'content-type': 'text/plain', 'connection': 'close', 'date': 'now' });
|
||||
+ res.end('bye');
|
||||
+ });
|
||||
+
|
||||
+ req.resume();
|
||||
+ });
|
||||
+
|
||||
+ server.listen(0, () => {
|
||||
+ const sock = net.connect(server.address().port);
|
||||
+ let data = '';
|
||||
+
|
||||
+ sock.on('data', (chunk) => data += chunk.toString('utf-8'));
|
||||
+
|
||||
+ sock.on('end', common.mustCall(function() {
|
||||
+ assert.strictEqual(
|
||||
+ data,
|
||||
+ 'HTTP/1.1 200 OK\r\n' +
|
||||
+ 'content-type: text/plain\r\n' +
|
||||
+ 'connection: close\r\n' +
|
||||
+ 'date: now\r\n' +
|
||||
+ 'Transfer-Encoding: chunked\r\n' +
|
||||
+ '\r\n' +
|
||||
+ '3\r\n' +
|
||||
+ 'bye\r\n' +
|
||||
+ '0\r\n' +
|
||||
+ '\r\n',
|
||||
+ );
|
||||
+
|
||||
+ server.close();
|
||||
+ }));
|
||||
+
|
||||
+ sock.end('' +
|
||||
+ 'GET / HTTP/1.1\r\n' +
|
||||
+ 'Host: localhost:8080\r\n' +
|
||||
+ 'Transfer-Encoding: chunked\r\n\r\n' +
|
||||
+ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
|
||||
+ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
|
||||
+ '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
|
||||
+ '0\r\n\r\n'
|
||||
+ );
|
||||
+ });
|
||||
+}
|
||||
Index: node-v14.21.3/lib/_http_server.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/lib/_http_server.js
|
||||
+++ node-v14.21.3/lib/_http_server.js
|
||||
@@ -636,6 +636,11 @@ const requestHeaderFieldsTooLargeRespons
|
||||
`HTTP/1.1 431 ${STATUS_CODES[431]}${CRLF}` +
|
||||
`Connection: close${CRLF}${CRLF}`, 'ascii'
|
||||
);
|
||||
+const requestChunkExtensionsTooLargeResponse = Buffer.from(
|
||||
+ `HTTP/1.1 413 ${STATUS_CODES[413]}\r\n` +
|
||||
+ 'Connection: close\r\n\r\n', 'ascii',
|
||||
+);
|
||||
+
|
||||
function socketOnError(e) {
|
||||
// Ignore further errors
|
||||
this.removeListener('error', socketOnError);
|
||||
@@ -649,6 +654,9 @@ function socketOnError(e) {
|
||||
case 'HPE_HEADER_OVERFLOW':
|
||||
response = requestHeaderFieldsTooLargeResponse;
|
||||
break;
|
||||
+ case 'HPE_CHUNK_EXTENSIONS_OVERFLOW':
|
||||
+ response = requestChunkExtensionsTooLargeResponse;
|
||||
+ break;
|
||||
case 'ERR_HTTP_REQUEST_TIMEOUT':
|
||||
response = requestTimeoutResponse;
|
||||
break;
|
||||
Index: node-v14.21.3/src/node_http_parser.cc
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/src/node_http_parser.cc
|
||||
+++ node-v14.21.3/src/node_http_parser.cc
|
||||
@@ -78,6 +78,8 @@ const uint32_t kOnExecute = 5;
|
||||
const uint32_t kOnTimeout = 6;
|
||||
// Any more fields than this will be flushed into JS
|
||||
const size_t kMaxHeaderFieldsCount = 32;
|
||||
+// Maximum size of chunk extensions
|
||||
+const size_t kMaxChunkExtensionsSize = 16384;
|
||||
|
||||
inline bool IsOWS(char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
@@ -202,6 +204,7 @@ class Parser : public AsyncWrap, public
|
||||
|
||||
int on_message_begin() {
|
||||
num_fields_ = num_values_ = 0;
|
||||
+ chunk_extensions_nread_ = 0;
|
||||
url_.Reset();
|
||||
status_message_.Reset();
|
||||
header_parsing_start_time_ = uv_hrtime();
|
||||
@@ -451,9 +454,22 @@ class Parser : public AsyncWrap, public
|
||||
return 0;
|
||||
}
|
||||
|
||||
- // Reset nread for the next chunk
|
||||
+ int on_chunk_extension(const char* at, size_t length) {
|
||||
+ chunk_extensions_nread_ += length;
|
||||
+
|
||||
+ if (chunk_extensions_nread_ > kMaxChunkExtensionsSize) {
|
||||
+ llhttp_set_error_reason(&parser_,
|
||||
+ "HPE_CHUNK_EXTENSIONS_OVERFLOW:Chunk extensions overflow");
|
||||
+ return HPE_USER;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ // Reset nread for the next chunk and also reset the extensions counter
|
||||
int on_chunk_header() {
|
||||
header_nread_ = 0;
|
||||
+ chunk_extensions_nread_ = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -904,6 +920,7 @@ class Parser : public AsyncWrap, public
|
||||
unsigned int execute_depth_ = 0;
|
||||
bool pending_pause_ = false;
|
||||
uint64_t header_nread_ = 0;
|
||||
+ uint64_t chunk_extensions_nread_ = 0;
|
||||
uint64_t max_http_header_size_;
|
||||
uint64_t headers_timeout_;
|
||||
uint64_t header_parsing_start_time_ = 0;
|
||||
@@ -938,6 +955,7 @@ const llhttp_settings_t Parser::settings
|
||||
Proxy<DataCall, &Parser::on_header_field>::Raw,
|
||||
Proxy<DataCall, &Parser::on_header_value>::Raw,
|
||||
Proxy<Call, &Parser::on_headers_complete>::Raw,
|
||||
+ Proxy<DataCall, &Parser::on_chunk_extension>::Raw,
|
||||
Proxy<DataCall, &Parser::on_body>::Raw,
|
||||
Proxy<Call, &Parser::on_message_complete>::Raw,
|
||||
Proxy<Call, &Parser::on_chunk_header>::Raw,
|
125
CVE-2024-22025.patch
Normal file
125
CVE-2024-22025.patch
Normal file
File diff suppressed because one or more lines are too long
26
CVE-2024-24806.patch
Normal file
26
CVE-2024-24806.patch
Normal file
@@ -0,0 +1,26 @@
|
||||
Index: node-v16.20.2/deps/uv/src/idna.c
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/uv/src/idna.c
|
||||
+++ node-v16.20.2/deps/uv/src/idna.c
|
||||
@@ -273,6 +273,9 @@ long uv__idna_toascii(const char* s, con
|
||||
char* ds;
|
||||
int rc;
|
||||
|
||||
+ if (s == se)
|
||||
+ return UV_EINVAL;
|
||||
+
|
||||
ds = d;
|
||||
|
||||
si = s;
|
||||
@@ -307,8 +310,9 @@ long uv__idna_toascii(const char* s, con
|
||||
return rc;
|
||||
}
|
||||
|
||||
- if (d < de)
|
||||
- *d++ = '\0';
|
||||
+ if (d >= de)
|
||||
+ return UV_EINVAL;
|
||||
|
||||
+ *d++ = '\0';
|
||||
return d - ds; /* Number of bytes written. */
|
||||
}
|
3005
CVE-2024-27982.patch
Normal file
3005
CVE-2024-27982.patch
Normal file
File diff suppressed because it is too large
Load Diff
33
CVE-2024-27983.patch
Normal file
33
CVE-2024-27983.patch
Normal file
@@ -0,0 +1,33 @@
|
||||
from:
|
||||
|
||||
https://github.com/nodejs/node/commit/0fb816dbcc
|
||||
|
||||
src: ensure to close stream when destroying session
|
||||
|
||||
Co-Authored-By: Anna Henningsen <anna@addaleax.net>
|
||||
PR-URL: nodejs-private/node-private#561
|
||||
Fixes: https://hackerone.com/reports/2319584
|
||||
Reviewed-By: Michael Dawson <midawson@redhat.com>
|
||||
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
|
||||
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
|
||||
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
|
||||
CVE-ID: CVE-2024-27983
|
||||
|
||||
|
||||
Index: node-v16.20.2/src/node_http2.cc
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/src/node_http2.cc
|
||||
+++ node-v16.20.2/src/node_http2.cc
|
||||
@@ -529,6 +529,12 @@ Http2Session::Http2Session(Http2State* h
|
||||
Http2Session::~Http2Session() {
|
||||
CHECK(!is_in_scope());
|
||||
Debug(this, "freeing nghttp2 session");
|
||||
+ // Ensure that all `Http2Stream` instances and the memory they hold
|
||||
+ // on to are destroyed before the nghttp2 session is.
|
||||
+ for (const auto& [id, stream] : streams_) {
|
||||
+ stream->Detach();
|
||||
+ }
|
||||
+ streams_.clear();
|
||||
// Explicitly reset session_ so the subsequent
|
||||
// current_nghttp2_memory_ check passes.
|
||||
session_.reset();
|
31
newicu_test_fixup.patch
Normal file
31
newicu_test_fixup.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
commit 3b73aa416f5903d2464d0cbed0377889a23de5cb
|
||||
Author: Michaël Zasso <targos@protonmail.com>
|
||||
Date: Tue Oct 25 16:13:17 2022 +0200
|
||||
|
||||
deps: update ICU to 72.1
|
||||
|
||||
Refs: https://github.com/unicode-org/icu/releases/tag/release-72-1
|
||||
PR-URL: https://github.com/nodejs/node/pull/45068
|
||||
Reviewed-By: Richard Lau <rlau@redhat.com>
|
||||
Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
|
||||
Reviewed-By: Steven R Loomis <srloomis@us.ibm.com>
|
||||
Reviewed-By: James M Snell <jasnell@gmail.com>
|
||||
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
|
||||
|
||||
Index: node-v14.21.3/test/parallel/test-intl.js
|
||||
===================================================================
|
||||
--- node-v14.21.3.orig/test/parallel/test-intl.js
|
||||
+++ node-v14.21.3/test/parallel/test-intl.js
|
||||
@@ -97,7 +97,11 @@ if (!common.hasIntl) {
|
||||
// Test format
|
||||
{
|
||||
const localeString = date0.toLocaleString(['en'], optsGMT);
|
||||
- assert.strictEqual(localeString, '1/1/1970, 12:00:00 AM');
|
||||
+ if (Number(process.versions.cldr) >= 42) {
|
||||
+ assert.strictEqual(localeString, '1/1/1970, 12:00:00 AM');
|
||||
+ } else {
|
||||
+ assert.strictEqual(localeString, '1/1/1970, 12:00:00 AM');
|
||||
+ }
|
||||
}
|
||||
// number format
|
||||
{
|
BIN
nodejs.keyring
BIN
nodejs.keyring
Binary file not shown.
@@ -1,3 +1,71 @@
|
||||
-------------------------------------------------------------------
|
||||
Tue Oct 29 13:13:51 UTC 2024 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
- openssl31.patch: fix unit tests with OpenSSL 3.1 (bsc#1232756)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Thu Apr 11 10:51:31 UTC 2024 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
- CVE-2024-27983.patch - Assertion failed in
|
||||
node::http2::Http2Session::~Http2Session() leads to
|
||||
HTTP/2 server crash- (High) (bsc#1222244, CVE-2024-27983)
|
||||
- CVE-2024-27982.patch - HTTP Request Smuggling via Content Length
|
||||
Obfuscation- (Medium) (bsc#1222384, CVE-2024-27982)
|
||||
- updated dependencies:
|
||||
+ llhttp version 6.1.1
|
||||
|
||||
- CVE-2024-22025.patch - test timeout adjustment
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Feb 20 09:52:34 UTC 2024 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
* CVE-2023-46809.patch: Node.js is vulnerable to the Marvin Attack
|
||||
(timing variant of the Bleichenbacher attack against
|
||||
PKCS#1 v1.5 padding) - (Medium) (CVE-2023-46809, bsc#1219997)
|
||||
* CVE-2024-22019.patch: http: Reading unprocessed HTTP request with
|
||||
unbounded chunk extension allows DoS attacks- (High)
|
||||
(CVE-2024-22019, bsc#1219993)
|
||||
* CVE-2024-22025.patch: fix Denial of Service by resource exhaustion
|
||||
in fetch() brotli decoding (CVE-2024-22025, bsc#1220014)
|
||||
* CVE-2024-24806.patch: fix improper domain lookup that
|
||||
potentially leads to SSRF attacks (CVE-2024-24806, bsc#1220053)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Oct 24 15:22:09 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
- CVE-2023-38552.patch: Integrity checks according to policies
|
||||
can be circumvented (CVE-2023-38552, bsc#1216272)
|
||||
- CVE-2023-44487.patch: nghttp2 Security Release (CVE-2023-44487, bsc#1216190)
|
||||
- nodejs.keyring: include new releaser keys
|
||||
- newicu_test_fixup.patch: workaround whitespaces funnies in
|
||||
some icu versions
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Fri Aug 11 12:08:00 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
* CVE-2023-32002.patch:
|
||||
+ fixes policies can be bypassed via Module._load
|
||||
+ fixes policies can be bypassed by module.constructor.createRequire
|
||||
(CVE-2023-32002, CVE-2023-32006, bsc#1214150, bsc#1214156)
|
||||
* CVE-2023-32559.patch: Policies can be bypassed via
|
||||
process.binding (CVE-2023-32559, bsc#1214154)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Fri Aug 4 14:51:10 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
- CVE-2023-30581.patch: fixes mainModule.__proto__ Bypass
|
||||
Experimental Policy Mechanism (CVE-2023-30581, bsc#1212574)
|
||||
- CVE-2023-30589.patch: HTTP Request Smuggling via empty headers
|
||||
separated by CR (CVE-2023-30589, bsc#1212582)
|
||||
- CVE-2023-30590.patch: DiffieHellman does not generate keys
|
||||
after setting a private key (CVE-2023-30590, bsc#1212583)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Thu Apr 13 14:24:48 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
- CVE-2022-25881.patch: http-cache-semantics(npm): Don't use regex
|
||||
to trim whitespace (bsc#1208744, CVE-2022-25881)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Feb 21 16:36:46 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
|
162
nodejs14.spec
162
nodejs14.spec
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file for package nodejs14
|
||||
#
|
||||
# Copyright (c) 2022 SUSE LLC
|
||||
# Copyright (c) 2024 SUSE LLC
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
@@ -49,6 +49,11 @@ Release: 0
|
||||
%bcond_with libalternatives
|
||||
%endif
|
||||
|
||||
# nodejs20+ is not for SLE12
|
||||
%if %{node_version_number} >= 20 && 0%{?suse_version} > 0 && 0%{?suse_version} < 1500
|
||||
ExclusiveArch do_not_build
|
||||
%endif
|
||||
|
||||
%if %node_version_number >= 12
|
||||
%define openssl_req_ver 1.1.1
|
||||
%else
|
||||
@@ -142,6 +147,21 @@ Patch13: openssl_binary_detection.patch
|
||||
|
||||
|
||||
Patch53: CVE-2022-0235.patch
|
||||
Patch61: CVE-2022-25881.patch
|
||||
Patch63: CVE-2023-30581.patch
|
||||
Patch64: CVE-2023-30589.patch
|
||||
Patch65: CVE-2023-30590.patch
|
||||
Patch66: CVE-2023-32559.patch
|
||||
Patch67: CVE-2023-32002.patch
|
||||
Patch68: CVE-2023-44487.patch
|
||||
Patch69: CVE-2023-38552.patch
|
||||
Patch72: CVE-2024-24806.patch
|
||||
Patch74: CVE-2024-22025.patch
|
||||
Patch75: CVE-2024-22019.patch
|
||||
Patch76: CVE-2023-46809.patch
|
||||
Patch77: CVE-2024-27983.patch
|
||||
Patch78: CVE-2024-27982.patch
|
||||
Patch80: openssl31.patch
|
||||
|
||||
## Patches specific to SUSE and openSUSE
|
||||
Patch100: linker_lto_jobs.patch
|
||||
@@ -164,6 +184,7 @@ Patch132: test-skip-y2038-on-32bit-time_t.patch
|
||||
# Use versioned binaries and paths
|
||||
Patch200: versioned.patch
|
||||
|
||||
Patch310: newicu_test_fixup.patch
|
||||
|
||||
BuildRequires: pkg-config
|
||||
BuildRequires: fdupes
|
||||
@@ -262,14 +283,6 @@ BuildRequires: group(nobody)
|
||||
|
||||
BuildRequires: pkgconfig(openssl) >= %{openssl_req_ver}
|
||||
|
||||
# require patched openssl library on SLES for nodejs16
|
||||
%if 0%{?suse_version} && "%{pkg_version openssl-1_1}" != "~~~"
|
||||
%if %node_version_number >= 16 && 0%{suse_version} <= 1500 && %{pkg_vcmp openssl-1_1 < '1.1.1e' } && 0%{with openssl_RSA_get0_pss_params}
|
||||
BuildRequires: openssl-has-RSA_get0_pss_params
|
||||
Requires: openssl-has-RSA_get0_pss_params
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version}
|
||||
|
||||
%if 0%{?suse_version} >= 1500
|
||||
@@ -306,6 +319,10 @@ BuildRequires: pkgconfig(libcares) >= 1.17.0
|
||||
Provides: bundled(libcares2) = 1.18.1
|
||||
%endif
|
||||
|
||||
%if %node_version_number >= 22
|
||||
BuildRequires: sqlite3-devel
|
||||
%endif
|
||||
|
||||
%if ! 0%{with intree_icu}
|
||||
BuildRequires: pkgconfig(icu-i18n) >= 65
|
||||
%else
|
||||
@@ -323,7 +340,7 @@ BuildRequires: valgrind
|
||||
%endif
|
||||
|
||||
%if %{with libalternatives}
|
||||
Requires: alts
|
||||
Suggests: alts
|
||||
%else
|
||||
Requires(postun): %{_sbindir}/update-alternatives
|
||||
%endif
|
||||
@@ -379,6 +396,9 @@ Provides: bundled(llhttp) = 2.1.6
|
||||
|
||||
|
||||
|
||||
# bundled url-ada parser, not ada
|
||||
|
||||
|
||||
Provides: bundled(node-acorn) = 8.4.1
|
||||
Provides: bundled(node-acorn-walk) = 8.1.0
|
||||
Provides: bundled(node-cjs-module-lexer) = 1.2.2
|
||||
@@ -848,11 +868,16 @@ echo "`grep node-v%{version}.tar.xz %{S:1} | head -n1 | cut -c1-64` %{S:0}" | s
|
||||
%setup -q -n node-%{version}
|
||||
%endif
|
||||
|
||||
%if %{node_version_number} == 16
|
||||
tar zxf %{S:12}
|
||||
%endif
|
||||
|
||||
%if %{node_version_number} <= 10
|
||||
rm -r deps/npm/*
|
||||
pushd deps/npm
|
||||
tar zxf %{SOURCE9} --strip-components=1
|
||||
tar Jxf %{SOURCE90}
|
||||
popd
|
||||
%endif
|
||||
|
||||
%if %{node_version_number} >= 10
|
||||
@@ -860,7 +885,7 @@ tar Jxf %{SOURCE11}
|
||||
%endif
|
||||
|
||||
# downgrade node-gyp to last version that supports python 3.4 for SLE12
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500 && %{node_version_number} >= 16
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500 && %{node_version_number} >= 16 && %{node_version_number} < 22
|
||||
rm -r deps/npm/node_modules/node-gyp
|
||||
mkdir deps/npm/node_modules/node-gyp
|
||||
pushd deps/npm/node_modules/node-gyp
|
||||
@@ -868,35 +893,56 @@ tar Jxf %{SOURCE5}
|
||||
popd
|
||||
|
||||
%if %{node_version_number} >= 19
|
||||
%else
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%patch1 -p1
|
||||
%patch3 -p1
|
||||
%if %{node_version_number} <= 12 && 0%{?suse_version} < 1500
|
||||
%patch5 -p1
|
||||
%patch -P 1 -p1
|
||||
%patch -P 3 -p1
|
||||
%if 0%{?suse_version} < 1500
|
||||
%endif
|
||||
%patch7 -p1
|
||||
%if %{node_version_number} <= 12 && 0%{?suse_version} < 1500
|
||||
%patch -P 5 -p1
|
||||
%endif
|
||||
%patch -P 7 -p1
|
||||
%if 0%{with valgrind_tests}
|
||||
%endif
|
||||
%patch13 -p1
|
||||
%patch53 -p1
|
||||
%patch100 -p1
|
||||
%patch101 -p1
|
||||
%patch102 -p1
|
||||
%patch -P 13 -p1
|
||||
%patch -P 53 -p1
|
||||
%patch -P 61 -p1
|
||||
%patch -P 63 -p1
|
||||
%patch -P 64 -p1
|
||||
%patch -P 65 -p1
|
||||
%patch -P 66 -p1
|
||||
%patch -P 67 -p1
|
||||
%patch -P 68 -p1
|
||||
%patch -P 69 -p1
|
||||
%patch -P 72 -p1
|
||||
%patch -P 74 -p1
|
||||
%patch -P 75 -p1
|
||||
%patch -P 76 -p1
|
||||
%patch -P 77 -p1
|
||||
%patch -P 78 -p1
|
||||
%patch -P 80 -p1
|
||||
%patch -P 100 -p1
|
||||
%patch -P 101 -p1
|
||||
%if 0%{?suse_version} >= 1500 || 0%{?suse_version} == 0
|
||||
%patch -P 102 -p1
|
||||
%endif
|
||||
# Add check_output to configure script (not part of Python 2.6 in SLE11).
|
||||
%if 0%{?suse_version} == 1110
|
||||
%endif
|
||||
%patch104 -p1
|
||||
%patch106 -p1
|
||||
%patch120 -p1
|
||||
%patch132 -p1
|
||||
%patch -P 104 -p1
|
||||
%patch -P 106 -p1
|
||||
%patch -P 120 -p1
|
||||
%patch -P 132 -p1
|
||||
%if ! 0%{with openssl_RSA_get0_pss_params}
|
||||
%endif
|
||||
%patch200 -p1
|
||||
%patch -P 200 -p1
|
||||
|
||||
%patch -P 310 -p1
|
||||
|
||||
%if %{node_version_number} <= 12
|
||||
%if %{node_version_number} == 12
|
||||
# minimist security update - patch50
|
||||
rm -r deps/npm/node_modules/mkdirp/node_modules/minimist
|
||||
rmdir ./deps/npm/node_modules/mkdirp/node_modules
|
||||
@@ -966,9 +1012,6 @@ EOF
|
||||
|
||||
. ./spec.build.config
|
||||
|
||||
# Node.js 4.x does not include the ICU database in the source tarball.
|
||||
%define has_small_icu %(test -d "deps/icu-small" && echo 1 || echo 0)
|
||||
|
||||
./configure \
|
||||
--prefix=%{_prefix} \
|
||||
%if 0%{?with nodejs_lto}
|
||||
@@ -983,11 +1026,6 @@ EOF
|
||||
%endif
|
||||
%if ! 0%{with intree_icu}
|
||||
--with-intl=system-icu \
|
||||
%else
|
||||
%if %{has_small_icu}
|
||||
--with-intl=small-icu \
|
||||
--with-icu-source=deps/icu-small \
|
||||
%endif
|
||||
%endif
|
||||
%if ! 0%{with intree_nghttp2}
|
||||
--shared-nghttp2 \
|
||||
@@ -1001,6 +1039,9 @@ EOF
|
||||
%if %{node_version_number} < 19
|
||||
--without-dtrace \
|
||||
%endif
|
||||
%if %{node_version_number} >= 22
|
||||
--shared-sqlite \
|
||||
%endif
|
||||
%if %{node_version_number} >= 16 && (0%{?suse_version} > 1550 || 0%{?sle_version} >= 150400)
|
||||
--openssl-default-cipher-list=PROFILE=SYSTEM \
|
||||
%endif
|
||||
@@ -1081,6 +1122,12 @@ ln -s -f npx-default %{buildroot}%{_sysconfdir}/alternatives/npx-default
|
||||
ln -s -f npx.1%{ext_man} %{buildroot}%{_sysconfdir}/alternatives/npx.1%{ext_man}
|
||||
ln -s %{_sysconfdir}/alternatives/npx-default %{buildroot}%{_bindir}/npx-default
|
||||
ln -s %{_sysconfdir}/alternatives/npx.1%{ext_man} %{buildroot}%{_mandir}/man1/npx.1%{ext_man}
|
||||
%if %{node_version_number} >= 14
|
||||
ln -s -f corepack-default %{buildroot}%{_sysconfdir}/alternatives/corepack-default
|
||||
ln -s -f corepack.1%{ext_man} %{buildroot}%{_sysconfdir}/alternatives/corepack.1%{ext_man}
|
||||
ln -s %{_sysconfdir}/alternatives/corepack-default %{buildroot}%{_bindir}/corepack-default
|
||||
ln -s %{_sysconfdir}/alternatives/corepack.1%{ext_man} %{buildroot}%{_mandir}/man1/corepack.1%{ext_man}
|
||||
%endif
|
||||
%endif
|
||||
|
||||
# libalternatives - can always ship
|
||||
@@ -1099,6 +1146,13 @@ binary=%{_bindir}/npx%{node_version_number}
|
||||
man=npx%{node_version_number}.1
|
||||
group=npm,npx
|
||||
EOF
|
||||
%if %{node_version_number} >= 14
|
||||
mkdir -p %{buildroot}%{_datadir}/libalternatives/corepack;
|
||||
cat > %{buildroot}%{_datadir}/libalternatives/corepack/%{node_version_number}.conf <<EOF
|
||||
binary=%{_bindir}/corepack%{node_version_number}
|
||||
man=corepack%{node_version_number}.1
|
||||
EOF
|
||||
%endif
|
||||
|
||||
# We need to own license directory on old versions of SLE
|
||||
%if 0%{?suse_version} < 1500
|
||||
@@ -1119,10 +1173,15 @@ export NODE_TEST_NO_INTERNET=1
|
||||
find test \( -name \*.out -or -name \*.js \) -exec sed -i 's,Use `node ,Use `node%{node_version_number} ,' {} \;
|
||||
%endif
|
||||
|
||||
%if %{node_version_number} >= 20
|
||||
rm test/parallel/test-strace-openat-openssl.js
|
||||
%endif
|
||||
|
||||
# Update the python3 executable name to point at forced python version
|
||||
# needed to fix build on SLE12 SP5
|
||||
%if 0%{?forced_python_version:1}
|
||||
sed -i -e "s,'python3','python%{forced_python_version}'," test/parallel/test-child-process-set-blocking.js
|
||||
test -e tools/pseudo-tty.py && sed -i -e "s,^#!/usr/bin/env python3$,#!/usr/bin/python%{forced_python_version}," tools/pseudo-tty.py ||:
|
||||
%endif
|
||||
|
||||
ln addon-rpm.gypi deps/npm/node_modules/node-gyp/addon-rpm.gypi
|
||||
@@ -1188,7 +1247,7 @@ make test-ci
|
||||
%defattr(-, root, root)
|
||||
%license LICENSE
|
||||
%doc doc/changelogs/CHANGELOG_V%{node_version_number}.md
|
||||
%doc AUTHORS *.md
|
||||
%doc *.md
|
||||
%doc deps/v8/tools/gdbinit
|
||||
%dir %{_libdir}/node_modules
|
||||
%dir %{_datadir}/libalternatives
|
||||
@@ -1239,6 +1298,14 @@ make test-ci
|
||||
%defattr(-, root, root)
|
||||
%{_bindir}/corepack%{node_version_number}
|
||||
%{_libdir}/node_modules/corepack%{node_version_number}
|
||||
%dir %{_datadir}/libalternatives/corepack
|
||||
%{_datadir}/libalternatives/corepack/%{node_version_number}.conf
|
||||
%if ! %{with libalternatives}
|
||||
%ghost %{_bindir}/corepack-default
|
||||
%ghost %{_mandir}/man1/corepack.1%{ext_man}
|
||||
%ghost %{_sysconfdir}/alternatives/corepack-default
|
||||
%ghost %{_sysconfdir}/alternatives/corepack.1%{ext_man}
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%files devel
|
||||
@@ -1263,6 +1330,11 @@ update-alternatives --remove node-default %{_bindir}/node%{node_version_number}
|
||||
update-alternatives --remove npm-default %{_bindir}/npm%{node_version_number}
|
||||
update-alternatives --remove npx-default %{_bindir}/npx%{node_version_number}
|
||||
|
||||
%if %{node_version_number} >= 14
|
||||
%post -n corepack%{node_version_number}
|
||||
update-alternatives --remove corepack-default %{_bindir}/corepack%{node_version_number}
|
||||
%endif
|
||||
|
||||
%else
|
||||
%pre
|
||||
# remove files that are no longer owned but provided by update-alternatives
|
||||
@@ -1302,6 +1374,24 @@ if [ ! -f %{_bindir}/npx%{node_version_number} ] ; then
|
||||
update-alternatives --remove npx-default %{_bindir}/npx%{node_version_number}
|
||||
fi
|
||||
|
||||
%if %{node_version_number} >= 14
|
||||
%pre -n corepack%{node_version_number}
|
||||
# remove files that are no longer owned but provided by update-alternatives
|
||||
if ! [ -L %{_mandir}/man1/corepack.1%{ext_man} ]; then
|
||||
rm -f %{_mandir}/man1/corepack.1%{ext_man}
|
||||
fi
|
||||
|
||||
%post -n corepack%{node_version_number}
|
||||
update-alternatives \
|
||||
--install %{_bindir}/corepack-default corepack-default %{_bindir}/corepack%{node_version_number} %{node_version_number} \
|
||||
--slave %{_mandir}/man1/corepack.1%{ext_man} corepack.1%{ext_man} %{_mandir}/man1/corepack%{node_version_number}.1%{ext_man}
|
||||
|
||||
%postun -n corepack%{node_version_number}
|
||||
if [ ! -f %{_bindir}/corepack%{node_version_number} ] ; then
|
||||
update-alternatives --remove corepack-default %{_bindir}/corepack%{node_version_number}
|
||||
fi
|
||||
%endif
|
||||
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
|
90
openssl31.patch
Normal file
90
openssl31.patch
Normal file
@@ -0,0 +1,90 @@
|
||||
Index: node-v16.20.2/test/parallel/test-crypto-rsa-dsa.js
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/test/parallel/test-crypto-rsa-dsa.js
|
||||
+++ node-v16.20.2/test/parallel/test-crypto-rsa-dsa.js
|
||||
@@ -223,20 +223,71 @@ function test_rsa(padding, encryptOaepHa
|
||||
|
||||
|
||||
if (padding === constants.RSA_PKCS1_PADDING) {
|
||||
- assert.throws(() => {
|
||||
- crypto.privateDecrypt({
|
||||
- key: rsaKeyPem,
|
||||
- padding: padding,
|
||||
- oaepHash: decryptOaepHash
|
||||
- }, encryptedBuffer);
|
||||
- }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
- assert.throws(() => {
|
||||
- crypto.privateDecrypt({
|
||||
- key: rsaPkcs8KeyPem,
|
||||
- padding: padding,
|
||||
- oaepHash: decryptOaepHash
|
||||
- }, encryptedBuffer);
|
||||
- }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ if (!process.config.variables.node_shared_openssl) {
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ } else {
|
||||
+ // The version of a linked against OpenSSL. May
|
||||
+ // or may not support implicit rejection. Figuring
|
||||
+ // this out in the test is not feasible but we
|
||||
+ // require that it pass based on one of the two
|
||||
+ // cases of supporting it or not.
|
||||
+ try {
|
||||
+ // The expected exceptions should be thrown if implicit rejection
|
||||
+ // is not supported
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ assert.throws(() => {
|
||||
+ crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ }, { code: 'ERR_INVALID_ARG_VALUE' });
|
||||
+ } catch (e) {
|
||||
+ if (e.toString() ===
|
||||
+ 'AssertionError [ERR_ASSERTION]: Missing expected exception.') {
|
||||
+ // Implicit rejection must be supported since
|
||||
+ // we did not get the exceptions that are thrown
|
||||
+ // when it is not, we should be able to decrypt
|
||||
+ let decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaKeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+
|
||||
+ decryptedBuffer = crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ padding: padding,
|
||||
+ oaepHash: decryptOaepHash
|
||||
+ }, encryptedBuffer);
|
||||
+ assert.deepStrictEqual(decryptedBuffer, input);
|
||||
+ } else {
|
||||
+ // There was an exception but it is not the one we expect if implicit
|
||||
+ // rejection is not supported so there was some other failure,
|
||||
+ // re-throw it so the test fails
|
||||
+ throw e;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
} else {
|
||||
let decryptedBuffer = crypto.privateDecrypt({
|
||||
key: rsaKeyPem,
|
Reference in New Issue
Block a user