From ed0b8114e06659707e46ddc56e11d9e2e98d19ca Mon Sep 17 00:00:00 2001 From: theanarkh Date: Mon, 10 Jun 2024 04:44:09 +0800 Subject: [PATCH] permission,net,dgram: add permission for net --- doc/api/cli.md | 180 ++++++++++++ doc/api/permissions.md | 8 + doc/node.1 | 12 + lib/internal/process/pre_execution.js | 8 + node.gyp | 2 + src/env.cc | 20 ++ src/node_options.cc | 16 ++ src/node_options.h | 4 + src/permission/net_permission.cc | 256 ++++++++++++++++++ src/permission/net_permission.h | 176 ++++++++++++ src/permission/permission.cc | 5 + src/permission/permission.h | 1 + src/permission/permission_base.h | 12 +- src/tcp_wrap.cc | 27 ++ src/udp_wrap.cc | 28 ++ .../parallel/test-permission-warning-flags.js | 4 + 16 files changed, 758 insertions(+), 1 deletion(-) create mode 100644 src/permission/net_permission.cc create mode 100644 src/permission/net_permission.h diff --git a/doc/api/cli.md b/doc/api/cli.md index cf64f45bfd4d64..8077fb4ba7e557 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -357,6 +357,176 @@ Error: Access to this API has been restricted } ``` +### `--allow-net-tcp-in` + + + +> Stability: 1.1 - Active development + +When using the [Permission Model][], the process will not be able to bind to any +ip or port by default. Attempts to do so will throw an `ERR_ACCESS_DENIED` unless +the user explicitly passes the `--allow-net-tcp-in` flag when starting Node.js. + +The valid arguments for the `--allow-net-tcp-in` flag are: + +* `*` - To allow all `bind` operations. +* Multiple addresses can be allowed using multiple `--allow-net-tcp-in` flags. + Example `--allow-net-tcp-in=127.0.0.1:8080 --allow-net-tcp-in=127.0.0.1:9090` + +Example: + +```js +const net = require('node:net'); +net.createServer().listen(9297, '127.0.0.1') +``` + +```console +$ node --experimental-permission --allow-fs-read=./index.js index.js +node:net:1840 + err = handle.bind(address, port); + ^ + +Error: Access to this API has been restricted + at createServerHandle (node:net:1840:20) + at Server.setupListenHandle [as _listen2] (node:net:1879:14) + at listenInCluster (node:net:1961:12) + at doListen (node:net:2135:7) + at process.processTicksAndRejections (node:internal/process/task_queues:83:21) { + code: 'ERR_ACCESS_DENIED', + permission: 'NetTCPIn', + resource: '127.0.0.1/9297' +} +``` + +### `--allow-net-tcp-out` + + + +> Stability: 1.1 - Active development + +When using the [Permission Model][], the process will not be able to connect to any +ip or port by default. Attempts to do so will throw an `ERR_ACCESS_DENIED` unless +the user explicitly passes the `--allow-net-tcp-out` flag when starting Node.js. + +The valid arguments for the `--allow-net-tcp-out` flag are: + +* `*` - To allow all `connect` operations. +* Multiple addresses can be allowed using multiple `--allow-net-tcp-out` flags. + Example `--allow-net-tcp-out=127.0.0.1:8080 --allow-net-tcp-out=127.0.0.1:9090` + +Example: + +```js +const net = require('node:net'); +net.connect(9297, '127.0.0.1'); +``` + +```console +$ node --experimental-permission --allow-fs-read=./index.js index.js +node:net:1075 + err = self._handle.connect(req, address, port); + ^ + +Error: Access to this API has been restricted + at internalConnect (node:net:1075:26) + at defaultTriggerAsyncIdScope (node:internal/async_hooks:464:18) + at node:net:1324:9 + at process.processTicksAndRejections (node:internal/process/task_queues:77:11) { + code: 'ERR_ACCESS_DENIED', + permission: 'NetTCPOut', + resource: '127.0.0.1/9297' +} + +``` + +### `--allow-net-udp-in` + + + +> Stability: 1.1 - Active development + +When using the [Permission Model][], the process will not be able to bind to any +ip or port by default. Attempts to do so will throw an `ERR_ACCESS_DENIED` unless +the user explicitly passes the `--allow-net-udp-in` flag when starting Node.js. + +The valid arguments for the `--allow-net-udp-in` flag are: + +* `*` - To allow all `bind` operations. +* Multiple addresses can be allowed using multiple `--allow-net-udp-in` flags. + Example `--allow-net-udp-in=127.0.0.1:8080 --allow-net-udp-in=127.0.0.1:9090` + +Example: + +```js +const dgram = require('node:dgram'); +dgram.createSocket('udp4').bind(9000, '127.0.0.1') +``` + +```console +$ node --experimental-permission --allow-fs-read=./index.js index.js +node:dgram:364 + const err = state.handle.bind(ip, port || 0, flags); + ^ + +Error: Access to this API has been restricted + at node:dgram:364:32 + at process.processTicksAndRejections (node:internal/process/task_queues:83:21) { + code: 'ERR_ACCESS_DENIED', + permission: 'NetUDPIn', + resource: '127.0.0.1/9297' +} +``` + +### `--allow-net-udp-out` + + + +> Stability: 1.1 - Active development + +When using the [Permission Model][], the process will not be able to connect to any +ip or port by default. Attempts to do so will throw an `ERR_ACCESS_DENIED` unless +the user explicitly passes the `--allow-net-udp-out` flag when starting Node.js. + +The valid arguments for the `--allow-net-udp-out` flag are: + +* `*` - To allow all `connect` operations. +* Multiple addresses can be allowed using multiple `--allow-net-udp-out` flags. + Example `--allow-net-udp-out=127.0.0.1:8080 --allow-net-udp-out=127.0.0.1:9090` + +Example: + +```js +const dgram = require('node:dgram'); +dgram.createSocket('udp4').bind(8000, '127.0.0.1', function () { + this.connect(9001, '127.0.0.1') +}); +``` + +```console +$ node --experimental-permission --allow-net-udp-in=127.0.0.1/8000 --allow-fs-read=./index.js index.js +node:dgram:433 + const err = state.handle.connect(ip, port); + ^ + +Error: Access to this API has been restricted + at doConnect (node:dgram:433:30) + at defaultTriggerAsyncIdScope (node:internal/async_hooks:464:18) + at afterDns (node:dgram:416:5) + at process.processTicksAndRejections (node:internal/process/task_queues:83:21) { + code: 'ERR_ACCESS_DENIED', + permission: 'NetUDPOut', + resource: '127.0.0.1/9001' +} +``` + ### `--build-snapshot` er + // ---> n + // If */slow* is inserted right after, it will create an + // empty node + // /slow + // ---> '\000' ASCII (0) || \0 + // ---> er + // ---> n + bool IsEndNode() const { + if (children.size() == 0) { + return true; + } + return is_leaf; + } + }; + + RadixTree(); + ~RadixTree(); + void Insert(const std::string& s); + bool Lookup(const std::string_view& s) const { return Lookup(s, false); } + bool Lookup(const std::string_view& s, bool when_empty_return) const; + + private: + Node* root_node_; + }; + private: + void GrantAccess(PermissionScope scope, const std::string& param); + + RadixTree granted_net_tcp_in_; + RadixTree granted_net_tcp_out_; + + RadixTree granted_net_udp_in_; + RadixTree granted_net_udp_out_; + + bool deny_all_tcp_in_ = true; + bool deny_all_tcp_out_ = true; + + bool allow_all_tcp_in_ = false; + bool allow_all_tcp_out_ = false; + + bool deny_all_udp_in_ = true; + bool deny_all_udp_out_ = true; + + bool allow_all_udp_in_ = false; + bool allow_all_udp_out_ = false; + + +}; + +} // namespace permission + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_PERMISSION_NET_PERMISSION_H_ diff --git a/src/permission/permission.cc b/src/permission/permission.cc index ad393df0e38976..010504533d6e2c 100644 --- a/src/permission/permission.cc +++ b/src/permission/permission.cc @@ -83,6 +83,7 @@ Permission::Permission() : enabled_(false) { std::shared_ptr inspector = std::make_shared(); std::shared_ptr wasi = std::make_shared(); + std::shared_ptr net = std::make_shared(); #define V(Name, _, __) \ nodes_.insert(std::make_pair(PermissionScope::k##Name, fs)); FILESYSTEM_PERMISSIONS(V) @@ -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, net)); + NET_PERMISSIONS(V) +#undef V } Local CreateAccessDeniedError(Environment* env, diff --git a/src/permission/permission.h b/src/permission/permission.h index 55309b6ba551c2..a1bff1579cc79d 100644 --- a/src/permission/permission.h +++ b/src/permission/permission.h @@ -8,6 +8,7 @@ #include "permission/child_process_permission.h" #include "permission/fs_permission.h" #include "permission/inspector_permission.h" +#include "permission/net_permission.h" #include "permission/permission_base.h" #include "permission/wasi_permission.h" #include "permission/worker_permission.h" diff --git a/src/permission/permission_base.h b/src/permission/permission_base.h index d4ab33d10142f2..5965b7dbd7cfb4 100644 --- a/src/permission/permission_base.h +++ b/src/permission/permission_base.h @@ -28,12 +28,22 @@ namespace permission { #define INSPECTOR_PERMISSIONS(V) V(Inspector, "inspector", PermissionsRoot) +#define NET_PERMISSIONS(V) \ + V(Net, "net", PermissionsRoot) \ + V(NetTCP, "net.tcp", Net) \ + V(NetTCPIn, "net.tcp.in", NetTCP) \ + V(NetTCPOut, "net.tcp.out", NetTCP) \ + V(NetUDP, "net.udp", Net) \ + V(NetUDPIn, "net.udp.in", NetUDP) \ + V(NetUDPOut, "net.udp.out", NetUDP) + #define PERMISSIONS(V) \ FILESYSTEM_PERMISSIONS(V) \ CHILD_PROCESS_PERMISSIONS(V) \ WASI_PERMISSIONS(V) \ WORKER_THREADS_PERMISSIONS(V) \ - INSPECTOR_PERMISSIONS(V) + INSPECTOR_PERMISSIONS(V) \ + NET_PERMISSIONS(V) #define V(name, _, __) k##name, enum class PermissionScope { diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index baa7618dfa0743..f81e6788d21114 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -260,6 +260,19 @@ void TCPWrap::Bind( int err = uv_ip_addr(*ip_address, port, &addr); if (err == 0) { + if (UNLIKELY(env->permission()->enabled())) { + std::string resource = *ip_address + std::string("/") + std::to_string(port); + if (!env->permission()->is_granted( + env, + permission::PermissionScope::kNetTCPIn, + resource)) { + node::permission::Permission::ThrowAccessDenied( + env, + permission::PermissionScope::kNetTCPIn, + resource); + return; + } + } err = uv_tcp_bind(&wrap->handle_, reinterpret_cast(&addr), flags); @@ -334,6 +347,20 @@ void TCPWrap::Connect(const FunctionCallbackInfo& args, int err = uv_ip_addr(*ip_address, &addr); if (err == 0) { + if (UNLIKELY(env->permission()->enabled())) { + int port = static_cast(args[2].As()->Value()); + std::string resource = *ip_address + std::string("/") + std::to_string(port); + if (!env->permission()->is_granted( + env, + permission::PermissionScope::kNetTCPOut, + resource)) { + node::permission::Permission::ThrowAccessDenied( + env, + permission::PermissionScope::kNetTCPOut, + resource); + return; + } + } AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(wrap); ConnectWrap* req_wrap = new ConnectWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_TCPCONNECTWRAP); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index a7c6bc6ae07607..2bec87dc43ad15 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -314,6 +314,20 @@ void UDPWrap::DoBind(const FunctionCallbackInfo& args, int family) { struct sockaddr_storage addr_storage; int err = sockaddr_for_family(family, address.out(), port, &addr_storage); if (err == 0) { + Environment* env = wrap->env(); + if (UNLIKELY(env->permission()->enabled())) { + std::string resource = *address + std::string("/") + std::to_string(port); + if (!env->permission()->is_granted( + env, + permission::PermissionScope::kNetUDPIn, + resource)) { + node::permission::Permission::ThrowAccessDenied( + env, + permission::PermissionScope::kNetUDPIn, + resource); + return; + } + } err = uv_udp_bind(&wrap->handle_, reinterpret_cast(&addr_storage), flags); @@ -342,6 +356,20 @@ void UDPWrap::DoConnect(const FunctionCallbackInfo& args, int family) { struct sockaddr_storage addr_storage; int err = sockaddr_for_family(family, address.out(), port, &addr_storage); if (err == 0) { + Environment* env = wrap->env(); + if (UNLIKELY(env->permission()->enabled())) { + std::string resource = *address + std::string("/") + std::to_string(port); + if (!env->permission()->is_granted( + env, + permission::PermissionScope::kNetUDPOut, + resource)) { + node::permission::Permission::ThrowAccessDenied( + env, + permission::PermissionScope::kNetUDPOut, + resource); + return; + } + } err = uv_udp_connect(&wrap->handle_, reinterpret_cast(&addr_storage)); } diff --git a/test/parallel/test-permission-warning-flags.js b/test/parallel/test-permission-warning-flags.js index 87fcb7ff7f3158..018015163a3206 100644 --- a/test/parallel/test-permission-warning-flags.js +++ b/test/parallel/test-permission-warning-flags.js @@ -9,6 +9,10 @@ const warnFlags = [ '--allow-child-process', '--allow-wasi', '--allow-worker', + '--allow-net-tcp-in=*', + '--allow-net-tcp-out=*', + '--allow-net-udp-in=*', + '--allow-net-udp-out=*', ]; for (const flag of warnFlags) {