Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib,src,permission: port path.resolve to C++ #50758

Merged
merged 1 commit into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
'src/node_watchdog.cc',
'src/node_worker.cc',
'src/node_zlib.cc',
'src/path.cc',
'src/permission/child_process_permission.cc',
'src/permission/fs_permission.cc',
'src/permission/inspector_permission.cc',
Expand Down Expand Up @@ -264,6 +265,7 @@
'src/node_wasi.h',
'src/node_watchdog.h',
'src/node_worker.h',
'src/path.h',
'src/permission/child_process_permission.h',
'src/permission/fs_permission.h',
'src/permission/inspector_permission.h',
Expand Down
14 changes: 9 additions & 5 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -898,21 +898,25 @@ Environment::Environment(IsolateData* isolate_data,
options_->allow_native_addons = false;
}
flags_ = flags_ | EnvironmentFlags::kNoCreateInspector;
permission()->Apply({"*"}, permission::PermissionScope::kInspector);
permission()->Apply(this, {"*"}, permission::PermissionScope::kInspector);
if (!options_->allow_child_process) {
permission()->Apply({"*"}, permission::PermissionScope::kChildProcess);
permission()->Apply(
this, {"*"}, permission::PermissionScope::kChildProcess);
}
if (!options_->allow_worker_threads) {
permission()->Apply({"*"}, permission::PermissionScope::kWorkerThreads);
permission()->Apply(
this, {"*"}, permission::PermissionScope::kWorkerThreads);
}

if (!options_->allow_fs_read.empty()) {
permission()->Apply(options_->allow_fs_read,
permission()->Apply(this,
options_->allow_fs_read,
permission::PermissionScope::kFileSystemRead);
}

if (!options_->allow_fs_write.empty()) {
permission()->Apply(options_->allow_fs_write,
permission()->Apply(this,
options_->allow_fs_write,
permission::PermissionScope::kFileSystemWrite);
}
}
Expand Down
272 changes: 272 additions & 0 deletions src/path.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
#include "path.h"
#include <string>
#include <vector>
#include "env-inl.h"
#include "node_internals.h"
#include "util.h"

namespace node {

#ifdef _WIN32
bool IsPathSeparator(const char c) noexcept {
return c == kPathSeparator || c == '/';
}
#else // POSIX
bool IsPathSeparator(const char c) noexcept {
return c == kPathSeparator;
}
#endif // _WIN32

std::string NormalizeString(const std::string_view path,
bool allowAboveRoot,
const std::string_view separator) {
std::string res;
int lastSegmentLength = 0;
int lastSlash = -1;
int dots = 0;
char code;
const auto pathLen = path.size();
for (uint8_t i = 0; i <= pathLen; ++i) {
if (i < pathLen) {
code = path[i];
} else if (IsPathSeparator(path[i])) {
break;
} else {
code = node::kPathSeparator;
}

if (IsPathSeparator(code)) {
if (lastSlash == static_cast<int>(i - 1) || dots == 1) {
// NOOP
} else if (dots == 2) {
int len = res.length();
if (len < 2 || lastSegmentLength != 2 || res[len - 1] != '.' ||
res[len - 2] != '.') {
if (len > 2) {
auto lastSlashIndex = res.find_last_of(separator);
if (lastSlashIndex == std::string::npos) {
res = "";
lastSegmentLength = 0;
} else {
res = res.substr(0, lastSlashIndex);
len = res.length();
lastSegmentLength = len - 1 - res.find_last_of(separator);
}
lastSlash = i;
dots = 0;
continue;
} else if (len != 0) {
res = "";
lastSegmentLength = 0;
lastSlash = i;
dots = 0;
continue;
}
}

if (allowAboveRoot) {
res += res.length() > 0 ? std::string(separator) + ".." : "..";
lastSegmentLength = 2;
}
} else {
if (!res.empty()) {
res += std::string(separator) +
std::string(path.substr(lastSlash + 1, i - (lastSlash + 1)));
} else {
res = path.substr(lastSlash + 1, i - (lastSlash + 1));
}
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i;
dots = 0;
} else if (code == '.' && dots != -1) {
++dots;
} else {
dots = -1;
}
}

return res;
}

#ifdef _WIN32
bool IsWindowsDeviceRoot(const char c) noexcept {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

std::string PathResolve(Environment* env,
const std::vector<std::string_view>& paths) {
std::string resolvedDevice = "";
std::string resolvedTail = "";
bool resolvedAbsolute = false;
const size_t numArgs = paths.size();
auto cwd = env->GetCwd(env->exec_path());

for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) {
std::string path;
if (i >= 0) {
path = std::string(paths[i]);
} else if (resolvedDevice.empty()) {
path = cwd;
} else {
// Windows has the concept of drive-specific current working
// directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive, or the process cwd if
// the drive cwd is not available. We're sure the device is not
// a UNC path at this points, because UNC paths are always absolute.
std::string resolvedDevicePath;
const std::string envvar = "=" + resolvedDevice;
credentials::SafeGetenv(envvar.c_str(), &resolvedDevicePath);
path = resolvedDevicePath.empty() ? cwd : resolvedDevicePath;

// Verify that a cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (path.empty() ||
(ToLower(path.substr(0, 2)) != ToLower(resolvedDevice) &&
path[2] == '/')) {
path = resolvedDevice + "\\";
}
}

const size_t len = path.length();
int rootEnd = 0;
std::string device = "";
bool isAbsolute = false;
const char code = path[0];

// Try to match a root
if (len == 1) {
if (IsPathSeparator(code)) {
// `path` contains just a path separator
rootEnd = 1;
isAbsolute = true;
}
} else if (IsPathSeparator(code)) {
// Possible UNC root

// If we started with a separator, we know we at least have an
// absolute path of some kind (UNC or otherwise)
isAbsolute = true;

if (IsPathSeparator(path[1])) {
// Matched double path separator at beginning
size_t j = 2;
size_t last = j;
// Match 1 or more non-path separators
while (j < len && !IsPathSeparator(path[j])) {
j++;
}
if (j < len && j != last) {
const std::string firstPart = path.substr(last, j - last);
// Matched!
last = j;
// Match 1 or more path separators
while (j < len && IsPathSeparator(path[j])) {
j++;
}
if (j < len && j != last) {
// Matched!
last = j;
// Match 1 or more non-path separators
while (j < len && !IsPathSeparator(path[j])) {
j++;
}
if (j == len || j != last) {
// We matched a UNC root
device = "\\\\" + firstPart + "\\" + path.substr(last, j - last);
rootEnd = j;
}
}
}
}
} else if (IsWindowsDeviceRoot(code) && path[1] == ':') {
// Possible device root
device = path.substr(0, 2);
rootEnd = 2;
if (len > 2 && IsPathSeparator(path[2])) {
// Treat separator following drive name as an absolute path
// indicator
isAbsolute = true;
rootEnd = 3;
}
}

if (!device.empty()) {
if (!resolvedDevice.empty()) {
if (ToLower(device) != ToLower(resolvedDevice)) {
// This path points to another device so it is not applicable
continue;
}
} else {
resolvedDevice = device;
}
}

if (resolvedAbsolute) {
if (!resolvedDevice.empty()) {
break;
}
} else {
resolvedTail = path.substr(rootEnd) + "\\" + resolvedTail;
resolvedAbsolute = isAbsolute;
if (isAbsolute && !resolvedDevice.empty()) {
break;
}
}
}

// At this point the path should be resolved to a full absolute path,
// but handle relative paths to be safe (might happen when process.cwd()
// fails)

// Normalize the tail path
resolvedTail = NormalizeString(resolvedTail, !resolvedAbsolute, "\\");

if (resolvedAbsolute) {
return resolvedDevice + "\\" + resolvedTail;
}

if (!resolvedDevice.empty() || !resolvedTail.empty()) {
return resolvedDevice + resolvedTail;
}

return ".";
}
#else // _WIN32
std::string PathResolve(Environment* env,
const std::vector<std::string_view>& paths) {
std::string resolvedPath;
bool resolvedAbsolute = false;
auto cwd = env->GetCwd(env->exec_path());
const size_t numArgs = paths.size();

for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) {
const std::string& path =
(i >= 0) ? std::string(paths[i]) : env->GetCwd(env->exec_path());

if (!path.empty()) {
resolvedPath = std::string(path) + "/" + resolvedPath;

if (path.front() == '/') {
resolvedAbsolute = true;
break;
}
}
}

// Normalize the path
auto normalizedPath = NormalizeString(resolvedPath, !resolvedAbsolute, "/");

if (resolvedAbsolute) {
return "/" + normalizedPath;
}

if (normalizedPath.empty()) {
return ".";
}

return normalizedPath;
}
#endif // _WIN32

} // namespace node
25 changes: 25 additions & 0 deletions src/path.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef SRC_PATH_H_
#define SRC_PATH_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <string>
#include <vector>

