From patchwork Thu Feb 12 05:21:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 80962 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C5F97EB48F6 for ; Thu, 12 Feb 2026 10:32:53 +0000 (UTC) Received: from alln-iport-5.cisco.com (alln-iport-5.cisco.com [173.37.142.92]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.39073.1770873684987539524 for ; Wed, 11 Feb 2026 21:21:25 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=k0s8C9ku; spf=pass (domain: cisco.com, ip: 173.37.142.92, mailfrom: adongare@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=7485; q=dns/txt; s=iport01; t=1770873685; x=1772083285; h=from:to:cc:subject:date:message-id:mime-version: content-transfer-encoding; bh=DKzrD17tWzRuMWJhAkyJs023aY/d/XqOYwwP+gfy7v8=; b=k0s8C9kuUCatDEaR66kDkQGNTGN0jghXuCFsR33Sz7t6pBRD0qxrgBiD oBiVxXqiXxRQu65/S+ntWpv3B1JZU2s0r0GCbA/K2NYBNVYhqAmiotm5z MjHC9dUgUVtz42Px/lgKX8kNkk0xuf3QE1EH448aTcyiNm0LaVXJAEs7G 0VzH9JnbwOb8F3V+QnymZiJ00VPyV2cRexzIlErq47/xCkB0XrieecbZ8 XGxgWccQLMfeHvMA8Jnc4d0j9Unc0/xVsFfvuwUmHw0OWzGYTiPOyZjaS 1AO4piqr4Qfq9+4ta/oEXPJJD1G5Am5+HLu4LjUmjoQFzdHz/x/uON3L6 w==; X-CSE-ConnectionGUID: gKNvqYc9QQyRrUPJhZm9oA== X-CSE-MsgGUID: 7iiU2rE+RHKC8N5FD3yjRg== X-IPAS-Result: A0CsBgDuYo1p/4v/Ja1agQmBUIJID3FeQ0mEV5F0gwGIZpI2gX8PAQEBDz0UBAEBhQeNHwImNAkOAQIEAQEBAQMCAwEBAQEBAQEBAQEBCwEBBQEBAQIBBwWBDhOGTw2GWgECASYPARgBLSwDAQIDAiYCIgsjIYMCAYI6AzYDEahpeoEygQGDKAE/AkNP2EcNglIBCxQBgQouhTuCeR8BhH9bGAFEhDQnGxuBcoQHdoEFgRpCAQGFO4JpBIIigQ6BZCcPiDGJI0iBAhwDWSwBVRMNCgsHBYFmAzUSKhVuMh2BIz4XM1gbBwWIFQ+JD3hwgSByAwsYDUgRLDcUGwQ9AW4HjktBgjIBWQE0ASoBgRoVFAo3EBhOkleQLoIhoB1xCiiDdIwejz4BhXsaM4QEgVeSPpJSC5h7gliIToJjhAmRKweBFYRogWg8gUcLB3AVgyIJSRkPji0LC4NegX+DFLxOJTICOgIHAQoBAQMJk2cBAQ IronPort-Data: A9a23:mW/GIKj4ITpIOQeGP44pDjRiX161MBEKZh0ujC45NGQN5FlHY01je htvXGHVMvmDMGHyctsgaIvioRwCupKGx99gTgM4qi1hE39jpJueD7x1DKtf0wB+jyHnZBg6h ynLQoCYdKjYdleF+FH1dOOn9SUgvU2xbuKUIPbePSxsThNTRi4kiBZy88Y0mYcAbeKRW2thg vus5ZeGULOZ82QsaDxMsfvZ8EoHUMna4Vv0gHRvPZing3eG/5UlJMp3Db28KXL+Xr5VEoaSL 87fzKu093/u5BwkDNWoiN7TKiXmlZaLYGBiIlIPM0STqkAqSh4ai87XB9JAAatjsAhlqvgqo Dl7WTNcfi9yVkHEsLx1vxC1iEiSN4UekFPMCSDXXcB+UyQqflO0q8iCAn3aMqUX2cBpGFBFp MUhOQoXUyihh9CzkbWSH7wEasQLdKEHPasFsX1miDWcBvE8TNWbGePB5MRT23E7gcUm8fT2P pVCL2EwKk6dPlsWYQZ/5JEWxI9EglH2fzpep1uPqII84nPYy0p6172F3N/9JILUHZwNxxnEz o7A1zv4AyxALvHc8xqm2SKK28HjxjL5Yo1HQdVU8dYv2jV/3Fc7DwUbU1a+q/S1hkOyHt5SN UEQ0i4vtrQpskuzQ9/wWhe1rHKJslgbQdU4LgEhwBuGxqyR50OSAXIJC2YbLtcnr8QxAzct0 zdlgu/UONCmi5XNIVr1y1tehWra1fQ9RYPaWRI5cA== IronPort-HdrOrdr: A9a23:aeHvsavrv6ANk376Cs+fOpJP7skDd9V00zEX/kB9WHVpmwKj+P xG+85rsSMc6QxhPU3I9urgBEDtex7hHP1OkOss1NWZPDUO0VHAROoJ0WKI+VPd8kPFmtK1rZ 0QEJSXzLbLfD5HZQGQ2njeL+od X-Talos-CUID: 9a23:6fEk2Wmer/NKSxsTrqti/xkX2SDXOXL+6mnJEkTpMF94cqeocX2QyqY1lOM7zg== X-Talos-MUID: 9a23:cX4tuQQtjvdDQuaWRXSzhTBfbt1FoJ6OK2tdt8gol5KVNxZ/bmI= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.21,286,1763424000"; d="scan'208";a="666010174" Received: from rcdn-l-core-02.cisco.com ([173.37.255.139]) by alln-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 12 Feb 2026 05:21:23 +0000 Received: from sjc-ads-10055.cisco.com (sjc-ads-10055.cisco.com [10.30.210.59]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-02.cisco.com (Postfix) with ESMTPS id CE73618000373; Thu, 12 Feb 2026 05:21:23 +0000 (GMT) Received: by sjc-ads-10055.cisco.com (Postfix, from userid 1870532) id 773BBCC12A6; Wed, 11 Feb 2026 21:21:23 -0800 (PST) From: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, to@cisco.com, Anil Dongare Subject: [meta-OE] [scarthgap] [PATCH 1/5] Nodejs 20.18.2: Fix CVE-2025-55132 Date: Wed, 11 Feb 2026 21:21:06 -0800 Message-ID: <20260212052114.3215220-1-adongare@cisco.com> X-Mailer: git-send-email 2.44.1 MIME-Version: 1.0 X-Outbound-SMTP-Client: 10.30.210.59, sjc-ads-10055.cisco.com X-Outbound-Node: rcdn-l-core-02.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 12 Feb 2026 10:32:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/124348 From: Anil Dongare Upstream Repository: https://github.com/nodejs/node.git Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-55132 Type: Security Fix CVE: CVE-2025-55132 Score: 5.3 Patch: https://github.com/nodejs/node/commit/ebbf942a83bc Signed-off-by: Anil Dongare --- .../nodejs/nodejs/CVE-2025-55132.patch | 178 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.18.2.bb | 1 + 2 files changed, 179 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55132.patch diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55132.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55132.patch new file mode 100644 index 0000000000..08c885473c --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55132.patch @@ -0,0 +1,178 @@ +From b89fc3633ec12b6d1da5b9978e6bb1c5fbacf021 Mon Sep 17 00:00:00 2001 +From: RafaelGSS +Date: Tue, 21 Oct 2025 18:25:31 -0300 +Subject: [PATCH 1/5] lib: disable futimes when permission model is enabled + +Refs: https://hackerone.com/reports/3390084 +PR-URL: https://github.com/nodejs-private/node-private/pull/748 +Reviewed-By: Matteo Collina +Reviewed-By: Anna Henningsen +CVE-ID: CVE-2025-55132 + +CVE: CVE-2025-55132 +Upstream-Status: Backport [https://github.com/nodejs/node/commit/ebbf942a83bc] + +(cherry picked from commit ebbf942a83bc70d90a3bcb6712c7b67bc479fdf5) +Signed-off-by: Anil Dongare +--- + lib/fs.js | 24 ++++++++++ + test/fixtures/permission/fs-write.js | 47 ++++++++++++++++++- + test/parallel/test-permission-fs-supported.js | 17 ++++++- + 3 files changed, 86 insertions(+), 2 deletions(-) + +diff --git a/lib/fs.js b/lib/fs.js +index 64f0b5e88ed..9206a18663c 100644 +--- a/lib/fs.js ++++ b/lib/fs.js +@@ -1274,6 +1274,11 @@ function rmSync(path, options) { + function fdatasync(fd, callback) { + const req = new FSReqCallback(); + req.oncomplete = makeCallback(callback); ++ ++ if (permission.isEnabled()) { ++ callback(new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.')); ++ return; ++ } + binding.fdatasync(fd, req); + } + +@@ -1285,6 +1290,9 @@ function fdatasync(fd, callback) { + * @returns {void} + */ + function fdatasyncSync(fd) { ++ if (permission.isEnabled()) { ++ throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'); ++ } + binding.fdatasync(fd); + } + +@@ -1298,6 +1306,10 @@ function fdatasyncSync(fd) { + function fsync(fd, callback) { + const req = new FSReqCallback(); + req.oncomplete = makeCallback(callback); ++ if (permission.isEnabled()) { ++ callback(new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.')); ++ return; ++ } + binding.fsync(fd, req); + } + +@@ -1308,6 +1320,9 @@ function fsync(fd, callback) { + * @returns {void} + */ + function fsyncSync(fd) { ++ if (permission.isEnabled()) { ++ throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'); ++ } + binding.fsync(fd); + } + +@@ -2164,6 +2179,11 @@ function futimes(fd, atime, mtime, callback) { + mtime = toUnixTimestamp(mtime, 'mtime'); + callback = makeCallback(callback); + ++ if (permission.isEnabled()) { ++ callback(new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.')); ++ return; ++ } ++ + const req = new FSReqCallback(); + req.oncomplete = callback; + binding.futimes(fd, atime, mtime, req); +@@ -2179,6 +2199,10 @@ function futimes(fd, atime, mtime, callback) { + * @returns {void} + */ + function futimesSync(fd, atime, mtime) { ++ if (permission.isEnabled()) { ++ throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'); ++ } ++ + binding.futimes( + fd, + toUnixTimestamp(atime, 'atime'), +diff --git a/test/fixtures/permission/fs-write.js b/test/fixtures/permission/fs-write.js +index 31e96860972..4b98b6d2b78 100644 +--- a/test/fixtures/permission/fs-write.js ++++ b/test/fixtures/permission/fs-write.js +@@ -490,4 +490,49 @@ const relativeProtectedFolder = process.env.RELATIVEBLOCKEDFOLDER; + }, { + code: 'ERR_ACCESS_DENIED', + }); +-} +\ No newline at end of file ++} ++ ++// fs.utimes with read-only fd ++{ ++ assert.throws(() => { ++ // blocked file is allowed to read ++ const fd = fs.openSync(blockedFile, 'r'); ++ const date = new Date(); ++ date.setFullYear(2100,0,1); ++ ++ fs.futimes(fd, date, date, common.expectsError({ ++ code: 'ERR_ACCESS_DENIED', ++ })); ++ fs.futimesSync(fd, date, date); ++ }, { ++ code: 'ERR_ACCESS_DENIED', ++ }); ++} ++ ++// fs.fdatasync with read-only fd ++{ ++ assert.throws(() => { ++ // blocked file is allowed to read ++ const fd = fs.openSync(blockedFile, 'r'); ++ fs.fdatasync(fd, common.expectsError({ ++ code: 'ERR_ACCESS_DENIED', ++ })); ++ fs.fdatasyncSync(fd); ++ }, { ++ code: 'ERR_ACCESS_DENIED', ++ }); ++} ++ ++// fs.fsync with read-only fd ++{ ++ assert.throws(() => { ++ // blocked file is allowed to read ++ const fd = fs.openSync(blockedFile, 'r'); ++ fs.fsync(fd, common.expectsError({ ++ code: 'ERR_ACCESS_DENIED', ++ })); ++ fs.fsyncSync(fd); ++ }, { ++ code: 'ERR_ACCESS_DENIED', ++ }); ++} +diff --git a/test/parallel/test-permission-fs-supported.js b/test/parallel/test-permission-fs-supported.js +index 1062117798b..805365f28b3 100644 +--- a/test/parallel/test-permission-fs-supported.js ++++ b/test/parallel/test-permission-fs-supported.js +@@ -77,7 +77,22 @@ const ignoreList = [ + 'unwatchFile', + ...syncAndAsyncAPI('lstat'), + ...syncAndAsyncAPI('realpath'), +- // fd required methods ++ // File descriptor–based metadata operations ++ // ++ // The kernel does not allow opening a file descriptor for an inode ++ // with write access if the inode itself is read-only. However, it still ++ // permits modifying the inode’s metadata (e.g., permission bits, ownership, ++ // timestamps) because you own the file. These changes can be made either ++ // by referring to the file by name (e.g., chmod) or through any existing ++ // file descriptor that identifies the same inode (e.g., fchmod). ++ // ++ // If the kernel required write access to change metadata, it would be ++ // impossible to modify the permissions of a file once it was made read-only. ++ // For that reason, syscalls such as fchmod, fchown, and futimes bypass ++ // the file descriptor’s access mode. Even a read-only ('r') descriptor ++ // can still update metadata. To prevent unintended modifications, ++ // these APIs are therefore blocked by default when permission model is ++ // enabled. + ...syncAndAsyncAPI('close'), + ...syncAndAsyncAPI('fchown'), + ...syncAndAsyncAPI('fchmod'), +-- +2.43.7 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb index d757a7395c..67574a2ec1 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb @@ -29,6 +29,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://zlib-fix-pointer-alignment.patch \ file://0001-src-fix-build-with-GCC-15.patch \ file://run-ptest \ + file://CVE-2025-55132.patch \ " SRC_URI:append:class-target = " \ file://0001-Using-native-binaries.patch \ From patchwork Thu Feb 12 05:21:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 80965 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id DF0EEEB48F9 for ; Thu, 12 Feb 2026 10:32:53 +0000 (UTC) Received: from alln-iport-7.cisco.com (alln-iport-7.cisco.com [173.37.142.94]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.38879.1770873688848250541 for ; Wed, 11 Feb 2026 21:21:29 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=MksSKr6j; spf=pass (domain: cisco.com, ip: 173.37.142.94, mailfrom: adongare@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=14792; q=dns/txt; s=iport01; t=1770873688; x=1772083288; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=nb3v8mWlBDe0G4s3CqTz2KtcIDIQCYX5dAp+ncI6SgM=; b=MksSKr6jWHYXyJhG8qv2ExnQA8oymli5SOZth763wfi4OkSXn/EKp+qv x1B5JpaZTIsZDe5Zkwuz+CngJnDvvbW0s9ggrqVxOurcV/fA+q3R1KbgP LpK53Kt7X7bXkh2Oxvn+m7djtVmtD3d6I0CcUlg1uoFRX6okr7/PMHCaf a+Nwr8lHk1Y3P7qsvaelTKy5DQKuVA0pk2SQoMK0qtZQ5p60/0CK/xw6u sEDdBkBcwzf3YYsbU5pGe5/zyFTlB8zQwgeRSHq53kZbyABeD7rPnZ/od eXN+tThe60KbSN9+7v6gfIsNRk5CIvmf7Cvrc6o63HJ7xCktQE4Ga3QRD Q==; X-CSE-ConnectionGUID: p3HX75khThWHokkMV8mQTw== X-CSE-MsgGUID: 7oGARGHWS4WR5cgWw1b2xA== X-IPAS-Result: A0CrBgBQYo1p/5T/Ja1aglmCSA9xXkNJlksDgn6IZpI2gX8PAQEBDz0UBAEBhQcCjR0CJjQJDgECBAEBAQEDAgMBAQEBAQEBAQEBAQsBAQUBAQECAQcFgQ4Thk8NhloBAgEDMgEYAS0QHAMBAi8gCyMIGYMCAYI6AzYDEahugiyBAYMoAT8CQ0/YRw2CUgELFAGBOIU7gnmFH1sYAYR4JxsbgXKEB3aBBYEaQgEBiCQEgiKBDoFkJw+RVEiBHgNZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPheBCxsHBYgVD4kPeHCBIHIDCxgNSBEsNxQbBD5uB45LQYIzWiETASoBUF8UCggjCwIDDBhOklcTB5I1oB1xCiiDdIwejz4BhXsaM4QEgVeSPpJSC5h7iyaCY4QJkSsHInOEaIFoPIFHCwdwFYMiCUkZD444g2mBf4MUvE4lMgI6AgcLAQEDCZFsLYFOAQE IronPort-Data: A9a23:S6sxpq5cd9PSGDjaNxSSuQxRtGjGchMFZxGqfqrLsTDasY5as4F+v msXW2+OOvbbMWCke9wgYIm29BxQvMKHnYJqGVZv+yswZn8b8sCt6fZ1gavT04J+CuWZESqLO u1HMoGowPgcFyGa/lH2dOC98RGQ7InQLpLkEunIJyttcgFtTSYlmHpLlvUw6mJSqYDR7zil5 5Wo+qUzBHf/g2QqajhNsfrawP9SlK2aVA0w7wRWic9j5Dcyp1FNZLoDKKe4KWfPQ4U8NoaSW +bZwbilyXjS9hErB8nNuu6TnpoiG+O60aCm0xK6aoD66vRwjnVaPpUTaJLwXXxqZwChxLid/ jniWauYEm/FNoWU8AgUvoIx/ytWZcWq85efSZSzXFD6I0DuKxPRL/tS4E4eJdY4yrkqAmFy9 cM4LzMOSy7TgtuK3+fuIgVsrpxLwMjDJogTvDRkiDreF/tjGMmFSKTR7tge1zA17ixMNa+BP IxCNnw1MUmGOkEfUrsUIMpWcOOAj3X4dTJRsl+9rqss6G+Vxwt0uFToGISLJoTbFZ0Jzi50o EqZ+EvQOD46C+fD9gDY+X2TmbbFxzPSDdd6+LqQs6QCbEeo7msLBRsbUFG2rfW0hguyVsxSL 2QQ+zEytu417EGtQ9z3UhG0rXLCuQQTM+e8CMUg4w2Lj66R6AGDCy1dF3hKaccts4k9QjlCO kK1ou4FzAdH6NW9IU9xPJ/Nxd9uEUD59VM/WBI= IronPort-HdrOrdr: A9a23:1BirCKGpKooNI/j/pLqE78eALOsnbusQ8zAXPo5KJiC9Ffbo8P xG88576faZslsssTQb6LK90cq7MBfhHPxOgbX5VI3KNGKNhILrFvAG0WKI+VPd8kPFmtK1rZ 0QEJSXzLbLfCFHZQGQ2njfL+od X-Talos-CUID: 9a23:sTssamxm6HocXtM/GauxBgUMQeAVLSDezEvQHG+/CmVMdu2QcGOPrfY= X-Talos-MUID: 9a23:HTgz0QuWLwSFDR9YXM2n2zZNHf1Hw5uVVH83zJ4JhNbdP3ZgEmLI X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.21,286,1763424000"; d="scan'208";a="664098048" Received: from rcdn-l-core-11.cisco.com ([173.37.255.148]) by alln-iport-7.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 12 Feb 2026 05:21:27 +0000 Received: from sjc-ads-10055.cisco.com (sjc-ads-10055.cisco.com [10.30.210.59]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-11.cisco.com (Postfix) with ESMTPS id 9C06118000152; Thu, 12 Feb 2026 05:21:27 +0000 (GMT) Received: by sjc-ads-10055.cisco.com (Postfix, from userid 1870532) id 49725CC12A6; Wed, 11 Feb 2026 21:21:27 -0800 (PST) From: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, to@cisco.com, Anil Dongare Subject: [meta-OE] [scarthgap] [PATCH 2/5] Nodejs 20.18.2: Fix CVE-2025-55130 Date: Wed, 11 Feb 2026 21:21:07 -0800 Message-ID: <20260212052114.3215220-2-adongare@cisco.com> X-Mailer: git-send-email 2.44.1 In-Reply-To: <20260212052114.3215220-1-adongare@cisco.com> References: <20260212052114.3215220-1-adongare@cisco.com> MIME-Version: 1.0 X-Outbound-SMTP-Client: 10.30.210.59, sjc-ads-10055.cisco.com X-Outbound-Node: rcdn-l-core-11.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 12 Feb 2026 10:32:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/124349 From: Anil Dongare Upstream Repository: https://github.com/nodejs/node.git Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-55130 Type: Security Fix CVE: CVE-2025-55130 Score: 9.1 Patch: https://github.com/nodejs/node/commit/6b4849583ad6 Signed-off-by: Anil Dongare --- .../nodejs/nodejs/CVE-2025-55130.patch | 315 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.18.2.bb | 1 + 2 files changed, 316 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55130.patch diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55130.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55130.patch new file mode 100644 index 0000000000..2e0b1a0fa7 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-55130.patch @@ -0,0 +1,315 @@ +From b64d87994164e965a46f4144ea59a01e76c30efd Mon Sep 17 00:00:00 2001 +From: RafaelGSS +Date: Mon, 10 Nov 2025 19:27:51 -0300 +Subject: [PATCH] lib,permission: require full read and write to symlink APIs + +Refs: https://hackerone.com/reports/3417819 +Signed-off-by: RafaelGSS +PR-URL: https://github.com/nodejs-private/node-private/pull/760 +Reviewed-By: Matteo Collina +CVE-ID: CVE-2025-55130 + +CVE: CVE-2025-55130 +Upstream-Status: Backport [https://github.com/nodejs/node/commit/6b4849583ad6] + +(cherry picked from commit 6b4849583ad655d96365d9c665dcdb7912b67e2c) +Signed-off-by: Anil Dongare +--- + lib/fs.js | 34 ++++++------------- + lib/internal/fs/promises.js | 20 +++-------- + .../permission/fs-symlink-target-write.js | 18 ++-------- + test/fixtures/permission/fs-symlink.js | 18 ++++++++-- + .../test-permission-fs-symlink-relative.js | 10 +++--- + test/parallel/test-permission-fs-symlink.js | 14 ++++++++ + 6 files changed, 52 insertions(+), 62 deletions(-) + +diff --git a/lib/fs.js b/lib/fs.js +index 9206a18663c..4f9ff6da371 100644 +--- a/lib/fs.js ++++ b/lib/fs.js +@@ -59,7 +59,6 @@ const { + } = constants; + + const pathModule = require('path'); +-const { isAbsolute } = pathModule; + const { isArrayBufferView } = require('internal/util/types'); + + const binding = internalBinding('fs'); +@@ -1744,18 +1743,12 @@ function symlink(target, path, type_, callback_) { + const type = (typeof type_ === 'string' ? type_ : null); + const callback = makeCallback(arguments[arguments.length - 1]); + +- if (permission.isEnabled()) { +- // The permission model's security guarantees fall apart in the presence of +- // relative symbolic links. Thus, we have to prevent their creation. +- if (BufferIsBuffer(target)) { +- if (!isAbsolute(BufferToString(target))) { +- callback(new ERR_ACCESS_DENIED('relative symbolic link target')); +- return; +- } +- } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { +- callback(new ERR_ACCESS_DENIED('relative symbolic link target')); +- return; +- } ++ // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass ++ // the permission model security guarantees. Thus, this API is disabled unless fs.read ++ // and fs.write permission has been given. ++ if (permission.isEnabled() && !permission.has('fs')) { ++ callback(new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.')); ++ return; + } + + target = getValidatedPath(target, 'target'); +@@ -1815,16 +1808,11 @@ function symlinkSync(target, path, type) { + } + } + +- if (permission.isEnabled()) { +- // The permission model's security guarantees fall apart in the presence of +- // relative symbolic links. Thus, we have to prevent their creation. +- if (BufferIsBuffer(target)) { +- if (!isAbsolute(BufferToString(target))) { +- throw new ERR_ACCESS_DENIED('relative symbolic link target'); +- } +- } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { +- throw new ERR_ACCESS_DENIED('relative symbolic link target'); +- } ++ // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass ++ // the permission model security guarantees. Thus, this API is disabled unless fs.read ++ // and fs.write permission has been given. ++ if (permission.isEnabled() && !permission.has('fs')) { ++ throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.'); + } + + target = getValidatedPath(target, 'target'); +diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js +index 8314cf29a85..f734f93d763 100644 +--- a/lib/internal/fs/promises.js ++++ b/lib/internal/fs/promises.js +@@ -17,7 +17,6 @@ const { + Symbol, + Uint8Array, + FunctionPrototypeBind, +- uncurryThis, + } = primordials; + + const { fs: constants } = internalBinding('constants'); +@@ -31,8 +30,6 @@ const { + + const binding = internalBinding('fs'); + const { Buffer } = require('buffer'); +-const { isBuffer: BufferIsBuffer } = Buffer; +-const BufferToString = uncurryThis(Buffer.prototype.toString); + + const { + codes: { +@@ -88,8 +85,6 @@ const { + kValidateObjectAllowNullable, + } = require('internal/validators'); + const pathModule = require('path'); +-const { isAbsolute } = pathModule; +-const { toPathIfFileURL } = require('internal/url'); + const { + kEmptyObject, + lazyDOMException, +@@ -983,16 +978,11 @@ async function symlink(target, path, type_) { + } + } + +- if (permission.isEnabled()) { +- // The permission model's security guarantees fall apart in the presence of +- // relative symbolic links. Thus, we have to prevent their creation. +- if (BufferIsBuffer(target)) { +- if (!isAbsolute(BufferToString(target))) { +- throw new ERR_ACCESS_DENIED('relative symbolic link target'); +- } +- } else if (typeof target !== 'string' || !isAbsolute(toPathIfFileURL(target))) { +- throw new ERR_ACCESS_DENIED('relative symbolic link target'); +- } ++ // Due to the nature of Node.js runtime, symlinks has different edge cases that can bypass ++ // the permission model security guarantees. Thus, this API is disabled unless fs.read ++ // and fs.write permission has been given. ++ if (permission.isEnabled() && !permission.has('fs')) { ++ throw new ERR_ACCESS_DENIED('fs.symlink API requires full fs.read and fs.write permissions.'); + } + + target = getValidatedPath(target, 'target'); +diff --git a/test/fixtures/permission/fs-symlink-target-write.js b/test/fixtures/permission/fs-symlink-target-write.js +index c17d674d59e..6e07bfa838e 100644 +--- a/test/fixtures/permission/fs-symlink-target-write.js ++++ b/test/fixtures/permission/fs-symlink-target-write.js +@@ -26,8 +26,7 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; + fs.symlinkSync(path.join(readOnlyFolder, 'file'), path.join(readWriteFolder, 'link-to-read-only'), 'file'); + }, common.expectsError({ + code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemWrite', +- resource: path.toNamespacedPath(path.join(readOnlyFolder, 'file')), ++ message: 'fs.symlink API requires full fs.read and fs.write permissions.', + })); + assert.throws(() => { + fs.linkSync(path.join(readOnlyFolder, 'file'), path.join(readWriteFolder, 'link-to-read-only')); +@@ -37,18 +36,6 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; + resource: path.toNamespacedPath(path.join(readOnlyFolder, 'file')), + })); + +- // App will be able to symlink to a writeOnlyFolder +- fs.symlink(path.join(readWriteFolder, 'file'), path.join(writeOnlyFolder, 'link-to-read-write'), 'file', (err) => { +- assert.ifError(err); +- // App will won't be able to read the symlink +- fs.readFile(path.join(writeOnlyFolder, 'link-to-read-write'), common.expectsError({ +- code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemRead', +- })); +- +- // App will be able to write to the symlink +- fs.writeFile(path.join(writeOnlyFolder, 'link-to-read-write'), 'some content', common.mustSucceed()); +- }); + fs.link(path.join(readWriteFolder, 'file'), path.join(writeOnlyFolder, 'link-to-read-write2'), (err) => { + assert.ifError(err); + // App will won't be able to read the link +@@ -66,8 +53,7 @@ const writeOnlyFolder = process.env.WRITEONLYFOLDER; + fs.symlinkSync(path.join(readWriteFolder, 'file'), path.join(readOnlyFolder, 'link-to-read-only'), 'file'); + }, common.expectsError({ + code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemWrite', +- resource: path.toNamespacedPath(path.join(readOnlyFolder, 'link-to-read-only')), ++ message: 'fs.symlink API requires full fs.read and fs.write permissions.', + })); + assert.throws(() => { + fs.linkSync(path.join(readWriteFolder, 'file'), path.join(readOnlyFolder, 'link-to-read-only')); +diff --git a/test/fixtures/permission/fs-symlink.js b/test/fixtures/permission/fs-symlink.js +index 4cf3b45f0eb..ba60f7811bd 100644 +--- a/test/fixtures/permission/fs-symlink.js ++++ b/test/fixtures/permission/fs-symlink.js +@@ -54,7 +54,6 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; + fs.readFileSync(blockedFile); + }, common.expectsError({ + code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemRead', + })); + assert.throws(() => { + fs.appendFileSync(blockedFile, 'data'); +@@ -68,7 +67,6 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; + fs.symlinkSync(regularFile, blockedFolder + '/asdf', 'file'); + }, common.expectsError({ + code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemWrite', + })); + assert.throws(() => { + fs.linkSync(regularFile, blockedFolder + '/asdf'); +@@ -82,7 +80,6 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; + fs.symlinkSync(blockedFile, path.join(__dirname, '/asdf'), 'file'); + }, common.expectsError({ + code: 'ERR_ACCESS_DENIED', +- permission: 'FileSystemRead', + })); + assert.throws(() => { + fs.linkSync(blockedFile, path.join(__dirname, '/asdf')); +@@ -90,4 +87,19 @@ const symlinkFromBlockedFile = process.env.EXISTINGSYMLINK; + code: 'ERR_ACCESS_DENIED', + permission: 'FileSystemRead', + })); ++} ++ ++// fs.symlink API is blocked by default ++{ ++ assert.throws(() => { ++ fs.symlinkSync(regularFile, regularFile); ++ }, common.expectsError({ ++ message: 'fs.symlink API requires full fs.read and fs.write permissions.', ++ code: 'ERR_ACCESS_DENIED', ++ })); ++ ++ fs.symlink(regularFile, regularFile, common.expectsError({ ++ message: 'fs.symlink API requires full fs.read and fs.write permissions.', ++ code: 'ERR_ACCESS_DENIED', ++ })); + } +\ No newline at end of file +diff --git a/test/parallel/test-permission-fs-symlink-relative.js b/test/parallel/test-permission-fs-symlink-relative.js +index 4cc7d920593..9080f16c663 100644 +--- a/test/parallel/test-permission-fs-symlink-relative.js ++++ b/test/parallel/test-permission-fs-symlink-relative.js +@@ -1,4 +1,4 @@ +-// Flags: --experimental-permission --allow-fs-read=* --allow-fs-write=* ++// Flags: --experimental-permission --allow-fs-read=* + 'use strict'; + + const common = require('../common'); +@@ -10,7 +10,7 @@ const { symlinkSync, symlink, promises: { symlink: symlinkAsync } } = require('f + + const error = { + code: 'ERR_ACCESS_DENIED', +- message: /relative symbolic link target/, ++ message: /symlink API requires full fs\.read and fs\.write permissions/, + }; + + for (const targetString of ['a', './b/c', '../d', 'e/../f', 'C:drive-relative', 'ntfs:alternate']) { +@@ -27,14 +27,14 @@ for (const targetString of ['a', './b/c', '../d', 'e/../f', 'C:drive-relative', + } + } + +-// Absolute should not throw ++// Absolute should throw too + for (const targetString of [path.resolve('.')]) { + for (const target of [targetString, Buffer.from(targetString)]) { + for (const path of [__filename]) { + symlink(target, path, common.mustCall((err) => { + assert(err); +- assert.strictEqual(err.code, 'EEXIST'); +- assert.match(err.message, /file already exists/); ++ assert.strictEqual(err.code, error.code); ++ assert.match(err.message, error.message); + })); + } + } +diff --git a/test/parallel/test-permission-fs-symlink.js b/test/parallel/test-permission-fs-symlink.js +index c7d753c267c..268a8ecb9aa 100644 +--- a/test/parallel/test-permission-fs-symlink.js ++++ b/test/parallel/test-permission-fs-symlink.js +@@ -21,15 +21,26 @@ const commonPathWildcard = path.join(__filename, '../../common*'); + const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md'); + const blockedFolder = tmpdir.resolve('subdirectory'); + const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); ++const allowedFolder = tmpdir.resolve('allowed-folder'); ++const traversalSymlink = path.join(allowedFolder, 'deep1', 'deep2', 'deep3', 'gotcha'); + + { + tmpdir.refresh(); + fs.mkdirSync(blockedFolder); ++ // Create deep directory structure for path traversal test ++ fs.mkdirSync(allowedFolder); ++ fs.writeFileSync(path.resolve(allowedFolder, '../protected-file.md'), 'protected'); ++ fs.mkdirSync(path.join(allowedFolder, 'deep1')); ++ fs.mkdirSync(path.join(allowedFolder, 'deep1', 'deep2')); ++ fs.mkdirSync(path.join(allowedFolder, 'deep1', 'deep2', 'deep3')); + } + + { + // Symlink previously created ++ // fs.symlink API is allowed when full-read and full-write access + fs.symlinkSync(blockedFile, symlinkFromBlockedFile); ++ // Create symlink for path traversal test - symlink points to parent directory ++ fs.symlinkSync(allowedFolder, traversalSymlink); + } + + { +@@ -38,6 +49,7 @@ const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); + [ + '--experimental-permission', + `--allow-fs-read=${file}`, `--allow-fs-read=${commonPathWildcard}`, `--allow-fs-read=${symlinkFromBlockedFile}`, ++ `--allow-fs-read=${allowedFolder}`, + `--allow-fs-write=${symlinkFromBlockedFile}`, + file, + ], +@@ -47,6 +59,8 @@ const symlinkFromBlockedFile = tmpdir.resolve('example-symlink.md'); + BLOCKEDFOLDER: blockedFolder, + BLOCKEDFILE: blockedFile, + EXISTINGSYMLINK: symlinkFromBlockedFile, ++ TRAVERSALSYMLINK: traversalSymlink, ++ ALLOWEDFOLDER: allowedFolder, + }, + } + ); +-- +2.43.7 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb index 67574a2ec1..7c97c7282c 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb @@ -30,6 +30,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://0001-src-fix-build-with-GCC-15.patch \ file://run-ptest \ file://CVE-2025-55132.patch \ + file://CVE-2025-55130.patch \ " SRC_URI:append:class-target = " \ file://0001-Using-native-binaries.patch \ From patchwork Thu Feb 12 05:21:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 80966 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id E0A3FEB48FC for ; Thu, 12 Feb 2026 10:32:53 +0000 (UTC) Received: from alln-iport-7.cisco.com (alln-iport-7.cisco.com [173.37.142.94]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.38879.1770873688848250541 for ; Wed, 11 Feb 2026 21:21:29 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=Q5kYWpUR; spf=pass (domain: cisco.com, ip: 173.37.142.94, mailfrom: adongare@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=20944; q=dns/txt; s=iport01; t=1770873689; x=1772083289; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=dTx7fm1OMI6O+k1xUM5LFLuFM6smWAM5fAhNuoS737M=; b=Q5kYWpURipSPDpgvWoSOIRype9MHXEAbmI7PYsLVjifuJRQLepyv2XE1 tD9UpP1dO/EOfvj7ia5ouruHJfJpdKqYByShdCrDa1s/lE0odVIZse3NK 202DW797WXYEZ28MF+c4cIRIzV8p3EMGNvC6e7ZGvP1iKGesGArVu0XVV H8KhJvAEfBVGIqy9qNKQH9mWMpJU0Lz4NIRMgwpZjqWCnQEIFjhEr/kgS Wzito+ly/DYok2WUDNeZFwfa/ydnI5om+u9wKZas7GefcnxW+2bFFJ5GG uuKnUgs9eDKmH6m5maqFnjhdWs7tELImuFOder/g4l0xH/WuAUFhjdr2R g==; X-CSE-ConnectionGUID: UdQntKpOS/SA6/W0XNJz2g== X-CSE-MsgGUID: jLkjp7FURQeU6pdJJO3trw== X-IPAS-Result: A0CCCABQYo1p/5X/Ja1aHgEBCxIMggULghgwD3FeQ0mWSwOBE4FriGaFZolxgl8UgWgDDwEBAQ89FAQBAYUHAo0dAiY1CA4BAgQBAQEBAwIDAQEBAQEBAQEBAQELAQEFAQEBAgEHBYEOE4ZPDYZaAQIBAycLARgBLRAcAwECLyALIwgZgwIBgjoDNgMRqCxCgXkzgQGDKAE/AkNP2EcNA4JPAQsUAYE4hTuCeYUfWxgBhHgnGxuBcoEVgnJ2gQWBGkIBAYEtGoZdBIIigQ6BZCcPiU6IBkiBHgNZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPheBCxsHBYgVD4kPeHCBIHIDCxgNSBEsNxQbBD5uB45LQYIsBwEVJxMJGwcTASoBIAJjEzU2Aw4YHB6SVxQQAZAdgiGgHXEKKIN0jB6PPgGFexozhVulEAuYe4smgmOECZFXBBxQhGiBagE5RoEBCwdwFYMiCUkZD44tCwuBEgECfIFNgX+DFLxOJTICOgIHCwEBAwmRa4F8AQE IronPort-Data: A9a23:fSVagK8vwa3Km+J63RpTDrUD0H+TJUtcMsCJ2f8bNWPcYEJGY0x3n WoZWjyDPf/YMTb3c951btix/UsFvZHTz4BgQQdk+SxEQiMRo6IpJzg2wmQcns+2BpeeJK6yx 5xGMrEsFOhtEDmE4EzrauS9xZVF/fngbqLmD+LZMTxGSwZhSSMw4TpugOdRbrRA2bBVOCvT/ 4mryyHjEAX9gWAsaDtOs/vrRC5H5ZwehhtJ5jTSWtgT1LPuvyF9JI4SI6i3M0z5TuF8dsamR /zOxa2O5WjQ+REgELuNyt4XpWVTH9Y+lSDX4pZnc/DKbipq/0Te4Y5nXBYoUnq7vh3S9zxHJ HqhgrTrIeshFvWkdO3wyHC0GQkmVUFN0OevzXRSLaV/wmWeG0YAzcmCA2kLMbQe5sVvJloJz tVGbw9VXjahlbi5lefTpulE3qzPLeHxN48Z/3UlxjbDALN/G9bIQr7B4plT2zJYasJmRKmFI ZFGL2AyMVKZP0wn1lQ/UPrSmM+hin75fDRCpXqepLE85C7YywkZPL3FboSJIo3aHZsE9qqej mDi/GbhXT8nDuWS9GSd/lCOobTThzyuDer+E5X9rJaGmma7wXQeDhATX1a3rfS1z0KzRd9bA 0gV4TY1668q+UqmS9PwUxG1rDiDpBF0ZjZLO/cx5AfIzu/f5ByUQzFeCDVAc9ch8sQxQFTGy 2O0oj8gPhQ32JX9dJ5X3u38Qe+aUcTNEVI/WA== IronPort-HdrOrdr: A9a23:6r7vuaitJFdkN+I/6oA5QWOLBnBQXt0ji2hC6mlwRA09TyVXra +TdZMgpHjJYVkqOU3I9ersBEDEewK/yXcX2/h0AV7BZmnbUQKTRekIh7cKgQeQfhEWndQy6U 4PScRD4aXLfDtHZQKQ2njALz7mq+P3lpyVuQ== X-Talos-CUID: 9a23:rv9siG/1K9fZAFeCLKWVv3MYI8Y5UWX69zDrChH/Nj5Zbp+HE1DFrQ== X-Talos-MUID: 9a23:m2wWCAWiSRAdp6/q/D3IhzN6a+lV2Z2jNR4VnbcknJKYLzMlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.21,286,1763424000"; d="scan'208";a="664098069" Received: from rcdn-l-core-12.cisco.com ([173.37.255.149]) by alln-iport-7.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 12 Feb 2026 05:21:28 +0000 Received: from sjc-ads-10055.cisco.com (sjc-ads-10055.cisco.com [10.30.210.59]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-12.cisco.com (Postfix) with ESMTPS id AD6F9180001E9; Thu, 12 Feb 2026 05:21:28 +0000 (GMT) Received: by sjc-ads-10055.cisco.com (Postfix, from userid 1870532) id 5B542CC12A6; Wed, 11 Feb 2026 21:21:28 -0800 (PST) From: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, to@cisco.com, Anil Dongare Subject: [meta-OE] [scarthgap] [PATCH 3/5] Nodejs 20.18.2: Fix CVE-2025-59466 Date: Wed, 11 Feb 2026 21:21:08 -0800 Message-ID: <20260212052114.3215220-3-adongare@cisco.com> X-Mailer: git-send-email 2.44.1 In-Reply-To: <20260212052114.3215220-1-adongare@cisco.com> References: <20260212052114.3215220-1-adongare@cisco.com> MIME-Version: 1.0 X-Outbound-SMTP-Client: 10.30.210.59, sjc-ads-10055.cisco.com X-Outbound-Node: rcdn-l-core-12.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 12 Feb 2026 10:32:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/124350 From: Anil Dongare Upstream Repository: https://github.com/nodejs/node.git Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-59466 Type: Security Fix CVE: CVE-2025-59466 Score: 7.5 Patch: https://github.com/nodejs/node/commit/ddadc31f09af Signed-off-by: Anil Dongare --- .../nodejs/nodejs/CVE-2025-59466.patch | 508 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.18.2.bb | 1 + 2 files changed, 509 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59466.patch diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59466.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59466.patch new file mode 100644 index 0000000000..6121432697 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59466.patch @@ -0,0 +1,508 @@ +From c5cad9d8ec0bccc8136903823c079da7cc6b1930 Mon Sep 17 00:00:00 2001 +From: Matteo Collina +Date: Tue, 9 Dec 2025 23:50:18 +0100 +Subject: [PATCH 3/6] src: rethrow stack overflow exceptions in async_hooks + +When a stack overflow exception occurs during async_hooks callbacks +(which use TryCatchScope::kFatal), detect the specific "Maximum call +stack size exceeded" RangeError and re-throw it instead of immediately +calling FatalException. This allows user code to catch the exception +with try-catch blocks instead of requiring uncaughtException handlers. + +The implementation adds IsStackOverflowError() helper to detect stack +overflow RangeErrors and re-throws them in TryCatchScope destructor +instead of calling FatalException. + +This fixes the issue where async_hooks would cause stack overflow +exceptions to exit with code 7 (kExceptionInFatalExceptionHandler) +instead of being catchable. + +Fixes: https://github.com/nodejs/node/issues/37989 +Ref: https://hackerone.com/reports/3456295 +PR-URL: https://github.com/nodejs-private/node-private/pull/773 +Refs: https://hackerone.com/reports/3456295 +Reviewed-By: Robert Nagy +Reviewed-By: Paolo Insogna +Reviewed-By: Marco Ippolito +Reviewed-By: Rafael Gonzaga +Reviewed-By: Anna Henningsen +CVE-ID: CVE-2025-59466 + +CVE: CVE-2025-59466 +Upstream-Status: Backport [https://github.com/nodejs/node/commit/ddadc31f09af] + +(cherry picked from commit ddadc31f09afdbab7545c86cc0f17d137beb8048) +Signed-off-by: Anil Dongare +--- + src/async_wrap.cc | 9 ++- + src/debug_utils.cc | 3 +- + src/node_errors.cc | 71 ++++++++++++++-- + src/node_errors.h | 2 +- + src/node_report.cc | 3 +- + ...async-hooks-stack-overflow-nested-async.js | 80 +++++++++++++++++++ + ...st-async-hooks-stack-overflow-try-catch.js | 47 +++++++++++ + .../test-async-hooks-stack-overflow.js | 47 +++++++++++ + ...andler-stack-overflow-on-stack-overflow.js | 29 +++++++ + ...caught-exception-handler-stack-overflow.js | 29 +++++++ + 10 files changed, 306 insertions(+), 14 deletions(-) + create mode 100644 test/parallel/test-async-hooks-stack-overflow-nested-async.js + create mode 100644 test/parallel/test-async-hooks-stack-overflow-try-catch.js + create mode 100644 test/parallel/test-async-hooks-stack-overflow.js + create mode 100644 test/parallel/test-uncaught-exception-handler-stack-overflow-on-stack-overflow.js + create mode 100644 test/parallel/test-uncaught-exception-handler-stack-overflow.js + +diff --git a/src/async_wrap.cc b/src/async_wrap.cc +index 65829a31a36..2b6fa142385 100644 +--- a/src/async_wrap.cc ++++ b/src/async_wrap.cc +@@ -67,7 +67,8 @@ static const char* const provider_names[] = { + void AsyncWrap::DestroyAsyncIdsCallback(Environment* env) { + Local fn = env->async_hooks_destroy_function(); + +- TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal); ++ TryCatchScope try_catch(env, ++ TryCatchScope::CatchMode::kFatalRethrowStackOverflow); + + do { + std::vector destroy_async_id_list; +@@ -96,7 +97,8 @@ void Emit(Environment* env, double async_id, AsyncHooks::Fields type, + + HandleScope handle_scope(env->isolate()); + Local async_id_value = Number::New(env->isolate(), async_id); +- TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal); ++ TryCatchScope try_catch(env, ++ TryCatchScope::CatchMode::kFatalRethrowStackOverflow); + USE(fn->Call(env->context(), Undefined(env->isolate()), 1, &async_id_value)); + } + +@@ -668,7 +670,8 @@ void AsyncWrap::EmitAsyncInit(Environment* env, + object, + }; + +- TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal); ++ TryCatchScope try_catch(env, ++ TryCatchScope::CatchMode::kFatalRethrowStackOverflow); + USE(init_fn->Call(env->context(), object, arraysize(argv), argv)); + } + +diff --git a/src/debug_utils.cc b/src/debug_utils.cc +index 9d36082db67..7d85ab72615 100644 +--- a/src/debug_utils.cc ++++ b/src/debug_utils.cc +@@ -333,7 +333,8 @@ void DumpJavaScriptBacktrace(FILE* fp) { + } + + Local stack; +- if (!GetCurrentStackTrace(isolate).ToLocal(&stack)) { ++ if (!GetCurrentStackTrace(isolate).ToLocal(&stack) || ++ stack->GetFrameCount() == 0) { + return; + } + +diff --git a/src/node_errors.cc b/src/node_errors.cc +index 69e474257b0..f984fd4c3d9 100644 +--- a/src/node_errors.cc ++++ b/src/node_errors.cc +@@ -188,7 +188,7 @@ static std::string GetErrorSource(Isolate* isolate, + } + + static std::atomic is_in_oom{false}; +-static std::atomic is_retrieving_js_stacktrace{false}; ++static thread_local std::atomic is_retrieving_js_stacktrace{false}; + MaybeLocal GetCurrentStackTrace(Isolate* isolate, int frame_count) { + if (isolate == nullptr) { + return MaybeLocal(); +@@ -216,9 +216,6 @@ MaybeLocal GetCurrentStackTrace(Isolate* isolate, int frame_count) { + StackTrace::CurrentStackTrace(isolate, frame_count, options); + + is_retrieving_js_stacktrace.store(false); +- if (stack->GetFrameCount() == 0) { +- return MaybeLocal(); +- } + + return scope.Escape(stack); + } +@@ -293,7 +290,8 @@ void PrintStackTrace(Isolate* isolate, + + void PrintCurrentStackTrace(Isolate* isolate, StackTracePrefix prefix) { + Local stack; +- if (GetCurrentStackTrace(isolate).ToLocal(&stack)) { ++ if (GetCurrentStackTrace(isolate).ToLocal(&stack) && ++ stack->GetFrameCount() > 0) { + PrintStackTrace(isolate, stack, prefix); + } + } +@@ -664,13 +662,52 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings( + }; + } + ++// Check if an exception is a stack overflow error (RangeError with ++// "Maximum call stack size exceeded" message). This is used to handle ++// stack overflow specially in TryCatchScope - instead of immediately ++// exiting, we can use the red zone to re-throw to user code. ++static bool IsStackOverflowError(Isolate* isolate, Local exception) { ++ if (!exception->IsNativeError()) return false; ++ ++ Local err_obj = exception.As(); ++ Local constructor_name = err_obj->GetConstructorName(); ++ ++ // Must be a RangeError ++ Utf8Value name(isolate, constructor_name); ++ if (name.ToStringView() != "RangeError") return false; ++ ++ // Check for the specific stack overflow message ++ Local context = isolate->GetCurrentContext(); ++ Local message_val; ++ if (!err_obj->Get(context, String::NewFromUtf8Literal(isolate, "message")) ++ .ToLocal(&message_val)) { ++ return false; ++ } ++ ++ if (!message_val->IsString()) return false; ++ ++ Utf8Value message(isolate, message_val.As()); ++ return message.ToStringView() == "Maximum call stack size exceeded"; ++} ++ + namespace errors { + + TryCatchScope::~TryCatchScope() { +- if (HasCaught() && !HasTerminated() && mode_ == CatchMode::kFatal) { ++ if (HasCaught() && !HasTerminated() && mode_ != CatchMode::kNormal) { + HandleScope scope(env_->isolate()); + Local exception = Exception(); + Local message = Message(); ++ ++ // Special handling for stack overflow errors in async_hooks: instead of ++ // immediately exiting, re-throw the exception. This allows the exception ++ // to propagate to user code's try-catch blocks. ++ if (mode_ == CatchMode::kFatalRethrowStackOverflow && ++ IsStackOverflowError(env_->isolate(), exception)) { ++ ReThrow(); ++ Reset(); ++ return; ++ } ++ + EnhanceFatalException enhance = CanContinue() ? + EnhanceFatalException::kEnhance : EnhanceFatalException::kDontEnhance; + if (message.IsEmpty()) +@@ -1221,8 +1258,26 @@ void TriggerUncaughtException(Isolate* isolate, + if (env->can_call_into_js()) { + // We do not expect the global uncaught exception itself to throw any more + // exceptions. If it does, exit the current Node.js instance. +- errors::TryCatchScope try_catch(env, +- errors::TryCatchScope::CatchMode::kFatal); ++ // Special case: if the original error was a stack overflow and calling ++ // _fatalException causes another stack overflow, rethrow it to allow ++ // user code's try-catch blocks to potentially catch it. ++ auto is_stack_overflow = [&] { ++ return IsStackOverflowError(env->isolate(), error); ++ }; ++ // Without a JS stack, rethrowing may or may not do anything. ++ // TODO(addaleax): In V8, expose a way to check whether there is a JS stack ++ // or TryCatch that would capture the rethrown exception. ++ auto has_js_stack = [&] { ++ HandleScope handle_scope(env->isolate()); ++ Local stack; ++ return GetCurrentStackTrace(env->isolate(), 1).ToLocal(&stack) && ++ stack->GetFrameCount() > 0; ++ }; ++ errors::TryCatchScope::CatchMode mode = ++ is_stack_overflow() && has_js_stack() ++ ? errors::TryCatchScope::CatchMode::kFatalRethrowStackOverflow ++ : errors::TryCatchScope::CatchMode::kFatal; ++ errors::TryCatchScope try_catch(env, mode); + // Explicitly disable verbose exception reporting - + // if process._fatalException() throws an error, we don't want it to + // trigger the per-isolate message listener which will call this +diff --git a/src/node_errors.h b/src/node_errors.h +index ac07b96b5ca..0b60e521df1 100644 +--- a/src/node_errors.h ++++ b/src/node_errors.h +@@ -265,7 +265,7 @@ namespace errors { + + class TryCatchScope : public v8::TryCatch { + public: +- enum class CatchMode { kNormal, kFatal }; ++ enum class CatchMode { kNormal, kFatal, kFatalRethrowStackOverflow }; + + explicit TryCatchScope(Environment* env, CatchMode mode = CatchMode::kNormal) + : v8::TryCatch(env->isolate()), env_(env), mode_(mode) {} +diff --git a/src/node_report.cc b/src/node_report.cc +index 5368d8eef2f..f7ec2c6ed0e 100644 +--- a/src/node_report.cc ++++ b/src/node_report.cc +@@ -463,7 +463,8 @@ static void PrintJavaScriptStack(JSONWriter* writer, + const char* trigger) { + HandleScope scope(isolate); + Local stack; +- if (!GetCurrentStackTrace(isolate, MAX_FRAME_COUNT).ToLocal(&stack)) { ++ if (!GetCurrentStackTrace(isolate, MAX_FRAME_COUNT).ToLocal(&stack) || ++ stack->GetFrameCount() == 0) { + PrintEmptyJavaScriptStack(writer); + return; + } +diff --git a/test/parallel/test-async-hooks-stack-overflow-nested-async.js b/test/parallel/test-async-hooks-stack-overflow-nested-async.js +new file mode 100644 +index 00000000000..779f8d75ae2 +--- /dev/null ++++ b/test/parallel/test-async-hooks-stack-overflow-nested-async.js +@@ -0,0 +1,80 @@ ++'use strict'; ++ ++// This test verifies that stack overflow during deeply nested async operations ++// with async_hooks enabled can be caught by try-catch. This simulates real-world ++// scenarios like processing deeply nested JSON structures where each level ++// creates async operations (e.g., database calls, API requests). ++ ++require('../common'); ++const assert = require('assert'); ++const { spawnSync } = require('child_process'); ++ ++if (process.argv[2] === 'child') { ++ const { createHook } = require('async_hooks'); ++ ++ // Enable async_hooks with all callbacks (simulates APM tools) ++ createHook({ ++ init() {}, ++ before() {}, ++ after() {}, ++ destroy() {}, ++ promiseResolve() {}, ++ }).enable(); ++ ++ // Simulate an async operation (like a database call or API request) ++ async function fetchThing(id) { ++ return { id, data: `data-${id}` }; ++ } ++ ++ // Recursively process deeply nested data structure ++ // This will cause stack overflow when the nesting is deep enough ++ function processData(data, depth = 0) { ++ if (Array.isArray(data)) { ++ for (const item of data) { ++ // Create a promise to trigger async_hooks init callback ++ fetchThing(depth); ++ processData(item, depth + 1); ++ } ++ } ++ } ++ ++ // Create deeply nested array structure iteratively (to avoid stack overflow ++ // during creation) ++ function createNestedArray(depth) { ++ let result = 'leaf'; ++ for (let i = 0; i < depth; i++) { ++ result = [result]; ++ } ++ return result; ++ } ++ ++ // Create a very deep nesting that will cause stack overflow during processing ++ const deeplyNested = createNestedArray(50000); ++ ++ try { ++ processData(deeplyNested); ++ // Should not complete successfully - the nesting is too deep ++ console.log('UNEXPECTED: Processing completed without error'); ++ process.exit(1); ++ } catch (err) { ++ assert.strictEqual(err.name, 'RangeError'); ++ assert.match(err.message, /Maximum call stack size exceeded/); ++ console.log('SUCCESS: try-catch caught the stack overflow in nested async'); ++ process.exit(0); ++ } ++} else { ++ // Parent process - spawn the child and check exit code ++ const result = spawnSync( ++ process.execPath, ++ [__filename, 'child'], ++ { encoding: 'utf8', timeout: 30000 } ++ ); ++ ++ // Should exit successfully (try-catch worked) ++ assert.strictEqual(result.status, 0, ++ `Expected exit code 0, got ${result.status}.\n` + ++ `stdout: ${result.stdout}\n` + ++ `stderr: ${result.stderr}`); ++ // Verify the error was handled by try-catch ++ assert.match(result.stdout, /SUCCESS: try-catch caught the stack overflow/); ++} +diff --git a/test/parallel/test-async-hooks-stack-overflow-try-catch.js b/test/parallel/test-async-hooks-stack-overflow-try-catch.js +new file mode 100644 +index 00000000000..43338905e78 +--- /dev/null ++++ b/test/parallel/test-async-hooks-stack-overflow-try-catch.js +@@ -0,0 +1,47 @@ ++'use strict'; ++ ++// This test verifies that when a stack overflow occurs with async_hooks ++// enabled, the exception can be caught by try-catch blocks in user code. ++ ++require('../common'); ++const assert = require('assert'); ++const { spawnSync } = require('child_process'); ++ ++if (process.argv[2] === 'child') { ++ const { createHook } = require('async_hooks'); ++ ++ createHook({ init() {} }).enable(); ++ ++ function recursive(depth = 0) { ++ // Create a promise to trigger async_hooks init callback ++ new Promise(() => {}); ++ return recursive(depth + 1); ++ } ++ ++ try { ++ recursive(); ++ // Should not reach here ++ process.exit(1); ++ } catch (err) { ++ assert.strictEqual(err.name, 'RangeError'); ++ assert.match(err.message, /Maximum call stack size exceeded/); ++ console.log('SUCCESS: try-catch caught the stack overflow'); ++ process.exit(0); ++ } ++ ++ // Should not reach here ++ process.exit(2); ++} else { ++ // Parent process - spawn the child and check exit code ++ const result = spawnSync( ++ process.execPath, ++ [__filename, 'child'], ++ { encoding: 'utf8', timeout: 30000 } ++ ); ++ ++ assert.strictEqual(result.status, 0, ++ `Expected exit code 0 (try-catch worked), got ${result.status}.\n` + ++ `stdout: ${result.stdout}\n` + ++ `stderr: ${result.stderr}`); ++ assert.match(result.stdout, /SUCCESS: try-catch caught the stack overflow/); ++} +diff --git a/test/parallel/test-async-hooks-stack-overflow.js b/test/parallel/test-async-hooks-stack-overflow.js +new file mode 100644 +index 00000000000..aff41969dbd +--- /dev/null ++++ b/test/parallel/test-async-hooks-stack-overflow.js +@@ -0,0 +1,47 @@ ++'use strict'; ++ ++// This test verifies that when a stack overflow occurs with async_hooks ++// enabled, the uncaughtException handler is still called instead of the ++// process crashing with exit code 7. ++ ++const common = require('../common'); ++const assert = require('assert'); ++const { spawnSync } = require('child_process'); ++ ++if (process.argv[2] === 'child') { ++ const { createHook } = require('async_hooks'); ++ ++ let handlerCalled = false; ++ ++ function recursive() { ++ // Create a promise to trigger async_hooks init callback ++ new Promise(() => {}); ++ return recursive(); ++ } ++ ++ createHook({ init() {} }).enable(); ++ ++ process.on('uncaughtException', common.mustCall((err) => { ++ assert.strictEqual(err.name, 'RangeError'); ++ assert.match(err.message, /Maximum call stack size exceeded/); ++ // Ensure handler is only called once ++ assert.strictEqual(handlerCalled, false); ++ handlerCalled = true; ++ })); ++ ++ setImmediate(recursive); ++} else { ++ // Parent process - spawn the child and check exit code ++ const result = spawnSync( ++ process.execPath, ++ [__filename, 'child'], ++ { encoding: 'utf8', timeout: 30000 } ++ ); ++ ++ // Should exit with code 0 (handler was called and handled the exception) ++ // Previously would exit with code 7 (kExceptionInFatalExceptionHandler) ++ assert.strictEqual(result.status, 0, ++ `Expected exit code 0, got ${result.status}.\n` + ++ `stdout: ${result.stdout}\n` + ++ `stderr: ${result.stderr}`); ++} +diff --git a/test/parallel/test-uncaught-exception-handler-stack-overflow-on-stack-overflow.js b/test/parallel/test-uncaught-exception-handler-stack-overflow-on-stack-overflow.js +new file mode 100644 +index 00000000000..1923b7f24d9 +--- /dev/null ++++ b/test/parallel/test-uncaught-exception-handler-stack-overflow-on-stack-overflow.js +@@ -0,0 +1,29 @@ ++'use strict'; ++ ++// This test verifies that when the uncaughtException handler itself causes ++// a stack overflow, the process exits with a non-zero exit code. ++// This is important to ensure we don't silently swallow errors. ++ ++require('../common'); ++const assert = require('assert'); ++const { spawnSync } = require('child_process'); ++ ++if (process.argv[2] === 'child') { ++ function f() { f(); } ++ process.on('uncaughtException', f); ++ f(); ++} else { ++ // Parent process - spawn the child and check exit code ++ const result = spawnSync( ++ process.execPath, ++ [__filename, 'child'], ++ { encoding: 'utf8', timeout: 30000 } ++ ); ++ ++ // Should exit with non-zero exit code since the uncaughtException handler ++ // itself caused a stack overflow. ++ assert.notStrictEqual(result.status, 0, ++ `Expected non-zero exit code, got ${result.status}.\n` + ++ `stdout: ${result.stdout}\n` + ++ `stderr: ${result.stderr}`); ++} +diff --git a/test/parallel/test-uncaught-exception-handler-stack-overflow.js b/test/parallel/test-uncaught-exception-handler-stack-overflow.js +new file mode 100644 +index 00000000000..050cd0923ee +--- /dev/null ++++ b/test/parallel/test-uncaught-exception-handler-stack-overflow.js +@@ -0,0 +1,29 @@ ++'use strict'; ++ ++// This test verifies that when the uncaughtException handler itself causes ++// a stack overflow, the process exits with a non-zero exit code. ++// This is important to ensure we don't silently swallow errors. ++ ++require('../common'); ++const assert = require('assert'); ++const { spawnSync } = require('child_process'); ++ ++if (process.argv[2] === 'child') { ++ function f() { f(); } ++ process.on('uncaughtException', f); ++ throw new Error('X'); ++} else { ++ // Parent process - spawn the child and check exit code ++ const result = spawnSync( ++ process.execPath, ++ [__filename, 'child'], ++ { encoding: 'utf8', timeout: 30000 } ++ ); ++ ++ // Should exit with non-zero exit code since the uncaughtException handler ++ // itself caused a stack overflow. ++ assert.notStrictEqual(result.status, 0, ++ `Expected non-zero exit code, got ${result.status}.\n` + ++ `stdout: ${result.stdout}\n` + ++ `stderr: ${result.stderr}`); ++} +-- +2.43.7 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb index 7c97c7282c..779c70dbd0 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb @@ -31,6 +31,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://run-ptest \ file://CVE-2025-55132.patch \ file://CVE-2025-55130.patch \ + file://CVE-2025-59466.patch \ " SRC_URI:append:class-target = " \ file://0001-Using-native-binaries.patch \ From patchwork Thu Feb 12 05:21:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 80963 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CE086EB48F8 for ; Thu, 12 Feb 2026 10:32:53 +0000 (UTC) Received: from alln-iport-8.cisco.com (alln-iport-8.cisco.com [173.37.142.95]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.39074.1770873690822979864 for ; Wed, 11 Feb 2026 21:21:31 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=fOynfV9p; spf=pass (domain: cisco.com, ip: 173.37.142.95, mailfrom: adongare@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=19434; q=dns/txt; s=iport01; t=1770873690; x=1772083290; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=hZACpAF1mLHgzY9SGaI15kpu9zWk/aNl7EZIsuNkcOk=; b=fOynfV9pRhIN0OTL9TwroFLPDeslU6xPcJoRsILH+Uesf4ffGBMkADFm wIB/8TQ1v+UwpINMtoo1+JXJ+j1TB7xczgP0Xv3IdaXn227AWKrV+KEq6 MDvaGef3ZWzZ6SpPgMH0P9vlF0vp4dohbzx5xa73cgbqIk2e5huxh2Gf6 +6Iaoe0/Ey5O8/JEOEGRm+LU53uocj007cwCR89fWOB6dvkc8/8uZ78Nr K6OisAVoUI4ggXYEKRtGFJxgliACzlmXQSz5MItPECrelQ7v6EqyX2+EF 9bx0EqhTnFmMslsggFmPH0l8/ujRbzCNbEf3ueR9lBcYwxjEqKQT7qKq3 w==; X-CSE-ConnectionGUID: WzRg/t3ORpyBwEM6bL9scA== X-CSE-MsgGUID: Vm+4uuo5QTOG21EQ7DT6ig== X-IPAS-Result: A0CwBgBQYo1p/4v/Ja1RCYJZghgwD3FeQ0mWSwOBE4pRkjYUgWsPAQEBDz0UBAEBhQcCjR0CJjQJDgECBAEBAQEDAgMBAQEBAQEBAQEBAQsBAQUBAQECAQcFgQ4Thk8NhloBAgEDJwsBGAEtEBwDAQIvIAsjCBmCKlgBgjoDNgMRqG6BeTOBAYMoAT8CQ0/YRw2CUgELFAGBOIU7gnmFH1sYAYNZgR8nGxuBcoEVgnJ2gQWBGkIBAYE0EwGGXASCDRWBDoFkJw+RVEiBHgNZLAFVEw0KCwcFgWYDNRIqFW4yHYEjPheBCxsHBYgVD4kPeHCBIHIDCxgNSBEsNxQbBD5uB45LQYFBclArEwEqAReBAjQ2BQwYDgGTFhA7kgSBNZ5ocQoog3SMHo8+hXwaM4VbpRALmHuLJoJjhAmRVxNdhGiBaDyBRwsHcBU7gmcJSRkPjioOC4NegX+DFLxOJTICOgIHCwEBAwmRbC2BTgEB IronPort-Data: A9a23:k38hTK5DJKVEDYIGrynXyQxRtGjGchMFZxGqfqrLsTDasY5as4F+v mMXUWvXPareYmf8e4x1b96w/B9SvJDUx9BiQVdtrnxnZn8b8sCt6fZ1gavT04J+CuWZESqLO u1HMoGowPgcFyGa/lH2dOC98RGQ7InQLpLkEunIJyttcgFtTSYlmHpLlvUw6mJSqYDR7zil5 5Wo+qUzBHf/g2QqajhNsfrawP9SlK2aVA0w7wRWic9j5Dcyp1FNZLoDKKe4KWfPQ4U8NoaSW +bZwbilyXjS9hErB8nNuu6TnpoiG+O60aCm0xK6aoD66vRwjnVaPpUTaJLwXXxqZwChxLid/ jniWauYEm/FNoWU8AgUvoIx/ytWZcWq85efSZSzXFD6I0DuKxPRL/tS4E4eAp8i9aFrWkZ07 e0yNx0oMBqn1vm96efuIgVsrpxLwMjDJogTvDRkiDreF/tjGcqFSKTR7tge1zA17ixMNa+BP IxCNnw1MUmGOkYeUrsUIMpWcOOAj3X4dTJRsl+9rqss6G+Vxwt0uFToGISLJ4HbGJQPwS50o Er4+ielHwwdaeWj7hXfq1+V2cPzwyL0Ddd6+LqQs6QCbEeo7msLBRsbUFG2rfW0hguyVsxSL 2QQ+zEytu417EGtQ9z3UhG0rXLCuQQTM+e8CMUg4w2Lj66R6AGDCy1cFHhKaccts4k9QjlCO kK1ou4FzAdH6NW9IU9xPJ/Oxd9uEUD59VM/WBI= IronPort-HdrOrdr: A9a23:bD6U0auBxx1cvrfwE1onxEm17skDcdV00zEX/kB9WHVpmwKj+P xG+85rsiMc5wxxZJhNo7290ey7MBHhHP1OkO0s1NWZPDUO0VHAROoJ0WKh+UyEJ8SUzIBgPM lbH5SWcOeAbmSTSa3BkXCF+xFK+qjgzJyV X-Talos-CUID: 9a23:4/hXSm1t6lmDukSoeIeD5bxfJdIffTrBl1DqO16SFktMFrexGESb0fYx X-Talos-MUID: 9a23:B0aYgwn5O5KWu6+KIJ1tdnpcMYQ0/rmpVXkxtrpc4uS9agBMZTaS2WE= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.21,286,1763424000"; d="scan'208";a="665595927" Received: from rcdn-l-core-02.cisco.com ([173.37.255.139]) by alln-iport-8.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 12 Feb 2026 05:21:29 +0000 Received: from sjc-ads-10055.cisco.com (sjc-ads-10055.cisco.com [10.30.210.59]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-02.cisco.com (Postfix) with ESMTPS id 8AD0F1800021E; Thu, 12 Feb 2026 05:21:29 +0000 (GMT) Received: by sjc-ads-10055.cisco.com (Postfix, from userid 1870532) id 38B48CC12A6; Wed, 11 Feb 2026 21:21:29 -0800 (PST) From: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, to@cisco.com, Anil Dongare Subject: [meta-OE] [scarthgap] [PATCH 4/5] Nodejs 20.18.2: Fix CVE-2026-21637 Date: Wed, 11 Feb 2026 21:21:09 -0800 Message-ID: <20260212052114.3215220-4-adongare@cisco.com> X-Mailer: git-send-email 2.44.1 In-Reply-To: <20260212052114.3215220-1-adongare@cisco.com> References: <20260212052114.3215220-1-adongare@cisco.com> MIME-Version: 1.0 X-Outbound-SMTP-Client: 10.30.210.59, sjc-ads-10055.cisco.com X-Outbound-Node: rcdn-l-core-02.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 12 Feb 2026 10:32:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/124351 From: Anil Dongare Upstream Repository: https://github.com/nodejs/node.git Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2026-21637 Type: Security Fix CVE: CVE-2026-21637 Score: 7.5 Patch: https://github.com/nodejs/node/commit/25d6799df65c Signed-off-by: Anil Dongare --- .../nodejs/nodejs/CVE-2026-21637.patch | 616 ++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.18.2.bb | 1 + 2 files changed, 617 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2026-21637.patch diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2026-21637.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2026-21637.patch new file mode 100644 index 0000000000..cdf23af7c1 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2026-21637.patch @@ -0,0 +1,616 @@ +From ba66644cc25154598c4c3988abf812022e32b8e4 Mon Sep 17 00:00:00 2001 +From: Matteo Collina +Date: Mon, 22 Dec 2025 18:25:33 +0100 +Subject: [PATCH 4/6] tls: route callback exceptions through error handlers + +Wrap pskCallback and ALPNCallback invocations in try-catch blocks +to route exceptions through owner.destroy() instead of letting them +become uncaught exceptions. This prevents remote attackers from +crashing TLS servers or causing resource exhaustion. + +Fixes: https://hackerone.com/reports/3473882 +PR-URL: https://github.com/nodejs-private/node-private/pull/782 +PR-URL: https://github.com/nodejs-private/node-private/pull/796 +Reviewed-By: Matteo Collina +CVE-ID: CVE-2026-21637 + +CVE: CVE-2026-21637 +Upstream-Status: Backport [https://github.com/nodejs/node/commit/25d6799df65c] + +(cherry picked from commit 25d6799df65cd08f5fd057cae23e00a81bf33757) +Signed-off-by: Anil Dongare +--- + lib/_tls_wrap.js | 157 ++++---- + test/parallel/test-tls-alpn-server-client.js | 32 +- + ...ls-psk-alpn-callback-exception-handling.js | 334 ++++++++++++++++++ + 3 files changed, 442 insertions(+), 81 deletions(-) + create mode 100644 test/parallel/test-tls-psk-alpn-callback-exception-handling.js + +diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js +index 4eb7b7ffa69..c3e48a6cbc8 100644 +--- a/lib/_tls_wrap.js ++++ b/lib/_tls_wrap.js +@@ -233,39 +233,44 @@ function callALPNCallback(protocolsBuffer) { + const handle = this; + const socket = handle[owner_symbol]; + +- const servername = handle.getServername(); ++ try { ++ const servername = handle.getServername(); + +- // Collect all the protocols from the given buffer: +- const protocols = []; +- let offset = 0; +- while (offset < protocolsBuffer.length) { +- const protocolLen = protocolsBuffer[offset]; +- offset += 1; ++ // Collect all the protocols from the given buffer: ++ const protocols = []; ++ let offset = 0; ++ while (offset < protocolsBuffer.length) { ++ const protocolLen = protocolsBuffer[offset]; ++ offset += 1; + +- const protocol = protocolsBuffer.slice(offset, offset + protocolLen); +- offset += protocolLen; ++ const protocol = protocolsBuffer.slice(offset, offset + protocolLen); ++ offset += protocolLen; + +- protocols.push(protocol.toString('ascii')); +- } ++ protocols.push(protocol.toString('ascii')); ++ } + +- const selectedProtocol = socket[kALPNCallback]({ +- servername, +- protocols, +- }); ++ const selectedProtocol = socket[kALPNCallback]({ ++ servername, ++ protocols, ++ }); + +- // Undefined -> all proposed protocols rejected +- if (selectedProtocol === undefined) return undefined; ++ // Undefined -> all proposed protocols rejected ++ if (selectedProtocol === undefined) return undefined; + +- const protocolIndex = protocols.indexOf(selectedProtocol); +- if (protocolIndex === -1) { +- throw new ERR_TLS_ALPN_CALLBACK_INVALID_RESULT(selectedProtocol, protocols); +- } +- let protocolOffset = 0; +- for (let i = 0; i < protocolIndex; i++) { +- protocolOffset += 1 + protocols[i].length; +- } ++ const protocolIndex = protocols.indexOf(selectedProtocol); ++ if (protocolIndex === -1) { ++ throw new ERR_TLS_ALPN_CALLBACK_INVALID_RESULT(selectedProtocol, protocols); ++ } ++ let protocolOffset = 0; ++ for (let i = 0; i < protocolIndex; i++) { ++ protocolOffset += 1 + protocols[i].length; ++ } + +- return protocolOffset; ++ return protocolOffset; ++ } catch (err) { ++ socket.destroy(err); ++ return undefined; ++ } + } + + function requestOCSP(socket, info) { +@@ -372,63 +377,75 @@ function onnewsession(sessionId, session) { + + function onPskServerCallback(identity, maxPskLen) { + const owner = this[owner_symbol]; +- const ret = owner[kPskCallback](owner, identity); +- if (ret == null) +- return undefined; + +- let psk; +- if (isArrayBufferView(ret)) { +- psk = ret; +- } else { +- if (typeof ret !== 'object') { +- throw new ERR_INVALID_ARG_TYPE( +- 'ret', +- ['Object', 'Buffer', 'TypedArray', 'DataView'], +- ret, ++ try { ++ const ret = owner[kPskCallback](owner, identity); ++ if (ret == null) ++ return undefined; ++ ++ let psk; ++ if (isArrayBufferView(ret)) { ++ psk = ret; ++ } else { ++ if (typeof ret !== 'object') { ++ throw new ERR_INVALID_ARG_TYPE( ++ 'ret', ++ ['Object', 'Buffer', 'TypedArray', 'DataView'], ++ ret, ++ ); ++ } ++ psk = ret.psk; ++ validateBuffer(psk, 'psk'); ++ } ++ ++ if (psk.length > maxPskLen) { ++ throw new ERR_INVALID_ARG_VALUE( ++ 'psk', ++ psk, ++ `Pre-shared key exceeds ${maxPskLen} bytes`, + ); + } +- psk = ret.psk; +- validateBuffer(psk, 'psk'); +- } + +- if (psk.length > maxPskLen) { +- throw new ERR_INVALID_ARG_VALUE( +- 'psk', +- psk, +- `Pre-shared key exceeds ${maxPskLen} bytes`, +- ); ++ return psk; ++ } catch (err) { ++ owner.destroy(err); ++ return undefined; + } +- +- return psk; + } + + function onPskClientCallback(hint, maxPskLen, maxIdentityLen) { + const owner = this[owner_symbol]; +- const ret = owner[kPskCallback](hint); +- if (ret == null) +- return undefined; + +- validateObject(ret, 'ret'); ++ try { ++ const ret = owner[kPskCallback](hint); ++ if (ret == null) ++ return undefined; ++ ++ validateObject(ret, 'ret'); ++ ++ validateBuffer(ret.psk, 'psk'); ++ if (ret.psk.length > maxPskLen) { ++ throw new ERR_INVALID_ARG_VALUE( ++ 'psk', ++ ret.psk, ++ `Pre-shared key exceeds ${maxPskLen} bytes`, ++ ); ++ } + +- validateBuffer(ret.psk, 'psk'); +- if (ret.psk.length > maxPskLen) { +- throw new ERR_INVALID_ARG_VALUE( +- 'psk', +- ret.psk, +- `Pre-shared key exceeds ${maxPskLen} bytes`, +- ); +- } ++ validateString(ret.identity, 'identity'); ++ if (Buffer.byteLength(ret.identity) > maxIdentityLen) { ++ throw new ERR_INVALID_ARG_VALUE( ++ 'identity', ++ ret.identity, ++ `PSK identity exceeds ${maxIdentityLen} bytes`, ++ ); ++ } + +- validateString(ret.identity, 'identity'); +- if (Buffer.byteLength(ret.identity) > maxIdentityLen) { +- throw new ERR_INVALID_ARG_VALUE( +- 'identity', +- ret.identity, +- `PSK identity exceeds ${maxIdentityLen} bytes`, +- ); ++ return { psk: ret.psk, identity: ret.identity }; ++ } catch (err) { ++ owner.destroy(err); ++ return undefined; + } +- +- return { psk: ret.psk, identity: ret.identity }; + } + + function onkeylog(line) { +diff --git a/test/parallel/test-tls-alpn-server-client.js b/test/parallel/test-tls-alpn-server-client.js +index 8f1a4b8e439..1ef32ca5938 100644 +--- a/test/parallel/test-tls-alpn-server-client.js ++++ b/test/parallel/test-tls-alpn-server-client.js +@@ -252,25 +252,35 @@ function TestALPNCallback() { + function TestBadALPNCallback() { + // Server always returns a fixed invalid value: + const serverOptions = { ++ key: loadPEM('agent2-key'), ++ cert: loadPEM('agent2-cert'), + ALPNCallback: common.mustCall(() => 'http/5') + }; + +- const clientsOptions = [{ +- ALPNProtocols: ['http/1', 'h2'], +- }]; ++ const server = tls.createServer(serverOptions); + +- process.once('uncaughtException', common.mustCall((error) => { ++ // Error should be emitted via tlsClientError, not as uncaughtException ++ server.on('tlsClientError', common.mustCall((error, socket) => { + assert.strictEqual(error.code, 'ERR_TLS_ALPN_CALLBACK_INVALID_RESULT'); ++ socket.destroy(); + })); + +- runTest(clientsOptions, serverOptions, function(results) { +- // Callback returns 'http/5' => doesn't match client ALPN => error & reset +- assert.strictEqual(results[0].server, undefined); +- const allowedErrors = ['ECONNRESET', 'ERR_SSL_TLSV1_ALERT_NO_APPLICATION_PROTOCOL']; +- assert.ok(allowedErrors.includes(results[0].client.error.code), `'${results[0].client.error.code}' was not one of ${allowedErrors}.`); ++ server.listen(0, serverIP, common.mustCall(() => { ++ const client = tls.connect({ ++ port: server.address().port, ++ host: serverIP, ++ rejectUnauthorized: false, ++ ALPNProtocols: ['http/1', 'h2'], ++ }, common.mustNotCall()); + +- TestALPNOptionsCallback(); +- }); ++ client.on('error', common.mustCall((err) => { ++ // Client gets reset when server handles error via tlsClientError ++ const allowedErrors = ['ECONNRESET', 'ERR_SSL_TLSV1_ALERT_NO_APPLICATION_PROTOCOL']; ++ assert.ok(allowedErrors.includes(err.code), `'${err.code}' was not one of ${allowedErrors}.`); ++ server.close(); ++ TestALPNOptionsCallback(); ++ })); ++ })); + } + + function TestALPNOptionsCallback() { +diff --git a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js +new file mode 100644 +index 00000000000..93bf7396d2a +--- /dev/null ++++ b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js +@@ -0,0 +1,334 @@ ++'use strict'; ++ ++// This test verifies that exceptions in pskCallback and ALPNCallback are ++// properly routed through tlsClientError instead of becoming uncaught ++// exceptions. This is a regression test for a vulnerability where callback ++// validation errors would bypass all standard TLS error handlers. ++// ++// The vulnerability allows remote attackers to crash TLS servers or cause ++// resource exhaustion (file descriptor leaks) when pskCallback or ALPNCallback ++// throw exceptions during validation. ++ ++const common = require('../common'); ++ ++if (!common.hasCrypto) ++ common.skip('missing crypto'); ++ ++const assert = require('assert'); ++const { describe, it } = require('node:test'); ++const tls = require('tls'); ++const fixtures = require('../common/fixtures'); ++ ++const CIPHERS = 'PSK+HIGH'; ++const TEST_TIMEOUT = 5000; ++ ++// Helper to create a promise that rejects on uncaughtException or timeout ++function createTestPromise() { ++ const { promise, resolve, reject } = Promise.withResolvers(); ++ let settled = false; ++ ++ const cleanup = () => { ++ if (!settled) { ++ settled = true; ++ process.removeListener('uncaughtException', onUncaught); ++ clearTimeout(timeout); ++ } ++ }; ++ ++ const onUncaught = (err) => { ++ cleanup(); ++ reject(new Error( ++ `Uncaught exception instead of tlsClientError: ${err.code || err.message}` ++ )); ++ }; ++ ++ const timeout = setTimeout(() => { ++ cleanup(); ++ reject(new Error('Test timed out - tlsClientError was not emitted')); ++ }, TEST_TIMEOUT); ++ ++ process.on('uncaughtException', onUncaught); ++ ++ return { ++ resolve: (value) => { ++ cleanup(); ++ resolve(value); ++ }, ++ reject: (err) => { ++ cleanup(); ++ reject(err); ++ }, ++ promise, ++ }; ++} ++ ++describe('TLS callback exception handling', () => { ++ ++ // Test 1: PSK server callback returning invalid type should emit tlsClientError ++ it('pskCallback returning invalid type emits tlsClientError', async (t) => { ++ const server = tls.createServer({ ++ ciphers: CIPHERS, ++ pskCallback: () => { ++ // Return invalid type (string instead of object/Buffer) ++ return 'invalid-should-be-object-or-buffer'; ++ }, ++ pskIdentityHint: 'test-hint', ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('tlsClientError', common.mustCall((err, socket) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE'); ++ socket.destroy(); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ ciphers: CIPHERS, ++ checkServerIdentity: () => {}, ++ pskCallback: () => ({ ++ psk: Buffer.alloc(32), ++ identity: 'test-identity', ++ }), ++ }); ++ ++ client.on('error', () => {}); ++ ++ await promise; ++ }); ++ ++ // Test 2: PSK server callback throwing should emit tlsClientError ++ it('pskCallback throwing emits tlsClientError', async (t) => { ++ const server = tls.createServer({ ++ ciphers: CIPHERS, ++ pskCallback: () => { ++ throw new Error('Intentional callback error'); ++ }, ++ pskIdentityHint: 'test-hint', ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('tlsClientError', common.mustCall((err, socket) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.message, 'Intentional callback error'); ++ socket.destroy(); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ ciphers: CIPHERS, ++ checkServerIdentity: () => {}, ++ pskCallback: () => ({ ++ psk: Buffer.alloc(32), ++ identity: 'test-identity', ++ }), ++ }); ++ ++ client.on('error', () => {}); ++ ++ await promise; ++ }); ++ ++ // Test 3: ALPN callback returning non-matching protocol should emit tlsClientError ++ it('ALPNCallback returning invalid result emits tlsClientError', async (t) => { ++ const server = tls.createServer({ ++ key: fixtures.readKey('agent2-key.pem'), ++ cert: fixtures.readKey('agent2-cert.pem'), ++ ALPNCallback: () => { ++ // Return a protocol not in the client's list ++ return 'invalid-protocol-not-in-list'; ++ }, ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('tlsClientError', common.mustCall((err, socket) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.code, 'ERR_TLS_ALPN_CALLBACK_INVALID_RESULT'); ++ socket.destroy(); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ rejectUnauthorized: false, ++ ALPNProtocols: ['http/1.1', 'h2'], ++ }); ++ ++ client.on('error', () => {}); ++ ++ await promise; ++ }); ++ ++ // Test 4: ALPN callback throwing should emit tlsClientError ++ it('ALPNCallback throwing emits tlsClientError', async (t) => { ++ const server = tls.createServer({ ++ key: fixtures.readKey('agent2-key.pem'), ++ cert: fixtures.readKey('agent2-cert.pem'), ++ ALPNCallback: () => { ++ throw new Error('Intentional ALPN callback error'); ++ }, ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('tlsClientError', common.mustCall((err, socket) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.message, 'Intentional ALPN callback error'); ++ socket.destroy(); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ rejectUnauthorized: false, ++ ALPNProtocols: ['http/1.1'], ++ }); ++ ++ client.on('error', () => {}); ++ ++ await promise; ++ }); ++ ++ // Test 5: PSK client callback returning invalid type should emit error event ++ it('client pskCallback returning invalid type emits error', async (t) => { ++ const PSK = Buffer.alloc(32); ++ ++ const server = tls.createServer({ ++ ciphers: CIPHERS, ++ pskCallback: () => PSK, ++ pskIdentityHint: 'test-hint', ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ ciphers: CIPHERS, ++ checkServerIdentity: () => {}, ++ pskCallback: () => { ++ // Return invalid type - should cause validation error ++ return 'invalid-should-be-object'; ++ }, ++ }); ++ ++ client.on('error', common.mustCall((err) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE'); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ await promise; ++ }); ++ ++ // Test 6: PSK client callback throwing should emit error event ++ it('client pskCallback throwing emits error', async (t) => { ++ const PSK = Buffer.alloc(32); ++ ++ const server = tls.createServer({ ++ ciphers: CIPHERS, ++ pskCallback: () => PSK, ++ pskIdentityHint: 'test-hint', ++ }); ++ ++ t.after(() => server.close()); ++ ++ const { promise, resolve, reject } = createTestPromise(); ++ ++ server.on('secureConnection', common.mustNotCall(() => { ++ reject(new Error('secureConnection should not fire')); ++ })); ++ ++ await new Promise((res) => server.listen(0, res)); ++ ++ const client = tls.connect({ ++ port: server.address().port, ++ host: '127.0.0.1', ++ ciphers: CIPHERS, ++ checkServerIdentity: () => {}, ++ pskCallback: () => { ++ throw new Error('Intentional client PSK callback error'); ++ }, ++ }); ++ ++ client.on('error', common.mustCall((err) => { ++ try { ++ assert.ok(err instanceof Error); ++ assert.strictEqual(err.message, 'Intentional client PSK callback error'); ++ resolve(); ++ } catch (e) { ++ reject(e); ++ } ++ })); ++ ++ await promise; ++ }); ++}); +-- +2.43.7 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb index 779c70dbd0..68eb40bc1d 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb @@ -32,6 +32,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://CVE-2025-55132.patch \ file://CVE-2025-55130.patch \ file://CVE-2025-59466.patch \ + file://CVE-2026-21637.patch \ " SRC_URI:append:class-target = " \ file://0001-Using-native-binaries.patch \ From patchwork Thu Feb 12 05:21:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 80964 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CC627EB48F7 for ; Thu, 12 Feb 2026 10:32:53 +0000 (UTC) Received: from alln-iport-5.cisco.com (alln-iport-5.cisco.com [173.37.142.92]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.38880.1770873691240509210 for ; Wed, 11 Feb 2026 21:21:31 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=RkDVz9O6; spf=pass (domain: cisco.com, ip: 173.37.142.92, mailfrom: adongare@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=3087; q=dns/txt; s=iport01; t=1770873691; x=1772083291; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=IDYQekh425bSell5LIajB6hv2kFG7PogKBL/oMoRjkc=; b=RkDVz9O6YCM+P7L6LebQssgLDgpbfs+KSNrAouYVDz1+galCzfcywIMn 4t+XJdH8lnbrKah4n6kQ3ja2MdA8mE8uHXK37ktWNzWS4Y40n/dc8AXA0 /aN1/MNWzoVUCE/e+0s0uecaDLXpiMzciEwosc3E8Na8RxlqTUBdHVNdU DGLY22DTlQUlhxdO9SMJQTQwIsAzHcYLa5XBx/qNoodlbgw6fVs005MlZ das1vxr58KvoT/Tc6AebqbLmWTme1FnZkQPacg26RsioXXmUMCxbfsW+M cPIPdKDGyunxkZ55wR+NeQ6ahY9Om+lXjDAlEwgI58jMrdXJD7b9MkObx Q==; X-CSE-ConnectionGUID: 0BTI/y92QU+T2uUB9EMK1A== X-CSE-MsgGUID: hOQY3tObTeyjt8YAgt0q7A== X-IPAS-Result: A0CrBgDuYo1p/5T/Ja1aglmCSA9xXkNJlksDgn6IZpI2gX8PAQEBDz0UBAEBhQcCjR0CJjQJDgECBAEBAQEDAgMBAQEBAQEBAQEBAQsBAQUBAQECAQcFgQ4Thk8NhloBAgEDMgEYAS0QHAMBAi8gCyMIGYMCAYI6AzYDEahpgiyBAYMoAT8CQ0/YRw2CUgELFAGBOIU7gnmFH1sYAYR4JxsbgXKEB3aBBYEaQgEBAhiBLoZcBIIigQ6BZCcPkVRIgR4DWSwBVRMNCgsHBYFmAzUSKhVuMh2BIz4XgQsbBwWIFQ+JD3hwgSByAwsYDUgRLDcUGwQ+bgeOS0GBbkV7EwEqASKBchgckwmST6AdcQoog3SMHo8+AYV7GjOFW6UQC5h7jgmECZEtgRqEaIFoPIFHCwdwFYMiCUkZD444g2mBf4MUvE4lMgI6AgcLAQEDCZNnAQE IronPort-Data: A9a23:kKGonqhVxSIIsq1W4cyMq9ySX161MBEKZh0ujC45NGQN5FlHY01je htvXz3UP62IamSnft9+aIrl8R8OvcTdndRmQQFq+C5nHytjpJueD7x1DKtf0wB+jyHnZBg6h ynLQoCYdKjYdleF+FH1dOOn9SUgvU2xbuKUIPbePSxsThNTRi4kiBZy88Y0mYcAbeKRW2thg vus5ZeGULOZ82QsaDxMsfvZ8EoHUMna4Vv0gHRvPZing3eG/5UlJMp3Db28KXL+Xr5VEoaSL 87fzKu093/u5BwkDNWoiN7TKiXmlZaLYGBiIlIPM0STqkAqSh4ai87XB9JAAatjsAhlqvgqo Dl7WTNcfi9yVkHEsLx1vxC1iEiSN4UekFPMCSDXXcB+UyQqflO0q8iCAn3aMqVA0NpWRnAfr 8UlawxXcy2p1s+T4+uSH7wEasQLdKEHPasFsX1miDWcBvE8TNWaGuPB5MRT23E7gcUm8fT2P pVCL2EwKk6dPlsWZgd/5JEWxI9EglH2fzpep1uPqII84nPYy0p6172F3N/9JILUHZwNxBjAz o7A12TeOyBBKPuV9TeU8G78t8bAgXviB6tHQdVU8dYv2jV/3Fc7DwUbU1a+q/S1hkOyHt5SN UEQ0i4vtrQpskuzQ9/wWhe1rHKJslgbQdU4LgEhwBuGxqyR50OSAXIJC2cYLtcnr8QxAzct0 zdlgu/UONCmi5XNIVr1y1tehWra1fQ9RYPaWRI5cA== IronPort-HdrOrdr: A9a23:y0esW6A902T957zlHemr55DYdb4zR+YMi2TDGXofdfUzSL3+qy nAppUmPHPP5Qr5HUtQ++xoW5PwJU80l6QU3WB5B97LN2PbUSmTXeRfBODZrQEIdReTygck79 YCT0C7Y+eAdGSTSq3BkW+FL+o= X-Talos-CUID: 9a23:4tRWC2kcdqiZJC4mp5FuDnvT8ZjXOSX38FaXMh7iMFsqdOeIdHXB1YZHyuM7zg== X-Talos-MUID: 9a23:SemCCQ8vk7hHu1gSrcXjdl2Qf8dP2pX1FGcHrZgXudXUBAV/J2+E0x3iFw== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.21,286,1763424000"; d="scan'208";a="666010260" Received: from rcdn-l-core-11.cisco.com ([173.37.255.148]) by alln-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 12 Feb 2026 05:21:30 +0000 Received: from sjc-ads-10055.cisco.com (sjc-ads-10055.cisco.com [10.30.210.59]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-11.cisco.com (Postfix) with ESMTPS id 4F5F718000145; Thu, 12 Feb 2026 05:21:30 +0000 (GMT) Received: by sjc-ads-10055.cisco.com (Postfix, from userid 1870532) id F1F55CC12A6; Wed, 11 Feb 2026 21:21:29 -0800 (PST) From: "Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: xe-linux-external@cisco.com, to@cisco.com, Anil Dongare Subject: [meta-OE] [scarthgap] [PATCH 5/5] Nodejs 20.18.2: Fix CVE-2025-59465 Date: Wed, 11 Feb 2026 21:21:10 -0800 Message-ID: <20260212052114.3215220-5-adongare@cisco.com> X-Mailer: git-send-email 2.44.1 In-Reply-To: <20260212052114.3215220-1-adongare@cisco.com> References: <20260212052114.3215220-1-adongare@cisco.com> MIME-Version: 1.0 X-Outbound-SMTP-Client: 10.30.210.59, sjc-ads-10055.cisco.com X-Outbound-Node: rcdn-l-core-11.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Thu, 12 Feb 2026 10:32:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/124352 From: Anil Dongare Upstream Repository: https://github.com/nodejs/node.git Bug Details: https://nvd.nist.gov/vuln/detail/CVE-2025-59465 Type: Security Fix CVE: CVE-2025-59465 Score: 7.5 Patch: https://github.com/nodejs/node/commit/eb8e41f8dbe6 Signed-off-by: Anil Dongare --- .../nodejs/nodejs/CVE-2025-59465.patch | 49 +++++++++++++++++++ .../recipes-devtools/nodejs/nodejs_20.18.2.bb | 1 + 2 files changed, 50 insertions(+) create mode 100644 meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59465.patch diff --git a/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59465.patch b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59465.patch new file mode 100644 index 0000000000..81d64609c3 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs/CVE-2025-59465.patch @@ -0,0 +1,49 @@ +From 27ddb91569c1bd9c72dbc557e53458b9ebfbc573 Mon Sep 17 00:00:00 2001 +From: RafaelGSS +Date: Fri, 31 Oct 2025 16:27:48 -0300 +Subject: [PATCH 5/6] lib: add TLSSocket default error handler + +This prevents the server from crashing due to an unhandled rejection +when a TLSSocket connection is abruptly destroyed during initialization +and the user has not attached an error handler to the socket. +e.g: + +```js +const server = http2.createSecureServer({ ... }) +server.on('secureConnection', socket => { + socket.on('error', err => { + console.log(err) + }) +}) +``` + +PR-URL: https://github.com/nodejs-private/node-private/pull/797 +Fixes: https://github.com/nodejs/node/issues/44751 +Refs: https://hackerone.com/bugs?subject=nodejs&report_id=3262404 +Reviewed-By: Matteo Collina +Reviewed-By: Anna Henningsen +CVE-ID: CVE-2025-59465 + +CVE: CVE-2025-59465 +Upstream-Status: Backport [https://github.com/nodejs/node/commit/eb8e41f8dbe6] + +(cherry picked from commit eb8e41f8dbe6de127fb11baca725e1b469612434) +Signed-off-by: Anil Dongare +--- + lib/_tls_wrap.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js +index c3e48a6cbc8..d9c7e32174d 100644 +--- a/lib/_tls_wrap.js ++++ b/lib/_tls_wrap.js +@@ -1268,6 +1268,7 @@ function tlsConnectionListener(rawSocket) { + socket[kErrorEmitted] = false; + socket.on('close', onSocketClose); + socket.on('_tlsError', onSocketTLSError); ++ socket.on('error', onSocketTLSError); + } + + // AUTHENTICATION MODES +-- +2.43.7 diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb index 68eb40bc1d..b1c9057557 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_20.18.2.bb @@ -33,6 +33,7 @@ SRC_URI = "http://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://CVE-2025-55130.patch \ file://CVE-2025-59466.patch \ file://CVE-2026-21637.patch \ + file://CVE-2025-59465.patch \ " SRC_URI:append:class-target = " \ file://0001-Using-native-binaries.patch \