Compare commits

...

10 Commits

Author SHA256 Message Date
41b5165b76 - openssl31.patch: fix unit tests with OpenSSL 3.1 (bsc#1232756)
OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=114
2024-11-04 14:52:50 +00:00
14afe8561b - openssl31.patch: fix unit tests with OpenSSL 3.1
OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=113
2024-10-29 13:16:26 +00:00
26e986c7a5 - 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

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=112
2024-04-12 14:11:00 +00:00
1d51fd3bc7 * 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)

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=111
2024-02-22 12:05:45 +00:00
0091714e09 OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=110 2023-10-25 07:45:10 +00:00
d3dec2361e - 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

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=109
2023-10-24 15:44:19 +00:00
9061f7712c * 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)

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=108
2023-08-11 13:04:35 +00:00
9b56d5b8bf - 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)

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=107
2023-08-04 16:06:06 +00:00
a10c843bae OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=106 2023-04-13 14:32:38 +00:00
3a6764d3d4 - CVE-2022-25881.patch: http-cache-semantics(npm): Don't use regex
to trim whitespace (bsc#1208744, CVE-2022-25881)

OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs14?expand=0&rev=105
2023-04-13 14:30:44 +00:00
20 changed files with 8700 additions and 36 deletions

34
CVE-2022-25881.patch Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

165
CVE-2023-30590.patch Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

26
CVE-2024-24806.patch Normal file
View 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

File diff suppressed because it is too large Load Diff

33
CVE-2024-27983.patch Normal file
View 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
View 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:00AM');
+ } else {
+ assert.strictEqual(localeString, '1/1/1970, 12:00:00 AM');
+ }
}
// number format
{

Binary file not shown.

View File

@@ -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>

View File

@@ -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
View 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,