Skip to content

Commit 49658da

Browse files
committed
permission: guard UDP handle adoption
1 parent 45d6d31 commit 49658da

3 files changed

Lines changed: 150 additions & 0 deletions

File tree

src/udp_wrap.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ void UDPWrap::DoBind(const FunctionCallbackInfo<Value>& args, int family) {
325325
err = uv_udp_bind(&wrap->handle_,
326326
reinterpret_cast<const sockaddr*>(&addr_storage),
327327
flags);
328+
if (err == 0) wrap->net_permission_granted_ = true;
328329
}
329330

330331
if (err == 0)
@@ -358,6 +359,7 @@ void UDPWrap::DoConnect(const FunctionCallbackInfo<Value>& args, int family) {
358359
if (err == 0) {
359360
err = uv_udp_connect(&wrap->handle_,
360361
reinterpret_cast<const sockaddr*>(&addr_storage));
362+
if (err == 0) wrap->net_permission_granted_ = true;
361363
}
362364

363365
args.GetReturnValue().Set(err);
@@ -368,9 +370,14 @@ void UDPWrap::Open(const FunctionCallbackInfo<Value>& args) {
368370
UDPWrap* wrap;
369371
ASSIGN_OR_RETURN_UNWRAP(
370372
&wrap, args.This(), args.GetReturnValue().Set(UV_EBADF));
373+
Environment* env = wrap->env();
374+
375+
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kNet, "");
376+
371377
CHECK(args[0]->IsNumber());
372378
int fd = FromV8Value<int>(args[0]);
373379
int err = uv_udp_open(&wrap->handle_, fd);
380+
if (err == 0) wrap->net_permission_granted_ = true;
374381

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

698705
int UDPWrap::RecvStart() {
699706
if (IsHandleClosing()) return UV_EBADF;
707+
708+
if (!net_permission_granted_) {
709+
THROW_IF_INSUFFICIENT_PERMISSIONS(
710+
env(), permission::PermissionScope::kNet, "", UV_EACCES);
711+
net_permission_granted_ = true;
712+
}
713+
700714
int err = uv_udp_recv_start(&handle_, OnAlloc, OnRecv);
701715
// UV_EALREADY means that the socket is already bound but that's okay
702716
if (err == UV_EALREADY)

src/udp_wrap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ class UDPWrap final : public HandleWrap,
214214

215215
uv_udp_t handle_;
216216

217+
bool net_permission_granted_ = false;
217218
bool current_send_has_callback_;
218219
v8::Local<v8::Object> current_send_req_wrap_;
219220
};
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
const common = require('../common');
5+
if (common.isWindows) {
6+
common.skip('Sending dgram sockets to child processes is not supported');
7+
}
8+
9+
const assert = require('assert');
10+
const dgram = require('dgram');
11+
const { fork, spawn } = require('child_process');
12+
13+
const mode = process.argv[2];
14+
const kStateSymbol = mode === undefined ?
15+
require('internal/dgram').kStateSymbol : undefined;
16+
17+
if (mode === 'ipc-denied') {
18+
process.on('message', common.mustNotCall());
19+
process.send('ready');
20+
} else if (mode === 'ipc-drop') {
21+
process.once('message', common.mustCall((message, handle) => {
22+
assert.strictEqual(message, 'socket');
23+
assert(process.permission.has('net'));
24+
25+
process.permission.drop('net');
26+
assert(!process.permission.has('net'));
27+
28+
handle.once('message', common.mustCall((data) => {
29+
assert.strictEqual(data.toString(), 'after-drop');
30+
handle.close();
31+
process.send('received');
32+
}));
33+
process.send('receiving');
34+
}));
35+
process.send('ready');
36+
} else {
37+
const deniedSocket = dgram.createSocket('udp4');
38+
deniedSocket.bind(0, '127.0.0.1', common.mustCall(() => {
39+
const child = fork(__filename, ['ipc-denied'], {
40+
execArgv: ['--permission', '--allow-fs-read=*'],
41+
silent: true,
42+
});
43+
let stderr = '';
44+
45+
child.stderr.setEncoding('utf8');
46+
child.stderr.on('data', (chunk) => { stderr += chunk; });
47+
child.once('message', common.mustCall((message) => {
48+
assert.strictEqual(message, 'ready');
49+
child.send('socket', deniedSocket);
50+
}));
51+
child.once('exit', common.mustCall((code, signal) => {
52+
assert.strictEqual(code, 1);
53+
assert.strictEqual(signal, null);
54+
assert.match(stderr, /ERR_ACCESS_DENIED/);
55+
assert.match(stderr, /permission: 'Net'/);
56+
deniedSocket.close();
57+
}));
58+
}));
59+
60+
const droppedSocket = dgram.createSocket('udp4');
61+
const sender = dgram.createSocket('udp4');
62+
droppedSocket.bind(0, '127.0.0.1', common.mustCall(() => {
63+
const child = fork(__filename, ['ipc-drop'], {
64+
execArgv: [
65+
'--permission',
66+
'--allow-net',
67+
'--allow-fs-read=*',
68+
],
69+
});
70+
let timer;
71+
72+
child.on('message', common.mustCall((message) => {
73+
if (message === 'ready') {
74+
child.send('socket', droppedSocket);
75+
} else if (message === 'receiving') {
76+
const { port } = droppedSocket.address();
77+
timer = setInterval(() => {
78+
sender.send('after-drop', port, '127.0.0.1');
79+
}, 10);
80+
} else {
81+
assert.strictEqual(message, 'received');
82+
clearInterval(timer);
83+
sender.close();
84+
droppedSocket.close();
85+
child.disconnect();
86+
}
87+
}, 3));
88+
child.once('exit', common.mustCall((code, signal) => {
89+
assert.strictEqual(code, 0);
90+
assert.strictEqual(signal, null);
91+
}));
92+
}));
93+
94+
const fdSocket = dgram.createSocket('udp4');
95+
fdSocket.bind(0, '127.0.0.1', common.mustCall(() => {
96+
const source = `
97+
const assert = require('node:assert');
98+
const dgram = require('node:dgram');
99+
const socket = dgram.createSocket('udp4');
100+
let error;
101+
try {
102+
socket.bind({ fd: 3 });
103+
} catch (err) {
104+
error = err;
105+
}
106+
if (error) {
107+
assert.strictEqual(error.code, 'ERR_ACCESS_DENIED');
108+
assert.strictEqual(error.permission, 'Net');
109+
process.exit(0);
110+
}
111+
process.exit(1);
112+
`;
113+
const child = spawn(
114+
process.execPath,
115+
['--permission', '--eval', source],
116+
{
117+
stdio: [
118+
'ignore',
119+
'ignore',
120+
'pipe',
121+
fdSocket[kStateSymbol].handle.fd,
122+
],
123+
},
124+
);
125+
let stderr = '';
126+
127+
child.stderr.setEncoding('utf8');
128+
child.stderr.on('data', (chunk) => { stderr += chunk; });
129+
child.once('exit', common.mustCall((code, signal) => {
130+
assert.strictEqual(code, 0, stderr);
131+
assert.strictEqual(signal, null);
132+
fdSocket.close();
133+
}));
134+
}));
135+
}

0 commit comments

Comments
 (0)