Skip to content

Commit bd094d6

Browse files
committed
permission: handle fstatfs and add pm supported list
PR-URL: nodejs-private/node-private#441 CVE-ID: CVE-2023-32005
1 parent 1bf3429 commit bd094d6

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

src/node_file.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,8 @@ static void StatFs(const FunctionCallbackInfo<Value>& args) {
12521252

12531253
BufferValue path(env->isolate(), args[0]);
12541254
CHECK_NOT_NULL(*path);
1255+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1256+
env, permission::PermissionScope::kFileSystemRead, path.ToStringView());
12551257

12561258
bool use_bigint = args[1]->IsTrue();
12571259
FSReqBase* req_wrap_async = GetReqWrap(args, 2, use_bigint);

test/fixtures/permission/fs-read.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,22 @@ const regularFile = __filename;
260260
resource: path.toNamespacedPath(blockedFile),
261261
}));
262262
}
263+
264+
// fs.exists
265+
{
266+
// It will return false (without performing IO) when permissions is not met
267+
fs.exists(blockedFile, (exists) => {
268+
assert.equal(exists, false);
269+
});
270+
}
271+
272+
// fs.statfs
273+
{
274+
assert.throws(() => {
275+
fs.statfs(blockedFile, () => {});
276+
}, common.expectsError({
277+
code: 'ERR_ACCESS_DENIED',
278+
permission: 'FileSystemRead',
279+
resource: path.toNamespacedPath(blockedFile),
280+
}));
281+
}

test/fixtures/permission/fs-write.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,16 @@ const absoluteProtectedFolder = path.resolve(relativeProtectedFolder);
379379
permission: 'FileSystemWrite',
380380
});
381381
}
382+
383+
// fs.unlink
384+
{
385+
assert.throws(() => {
386+
fs.unlink(blockedFile, (err) => {
387+
assert.ifError(err);
388+
});
389+
}, {
390+
code: 'ERR_ACCESS_DENIED',
391+
permission: 'FileSystemWrite',
392+
resource: path.toNamespacedPath(blockedFile),
393+
});
394+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('assert');
5+
6+
// Most of the times, the function called for async and Sync
7+
// methods are the same on node_file.cc
8+
function syncAndAsyncAPI(funcName) {
9+
return [funcName, funcName + 'Sync'];
10+
}
11+
12+
// This tests guarantee whenever a new API under fs module is exposed
13+
// it must contain a test to the permission model.
14+
// Otherwise, a vulnerability might be exposed. If you are adding a new
15+
// fs method, please, make sure to include a test for it on test-permission-fs-*
16+
// and include to the supportedApis list.
17+
//
18+
//
19+
// This list is synced with
20+
// fixtures/permission/fs-read and
21+
// fixtures/permission/fs-write
22+
const supportedApis = [
23+
...syncAndAsyncAPI('appendFile'),
24+
...syncAndAsyncAPI('access'),
25+
...syncAndAsyncAPI('chown'),
26+
...syncAndAsyncAPI('chmod'),
27+
...syncAndAsyncAPI('copyFile'),
28+
...syncAndAsyncAPI('cp'),
29+
'createReadStream',
30+
'createWriteStream',
31+
...syncAndAsyncAPI('exists'),
32+
...syncAndAsyncAPI('lchown'),
33+
...syncAndAsyncAPI('lchmod'),
34+
...syncAndAsyncAPI('link'),
35+
...syncAndAsyncAPI('lutimes'),
36+
...syncAndAsyncAPI('mkdir'),
37+
...syncAndAsyncAPI('mkdtemp'),
38+
...syncAndAsyncAPI('open'),
39+
'openAsBlob',
40+
...syncAndAsyncAPI('mkdtemp'),
41+
...syncAndAsyncAPI('readdir'),
42+
...syncAndAsyncAPI('readFile'),
43+
...syncAndAsyncAPI('readlink'),
44+
...syncAndAsyncAPI('rename'),
45+
...syncAndAsyncAPI('rm'),
46+
...syncAndAsyncAPI('rmdir'),
47+
...syncAndAsyncAPI('stat'),
48+
...syncAndAsyncAPI('statfs'),
49+
...syncAndAsyncAPI('statfs'),
50+
...syncAndAsyncAPI('symlink'),
51+
...syncAndAsyncAPI('truncate'),
52+
...syncAndAsyncAPI('unlink'),
53+
...syncAndAsyncAPI('utimes'),
54+
'watch',
55+
'watchFile',
56+
...syncAndAsyncAPI('writeFile'),
57+
...syncAndAsyncAPI('opendir'),
58+
];
59+
60+
// Non functions
61+
const ignoreList = [
62+
'constants',
63+
'promises',
64+
'X_OK',
65+
'W_OK',
66+
'R_OK',
67+
'F_OK',
68+
'Dir',
69+
'FileReadStream',
70+
'FileWriteStream',
71+
'_toUnixTimestamp',
72+
'Stats',
73+
'ReadStream',
74+
'WriteStream',
75+
'Dirent',
76+
// fs.watch is already blocked
77+
'unwatchFile',
78+
...syncAndAsyncAPI('lstat'),
79+
...syncAndAsyncAPI('realpath'),
80+
// fd required methods
81+
...syncAndAsyncAPI('close'),
82+
...syncAndAsyncAPI('fchown'),
83+
...syncAndAsyncAPI('fchmod'),
84+
...syncAndAsyncAPI('fdatasync'),
85+
...syncAndAsyncAPI('fstat'),
86+
...syncAndAsyncAPI('fsync'),
87+
...syncAndAsyncAPI('ftruncate'),
88+
...syncAndAsyncAPI('futimes'),
89+
...syncAndAsyncAPI('read'),
90+
...syncAndAsyncAPI('readv'),
91+
...syncAndAsyncAPI('write'),
92+
...syncAndAsyncAPI('writev'),
93+
];
94+
95+
{
96+
const fsList = Object.keys(require('fs'));
97+
for (const k of fsList) {
98+
if (!supportedApis.includes(k) && !ignoreList.includes(k)) {
99+
assert.fail(`fs.${k} was exposed but is neither on the supported list ` +
100+
'of the permission model nor on the ignore list.');
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)