namespace node {

class Environment;

bool IsPathSeparator(const char c) noexcept;

std::string NormalizeString(const std::string_view path,
bool allowAboveRoot,
const std::string_view separator);

std::string PathResolve(Environment* env,
const std::vector<std::string_view>& args);
} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_PATH_H_
3 changes: 2 additions & 1 deletion src/permission/child_process_permission.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ namespace permission {

// Currently, ChildProcess manage a single state
// Once denied, it's always denied
void ChildProcessPermission::Apply(const std::vector<std::string>& allow,
void ChildProcessPermission::Apply(Environment* env,
const std::vector<std::string>& allow,
PermissionScope scope) {
deny_all_ = true;
}
Expand Down
3 changes: 2 additions & 1 deletion src/permission/child_process_permission.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace permission {

class ChildProcessPermission final : public PermissionBase {
public:
void Apply(const std::vector<std::string>& allow,
void Apply(Environment* env,
const std::vector<std::string>& allow,
PermissionScope scope) override;
bool is_granted(PermissionScope perm,
const std::string_view& param = "") const override;
Expand Down
7 changes: 4 additions & 3 deletions src/permission/fs_permission.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "fs_permission.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "util.h"
#include "path.h"
#include "v8.h"

#include <fcntl.h>
Expand Down Expand Up @@ -117,7 +117,8 @@ namespace permission {

// allow = '*'
// allow = '/tmp/,/home/example.js'
void FSPermission::Apply(const std::vector<std::string>& allow,
void FSPermission::Apply(Environment* env,
const std::vector<std::string>& allow,
PermissionScope scope) {
for (const std::string& res : allow) {
if (res == "*") {
Expand All @@ -130,7 +131,7 @@ void FSPermission::Apply(const std::vector<std::string>& allow,
}
return;
}
GrantAccess(scope, res);
GrantAccess(scope, PathResolve(env, {res}));
}
}

Expand Down
Loading
Loading