Repository: nodejs/node
Pull request: http2: validate initialWindowSize per HTTP/2 spec

The HTTP/2 spec (RFC 7540) defines SETTINGS_INITIAL_WINDOW_SIZE maximum as 2^31-1. Values above this must be treated as a FLOW_CONTROL_ERROR. Previously, Node.js allowed values up to 2^32-1 which caused nghttp2_submit_settings() to return NGHTTP2_ERR_INVALID_ARGUMENT, triggering an uncatchable assertion failure and crashing the process.

This change adds proper validation to reject values >= 2^31 with a catchable RangeError before they reach nghttp2.

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/http2/core.js
patch: @@ -230,6 +230,7 @@ function debugSessionObj(session, message, ...args) {
 
 const kMaxFrameSize = (2 ** 24) - 1;
 const kMaxInt = (2 ** 32) - 1;
+const kMaxInitialWindowSize = (2 ** 31) - 1;  // HTTP/2 spec maximum
 const kMaxStreams = (2 ** 32) - 1;
 const kMaxALTSVC = (2 ** 14) - 2;
 
@@ -989,7 +990,7 @@ function pingCallback(cb) {
 
 // Validates the values in a settings object. Specifically:
 // 1. headerTableSize must be a number in the range 0 <= n <= kMaxInt
-// 2. initialWindowSize must be a number in the range 0 <= n <= kMaxInt
+// 2. initialWindowSize must be a number in the range 0 <= n <= 2^31-1
 // 3. maxFrameSize must be a number in the range 16384 <= n <= kMaxFrameSize
 // 4. maxConcurrentStreams must be a number in the range 0 <= n <= kMaxStreams
 // 5. maxHeaderListSize must be a number in the range 0 <= n <= kMaxInt
@@ -1014,7 +1015,7 @@ const validateSettings = hideStackFrames((settings) => {
                                       0, kMaxInt);
   assertWithinRange.withoutStackTrace('initialWindowSize',
                                       settings.initialWindowSize,
-                                      0, kMaxInt);
+                                      0, kMaxInitialWindowSize);
   assertWithinRange.withoutStackTrace('maxFrameSize',
                                       settings.maxFrameSize,
                                       16384, kMaxFrameSize);

filename: test/parallel/test-http2-getpackedsettings.js
patch: @@ -20,7 +20,7 @@ assert.deepStrictEqual(val, check);
   ['headerTableSize', 0],
   ['headerTableSize', 2 ** 32 - 1],
   ['initialWindowSize', 0],
-  ['initialWindowSize', 2 ** 32 - 1],
+  ['initialWindowSize', 2 ** 31 - 1],  // Max per HTTP/2 spec
   ['maxFrameSize', 16384],
   ['maxFrameSize', 2 ** 24 - 1],
   ['maxConcurrentStreams', 0],
@@ -42,6 +42,8 @@ http2.getPackedSettings({ enablePush: false });
   ['headerTableSize', -1],
   ['headerTableSize', 2 ** 32],
   ['initialWindowSize', -1],
+  ['initialWindowSize', 2 ** 31],  // Max per HTTP/2 spec is 2^31-1
+  ['initialWindowSize', 2 ** 32 - 1],  // Regression test for nghttp2 crash
   ['initialWindowSize', 2 ** 32],
   ['maxFrameSize', 16383],
   ['maxFrameSize', 2 ** 24],

filename: test/parallel/test-http2-session-settings.js
patch: @@ -133,6 +133,8 @@ server.listen(
         ['headerTableSize', -1],
         ['headerTableSize', 2 ** 32],
         ['initialWindowSize', -1],
+        ['initialWindowSize', 2 ** 31],  // Max per HTTP/2 spec is 2^31-1
+        ['initialWindowSize', 2 ** 32 - 1],  // Regression test for nghttp2 crash
         ['initialWindowSize', 2 ** 32],
         ['maxFrameSize', 16383],
         ['maxFrameSize', 2 ** 24],