Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b4b887c

Browse filesBrowse 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 <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> CVE-ID: CVE-2025-55132
1 parent fc996fd commit b4b887c
Copy full SHA for b4b887c

File tree

Expand file treeCollapse file tree

3 files changed

+85
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+85
-1
lines changed
Open diff view settings
Collapse file

‎lib/fs.js‎

Copy file name to clipboardExpand all lines: lib/fs.js
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,6 +1223,11 @@ function rmSync(path, options) {
12231223
function fdatasync(fd, callback) {
12241224
const req = new FSReqCallback();
12251225
req.oncomplete = makeCallback(callback);
1226+
1227+
if (permission.isEnabled()) {
1228+
callback(new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.'));
1229+
return;
1230+
}
12261231
binding.fdatasync(fd, req);
12271232
}
12281233

@@ -1234,6 +1239,9 @@ function fdatasync(fd, callback) {
12341239
* @returns {void}
12351240
*/
12361241
function fdatasyncSync(fd) {
1242+
if (permission.isEnabled()) {
1243+
throw new ERR_ACCESS_DENIED('fdatasync API is disabled when Permission Model is enabled.');
1244+
}
12371245
binding.fdatasync(fd);
12381246
}
12391247

@@ -1247,6 +1255,10 @@ function fdatasyncSync(fd) {
12471255
function fsync(fd, callback) {
12481256
const req = new FSReqCallback();
12491257
req.oncomplete = makeCallback(callback);
1258+
if (permission.isEnabled()) {
1259+
callback(new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.'));
1260+
return;
1261+
}
12501262
binding.fsync(fd, req);
12511263
}
12521264

@@ -1257,6 +1269,9 @@ function fsync(fd, callback) {
12571269
* @returns {void}
12581270
*/
12591271
function fsyncSync(fd) {
1272+
if (permission.isEnabled()) {
1273+
throw new ERR_ACCESS_DENIED('fsync API is disabled when Permission Model is enabled.');
1274+
}
12601275
binding.fsync(fd);
12611276
}
12621277

@@ -2187,6 +2202,11 @@ function futimes(fd, atime, mtime, callback) {
21872202
mtime = toUnixTimestamp(mtime, 'mtime');
21882203
callback = makeCallback(callback);
21892204

2205+
if (permission.isEnabled()) {
2206+
callback(new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.'));
2207+
return;
2208+
}
2209+
21902210
const req = new FSReqCallback();
21912211
req.oncomplete = callback;
21922212
binding.futimes(fd, atime, mtime, req);
@@ -2202,6 +2222,10 @@ function futimes(fd, atime, mtime, callback) {
22022222
* @returns {void}
22032223
*/
22042224
function futimesSync(fd, atime, mtime) {
2225+
if (permission.isEnabled()) {
2226+
throw new ERR_ACCESS_DENIED('futimes API is disabled when Permission Model is enabled.');
2227+
}
2228+
22052229
binding.futimes(
22062230
fd,
22072231
toUnixTimestamp(atime, 'atime'),
Collapse file

‎test/fixtures/permission/fs-write.js‎

Copy file name to clipboardExpand all lines: test/fixtures/permission/fs-write.js
+45Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,3 +584,48 @@ const relativeProtectedFolder = process.env.RELATIVEBLOCKEDFOLDER;
584584
code: 'ERR_ACCESS_DENIED',
585585
});
586586
}
587+
588+
// fs.utimes with read-only fd
589+
{
590+
assert.throws(() => {
591+
// blocked file is allowed to read
592+
const fd = fs.openSync(blockedFile, 'r');
593+
const date = new Date();
594+
date.setFullYear(2100,0,1);
595+
596+
fs.futimes(fd, date, date, common.expectsError({
597+
code: 'ERR_ACCESS_DENIED',
598+
}));
599+
fs.futimesSync(fd, date, date);
600+
}, {
601+
code: 'ERR_ACCESS_DENIED',
602+
});
603+
}
604+
605+
// fs.fdatasync with read-only fd
606+
{
607+
assert.throws(() => {
608+
// blocked file is allowed to read
609+
const fd = fs.openSync(blockedFile, 'r');
610+
fs.fdatasync(fd, common.expectsError({
611+
code: 'ERR_ACCESS_DENIED',
612+
}));
613+
fs.fdatasyncSync(fd);
614+
}, {
615+
code: 'ERR_ACCESS_DENIED',
616+
});
617+
}
618+
619+
// fs.fsync with read-only fd
620+
{
621+
assert.throws(() => {
622+
// blocked file is allowed to read
623+
const fd = fs.openSync(blockedFile, 'r');
624+
fs.fsync(fd, common.expectsError({
625+
code: 'ERR_ACCESS_DENIED',
626+
}));
627+
fs.fsyncSync(fd);
628+
}, {
629+
code: 'ERR_ACCESS_DENIED',
630+
});
631+
}
Collapse file

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

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.