1 #include "fs_permission.h"
2 #include "base_object-inl.h"
3 #include "debug_utils-inl.h"
4 #include "util.h"
5 #include "v8.h"
6
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <stdlib.h>
10 #include <algorithm>
11 #include <filesystem>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15
16 namespace {
17
18 std::string WildcardIfDir(const std::string& res) noexcept {
19 uv_fs_t req;
20 int rc = uv_fs_stat(nullptr, &req, res.c_str(), nullptr);
21 if (rc == 0) {
22 const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
23 if (s->st_mode & S_IFDIR) {
24 // add wildcard when directory
25 if (res.back() == node::kPathSeparator) {
26 return res + "*";
27 }
28 return res + node::kPathSeparator + "*";
29 }
30 }
31 uv_fs_req_cleanup(&req);
32 return res;
33 }
34
FreeRecursivelyNode( node::permission::FSPermission::RadixTree::Node* node)35 void FreeRecursivelyNode(
36 node::permission::FSPermission::RadixTree::Node* node) {
37 if (node == nullptr) {
38 return;
39 }
40
41 if (node->children.size()) {
42 for (auto& c : node->children) {
43 FreeRecursivelyNode(c.second);
44 }
45 }
46
47 if (node->wildcard_child != nullptr) {
48 delete node->wildcard_child;
49 }
50 delete node;
51 }
52
is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree, const std::string_view& param)53 bool is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree,
54 const std::string_view& param) {
55 #ifdef _WIN32
56 // is UNC file path
57 if (param.rfind("\\\\", 0) == 0) {
58 // return lookup with normalized param
59 int starting_pos = 4; // "\\?\"
60 if (param.rfind("\\\\?\\UNC\\") == 0) {
61 starting_pos += 4; // "UNC\"
62 }
63 auto normalized = param.substr(starting_pos);
64 return granted_tree->Lookup(normalized, true);
65 }
66 #endif
67 return granted_tree->Lookup(param, true);
68 }
69
70 } // namespace
71
72 namespace node {
73
74 namespace permission {
75
PrintTree(const FSPermission::RadixTree::Node* node, size_t spaces = 0)76 void PrintTree(const FSPermission::RadixTree::Node* node, size_t spaces = 0) {
77 std::string whitespace(spaces, ' ');
78
79 if (node == nullptr) {
80 return;
81 }
82 if (node->wildcard_child != nullptr) {
83 per_process::Debug(DebugCategory::PERMISSION_MODEL,
84 "%s Wildcard: %s\n",
85 whitespace,
86 node->prefix);
87 } else {
88 per_process::Debug(DebugCategory::PERMISSION_MODEL,
89 "%s Prefix: %s\n",
90 whitespace,
91 node->prefix);
92 if (node->children.size()) {
93 size_t child = 0;
94 for (const auto& pair : node->children) {
95 ++child;
96 per_process::Debug(DebugCategory::PERMISSION_MODEL,
97 "%s Child(%s): %s\n",
98 whitespace,
99 child,
100 std::string(1, pair.first));
101 PrintTree(pair.second, spaces + 2);
102 }
103 per_process::Debug(DebugCategory::PERMISSION_MODEL,
104 "%s End of tree - child(%s)\n",
105 whitespace,
106 child);
107 } else {
108 per_process::Debug(DebugCategory::PERMISSION_MODEL,
109 "%s End of tree: %s\n",
110 whitespace,
111 node->prefix);
112 }
113 }
114 }
115
116 // allow = '*'
117 // allow = '/tmp/,/home/example.js'
Apply(const std::string& allow, PermissionScope scope)118 void FSPermission::Apply(const std::string& allow, PermissionScope scope) {
119 using std::string_view_literals::operator""sv;
120 for (const std::string_view res : SplitString(allow, ","sv)) {
121 if (res == "*"sv) {
122 if (scope == PermissionScope::kFileSystemRead) {
123 deny_all_in_ = false;
124 allow_all_in_ = true;
125 } else {
126 deny_all_out_ = false;
127 allow_all_out_ = true;
128 }
129 return;
130 }
131 GrantAccess(scope, std::string(res.data(), res.size()));
132 }
133 }
134
GrantAccess(PermissionScope perm, const std::string& res)135 void FSPermission::GrantAccess(PermissionScope perm, const std::string& res) {
136 const std::string path = WildcardIfDir(res);
137 if (perm == PermissionScope::kFileSystemRead) {
138 granted_in_fs_.Insert(path);
139 deny_all_in_ = false;
140 } else if (perm == PermissionScope::kFileSystemWrite) {
141 granted_out_fs_.Insert(path);
142 deny_all_out_ = false;
143 }
144 }
145
is_granted(PermissionScope perm, const std::string_view& param = �)146 bool FSPermission::is_granted(PermissionScope perm,
147 const std::string_view& param = "") {
148 switch (perm) {
149 case PermissionScope::kFileSystem:
150 return allow_all_in_ && allow_all_out_;
151 case PermissionScope::kFileSystemRead:
152 return !deny_all_in_ &&
153 ((param.empty() && allow_all_in_) || allow_all_in_ ||
154 is_tree_granted(&granted_in_fs_, param));
155 case PermissionScope::kFileSystemWrite:
156 return !deny_all_out_ &&
157 ((param.empty() && allow_all_out_) || allow_all_out_ ||
158 is_tree_granted(&granted_out_fs_, param));
159 default:
160 return false;
161 }
162 }
163
RadixTree()164 FSPermission::RadixTree::RadixTree() : root_node_(new Node("")) {}
165
~RadixTree()166 FSPermission::RadixTree::~RadixTree() {
167 FreeRecursivelyNode(root_node_);
168 }
169
Lookup(const std::string_view& s, bool when_empty_return = false)170 bool FSPermission::RadixTree::Lookup(const std::string_view& s,
171 bool when_empty_return = false) {
172 FSPermission::RadixTree::Node* current_node = root_node_;
173 if (current_node->children.size() == 0) {
174 return when_empty_return;
175 }
176
177 unsigned int parent_node_prefix_len = current_node->prefix.length();
178 const std::string path(s);
179 auto path_len = path.length();
180
181 while (true) {
182 if (parent_node_prefix_len == path_len && current_node->IsEndNode()) {
183 return true;
184 }
185
186 auto node = current_node->NextNode(path, parent_node_prefix_len);
187 if (node == nullptr) {
188 return false;
189 }
190
191 current_node = node;
192 parent_node_prefix_len += current_node->prefix.length();
193 if (current_node->wildcard_child != nullptr &&
194 path_len >= (parent_node_prefix_len - 2 /* slash* */)) {
195 return true;
196 }
197 }
198 }
199
Insert(const std::string& path)200 void FSPermission::RadixTree::Insert(const std::string& path) {
201 FSPermission::RadixTree::Node* current_node = root_node_;
202
203 unsigned int parent_node_prefix_len = current_node->prefix.length();
204 int path_len = path.length();
205
206 for (int i = 1; i <= path_len; ++i) {
207 bool is_wildcard_node = path[i - 1] == '*';
208 bool is_last_char = i == path_len;
209
210 if (is_wildcard_node || is_last_char) {
211 std::string node_path = path.substr(parent_node_prefix_len, i);
212 current_node = current_node->CreateChild(node_path);
213 }
214
215 if (is_wildcard_node) {
216 current_node = current_node->CreateWildcardChild();
217 parent_node_prefix_len = i;
218 }
219 }
220
221 if (UNLIKELY(per_process::enabled_debug_list.enabled(
222 DebugCategory::PERMISSION_MODEL))) {
223 per_process::Debug(DebugCategory::PERMISSION_MODEL, "Inserting %s\n", path);
224 PrintTree(root_node_);
225 }
226 }
227
228 } // namespace permission
229 } // namespace node
230