Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "node_version.h"

#if HAVE_OPENSSL
#include <openssl/conf.h>
#include "ncrypto.h"
#include "node_crypto.h"
#if OPENSSL_VERSION_MAJOR >= 3 && !defined(CONF_MFLAGS_IGNORE_MISSING_FILE)
Expand Down Expand Up @@ -135,6 +136,7 @@
#include <cstdlib>
#include <cstring>

#include <fstream>
#include <string>
#include <tuple>
#include <vector>
Expand Down Expand Up @@ -1099,6 +1101,17 @@ bool CanEnableWebAssemblyTrapHandler() {
}
#endif // NODE_USE_V8_WASM_TRAP_HANDLER

#if HAVE_OPENSSL && !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_MAJOR >= 3
// Whether the configuration file that OpenSSL uses when none was requested
// explicitly can be opened for reading.
static bool DefaultOpenSSLConfIsReadable() {
char* path = CONF_get1_default_config_file();
if (path == nullptr) return false;
auto free_path = OnScopeLeave([&]() { OPENSSL_free(path); });
return std::ifstream(path).is_open();
}
#endif

static std::shared_ptr<InitializationResultImpl>
InitializeOncePerProcessInternal(const std::vector<std::string>& args,
ProcessInitializationFlags::Flags flags =
Expand Down Expand Up @@ -1229,6 +1242,16 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, settings);
OPENSSL_INIT_free(settings);

// CONF_MFLAGS_IGNORE_MISSING_FILE only covers a missing file, not a default
// configuration file that exists but cannot be opened, e.g. when /etc/ssl
// is not readable by the current user. Loading it was not something the
// user asked for, so ignore that failure as well instead of refusing to
// start. Refs: https://github.com/nodejs/node/issues/62230
if (ERR_peek_error() != 0 && conf_file == nullptr &&
!DefaultOpenSSLConfIsReadable()) {
ERR_clear_error();
}

if (ERR_peek_error() != 0) {
// XXX: ERR_GET_REASON does not return something that is
// useful as an exit code at all.
Expand Down
33 changes: 33 additions & 0 deletions test/parallel/test-openssl-unreadable-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

// An unreadable default OpenSSL configuration file must not abort startup.
// Refs: https://github.com/nodejs/node/issues/62230

const common = require('../common');
const assert = require('node:assert');
const { spawnSync } = require('node:child_process');

if (!common.hasCrypto)
common.skip('missing crypto');
if (!common.isLinux)
common.skip('linux only');
if (process.config.variables.node_shared_openssl)
common.skip('shared openssl may read a different configuration file');

// Replace /etc/ssl with an empty tmpfs in a private mount namespace, where
// openssl.cnf is a symlink loop: opening it then fails with ELOOP instead of
// ENOENT, which OpenSSL ignores on its own. The namespace goes away with the
// process, so the host /etc/ssl is left alone.
const setup = 'mount -t tmpfs tmpfs /etc/ssl && ln -s openssl.cnf /etc/ssl/openssl.cnf';

if (spawnSync('unshare', ['-Urm', 'sh', '-c', setup]).status !== 0)
common.skip('cannot set up an unprivileged user and mount namespace');

const child = spawnSync(
'unshare',
['-Urm', 'sh', '-c', `${setup} && exec "$0" -p 42`, process.execPath],
{ encoding: 'utf8' });

assert.strictEqual(child.stderr, '');
assert.strictEqual(child.status, 0);
assert.strictEqual(child.stdout.trim(), '42');
Loading