Skip to content

Commit 9c645ef

Browse files
committed
permission,dns: add permission for dns
1 parent ce531af commit 9c645ef

File tree

13 files changed

+199
-6
lines changed

13 files changed

+199
-6
lines changed

doc/api/cli.md

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

360+
### `--allow-net-dns`
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 make dns query
369+
by default.
370+
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
371+
user explicitly passes the `--allow-net-dns` flag when starting Node.js.
372+
373+
Example:
374+
375+
```js
376+
const dns = require('node:dns');
377+
dns.lookup("localhost", () => {});
378+
```
379+
380+
```console
381+
$ node --experimental-permission --allow-fs-read=\* index.js
382+
node:dns:235
383+
const err = cares.getaddrinfo(
384+
^
385+
386+
Error: Access to this API has been restricted
387+
at Object.lookup (node:dns:235:21)
388+
at Object.<anonymous> (/home/index.js:2:5)
389+
at Module._compile (node:internal/modules/cjs/loader:1460:14)
390+
at Module._extensions..js (node:internal/modules/cjs/loader:1544:10)
391+
at Module.load (node:internal/modules/cjs/loader:1275:32)
392+
at Module._load (node:internal/modules/cjs/loader:1091:12)
393+
at wrapModuleLoad (node:internal/modules/cjs/loader:212:19)
394+
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
395+
at node:internal/main/run_main_module:30:49 {
396+
code: 'ERR_ACCESS_DENIED',
397+
permission: 'NetDNS',
398+
resource: 'lookup'
399+
}
400+
```
401+
360402
### `--build-snapshot`
361403

362404
<!-- YAML

doc/api/permissions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ There are constraints you need to know before using this system:
575575
* Inspector protocol
576576
* File system access
577577
* WASI
578+
* DNS
578579
* The Permission Model is initialized after the Node.js environment is set up.
579580
However, certain flags such as `--env-file` or `--openssl-config` are designed
580581
to read files before environment initialization. As a result, such flags are

src/cares_wrap.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,8 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
14171417
node::Utf8Value utf8name(env->isolate(), string);
14181418
auto plain_name = utf8name.ToStringView();
14191419
std::string name = ada::idna::to_ascii(plain_name);
1420+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1421+
env, permission::PermissionScope::kNetDNS, name);
14201422
channel->ModifyActivityQueryCount(1);
14211423
int err = wrap->Send(name.c_str());
14221424
if (err) {
@@ -1429,7 +1431,6 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
14291431
args.GetReturnValue().Set(err);
14301432
}
14311433

1432-
14331434
void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
14341435
auto cleanup = OnScopeLeave([&]() { uv_freeaddrinfo(res); });
14351436
BaseObjectPtr<GetAddrInfoReqWrap> req_wrap{
@@ -1568,15 +1569,15 @@ void CanonicalizeIP(const FunctionCallbackInfo<Value>& args) {
15681569

15691570
void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
15701571
Environment* env = Environment::GetCurrent(args);
1571-
15721572
CHECK(args[0]->IsObject());
15731573
CHECK(args[1]->IsString());
15741574
CHECK(args[2]->IsInt32());
15751575
CHECK(args[4]->IsUint32());
15761576
Local<Object> req_wrap_obj = args[0].As<Object>();
15771577
node::Utf8Value hostname(env->isolate(), args[1]);
15781578
std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView());
1579-
1579+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1580+
env, permission::PermissionScope::kNetDNS, ascii_hostname);
15801581
int32_t flags = 0;
15811582
if (args[3]->IsInt32()) {
15821583
flags = args[3].As<Int32>()->Value();
@@ -1639,7 +1640,9 @@ void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
16391640
node::Utf8Value ip(env->isolate(), args[1]);
16401641
const unsigned port = args[2]->Uint32Value(env->context()).FromJust();
16411642
struct sockaddr_storage addr;
1642-
1643+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1644+
env, permission::PermissionScope::kNetDNS,
1645+
(*ip + std::string(":") + std::to_string(port)));
16431646
CHECK(uv_ip4_addr(*ip, port, reinterpret_cast<sockaddr_in*>(&addr)) == 0 ||
16441647
uv_ip6_addr(*ip, port, reinterpret_cast<sockaddr_in6*>(&addr)) == 0);
16451648

