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