Skip to content

Commit 3badc11

Browse files
RafaelGSSCeres6
authored andcommitted
lib,src,permission: port path.resolve to C++
Co-Authored-By: Carlos Espa <cespatorres@gmail.com> PR-URL: #50758 Refs: nodejs/security-wg#898 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Claudio Wunder <cwunder@gnome.org>
1 parent b616f6f commit 3badc11

18 files changed

+416
-21
lines changed

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
'src/node_watchdog.cc',
143143
'src/node_worker.cc',
144144
'src/node_zlib.cc',
145+
'src/path.cc',
145146
'src/permission/child_process_permission.cc',
146147
'src/permission/fs_permission.cc',
147148
'src/permission/inspector_permission.cc',
@@ -262,6 +263,7 @@
262263
'src/node_wasi.h',
263264
'src/node_watchdog.h',
264265
'src/node_worker.h',
266+
'src/path.h',
265267
'src/permission/child_process_permission.h',
266268
'src/permission/fs_permission.h',
267269
'src/permission/inspector_permission.h',

src/env.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -898,21 +898,25 @@ Environment::Environment(IsolateData* isolate_data,
898898
options_->allow_native_addons = false;
899899
}
900900
flags_ = flags_ | EnvironmentFlags::kNoCreateInspector;
901-
permission()->Apply({"*"}, permission::PermissionScope::kInspector);
901+
permission()->Apply(this, {"*"}, permission::PermissionScope::kInspector);
902902
if (!options_->allow_child_process) {
903-
permission()->Apply({"*"}, permission::PermissionScope::kChildProcess);
903+
permission()->Apply(
904+
this, {"*"}, permission::PermissionScope::kChildProcess);
904905
}
905906
if (!options_->allow_worker_threads) {
906-
permission()->Apply({"*"}, permission::PermissionScope::kWorkerThreads);
907+
permission()->Apply(
908+
this, {"*"}, permission::PermissionScope::kWorkerThreads);
907909
}
908910

