Skip to content

Commit 14fbbb5

Browse files
committed
lib: disable futimes when permission model is enabled
Refs: https://hackerone.com/reports/3390084 PR-URL: nodejs-private/node-private#748 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> CVE-ID: CVE-2025-55132 PR-URL: nodejs-private/node-private#802 Reviewed-By: Rafael Gonzaga <[email protected]> CVE-ID: CVE-2025-55132
1 parent 1febc48 commit 14fbbb5

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

lib/fs.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,11 @@ function rmSync(path, options) {
12751275
function fdatasync(fd, callback) {
12761276
const req = new FSReqCallback();
12771277
req.oncomplete = makeCallback(callback);
1278+
1279+
if (permission.isEnabled()) {
1280+
callback(new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'));
1281+
return;
1282+
}
12781283
binding.fdatasync(fd, req);
12791284
}
12801285

@@ -1286,6 +1291,9 @@ function fdatasync(fd, callback) {
12861291
* @returns {void}
12871292
*/
12881293
function fdatasyncSync(fd) {
1294+
if (permission.isEnabled()) {
1295+
throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.');
1296+
}
12891297
binding.fdatasync(fd);
12901298
}
12911299

@@ -1299,6 +1307,10 @@ function fdatasyncSync(fd) {
12991307
function fsync(fd, callback) {
13001308
const req = new FSReqCallback();
13011309
req.oncomplete = makeCallback(callback);
1310+
if (permission.isEnabled()) {
1311+
callback(new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'));
1312+
return;
1313+
}
13021314
binding.fsync(fd, req);
13031315
}
13041316

@@ -1309,6 +1321,9 @@ function fsync(fd, callback) {
13091321
* @returns {void}
13101322
*/
13111323
function fsyncSync(fd) {
1324+
if (permission.isEnabled()) {
1325+
throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.');
1326+
}
13121327
binding.fsync(fd);
13131328
}
13141329

@@ -2165,6 +2180,11 @@ function futimes(fd, atime, mtime, callback) {
21652180
mtime = toUnixTimestamp(mtime, 'mtime');
21662181
callback = makeCallback(callback);
21672182

2183+
if (permission.isEnabled()) {
2184+
callback(new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'));
2185+
return;
2186+
}
2187+
21682188
const req = new FSReqCallback();
21692189
req.oncomplete = callback;
21702190
binding.futimes(fd, atime, mtime, req);
@@ -2180,6 +2200,10 @@ function futimes(fd, atime, mtime, callback) {
21802200
* @returns {void}
21812201
*/
21822202
function futimesSync(fd, atime, mtime) {
2203+
if (permission.isEnabled()) {
2204+
throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.');
2205+
}
2206+
21832207
binding.futimes(
21842208
fd,
21852209
toUnixTimestamp(atime, 'atime'),

test/fixtures/permission/fs-write.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,4 +553,49 @@ const relativeProtectedFolder = process.env.RELATIVEBLOCKEDFOLDER;
553553
}, {
554554
code: 'ERR_ACCESS_DENIED',
555555
});
556+
}
557+
558+
// fs.utimes with read-only fd
559+
{
560+
assert.throws(() => {
561+
// blocked file is allowed to read
562+
const fd = fs.openSync(blockedFile, 'r');
563+
const date = new Date();
564+
date.setFullYear(2100,0,1);
565+
566+
fs.futimes(fd, date, date, common.expectsError({
567+
code: 'ERR_ACCESS_DENIED',
568+
}));
569+
fs.futimesSync(fd, date, date);
570+
}, {
571+
code: 'ERR_ACCESS_DENIED',
572+
});
573+
}
574+
575+
// fs.fdatasync with read-only fd
576+
{
577+
assert.throws(() => {
578+
// blocked file is allowed to read
579+
const fd = fs.openSync(blockedFile, 'r');
580+
fs.fdatasync(fd, common.expectsError({
581+
code: 'ERR_ACCESS_DENIED',
582+
}));
583+
fs.fdatasyncSync(fd);
584+
}, {
585+
code: 'ERR_ACCESS_DENIED',
586+
});
587+
}
588+
589+
// fs.fsync with read-only fd
590+
{
591+
assert.throws(() => {
592+
// blocked file is allowed to read
593+
const fd = fs.openSync(blockedFile, 'r');
594+
fs.fsync(fd, common.expectsError({
595+
code: 'ERR_ACCESS_DENIED',
596+
}));
597+
fs.fsyncSync(fd);
598+
}, {
599+
code: 'ERR_ACCESS_DENIED',
600+
});
556601
}

test/parallel/test-permission-fs-supported.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,22 @@ const ignoreList = [
7777
'unwatchFile',
7878
...syncAndAsyncAPI('lstat'),
7979
...syncAndAsyncAPI('realpath'),
80-
// fd required methods
80+
// File descriptor–based metadata operations
81+
//
82+
// The kernel does not allow opening a file descriptor for an inode
83+
// with write access if the inode itself is read-only. However, it still
84+
// permits modifying the inode’s metadata (e.g., permission bits, ownership,
85+
// timestamps) because you own the file. These changes can be made either
86+
// by referring to the file by name (e.g., chmod) or through any existing
87+
// file descriptor that identifies the same inode (e.g., fchmod).
88+
//
89+
// If the kernel required write access to change metadata, it would be
90+
// impossible to modify the permissions of a file once it was made read-only.
91+
// For that reason, syscalls such as fchmod, fchown, and futimes bypass
92+
// the file descriptor’s access mode. Even a read-only ('r') descriptor
93+
// can still update metadata. To prevent unintended modifications,
94+
// these APIs are therefore blocked by default when permission model is
95+
// enabled.
8196
...syncAndAsyncAPI('close'),
8297
...syncAndAsyncAPI('fchown'),
8398
...syncAndAsyncAPI('fchmod'),

0 commit comments

Comments
 (0)