From e2562047763a666599b64d016cdbf399dab855d8 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 20 May 2019 01:57:17 +0200 Subject: [PATCH] src: reset SIGSEGV handler before crashing Without this, we would re-enter the signal handler immediately after re-raising the signal, leading to an infinite loop. PR-URL: https://github.com/nodejs/node/pull/27775 Refs: https://github.com/nodejs/node/pull/27246 Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell Reviewed-By: Rich Trott --- src/node.cc | 6 ++++++ src/node_process_methods.cc | 7 +++++++ test/abort/test-signal-handler.js | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/abort/test-signal-handler.js diff --git a/src/node.cc b/src/node.cc index 388a9ea4486a88..f4a953a7c38abf 100644 --- a/src/node.cc +++ b/src/node.cc @@ -487,6 +487,12 @@ void TrapWebAssemblyOrContinue(int signo, siginfo_t* info, void* ucontext) { if (prev != nullptr) { prev(signo, info, ucontext); } else { + // Reset to the default signal handler, i.e. cause a hard crash. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + CHECK_EQ(sigaction(signo, &sa, nullptr), 0); + ResetStdio(); raise(signo); } diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index a9ddd70bcbe2d6..912ac9bec23960 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -63,6 +63,12 @@ static void Abort(const FunctionCallbackInfo& args) { Abort(); } +// For internal testing only, not exposed to userland. +static void CauseSegfault(const FunctionCallbackInfo& args) { + // This should crash hard all platforms. + *static_cast(nullptr) = nullptr; +} + static void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->owns_process_state()); @@ -405,6 +411,7 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "_debugProcess", DebugProcess); env->SetMethod(target, "_debugEnd", DebugEnd); env->SetMethod(target, "abort", Abort); + env->SetMethod(target, "causeSegfault", CauseSegfault); env->SetMethod(target, "chdir", Chdir); } diff --git a/test/abort/test-signal-handler.js b/test/abort/test-signal-handler.js new file mode 100644 index 00000000000000..f68e6881f6e5b6 --- /dev/null +++ b/test/abort/test-signal-handler.js @@ -0,0 +1,23 @@ +'use strict'; +const common = require('../common'); +if (common.isWindows) + common.skip('No signals on Window'); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); + +// Test that a hard crash does not cause an endless loop. + +if (process.argv[2] === 'child') { + const { internalBinding } = require('internal/test/binding'); + const { causeSegfault } = internalBinding('process_methods'); + + causeSegfault(); +} else { + const child = spawnSync(process.execPath, + ['--expose-internals', __filename, 'child'], + { stdio: 'inherit' }); + // FreeBSD and macOS use SIGILL for the kind of crash we're causing here. + assert(child.signal === 'SIGSEGV' || child.signal === 'SIGILL', + `child.signal = ${child.signal}`); +}