Skip to content

Commit d34f32b

Browse files
committed
permission,net,dgram: add permission for net
1 parent 3a3dfbd commit d34f32b

21 files changed

+1144
-17
lines changed

doc/api/cli.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,58 @@ Error: Access to this API has been restricted
357357
}
358358
```
359359

360+
### `--allow-net-udp`
361+
362+
<!-- YAML
363+
added: REPLACEME
364+
-->
365+
366+
> Stability: 1.1 - Active development
367+
368+
When using the [Permission Model][], the process will not be able to create,
369+
receive, and send UDP packages by default. Attempts to do so will throw an
370+
`ERR_ACCESS_DENIED` unless the user explicitly passes the `--allow-net-udp` flag
371+
when starting Node.js. Node.js will check the permission when create a UDP socket
372+
from fd.
373+
374+
The argument format is `--allow-net-udp=domain_or_ip[/netmask][:port]`.
375+
The valid arguments are:
376+
377+
* `*` - To allow all operations.
378+
* `--allow-net-udp=nodejs.org`
379+
* `--allow-net-udp=127.0.0.1`
380+
* `--allow-net-udp=127.0.0.1:8888`
381+
* `--allow-net-udp=127.0.0.1:*`
382+
* `--allow-net-udp=*:9999`
383+
* `--allow-net-udp=127.0.0.1/24:*`
384+
* `--allow-net-udp=127.0.0.1/255.255.255.0:*`
385+
* `--allow-net-udp=127.0.0.1:8080 --allow-net-udp=127.0.0.1:9090`
386+
* `--allow-net-udp=127.0.0.1:8080,localhost:9090`
387+
388+
Example:
389+
390+
```js
391+
const dgram = require('node:dgram');
392+
dgram.createSocket('udp4').bind(9297, '127.0.0.1')
393+
```
394+
395+
```console
396+
$ node --experimental-permission --allow-fs-read=./index.js index.js
397+
node:events:498
398+
throw er; // Unhandled 'error' event
399+
^
400+
401+
Error [ERR_ACCESS_DENIED]: Access to this API has been restricted. Permission: bind to 127.0.0.1/9297
402+
at node:dgram:379:18
403+
at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
404+
Emitted 'error' event on Socket instance at:
405+
at afterDns (node:dgram:337:12)
406+
at node:dgram:379:9
407+
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
408+
code: 'ERR_ACCESS_DENIED'
409+
}
410+
```
411+
360412
### `--build-snapshot`
361413

362414
<!-- YAML
@@ -1014,6 +1066,7 @@ following permissions are restricted:
10141066
* Child Process - manageable through [`--allow-child-process`][] flag
10151067
* Worker Threads - manageable through [`--allow-worker`][] flag
10161068
* WASI - manageable through [`--allow-wasi`][] flag
1069+
* UDP - manageable through [`--allow-net-udp`][] flag
10171070

10181071
### `--experimental-require-module`
10191072

@@ -2835,6 +2888,7 @@ one is included in the list below.
28352888
* `--allow-child-process`
28362889
* `--allow-fs-read`
28372890
* `--allow-fs-write`
2891+
* `--allow-net-udp`
28382892
* `--allow-wasi`
28392893
* `--allow-worker`
28402894
* `--conditions`, `-C`
@@ -3390,6 +3444,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
33903444
[`--allow-child-process`]: #--allow-child-process
33913445
[`--allow-fs-read`]: #--allow-fs-read
33923446
[`--allow-fs-write`]: #--allow-fs-write
3447+
[`--allow-net-udp`]: #--allow-net-udp
33933448
[`--allow-wasi`]: #--allow-wasi
33943449
[`--allow-worker`]: #--allow-worker
33953450
[`--build-snapshot`]: #--build-snapshot

doc/api/permissions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,8 @@ using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
509509
To allow native addons when using permission model, use the [`--allow-addons`][]
510510
flag. For WASI, use the [`--allow-wasi`][] flag.
511511

512+
For UDP, use [`--allow-net-udp`][] flag.
513+
512514
#### Runtime API
513515

514516
When enabling the Permission Model through the [`--experimental-permission`][]
@@ -583,6 +585,7 @@ There are constraints you need to know before using this system:
583585
* Inspector protocol
584586
* File system access
585587
* WASI
588+
* UDP
586589
* The Permission Model is initialized after the Node.js environment is set up.
587590
However, certain flags such as `--env-file` or `--openssl-config` are designed
588591
to read files before environment initialization. As a result, such flags are
@@ -607,6 +610,7 @@ There are constraints you need to know before using this system:
607610
[`--allow-child-process`]: cli.md#--allow-child-process
608611
[`--allow-fs-read`]: cli.md#--allow-fs-read
609612
[`--allow-fs-write`]: cli.md#--allow-fs-write
613+
[`--allow-net-udp`]: cli.md#--allow-net-udp
610614
[`--allow-wasi`]: cli.md#--allow-wasi
611615
[`--allow-worker`]: cli.md#--allow-worker
612616
[`--experimental-permission`]: cli.md#--experimental-permission

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ Allow execution of WASI when using the permission model.
9191
.It Fl -allow-worker
9292
Allow creating worker threads when using the permission model.
9393
.
94+
.It Fl -allow-net-udp
95+
Allow create, receive, and, send UDP packages when using the permission model.
96+
.
9497
.It Fl -completion-bash
9598
Print source-able bash completion script for Node.js.
9699
.

lib/dgram.js

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const {
3838
ErrnoException,
3939
ExceptionWithHostPort,
4040
codes: {
41+
ERR_ACCESS_DENIED,
4142
ERR_BUFFER_OUT_OF_BOUNDS,
4243
ERR_INVALID_ARG_TYPE,
4344
ERR_INVALID_FD_TYPE,
@@ -81,6 +82,7 @@ const {
8182

8283
const dc = require('diagnostics_channel');
8384
const udpSocketChannel = dc.channel('udp.socket');
85+
const permission = require('internal/process/permission');
8486

8587
const BIND_STATE_UNBOUND = 0;
8688
const BIND_STATE_BINDING = 1;
@@ -327,12 +329,9 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
327329
else
328330
address = '::';
329331
}
330-
331-
// Resolve address first
332-
state.handle.lookup(address, (err, ip) => {
332+
const afterDns = (err, ip) => {
333333
if (!state.handle)
334334
return; // Handle has been closed in the mean time
335-
336335
if (err) {
337336
state.bindState = BIND_STATE_UNBOUND;
338337
this.emit('error', err);
@@ -361,7 +360,7 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
361360
this.emit('error', ex);
362361
});
363362
} else {
364-
const err = state.handle.bind(ip, port || 0, flags);
363+
const err = state.handle.bind(ip, port || 0, flags, false);
365364
if (err) {
366365
const ex = new ExceptionWithHostPort(err, 'bind', ip, port);
367366
state.bindState = BIND_STATE_UNBOUND;
@@ -372,7 +371,22 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) {
372371

373372
startListening(this);
374373
}
375-
});
374+
};
375+
if (permission.isEnabled()) {
376+
const resource = `${address}/${port || '*'}`;
377+
if (!permission.has('net.udp', resource)) {
378+
process.nextTick(() => {
379+
afterDns(new ERR_ACCESS_DENIED(
380+
`bind to ${resource}`,
381+
'NetUDP',
382+
resource,
383+
));
384+
});
385+
return this;
386+
}
387+
}
388+
// Resolve address first
389+
state.handle.lookup(address, afterDns);
376390

377391
return this;
378392
};
@@ -413,13 +427,38 @@ function _connect(port, address, callback) {
413427
this.once('connect', callback);
414428

415429
const afterDns = (ex, ip) => {
430+
if (!ex && !address && permission.isEnabled()) {
431+
const resource = `${ip}/${port}`;
432+
if (!permission.has('net.udp', resource)) {
433+
ex = new ERR_ACCESS_DENIED(
434+
`connect to ${resource}`,
435+
'NetUDP',
436+
resource,
437+
);
438+
}
439+
}
416440
defaultTriggerAsyncIdScope(
417441
this[async_id_symbol],
418442
doConnect,
419443
ex, this, ip, address, port, callback,
420444
);
421445
};
422-
446+
// If the address which is allowed is a domain,
447+
// we should allow bind to all IPs this domain ponit to,
448+
// so we need check here instead of in afterDns
449+
if (address && permission.isEnabled()) {
450+
const resource = `${address}/${port}`;
451+
if (!permission.has('net.udp', resource)) {
452+
process.nextTick(() => {
453+
afterDns(new ERR_ACCESS_DENIED(
454+
`connect to ${resource}`,
455+
'NetUDP',
456+
resource,
457+
));
458+
});
459+
return;
460+
}
461+
}
423462
state.handle.lookup(address, afterDns);
424463
}
425464

@@ -430,7 +469,7 @@ function doConnect(ex, self, ip, address, port, callback) {
430469
return;
431470

432471
if (!ex) {
433-
const err = state.handle.connect(ip, port);
472+
const err = state.handle.connect(ip, port, false);
434473
if (err) {
435474
ex = new ExceptionWithHostPort(err, 'connect', address, port);
436475
}
@@ -663,6 +702,17 @@ Socket.prototype.send = function(buffer,
663702
}
664703

665704
const afterDns = (ex, ip) => {
705+
// If we have not checked before dns, check it now
706+
if (!ex && !connected && !address && permission.isEnabled()) {
707+
const resource = `${ip}/${port}`;
708+
if (!permission.has('net.udp', resource)) {
709+
ex = new ERR_ACCESS_DENIED(
710+
`send to ${resource}`,
711+
'NetUDP',
712+
resource,
713+
);
714+
}
715+
}
666716
defaultTriggerAsyncIdScope(
667717
this[async_id_symbol],
668718
doSend,
@@ -671,6 +721,19 @@ Socket.prototype.send = function(buffer,
671721
};
672722

673723
if (!connected) {
724+
if (address && permission.isEnabled()) {
725+
const resource = `${address}/${port}`;
726+
if (!permission.has('net.udp', resource)) {
727+
process.nextTick(() => {
728+
afterDns(new ERR_ACCESS_DENIED(
729+
`send to ${resource}`,
730+
'NetUDP',
731+
resource,
732+
));
733+
});
734+
return;
735+
}
736+
}
674737
state.handle.lookup(address, afterDns);
675738
} else {
676739
afterDns(null, null);
@@ -703,7 +766,7 @@ function doSend(ex, self, ip, list, address, port, callback) {
703766

704767
let err;
705768
if (port)
706-
err = state.handle.send(req, list, list.length, port, ip, !!callback);
769+
err = state.handle.send(req, list, list.length, port, ip, !!callback, false);
707770
else
708771
err = state.handle.send(req, list, list.length, !!callback);
709772

lib/internal/dgram.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function _createSocketHandle(address, port, addressType, fd, flags) {
7272
err = handle.open(fd);
7373
}
7474
} else if (port || address) {
75-
err = handle.bind(address, port || 0, flags);
75+
err = handle.bind(address, port || 0, flags, true);
7676
}
7777

7878
if (err) {

lib/internal/process/pre_execution.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ function initializePermission() {
597597
'--allow-child-process',
598598
'--allow-wasi',
599599
'--allow-worker',
600+
'--allow-net-udp',
600601
];
601602
ArrayPrototypeForEach(availablePermissionFlags, (flag) => {
602603
const value = getOptionValue(flag);

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
'src/permission/permission.cc',
159159
'src/permission/wasi_permission.cc',
160160
'src/permission/worker_permission.cc',
161+
'src/permission/net_permission.cc',
161162
'src/pipe_wrap.cc',
162163
'src/process_wrap.cc',
163164
'src/signal_wrap.cc',
@@ -283,6 +284,7 @@
283284
'src/permission/permission.h',
284285
'src/permission/wasi_permission.h',
285286
'src/permission/worker_permission.h',
287+
'src/permission/net_permission.h',
286288
'src/pipe_wrap.h',
287289
'src/req_wrap.h',
288290
'src/req_wrap-inl.h',

src/env.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,10 @@ Environment::Environment(IsolateData* isolate_data,
950950
options_->allow_fs_write,
951951
permission::PermissionScope::kFileSystemWrite);
952952
}
953+
if (!options_->allow_net_udp.empty()) {
954+
permission()->Apply(
955+
this, options_->allow_net_udp, permission::PermissionScope::kNetUDP);
956+
}
953957
}
954958
}
955959

0 commit comments

Comments
 (0)