Skip to content

Commit da26847

Browse files
committed
src,permission: add --allow-net permission
Signed-off-by: RafaelGSS <[email protected]>
1 parent 563be01 commit da26847

32 files changed

+659
-19
lines changed

doc/api/cli.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,38 @@ When passing a single flag with a comma a warning will be displayed.
263263

264264
Examples can be found in the [File System Permissions][] documentation.
265265

266+
### `--allow-net`
267+
268+
<!-- YAML
269+
added: REPLACEME
270+
-->
271+
272+
> Stability: 1.1 - Active development
273+
274+
When using the [Permission Model][], the process will not be able to access
275+
network by default.
276+
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
277+
user explicitly passes the `--allow-net` flag when starting Node.js.
278+
279+
Example:
280+
281+
```js
282+
const http = require('node:http');
283+
// Attempt to bypass the permission
284+
const req = http.get('http://example.com', () => {});
285+
286+
req.on('error', (err) => {
287+
console.log('err', err);
288+
});
289+
```
290+
291+
```console
292+
$ node --permission index.js
293+
Error: connect ERR_ACCESS_DENIED Access to this API has been restricted. Use --allow-net to manage permissions.
294+
code: 'ERR_ACCESS_DENIED',
295+
}
296+
```
297+
266298
### `--allow-wasi`
267299

268300
<!-- YAML
@@ -1932,6 +1964,7 @@ following permissions are restricted:
19321964

19331965
* File System - manageable through
19341966
[`--allow-fs-read`][], [`--allow-fs-write`][] flags
1967+
* Network - manageable through [`--allow-net`][] flag
19351968
* Child Process - manageable through [`--allow-child-process`][] flag
19361969
* Worker Threads - manageable through [`--allow-worker`][] flag
19371970
* WASI - manageable through [`--allow-wasi`][] flag
@@ -3278,6 +3311,7 @@ one is included in the list below.
32783311
* `--allow-child-process`
32793312
* `--allow-fs-read`
32803313
* `--allow-fs-write`
3314+
* `--allow-net`
32813315
* `--allow-wasi`
32823316
* `--allow-worker`
32833317
* `--conditions`, `-C`
@@ -3880,6 +3914,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
38803914
[`--allow-child-process`]: #--allow-child-process
38813915
[`--allow-fs-read`]: #--allow-fs-read
38823916
[`--allow-fs-write`]: #--allow-fs-write
3917+
[`--allow-net`]: #--allow-net
38833918
[`--allow-wasi`]: #--allow-wasi
38843919
[`--allow-worker`]: #--allow-worker
38853920
[`--build-snapshot`]: #--build-snapshot

doc/api/permissions.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ The available permissions are documented by the [`--permission`][]
5151
flag.
5252

5353
When starting Node.js with `--permission`,
54-
the ability to access the file system through the `fs` module, spawn processes,
55-
use `node:worker_threads`, use native addons, use WASI, and enable the runtime inspector
56-
will be restricted.
54+
the ability to access the file system through the `fs` module, access the network,
55+
spawn processes, use `node:worker_threads`, use native addons, use WASI, and
56+
enable the runtime inspector will be restricted.
5757

