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