From: Anil Dongare <[email protected]>

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 <[email protected]>
---
 .../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 <[email protected]>
+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 <[email protected]>
+PR-URL: https://github.com/nodejs-private/node-private/pull/760
+Reviewed-By: Matteo Collina <[email protected]>
+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 <[email protected]>
+---
+ 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 \
-- 
2.44.1

-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#124349): 
https://lists.openembedded.org/g/openembedded-devel/message/124349
Mute This Topic: https://lists.openembedded.org/mt/117772146/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

  • ... Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
    • ... Anil Dongare -X (adongare - E INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org

Reply via email to