diff --git a/SPECS/nodejs/CVE-2024-22020.patch b/SPECS/nodejs/CVE-2024-22020.patch new file mode 100644 index 00000000000..4380c240529 --- /dev/null +++ b/SPECS/nodejs/CVE-2024-22020.patch @@ -0,0 +1,49 @@ +From b2e718b4ce54713cd57207125cb02ff567a9bf04 Mon Sep 17 00:00:00 2001 +From: Kanishk-Bansal +Date: Tue, 11 Feb 2025 17:08:22 +0000 +Subject: [PATCH] Fix CVE-2024-22020 + +--- + lib/internal/modules/esm/resolve.js | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js +index 96dd20a8..de53b11f 100644 +--- a/lib/internal/modules/esm/resolve.js ++++ b/lib/internal/modules/esm/resolve.js +@@ -1142,9 +1142,19 @@ function defaultResolve(specifier, context = {}) { + } else { + parsed = new URL(specifier); + } +- + // Avoid accessing the `protocol` property due to the lazy getters. + const protocol = parsed.protocol; ++ ++ if (protocol === 'data:' && ++ parsedParentURL.protocol !== 'file:' && ++ experimentalNetworkImports) { ++ throw new ERR_NETWORK_IMPORT_DISALLOWED( ++ specifier, ++ parsedParentURL, ++ 'import data: from a non file: is not allowed', ++ ); ++ } ++ + if (protocol === 'data:' || + (experimentalNetworkImports && + ( +@@ -1155,7 +1165,10 @@ function defaultResolve(specifier, context = {}) { + ) { + return { __proto__: null, url: parsed.href }; + } +- } catch { ++ } catch (e) { ++ if (e?.code === 'ERR_NETWORK_IMPORT_DISALLOWED') { ++ throw e; ++ } + // Ignore exception + } + +-- +2.45.2 + diff --git a/SPECS/nodejs/CVE-2024-22195.patch b/SPECS/nodejs/CVE-2024-22195.patch new file mode 100644 index 00000000000..fd2ed76bbb0 --- /dev/null +++ b/SPECS/nodejs/CVE-2024-22195.patch @@ -0,0 +1,60 @@ +From 5bab005747120011816817aaf2174ab46d0c72c9 Mon Sep 17 00:00:00 2001 +From: Kanishk-Bansal +Date: Wed, 12 Feb 2025 10:20:59 +0000 +Subject: [PATCH] Fix CVE-2024-22195 +Backport of https://github.com/pallets/jinja/commit/7dd3680e6eea0d77fde024763657aa4d884ddb23 + +--- + deps/v8/third_party/jinja2/filters.py | 24 +++++++++++++++++------- + 1 file changed, 17 insertions(+), 7 deletions(-) + +diff --git a/deps/v8/third_party/jinja2/filters.py b/deps/v8/third_party/jinja2/filters.py +index 74b108dc..46347251 100644 +--- a/deps/v8/third_party/jinja2/filters.py ++++ b/deps/v8/third_party/jinja2/filters.py +@@ -204,12 +204,15 @@ def do_lower(s): + """Convert a value to lowercase.""" + return soft_unicode(s).lower() + ++_space_re = re.compile(r"\s", flags=re.ASCII) ++ + + @evalcontextfilter + def do_xmlattr(_eval_ctx, d, autospace=True): + """Create an SGML/XML attribute string based on the items in a dict. +- All values that are neither `none` nor `undefined` are automatically +- escaped: ++ ++ If any key contains a space, this fails with a ``ValueError``. Values that ++ are neither ``none`` nor ``undefined`` are automatically escaped. + + .. sourcecode:: html+jinja + +@@ -228,12 +231,19 @@ def do_xmlattr(_eval_ctx, d, autospace=True): + + As you can see it automatically prepends a space in front of the item + if the filter returned something unless the second parameter is false. ++ ++ .. versionchanged:: 3.1.3 ++ Keys with spaces are not allowed. + """ +- rv = u" ".join( +- u'%s="%s"' % (escape(key), escape(value)) +- for key, value in iteritems(d) +- if value is not None and not isinstance(value, Undefined) +- ) ++ items = [] ++ for key, value in d.items(): ++ if value is None or isinstance(value, Undefined): ++ continue ++ if _space_re.search(key) is not None: ++ raise ValueError(f"Spaces are not allowed in attributes: '{key}'") ++ items.append(f'{escape(key)}="{escape(value)}"') ++ rv = " ".join(items) ++ + if autospace and rv: + rv = u" " + rv + if _eval_ctx.autoescape: +-- +2.45.2 + diff --git a/SPECS/nodejs/CVE-2025-22150.patch b/SPECS/nodejs/CVE-2025-22150.patch new file mode 100644 index 00000000000..70745dfb614 --- /dev/null +++ b/SPECS/nodejs/CVE-2025-22150.patch @@ -0,0 +1,40 @@ +From d059d899b9c92a1479432016fe48a01d9254d89a Mon Sep 17 00:00:00 2001 +From: Kanishk Bansal +Date: Wed, 5 Feb 2025 12:28:55 +0000 +Subject: [PATCH] Address CVE-2025-22150 + +--- + deps/undici/src/lib/fetch/body.js | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/deps/undici/src/lib/fetch/body.js b/deps/undici/src/lib/fetch/body.js +index fd8481b7..4afcfdfb 100644 +--- a/deps/undici/src/lib/fetch/body.js ++++ b/deps/undici/src/lib/fetch/body.js +@@ -22,6 +22,14 @@ const { isUint8Array, isArrayBuffer } = require('util/types') + const { File: UndiciFile } = require('./file') + const { parseMIMEType, serializeAMimeType } = require('./dataURL') + ++let random ++try { ++ const crypto = require('node:crypto') ++ random = (max) => crypto.randomInt(0, max) ++} catch { ++ random = (max) => Math.floor(Math.random(max)) ++} ++ + let ReadableStream = globalThis.ReadableStream + + /** @type {globalThis['File']} */ +@@ -107,7 +115,7 @@ function extractBody (object, keepalive = false) { + // Set source to a copy of the bytes held by object. + source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) + } else if (util.isFormDataLike(object)) { +- const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` ++ const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` + const prefix = `--${boundary}\r\nContent-Disposition: form-data` + + /*! formdata-polyfill. MIT License. Jimmy Wärting */ +-- +2.43.0 + diff --git a/SPECS/nodejs/CVE-2025-23085.patch b/SPECS/nodejs/CVE-2025-23085.patch new file mode 100644 index 00000000000..b5594ba6fda --- /dev/null +++ b/SPECS/nodejs/CVE-2025-23085.patch @@ -0,0 +1,200 @@ +From 4f5c7b360f31f31ac160b171861c5f52f48e367e Mon Sep 17 00:00:00 2001 +From: Kanishk-Bansal +Date: Tue, 11 Feb 2025 15:53:59 +0000 +Subject: [PATCH] Fix CVE-2025-23085 + +--- + lib/internal/http2/core.js | 15 ++++++-- + src/node_http2.cc | 36 ++++++++++++++++--- + ...2-connect-method-extended-cant-turn-off.js | 6 ++++ + ...-http2-options-max-headers-block-length.js | 4 ++- + ...tp2-options-max-headers-exceeds-nghttp2.js | 4 ++- + 5 files changed, 55 insertions(+), 10 deletions(-) + +diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js +index 92ce193b..38844d30 100644 +--- a/lib/internal/http2/core.js ++++ b/lib/internal/http2/core.js +@@ -608,11 +608,20 @@ function onFrameError(id, type, code) { + return; + debugSessionObj(session, 'error sending frame type %d on stream %d, code: %d', + type, id, code); +- const emitter = session[kState].streams.get(id) || session; ++ ++ const stream = session[kState].streams.get(id); ++ const emitter = stream || session; + emitter[kUpdateTimer](); + emitter.emit('frameError', type, code, id); +- session[kState].streams.get(id).close(code); +- session.close(); ++ ++ // When a frameError happens is not uncommon that a pending GOAWAY ++ // package from nghttp2 is on flight with a correct error code. ++ // We schedule it using setImmediate to give some time for that ++ // package to arrive. ++ setImmediate(() => { ++ stream?.close(code); ++ session.close(); ++ }); + } + + function onAltSvc(stream, origin, alt) { +diff --git a/src/node_http2.cc b/src/node_http2.cc +index eb3506ff..38d47f0c 100644 +--- a/src/node_http2.cc ++++ b/src/node_http2.cc +@@ -750,6 +750,7 @@ bool Http2Session::CanAddStream() { + } + + void Http2Session::AddStream(Http2Stream* stream) { ++ Debug(this, "Adding stream: %d", stream->id()); + CHECK_GE(++statistics_.stream_count, 0); + streams_[stream->id()] = BaseObjectPtr(stream); + size_t size = streams_.size(); +@@ -760,6 +761,7 @@ void Http2Session::AddStream(Http2Stream* stream) { + + + BaseObjectPtr Http2Session::RemoveStream(int32_t id) { ++ Debug(this, "Removing stream: %d", id); + BaseObjectPtr stream; + if (streams_.empty()) + return stream; +@@ -936,6 +938,7 @@ int Http2Session::OnHeaderCallback(nghttp2_session* handle, + if (UNLIKELY(!stream)) + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + ++ Debug(session, "handling header key/pair for stream %d", id); + // If the stream has already been destroyed, ignore. + if (!stream->is_destroyed() && !stream->AddHeader(name, value, flags)) { + // This will only happen if the connected peer sends us more +@@ -1005,9 +1008,21 @@ int Http2Session::OnInvalidFrame(nghttp2_session* handle, + return 1; + } + +- // If the error is fatal or if error code is ERR_STREAM_CLOSED... emit error ++ // If the error is fatal or if error code is one of the following ++ // we emit and error: ++ // ++ // ERR_STREAM_CLOSED: An invalid frame has been received in a closed stream. ++ // ++ // ERR_PROTO: The RFC 7540 specifies: ++ // "An endpoint that encounters a connection error SHOULD first send a GOAWAY ++ // frame (Section 6.8) with the stream identifier of the last stream that it ++ // successfully received from its peer. ++ // The GOAWAY frame includes an error code that indicates the type of error" ++ // The GOAWAY frame is already sent by nghttp2. We emit the error ++ // to liberate the Http2Session to destroy. + if (nghttp2_is_fatal(lib_error_code) || +- lib_error_code == NGHTTP2_ERR_STREAM_CLOSED) { ++ lib_error_code == NGHTTP2_ERR_STREAM_CLOSED || ++ lib_error_code == NGHTTP2_ERR_PROTO) { + Environment* env = session->env(); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); +@@ -1070,7 +1085,6 @@ int Http2Session::OnFrameNotSent(nghttp2_session* handle, + Debug(session, "frame type %d was not sent, code: %d", + frame->hd.type, error_code); + +- // Do not report if the frame was not sent due to the session closing + if (error_code == NGHTTP2_ERR_SESSION_CLOSING || + error_code == NGHTTP2_ERR_STREAM_CLOSED || + error_code == NGHTTP2_ERR_STREAM_CLOSING) { +@@ -1079,7 +1093,15 @@ int Http2Session::OnFrameNotSent(nghttp2_session* handle, + // to destroy the session completely. + // Further information see: https://github.com/nodejs/node/issues/35233 + session->DecrefHeaders(frame); +- return 0; ++ // Currently, nghttp2 doesn't not inform us when is the best ++ // time to call session.close(). It relies on a closing connection ++ // from peer. If that doesn't happen, the nghttp2_session will be ++ // closed but the Http2Session will still be up causing a memory leak. ++ // Therefore, if the GOAWAY frame couldn't be send due to ++ // ERR_SESSION_CLOSING we should force close from our side. ++ if (frame->hd.type != 0x03) { ++ return 0; ++ } + } + + Isolate* isolate = env->isolate(); +@@ -1145,12 +1167,15 @@ int Http2Session::OnStreamClose(nghttp2_session* handle, + // ignore these. If this callback was not provided, nghttp2 would handle + // invalid headers strictly and would shut down the stream. We are intentionally + // being more lenient here although we may want to revisit this choice later. +-int Http2Session::OnInvalidHeader(nghttp2_session* session, ++int Http2Session::OnInvalidHeader(nghttp2_session* handle, + const nghttp2_frame* frame, + nghttp2_rcbuf* name, + nghttp2_rcbuf* value, + uint8_t flags, + void* user_data) { ++ Http2Session* session = static_cast(user_data); ++ int32_t id = GetFrameID(frame); ++ Debug(session, "invalid header received for stream %d", id); + // Ignore invalid header fields by default. + return 0; + } +@@ -1544,6 +1569,7 @@ void Http2Session::HandlePingFrame(const nghttp2_frame* frame) { + + // Called by OnFrameReceived when a complete SETTINGS frame has been received. + void Http2Session::HandleSettingsFrame(const nghttp2_frame* frame) { ++ Debug(this, "handling settings frame"); + bool ack = frame->hd.flags & NGHTTP2_FLAG_ACK; + if (!ack) { + js_fields_->bitfield &= ~(1 << kSessionRemoteSettingsIsUpToDate); +diff --git a/test/parallel/test-http2-connect-method-extended-cant-turn-off.js b/test/parallel/test-http2-connect-method-extended-cant-turn-off.js +index f4d033ef..456aa1ce 100644 +--- a/test/parallel/test-http2-connect-method-extended-cant-turn-off.js ++++ b/test/parallel/test-http2-connect-method-extended-cant-turn-off.js +@@ -27,4 +27,10 @@ server.listen(0, common.mustCall(() => { + server.close(); + })); + })); ++ ++ client.on('error', common.expectsError({ ++ code: 'ERR_HTTP2_ERROR', ++ name: 'Error', ++ message: 'Protocol error' ++ })); + })); +diff --git a/test/parallel/test-http2-options-max-headers-block-length.js b/test/parallel/test-http2-options-max-headers-block-length.js +index af1cc6f9..15b142ac 100644 +--- a/test/parallel/test-http2-options-max-headers-block-length.js ++++ b/test/parallel/test-http2-options-max-headers-block-length.js +@@ -35,9 +35,11 @@ server.listen(0, common.mustCall(() => { + assert.strictEqual(code, h2.constants.NGHTTP2_FRAME_SIZE_ERROR); + })); + ++ // NGHTTP2 will automatically send the NGHTTP2_REFUSED_STREAM with ++ // the GOAWAY frame. + req.on('error', common.expectsError({ + code: 'ERR_HTTP2_STREAM_ERROR', + name: 'Error', +- message: 'Stream closed with error code NGHTTP2_FRAME_SIZE_ERROR' ++ message: 'Stream closed with error code NGHTTP2_REFUSED_STREAM' + })); + })); +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 df3aefff..7767dbbc 100644 +--- a/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js ++++ b/test/parallel/test-http2-options-max-headers-exceeds-nghttp2.js +@@ -59,6 +59,9 @@ server.listen(0, common.mustCall(() => { + 'session', + common.mustCall((session) => { + assert.strictEqual(session instanceof ServerHttp2Session, true); ++ session.on('close', common.mustCall(() => { ++ server.close(); ++ })); + }), + ); + server.on( +@@ -80,7 +83,6 @@ server.listen(0, common.mustCall(() => { + assert.strictEqual(err.name, 'Error'); + assert.strictEqual(err.message, 'Session closed with error code 9'); + assert.strictEqual(session instanceof ServerHttp2Session, true); +- server.close(); + }), + ); + +-- +2.45.2 + diff --git a/SPECS/nodejs/nodejs18.spec b/SPECS/nodejs/nodejs18.spec index fb9b9f05b02..c30577fc0af 100644 --- a/SPECS/nodejs/nodejs18.spec +++ b/SPECS/nodejs/nodejs18.spec @@ -6,7 +6,7 @@ Name: nodejs18 # WARNINGS: MUST check and update the 'npm_version' macro for every version update of this package. # The version of NPM can be found inside the sources under 'deps/npm/package.json'. Version: 18.20.3 -Release: 2%{?dist} +Release: 3%{?dist} License: BSD and MIT and Public Domain and NAIST-2003 and Artistic-2.0 Group: Applications/System Vendor: Microsoft Corporation @@ -18,6 +18,10 @@ URL: https://github.com/nodejs/node Source0: https://nodejs.org/download/release/v%{version}/node-v%{version}.tar.xz Patch0: CVE-2023-21100.patch Patch1: CVE-2024-21538.patch +Patch2: CVE-2025-22150.patch +Patch3: CVE-2025-23085.patch +Patch4: CVE-2024-22020.patch +Patch5: CVE-2024-22195.patch BuildRequires: brotli-devel BuildRequires: coreutils >= 8.22 BuildRequires: gcc @@ -118,6 +122,9 @@ make cctest %{_datadir}/systemtap/tapset/node.stp %changelog +* Tue Feb 11 2025 Kanishk Bansal - 18.20.3-3 +- Patch CVE-2025-22150, CVE-2025-23085, CVE-2024-22020, CVE-2024-22195 + * Tue Nov 19 2024 Bala - 18.20.3-2 - Patch CVE-2024-21538