http2: only call into JS when necessary for session events · nodejs/node@dd60d35 · GitHub
Skip to content

Commit dd60d35

Browse files
addaleaxBethGriggs
authored andcommitted
http2: only call into JS when necessary for session events
For some JS events, it only makes sense to call into JS when there are listeners for the event in question. The overhead is noticeable if a lot of these events are emitted during the lifetime of a session. To reduce this overhead, keep track of whether any/how many JS listeners are present, and if there are none, skip calls into JS altogether. This is part of performance improvements to mitigate CVE-2019-9513. Backport-PR-URL: #29124 PR-URL: #29122 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 00f6846 commit dd60d35

4 files changed

Lines changed: 163 additions & 10 deletions

File tree

lib/internal/http2/core.js

Lines changed: 112 additions & 7 deletions

src/env.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class ModuleWrap;
161161
V(family_string, "family") \
162162
V(fatal_exception_string, "_fatalException") \
163163
V(fd_string, "fd") \
164+
V(fields_string, "fields") \
164165
V(file_string, "file") \
165166
V(fingerprint_string, "fingerprint") \
166167
V(flags_string, "flags") \

src/node_http2.cc

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ using v8::ObjectTemplate;
2020
using v8::String;
2121
using v8::Uint32;
2222
using v8::Uint32Array;
23+
using v8::Uint8Array;
2324
using v8::Undefined;
2425

2526
using node::performance::PerformanceEntry;
@@ -667,6 +668,15 @@ Http2Session::Http2Session(Environment* env,
667668

668669
outgoing_storage_.reserve(4096);
669670
outgoing_buffers_.reserve(32);
671+
672+
{
673+
// Make the js_fields_ property accessible to JS land.
674+
Local<ArrayBuffer> ab =
675+
ArrayBuffer::New(env->isolate(), js_fields_, kSessionUint8FieldCount);
676+
Local<Uint8Array> uint8_arr =
677+
Uint8Array::New(ab, 0, kSessionUint8FieldCount);
678+
/*USE*/(wrap->Set(env->context(), env->fields_string(), uint8_arr));
679+
}
670680
}
671681

672682
void Http2Session::Unconsume() {
@@ -1090,7 +1100,8 @@ inline int Http2Session::OnFrameNotSent(nghttp2_session* handle,
10901100
// Do not report if the frame was not sent due to the session closing
10911101
if (error_code == NGHTTP2_ERR_SESSION_CLOSING ||
10921102
error_code == NGHTTP2_ERR_STREAM_CLOSED ||
1093-
error_code == NGHTTP2_ERR_STREAM_CLOSING) {
1103+
error_code == NGHTTP2_ERR_STREAM_CLOSING ||
1104+
session->js_fields_[kSessionFrameErrorListenerCount] == 0) {
10941105
return 0;
10951106
}
10961107

@@ -1346,7 +1357,8 @@ inline void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) {
13461357
// received. Notifies JS land about the priority change. Note that priorities
13471358
// are considered advisory only, so this has no real effect other than to
13481359
// simply let user code know that the priority has changed.
1349-
inline void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1360+
void Http2Session::HandlePriorityFrame(const nghttp2_frame* frame) {
1361+
if (js_fields_[kSessionPriorityListenerCount] == 0) return;
13501362
Isolate* isolate = env()->isolate();
13511363
HandleScope scope(isolate);
13521364
Local<Context> context = env()->context();
@@ -1413,7 +1425,8 @@ inline void Http2Session::HandleGoawayFrame(const nghttp2_frame* frame) {
14131425
}
14141426

14151427
// Called by OnFrameReceived when a complete ALTSVC frame has been received.
1416-
inline void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1428+
void Http2Session::HandleAltSvcFrame(const nghttp2_frame* frame) {
1429+
if (!(js_fields_[kBitfield] & (1 << kSessionHasAltsvcListeners))) return;
14171430
Isolate* isolate = env()->isolate();
14181431
HandleScope scope(isolate);
14191432
Local<Context> context = env()->context();
@@ -1499,6 +1512,7 @@ inline void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
14991512
return;
15001513
}
15011514

1515+
if (!(js_fields_[kBitfield] & (1 << kSessionHasPingListeners))) return;
15021516
// Notify the session that a ping occurred
15031517
arg = Buffer::Copy(env(),
15041518
reinterpret_cast<const char*>(frame->ping.opaque_data),
@@ -1510,6 +1524,9 @@ inline void Http2Session::HandlePingFrame(const nghttp2_frame* frame) {
15101524
inline void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) {
15111525
bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK;
15121526
if (!ack) {
1527+
js_fields_[kBitfield] &= ~(1 << kSessionRemoteSettingsIsUpToDate);
1528+
if (!(js_fields_[kBitfield] & (1 << kSessionHasRemoteSettingsListeners)))
1529+
return;
15131530
// This is not a SETTINGS acknowledgement, notify and return
15141531
MakeCallback(env()->onsettings_string(), 0, nullptr);
15151532
return;
@@ -3135,6 +3152,16 @@ void Initialize(Local<Object> target,
31353152
NODE_DEFINE_CONSTANT(target, PADDING_BUF_MAX_PAYLOAD_LENGTH);
31363153
NODE_DEFINE_CONSTANT(target, PADDING_BUF_RETURN_VALUE);
31373154

3155+
NODE_DEFINE_CONSTANT(target, kBitfield);
3156+
NODE_DEFINE_CONSTANT(target, kSessionPriorityListenerCount);
3157+
NODE_DEFINE_CONSTANT(target, kSessionFrameErrorListenerCount);
3158+
NODE_DEFINE_CONSTANT(target, kSessionUint8FieldCount);
3159+
3160+
NODE_DEFINE_CONSTANT(target, kSessionHasRemoteSettingsListeners);
3161+
NODE_DEFINE_CONSTANT(target, kSessionRemoteSettingsIsUpToDate);
3162+
NODE_DEFINE_CONSTANT(target, kSessionHasPingListeners);
3163+
NODE_DEFINE_CONSTANT(target, kSessionHasAltsvcListeners);
3164+
31383165
// Method to fetch the nghttp2 string description of an nghttp2 error code
31393166
env->SetMethod(target, "nghttp2ErrorString", HttpErrorString);
31403167

src/node_http2.h

Lines changed: 20 additions & 0 deletions

0 commit comments

Comments
 (0)