Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions src/permission/permission.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ namespace permission {
const auto resource__ = (resource); \
if (!env__->permission()->is_granted(env__, perm__, resource__)) \
[[unlikely]] { \
node::permission::Permission::ThrowAccessDenied( \
env__, perm__, resource__); \
if (!env__->permission()->warning_only()) return __VA_ARGS__; \
if (!env__->permission()->warning_only()) { \
node::permission::Permission::ThrowAccessDenied( \
env__, perm__, resource__); \
return __VA_ARGS__; \
} \
} \
} while (0)

Expand All @@ -51,9 +53,11 @@ namespace permission {
const auto resource__ = (resource); \
if (!env__->permission()->is_granted(env__, perm__, resource__)) \
[[unlikely]] { \
node::permission::Permission::AsyncThrowAccessDenied( \
env__, (wrap), perm__, resource__); \
if (!env__->permission()->warning_only()) return __VA_ARGS__; \
if (!env__->permission()->warning_only()) { \
node::permission::Permission::AsyncThrowAccessDenied( \
env__, (wrap), perm__, resource__); \
return __VA_ARGS__; \
} \
} \
} while (0)

Expand All @@ -65,14 +69,17 @@ namespace permission {
const auto resource__ = (resource); \
if (!env__->permission()->is_granted(env__, perm__, resource__)) \
[[unlikely]] { \
Local<Value> err_access; \
if (node::permission::CreateAccessDeniedError(env__, perm__, resource__) \
.ToLocal(&err_access)) { \
args.GetReturnValue().Set(err_access); \
} else { \
args.GetReturnValue().Set(UV_EACCES); \
if (!env__->permission()->warning_only()) { \
Local<Value> err_access; \
if (node::permission::CreateAccessDeniedError(env__, perm__, \
resource__) \
.ToLocal(&err_access)) { \
args.GetReturnValue().Set(err_access); \
} else { \
args.GetReturnValue().Set(UV_EACCES); \
} \
return __VA_ARGS__; \
} \
return __VA_ARGS__; \
} \
} while (0)

Expand Down
58 changes: 58 additions & 0 deletions test/parallel/test-permission-audit-fs-does-not-deny.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const common = require('../common');
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
common.skip('This test only works on a main thread');
}

const assert = require('assert');
const { spawnSync } = require('child_process');
const { test } = require('node:test');
const fixtures = require('../common/fixtures');

const blockedFile = fixtures.path('permission', 'deny', 'protected-file.md');

function runAudit(mode) {
const childScript = `
const dc = require('node:diagnostics_channel');
const msgs = [];
dc.subscribe('node:permission-model:fs', (m) => msgs.push({
permission: m.permission,
resource: m.resource,
}));
try {
${mode === 'eval' ?
`eval('require("node:fs").readFileSync(${JSON.stringify(blockedFile)})');` :
`require('node:fs').readFileSync(${JSON.stringify(blockedFile)});`}
console.log('RESULT NO_THROW');
} catch (e) {
console.log('RESULT THREW ' + e.code);
}
console.log('AUDIT ' + JSON.stringify(msgs));
`;

const { status, stdout, stderr } = spawnSync(
process.execPath,
['--permission-audit', '-e', childScript],
{ encoding: 'utf8' },
);
assert.strictEqual(status, 0, stderr);
const lines = stdout.split('\n');
assert.ok(lines.includes('RESULT NO_THROW'), stdout);
const auditLine = lines.find((l) => l.startsWith('AUDIT '));
assert.ok(auditLine, stdout);
const msgs = JSON.parse(auditLine.replace('AUDIT ', ''));
assert.strictEqual(msgs.length, 1);
assert.strictEqual(msgs[0].permission, 'FileSystemRead');
assert.ok(msgs[0].resource.endsWith('protected-file.md'));
}

test('permission-audit logs fs denial without throwing', () => {
runAudit('direct');
});

test('permission-audit logs fs denial without throwing (eval)', () => {
runAudit('eval');
});
69 changes: 69 additions & 0 deletions test/parallel/test-permission-audit-net-does-not-deny.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict';

const common = require('../common');
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
common.skip('This test only works on a main thread');
}

const assert = require('assert');
const { spawnSync } = require('child_process');
const { test } = require('node:test');
const net = require('net');

async function runAudit(mode) {
const server = net.createServer();

await new Promise((resolve, reject) => {
server.on('error', reject);
server.listen(0, '127.0.0.1', resolve);
});
const { port } = server.address();
const env = { ...process.env, PORT: String(port), HOST: '127.0.0.1' };

const childScript = `
const dc = require('node:diagnostics_channel');
const net = require('node:net');
const msgs = [];
dc.subscribe('node:permission-model:net', (m) => msgs.push({
permission: m.permission,
resource: m.resource,
}));
const s = ${mode === 'eval' ?
`eval('net.connect(Number(process.env.PORT), process.env.HOST)')` :
`net.connect(Number(process.env.PORT), process.env.HOST)`};
s.on('connect', () => { s.destroy(); console.log('RESULT CONNECTED'); });
s.on('error', (e) => { console.log('RESULT ERROR ' + e.code); });
s.on('close', () => {
console.log('AUDIT ' + JSON.stringify(msgs));
});
`;

try {
const { status, stdout, stderr } = spawnSync(
process.execPath,
['--permission-audit', '-e', childScript],
{ encoding: 'utf8', env },
);
assert.strictEqual(status, 0, stderr);
const lines = stdout.split('\n');
assert.strictEqual(lines[0], 'RESULT CONNECTED', stdout);
const auditLine = lines.find((l) => l.startsWith('AUDIT '));
assert.ok(auditLine, stdout);
const msgs = JSON.parse(auditLine.replace('AUDIT ', ''));
assert.strictEqual(msgs.length, 1);
assert.strictEqual(msgs[0].permission, 'Net');
assert.ok(msgs[0].resource.includes('127.0.0.1'));
} finally {
server.close();
}
}

test('permission-audit logs net denial without blocking connect', async () => {
await runAudit('direct');
});

test('permission-audit logs net denial without blocking connect (eval)', async () => {
await runAudit('eval');
});
Loading