From de6dd67790a8bedbf90a8103a0ca874e224e287c Mon Sep 17 00:00:00 2001 From: Richard Lau Date: Thu, 19 Jan 2023 17:31:08 +0000 Subject: [PATCH] crypto: avoid hang when no algorithm available Avoid an endless loop if no algorithm is available to seed the cryptographically secure pseudorandom number generator (CSPRNG). Co-authored-by: Anna Henningsen PR-URL: https://github.com/nodejs/node/pull/46237 Fixes: https://github.com/nodejs/node/issues/46200 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis --- src/crypto/crypto_util.cc | 14 +++++++++ test/fixtures/openssl3-conf/base_only.cnf | 12 +++++++ test/parallel/test-crypto-no-algorithm.js | 38 +++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 test/fixtures/openssl3-conf/base_only.cnf create mode 100644 test/parallel/test-crypto-no-algorithm.js diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc index 33d0baa7cd86e2..59ae7f8be981a2 100644 --- a/src/crypto/crypto_util.cc +++ b/src/crypto/crypto_util.cc @@ -65,6 +65,20 @@ MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length) { if (1 == RAND_status()) if (1 == RAND_bytes(static_cast(buffer), length)) return {true}; +#if OPENSSL_VERSION_MAJOR >= 3 + const auto code = ERR_peek_last_error(); + // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() + // and RAND_status() but fail in RAND_bytes() if it cannot look up + // a matching algorithm for the CSPRNG. + if (ERR_GET_LIB(code) == ERR_LIB_RAND) { + const auto reason = ERR_GET_REASON(code); + if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || + reason == RAND_R_UNABLE_TO_FETCH_DRBG || + reason == RAND_R_UNABLE_TO_CREATE_DRBG) { + return {false}; + } + } +#endif } while (1 == RAND_poll()); return {false}; diff --git a/test/fixtures/openssl3-conf/base_only.cnf b/test/fixtures/openssl3-conf/base_only.cnf new file mode 100644 index 00000000000000..0a2e1bb4c15fbd --- /dev/null +++ b/test/fixtures/openssl3-conf/base_only.cnf @@ -0,0 +1,12 @@ +nodejs_conf = nodejs_init + +[nodejs_init] +providers = provider_sect + +# List of providers to load +[provider_sect] +base = base_sect + +[base_sect] +activate = 1 + diff --git a/test/parallel/test-crypto-no-algorithm.js b/test/parallel/test-crypto-no-algorithm.js new file mode 100644 index 00000000000000..37db38cf613b65 --- /dev/null +++ b/test/parallel/test-crypto-no-algorithm.js @@ -0,0 +1,38 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +if (!common.hasOpenSSL3) + common.skip('this test requires OpenSSL 3.x'); + +const assert = require('node:assert/strict'); +const crypto = require('node:crypto'); + +if (common.isMainThread) { + // TODO(richardlau): Decide if `crypto.setFips` should error if the + // provider named "fips" is not available. + crypto.setFips(1); + crypto.randomBytes(20, common.mustCall((err) => { + // crypto.randomBytes should either succeed or fail but not hang. + if (err) { + assert.match(err.message, /digital envelope routines::unsupported/); + const expected = /random number generator::unable to fetch drbg/; + assert(err.opensslErrorStack.some((msg) => expected.test(msg)), + `did not find ${expected} in ${err.opensslErrorStack}`); + } + })); +} + +{ + // Startup test. Should not hang. + const { path } = require('../common/fixtures'); + const { spawnSync } = require('node:child_process'); + const baseConf = path('openssl3-conf', 'base_only.cnf'); + const cp = spawnSync(process.execPath, + [ `--openssl-config=${baseConf}`, '-p', '"hello"' ], + { encoding: 'utf8' }); + assert(common.nodeProcessAborted(cp.status, cp.signal), + `process did not abort, code:${cp.status} signal:${cp.signal}`); +}