Skip to content

Commit c3eb187

Browse files
mhdawsonMylesBorins
authored andcommittedApr 16, 2018
n-api: avoid crash in napi_escape_scope()
V8 will crash if escape is called twice on the same scope. Add checks to avoid crashing if napi_escape_scope() is called to try and do this. Add test that tries to call napi_create_scope() twice. Backport-PR-URL: #19447 PR-URL: #13651 Reviewed-By: Jason Ginchereau <jasongin@microsoft.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
1 parent cc3a4af commit c3eb187

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed
 

‎src/node_api.cc

+23-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
#include <node_buffer.h>
1212
#include <node_object_wrap.h>
13+
1314
#include <string.h>
1415
#include <algorithm>
1516
#include <cassert>
1617
#include <cmath>
1718
#include <vector>
1819
#include "uv.h"
1920
#include "node_api.h"
21+
#include "node_internals.h"
2022
#include "env-inl.h"
2123
#include "node_api_backport.h"
2224

@@ -160,14 +162,20 @@ class HandleScopeWrapper {
160162
// across different versions.
161163
class EscapableHandleScopeWrapper {
162164
public:
163-
explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {}
165+
explicit EscapableHandleScopeWrapper(v8::Isolate* isolate)
166+
: scope(isolate), escape_called_(false) {}
167+
bool escape_called() const {
168+
return escape_called_;
169+
}
164170
template <typename T>
165171
v8::Local<T> Escape(v8::Local<T> handle) {
172+
escape_called_ = true;
166173
return scope.Escape(handle);
167174
}
168175

169176
private:
170177
v8::EscapableHandleScope scope;
178+
bool escape_called_;
171179
};
172180

173181
napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) {
@@ -720,7 +728,8 @@ const char* error_messages[] = {nullptr,
720728
"An array was expected",
721729
"Unknown failure",
722730
"An exception is pending",
723-
"The async work item was cancelled"};
731+
"The async work item was cancelled",
732+
"napi_escape_handle already called on scope"};
724733

725734
static napi_status napi_clear_last_error(napi_env env) {
726735
CHECK_ENV(env);
@@ -748,10 +757,14 @@ napi_status napi_get_last_error_info(napi_env env,
748757
CHECK_ENV(env);
749758
CHECK_ARG(env, result);
750759

760+
// you must update this assert to reference the last message
761+
// in the napi_status enum each time a new error message is added.
762+
// We don't have a napi_status_last as this would result in an ABI
763+
// change each time a message was added.
751764
static_assert(
752-
(sizeof (error_messages) / sizeof (*error_messages)) == napi_status_last,
765+
node::arraysize(error_messages) == napi_escape_called_twice + 1,
753766
"Count of error messages must match count of error values");
754-
assert(env->last_error.error_code < napi_status_last);
767+
assert(env->last_error.error_code <= napi_escape_called_twice);
755768

756769
// Wait until someone requests the last error information to fetch the error
757770
// message string
@@ -2213,9 +2226,12 @@ napi_status napi_escape_handle(napi_env env,
22132226

22142227
v8impl::EscapableHandleScopeWrapper* s =
22152228
v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope);
2216-
*result = v8impl::JsValueFromV8LocalValue(
2217-
s->Escape(v8impl::V8LocalValueFromJsValue(escapee)));
2218-
return napi_clear_last_error(env);
2229+
if (!s->escape_called()) {
2230+
*result = v8impl::JsValueFromV8LocalValue(
2231+
s->Escape(v8impl::V8LocalValueFromJsValue(escapee)));
2232+
return napi_clear_last_error(env);
2233+
}
2234+
return napi_set_last_error(env, napi_escape_called_twice);
22192235
}
22202236

22212237
napi_status napi_new_instance(napi_env env,

‎src/node_api_types.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ typedef enum {
6767
napi_generic_failure,
6868
napi_pending_exception,
6969
napi_cancelled,
70-
napi_status_last
70+
napi_escape_called_twice
7171
} napi_status;
7272

7373
typedef napi_value (*napi_callback)(napi_env env,

‎test/addons-napi/test_handle_scope/test.js

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ testHandleScope.NewScope();
1010

1111
assert.ok(testHandleScope.NewScopeEscape() instanceof Object);
1212

13+
assert.throws(
14+
() => {
15+
testHandleScope.NewScopeEscapeTwice();
16+
},
17+
Error);
18+
1319
assert.throws(
1420
() => {
1521
testHandleScope.NewScopeWithException(() => { throw new RangeError(); });

‎test/addons-napi/test_handle_scope/test_handle_scope.c

+14
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ napi_value NewScopeEscape(napi_env env, napi_callback_info info) {
2929
return escapee;
3030
}
3131

32+
napi_value NewScopeEscapeTwice(napi_env env, napi_callback_info info) {
33+
napi_escapable_handle_scope scope;
34+
napi_value output = NULL;
35+
napi_value escapee = NULL;
36+
37+
NAPI_CALL(env, napi_open_escapable_handle_scope(env, &scope));
38+
NAPI_CALL(env, napi_create_object(env, &output));
39+
NAPI_CALL(env, napi_escape_handle(env, scope, output, &escapee));
40+
NAPI_CALL(env, napi_escape_handle(env, scope, output, &escapee));
41+
NAPI_CALL(env, napi_close_escapable_handle_scope(env, scope));
42+
return escapee;
43+
}
44+
3245
napi_value NewScopeWithException(napi_env env, napi_callback_info info) {
3346
napi_handle_scope scope;
3447
size_t argc;
@@ -57,6 +70,7 @@ void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
5770
napi_property_descriptor properties[] = {
5871
DECLARE_NAPI_PROPERTY("NewScope", NewScope),
5972
DECLARE_NAPI_PROPERTY("NewScopeEscape", NewScopeEscape),
73+
DECLARE_NAPI_PROPERTY("NewScopeEscapeTwice", NewScopeEscapeTwice),
6074
DECLARE_NAPI_PROPERTY("NewScopeWithException", NewScopeWithException),
6175
};
6276

0 commit comments

Comments
 (0)
Please sign in to comment.