http2: cap originSet size to prevent unbounded memory growth · nodejs/node@cc7c11b · GitHub
Skip to content

Commit cc7c11b

Browse files
mcollinaaduh95
authored andcommitted
http2: cap originSet size to prevent unbounded memory growth
A malicious HTTP/2 server can send repeated ORIGIN frames with unique origins, causing unbounded growth of the client-side originSet for the lifetime of the session. Cap the set at 128 entries; once full, new origins from ORIGIN frames are silently dropped. Refs: https://hackerone.com/reports/3676863 PR-URL: nodejs-private/node-private#855 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-48619
1 parent 8e75c73 commit cc7c11b

5 files changed

Lines changed: 141 additions & 1 deletion

File tree

doc/api/errors.md

Lines changed: 11 additions & 0 deletions

doc/api/http2.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,6 +3260,8 @@ changes:
32603260
This is similar to [`server.maxHeadersCount`][] or
32613261
[`request.maxHeadersCount`][] in the `node:http` module. The minimum value
32623262
is `1`. **Default:** `128`.
3263+
* `maxOriginSetSize` {number} Sets the maximum number of uniq origin the sever
3264+
can send via ORIGIN frames. **Default:** `128`.
32633265
* `maxOutstandingPings` {number} Sets the maximum number of outstanding,
32643266
unacknowledged pings. **Default:** `10`.
32653267
* `maxReservedRemoteStreams` {number} Sets the maximum number of reserved push

lib/internal/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,8 @@ E('ERR_HTTP2_STREAM_SELF_DEPENDENCY',
13231323
E('ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS',
13241324
'Number of custom settings exceeds MAX_ADDITIONAL_SETTINGS', Error);
13251325
E('ERR_HTTP2_TOO_MANY_INVALID_FRAMES', 'Too many invalid HTTP/2 frames', Error);
1326+
E('ERR_HTTP2_TOO_MANY_ORIGINS',
1327+
'The server sent more ORIGIN frames than the allowed number of %s', Error);
13261328
E('ERR_HTTP2_TRAILERS_ALREADY_SENT',
13271329
'Trailing headers have already been sent', Error);
13281330
E('ERR_HTTP2_TRAILERS_NOT_READY',

lib/internal/http2/core.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ const {
102102
ERR_HTTP2_STREAM_ERROR,
103103
ERR_HTTP2_STREAM_SELF_DEPENDENCY,
104104
ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
105+
ERR_HTTP2_TOO_MANY_ORIGINS,
105106
ERR_HTTP2_TRAILERS_ALREADY_SENT,
106107
ERR_HTTP2_TRAILERS_NOT_READY,
107108
ERR_HTTP2_UNSUPPORTED_PROTOCOL,
@@ -255,6 +256,7 @@ const kInit = Symbol('init');
255256
const kInfoHeaders = Symbol('sent-info-headers');
256257
const kLocalSettings = Symbol('local-settings');
257258
const kNativeFields = Symbol('kNativeFields');
259+
const kMaxOriginSetSize = Symbol('max-ORIGIN-set-size');
258260
const kOptions = Symbol('options');
259261
const kOwner = owner_symbol;
260262
const kOrigin = Symbol('origin');
@@ -723,8 +725,13 @@ function onOrigin(origins) {
723725
if (!session.encrypted || session.destroyed)
724726
return undefined;
725727
const originSet = initOriginSet(session);
726-
for (let n = 0; n < origins.length; n++)
728+
for (let n = 0; n < origins.length; n++) {
729+
if (originSet.size >= session[kMaxOriginSetSize]) {
730+
session.destroy(new ERR_HTTP2_TOO_MANY_ORIGINS(session[kMaxOriginSetSize]));
731+
return;
732+
}
727733
originSet.add(origins[n]);
734+
}
728735
session.emit('origin', origins);
729736
}
730737

@@ -3579,6 +3586,13 @@ function connect(authority, options, listener) {
35793586
assertIsObject(options, 'options');
35803587
options = { ...options };
35813588

3589+
let { maxOriginSetSize } = options;
3590+
if (maxOriginSetSize != null) {
3591+
validateNumber(maxOriginSetSize, 'options.maxOriginSetSize', 0);
3592+
} else {
3593+
maxOriginSetSize = 128;
3594+
}
3595+
35823596
assertIsArray(options.remoteCustomSettings, 'options.remoteCustomSettings');
35833597
if (options.remoteCustomSettings) {
35843598
options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
@@ -3634,6 +3648,7 @@ function connect(authority, options, listener) {
36343648

36353649
session[kAuthority] = `${options.servername || host}:${port}`;
36363650
session[kProtocol] = protocol;
3651+
session[kMaxOriginSetSize] = maxOriginSetSize;
36373652

36383653
if (typeof listener === 'function')
36393654
session.once('connect', listener);
Lines changed: 110 additions & 0 deletions

0 commit comments

Comments
 (0)