-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPathValidator.cpp
More file actions
133 lines (117 loc) · 4.47 KB
/
Copy pathPathValidator.cpp
File metadata and controls
133 lines (117 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//===-- PathValidator.cpp - Path sandboxing ---------------------*- C++ -*-===//
//
// Part of the ZBC semihosting monorepo. MIT licensed (see LICENSE).
//
//===----------------------------------------------------------------------===//
#include "zbc/PathValidator.h"
#include <filesystem>
#include <system_error>
namespace fs = std::filesystem;
namespace zbc {
namespace {
/// Resolve "..", ".", and symlinks without requiring the path to exist
/// (so newly-created files validate). Returns empty on failure.
std::string resolveReal(const fs::path &P) {
std::error_code EC;
fs::path R = fs::weakly_canonical(P, EC);
if (EC)
return {};
return R.string();
}
} // namespace
PathValidator::PathValidator(PathValidatorConfig Config)
: Config(std::move(Config)) {
if (!Config.SandboxDir.empty()) {
std::string Resolved = resolveReal(Config.SandboxDir);
if (Resolved.empty()) {
// SECURITY: cannot resolve sandbox root -> deny everything.
reportViolation(ViolationType::SandboxEscape, Config.SandboxDir);
Config.SandboxDir.clear();
return;
}
// Store with a trailing separator for prefix matching.
if (Resolved.back() != static_cast<char>(fs::path::preferred_separator))
Resolved.push_back(static_cast<char>(fs::path::preferred_separator));
Config.SandboxDir = std::move(Resolved);
}
}
Result<std::string> PathValidator::validate(std::string_view Path,
bool ForWrite,
bool FollowLeafSymlink) const {
if (Path.find('\0') != std::string_view::npos) {
reportViolation(ViolationType::NullByte, Path);
return Result<std::string>::error("path contains null byte");
}
if (ForWrite && Config.ReadOnly) {
reportViolation(ViolationType::WriteProtected, Path);
return Result<std::string>::error("write denied (read-only mode)");
}
// Make relative paths relative to the sandbox root.
fs::path Requested(Path);
fs::path Combined;
if (Requested.is_relative() && !Config.SandboxDir.empty())
Combined = fs::path(Config.SandboxDir) / Requested;
else
Combined = Requested;
// For LSTAT / READLINK we must not resolve the final component (or
// weakly_canonical would silently leave us pointed at the symlink's
// target). Canonicalize the parent and rejoin the leaf -- that catches
// traversal through the parent while preserving the leaf-as-symlink
// semantics the opcode needs.
std::string Resolved;
if (!FollowLeafSymlink && Combined.has_parent_path() &&
Combined.has_filename()) {
std::error_code EC;
fs::path ParentResolved = fs::weakly_canonical(Combined.parent_path(), EC);
if (!EC)
Resolved = (ParentResolved / Combined.filename()).string();
} else {
Resolved = resolveReal(Combined);
}
if (Resolved.empty()) {
// SECURITY: an unresolvable path could hide a traversal/symlink escape.
reportViolation(ViolationType::SandboxEscape, Path);
return Result<std::string>::error("cannot verify path within sandbox");
}
if (!isAllowed(Resolved, ForWrite)) {
reportViolation(ForWrite ? ViolationType::WriteProtected
: ViolationType::NotAllowed,
Path);
return Result<std::string>::error("access denied");
}
return Resolved;
}
bool PathValidator::isAllowed(const std::string &ResolvedPath,
bool ForWrite) const {
if (!Config.SandboxDir.empty()) {
// SandboxDir has a trailing separator; allow the dir itself or anything
// beneath it.
std::string NoSep = Config.SandboxDir;
NoSep.pop_back();
if (ResolvedPath == NoSep ||
ResolvedPath.compare(0, Config.SandboxDir.size(),
Config.SandboxDir) == 0)
return true;
}
for (const auto &Rule : Config.AllowedPaths) {
const std::string &Prefix = Rule.first;
bool AllowWrite = Rule.second;
if (ResolvedPath.compare(0, Prefix.size(), Prefix) == 0) {
if (!ForWrite || AllowWrite)
return true;
}
}
return false;
}
void PathValidator::reportViolation(ViolationType Type,
std::string_view Path) const {
if (Config.OnViolation)
Config.OnViolation(Type, Path);
}
void PathValidator::addAllowedPath(std::string_view Prefix, bool AllowWrite) {
std::string Resolved = resolveReal(fs::path(Prefix));
if (Resolved.empty())
Resolved = std::string(Prefix);
Config.AllowedPaths.emplace_back(std::move(Resolved), AllowWrite);
}
} // namespace zbc