diff --git a/src/permission/fs_permission.cc b/src/permission/fs_permission.cc new file mode 100644 index 00000000000000..7a8a0ba2511d4b --- /dev/null +++ b/src/permission/fs_permission.cc @@ -0,0 +1,180 @@ +#include "fs_permission.h" +#include "base_object-inl.h" +#include "util.h" +#include "v8.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::string WildcardIfDir(const std::string& res) noexcept { + uv_fs_t req; + int rc = uv_fs_stat(nullptr, &req, res.c_str(), nullptr); + if (rc == 0) { + const uv_stat_t* const s = static_cast(req.ptr); + if (s->st_mode & S_IFDIR) { + // add wildcard when directory + if (res.back() == node::kPathSeparator) { + return res + "*"; + } + return res + node::kPathSeparator + "*"; + } + } + uv_fs_req_cleanup(&req); + return res; +} + +void FreeRecursivelyNode( + node::permission::FSPermission::RadixTree::Node* node) { + if (node == nullptr) { + return; + } + + if (node->children.size()) { + for (auto& c : node->children) { + FreeRecursivelyNode(c.second); + } + } + + if (node->wildcard_child != nullptr) { + delete node->wildcard_child; + } + delete node; +} + +bool is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree, + const std::string_view& param) { +#ifdef _WIN32 + // is UNC file path + if (param.rfind("\\\\", 0) == 0) { + // return lookup with normalized param + int starting_pos = 4; // "\\?\" + if (param.rfind("\\\\?\\UNC\\") == 0) { + starting_pos += 4; // "UNC\" + } + auto normalized = param.substr(starting_pos); + return granted_tree->Lookup(normalized, true); + } +#endif + return granted_tree->Lookup(param, true); +} + +} // namespace + +namespace node { + +namespace permission { + +// allow = '*' +// allow = '/tmp/,/home/example.js' +void FSPermission::Apply(const std::string& allow, PermissionScope scope) { + for (const auto& res : SplitString(allow, ',')) { + if (res == "*") { + if (scope == PermissionScope::kFileSystemRead) { + deny_all_in_ = false; + allow_all_in_ = true; + } else { + deny_all_out_ = false; + allow_all_out_ = true; + } + return; + } + GrantAccess(scope, res); + } +} + +void FSPermission::GrantAccess(PermissionScope perm, const std::string& res) { + const std::string path = WildcardIfDir(res); + if (perm == PermissionScope::kFileSystemRead) { + granted_in_fs_.Insert(path); + deny_all_in_ = false; + } else if (perm == PermissionScope::kFileSystemWrite) { + granted_out_fs_.Insert(path); + deny_all_out_ = false; + } +} + +bool FSPermission::is_granted(PermissionScope perm, + const std::string_view& param = "") { + switch (perm) { + case PermissionScope::kFileSystem: + return allow_all_in_ && allow_all_out_; + case PermissionScope::kFileSystemRead: + return !deny_all_in_ && + ((param.empty() && allow_all_in_) || allow_all_in_ || + is_tree_granted(&granted_in_fs_, param)); + case PermissionScope::kFileSystemWrite: + return !deny_all_out_ && + ((param.empty() && allow_all_out_) || allow_all_out_ || + is_tree_granted(&granted_out_fs_, param)); + default: + return false; + } +} + +FSPermission::RadixTree::RadixTree() : root_node_(new Node("")) {} + +FSPermission::RadixTree::~RadixTree() { + FreeRecursivelyNode(root_node_); +} + +bool FSPermission::RadixTree::Lookup(const std::string_view& s, + bool when_empty_return = false) { + FSPermission::RadixTree::Node* current_node = root_node_; + if (current_node->children.size() == 0) { + return when_empty_return; + } + + unsigned int parent_node_prefix_len = current_node->prefix.length(); + const std::string path(s); + auto path_len = path.length(); + + while (true) { + if (parent_node_prefix_len == path_len && current_node->IsEndNode()) { + return true; + } + + auto node = current_node->NextNode(path, parent_node_prefix_len); + if (node == nullptr) { + return false; + } + + current_node = node; + parent_node_prefix_len += current_node->prefix.length(); + if (current_node->wildcard_child != nullptr && + path_len >= (parent_node_prefix_len - 2 /* slash* */)) { + return true; + } + } +} + +void FSPermission::RadixTree::Insert(const std::string& path) { + FSPermission::RadixTree::Node* current_node = root_node_; + + unsigned int parent_node_prefix_len = current_node->prefix.length(); + int path_len = path.length(); + + for (int i = 1; i <= path_len; ++i) { + bool is_wildcard_node = path[i - 1] == '*'; + bool is_last_char = i == path_len; + + if (is_wildcard_node || is_last_char) { + std::string node_path = path.substr(parent_node_prefix_len, i); + current_node = current_node->CreateChild(node_path); + } + + if (is_wildcard_node) { + current_node = current_node->CreateWildcardChild(); + parent_node_prefix_len = i; + } + } +} + +} // namespace permission +} // namespace node diff --git a/src/permission/fs_permission.h b/src/permission/fs_permission.h new file mode 100644 index 00000000000000..f393c6a042e662 --- /dev/null +++ b/src/permission/fs_permission.h @@ -0,0 +1,152 @@ +#ifndef SRC_PERMISSION_FS_PERMISSION_H_ +#define SRC_PERMISSION_FS_PERMISSION_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" + +#include +#include +#include "permission/permission_base.h" +#include "util.h" + +namespace node { + +namespace permission { + +class FSPermission final : public PermissionBase { + public: + void Apply(const std::string& deny, PermissionScope scope) override; + bool is_granted(PermissionScope perm, const std::string_view& param) override; + + // For debugging purposes, use the gist function to print the whole tree + // https://gist.github.com/RafaelGSS/5b4f09c559a54f53f9b7c8c030744d19 + struct RadixTree { + struct Node { + std::string prefix; + std::unordered_map children; + Node* wildcard_child; + + explicit Node(const std::string& pre) + : prefix(pre), wildcard_child(nullptr) {} + + Node() : wildcard_child(nullptr) {} + + Node* CreateChild(std::string prefix) { + char label = prefix[0]; + + Node* child = children[label]; + if (child == nullptr) { + children[label] = new Node(prefix); + return children[label]; + } + + // swap prefix + unsigned int i = 0; + unsigned int prefix_len = prefix.length(); + for (; i < child->prefix.length(); ++i) { + if (i > prefix_len || prefix[i] != child->prefix[i]) { + std::string parent_prefix = child->prefix.substr(0, i); + std::string child_prefix = child->prefix.substr(i); + + child->prefix = child_prefix; + Node* split_child = new Node(parent_prefix); + split_child->children[child_prefix[0]] = child; + children[parent_prefix[0]] = split_child; + + return split_child->CreateChild(prefix.substr(i)); + } + } + return child->CreateChild(prefix.substr(i)); + } + + Node* CreateWildcardChild() { + if (wildcard_child != nullptr) { + return wildcard_child; + } + wildcard_child = new Node(); + return wildcard_child; + } + + Node* NextNode(const std::string& path, unsigned int idx) { + if (idx >= path.length()) { + return nullptr; + } + + auto it = children.find(path[idx]); + if (it == children.end()) { + return nullptr; + } + auto child = it->second; + // match prefix + unsigned int prefix_len = child->prefix.length(); + for (unsigned int i = 0; i < path.length(); ++i) { + if (i >= prefix_len || child->prefix[i] == '*') { + return child; + } + + // Handle optional trailing + // path = /home/subdirectory + // child = subdirectory/* + if (idx >= path.length() && + child->prefix[i] == node::kPathSeparator) { + continue; + } + + if (path[idx++] != child->prefix[i]) { + return nullptr; + } + } + return child; + } + + // A node can be a *end* node and have children + // E.g: */slower*, */slown* are inserted: + // /slow + // ---> er + // ---> n + // If */slow* is inserted right after, it will create an + // empty node + // /slow + // ---> '\000' ASCII (0) || \0 + // ---> er + // ---> n + bool IsEndNode() { + if (children.size() == 0) { + return true; + } + return children['\0'] != nullptr; + } + }; + + RadixTree(); + ~RadixTree(); + void Insert(const std::string& s); + bool Lookup(const std::string_view& s) { return Lookup(s, false); } + bool Lookup(const std::string_view& s, bool when_empty_return); + + private: + Node* root_node_; + }; + + private: + void GrantAccess(PermissionScope scope, const std::string& param); + void RestrictAccess(PermissionScope scope, + const std::vector& params); + // fs granted on startup + RadixTree granted_in_fs_; + RadixTree granted_out_fs_; + + bool deny_all_in_ = true; + bool deny_all_out_ = true; + + bool allow_all_in_ = false; + bool allow_all_out_ = false; +}; + +} // namespace permission + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#endif // SRC_PERMISSION_FS_PERMISSION_H_