5858
```console
5959
$ node --permission index.js
@@ -69,7 +69,8 @@ Error: Access to this API has been restricted
6969
Allowing access to spawning a process and creating worker threads can be done
7070
using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
7171

72-
To allow native addons when using permission model, use the [`--allow-addons`][]
72+
To allow network access, use [`--allow-net`][] and for allowing native addons
73+
when using permission model, use the [`--allow-addons`][]
7374
flag. For WASI, use the [`--allow-wasi`][] flag.
7475

7576
#### Runtime API
@@ -180,6 +181,7 @@ There are constraints you need to know before using this system:
180181
* The model does not inherit to a child node process or a worker thread.
181182
* When using the Permission Model the following features will be restricted:
182183
* Native modules
184+
* Network
183185
* Child process
184186
* Worker Threads
185187
* Inspector protocol
@@ -210,6 +212,7 @@ There are constraints you need to know before using this system:
210212
[`--allow-child-process`]: cli.md#--allow-child-process
211213
[`--allow-fs-read`]: cli.md#--allow-fs-read
212214
[`--allow-fs-write`]: cli.md#--allow-fs-write
215+
[`--allow-net`]: cli.md#--allow-net
213216
[`--allow-wasi`]: cli.md#--allow-wasi
214217
[`--allow-worker`]: cli.md#--allow-worker
215218
[`--permission`]: cli.md#--permission

doc/node-config-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
}
4646
]
4747
},
48+
"allow-net": {
49+
"type": "boolean"
50+
},
4851
"allow-wasi": {
4952
"type": "boolean"
5053
},

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ Allow using native addons when using the permission model.
8585
.It Fl -allow-child-process
8686
Allow spawning process when using the permission model.
8787
.
88+
.It Fl -allow-net
89+
Allow network access when using the permission model.
90+
.
8891
.It Fl -allow-wasi
8992
Allow execution of WASI when using the permission model.
9093
.

lib/internal/errors.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ function setInternalPrepareStackTrace(callback) {
113113
internalPrepareStackTrace = callback;
114114
}
115115

116+
function isPermissionModelError(err) {
117+
return typeof err !== 'number' && err.code && err.code === 'ERR_ACCESS_DENIED';
118+
}
119+
116120
/**
117121
* Every realm has its own prepareStackTraceCallback. When `error.stack` is
118122
* accessed, if the error is created in a shadow realm, the shadow realm's
@@ -762,17 +766,23 @@ class ExceptionWithHostPort extends Error {
762766
// This can be replaced with [ code ] = errmap.get(err) when this method
763767
// is no longer exposed to user land.
764768
util ??= require('util');
765-
const code = util.getSystemErrorName(err);
769+
let code;
766770
let details = '';
767-
if (port && port > 0) {
768-
details = ` ${address}:${port}`;
769-
} else if (address) {
770-
details = ` ${address}`;
771-
}
772-
if (additional) {
773-
details += ` - Local (${additional})`;
771+
// True when permission model is enabled
772+
if (isPermissionModelError(err)) {
773+
code = err.code;
774+
details = ` ${err.message}`;
775+
} else {
776+
code = util.getSystemErrorName(err);
777+
if (port && port > 0) {
778+
details = ` ${address}:${port}`;
779+
} else if (address) {
780+
details = ` ${address}`;
781+
}
782+
if (additional) {
783+
details += ` - Local (${additional})`;
784+
}
774785
}
775-
776786
super(`${syscall} ${code}${details}`);
777787

778788
this.errno = err;
@@ -798,7 +808,7 @@ class DNSException extends Error {
798808
constructor(code, syscall, hostname) {
799809
let errno;
800810
// If `code` is of type number, it is a libuv error number, else it is a
801-
// c-ares error code.
811+
// c-ares/permission model error code.
802812
// TODO(joyeecheung): translate c-ares error codes into numeric ones and
803813
// make them available in a property that's not error.errno (since they
804814
// can be in conflict with libuv error codes). Also make sure
@@ -813,6 +823,9 @@ class DNSException extends Error {
813823
} else {
814824
code = lazyInternalUtil().getSystemErrorName(code);
815825
}
826+
} else if (isPermissionModelError(code)) {
827+
// Expects a ERR_ACCESS_DENIED object
828+
code = code.code;
816829
}
817830
super(`${syscall} ${code}${hostname ? ` ${hostname}` : ''}`);
818831
this.errno = errno;

lib/internal/process/pre_execution.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ function initializePermission() {
614614
'--allow-fs-write',
615615
'--allow-addons',
616616
'--allow-child-process',
617+
'--allow-net',
617618
'--allow-wasi',
618619
'--allow-worker',
619620
];

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
'src/permission/permission.cc',
164164
'src/permission/wasi_permission.cc',
165165
'src/permission/worker_permission.cc',
166+
'src/permission/net_permission.cc',
166167
'src/pipe_wrap.cc',
167168
'src/process_wrap.cc',
168169
'src/signal_wrap.cc',
@@ -291,6 +292,7 @@
291292
'src/permission/permission.h',
292293
'src/permission/wasi_permission.h',
293294
'src/permission/worker_permission.h',
295+
'src/permission/net_permission.h',
294296
'src/pipe_wrap.h',
295297
'src/req_wrap.h',
296298
'src/req_wrap-inl.h',

src/cares_wrap.cc

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,16 @@ Maybe<int> SoaTraits::Parse(QuerySoaWrap* wrap,
16131613
}
16141614

16151615
int ReverseTraits::Send(QueryReverseWrap* wrap, const char* name) {
1616+
permission::PermissionScope scope = permission::PermissionScope::kNet;
1617+
Environment* env_holder = wrap->env();
1618+
1619+
if (!env_holder->permission()->is_granted(env_holder, scope, name))
1620+
[[unlikely]] {
1621+
wrap->QueuePermissionModelResponseCallback(name);
1622+
// Error will be returned in the callback
1623+
return ARES_SUCCESS;
1624+
}
1625+
16161626
int length, family;
16171627
char address_buffer[sizeof(struct in6_addr)];
16181628

@@ -1851,6 +1861,10 @@ void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
18511861
CHECK(args[4]->IsUint32());
18521862
Local<Object> req_wrap_obj = args[0].As<Object>();
18531863
node::Utf8Value hostname(env->isolate(), args[1]);
1864+
1865+
ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(
1866+
env, permission::PermissionScope::kNet, hostname.ToStringView(), args);
1867+
18541868
std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView());
18551869

18561870
int32_t flags = 0;
@@ -1925,10 +1939,18 @@ void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
19251939
TRACING_CATEGORY_NODE2(dns, native), "lookupService", req_wrap.get(),
19261940
"ip", TRACE_STR_COPY(*ip), "port", port);
19271941

1928-
int err = req_wrap->Dispatch(uv_getnameinfo,
1929-
AfterGetNameInfo,
1930-
reinterpret_cast<struct sockaddr*>(&addr),
1931-
NI_NAMEREQD);
1942+
int err = 0;
1943+
if (!env->permission()->is_granted(
1944+
env, permission::PermissionScope::kNet, ip.ToStringView()))
1945+
[[unlikely]] {
1946+
req_wrap->InsufficientPermissionError(*ip);
1947+
} else {
1948+
err = req_wrap->Dispatch(uv_getnameinfo,
1949+
AfterGetNameInfo,
1950+
reinterpret_cast<struct sockaddr*>(&addr),
1951+
NI_NAMEREQD);
1952+
}
1953+
19321954
if (err == 0)
19331955
// Release ownership of the pointer allowing the ownership to be transferred
19341956
USE(req_wrap.release());

src/cares_wrap.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "memory_tracker.h"
1212
#include "node.h"
1313
#include "node_internals.h"
14+
#include "permission/permission.h"
1415
#include "util.h"
1516

1617
#include "ares.h"
@@ -214,6 +215,8 @@ class GetNameInfoReqWrap final : public ReqWrap<uv_getnameinfo_t> {
214215
public:
215216
GetNameInfoReqWrap(Environment* env, v8::Local<v8::Object> req_wrap_obj);
216217

218+
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet)
219+
217220
SET_NO_MEMORY_INFO()
218221
SET_MEMORY_INFO_NAME(GetNameInfoReqWrap)
219222
SET_SELF_SIZE(GetNameInfoReqWrap)
@@ -249,6 +252,15 @@ class QueryWrap final : public AsyncWrap {
249252
void AresQuery(const char* name,
250253
ares_dns_class_t dnsclass,
251254
ares_dns_rec_type_t type) {
255+
permission::PermissionScope scope = permission::PermissionScope::kNet;
256+
Environment* env_holder = env();
257+
258+
if (!env_holder->permission()->is_granted(env_holder, scope, name))
259+
[[unlikely]] {
260+
QueuePermissionModelResponseCallback(name);
261+
return;
262+
}
263+
252264
channel_->EnsureServers();
253265
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
254266
TRACING_CATEGORY_NODE2(dns, native), trace_name_, this,
@@ -262,6 +274,8 @@ class QueryWrap final : public AsyncWrap {
262274
nullptr);
263275
}
264276

277+
SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(permission::PermissionScope::kNet);
278+
265279
void ParseError(int status) {
266280
CHECK_NE(status, ARES_SUCCESS);
267281
v8::HandleScope handle_scope(env()->isolate());
@@ -356,6 +370,20 @@ class QueryWrap final : public AsyncWrap {
356370
wrap->QueueResponseCallback(status);
357371
}
358372

373+
void QueuePermissionModelResponseCallback(const char* resource) {
374+
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
375+
const std::string res{resource};
376+
env()->SetImmediate([this, strong_ref, res](Environment*) {
377+
InsufficientPermissionError(res);
378+
379+
// Delete once strong_ref goes out of scope.
380+
Detach();
381+
});
382+
383+
channel_->set_query_last_ok(true);
384+
channel_->ModifyActivityQueryCount(-1);
385+
}
386+
359387
void QueueResponseCallback(int status) {
360388
BaseObjectPtr<QueryWrap<Traits>> strong_ref{this};
361389
env()->SetImmediate([this, strong_ref](Environment*) {

src/env.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,10 @@ Environment::Environment(IsolateData* isolate_data,
939939
options_->allow_fs_write,
940940
permission::PermissionScope::kFileSystemWrite);
941941
}
942+
943+
if (options_->allow_net) {
944+
permission()->Apply(this, {"*"}, permission::PermissionScope::kNet);
945+
}
942946
}
943947
}
944948

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
503503
"allow use of child process when any permissions are set",
504504
&EnvironmentOptions::allow_child_process,
505505
kAllowedInEnvvar);
506+
AddOption("--allow-net",
507+
"allow use of network when any permissions are set",
508+
&EnvironmentOptions::allow_net,
509+
kAllowedInEnvvar);
506510
AddOption("--allow-wasi",
507511
"allow wasi when any permissions are set",
508512
&EnvironmentOptions::allow_wasi,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class EnvironmentOptions : public Options {
142142
std::vector<std::string> allow_fs_write;
143143
bool allow_addons = false;
144144
bool allow_child_process = false;
145+
bool allow_net = false;
145146
bool allow_wasi = false;
146147
bool allow_worker_threads = false;
147148
bool experimental_repl_await = true;

src/permission/net_permission.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "net_permission.h"
2+
3+
#include <iostream>
4+
#include <string>
5+
6+
namespace node {
7+
8+
namespace permission {
9+
10+
void NetPermission::Apply(Environment* env,
11+
const std::vector<std::string>& allow,
12+
PermissionScope scope) {
13+
allow_net_ = true;
14+
}
15+
16+
bool NetPermission::is_granted(Environment* env,
17+
PermissionScope perm,
18+
const std::string_view& param) const {
19+
return allow_net_;
20+
}
21+
22+
} // namespace permission
23+
} // namespace node

0 commit comments

Comments
 (0)