Coverage Report

Created: 2025-12-30 08:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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