Skip to content

Commit

Permalink
permission,dns: add permission for dns
Browse files Browse the repository at this point in the history
  • Loading branch information
theanarkh committed Jun 9, 2024
1 parent ce531af commit 9c645ef
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 6 deletions.
42 changes: 42 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,48 @@ Error: Access to this API has been restricted
}
```

### `--allow-net-dns`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active development
When using the [Permission Model][], the process will not be able to make dns query
by default.
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
user explicitly passes the `--allow-net-dns` flag when starting Node.js.

Example:

```js
const dns = require('node:dns');
dns.lookup("localhost", () => {});
```

```console
$ node --experimental-permission --allow-fs-read=\* index.js
node:dns:235
const err = cares.getaddrinfo(
^

Error: Access to this API has been restricted
at Object.lookup (node:dns:235:21)
at Object.<anonymous> (/home/index.js:2:5)
at Module._compile (node:internal/modules/cjs/loader:1460:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1544:10)
at Module.load (node:internal/modules/cjs/loader:1275:32)
at Module._load (node:internal/modules/cjs/loader:1091:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:212:19)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
at node:internal/main/run_main_module:30:49 {
code: 'ERR_ACCESS_DENIED',
permission: 'NetDNS',
resource: 'lookup'
}
```

### `--build-snapshot`

<!-- YAML
Expand Down
1 change: 1 addition & 0 deletions doc/api/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ There are constraints you need to know before using this system:
* Inspector protocol
* File system access
* WASI
* DNS
* The Permission Model is initialized after the Node.js environment is set up.
However, certain flags such as `--env-file` or `--openssl-config` are designed
to read files before environment initialization. As a result, such flags are
Expand Down
11 changes: 7 additions & 4 deletions src/cares_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,8 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
node::Utf8Value utf8name(env->isolate(), string);
auto plain_name = utf8name.ToStringView();
std::string name = ada::idna::to_ascii(plain_name);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kNetDNS, name);
channel->ModifyActivityQueryCount(1);
int err = wrap->Send(name.c_str());
if (err) {
Expand All @@ -1429,7 +1431,6 @@ static void Query(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(err);
}


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

void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK(args[0]->IsObject());
CHECK(args[1]->IsString());
CHECK(args[2]->IsInt32());
CHECK(args[4]->IsUint32());
Local<Object> req_wrap_obj = args[0].As<Object>();
node::Utf8Value hostname(env->isolate(), args[1]);
std::string ascii_hostname = ada::idna::to_ascii(hostname.ToStringView());

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kNetDNS, ascii_hostname);
int32_t flags = 0;
if (args[3]->IsInt32()) {
flags = args[3].As<Int32>()->Value();
Expand Down Expand Up @@ -1639,7 +1640,9 @@ void GetNameInfo(const FunctionCallbackInfo<Value>& args) {
node::Utf8Value ip(env->isolate(), args[1]);
const unsigned port = args[2]->Uint32Value(env->context()).FromJust();
struct sockaddr_storage addr;

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kNetDNS,
(*ip + std::string(":") + std::to_string(port)));
CHECK(uv_ip4_addr(*ip, port, reinterpret_cast<sockaddr_in*>(&addr)) == 0 ||
uv_ip6_addr(*ip, port, reinterpret_cast<sockaddr_in6*>(&addr)) == 0);

Expand Down
1 change: 0 additions & 1 deletion src/cares_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ class QueryWrap final : public AsyncWrap {
: AsyncWrap(channel->env(), req_wrap_obj, AsyncWrap::PROVIDER_QUERYWRAP),
channel_(channel),
trace_name_(Traits::name) {}

~QueryWrap() {
CHECK_EQ(false, persistent().IsEmpty());

Expand Down
5 changes: 5 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,11 @@ Environment::Environment(IsolateData* isolate_data,
options_->allow_fs_write,
permission::PermissionScope::kFileSystemWrite);
}
if (!options_->allow_net_dns) {
permission()->Apply(this,
{"*"},
permission::PermissionScope::kNetDNS);
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"allow worker threads when any permissions are set",
&EnvironmentOptions::allow_worker_threads,
kAllowedInEnvvar);
AddOption("--allow-net-dns",
"allow dns when any permissions are set",
&EnvironmentOptions::allow_net_dns,
kAllowedInEnvvar);
AddOption("--experimental-repl-await",
"experimental await keyword support in REPL",
&EnvironmentOptions::experimental_repl_await,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class EnvironmentOptions : public Options {
bool allow_child_process = false;
bool allow_wasi = false;
bool allow_worker_threads = false;
bool allow_net_dns = false;
bool experimental_repl_await = true;
bool experimental_vm_modules = false;
bool expose_internals = false;
Expand Down
23 changes: 23 additions & 0 deletions src/permission/net_dns_permission.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "net_dns_permission.h"

#include <string>
#include <vector>

namespace node {

namespace permission {

void NetDNSPermission::Apply(Environment* env,
const std::vector<std::string>& allow,
PermissionScope scope) {
deny_all_ = true;
}

bool NetDNSPermission::is_granted(Environment* env,
PermissionScope perm,
const std::string_view& param) const {
return deny_all_ == false;
}

} // namespace permission
} // namespace node
32 changes: 32 additions & 0 deletions src/permission/net_dns_permission.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef SRC_PERMISSION_NET_DNS_PERMISSION_H_
#define SRC_PERMISSION_NET_DNS_PERMISSION_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <vector>
#include "permission/permission_base.h"


namespace node {

namespace permission {

class NetDNSPermission final : public PermissionBase {
public:
void Apply(Environment* env,
const std::vector<std::string>& allow,
PermissionScope scope) override;
bool is_granted(Environment* env,
PermissionScope perm,
const std::string_view& param = "") const override;

private:
bool deny_all_;
};

} // namespace permission

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_PERMISSION_NET_DNS_PERMISSION_H_
5 changes: 5 additions & 0 deletions src/permission/permission.cc
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Permission::Permission() : enabled_(false) {
std::shared_ptr<PermissionBase> inspector =
std::make_shared<InspectorPermission>();
std::shared_ptr<PermissionBase> wasi = std::make_shared<WASIPermission>();
std::shared_ptr<PermissionBase> dns = std::make_shared<NetDNSPermission>();
#define V(Name, _, __) \
nodes_.insert(std::make_pair(PermissionScope::k##Name, fs));
FILESYSTEM_PERMISSIONS(V)
Expand All @@ -103,6 +104,10 @@ Permission::Permission() : enabled_(false) {
nodes_.insert(std::make_pair(PermissionScope::k##Name, wasi));
WASI_PERMISSIONS(V)
#undef V
#define V(Name, _, __) \
nodes_.insert(std::make_pair(PermissionScope::k##Name, dns));
NET_DNS_PERMISSIONS(V)
#undef V
}

Local<Value> CreateAccessDeniedError(Environment* env,
Expand Down
1 change: 1 addition & 0 deletions src/permission/permission.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "permission/permission_base.h"
#include "permission/wasi_permission.h"
#include "permission/worker_permission.h"
#include "permission/net_dns_permission.h"
#include "v8.h"

#include <string_view>
Expand Down
5 changes: 4 additions & 1 deletion src/permission/permission_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ namespace permission {

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

#define NET_DNS_PERMISSIONS(V) V(NetDNS, "net.dns", PermissionsRoot)

#define PERMISSIONS(V) \
FILESYSTEM_PERMISSIONS(V) \
CHILD_PROCESS_PERMISSIONS(V) \
WASI_PERMISSIONS(V) \
WORKER_THREADS_PERMISSIONS(V) \
INSPECTOR_PERMISSIONS(V)
INSPECTOR_PERMISSIONS(V) \
NET_DNS_PERMISSIONS(V)

#define V(name, _, __) k##name,
enum class PermissionScope {
Expand Down
74 changes: 74 additions & 0 deletions test/parallel/test-permission-dns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Flags: --experimental-permission --allow-fs-read=*
'use strict';
require('../common');
const assert = require('assert');
const dns = require('dns');
const common = require('../common');

{
const functions = [
() => dns.lookup('example.com', () => {}),
() => dns.lookupService('127.0.0.1', 22, () => {}),
() => dns.reverse('8.8.8.8', () => {}),
() => dns.resolveAny('example.com', () => {}),
() => dns.resolve4('example.com', () => {}),
() => dns.resolve6('example.com', () => {}),
() => dns.resolveCaa('example.com', () => {}),
() => dns.resolveCname('example.com', () => {}),
() => dns.resolveMx('example.com', () => {}),
() => dns.resolveNs('example.com', () => {}),
() => dns.resolveTxt('example.com', () => {}),
() => dns.resolveSrv('example.com', () => {}),
() => dns.resolvePtr('example.com', () => {}),
() => dns.resolveNaptr('example.com', () => {}),
() => dns.resolveSoa('example.com', () => {}),
];
for (let i = 0; i < functions.length; i++) {
assert.throws(functions[i], /Error: Access to this API has been restricted/);
}
}

{
const resolvers = new dns.Resolver();
const functions = [
() => resolvers.reverse('8.8.8.8', () => {}),
() => resolvers.resolveAny('example.com', () => {}),
() => resolvers.resolve4('example.com', () => {}),
() => resolvers.resolve6('example.com', () => {}),
() => resolvers.resolveCaa('example.com', () => {}),
() => resolvers.resolveCname('example.com', () => {}),
() => resolvers.resolveMx('example.com', () => {}),
() => resolvers.resolveNs('example.com', () => {}),
() => resolvers.resolveTxt('example.com', () => {}),
() => resolvers.resolveSrv('example.com', () => {}),
() => resolvers.resolvePtr('example.com', () => {}),
() => resolvers.resolveNaptr('example.com', () => {}),
() => resolvers.resolveSoa('example.com', () => {}),
];
for (let i = 0; i < functions.length; i++) {
assert.throws(functions[i], /Error: Access to this API has been restricted/);
}
}

{
const functions = [
() => dns.promises.lookup('example.com'),
() => dns.promises.lookupService('127.0.0.1', 22),
() => dns.promises.reverse('8.8.8.8'),
() => dns.promises.resolveAny('example.com'),
() => dns.promises.resolve4('example.com'),
() => dns.promises.resolve6('example.com'),
() => dns.promises.resolveCaa('example.com'),
() => dns.promises.resolveCname('example.com'),
() => dns.promises.resolveMx('example.com'),
() => dns.promises.resolveNs('example.com'),
() => dns.promises.resolveTxt('example.com'),
() => dns.promises.resolveSrv('example.com'),
() => dns.promises.resolvePtr('example.com'),
() => dns.promises.resolveNaptr('example.com'),
() => dns.promises.resolveSoa('example.com'),
];
for (let i = 0; i < functions.length; i++) {
assert.rejects(functions[i], /Error: Access to this API has been restricted/).then(common.mustCall());
}
}

0 comments on commit 9c645ef

Please sign in to comment.