909911
if (!options_->allow_fs_read.empty()) {
910-
permission()->Apply(options_->allow_fs_read,
912+
permission()->Apply(this,
913+
options_->allow_fs_read,
911914
permission::PermissionScope::kFileSystemRead);
912915
}
913916

914917
if (!options_->allow_fs_write.empty()) {
915-
permission()->Apply(options_->allow_fs_write,
918+
permission()->Apply(this,
919+
options_->allow_fs_write,
916920
permission::PermissionScope::kFileSystemWrite);
917921
}
918922
}

src/path.cc

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#include "path.h"
2+
#include <string>
3+
#include <vector>
4+
#include "env-inl.h"
5+
#include "node_internals.h"
6+
#include "util.h"
7+
8+
namespace node {
9+
10+
#ifdef _WIN32
11+
bool IsPathSeparator(const char c) noexcept {
12+
return c == kPathSeparator || c == '/';
13+
}
14+
#else // POSIX
15+
bool IsPathSeparator(const char c) noexcept {
16+
return c == kPathSeparator;
17+
}
18+
#endif // _WIN32
19+
20+
std::string NormalizeString(const std::string_view path,
21+
bool allowAboveRoot,
22+
const std::string_view separator) {
23+
std::string res;
24+
int lastSegmentLength = 0;
25+
int lastSlash = -1;
26+
int dots = 0;
27+
char code;
28+
const auto pathLen = path.size();
29+
for (uint8_t i = 0; i <= pathLen; ++i) {
30+
if (i < pathLen) {
31+
code = path[i];
32+
} else if (IsPathSeparator(path[i])) {
33+
break;
34+
} else {
35+
code = node::kPathSeparator;
36+
}
37+
38+
if (IsPathSeparator(code)) {
39+
if (lastSlash == static_cast<int>(i - 1) || dots == 1) {
40+
// NOOP
41+
} else if (dots == 2) {
42+
int len = res.length();
43+
if (len < 2 || lastSegmentLength != 2 || res[len - 1] != '.' ||
44+
res[len - 2] != '.') {
45+
if (len > 2) {
46+
auto lastSlashIndex = res.find_last_of(separator);
47+
if (lastSlashIndex == std::string::npos) {
48+
res = "";
49+
lastSegmentLength = 0;
50+
} else {
51+
res = res.substr(0, lastSlashIndex);
52+
len = res.length();
53+
lastSegmentLength = len - 1 - res.find_last_of(separator);
54+
}
55+
lastSlash = i;
56+
dots = 0;
57+
continue;
58+
} else if (len != 0) {
59+
res = "";
60+
lastSegmentLength = 0;
61+
lastSlash = i;
62+
dots = 0;
63+
continue;
64+
}
65+
}
66+
67+
if (allowAboveRoot) {
68+
res += res.length() > 0 ? std::string(separator) + ".." : "..";
69+
lastSegmentLength = 2;
70+
}
71+
} else {
72+
if (!res.empty()) {
73+
res += std::string(separator) +
74+
std::string(path.substr(lastSlash + 1, i - (lastSlash + 1)));
75+
} else {
76+
res = path.substr(lastSlash + 1, i - (lastSlash + 1));
77+
}
78+
lastSegmentLength = i - lastSlash - 1;
79+
}
80+
lastSlash = i;
81+
dots = 0;
82+
} else if (code == '.' && dots != -1) {
83+
++dots;
84+
} else {
85+
dots = -1;
86+
}
87+
}
88+
89+
return res;
90+
}
91+
92+
#ifdef _WIN32
93+
bool IsWindowsDeviceRoot(const char c) noexcept {
94+
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
95+
}
96+
97+
std::string PathResolve(Environment* env,
98+
const std::vector<std::string_view>& paths) {
99+
std::string resolvedDevice = "";
100+
std::string resolvedTail = "";
101+
bool resolvedAbsolute = false;
102+
const size_t numArgs = paths.size();
103+
auto cwd = env->GetCwd(env->exec_path());
104+
105+
for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) {
106+
std::string path;
107+
if (i >= 0) {
108+
path = std::string(paths[i]);
109+
} else if (resolvedDevice.empty()) {
110+
path = cwd;
111+
} else {
112+
// Windows has the concept of drive-specific current working
113+
// directories. If we've resolved a drive letter but not yet an
114+
// absolute path, get cwd for that drive, or the process cwd if
115+
// the drive cwd is not available. We're sure the device is not
116+
// a UNC path at this points, because UNC paths are always absolute.
117+
std::string resolvedDevicePath;
118+
const std::string envvar = "=" + resolvedDevice;
119+
credentials::SafeGetenv(envvar.c_str(), &resolvedDevicePath);
120+
path = resolvedDevicePath.empty() ? cwd : resolvedDevicePath;
121+
122+
// Verify that a cwd was found and that it actually points
123+
// to our drive. If not, default to the drive's root.
124+
if (path.empty() ||
125+
(ToLower(path.substr(0, 2)) != ToLower(resolvedDevice) &&
126+
path[2] == '/')) {
127+
path = resolvedDevice + "\\";
128+
}
129+
}
130+
131+
const size_t len = path.length();
132+
int rootEnd = 0;
133+
std::string device = "";
134+
bool isAbsolute = false;
135+
const char code = path[0];
136+
137+
// Try to match a root
138+
if (len == 1) {
139+
if (IsPathSeparator(code)) {
140+
// `path` contains just a path separator
141+
rootEnd = 1;
142+
isAbsolute = true;
143+
}
144+
} else if (IsPathSeparator(code)) {
145+
// Possible UNC root
146+
147+
// If we started with a separator, we know we at least have an
148+
// absolute path of some kind (UNC or otherwise)
149+
isAbsolute = true;
150+
151+
if (IsPathSeparator(path[1])) {
152+
// Matched double path separator at beginning
153+
size_t j = 2;
154+
size_t last = j;
155+
// Match 1 or more non-path separators
156+
while (j < len && !IsPathSeparator(path[j])) {
157+
j++;
158+
}
159+
if (j < len && j != last) {
160+
const std::string firstPart = path.substr(last, j - last);
161+
// Matched!
162+
last = j;
163+
// Match 1 or more path separators
164+
while (j < len && IsPathSeparator(path[j])) {
165+
j++;
166+
}
167+
if (j < len && j != last) {
168+
// Matched!
169+
last = j;
170+
// Match 1 or more non-path separators
171+
while (j < len && !IsPathSeparator(path[j])) {
172+
j++;
173+
}
174+
if (j == len || j != last) {
175+
// We matched a UNC root
176+
device = "\\\\" + firstPart + "\\" + path.substr(last, j - last);
177+
rootEnd = j;
178+
}
179+
}
180+
}
181+
}
182+
} else if (IsWindowsDeviceRoot(code) && path[1] == ':') {
183+
// Possible device root
184+
device = path.substr(0, 2);
185+
rootEnd = 2;
186+
if (len > 2 && IsPathSeparator(path[2])) {
187+
// Treat separator following drive name as an absolute path
188+
// indicator
189+
isAbsolute = true;
190+
rootEnd = 3;
191+
}
192+
}
193+
194+
if (!device.empty()) {
195+
if (!resolvedDevice.empty()) {
196+
if (ToLower(device) != ToLower(resolvedDevice)) {
197+
// This path points to another device so it is not applicable
198+
continue;
199+
}
200+
} else {
201+
resolvedDevice = device;
202+
}
203+
}
204+
205+
if (resolvedAbsolute) {
206+
if (!resolvedDevice.empty()) {
207+
break;
208+
}
209+
} else {
210+
resolvedTail = path.substr(rootEnd) + "\\" + resolvedTail;
211+
resolvedAbsolute = isAbsolute;
212+
if (isAbsolute && !resolvedDevice.empty()) {
213+
break;
214+
}
215+
}
216+
}
217+
218+
// At this point the path should be resolved to a full absolute path,
219+
// but handle relative paths to be safe (might happen when process.cwd()
220+
// fails)
221+
222+
// Normalize the tail path
223+
resolvedTail = NormalizeString(resolvedTail, !resolvedAbsolute, "\\");
224+
225+
if (resolvedAbsolute) {
226+
return resolvedDevice + "\\" + resolvedTail;
227+
}
228+
229+
if (!resolvedDevice.empty() || !resolvedTail.empty()) {
230+
return resolvedDevice + resolvedTail;
231+
}
232+
233+
return ".";
234+
}
235+
#else // _WIN32
236+
std::string PathResolve(Environment* env,
237+
const std::vector<std::string_view>& paths) {
238+
std::string resolvedPath;
239+
bool resolvedAbsolute = false;
240+
auto cwd = env->GetCwd(env->exec_path());
241+
const size_t numArgs = paths.size();
242+
243+
for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) {
244+
const std::string& path =
245+
(i >= 0) ? std::string(paths[i]) : env->GetCwd(env->exec_path());
246+
247+
if (!path.empty()) {
248+
resolvedPath = std::string(path) + "/" + resolvedPath;
249+
250+
if (path.front() == '/') {
251+
resolvedAbsolute = true;
252+
break;
253+
}
254+
}
255+
}
256+
257+
// Normalize the path
258+
auto normalizedPath = NormalizeString(resolvedPath, !resolvedAbsolute, "/");
259+
260+
if (resolvedAbsolute) {
261+
return "/" + normalizedPath;
262+
}
263+
264+
if (normalizedPath.empty()) {
265+
return ".";
266+
}
267+
268+
return normalizedPath;
269+
}
270+
#endif // _WIN32
271+
272+
} // namespace node

