src: handle fatal error when Environment is not assigned to context

Previously when an uncaught JS error is thrown before Environment was
assigned to the context (e.g. a SyntaxError in a per-context script),
it triggered an infinite recursion:

1. The error message listener `node::OnMessage()` triggered
   `node::FatalException()`
2. `node::FatalException()` attempted to get the Environment
   assigned to the context entered using `Environment::GetCurrent()`
3. `Environment::GetCurrent()` previously incorrectly accepted
   out-of-bound access with the length of the embedder data array
   as index, and called `context->GetAlignedPointerFromEmbedderData()`
4. The out-of-bound access in `GetAlignedPointerFromEmbedderData()`
   triggered a fatal error, which was handled by `node::FatalError()`
5. `node::FatalError()` called `Environment::GetCurrent()`, then
   we went back to 3.

This patch fixes the incorrect guard in 3. When
`Environment::GetCurrent()` returns nullptr (when Environment is not
yet assigned to the context) in 2, it now prints the JS stack trace
and crashes directly.

PR-URL: https://github.com/nodejs/node/pull/27236
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
Joyee Cheung 2019-04-15 10:09:21 +08:00
parent 83d1ca7de9
commit cdba9f23ec
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
2 changed files with 39 additions and 17 deletions

View File

@ -302,15 +302,18 @@ inline Environment* Environment::GetCurrent(v8::Isolate* isolate) {
} }
inline Environment* Environment::GetCurrent(v8::Local<v8::Context> context) { inline Environment* Environment::GetCurrent(v8::Local<v8::Context> context) {
if (UNLIKELY(context.IsEmpty() || if (UNLIKELY(context.IsEmpty())) {
context->GetNumberOfEmbedderDataFields() < return nullptr;
ContextEmbedderIndex::kContextTag || }
context->GetAlignedPointerFromEmbedderData( if (UNLIKELY(context->GetNumberOfEmbedderDataFields() <=
ContextEmbedderIndex::kContextTag) != ContextEmbedderIndex::kContextTag)) {
Environment::kNodeContextTagPtr)) { return nullptr;
}
if (UNLIKELY(context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::kContextTag) !=
Environment::kNodeContextTagPtr)) {
return nullptr; return nullptr;
} }
return static_cast<Environment*>( return static_cast<Environment*>(
context->GetAlignedPointerFromEmbedderData( context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::kEnvironment)); ContextEmbedderIndex::kEnvironment));

View File

@ -6,6 +6,7 @@
#ifdef NODE_REPORT #ifdef NODE_REPORT
#include "node_report.h" #include "node_report.h"
#endif #endif
#include "node_v8_platform-inl.h"
namespace node { namespace node {
@ -171,13 +172,10 @@ void PrintStackTrace(Isolate* isolate, Local<StackTrace> stack) {
fflush(stderr); fflush(stderr);
} }
void PrintCaughtException(Isolate* isolate, void PrintException(Isolate* isolate,
Local<Context> context, Local<Context> context,
const v8::TryCatch& try_catch) { Local<Value> err,
CHECK(try_catch.HasCaught()); Local<Message> message) {
Local<Value> err = try_catch.Exception();
Local<Message> message = try_catch.Message();
Local<v8::StackTrace> stack = message->GetStackTrace();
node::Utf8Value reason(isolate, node::Utf8Value reason(isolate,
err->ToDetailString(context).ToLocalChecked()); err->ToDetailString(context).ToLocalChecked());
bool added_exception_line = false; bool added_exception_line = false;
@ -185,7 +183,16 @@ void PrintCaughtException(Isolate* isolate,
GetErrorSource(isolate, context, message, &added_exception_line); GetErrorSource(isolate, context, message, &added_exception_line);
fprintf(stderr, "%s\n", source.c_str()); fprintf(stderr, "%s\n", source.c_str());
fprintf(stderr, "%s\n", *reason); fprintf(stderr, "%s\n", *reason);
PrintStackTrace(isolate, stack);
Local<v8::StackTrace> stack = message->GetStackTrace();
if (!stack.IsEmpty()) PrintStackTrace(isolate, stack);
}
void PrintCaughtException(Isolate* isolate,
Local<Context> context,
const v8::TryCatch& try_catch) {
CHECK(try_catch.HasCaught());
PrintException(isolate, context, try_catch.Exception(), try_catch.Message());
} }
void AppendExceptionLine(Environment* env, void AppendExceptionLine(Environment* env,
@ -777,8 +784,20 @@ void FatalException(Isolate* isolate,
CHECK(!error.IsEmpty()); CHECK(!error.IsEmpty());
HandleScope scope(isolate); HandleScope scope(isolate);
Environment* env = Environment::GetCurrent(isolate); CHECK(isolate->InContext());
CHECK_NOT_NULL(env); // TODO(addaleax): Handle nullptr here. Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) {
// This means that the exception happens before Environment is assigned
// to the context e.g. when there is a SyntaxError in a per-context
// script - which usually indicates that there is a bug because no JS
// error is supposed to be thrown at this point.
// Since we don't have access to Environment here, there is not
// much we can do, so we just print whatever is useful and crash.
PrintException(isolate, context, error, message);
Abort();
}
Local<Object> process_object = env->process_object(); Local<Object> process_object = env->process_object();
Local<String> fatal_exception_string = env->fatal_exception_string(); Local<String> fatal_exception_string = env->fatal_exception_string();
Local<Value> fatal_exception_function = Local<Value> fatal_exception_function =