new file mode 100644
@@ -0,0 +1,349 @@
+From a1a5bb968303229d4a3a7c9e389dc3ece6237b4c Mon Sep 17 00:00:00 2001
+From: Tim Perry <pimterry@gmail.com>
+Date: Wed, 22 Apr 2026 13:36:27 +0200
+Subject: [PATCH] deps: fix integration issues with the latest nghttp2
+
+This is a set of src & tests fixes for nghttp2 due to changes in
+v1.67.0+ which require a selection of changes to how we handle
+low-level protocol errors when using the latest versions of nghttp2,
+changing both some src error handling and updating some tests to match.
+
+Signed-off-by: Tim Perry <pimterry@gmail.com>
+PR-URL: https://github.com/nodejs/node/pull/62891
+Backport-PR-URL: https://github.com/nodejs/node/pull/63195
+Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
+Reviewed-By: Tim Perry <pimterry@gmail.com>
+Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
+Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
+Refs: https://hackerone.com/reports/3658225
+
+CVE: CVE-2026-48937
+Upstream-Status: Backport [https://github.com/nodejs/node/commit/a1a5bb968303229d4a3a7c9e389dc3ece6237b4c]
+
+Signed-off-by: Rohini Sangam <rsangam@mvista.com>
+---
+ src/node_http2.cc | 31 +++++++
+ src/node_http2.h | 2 +
+ ...t-http2-misbehaving-flow-control-paused.js | 33 ++++---
+ .../test-http2-misbehaving-flow-control.js | 33 ++++---
+ ...tp2-options-max-headers-exceeds-nghttp2.js | 12 ++-
+ ...http2-session-cleanup-on-nghttp2-goaway.js | 91 +++++++++++++++++++
+ 6 files changed, 176 insertions(+), 26 deletions(-)
+ create mode 100644 test/parallel/test-http2-session-cleanup-on-nghttp2-goaway.js
+
+diff --git a/src/node_http2.cc b/src/node_http2.cc
+index 4f44789b..5047e5c4 100644
+--- a/src/node_http2.cc
++++ b/src/node_http2.cc
+@@ -1246,6 +1246,24 @@ int Http2Session::OnFrameSent(nghttp2_session* handle,
+ void* user_data) {
+ Http2Session* session = static_cast<Http2Session*>(user_data);
+ session->statistics_.frame_sent += 1;
++
++ // If nghttp2 has internally terminated the session (e.g. due to a protocol
++ // error like oversized frames, padding errors, or HPACK compression
++ // failures), it calls nghttp2_session_terminate_session() directly which
++ // queues a GOAWAY but does not invoke any application-level callback.
++ // Detect that case here: a GOAWAY was sent but we never initiated it
++ // (no Close(), no session.close(), no session.goaway()).
++ //
++ // We set a flag here, and then throw the error at the end of
++ // SendPendingData, to wait until the GOAWAY is written before the session
++ // is torn down.
++ if (frame->hd.type == NGHTTP2_GOAWAY && !session->is_closing() &&
++ !session->is_destroyed() && !session->IsGracefulCloseInitiated() &&
++ !session->goaway_initiated_) {
++ Debug(session, "nghttp2 session terminated internally");
++ session->internal_goaway_sent_ = true;
++ }
++
+ return 0;
+ }
+
+@@ -1976,6 +1994,18 @@ uint8_t Http2Session::SendPendingData() {
+
+ MaybeStopReading();
+
++ // If nghttp2 has internally torn down the session (detected in OnFrameSent)
++ // during the nghttp2_session_mem_send loop above, at this point we error:
++ if (internal_goaway_sent_) {
++ internal_goaway_sent_ = false;
++ if (!is_closing() && !is_destroyed()) {
++ Isolate* isolate = env()->isolate();
++ HandleScope scope(isolate);
++ Local<Value> arg = Integer::New(isolate, NGHTTP2_ERR_PROTO);
++ MakeCallback(env()->http2session_on_error_function(), 1, &arg);
++ }
++ }
++
+ return 0;
+ }
+
+@@ -2901,6 +2931,7 @@ void Http2Session::Goaway(uint32_t code,
+ if (is_destroyed())
+ return;
+
++ goaway_initiated_ = true;
+ Http2Scope h2scope(this);
+ // the last proc stream id is the most recently created Http2Stream.
+ if (lastStreamID <= 0)
+diff --git a/src/node_http2.h b/src/node_http2.h
+index a60a7ba0..91046052 100644
+--- a/src/node_http2.h
++++ b/src/node_http2.h
+@@ -968,6 +968,8 @@ class Http2Session : public AsyncWrap,
+
+ // Flag to indicate that JavaScript has initiated a graceful closure
+ bool graceful_close_initiated_ = false;
++ bool goaway_initiated_ = false;
++ bool internal_goaway_sent_ = false;
+ };
+
+ struct Http2SessionPerformanceEntryTraits {
+diff --git a/test/parallel/test-http2-misbehaving-flow-control-paused.js b/test/parallel/test-http2-misbehaving-flow-control-paused.js
+index e54bb832..b9a0d5cb 100644
+--- a/test/parallel/test-http2-misbehaving-flow-control-paused.js
++++ b/test/parallel/test-http2-misbehaving-flow-control-paused.js
+@@ -47,36 +47,45 @@ const data = Buffer.from([
+ // Bad client! Bad!
+ //
+ // Fortunately, nghttp2 handles this situation for us by keeping track
+-// of the flow control window and responding with a FLOW_CONTROL_ERROR
+-// causing the stream to get shut down...
+-//
+-// At least, that's what is supposed to happen.
++// of the flow control window and sending GOAWAY to end the session.
+
+ let client;
+
+ const server = h2.createServer({ settings: { initialWindowSize: 36 } });
+-server.on('stream', (stream) => {
++
++server.on('session', common.mustCall((session) => {
++ session.on('error', common.expectsError({
++ code: 'ERR_HTTP2_ERROR',
++ name: 'Error',
++ message: 'Protocol error'
++ }));
++}));
++
++server.on('stream', common.mustCall((stream) => {
+ // Set the high water mark to zero, since otherwise we still accept
+ // reads from the source stream (if we can consume them).
+ stream._readableState.highWaterMark = 0;
+ stream.pause();
+ stream.on('error', common.expectsError({
+- code: 'ERR_HTTP2_STREAM_ERROR',
++ code: 'ERR_HTTP2_ERROR',
+ name: 'Error',
+- message: 'Stream closed with error code NGHTTP2_FLOW_CONTROL_ERROR'
++ message: 'Protocol error'
+ }));
+ stream.on('close', common.mustCall(() => {
+ server.close();
+ client.destroy();
+ }));
+ stream.on('end', common.mustNotCall());
+-});
++}));
+
+-server.listen(0, () => {
+- client = net.connect(server.address().port, () => {
++server.listen(0, common.mustCall(() => {
++ client = net.connect(server.address().port, common.mustCall(() => {
+ client.write(preamble);
+ client.write(data);
+ client.write(data);
+ client.write(data);
+- });
+-});
++
++ // TCP connection is closed by the server
++ client.on('close', common.mustCall());
++ }));
++}));
+diff --git a/test/parallel/test-http2-misbehaving-flow-control.js b/test/parallel/test-http2-misbehaving-flow-control.js
+index 6774be22..fb0f7a1e 100644
+--- a/test/parallel/test-http2-misbehaving-flow-control.js
++++ b/test/parallel/test-http2-misbehaving-flow-control.js
+@@ -58,18 +58,24 @@ const data = Buffer.from([
+ // Bad client! Bad!
+ //
+ // Fortunately, nghttp2 handles this situation for us by keeping track
+-// of the flow control window and responding with a FLOW_CONTROL_ERROR
+-// causing the stream to get shut down...
+-//
+-// At least, that's what is supposed to happen.
++// of the flow control window and sending GOAWAY to end the session.
+
+ let client;
+ const server = h2.createServer({ settings: { initialWindowSize: 18 } });
+-server.on('stream', (stream) => {
++
++server.on('session', common.mustCall((session) => {
++ session.on('error', common.expectsError({
++ code: 'ERR_HTTP2_ERROR',
++ name: 'Error',
++ message: 'Protocol error'
++ }));
++}));
++
++server.on('stream', common.mustCall((stream) => {
+ stream.on('error', common.expectsError({
+- code: 'ERR_HTTP2_STREAM_ERROR',
++ code: 'ERR_HTTP2_ERROR',
+ name: 'Error',
+- message: 'Stream closed with error code NGHTTP2_FLOW_CONTROL_ERROR'
++ message: 'Protocol error'
+ }));
+ stream.on('close', common.mustCall(() => {
+ server.close(common.mustCall());
+@@ -78,14 +84,17 @@ server.on('stream', (stream) => {
+ stream.resume();
+ stream.respond();
+ stream.end('ok');
+-});
++}));
+
+ server.on('close', common.mustCall());
+
+-server.listen(0, () => {
+- client = net.connect(server.address().port, () => {
++server.listen(0, common.mustCall(() => {
++ client = net.connect(server.address().port, common.mustCall(() => {
+ client.write(preamble);
+ client.write(data);
+ client.write(data);
+- });
+-});
++
++ // TCP connection is closed by the server
++ client.on('close', common.mustCall());
++ }));
++}));
+diff --git a/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js b/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js
+index 7767dbbc..46f560f9 100644
+--- a/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js
++++ b/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js
+@@ -90,11 +90,19 @@ server.listen(0, common.mustCall(() => {
+ 0,
+ common.mustCall(() => {
+ const client = h2.connect(`http://localhost:${server.address().port}`);
+- client.on('error', common.mustNotCall());
++ // The server sends oversized headers that cause a compression error on
++ // the client side, so nghttp2 internally terminates the client session.
++ client.on('error', common.expectsError({
++ code: 'ERR_HTTP2_ERROR',
++ name: 'Error',
++ }));
+
+ const req = client.request();
+ req.on('response', common.mustNotCall());
+- req.on('error', common.mustNotCall());
++ req.on('error', common.expectsError({
++ code: 'ERR_HTTP2_ERROR',
++ name: 'Error',
++ }));
+ req.end();
+ }),
+ );
+diff --git a/test/parallel/test-http2-session-cleanup-on-nghttp2-goaway.js b/test/parallel/test-http2-session-cleanup-on-nghttp2-goaway.js
+new file mode 100644
+index 00000000..f3576f9f
+--- /dev/null
++++ b/test/parallel/test-http2-session-cleanup-on-nghttp2-goaway.js
+@@ -0,0 +1,91 @@
++'use strict';
++
++const common = require('../common');
++
++if (!common.hasCrypto)
++ common.skip('missing crypto');
++
++const http2 = require('http2');
++const net = require('net');
++
++// When nghttp2 internally sends a GOAWAY frame due to a protocol error, it
++// may call nghttp2_session_terminate_session() directly, bypassing the
++// on_invalid_frame_recv_callback entirely. This test ensures that even
++// in that scenario, we still correctly clean up the session & connection.
++//
++// This test reproduces this with a client who sends a frame header with
++// a length exceeding the default max_frame_size (16384). nghttp2 responds
++// with GOAWAY(FRAME_SIZE_ERROR) without notifying Node through any callback.
++
++const server = http2.createServer();
++
++server.on('session', common.mustCall((session) => {
++ session.on('error', common.expectsError({
++ code: 'ERR_HTTP2_ERROR',
++ name: 'Error',
++ message: 'Protocol error'
++ }));
++
++ session.on('close', common.mustCall(() => server.close()));
++}));
++
++server.listen(0, common.mustCall(() => {
++ const conn = net.connect({
++ port: server.address().port,
++ allowHalfOpen: true,
++ });
++
++ // HTTP/2 client connection preface.
++ conn.write('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
++
++ // Empty SETTINGS frame.
++ const settingsFrame = Buffer.alloc(9);
++ settingsFrame[3] = 0x04; // type: SETTINGS
++ conn.write(settingsFrame);
++
++ let inbuf = Buffer.alloc(0);
++ let state = 'settingsHeader';
++ let settingsFrameLength;
++
++ conn.on('data', (chunk) => {
++ inbuf = Buffer.concat([inbuf, chunk]);
++
++ switch (state) {
++ case 'settingsHeader':
++ if (inbuf.length < 9) return;
++ settingsFrameLength = inbuf.readUIntBE(0, 3);
++ inbuf = inbuf.slice(9);
++ state = 'readingSettings';
++ // Fallthrough
++ case 'readingSettings': {
++ if (inbuf.length < settingsFrameLength) return;
++ inbuf = inbuf.slice(settingsFrameLength);
++ state = 'done';
++
++ // ACK the server SETTINGS.
++ const ack = Buffer.alloc(9);
++ ack[3] = 0x04; // type: SETTINGS
++ ack[4] = 0x01; // flag: ACK
++ conn.write(ack);
++
++ // Send a HEADERS frame header claiming length 16385, which exceeds
++ // the default max_frame_size of 16384. nghttp2 checks the length
++ // before reading any payload, so no body is needed. This triggers
++ // nghttp2_session_terminate_session(FRAME_SIZE_ERROR) directly in
++ // nghttp2_session_mem_recv2 — bypassing on_invalid_frame_recv_callback.
++ const oversized = Buffer.alloc(9);
++ oversized.writeUIntBE(16385, 0, 3); // length: 16385 (one over max)
++ oversized[3] = 0x01; // type: HEADERS
++ oversized[4] = 0x04; // flags: END_HEADERS
++ oversized.writeUInt32BE(1, 5); // stream id: 1
++ conn.write(oversized);
++
++ // No need to write the data - the header alone triggers the check.
++ }
++ }
++ });
++
++ // The server must close the connection after sending GOAWAY:
++ conn.on('end', common.mustCall(() => conn.end()));
++ conn.on('close', common.mustCall());
++}));
+--
+2.44.4
+
@@ -28,6 +28,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \
file://run-ptest \
file://CVE-2026-48617.patch \
file://CVE-2026-48619.patch \
+ file://CVE-2026-48937.patch \
"
SRC_URI:append:class-target = " \
file://0001-Using-native-binaries.patch \
Pick patch from [1] also mentioned at Debian tracker in [2] [1] https://github.com/nodejs/node/commit/a1a5bb968303229d4a3a7c9e389dc3ece6237b4c [2] https://security-tracker.debian.org/tracker/CVE-2026-48937 Signed-off-by: Rohini Sangam <rsangam@mvista.com> --- .../nodejs/nodejs/CVE-2026-48937.patch | 349 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.20.2.bb | 1 + 2 files changed, 350 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2026-48937.patch