src/path.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef SRC_PATH_H_
2+
#define SRC_PATH_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include <string>
7+
#include <vector>
8+
9+
namespace node {
10+
11+
class Environment;
12+
13+
bool IsPathSeparator(const char c) noexcept;
14+
15+
std::string NormalizeString(const std::string_view path,
16+
bool allowAboveRoot,
17+
const std::string_view separator);
18+
19+
std::string PathResolve(Environment* env,
20+
const std::vector<std::string_view>& args);
21+
} // namespace node
22+
23+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
24+
25+
#endif // SRC_PATH_H_

src/permission/child_process_permission.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ namespace permission {
99

1010
// Currently, ChildProcess manage a single state
1111
// Once denied, it's always denied
12-
void ChildProcessPermission::Apply(const std::vector<std::string>& allow,
12+
void ChildProcessPermission::Apply(Environment* env,
13+
const std::vector<std::string>& allow,
1314
PermissionScope scope) {
1415
deny_all_ = true;
1516
}

src/permission/child_process_permission.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ namespace permission {
1212

1313
class ChildProcessPermission final : public PermissionBase {
1414
public:
15-
void Apply(const std::vector<std::string>& allow,
15+
void Apply(Environment* env,
16+
const std::vector<std::string>& allow,
1617
PermissionScope scope) override;
1718
bool is_granted(PermissionScope perm,
1819
const std::string_view& param = "") const override;

src/permission/fs_permission.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "fs_permission.h"
22
#include "base_object-inl.h"
33
#include "debug_utils-inl.h"
4-
#include "util.h"
4+
#include "path.h"
55
#include "v8.h"
66

77
#include <fcntl.h>
@@ -117,7 +117,8 @@ namespace permission {
117117

118118
// allow = '*'
119119
// allow = '/tmp/,/home/example.js'
120-
void FSPermission::Apply(const std::vector<std::string>& allow,
120+
void FSPermission::Apply(Environment* env,
121+
const std::vector<std::string>& allow,
121122
PermissionScope scope) {
122123
for (const std::string& res : allow) {
123124
if (res == "*") {
@@ -130,7 +131,7 @@ void FSPermission::Apply(const std::vector<std::string>& allow,
130131
}
131132
return;
132133
}
133-
GrantAccess(scope, res);
134+
GrantAccess(scope, PathResolve(env, {res}));
134135
}
135136
}
136137

0 commit comments

Comments
 (0)