Fix CVE-2023-46809,CVE-2024-22019,CVE-2024-22025,CVE-2024-27982 and CVE-2024-27983

(cherry picked from commit e7f6c2348b691a703920fea16a74541b2a4f3fb0)
This commit is contained in:
starlet-dx 2024-09-19 15:08:19 +08:00 committed by openeuler-sync-bot
parent a10a1f78cb
commit e0ba077bad
6 changed files with 4408 additions and 1 deletions

615
CVE-2023-46809.patch Normal file
View File

@ -0,0 +1,615 @@
From: Michael Dawson <midawson@redhat.com>
Date: Thu, 4 Jan 2024 21:32:51 +0000
Subject: CVE-2023-46809 crypto: disable PKCS#1 padding for privateDecrypt
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=2269177
Disable RSA_PKCS1_PADDING for crypto.privateDecrypt() in order
to protect against the Marvin attack.
Includes a security revert flag that can be used to restore
support.
Signed-off-by: Michael Dawson <midawson@redhat.com>
bug-github-pull: https://github.com/nodejs-private/node-private/pull/525
bug-hakerone: https://hackerone.com/bugs?subject=nodejs&report_id=2269177
bug: https://nodejs.org/en/blog/vulnerability/february-2024-security-releases/#nodejs-is-vulnerable-to-the-marvin-attack-timing-variant-of-the-bleichenbacher-attack-against-pkcs1
origin: backport, https://github.com/nodejs/node/commit/d3d357ab096884f10f5d2f164149727eea875635
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
CVE-ID: CVE-2023-46809
---
src/node_crypto.cc | 28 ++
src/node_revert.h | 4 +-
test/parallel/test-crypto-rsa-dsa-revert.js | 466 ++++++++++++++++++++++++++++
test/parallel/test-crypto-rsa-dsa.js | 42 ++-
4 files changed, 525 insertions(+), 15 deletions(-)
create mode 100644 test/parallel/test-crypto-rsa-dsa-revert.js
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 6c2a5de..91d52c5 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -37,6 +37,7 @@
#include "string_bytes.h"
#include "threadpoolwork-inl.h"
#include "util-inl.h"
+#include "node_revert.h"
#include "v8.h"
#include <openssl/ec.h>
@@ -5111,6 +5112,33 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
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;
diff --git a/src/node_revert.h b/src/node_revert.h
index b95eb0d..c636720 100644
--- a/src/node_revert.h
+++ b/src/node_revert.h
@@ -22,9 +22,7 @@ namespace node {
XX(CVE_2019_9518, "CVE-2019-9518", "HTTP/2 Empty DATA Frame Flooding") \
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")
- // TODO(addaleax): Remove all of the above before Node.js 13 as the comment
- // at the start of the file indicates.
+ XX(CVE_2023_46809, "CVE-2023-46809", "Marvin attack on PKCS#1 padding")
enum reversion {
#define V(code, ...) SECURITY_REVERT_##code,
diff --git a/test/parallel/test-crypto-rsa-dsa-revert.js b/test/parallel/test-crypto-rsa-dsa-revert.js
new file mode 100644
index 0000000..d40c66f
--- /dev/null
+++ b/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);
+}
diff --git a/test/parallel/test-crypto-rsa-dsa.js b/test/parallel/test-crypto-rsa-dsa.js
index 9b8c3f6..b45bfeb 100644
--- a/test/parallel/test-crypto-rsa-dsa.js
+++ b/test/parallel/test-crypto-rsa-dsa.js
@@ -169,19 +169,37 @@ function test_rsa(padding, encryptOaepHash, decryptOaepHash) {
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');

565
CVE-2024-22019.patch Normal file
View File

@ -0,0 +1,565 @@
From: Paolo Insogna <paolo@cowtech.it>
Date: Tue, 9 Jan 2024 18:10:04 +0100
Subject: CVE-2024-22019: http: add maximum chunk extension size
PR-URL: https://github.com/nodejs-private/node-private/pull/520
Refs: https://github.com/nodejs-private/node-private/pull/518
CVE-ID: CVE-2024-22019
origin: backport, https://github.com/nodejs/node/commit/911cb33cdadab57a75f97186290ea8f3903a6171.patch
bug: https://nodejs.org/en/blog/vulnerability/february-2024-security-releases/#reading-unprocessed-http-request-with-unbounded-chunk-extension-allows-dos-attacks-cve-2024-22019---high
---
deps/llhttp/include/llhttp.h | 4 +
deps/llhttp/src/api.c | 22 ++++
deps/llhttp/src/llhttp.c | 122 +++++++++++++++++---
doc/api/errors.md | 12 ++
lib/_http_server.js | 25 ++++-
src/node_http_parser_impl.h | 25 ++++-
test/parallel/test-http-chunk-extensions-limit.js | 131 ++++++++++++++++++++++
7 files changed, 324 insertions(+), 17 deletions(-)
create mode 100644 test/parallel/test-http-chunk-extensions-limit.js
diff --git a/deps/llhttp/include/llhttp.h b/deps/llhttp/include/llhttp.h
index fe3a927..d9cf6d3 100644
--- a/deps/llhttp/include/llhttp.h
+++ b/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` */
diff --git a/deps/llhttp/src/api.c b/deps/llhttp/src/api.c
index 6f72465..1b7ad0e 100644
--- a/deps/llhttp/src/api.c
+++ b/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);
@@ -202,6 +217,13 @@ int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
}
+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;
CALLBACK_MAYBE(s, on_chunk_complete, s);
diff --git a/deps/llhttp/src/llhttp.c b/deps/llhttp/src/llhttp.c
index e8f42ce..3fcea4b 100644
--- a/deps/llhttp/src/llhttp.c
+++ b/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__run(
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__run(
/* 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__run(
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__run(
/* 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__run(
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__run(
/* 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__run(
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__run(
/* 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";
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 4dbb51b..3c8a858 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2212,6 +2212,18 @@ malconfigured clients, if more than 8KB of HTTP header data is received then
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`
diff --git a/lib/_http_server.js b/lib/_http_server.js
index 4d1f8e4..31ebae0 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -591,6 +591,11 @@ const requestHeaderFieldsTooLargeResponse = Buffer.from(
`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);
@@ -598,8 +603,24 @@ function socketOnError(e) {
if (!this.server.emit('clientError', e, this)) {
if (this.writable && this.bytesWritten === 0) {
- const response = e.code === 'HPE_HEADER_OVERFLOW' ?
- requestHeaderFieldsTooLargeResponse : badRequestResponse;
+ let response;
+
+ switch (e.code) {
+ case 'HPE_HEADER_OVERFLOW':
+ response = requestHeaderFieldsTooLargeResponse;
+ break;
+ case 'HPE_CHUNK_EXTENSIONS_OVERFLOW':
+ response = requestChunkExtensionsTooLargeResponse;
+ break;
+/* case 'ERR_HTTP_REQUEST_TIMEOUT':
+ response = requestTimeoutResponse;
+ break;
+*/
+ default:
+ response = badRequestResponse;
+ break;
+ }
+
this.write(response);
}
this.destroy(e);
diff --git a/src/node_http_parser_impl.h b/src/node_http_parser_impl.h
index 77d09a9..891a108 100644
--- a/src/node_http_parser_impl.h
+++ b/src/node_http_parser_impl.h
@@ -84,6 +84,8 @@ const uint32_t kOnExecute = 4;
const uint32_t kOnTimeout = 5;
// 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';
@@ -187,6 +189,9 @@ class Parser : public AsyncWrap, public StreamListener {
int on_message_begin() {
num_fields_ = num_values_ = 0;
+#ifdef NODE_EXPERIMENTAL_HTTP
+ chunk_extensions_nread_ = 0;
+#endif
url_.Reset();
status_message_.Reset();
header_parsing_start_time_ = uv_hrtime();
@@ -432,9 +437,22 @@ class Parser : public AsyncWrap, public StreamListener {
}
#ifdef NODE_EXPERIMENTAL_HTTP
- // 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;
}
@@ -857,6 +875,7 @@ class Parser : public AsyncWrap, public StreamListener {
llhttp_init(&parser_, type, &settings);
llhttp_set_lenient(&parser_, lenient);
header_nread_ = 0;
+ chunk_extensions_nread_ = 0;
#else /* !NODE_EXPERIMENTAL_HTTP */
http_parser_init(&parser_, type);
parser_.lenient_http_headers = lenient;
@@ -916,6 +935,7 @@ class Parser : public AsyncWrap, public StreamListener {
unsigned int execute_depth_ = 0;
bool pending_pause_ = false;
uint64_t header_nread_ = 0;
+ uint64_t chunk_extensions_nread_ = 0;
#endif /* NODE_EXPERIMENTAL_HTTP */
uint64_t headers_timeout_;
uint64_t header_parsing_start_time_ = 0;
@@ -948,6 +968,9 @@ const parser_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,
+#ifdef NODE_EXPERIMENTAL_HTTP
+ Proxy<DataCall, &Parser::on_chunk_extension>::Raw,
+#endif
Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw,
#ifdef NODE_EXPERIMENTAL_HTTP
diff --git a/test/parallel/test-http-chunk-extensions-limit.js b/test/parallel/test-http-chunk-extensions-limit.js
new file mode 100644
index 0000000..6868b3d
--- /dev/null
+++ b/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'
+ );
+ });
+}

154
CVE-2024-22025.patch Normal file

File diff suppressed because one or more lines are too long

3031
CVE-2024-27982.patch Normal file

File diff suppressed because it is too large Load Diff

34
CVE-2024-27983.patch Normal file
View File

@ -0,0 +1,34 @@
From: RafaelGSS <rafael.nunu@hotmail.com>
Date: Tue, 26 Mar 2024 15:55:13 -0300
Subject: CVE-2024-27983 ensure to close stream when destroying session
Co-Authored-By: Anna Henningsen <anna@addaleax.net>
PR-URL: https://github.com/nodejs-private/node-private/pull/561
bug-hakerone: 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
origin: backport, https://github.com/nodejs/node/commit/0fb816dbccde955cd24acc1b16497a91fab507c8.patch
---
src/node_http2.cc | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 5156aa3..c441921 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -590,6 +590,12 @@ Http2Session::Http2Session(Environment* env,
Http2Session::~Http2Session() {
CHECK_EQ(flags_ & SESSION_STATE_HAS_SCOPE, 0);
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();
nghttp2_session_del(session_);
CHECK_EQ(current_nghttp2_memory_, 0);
}

View File

@ -1,5 +1,5 @@
%bcond_with bootstrap
%global baserelease 9
%global baserelease 10
%{?!_pkgdocdir:%global _pkgdocdir %{_docdir}/%{name}-%{version}}
%global nodejs_epoch 1
%global nodejs_major 12
@ -106,6 +106,11 @@ Patch00028: CVE-2023-0464.patch
Patch00029: CVE-2023-0465.patch
# https://github.com/nghttp2/nghttp2/commit/72b4af6143681f528f1d237b21a9a7aee1738832
Patch00030: CVE-2023-44487.patch
Patch00031: CVE-2023-46809.patch
Patch00032: CVE-2024-22019.patch
Patch00033: CVE-2024-22025.patch
Patch00034: CVE-2024-27982.patch
Patch00035: CVE-2024-27983.patch
BuildRequires: python3-devel
BuildRequires: zlib-devel
@ -508,6 +513,9 @@ end
%{_pkgdocdir}/npm/docs
%changelog
* Thu Sep 19 2024 yaoxin <yao_xin001@hoperun.com>- 1:12.22.11-10
- Fix CVE-2023-46809,CVE-2024-22019,CVE-2024-22025,CVE-2024-27982 and CVE-2024-27983
* Tue Feb 06 2024 yaoxin <yao_xin001@hoperun.com>- 1:12.22.11-9
- Fix CVE-2023-44487