src/cares_wrap.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ class QueryWrap final : public AsyncWrap {
233233
: AsyncWrap(channel->env(), req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP),
234234
channel_(channel),
235235
trace_name_(Traits::name) {}
236-
237236
~QueryWrap() {
238237
CHECK_EQ(false, persistent().IsEmpty());
239238

src/env.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,11 @@ Environment::Environment(IsolateData* isolate_data,
936936
options_->allow_fs_write,
937937
permission::PermissionScope::kFileSystemWrite);
938938
}
939+
if (!options_->allow_net_dns) {
940+
permission()->Apply(this,
941+
{"*"},
942+
permission::PermissionScope::kNetDNS);
943+
}
939944
}
940945
}
941946

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
464464
"allow worker threads when any permissions are set",
465465
&EnvironmentOptions::allow_worker_threads,
466466
kAllowedInEnvvar);
467+
AddOption("--allow-net-dns",
468+
"allow dns when any permissions are set",
469+
&EnvironmentOptions::allow_net_dns,
470+
kAllowedInEnvvar);
467471
AddOption("--experimental-repl-await",
468472
"experimental await keyword support in REPL",
469473
&EnvironmentOptions::experimental_repl_await,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class EnvironmentOptions : public Options {
132132
bool allow_child_process = false;
133133
bool allow_wasi = false;
134134
bool allow_worker_threads = false;
135+
bool allow_net_dns = false;
135136
bool experimental_repl_await = true;
136137
bool experimental_vm_modules = false;
137138
bool expose_internals = false;

src/permission/net_dns_permission.cc

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

src/permission/net_dns_permission.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef SRC_PERMISSION_NET_DNS_PERMISSION_H_
2+
#define SRC_PERMISSION_NET_DNS_PERMISSION_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include <vector>
7+
#include "permission/permission_base.h"
8+
9+
10+
namespace node {
11+
12+
namespace permission {
13+
14+
class NetDNSPermission final : public PermissionBase {
15+
public:
16+
void Apply(Environment* env,
17+
const std::vector<std::string>& allow,
18+
PermissionScope scope) override;
19+
bool is_granted(Environment* env,
20+
PermissionScope perm,
21+
const std::string_view& param = "") const override;
22+
23+
private:
24+
bool deny_all_;
25+
};
26+
27+
} // namespace permission
28+
29+
} // namespace node
30+
31+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
32+
#endif // SRC_PERMISSION_NET_DNS_PERMISSION_H_

src/permission/permission.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ Permission::Permission() : enabled_(false) {
8383
std::shared_ptr<PermissionBase> inspector =
8484
std::make_shared<InspectorPermission>();
8585
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
86+
std::shared_ptr<PermissionBase> dns = std::make_shared<NetDNSPermission>();
8687
#define V(Name, _, __) \
8788
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
8889
FILESYSTEM_PERMISSIONS(V)
@@ -103,6 +104,10 @@ Permission::Permission() : enabled_(false) {
103104
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
104105
WASI_PERMISSIONS(V)
105106
#undef V
107+
#define V(Name, _, __) \
108+
nodes_.insert(std::make_pair(PermissionScope::k##Name, dns));
109+
NET_DNS_PERMISSIONS(V)
110+
#undef V
106111
}
107112

108113
Local<Value> CreateAccessDeniedError(Environment* env,

src/permission/permission.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "permission/permission_base.h"
1212
#include "permission/wasi_permission.h"
1313
#include "permission/worker_permission.h"
14+
#include "permission/net_dns_permission.h"
1415
#include "v8.h"
1516

1617
#include <string_view>

src/permission/permission_base.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ namespace permission {
2828

2929
#define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot)
3030

31+
#define NET_DNS_PERMISSIONS(V) V(NetDNS, "net.dns", PermissionsRoot)
32+
3133
#define PERMISSIONS(V) \
3234
FILESYSTEM_PERMISSIONS(V) \
3335
CHILD_PROCESS_PERMISSIONS(V) \
3436
WASI_PERMISSIONS(V) \
3537
WORKER_THREADS_PERMISSIONS(V) \
36-
INSPECTOR_PERMISSIONS(V)
38+
INSPECTOR_PERMISSIONS(V) \
39+
NET_DNS_PERMISSIONS(V)
3740

3841
#define V(name, _, __) k##name,
3942
enum class PermissionScope {

test/parallel/test-permission-dns.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Flags: --experimental-permission --allow-fs-read=*
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
const dns = require('dns');
6+
const common = require('../common');
7+
8+
{
9+
const functions = [
10+
() => dns.lookup('example.com', () => {}),
11+
() => dns.lookupService('127.0.0.1', 22, () => {}),
12+
() => dns.reverse('8.8.8.8', () => {}),
13+
() => dns.resolveAny('example.com', () => {}),
14+
() => dns.resolve4('example.com', () => {}),
15+
() => dns.resolve6('example.com', () => {}),
16+
() => dns.resolveCaa('example.com', () => {}),
17+
() => dns.resolveCname('example.com', () => {}),
18+
() => dns.resolveMx('example.com', () => {}),
19+
() => dns.resolveNs('example.com', () => {}),
20+
() => dns.resolveTxt('example.com', () => {}),
21+
() => dns.resolveSrv('example.com', () => {}),
22+
() => dns.resolvePtr('example.com', () => {}),
23+
() => dns.resolveNaptr('example.com', () => {}),
24+
() => dns.resolveSoa('example.com', () => {}),
25+
];
26+
for (let i = 0; i < functions.length; i++) {
27+
assert.throws(functions[i], /Error: Access to this API has been restricted/);
28+
}
29+
}
30+
31+
{
32+
const resolvers = new dns.Resolver();
33+
const functions = [
34+
() => resolvers.reverse('8.8.8.8', () => {}),
35+
() => resolvers.resolveAny('example.com', () => {}),
36+
() => resolvers.resolve4('example.com', () => {}),
37+
() => resolvers.resolve6('example.com', () => {}),
38+
() => resolvers.resolveCaa('example.com', () => {}),
39+
() => resolvers.resolveCname('example.com', () => {}),
40+
() => resolvers.resolveMx('example.com', () => {}),
41+
() => resolvers.resolveNs('example.com', () => {}),
42+
() => resolvers.resolveTxt('example.com', () => {}),
43+
() => resolvers.resolveSrv('example.com', () => {}),
44+
() => resolvers.resolvePtr('example.com', () => {}),
45+
() => resolvers.resolveNaptr('example.com', () => {}),
46+
() => resolvers.resolveSoa('example.com', () => {}),
47+
];
48+
for (let i = 0; i < functions.length; i++) {
49+
assert.throws(functions[i], /Error: Access to this API has been restricted/);
50+
}
51+
}
52+
53+
{
54+
const functions = [
55+
() => dns.promises.lookup('example.com'),
56+
() => dns.promises.lookupService('127.0.0.1', 22),
57+
() => dns.promises.reverse('8.8.8.8'),
58+
() => dns.promises.resolveAny('example.com'),
59+
() => dns.promises.resolve4('example.com'),
60+
() => dns.promises.resolve6('example.com'),
61+
() => dns.promises.resolveCaa('example.com'),
62+
() => dns.promises.resolveCname('example.com'),
63+
() => dns.promises.resolveMx('example.com'),
64+
() => dns.promises.resolveNs('example.com'),
65+
() => dns.promises.resolveTxt('example.com'),
66+
() => dns.promises.resolveSrv('example.com'),
67+
() => dns.promises.resolvePtr('example.com'),
68+
() => dns.promises.resolveNaptr('example.com'),
69+
() => dns.promises.resolveSoa('example.com'),
70+
];
71+
for (let i = 0; i < functions.length; i++) {
72+
assert.rejects(functions[i], /Error: Access to this API has been restricted/).then(common.mustCall());
73+
}
74+
}

0 commit comments

Comments
 (0)