* 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-24758.patch: ignore proxy-authorization headers (CVE-2024-24758, bsc#1220017) * CVE-2024-24806.patch: fix improper domain lookup that potentially leads to SSRF attacks (CVE-2024-24806, bsc#1220053) - CVE-2023-38552.patch: Integrity checks according to policies can be circumvented (CVE-2023-38552, bsc#1216272) - CVE-2023-39333.patch, wasm-fixture.tar.gz: Code injection via WebAssembly export names (CVE-2023-39333, bsc#1216273) - CVE-2023-45143.patch: undici Security Release (CVE-2023-45143, bsc#1216205) - nodejs.keyring: include new releaser keys OBS-URL: https://build.opensuse.org/package/show/devel:languages:nodejs/nodejs16?expand=0&rev=100
This commit is contained in:
594
CVE-2023-46809.patch
Normal file
594
CVE-2023-46809.patch
Normal file
@@ -0,0 +1,594 @@
|
||||
Index: node-v16.20.2/test/parallel/test-crypto-rsa-dsa-revert.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v16.20.2/test/parallel/test-crypto-rsa-dsa-revert.js
|
||||
@@ -0,0 +1,475 @@
|
||||
+'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(ab, ab2enc);
|
||||
+ crypto.publicEncrypt(new Uint8Array(ab), new Uint8Array(ab2enc));
|
||||
+ crypto.publicEncrypt(new DataView(ab), new DataView(ab2enc));
|
||||
+ otherEncrypted = crypto.publicEncrypt({
|
||||
+ key: Buffer.from(ab).toString('hex'),
|
||||
+ encoding: 'hex'
|
||||
+ }, Buffer.from(ab2enc).toString('hex'));
|
||||
+ }
|
||||
+
|
||||
+ 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');
|
||||
+
|
||||
+ const otherDecrypted = crypto.privateDecrypt({
|
||||
+ key: rsaPkcs8KeyPem,
|
||||
+ oaepHash,
|
||||
+ oaepLabel: copiedLabel
|
||||
+ }, Buffer.from(ct, 'hex'));
|
||||
+
|
||||
+ assert.strictEqual(otherDecrypted.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-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
|
||||
@@ -221,19 +221,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-v16.20.2/src/crypto/crypto_cipher.cc
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/src/crypto/crypto_cipher.cc
|
||||
+++ node-v16.20.2/src/crypto/crypto_cipher.cc
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "node_buffer.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_process-inl.h"
|
||||
+#include "node_revert.h"
|
||||
#include "v8.h"
|
||||
|
||||
namespace node {
|
||||
@@ -1061,6 +1062,34 @@ 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 EVP_MD* digest = nullptr;
|
||||
if (args[offset + 2]->IsString()) {
|
||||
const Utf8Value oaep_str(env->isolate(), args[offset + 2]);
|
||||
Index: node-v16.20.2/src/node_revert.h
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/src/node_revert.h
|
||||
+++ node-v16.20.2/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,
|
||||
527
CVE-2024-22019.patch
Normal file
527
CVE-2024-22019.patch
Normal file
@@ -0,0 +1,527 @@
|
||||
Index: node-v16.20.2/deps/llhttp/CMakeLists.txt
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/llhttp/CMakeLists.txt
|
||||
+++ node-v16.20.2/deps/llhttp/CMakeLists.txt
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.5.1)
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
|
||||
-project(llhttp VERSION 6.0.11)
|
||||
+project(llhttp VERSION 6.1.0)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
Index: node-v16.20.2/deps/llhttp/include/llhttp.h
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/llhttp/include/llhttp.h
|
||||
+++ node-v16.20.2/deps/llhttp/include/llhttp.h
|
||||
@@ -2,8 +2,8 @@
|
||||
#define INCLUDE_LLHTTP_H_
|
||||
|
||||
#define LLHTTP_VERSION_MAJOR 6
|
||||
-#define LLHTTP_VERSION_MINOR 0
|
||||
-#define LLHTTP_VERSION_PATCH 11
|
||||
+#define LLHTTP_VERSION_MINOR 1
|
||||
+#define LLHTTP_VERSION_PATCH 0
|
||||
|
||||
#ifndef LLHTTP_STRICT_MODE
|
||||
# define LLHTTP_STRICT_MODE 0
|
||||
@@ -349,6 +349,9 @@ 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-v16.20.2/deps/llhttp/src/api.c
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/llhttp/src/api.c
|
||||
+++ node-v16.20.2/deps/llhttp/src/api.c
|
||||
@@ -355,6 +355,13 @@ int llhttp__on_chunk_header(llhttp_t* s,
|
||||
}
|
||||
|
||||
|
||||
+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);
|
||||
Index: node-v16.20.2/deps/llhttp/src/llhttp.c
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/llhttp/src/llhttp.c
|
||||
+++ node-v16.20.2/deps/llhttp/src/llhttp.c
|
||||
@@ -340,6 +340,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,
|
||||
@@ -539,6 +541,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);
|
||||
@@ -1226,8 +1232,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;
|
||||
@@ -1236,6 +1241,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) {
|
||||
@@ -1246,13 +1279,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;
|
||||
@@ -6074,6 +6103,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";
|
||||
@@ -8441,6 +8488,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,
|
||||
@@ -8635,6 +8684,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);
|
||||
@@ -9299,8 +9352,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;
|
||||
@@ -9309,6 +9361,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) {
|
||||
@@ -9319,13 +9399,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;
|
||||
@@ -13951,6 +14027,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-v16.20.2/doc/api/errors.md
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/doc/api/errors.md
|
||||
+++ node-v16.20.2/doc/api/errors.md
|
||||
@@ -3043,6 +3043,18 @@ malconfigured clients, if more than 8 Ki
|
||||
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-v16.20.2/test/parallel/test-http-chunk-extensions-limit.js
|
||||
===================================================================
|
||||
--- /dev/null
|
||||
+++ node-v16.20.2/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-v16.20.2/tools/update-llhttp.sh
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/tools/update-llhttp.sh
|
||||
+++ node-v16.20.2/tools/update-llhttp.sh
|
||||
@@ -59,5 +59,5 @@ echo ""
|
||||
echo "Please git add llhttp, commit the new version:"
|
||||
echo ""
|
||||
echo "$ git add -A deps/llhttp"
|
||||
-echo "$ git commit -m \"deps: update nghttp2 to $LLHTTP_VERSION\""
|
||||
+echo "$ git commit -m \"deps: update llhttp to $LLHTTP_VERSION\""
|
||||
echo ""
|
||||
Index: node-v16.20.2/lib/_http_server.js
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/lib/_http_server.js
|
||||
+++ node-v16.20.2/lib/_http_server.js
|
||||
@@ -706,6 +706,11 @@ const requestHeaderFieldsTooLargeRespons
|
||||
`HTTP/1.1 431 ${STATUS_CODES[431]}\r\n` +
|
||||
'Connection: close\r\n\r\n', '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);
|
||||
@@ -719,6 +724,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-v16.20.2/src/node_http_parser.cc
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/src/node_http_parser.cc
|
||||
+++ node-v16.20.2/src/node_http_parser.cc
|
||||
@@ -79,6 +79,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;
|
||||
|
||||
const uint32_t kLenientNone = 0;
|
||||
const uint32_t kLenientHeaders = 1 << 0;
|
||||
@@ -206,6 +208,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();
|
||||
@@ -443,9 +446,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;
|
||||
}
|
||||
|
||||
@@ -887,6 +903,7 @@ class Parser : public AsyncWrap, public
|
||||
const char* current_buffer_data_;
|
||||
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;
|
||||
@@ -921,6 +938,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,
|
||||
124
CVE-2024-22025.patch
Normal file
124
CVE-2024-22025.patch
Normal file
File diff suppressed because one or more lines are too long
32
CVE-2024-24758.patch
Normal file
32
CVE-2024-24758.patch
Normal file
@@ -0,0 +1,32 @@
|
||||
Index: node-v16.20.2/deps/undici/src/lib/fetch/index.js
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/undici/src/lib/fetch/index.js
|
||||
+++ node-v16.20.2/deps/undici/src/lib/fetch/index.js
|
||||
@@ -1200,6 +1200,13 @@ async function httpRedirectFetch (fetchP
|
||||
if (!sameOrigin(requestCurrentURL(request), locationURL)) {
|
||||
// https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
|
||||
request.headersList.delete('authorization')
|
||||
+
|
||||
+ // https://fetch.spec.whatwg.org/#authentication-entries
|
||||
+ request.headersList.delete('proxy-authorization', true)
|
||||
+
|
||||
+ // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement.
|
||||
+ request.headersList.delete('cookie')
|
||||
+ request.headersList.delete('host')
|
||||
}
|
||||
|
||||
// 14. If request’s body is non-null, then set request’s body to the first return
|
||||
Index: node-v16.20.2/deps/undici/undici.js
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/undici/undici.js
|
||||
+++ node-v16.20.2/deps/undici/undici.js
|
||||
@@ -11167,6 +11167,9 @@ var require_fetch = __commonJS({
|
||||
}
|
||||
if (!sameOrigin(requestCurrentURL(request), locationURL)) {
|
||||
request.headersList.delete("authorization");
|
||||
+ request.headersList.delete("proxy-authorization", true);
|
||||
+ request.headersList.delete("cookie");
|
||||
+ request.headersList.delete("host");
|
||||
}
|
||||
if (request.body != null) {
|
||||
assert(request.body.source != null);
|
||||
26
CVE-2024-24806.patch
Normal file
26
CVE-2024-24806.patch
Normal file
@@ -0,0 +1,26 @@
|
||||
Index: node-v16.20.2/deps/uv/src/idna.c
|
||||
===================================================================
|
||||
--- node-v16.20.2.orig/deps/uv/src/idna.c
|
||||
+++ node-v16.20.2/deps/uv/src/idna.c
|
||||
@@ -273,6 +273,9 @@ long uv__idna_toascii(const char* s, con
|
||||
char* ds;
|
||||
int rc;
|
||||
|
||||
+ if (s == se)
|
||||
+ return UV_EINVAL;
|
||||
+
|
||||
ds = d;
|
||||
|
||||
si = s;
|
||||
@@ -307,8 +310,9 @@ long uv__idna_toascii(const char* s, con
|
||||
return rc;
|
||||
}
|
||||
|
||||
- if (d < de)
|
||||
- *d++ = '\0';
|
||||
+ if (d >= de)
|
||||
+ return UV_EINVAL;
|
||||
|
||||
+ *d++ = '\0';
|
||||
return d - ds; /* Number of bytes written. */
|
||||
}
|
||||
@@ -1,10 +1,29 @@
|
||||
-------------------------------------------------------------------
|
||||
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-24758.patch: ignore proxy-authorization headers
|
||||
(CVE-2024-24758, bsc#1220017)
|
||||
* CVE-2024-24806.patch: fix improper domain lookup that
|
||||
potentially leads to SSRF attacks (CVE-2024-24806, bsc#1220053)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Oct 17 11:52:34 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-39333.patch: Code injection via WebAssembly export names (CVE-2023-39333, bsc#1216273)
|
||||
- CVE-2023-38552.patch: Integrity checks according to policies
|
||||
can be circumvented (CVE-2023-38552, bsc#1216272)
|
||||
- CVE-2023-39333.patch, wasm-fixture.tar.gz: Code injection via
|
||||
WebAssembly export names (CVE-2023-39333, bsc#1216273)
|
||||
- CVE-2023-44487.patch: nghttp2 Security Release (CVE-2023-44487, bsc#1216190)
|
||||
- CVE-2023-45143.patch: undici Security Release (CVE-2023-39333, bsc#1216273)
|
||||
- CVE-2023-45143.patch: undici Security Release (CVE-2023-45143, bsc#1216205)
|
||||
- nodejs.keyring: include new releaser keys
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Thu Aug 10 14:33:17 UTC 2023 - Adam Majer <adam.majer@suse.de>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file for package nodejs16
|
||||
#
|
||||
# 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
|
||||
@@ -135,9 +140,9 @@ Source3: nodejs.keyring
|
||||
Source5: node-gyp_7.1.2.tar.xz
|
||||
# Only required to run unit tests in NodeJS 10+
|
||||
Source10: update_npm_tarball.sh
|
||||
Source11: node_modules.tar.xz
|
||||
Source11: node_modules.tar.xz
|
||||
Source12: wasm-fixtures.tar.gz
|
||||
Source20: bash_output_helper.bash
|
||||
Source21: wasm-fixtures.tar.gz
|
||||
|
||||
## Patches not distribution specific
|
||||
Patch1: cares_public_headers.patch
|
||||
@@ -151,7 +156,11 @@ Patch68: CVE-2023-44487.patch
|
||||
Patch69: CVE-2023-38552.patch
|
||||
Patch70: CVE-2023-39333.patch
|
||||
Patch71: CVE-2023-45143.patch
|
||||
|
||||
Patch72: CVE-2024-24806.patch
|
||||
Patch73: CVE-2024-24758.patch
|
||||
Patch74: CVE-2024-22025.patch
|
||||
Patch75: CVE-2024-22019.patch
|
||||
Patch76: CVE-2023-46809.patch
|
||||
|
||||
## Patches specific to SUSE and openSUSE
|
||||
Patch100: linker_lto_jobs.patch
|
||||
@@ -382,6 +391,7 @@ Provides: bundled(llhttp) = 6.0.11
|
||||
Provides: bundled(ngtcp2) = 0.8.1
|
||||
Provides: bundled(base64) = 0.4.0
|
||||
|
||||
|
||||
# bundled url-ada parser, not ada
|
||||
|
||||
|
||||
@@ -639,11 +649,15 @@ The API documentation for the Node.js JavaScript runtime.
|
||||
%prep
|
||||
%if ! %{git_node}
|
||||
echo "`grep node-v%{version}.tar.xz %{S:1} | head -n1 | cut -c1-64` %{S:0}" | sha256sum -c
|
||||
%setup -q -n node-v%{version} -a21
|
||||
%setup -q -n node-v%{version}
|
||||
%else
|
||||
%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
|
||||
@@ -665,6 +679,7 @@ tar Jxf %{SOURCE5}
|
||||
popd
|
||||
|
||||
%if %{node_version_number} >= 19
|
||||
%else
|
||||
%endif
|
||||
%endif
|
||||
|
||||
@@ -681,9 +696,16 @@ popd
|
||||
%patch69 -p1
|
||||
%patch70 -p1
|
||||
%patch71 -p1
|
||||
%patch72 -p1
|
||||
%patch73 -p1
|
||||
%patch74 -p1
|
||||
%patch75 -p1
|
||||
%patch76 -p1
|
||||
%patch100 -p1
|
||||
%patch101 -p1
|
||||
%if 0%{?suse_version} >= 1500 || 0%{?suse_version} == 0
|
||||
%patch102 -p1
|
||||
%endif
|
||||
# Add check_output to configure script (not part of Python 2.6 in SLE11).
|
||||
%if 0%{?suse_version} == 1110
|
||||
%endif
|
||||
|
||||
Reference in New Issue
Block a user