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
14 changes: 14 additions & 0 deletions src/udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ void UDPWrap::DoBind(const FunctionCallbackInfo<Value>& args, int family) {
err = uv_udp_bind(&wrap->handle_,
reinterpret_cast<const sockaddr*>(&addr_storage),
flags);
if (err == 0) wrap->net_permission_granted_ = true;
}

if (err == 0)
Expand Down Expand Up @@ -358,6 +359,7 @@ void UDPWrap::DoConnect(const FunctionCallbackInfo<Value>& args, int family) {
if (err == 0) {
err = uv_udp_connect(&wrap->handle_,
reinterpret_cast<const sockaddr*>(&addr_storage));
if (err == 0) wrap->net_permission_granted_ = true;
}

args.GetReturnValue().Set(err);
Expand All @@ -368,9 +370,14 @@ void UDPWrap::Open(const FunctionCallbackInfo<Value>& args) {
UDPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
Environment* env = wrap->env();

THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");

CHECK(args[0]->IsNumber());
int fd = FromV8Value<int>(args[0]);
int err = uv_udp_open(&wrap->handle_, fd);
if (err == 0) wrap->net_permission_granted_ = true;

args.GetReturnValue().Set(err);
}
Expand Down Expand Up @@ -697,6 +704,13 @@ void UDPWrapBase::RecvStart(const FunctionCallbackInfo<Value>& args) {

int UDPWrap::RecvStart() {
if (IsHandleClosing()) return UV_EBADF;

if (!net_permission_granted_) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
env(), permission::PermissionScope::kNet, "", UV_EACCES);
net_permission_granted_ = true;
}

int err = uv_udp_recv_start(&handle_, OnAlloc, OnRecv);
// UV_EALREADY means that the socket is already bound but that's okay
if (err == UV_EALREADY)
Expand Down
1 change: 1 addition & 0 deletions src/udp_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class UDPWrap final : public HandleWrap,

uv_udp_t handle_;

bool net_permission_granted_ = false;
bool current_send_has_callback_;
v8::Local<v8::Object> current_send_req_wrap_;
};
Expand Down
135 changes: 135 additions & 0 deletions test/parallel/test-permission-net-udp-handle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Flags: --expose-internals
'use strict';

const common = require('../common');
if (common.isWindows) {
common.skip('Sending dgram sockets to child processes is not supported');
}

const assert = require('assert');
const dgram = require('dgram');
const { fork, spawn } = require('child_process');

const mode = process.argv[2];
const kStateSymbol = mode === undefined ?
require('internal/dgram').kStateSymbol : undefined;

if (mode === 'ipc-denied') {
process.on('message', common.mustNotCall());
process.send('ready');
} else if (mode === 'ipc-drop') {
process.once('message', common.mustCall((message, handle) => {
assert.strictEqual(message, 'socket');
assert(process.permission.has('net'));

process.permission.drop('net');
assert(!process.permission.has('net'));

handle.once('message', common.mustCall((data) => {
assert.strictEqual(data.toString(), 'after-drop');
handle.close();
process.send('received');
}));
process.send('receiving');
}));
process.send('ready');
} else {
const deniedSocket = dgram.createSocket('udp4');
deniedSocket.bind(0, '127.0.0.1', common.mustCall(() => {
const child = fork(__filename, ['ipc-denied'], {
execArgv: ['--permission', '--allow-fs-read=*'],
silent: true,
});
let stderr = '';

child.stderr.setEncoding('utf8');
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.once('message', common.mustCall((message) => {
assert.strictEqual(message, 'ready');
child.send('socket', deniedSocket);
}));
child.once('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.match(stderr, /ERR_ACCESS_DENIED/);
assert.match(stderr, /permission: 'Net'/);
deniedSocket.close();
}));
}));

const droppedSocket = dgram.createSocket('udp4');
const sender = dgram.createSocket('udp4');
droppedSocket.bind(0, '127.0.0.1', common.mustCall(() => {
const child = fork(__filename, ['ipc-drop'], {
execArgv: [
'--permission',
'--allow-net',
'--allow-fs-read=*',
],
});
let timer;

child.on('message', common.mustCall((message) => {
if (message === 'ready') {
child.send('socket', droppedSocket);
} else if (message === 'receiving') {
const { port } = droppedSocket.address();
timer = setInterval(() => {
sender.send('after-drop', port, '127.0.0.1');
}, 10);
} else {
assert.strictEqual(message, 'received');
clearInterval(timer);
sender.close();
droppedSocket.close();
child.disconnect();
}
}, 3));
child.once('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}));
}));

const fdSocket = dgram.createSocket('udp4');
fdSocket.bind(0, '127.0.0.1', common.mustCall(() => {
const source = `
const assert = require('node:assert');
const dgram = require('node:dgram');
const socket = dgram.createSocket('udp4');
let error;
try {
socket.bind({ fd: 3 });
} catch (err) {
error = err;
}
if (error) {
assert.strictEqual(error.code, 'ERR_ACCESS_DENIED');
assert.strictEqual(error.permission, 'Net');
process.exit(0);
}
process.exit(1);
`;
const child = spawn(
process.execPath,
['--permission', '--eval', source],
{
stdio: [
'ignore',
'ignore',
'pipe',
fdSocket[kStateSymbol].handle.fd,
],
},
);
let stderr = '';

child.stderr.setEncoding('utf8');
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.once('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, 0, stderr);
assert.strictEqual(signal, null);
fdSocket.close();
}));
}));
}
Loading