Compare commits

...

10 Commits

Author SHA1 Message Date
openeuler-ci-bot
87f6ab1f33
!163 Fix CVE-2023-46809,CVE-2024-22019,CVE-2024-22025,CVE-2024-27982 and CVE-2024-27983
From: @starlet-dx 
Reviewed-by: @wang--ge 
Signed-off-by: @wang--ge
2024-09-19 08:23:39 +00:00
starlet-dx
bf7be5724d Fix CVE-2023-46809,CVE-2024-22019,CVE-2024-22025,CVE-2024-27982 and CVE-2024-27983 2024-09-19 15:59:56 +08:00
openeuler-ci-bot
69a46a3a67
!147 [sync] PR-142: Fix CVE-2023-44487
From: @openeuler-sync-bot 
Reviewed-by: @wang--ge 
Signed-off-by: @wang--ge
2024-02-07 01:20:43 +00:00
starlet-dx
3956f1c25b Fix CVE-2023-44487
(cherry picked from commit 73698fdbab5fe8fa0d5f028fde34423683570858)
2024-02-06 18:08:06 +08:00
openeuler-ci-bot
5ecad71650
!135 Fix CVE-2023-0464 and CVE-2023-0465
From: @starlet-dx 
Reviewed-by: @wang--ge 
Signed-off-by: @wang--ge
2024-02-05 09:38:36 +00:00
starlet-dx
235363be93 Fix CVE-2023-0464 and CVE-2023-0465 2024-02-05 18:21:04 +08:00
openeuler-ci-bot
9a422afb58
!125 Update CVE-2023-23918.patch for fix nodejs-raw-body,nodejs-istanbul build error
From: @wk333 
Reviewed-by: @lyn1001 
Signed-off-by: @lyn1001
2023-10-26 07:50:10 +00:00
wk333
d15dac7e21 Update CVE-2023-23918.patch for fix nodejs-raw-body,nodejs-istanbul build error 2023-10-26 14:21:11 +08:00
openeuler-ci-bot
f62a45736b
!122 [sync] PR-119: Fix CVEs
From: @openeuler-sync-bot 
Reviewed-by: @lyn1001 
Signed-off-by: @lyn1001
2023-08-29 11:13:41 +00:00
wk333
ce983ceded Fix CVE-2022-32212,CVE-2022-32213,CVE-2022-32214,CVE-2022-32215,CVE-2022-25881,CVE-2023-23918,CVE-2023-23920,CVE-2023-30589,CVE-2023-30590,CVE-2023-30581,CVE-2023-32002,CVE-2023-32006,CVE-2023-32559,CVE-2022-35256
(cherry picked from commit d16c67a1e4f5190cc8a07fbc940bd65334982f9d)
2023-08-29 17:49:48 +08:00
22 changed files with 14135 additions and 91 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
+}();

View File

