CVE-2022-3996
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Node.js is a JavaScript runtime built on the V8 engine that lets developers run JavaScript outside the browser to build servers, APIs, and command line tools. Every official Node.js build ships with its own copy of OpenSSL compiled into the binary, and that bundled library provides the TLS, X.509 certificate, and cryptography features exposed through the Node.js crypto and tls modules.
An improper locking vulnerability (CVE-2022-3996) has been identified in the OpenSSL library bundled with Node.js. If an X.509 certificate contains a malformed policy constraint and X.509 policy processing is enabled, OpenSSL takes the same write lock twice on the certificate error path. On operating systems whose read/write locks are not recursive, most widely Windows, the calling thread blocks on a lock it already holds and the process hangs, causing a denial of service.
Per MITRE CWE-667, Improper Locking: "the product does not properly acquire or release a lock on a resource, leading to unexpected resource state changes and behaviors." Here the unexpected state is a self-deadlock: the affected thread stops making progress, and because certificate verification typically happens on a request path, the hang can take the surrounding Node.js process down with it.
This issue affects the Node.js 17.x, 18.x, and 19.x release lines up to the versions listed below, all of which bundle OpenSSL 3.0.0 through 3.0.7. The Node.js 16.x line and earlier bundle OpenSSL 1.1.1, which is not affected by this specific locking defect.
Details
Module Info
- Product: Node.js
- Affected packages: node (the Node.js runtime distribution, including the bundled OpenSSL dependency under
deps/openssl) - Affected versions:
- 17.x <=17.9.1
- 18.x <18.14.1
- 19.x <19.6.1
- GitHub repository: https://github.com/nodejs/node
- Published packages: https://github.com/nodejs/node/releases
- Package manager: Not applicable — Node.js is distributed as runtime builds from nodejs.org rather than as a published npm package; the bundled OpenSSL is compiled directly into the runtime binary.
- Fixed in: Node.js NES v16.20.3 (July 30, 2024)
Vulnerability Info
This High-severity vulnerability is found in the OpenSSL library that Node.js bundles and links into its own binary, in every Node.js release that ships OpenSSL 3.0.0 through 3.0.7. NVD and the CISA Coordinator both publish CVSS v3.1 7.5 (High) for the availability impact, while the OpenSSL project rates the same issue Low because policy processing is off by default and rarely enabled on public facing servers; the upstream fix commit is itself titled "Fixes LOW CVE-2022-3996."
A GitHub Security Advisory, GHSA-vr8j-hgmm-jh9r, also covers this flaw, but it is scoped to the Rust openssl-src crate rather than to Node.js or npm, so its score reflects a different ecosystem's packaging of the same OpenSSL flaw and is not a Node.js severity assessment. That advisory carries no CVSS v3.1 score at all; its only published score is CVSS v4.0 8.7.
X.509 certificates can carry certificate policy and policy mapping extensions, and a verifier can be told to require and evaluate them. OpenSSL only performs that evaluation when policy processing is explicitly turned on, either by passing the -policy argument to the OpenSSL command line utilities or by an application calling X509_VERIFY_PARAM_add0_policy() or X509_VERIFY_PARAM_set1_policies() (see the OpenSSL X509_VERIFY_PARAM documentation). With policy processing off, the vulnerable code path is never reached.
When policy processing is on, OpenSSL builds a policy cache for each certificate. ossl_policy_cache_set() acquires the certificate's write lock and then calls ossl_policy_cache_set_mapping() in crypto/x509/pcy_map.c. On the bad_mapping error path, reached when the certificate's policy mappings are malformed, that function acquired the very same write lock a second time in order to set the invalid-policy flag:
if (ret == -1 && CRYPTO_THREAD_write_lock(x->lock)) {
x->ex_flags |= EXFLAG_INVALID_POLICY;
CRYPTO_THREAD_unlock(x->lock);
}
OpenSSL's read/write locks are not recursive, so the second acquisition waits on a lock the current thread already owns. On Windows and other platforms with the same locking semantics the thread deadlocks and the process hangs. The redundant block, which had been added in 2021 to silence a static-analysis data race report, was removed in OpenSSL commit 7725e7b and shipped in OpenSSL 3.0.8 on February 7, 2023. Node.js picked up that OpenSSL release in Node.js 18.14.1 and Node.js 19.6.1, both published on February 16, 2023. The Node.js 17.x line reached End-of-Life on June 1, 2022 and never received the update.
Note: The Node.js project assessed this OpenSSL advisory and concluded that Node.js itself is not affected. Per the Node.js OpenSSL 3.0.7 update assessment: "Node.js doesn't call OpenSSL as a separate process (so the possibility to use the -policy flag is invalid), nor call the functions X509_VERIFY_PARAM_add0_policy() and X509_VERIFY_PARAM_set1_policies(). Therefore, Node.js is not affected by this vulnerability." HeroDevs still lists this entry under Node.js because NES customers scan their deployments for the CVE ID against the bundled OpenSSL version, regardless of whether the vulnerable code path is reachable through Node.js's own API surface. The vulnerable code is still present in the OpenSSL that these Node.js versions bundle, so native addons and embedders that call those OpenSSL policy APIs directly against the bundled library remain exposed, and software composition scanners continue to flag the bundled OpenSSL version. Builds configured with --shared-openssl use the host system's OpenSSL instead, so their exposure depends on that library's version rather than the bundled one.
Mitigation
The Node.js 17.x, 18.x, and 19.x release lines are all End-of-Life and will not receive any further updates to address this issue. For more information see here.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a Node.js release line that still receives official security updates.
- Migrate affected applications away from the End-of-Life Node.js release lines.
- Leverage a commercial support partner like HeroDevs for post-EOL security support, such as Node.js NES, which shipped this fix as part of its cumulative OpenSSL 3.0 catch-up release, v16.20.3 (July 30, 2024).
Credits
- Polar Bear (reporter)
- Dr Paul Dale from the OpenSSL project (remediation developer)