Skip to content

Commit

Permalink
src: avoid copying string in fs_permission
Browse files Browse the repository at this point in the history
PR-URL: nodejs#47746
Reviewed-By: Rafael Gonzaga <[email protected]>
Reviewed-By: Daeyeon Jeong <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: Deokjin Kim <[email protected]>
Reviewed-By: Darshan Sen <[email protected]>
  • Loading branch information
anonrig authored and MoLow committed Jul 6, 2023
1 parent 0927c67 commit 933673d
Show file tree
Hide file tree
Showing 2 changed files with 332 additions and 0 deletions.
180 changes: 180 additions & 0 deletions src/permission/fs_permission.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#include "fs_permission.h"
#include "base_object-inl.h"
#include "util.h"
#include "v8.h"

#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <algorithm>
#include <filesystem>
#include <string>
#include <vector>

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<const uv_stat_t*>(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
152 changes: 152 additions & 0 deletions src/permission/fs_permission.h
Original file line number Diff line number Diff line change
@@ -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 <unordered_map>
#include <vector>
#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<char, Node*> 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<std::string>& 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_

0 comments on commit 933673d

Please sign in to comment.