@ -1,21 +1,18 @@
From 1aa5036c31ac2a9b2a2528af454675ad412f1464 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= <tniessen@tnie.de>
Date: Fri, 27 May 2022 21:18:49 +0000
Subject: [PATCH] src: fix IPv4 validation in inspector_socket
commit 1aa5036c31ac2a9b2a2528af454675ad412f1464
Author: Tobias Nießen <tniessen@tnie.de>
Date: Fri May 27 21:18:49 2022 +0000
Co-authored-by: RafaelGSS <rafael.nunu@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: RafaelGSS <rafael.nunu@hotmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/320
CVE-ID: CVE-2022-32212
---
src/inspector_socket.cc | 18 +++++--
test/cctest/test_inspector_socket.cc | 74 ++++++++++++++++++++++++++++
2 files changed, 87 insertions(+), 5 deletions(-)
src: fix IPv4 validation in inspector_socket
Co-authored-by: RafaelGSS <rafael.nunu@hotmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: RafaelGSS <rafael.nunu@hotmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/320
CVE-ID: CVE-2022-32212
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
index 1650c3fe01de..79b50e6a452d 100644
index 1650c3fe01..79b50e6a45 100644
--- a/src/inspector_socket.cc
+++ b/src/inspector_socket.cc
@@ -164,14 +164,22 @@ static std::string TrimPort(const std::string& host) {
@ -47,7 +44,7 @@ index 1650c3fe01de..79b50e6a452d 100644
// Constants for hybi-10 frame format.
diff --git a/test/cctest/test_inspector_socket.cc b/test/cctest/test_inspector_socket.cc
index dc8cd962141e..c740d961d9b7 100644
index dc8cd96214..c740d961d9 100644
--- a/test/cctest/test_inspector_socket.cc
+++ b/test/cctest/test_inspector_socket.cc
@@ -851,4 +851,78 @@ TEST_F(InspectorSocketTest, HostCheckedForUPGRADE) {

File diff suppressed because it is too large Load Diff

1581
CVE-2022-35256.patch Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
From b358fb27a4253c6827378a64163448c04301e19c Mon Sep 17 00:00:00 2001
From: RafaelGSS <rafael.nunu@hotmail.com>
Date: Wed, 13 Jul 2022 13:20:22 -0300
Subject: [PATCH] src: fix IPv4 non routable validation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/337
CVE-ID: CVE-2022-32212, CVE-2018-7160
---
src/inspector_socket.cc | 1 +
test/cctest/test_inspector_socket.cc | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
index 79b50e6a452d..ab1cdf1fa5bd 100644
--- a/src/inspector_socket.cc
+++ b/src/inspector_socket.cc
@@ -164,6 +164,7 @@ static std::string TrimPort(const std::string& host) {
static bool IsIPAddress(const std::string& host) {
if (host.length() >= 4 && host.front() == '[' && host.back() == ']')
return true;
+ if (host.front() == '0') return false;
uint_fast16_t accum = 0;
uint_fast8_t quads = 0;
bool empty = true;
diff --git a/test/cctest/test_inspector_socket.cc b/test/cctest/test_inspector_socket.cc
index c740d961d9b7..6ae92c4b27e2 100644
--- a/test/cctest/test_inspector_socket.cc
+++ b/test/cctest/test_inspector_socket.cc
@@ -925,4 +925,12 @@ TEST_F(InspectorSocketTest, HostIpTooManyOctetsChecked) {
expect_handshake_failure();
}
+TEST_F(InspectorSocketTest, HostIPNonRoutable) {
+ const std::string INVALID_HOST_IP_REQUEST = "GET /json HTTP/1.1\r\n"
+ "Host: 0.0.0.0:9229\r\n\r\n";
+ send_in_chunks(INVALID_HOST_IP_REQUEST.c_str(),
+ INVALID_HOST_IP_REQUEST.length());
+ expect_handshake_failure();
+}
+
} // anonymous namespace

View File

@ -19,11 +19,11 @@ Reviewed-by: Rich Trott <rtrott@gmail.com>
test/cctest/test_inspector_socket.cc | 80 ++++++++++++++++++++++++++++
2 files changed, 142 insertions(+), 16 deletions(-)
diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc
index 8cabdaec2821..a28bd557c8ab 100644
--- a/src/inspector_socket.cc
+++ b/src/inspector_socket.cc
@@ -6,6 +6,7 @@
Index: node-v12.22.12/src/inspector_socket.cc
===================================================================
--- node-v12.22.12.orig/src/inspector_socket.cc
+++ node-v12.22.12/src/inspector_socket.cc
@@ -10,6 +10,7 @@
#include "openssl/sha.h" // Sha-1 hash
@ -31,13 +31,12 @@ index 8cabdaec2821..a28bd557c8ab 100644
#include <cstring>
#include <map>
@@ -162,25 +163,70 @@ static std::string TrimPort(const std::string& host) {
@@ -166,24 +167,70 @@ static std::string TrimPort(const std::s
}
static bool IsIPAddress(const std::string& host) {
- if (host.length() >= 4 && host.front() == '[' && host.back() == ']')
- return true;
- if (host.front() == '0') return false;
- uint_fast16_t accum = 0;
- uint_fast8_t quads = 0;
- bool empty = true;
@ -118,11 +117,11 @@ index 8cabdaec2821..a28bd557c8ab 100644
}
// Constants for hybi-10 frame format.
diff --git a/test/cctest/test_inspector_socket.cc b/test/cctest/test_inspector_socket.cc
index 6ae92c4b27e2..b351a23002c9 100644
--- a/test/cctest/test_inspector_socket.cc
+++ b/test/cctest/test_inspector_socket.cc
@@ -925,6 +925,54 @@ TEST_F(InspectorSocketTest, HostIpTooManyOctetsChecked) {
Index: node-v12.22.12/test/cctest/test_inspector_socket.cc
===================================================================
--- node-v12.22.12.orig/test/cctest/test_inspector_socket.cc
+++ node-v12.22.12/test/cctest/test_inspector_socket.cc
@@ -925,4 +925,85 @@ TEST_F(InspectorSocketTest, HostIpTooMan
expect_handshake_failure();
}
@ -174,13 +173,7 @@ index 6ae92c4b27e2..b351a23002c9 100644
+ expect_handshake_failure();
+}
+
TEST_F(InspectorSocketTest, HostIPNonRoutable) {
const std::string INVALID_HOST_IP_REQUEST = "GET /json HTTP/1.1\r\n"
"Host: 0.0.0.0:9229\r\n\r\n";
@@ -933,4 +981,36 @@ TEST_F(InspectorSocketTest, HostIPNonRoutable) {
expect_handshake_failure();
}
+
+TEST_F(InspectorSocketTest, HostIPv6NonRoutable) {
+ const std::string INVALID_HOST_IP_REQUEST = "GET /json HTTP/1.1\r\n"
+ "Host: [::]:9229\r\n\r\n";

219
CVE-2023-0464.patch Normal file
View File

@ -0,0 +1,219 @@
From 879f7080d7e141f415c79eaa3a8ac4a3dad0348b Mon Sep 17 00:00:00 2001
From: Pauli <pauli@openssl.org>
Date: Wed, 8 Mar 2023 15:28:20 +1100
Subject: [PATCH] x509: excessive resource use verifying policy constraints
A security vulnerability has been identified in all supported versions
of OpenSSL related to the verification of X.509 certificate chains
that include policy constraints. Attackers may be able to exploit this
vulnerability by creating a malicious certificate chain that triggers
exponential use of computational resources, leading to a denial-of-service
(DoS) attack on affected systems.
Fixes CVE-2023-0464
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
(Merged from https://github.com/openssl/openssl/pull/20569)
---
deps/openssl/openssl/crypto/x509v3/pcy_local.h | 8 +++++++-
deps/openssl/openssl/crypto/x509v3/pcy_node.c | 12 +++++++++---
deps/openssl/openssl/crypto/x509v3/pcy_tree.c | 37 +++++++++++++++++++++++++++----------
3 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/deps/openssl/openssl/crypto/x509v3/pcy_local.h b/deps/openssl/openssl/crypto/x509v3/pcy_local.h
index 5daf78de45850..344aa067659cd 100644
--- a/deps/openssl/openssl/crypto/x509v3/pcy_local.h
+++ b/deps/openssl/openssl/crypto/x509v3/pcy_local.h
@@ -111,6 +111,11 @@ struct X509_POLICY_LEVEL_st {
};
struct X509_POLICY_TREE_st {
+ /* The number of nodes in the tree */
+ size_t node_count;
+ /* The maximum number of nodes in the tree */
+ size_t node_maximum;
+
/* This is the tree 'level' data */
X509_POLICY_LEVEL *levels;
int nlevel;
@@ -159,7 +164,8 @@ X509_POLICY_NODE *tree_find_sk(STACK_OF(X509_POLICY_NODE) *sk,
X509_POLICY_NODE *level_add_node(X509_POLICY_LEVEL *level,
X509_POLICY_DATA *data,
X509_POLICY_NODE *parent,
- X509_POLICY_TREE *tree);
+ X509_POLICY_TREE *tree,
+ int extra_data);
void policy_node_free(X509_POLICY_NODE *node);
int policy_node_match(const X509_POLICY_LEVEL *lvl,
const X509_POLICY_NODE *node, const ASN1_OBJECT *oid);
diff --git a/deps/openssl/openssl/crypto/x509v3/pcy_node.c b/deps/openssl/openssl/crypto/x509v3/pcy_node.c
index e2d7b15322363..d574fb9d665dc 100644
--- a/deps/openssl/openssl/crypto/x509v3/pcy_node.c
+++ b/deps/openssl/openssl/crypto/x509v3/pcy_node.c
@@ -59,10 +59,15 @@ X509_POLICY_NODE *level_find_node(const X509_POLICY_LEVEL *level,
X509_POLICY_NODE *level_add_node(X509_POLICY_LEVEL *level,
X509_POLICY_DATA *data,
X509_POLICY_NODE *parent,
- X509_POLICY_TREE *tree)
+ X509_POLICY_TREE *tree,
+ int extra_data)
{
X509_POLICY_NODE *node;
+ /* Verify that the tree isn't too large. This mitigates CVE-2023-0464 */
+ if (tree->node_maximum > 0 && tree->node_count >= tree->node_maximum)
+ return NULL;
+
node = OPENSSL_zalloc(sizeof(*node));
if (node == NULL) {
X509V3err(X509V3_F_LEVEL_ADD_NODE, ERR_R_MALLOC_FAILURE);
@@ -70,7 +75,7 @@ X509_POLICY_NODE *level_add_node(X509_POLICY_LEVEL *level,
}
node->data = data;
node->parent = parent;
- if (level) {
+ if (level != NULL) {
if (OBJ_obj2nid(data->valid_policy) == NID_any_policy) {
if (level->anyPolicy)
goto node_error;
@@ -90,7 +95,7 @@ X509_POLICY_NODE *level_add_node(X509_POLICY_LEVEL *level,
}
}
- if (tree) {
+ if (extra_data) {
if (tree->extra_data == NULL)
tree->extra_data = sk_X509_POLICY_DATA_new_null();
if (tree->extra_data == NULL){
@@ -103,6 +108,7 @@ X509_POLICY_NODE *level_add_node(X509_POLICY_LEVEL *level,
}
}
+ tree->node_count++;
if (parent)
parent->nchild++;
diff --git a/deps/openssl/openssl/crypto/x509v3/pcy_tree.c b/deps/openssl/openssl/crypto/x509v3/pcy_tree.c
index 6e8322cbc5e38..6c7fd35405000 100644
--- a/deps/openssl/openssl/crypto/x509v3/pcy_tree.c
+++ b/deps/openssl/openssl/crypto/x509v3/pcy_tree.c
@@ -13,6 +13,18 @@
#include "pcy_local.h"
+/*
+ * If the maximum number of nodes in the policy tree isn't defined, set it to
+ * a generous default of 1000 nodes.
+ *
+ * Defining this to be zero means unlimited policy tree growth which opens the
+ * door on CVE-2023-0464.
+ */
+
+#ifndef OPENSSL_POLICY_TREE_NODES_MAX
+# define OPENSSL_POLICY_TREE_NODES_MAX 1000
+#endif
+
/*
* Enable this to print out the complete policy tree at various point during
* evaluation.
@@ -168,6 +180,9 @@ static int tree_init(X509_POLICY_TREE **ptree, STACK_OF(X509) *certs,
return X509_PCY_TREE_INTERNAL;
}
+ /* Limit the growth of the tree to mitigate CVE-2023-0464 */
+ tree->node_maximum = OPENSSL_POLICY_TREE_NODES_MAX;
+
/*
* http://tools.ietf.org/html/rfc5280#section-6.1.2, figure 3.
*
@@ -184,7 +199,7 @@ static int tree_init(X509_POLICY_TREE **ptree, STACK_OF(X509) *certs,
level = tree->levels;
if ((data = policy_data_new(NULL, OBJ_nid2obj(NID_any_policy), 0)) == NULL)
goto bad_tree;
- if (level_add_node(level, data, NULL, tree) == NULL) {
+ if (level_add_node(level, data, NULL, tree, 1) == NULL) {
policy_data_free(data);
goto bad_tree;
}
@@ -243,7 +258,8 @@ static int tree_init(X509_POLICY_TREE **ptree, STACK_OF(X509) *certs,
* Return value: 1 on success, 0 otherwise
*/
static int tree_link_matching_nodes(X509_POLICY_LEVEL *curr,
- X509_POLICY_DATA *data)
+ X509_POLICY_DATA *data,
+ X509_POLICY_TREE *tree)
{
X509_POLICY_LEVEL *last = curr - 1;
int i, matched = 0;
@@ -253,13 +269,13 @@ static int tree_link_matching_nodes(X509_POLICY_LEVEL *curr,
X509_POLICY_NODE *node = sk_X509_POLICY_NODE_value(last->nodes, i);
if (policy_node_match(last, node, data->valid_policy)) {
- if (level_add_node(curr, data, node, NULL) == NULL)
+ if (level_add_node(curr, data, node, tree, 0) == NULL)
return 0;
matched = 1;
}
}
if (!matched && last->anyPolicy) {
- if (level_add_node(curr, data, last->anyPolicy, NULL) == NULL)
+ if (level_add_node(curr, data, last->anyPolicy, tree, 0) == NULL)
return 0;
}
return 1;
@@ -272,7 +288,8 @@ static int tree_link_matching_nodes(X509_POLICY_LEVEL *curr,
* Return value: 1 on success, 0 otherwise.
*/
static int tree_link_nodes(X509_POLICY_LEVEL *curr,
- const X509_POLICY_CACHE *cache)
+ const X509_POLICY_CACHE *cache,
+ X509_POLICY_TREE *tree)
{
int i;
@@ -280,7 +297,7 @@ static int tree_link_nodes(X509_POLICY_LEVEL *curr,
X509_POLICY_DATA *data = sk_X509_POLICY_DATA_value(cache->data, i);
/* Look for matching nodes in previous level */
- if (!tree_link_matching_nodes(curr, data))
+ if (!tree_link_matching_nodes(curr, data, tree))
return 0;
}
return 1;
@@ -311,7 +328,7 @@ static int tree_add_unmatched(X509_POLICY_LEVEL *curr,
/* Curr may not have anyPolicy */
data->qualifier_set = cache->anyPolicy->qualifier_set;
data->flags |= POLICY_DATA_FLAG_SHARED_QUALIFIERS;
- if (level_add_node(curr, data, node, tree) == NULL) {
+ if (level_add_node(curr, data, node, tree, 1) == NULL) {
policy_data_free(data);
return 0;
}
@@ -373,7 +390,7 @@ static int tree_link_any(X509_POLICY_LEVEL *curr,
}
/* Finally add link to anyPolicy */
if (last->anyPolicy &&
- level_add_node(curr, cache->anyPolicy, last->anyPolicy, NULL) == NULL)
+ level_add_node(curr, cache->anyPolicy, last->anyPolicy, tree, 0) == NULL)
return 0;
return 1;
}
@@ -555,7 +572,7 @@ static int tree_calculate_user_set(X509_POLICY_TREE *tree,
extra->qualifier_set = anyPolicy->data->qualifier_set;
extra->flags = POLICY_DATA_FLAG_SHARED_QUALIFIERS
| POLICY_DATA_FLAG_EXTRA_NODE;
- node = level_add_node(NULL, extra, anyPolicy->parent, tree);
+ node = level_add_node(NULL, extra, anyPolicy->parent, tree, 1);
}
if (!tree->user_policies) {
tree->user_policies = sk_X509_POLICY_NODE_new_null();
@@ -582,7 +599,7 @@ static int tree_evaluate(X509_POLICY_TREE *tree)
for (i = 1; i < tree->nlevel; i++, curr++) {
cache = policy_cache_set(curr->cert);
- if (!tree_link_nodes(curr, cache))
+ if (!tree_link_nodes(curr, cache, tree))
return X509_PCY_TREE_INTERNAL;
if (!(curr->flags & X509_V_FLAG_INHIBIT_ANY)

51
CVE-2023-0465.patch Normal file
View File

@ -0,0 +1,51 @@
From b013765abfa80036dc779dd0e50602c57bb3bf95 Mon Sep 17 00:00:00 2001
From: Matt Caswell <matt@openssl.org>
Date: Tue, 7 Mar 2023 16:52:55 +0000
Subject: [PATCH] Ensure that EXFLAG_INVALID_POLICY is checked even in leaf
certs
Even though we check the leaf cert to confirm it is valid, we
later ignored the invalid flag and did not notice that the leaf
cert was bad.
Fixes: CVE-2023-0465
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20588)
---
deps/openssl/openssl/crypto/x509/x509_vfy.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/deps/openssl/openssl/crypto/x509/x509_vfy.c b/deps/openssl/openssl/crypto/x509/x509_vfy.c
index 925fbb5412583..1dfe4f9f31a58 100644
--- a/deps/openssl/openssl/crypto/x509/x509_vfy.c
+++ b/deps/openssl/openssl/crypto/x509/x509_vfy.c
@@ -1649,18 +1649,25 @@ static int check_policy(X509_STORE_CTX *ctx)
}
/* Invalid or inconsistent extensions */
if (ret == X509_PCY_TREE_INVALID) {
- int i;
+ int i, cbcalled = 0;
/* Locate certificates with bad extensions and notify callback. */
- for (i = 1; i < sk_X509_num(ctx->chain); i++) {
+ for (i = 0; i < sk_X509_num(ctx->chain); i++) {
X509 *x = sk_X509_value(ctx->chain, i);
if (!(x->ex_flags & EXFLAG_INVALID_POLICY))
continue;
+ cbcalled = 1;
if (!verify_cb_cert(ctx, x, i,
X509_V_ERR_INVALID_POLICY_EXTENSION))
return 0;
}
+ if (!cbcalled) {
+ /* Should not be able to get here */
+ X509err(X509_F_CHECK_POLICY, ERR_R_INTERNAL_ERROR);
+ return 0;
+ }
+ /* The callback ignored the error so we return success */
return 1;
}
if (ret == X509_PCY_TREE_FAILURE) {

699
CVE-2023-23918.patch Normal file
View File

@ -0,0 +1,699 @@
commit f7892c16be6507e26c2ae478c261fa3fa2d84f52
Author: RafaelGSS <rafael.nunu@hotmail.com>
Date: Mon Feb 13 15:41:30 2023 -0300
lib: makeRequireFunction patch when experimental policy
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/373
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1747642
CVE-ID: CVE-2023-23918
PR-URL: https://github.com/nodejs-private/node-private/pull/358
Reviewed-by: Bradley Farias <bradley.meck@gmail.com>
Reviewed-by: Michael Dawson <midawson@redhat.com>
commit 83975b7fb463e29c92c8e83693b725e269cee149
Author: RafaelGSS <rafael.nunu@hotmail.com>
Date: Tue Oct 25 00:23:27 2022 -0300
policy: makeRequireFunction on mainModule.require
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
Co-authored-by: Bradley Farias <bradley.meck@gmail.com>
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/373
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1747642
CVE-ID: CVE-2023-23918
PR-URL: https://github.com/nodejs-private/node-private/pull/358
Reviewed-by: Bradley Farias <bradley.meck@gmail.com>
Reviewed-by: Michael Dawson <midawson@redhat.com>
commit fa115ee8ac32883c96df4a93fafb600bd665a5aa
Author: Antoine du Hamel <duhamelantoine1995@gmail.com>
Date: Fri Aug 5 00:41:48 2022 +0200
module: protect against prototype mutation
Ensures that mutating the `Object` prototype does not influence the
parsing of `package.json` files.
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/373
PR-URL: https://github.com/nodejs/node/pull/44007
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Index: node-v12.22.12/lib/internal/modules/cjs/helpers.js
===================================================================
--- node-v12.22.12.orig/lib/internal/modules/cjs/helpers.js
+++ node-v12.22.12/lib/internal/modules/cjs/helpers.js
@@ -5,6 +5,7 @@ const {
SafeMap,
} = primordials;
const {
+ ERR_INVALID_ARG_TYPE,
ERR_MANIFEST_DEPENDENCY_MISSING,
ERR_UNKNOWN_BUILTIN_MODULE
} = require('internal/errors').codes;
@@ -14,11 +15,22 @@ const { validateString } = require('inte
const path = require('path');
const { pathToFileURL, fileURLToPath } = require('internal/url');
const { URL } = require('url');
+const { setOwnProperty } = require('internal/util');
+
+const {
+ require_private_symbol,
+} = internalBinding('util');
let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
debug = fn;
});
+// TODO: Use this set when resolving pkg#exports conditions in loader.js.
+const cjsConditions = [
+ 'require',
+ 'node',
+];
+
function loadNativeModule(filename, request) {
const mod = NativeModule.map.get(filename);
if (mod) {
@@ -28,20 +40,31 @@ 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) {
const { resolve, reaction } = redirects;
const id = mod.filename || mod.id;
+ const conditions = cjsConditions;
require = function require(path) {
let missing = true;
- const destination = resolve(path);
+ const destination = resolve(path, conditions);
if (destination === true) {
missing = false;
} else if (destination) {
@@ -61,17 +84,17 @@ function makeRequireFunction(mod, redire
filepath = fileURLToPath(destination);
urlToFileCache.set(href, filepath);
}
- return mod.require(filepath);
+ return mod[require_private_symbol](mod, filepath);
}
}
if (missing) {
- reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(id, path));
+ reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(id, path, ArrayPrototypeJoin([...conditions], ', ')));
}
- return mod.require(path);
+ return mod[require_private_symbol](mod, path);
};
} else {
require = function require(path) {
- return mod.require(path);
+ return mod[require_private_symbol](mod, path);
};
}
@@ -89,7 +112,7 @@ function makeRequireFunction(mod, redire
resolve.paths = paths;
- require.main = process.mainModule;
+ setOwnProperty(require, 'main', process.mainModule);
// Enable support to add extra extension types.
require.extensions = Module._extensions;
Index: node-v12.22.12/lib/internal/modules/cjs/loader.js
===================================================================
--- node-v12.22.12.orig/lib/internal/modules/cjs/loader.js
+++ node-v12.22.12/lib/internal/modules/cjs/loader.js
@@ -59,7 +59,7 @@ const {
rekeySourceMap
} = require('internal/source_map/source_map_cache');
const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
-const { deprecate } = require('internal/util');
+const { deprecate, filterOwnProperties, setOwnProperty } = require('internal/util');
const vm = require('vm');
const assert = require('internal/assert');
const fs = require('fs');
@@ -70,6 +70,9 @@ const { internalModuleStat } = internalB
const packageJsonReader = require('internal/modules/package_json_reader');
const { safeGetenv } = internalBinding('credentials');
const {
+ require_private_symbol,
+} = internalBinding('util');
+const {
makeRequireFunction,
normalizeReferrerURL,
stripBOM,
@@ -120,6 +123,20 @@ const relativeResolveCache = ObjectCreat
let requireDepth = 0;
let statCache = null;
+function internalRequire(module, id) {
+ validateString(id, 'id');
+ if (id === '') {
+ throw new ERR_INVALID_ARG_VALUE('id', id,
+ 'must be a non-empty string');
+ }
+ requireDepth++;
+ try {
+ return Module._load(id, module, /* isMain */ false);
+ } finally {
+ requireDepth--;
+ }
+}
+
function stat(filename) {
filename = path.toNamespacedPath(filename);
if (statCache !== null) {
@@ -140,12 +157,21 @@ function updateChildren(parent, child, s
function Module(id = '', parent) {
this.id = id;
this.path = path.dirname(id);
- this.exports = {};
+ setOwnProperty(this, 'exports', {});
this.parent = parent;
updateChildren(parent, this, false);
this.filename = null;
this.loaded = false;
this.children = [];
+ let redirects;
+ if (manifest) {
+ const moduleURL = pathToFileURL(id);
+ redirects = manifest.getRedirector(moduleURL);
+ }
+ setOwnProperty(this, 'require', makeRequireFunction(this, redirects));
+ // Loads a module at the given file path. Returns that module's
+ // `exports` property.
+ this[require_private_symbol] = internalRequire;
}
const builtinModules = [];
@@ -242,14 +268,13 @@ function readPackage(requestPath) {
}
try {
- const parsed = JSONParse(json);
- const filtered = {
- name: parsed.name,
- main: parsed.main,
- exports: parsed.exports,
- imports: parsed.imports,
- type: parsed.type
- };
+ const filtered = filterOwnProperties(JSONParse(json), [
+ 'name',
+ 'main',
+ 'exports',
+ 'imports',
+ 'type',
+ ]);
packageJsonCache.set(jsonPath, filtered);
return filtered;
} catch (e) {
@@ -684,6 +709,7 @@ Module._load = function(request, parent,
if (isMain) {
process.mainModule = module;
+ setOwnProperty(module.require, 'main', process.mainModule);
module.id = '.';
}
@@ -873,24 +899,6 @@ Module.prototype.load = function(filenam
ESMLoader.cjsCache.set(this, exports);
};
-
-// Loads a module at the given file path. Returns that module's
-// `exports` property.
-Module.prototype.require = function(id) {
- validateString(id, 'id');
- if (id === '') {
- throw new ERR_INVALID_ARG_VALUE('id', id,
- 'must be a non-empty string');
- }
- requireDepth++;
- try {
- return Module._load(id, this, /* isMain */ false);
- } finally {
- requireDepth--;
- }
-};
-
-
// Resolved path to process.argv[1] will be lazily placed here
// (needed for setting breakpoint when called with --inspect-brk)
let resolvedArgv;
@@ -952,10 +960,9 @@ function wrapSafe(filename, content, cjs
// Returns exception, if any.
Module.prototype._compile = function(content, filename) {
let moduleURL;
- let redirects;
if (manifest) {
moduleURL = pathToFileURL(filename);
- redirects = manifest.getRedirector(moduleURL);
+ manifest.getRedirector(moduleURL);
manifest.assertIntegrity(moduleURL, content);
}
@@ -986,7 +993,6 @@ Module.prototype._compile = function(con
}
}
const dirname = path.dirname(filename);
- const require = makeRequireFunction(this, redirects);
let result;
const exports = this.exports;
const thisValue = exports;
@@ -994,9 +1000,9 @@ Module.prototype._compile = function(con
if (requireDepth === 0) statCache = new Map();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
- require, module, filename, dirname);
+ module.require, module, filename, dirname);
} else {
- result = compiledWrapper.call(thisValue, exports, require, module,
+ result = compiledWrapper.call(thisValue, exports, module.require, module,
filename, dirname);
}
hasLoadedAnyUserCJSModule = true;
@@ -1038,7 +1044,7 @@ Module._extensions['.json'] = function(m
}
try {
- module.exports = JSONParse(stripBOM(content));
+ setOwnProperty(module, 'exports', JSONParse(stripBOM(content)));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
@@ -1144,7 +1150,7 @@ Module._preloadModules = function(reques
}
}
for (let n = 0; n < requests.length; n++)
- parent.require(requests[n]);
+ internalRequire(parent, requests[n]);
};
Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
@@ -1155,5 +1161,14 @@ Module.syncBuiltinESMExports = function
}
};
+ObjectDefineProperty(Module.prototype, 'constructor', {
+ __proto__: null,
+ get: function() {
+ return manifest ? undefined : Module;
+ },
+ configurable: false,
+ enumerable: false,
+});
+
// Backwards compatibility
Module.Module = Module;
Index: node-v12.22.12/lib/internal/util.js
===================================================================
--- node-v12.22.12.orig/lib/internal/util.js
+++ node-v12.22.12/lib/internal/util.js
@@ -11,6 +11,7 @@ const {
ObjectGetOwnPropertyDescriptor,
ObjectGetOwnPropertyDescriptors,
ObjectGetPrototypeOf,
+ ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
Promise,
ReflectConstruct,
@@ -404,6 +405,35 @@ function sleep(msec) {
_sleep(msec);
}
+function filterOwnProperties(source, keys) {
+ const filtered = ObjectCreate(null);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (ObjectPrototypeHasOwnProperty(source, key)) {
+ filtered[key] = source[key];
+ }
+ }
+
+ return filtered;
+}
+
+/**
+ * Mimics `obj[key] = value` but ignoring potential prototype inheritance.
+ * @param {any} obj
+ * @param {string} key
+ * @param {any} value
+ * @returns {any}
+ */
+function setOwnProperty(obj, key, value) {
+ return ObjectDefineProperty(obj, key, {
+ __proto__: null,
+ configurable: true,
+ enumerable: true,
+ value,
+ writable: true,
+ });
+}
+
module.exports = {
assertCrypto,
cachedResult,
@@ -413,6 +443,7 @@ module.exports = {
deprecate,
emitExperimentalWarning,
filterDuplicateStrings,
+ filterOwnProperties,
getConstructorOf,
getSystemErrorName,
isError,
@@ -435,5 +466,6 @@ module.exports = {
// Used by the buffer module to capture an internal reference to the
// default isEncoding implementation, just in case userland overrides it.
kIsEncodingSymbol: Symbol('kIsEncodingSymbol'),
- kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol')
+ kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol'),
+ setOwnProperty,
};
Index: node-v12.22.12/test/common/fixtures.js
===================================================================
--- node-v12.22.12.orig/test/common/fixtures.js
+++ node-v12.22.12/test/common/fixtures.js
@@ -3,6 +3,7 @@
const path = require('path');
const fs = require('fs');
+const { pathToFileURL } = require('url');
const fixturesDir = path.join(__dirname, '..', 'fixtures');
@@ -10,6 +11,10 @@ function fixturesPath(...args) {
return path.join(fixturesDir, ...args);
}
+function fixturesFileURL(...args) {
+ return pathToFileURL(fixturesPath(...args));
+}
+
function readFixtureSync(args, enc) {
if (Array.isArray(args))
return fs.readFileSync(fixturesPath(...args), enc);
@@ -23,6 +28,7 @@ function readFixtureKey(name, enc) {
module.exports = {
fixturesDir,
path: fixturesPath,
+ fileURL: fixturesFileURL,
readSync: readFixtureSync,
readKey: readFixtureKey
};
Index: node-v12.22.12/test/fixtures/es-module-specifiers/index.mjs
===================================================================
--- node-v12.22.12.orig/test/fixtures/es-module-specifiers/index.mjs
+++ node-v12.22.12/test/fixtures/es-module-specifiers/index.mjs
@@ -1,10 +1,11 @@
import explicit from 'explicit-main';
import implicit from 'implicit-main';
import implicitModule from 'implicit-main-type-module';
+import noMain from 'no-main-field';
function getImplicitCommonjs () {
return import('implicit-main-type-commonjs');
}
-export {explicit, implicit, implicitModule, getImplicitCommonjs};
+export {explicit, implicit, implicitModule, getImplicitCommonjs, noMain};
export default 'success';
Index: node-v12.22.12/test/fixtures/es-module-specifiers/node_modules/no-main-field/index.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/es-module-specifiers/node_modules/no-main-field/index.js
@@ -0,0 +1,3 @@
+'use strict';
+module.exports = 'no main field';
+
Index: node-v12.22.12/test/fixtures/es-module-specifiers/node_modules/no-main-field/package.json
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/es-module-specifiers/node_modules/no-main-field/package.json
@@ -0,0 +1 @@
+{}
Index: node-v12.22.12/test/parallel/test-module-prototype-mutation.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/parallel/test-module-prototype-mutation.js
@@ -0,0 +1,13 @@
+'use strict';
+const common = require('../common');
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+
+assert.strictEqual(
+ require(fixtures.path('es-module-specifiers', 'node_modules', 'no-main-field')),
+ 'no main field'
+);
+
+import(fixtures.fileURL('es-module-specifiers', 'index.mjs'))
+ .then(common.mustCall((module) => assert.strictEqual(module.noMain, 'no main field')));
+
Index: node-v12.22.12/test/message/source_map_throw_catch.out
===================================================================
--- node-v12.22.12.orig/test/message/source_map_throw_catch.out
+++ node-v12.22.12/test/message/source_map_throw_catch.out
@@ -8,7 +8,7 @@ Error: an exception
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
- at Module.require (internal/modules/cjs/loader.js:*)
+ at Module.internalRequire (internal/modules/cjs/loader.js:*)
at require (internal/modules/cjs/helpers.js:*)
at Object.<anonymous> (*source_map_throw_catch.js:6:3)
at Module._compile (internal/modules/cjs/loader.js:*)
Index: node-v12.22.12/test/message/source_map_throw_first_tick.out
===================================================================
--- node-v12.22.12.orig/test/message/source_map_throw_first_tick.out
+++ node-v12.22.12/test/message/source_map_throw_first_tick.out
@@ -8,7 +8,7 @@ Error: an exception
at Object.Module._extensions..js (internal/modules/cjs/loader.js:*)
at Module.load (internal/modules/cjs/loader.js:*)
at Function.Module._load (internal/modules/cjs/loader.js:*)
- at Module.require (internal/modules/cjs/loader.js:*)
+ at Module.internalRequire (internal/modules/cjs/loader.js:*)
at require (internal/modules/cjs/helpers.js:*)
at Object.<anonymous> (*source_map_throw_first_tick.js:5:1)
at Module._compile (internal/modules/cjs/loader.js:*)
Index: node-v12.22.12/src/env.h
===================================================================
--- node-v12.22.12.orig/src/env.h
+++ node-v12.22.12/src/env.h
@@ -161,6 +161,7 @@ constexpr size_t kFsStatsBufferLength =
V(napi_wrapper, "node:napi:wrapper") \
V(sab_lifetimepartner_symbol, "node:sharedArrayBufferLifetimePartner") \
V(untransferable_object_private_symbol, "node:untransferableObject") \
+ V(require_private_symbol, "node:require_private_symbol")
// Symbols are per-isolate primitives but Environment proxies them
// for the sake of convenience.
Index: node-v12.22.12/test/fixtures/policy-manifest/object-define-property-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/object-define-property-bypass.js
@@ -0,0 +1,19 @@
+let requires = new WeakMap()
+Object.defineProperty(Object.getPrototypeOf(module), 'require', {
+ get() {
+ return requires.get(this);
+ },
+ set(v) {
+ requires.set(this, v);
+ process.nextTick(() => {
+ let fs = Reflect.apply(v, this, ['fs'])
+ if (typeof fs.readFileSync === 'function') {
+ process.exit(1);
+ }
+ })
+ return requires.get(this);
+ },
+ configurable: true
+})
+
+require('./valid-module')
Index: node-v12.22.12/test/fixtures/policy-manifest/onerror-exit.json
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/onerror-exit.json
@@ -0,0 +1,9 @@
+{
+ "onerror": "exit",
+ "scopes": {
+ "file:": {
+ "integrity": true,
+ "dependencies": {}
+ }
+ }
+}
Index: node-v12.22.12/test/fixtures/policy-manifest/onerror-resource-exit.json
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/onerror-resource-exit.json
@@ -0,0 +1,17 @@
+{
+ "onerror": "exit",
+ "resources": {
+ "./object-define-property-bypass.js": {
+ "integrity": true,
+ "dependencies": {
+ "./valid-module": true
+ }
+ },
+ "./valid-module.js": {
+ "integrity": true,
+ "dependencies": {
+ "fs": true
+ }
+ }
+ }
+}
Index: node-v12.22.12/test/parallel/test-policy-manifest.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/parallel/test-policy-manifest.js
@@ -0,0 +1,63 @@
+'use strict';
+
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+common.requireNoPackageJSONAbove();
+
+const assert = require('assert');
+const { spawnSync } = require('child_process');
+const fixtures = require('../common/fixtures.js');
+
+{
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
+ const result = spawnSync(process.execPath, [
+ '--experimental-policy',
+ policyFilepath,
+ '-e',
+ 'require("os").cpus()',
+ ]);
+
+ assert.notStrictEqual(result.status, 0);
+ const stderr = result.stderr.toString();
+ assert.match(stderr, /ERR_MANIFEST_ASSERT_INTEGRITY/);
+ assert.match(stderr, /The resource was not found in the policy/);
+}
+
+{
+ const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json');
+ const mainModuleBypass = fixtures.path(
+ 'policy-manifest',
+ 'main-module-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_ASSERT_INTEGRITY/);
+ assert.match(stderr, /The resource was not found in the policy/);
+}
+
+{
+ const policyFilepath = fixtures.path(
+ 'policy-manifest',
+ 'onerror-resource-exit.json',
+ );
+ const objectDefinePropertyBypass = fixtures.path(
+ 'policy-manifest',
+ 'object-define-property-bypass.js',
+ );
+ const result = spawnSync(process.execPath, [
+ '--experimental-policy',
+ policyFilepath,
+ objectDefinePropertyBypass,
+ ]);
+
+ assert.strictEqual(result.status, 0);
+}
Index: node-v12.22.12/test/common/index.js
===================================================================
--- node-v12.22.12.orig/test/common/index.js
+++ node-v12.22.12/test/common/index.js
@@ -710,6 +710,20 @@ function gcUntil(name, condition) {
});
}
+function requireNoPackageJSONAbove(dir = __dirname) {
+ let possiblePackage = path.join(dir, '..', 'package.json');
+ let lastPackage = null;
+ while (possiblePackage !== lastPackage) {
+ if (fs.existsSync(possiblePackage)) {
+ assert.fail(
+ 'This test shouldn\'t load properties from a package.json above ' +
+ `its file location. Found package.json at ${possiblePackage}.`);
+ }
+ lastPackage = possiblePackage;
+ possiblePackage = path.join(possiblePackage, '..', '..', 'package.json');
+ }
+}
+
const common = {
allowGlobals,
buildType,
@@ -751,6 +765,7 @@ const common = {
platformTimeout,
printSkipMessage,
pwdCommand,
+ requireNoPackageJSONAbove,
runWithInvalidFD,
skip,
skipIf32Bits,
Index: node-v12.22.12/lib/internal/policy/manifest.js
===================================================================
--- node-v12.22.12.orig/lib/internal/policy/manifest.js
+++ node-v12.22.12/lib/internal/policy/manifest.js
@@ -115,7 +115,7 @@ class Manifest {
}
this.#reaction = reaction;
- const manifestEntries = ObjectEntries(obj.resources);
+ const manifestEntries = ObjectEntries(obj.resources ? obj.resources : ObjectCreate(null));
const parsedURLs = new SafeMap();
for (let i = 0; i < manifestEntries.length; i++) {
Index: node-v12.22.12/test/fixtures/policy-manifest/valid-module.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/valid-module.js
@@ -0,0 +1,2 @@
+
+
Index: node-v12.22.12/test/fixtures/policy-manifest/main-module-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/main-module-bypass.js
@@ -0,0 +1 @@
+process.mainModule.require('os').cpus();

28
CVE-2023-23920.patch Normal file
View File

@ -0,0 +1,28 @@
commit 97a0443f1369e65cf656a529b2f5433bfd56ad92
Author: RafaelGSS <rafael.nunu@hotmail.com>
Date: Wed Jan 18 17:37:37 2023 -0300
build: build ICU with ICU_NO_USER_DATA_OVERRIDE
CVE-ID: CVE-2023-23920
Backport-PR-URL: https://github.com/nodejs-private/node-private/pull/377
PR-URL: https://github.com/nodejs-private/node-private/pull/374
Refs: https://hackerone.com/bugs?subject=nodejs&report_id=1625036
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Richard Lau <rlau@redhat.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
diff --git a/configure.py b/configure.py
index 892e1d4202..9a75dbb76d 100755
--- a/configure.py
+++ b/configure.py
@@ -1487,6 +1487,9 @@ def configure_intl(o):
# always set icu_small, node.gyp depends on it being defined.
o['variables']['icu_small'] = b(False)
+ # prevent data override
+ o['defines'] += ['ICU_NO_USER_DATA_OVERRIDE']
+
with_intl = options.with_intl
with_icu_source = options.with_icu_source
have_icu_path = bool(options.with_icu_path)

62
CVE-2023-30581.patch Normal file
View File

@ -0,0 +1,62 @@
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
Index: node-v12.22.12/lib/internal/modules/cjs/loader.js
===================================================================
--- node-v12.22.12.orig/lib/internal/modules/cjs/loader.js
+++ node-v12.22.12/lib/internal/modules/cjs/loader.js
@@ -167,6 +167,7 @@ function Module(id = '', parent) {
if (manifest) {
const moduleURL = pathToFileURL(id);
redirects = manifest.getRedirector(moduleURL);
+ setOwnProperty(this.__proto__, 'require', makeRequireFunction(this, redirects));
}
setOwnProperty(this, 'require', makeRequireFunction(this, redirects));
// Loads a module at the given file path. Returns that module's
@@ -708,7 +709,7 @@ Module._load = function(request, parent,
const module = cachedModule || new Module(filename, parent);
if (isMain) {
- process.mainModule = module;
+ setOwnProperty(process, 'mainModule', module);
setOwnProperty(module.require, 'main', process.mainModule);
module.id = '.';
}
Index: node-v12.22.12/test/fixtures/policy-manifest/main-module-proto-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/main-module-proto-bypass.js
@@ -0,0 +1 @@
+process.mainModule.__proto__.require("os")
Index: node-v12.22.12/test/parallel/test-policy-manifest.js
===================================================================
--- node-v12.22.12.orig/test/parallel/test-policy-manifest.js
+++ node-v12.22.12/test/parallel/test-policy-manifest.js
@@ -61,3 +61,18 @@ const fixtures = require('../common/fixt
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_ASSERT_INTEGRITY/);
+ assert.match(stderr, /The resource was not found in the policy/);
+}
+

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-v12.22.12/doc/api/crypto.md
===================================================================
--- node-v12.22.12.orig/doc/api/crypto.md
+++ node-v12.22.12/doc/api/crypto.md
@@ -632,12 +632,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
@@ -699,6 +704,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
@@ -3527,6 +3536,7 @@ See the [list of SSL OP Flags][] for det
</table>
[`Buffer`]: buffer.html
+[`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
@@ -3559,6 +3569,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-v12.22.12/test/parallel/test-crypto-dh.js
===================================================================
--- node-v12.22.12.orig/test/parallel/test-crypto-dh.js
+++ node-v12.22.12/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();
@@ -471,3 +472,59 @@ assert.throws(
'crypto.getDiffieHellman(\'modp1\').setPublicKey(\'\') ' +
'failed to throw the expected error.'
);
+
+{
+ 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']);
+}
+

View File

@ -0,0 +1,122 @@
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-v12.22.12/test/fixtures/policy-manifest/createRequire-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/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-v12.22.12/test/fixtures/policy-manifest/main-constructor-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/main-constructor-bypass.js
@@ -0,0 +1,2 @@
+const m = new require.main.constructor();
+m.require('./invalid-module')
Index: node-v12.22.12/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/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-v12.22.12/test/fixtures/policy-manifest/manifest-impersonate.json
===================================================================
--- /dev/null
+++ node-v12.22.12/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-v12.22.12/test/fixtures/policy-manifest/module-constructor-bypass.js
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy-manifest/module-constructor-bypass.js
@@ -0,0 +1 @@
+module.constructor._load('child_process');
Index: node-v12.22.12/test/parallel/test-policy-manifest.js
===================================================================
--- node-v12.22.12.orig/test/parallel/test-policy-manifest.js
+++ node-v12.22.12/test/parallel/test-policy-manifest.js
@@ -76,3 +76,58 @@ const fixtures = require('../common/fixt
assert.match(stderr, /The resource was not found in the policy/);
}
+
+{
+ 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, /ERR_MANIFEST_ASSERT_INTEGRITY/);
+}
+
+{
+ 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, /policy is not defined/);
+}
+
+{
+ 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, /ERR_MANIFEST_ASSERT_INTEGRITY/);
+}
+
+{
+ 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, /ERR_MANIFEST_ASSERT_INTEGRITY/);
+}

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-v12.22.12/doc/api/deprecations.md
===================================================================
--- node-v12.22.12.orig/doc/api/deprecations.md
+++ node-v12.22.12/doc/api/deprecations.md
@@ -2162,6 +2162,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.
+
<a id="DEP0112"></a>
### DEP0112: `dgram` private APIs
<!-- YAML
@@ -2710,3 +2713,5 @@ const moduleParents = Object.values(requ
[from_arraybuffer]: buffer.html#buffer_static_method_buffer_from_arraybuffer_byteoffset_length
[from_string_encoding]: buffer.html#buffer_static_method_buffer_from_string_encoding
[legacy `urlObject`]: url.html#url_legacy_urlobject
+[policies]: permissions.md#policies
+
Index: node-v12.22.12/doc/api/errors.md
===================================================================
--- node-v12.22.12.orig/doc/api/errors.md
+++ node-v12.22.12/doc/api/errors.md
@@ -610,6 +610,14 @@ A human-readable string describing the r
<a id="nodejs-error-codes"></a>
## Node.js error codes
+<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-v12.22.12/lib/internal/errors.js
===================================================================
--- node-v12.22.12.orig/lib/internal/errors.js
+++ node-v12.22.12/lib/internal/errors.js
@@ -746,6 +746,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-v12.22.12/lib/internal/process/policy.js
===================================================================
--- node-v12.22.12.orig/lib/internal/process/policy.js
+++ node-v12.22.12/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-v12.22.12/test/fixtures/policy/process-binding/app.js
===================================================================
--- /dev/null
+++ node-v12.22.12/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-v12.22.12/test/fixtures/policy/process-binding/policy.json
===================================================================
--- /dev/null
+++ node-v12.22.12/test/fixtures/policy/process-binding/policy.json
@@ -0,0 +1,10 @@
+{
+ "resources": {
+ "./app.js": {
+ "integrity": true,
+ "dependencies": {
+ "assert": true
+ }
+ }
+ }
+}
Index: node-v12.22.12/test/parallel/test-policy-process-binding.js
===================================================================
--- /dev/null
+++ node-v12.22.12/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('assert');
+const { spawnSync } = require('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);

495
CVE-2023-44487.patch Normal file
View File

@ -0,0 +1,495 @@
From 72b4af6143681f528f1d237b21a9a7aee1738832 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sun, 1 Oct 2023 00:05:01 +0900
Subject: [PATCH] Rework session management
Origin:
https://github.com/nghttp2/nghttp2/commit/72b4af6143681f528f1d237b21a9a7aee1738832
---
deps/nghttp2/lib/Makefile.am | 4 ++
deps/nghttp2/lib/includes/nghttp2/nghttp2.h | 17 +++++
deps/nghttp2/lib/nghttp2_option.c | 7 ++
deps/nghttp2/lib/nghttp2_option.h | 6 ++
deps/nghttp2/lib/nghttp2_ratelim.c | 75 +++++++++++++++++++++
deps/nghttp2/lib/nghttp2_ratelim.h | 57 ++++++++++++++++
deps/nghttp2/lib/nghttp2_session.c | 34 +++++++++-
deps/nghttp2/lib/nghttp2_session.h | 12 +++-
deps/nghttp2/lib/nghttp2_time.c | 62 +++++++++++++++++
deps/nghttp2/lib/nghttp2_time.h | 38 +++++++++++
10 files changed, 310 insertions(+), 2 deletions(-)
create mode 100644 deps/nghttp2/lib/nghttp2_ratelim.c
create mode 100644 deps/nghttp2/lib/nghttp2_ratelim.h
create mode 100644 deps/nghttp2/lib/nghttp2_time.c
create mode 100644 deps/nghttp2/lib/nghttp2_time.h
diff --git a/deps/nghttp2/lib/Makefile.am b/deps/nghttp2/lib/Makefile.am
index 24a5bd62..595714d0 100644
--- a/deps/nghttp2/lib/Makefile.am
+++ b/deps/nghttp2/lib/Makefile.am
@@ -49,6 +49,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \
nghttp2_mem.c \
nghttp2_http.c \
nghttp2_rcbuf.c \
+ nghttp2_ratelim.c \
+ nghttp2_time.c \
nghttp2_debug.c
HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
@@ -65,6 +67,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
nghttp2_mem.h \
nghttp2_http.h \
nghttp2_rcbuf.h \
+ nghttp2_ratelim.h \
+ nghttp2_time.h \
nghttp2_debug.h
libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
diff --git a/deps/nghttp2/lib/includes/nghttp2/nghttp2.h b/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
index 9be6eea5..e0128cf5 100644
--- a/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
+++ b/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
@@ -2682,6 +2682,23 @@ NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
size_t val);
+/**
+ * @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
*
diff --git a/deps/nghttp2/lib/nghttp2_option.c b/deps/nghttp2/lib/nghttp2_option.c
index 34348e66..0d9a4044 100644
--- a/deps/nghttp2/lib/nghttp2_option.c
+++ b/deps/nghttp2/lib/nghttp2_option.c
@@ -126,3 +126,10 @@ void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
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;
+}
diff --git a/deps/nghttp2/lib/nghttp2_option.h b/deps/nghttp2/lib/nghttp2_option.h
index 939729fd..e6ba9100 100644
--- a/deps/nghttp2/lib/nghttp2_option.h
+++ b/deps/nghttp2/lib/nghttp2_option.h
@@ -68,12 +68,18 @@ typedef enum {
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
+ NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15,
} nghttp2_option_flag;
/**
* Struct to store option values for nghttp2_session.
*/
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
*/
diff --git a/deps/nghttp2/lib/nghttp2_ratelim.c b/deps/nghttp2/lib/nghttp2_ratelim.c
new file mode 100644
index 00000000..7011655b
--- /dev/null
+++ b/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;
+}
diff --git a/deps/nghttp2/lib/nghttp2_ratelim.h b/deps/nghttp2/lib/nghttp2_ratelim.h
new file mode 100644
index 00000000..866ed3f0
--- /dev/null
+++ b/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 */
diff --git a/deps/nghttp2/lib/nghttp2_session.c b/deps/nghttp2/lib/nghttp2_session.c
index 39f81f49..3f5f76f2 100644
--- a/deps/nghttp2/lib/nghttp2_session.c
+++ b/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 **session_ptr,
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 **session_ptr,
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,
@@ -4142,6 +4153,23 @@ static int session_process_priority_frame(nghttp2_session *session) {
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;
@@ -4171,7 +4199,8 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
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) {
@@ -6973,6 +7002,9 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
nghttp2_mem_free(mem, item);
return rv;
}
+
+ session->goaway_flags |= NGHTTP2_GOAWAY_SUBMITTED;
+
return 0;
}
diff --git a/deps/nghttp2/lib/nghttp2_session.h b/deps/nghttp2/lib/nghttp2_session.h
index 07bfbb6c..9d429921 100644
--- a/deps/nghttp2/lib/nghttp2_session.h
+++ b/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;
diff --git a/deps/nghttp2/lib/nghttp2_time.c b/deps/nghttp2/lib/nghttp2_time.c
new file mode 100644
index 00000000..2a5f1a6f
--- /dev/null
+++ b/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 */
diff --git a/deps/nghttp2/lib/nghttp2_time.h b/deps/nghttp2/lib/nghttp2_time.h
new file mode 100644
index 00000000..03c0bbe9
--- /dev/null
+++ b/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 */
--
2.33.0

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 3
%global baserelease 8
%{?!_pkgdocdir:%global _pkgdocdir %{_docdir}/%{name}-%{version}}
%global nodejs_epoch 1
%global nodejs_major 12
@ -83,13 +83,33 @@ Patch0001: 0001-Disable-running-gyp-on-shared-deps.patch
Patch0002: 0002-Install-both-binaries-and-use-libdir.patch
Patch0003: 0004-Make-AARCH64-compile-on-64KB-physical-pages.patch
Patch00010: 0005-use-getauxval-in-node_main_cc.patch
Patch00011: CVE-2022-43548-pre-1.patch
Patch00012: CVE-2022-43548-pre-2.patch
Patch00013: CVE-2022-43548.patch
Patch00014: CVE-2023-0286.patch
Patch00015: CVE-2023-0215.patch
Patch00016: CVE-2022-4304.patch
Patch00017: CVE-2022-4450.patch
Patch00011: CVE-2022-32212.patch
Patch00012: CVE-2022-32213-CVE-2022-32214-CVE-2022-32215.patch
Patch00013: CVE-2022-35256.patch
Patch00014: CVE-2022-43548.patch
Patch00015: CVE-2023-0286.patch
Patch00016: CVE-2023-0215.patch
Patch00017: CVE-2022-4304.patch
Patch00018: CVE-2022-4450.patch
Patch00019: CVE-2023-23918.patch
Patch00020: CVE-2023-30581.patch
Patch00021: CVE-2023-32002-CVE-2023-32006.patch
Patch00022: CVE-2023-30589.patch
Patch00023: CVE-2023-30590.patch
Patch00024: CVE-2022-25881.patch
Patch00025: CVE-2023-23920.patch
Patch00026: CVE-2023-32559.patch
# https://github.com/openssl/openssl/commit/879f7080d7e141f415c79eaa3a8ac4a3dad0348b
Patch00027: CVE-2023-0464.patch
# https://github.com/openssl/openssl/commit/b013765abfa80036dc779dd0e50602c57bb3bf95
Patch00028: CVE-2023-0465.patch
# https://github.com/nghttp2/nghttp2/commit/72b4af6143681f528f1d237b21a9a7aee1738832
Patch00029: CVE-2023-44487.patch
Patch00030: CVE-2023-46809.patch
Patch00031: CVE-2024-22019.patch
Patch00032: CVE-2024-22025.patch
Patch00033: CVE-2024-27982.patch
Patch00034: CVE-2024-27983.patch
BuildRequires: python3-devel
BuildRequires: zlib-devel
@ -492,6 +512,24 @@ end
%{_pkgdocdir}/npm/docs
%changelog
* Thu Sep 19 2024 yaoxin <yao_xin001@hoperun.com>- 1:12.22.11-8
- 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-7
- Fix CVE-2023-44487
* Mon Feb 05 2024 yaoxin <yao_xin001@hoperun.com> - 1:12.22.11-6
- Fix CVE-2023-0464 and CVE-2023-0465
* Thu Oct 26 2023 wangkai <13474090681@163.com> - 1:12.22.11-5
- Update CVE-2023-23918.patch for fix nodejs-raw-body,nodejs-istanbul build error
* Tue Aug 29 2023 wangkai <13474090681@163.com> - 1:12.22.11-4
- Fix CVE-2022-32212,CVE-2022-32213,CVE-2022-32214,CVE-2022-32215,
- CVE-2022-25881,CVE-2023-23918,CVE-2023-23920,CVE-2023-30589,
- CVE-2023-30590,CVE-2023-30581,CVE-2023-32002,CVE-2023-32006,
- CVE-2023-32559,CVE-2022-35256
* Wed Feb 22 2023 yaoxin <yaoxin30@h-partners.com> - 1:12.22.11-3
- Fix CVE-2023-0286,CVE-2023-0215,CVE-2022-4304 and CVE-2022-4450