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 <matteo.collina@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
CVE-ID: CVE-2025-55132
This commit is contained in:
RafaelGSS authored and Marco Ippolito committed 2026-01-13 17:01:27 +01:00
1 parent 6ea77ad552
commit fb4389332d
3 files changed
+85 -1

No files matched your search

+24
View File
@@ -1224,6 +1224,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);
}
@@ -1235,6 +1240,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);
}
@@ -1248,6 +1256,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);
}
@@ -1258,6 +1270,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);
}
@@ -2188,6 +2203,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);
@@ -2203,6 +2223,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'),
+45
View File
@@ -584,3 +584,48 @@ const relativeProtectedFolder = process.env.RELATIVEBLOCKEDFOLDER;
code: 'ERR_ACCESS_DENIED',
});
}
// 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',
});
}
+16 -1
View File
@@ -82,7 +82,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'),