/src/rocksdb/env/env_chroot.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2016-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | |
6 | | #if !defined(OS_WIN) |
7 | | |
8 | | #include "env/env_chroot.h" |
9 | | |
10 | | #include <unistd.h> // geteuid |
11 | | |
12 | | #include <cerrno> // errno |
13 | | #include <cstdlib> // realpath, free |
14 | | |
15 | | #include "env/composite_env_wrapper.h" |
16 | | #include "env/fs_remap.h" |
17 | | #include "rocksdb/utilities/options_type.h" |
18 | | #include "util/string_util.h" // errnoStr |
19 | | |
20 | | namespace ROCKSDB_NAMESPACE { |
21 | | namespace { |
22 | | static std::unordered_map<std::string, OptionTypeInfo> chroot_fs_type_info = { |
23 | | {"chroot_dir", {0, OptionType::kString}}}; |
24 | | } // namespace |
25 | | ChrootFileSystem::ChrootFileSystem(const std::shared_ptr<FileSystem>& base, |
26 | | const std::string& chroot_dir) |
27 | 0 | : RemapFileSystem(base), chroot_dir_(chroot_dir) { |
28 | 0 | RegisterOptions("chroot_dir", &chroot_dir_, &chroot_fs_type_info); |
29 | 0 | } |
30 | | |
31 | 0 | Status ChrootFileSystem::PrepareOptions(const ConfigOptions& options) { |
32 | 0 | Status s = FileSystemWrapper::PrepareOptions(options); |
33 | 0 | if (!s.ok()) { |
34 | 0 | return s; |
35 | 0 | } else if (chroot_dir_.empty()) { |
36 | 0 | s = Status::InvalidArgument("ChRootFileSystem requires a chroot dir"); |
37 | 0 | } else { |
38 | 0 | s = target_->FileExists(chroot_dir_, IOOptions(), nullptr); |
39 | 0 | } |
40 | 0 | if (s.ok()) { |
41 | | #if defined(OS_AIX) |
42 | | char resolvedName[PATH_MAX]; |
43 | | char* real_chroot_dir = realpath(chroot_dir_.c_str(), resolvedName); |
44 | | #else |
45 | 0 | char* real_chroot_dir = realpath(chroot_dir_.c_str(), nullptr); |
46 | 0 | #endif |
47 | | // chroot_dir must exist so realpath() returns non-nullptr. |
48 | 0 | assert(real_chroot_dir != nullptr); |
49 | 0 | chroot_dir_ = real_chroot_dir; |
50 | 0 | #if !defined(OS_AIX) |
51 | 0 | free(real_chroot_dir); |
52 | 0 | #endif |
53 | 0 | } |
54 | 0 | return s; |
55 | 0 | } |
56 | | |
57 | | IOStatus ChrootFileSystem::GetTestDirectory(const IOOptions& options, |
58 | | std::string* path, |
59 | 0 | IODebugContext* dbg) { |
60 | | // Adapted from PosixEnv's implementation since it doesn't provide a way to |
61 | | // create directory in the chroot. |
62 | 0 | char buf[256]; |
63 | 0 | snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid())); |
64 | 0 | *path = buf; |
65 | | |
66 | | // Directory may already exist, so ignore return |
67 | 0 | return CreateDirIfMissing(*path, options, dbg); |
68 | 0 | } |
69 | | |
70 | | // Returns status and expanded absolute path including the chroot directory. |
71 | | // Checks whether the provided path breaks out of the chroot. If it returns |
72 | | // non-OK status, the returned path should not be used. |
73 | | std::pair<IOStatus, std::string> ChrootFileSystem::EncodePath( |
74 | 0 | const std::string& path) { |
75 | 0 | if (path.empty() || path[0] != '/') { |
76 | 0 | return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""}; |
77 | 0 | } |
78 | 0 | std::pair<IOStatus, std::string> res; |
79 | 0 | res.second = chroot_dir_ + path; |
80 | | #if defined(OS_AIX) |
81 | | char resolvedName[PATH_MAX]; |
82 | | char* normalized_path = realpath(res.second.c_str(), resolvedName); |
83 | | #else |
84 | 0 | char* normalized_path = realpath(res.second.c_str(), nullptr); |
85 | 0 | #endif |
86 | 0 | if (normalized_path == nullptr) { |
87 | 0 | res.first = IOStatus::NotFound(res.second, errnoStr(errno).c_str()); |
88 | 0 | } else if (strlen(normalized_path) < chroot_dir_.size() || |
89 | 0 | strncmp(normalized_path, chroot_dir_.c_str(), |
90 | 0 | chroot_dir_.size()) != 0) { |
91 | 0 | res.first = IOStatus::IOError(res.second, |
92 | 0 | "Attempted to access path outside chroot"); |
93 | 0 | } else { |
94 | 0 | res.first = IOStatus::OK(); |
95 | 0 | } |
96 | 0 | #if !defined(OS_AIX) |
97 | 0 | free(normalized_path); |
98 | 0 | #endif |
99 | 0 | return res; |
100 | 0 | } |
101 | | |
102 | | // Similar to EncodePath() except assumes the basename in the path hasn't been |
103 | | // created yet. |
104 | | std::pair<IOStatus, std::string> ChrootFileSystem::EncodePathWithNewBasename( |
105 | 0 | const std::string& path) { |
106 | 0 | if (path.empty() || path[0] != '/') { |
107 | 0 | return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""}; |
108 | 0 | } |
109 | | // Basename may be followed by trailing slashes |
110 | 0 | size_t final_idx = path.find_last_not_of('/'); |
111 | 0 | if (final_idx == std::string::npos) { |
112 | | // It's only slashes so no basename to extract |
113 | 0 | return EncodePath(path); |
114 | 0 | } |
115 | | |
116 | | // Pull off the basename temporarily since realname(3) (used by |
117 | | // EncodePath()) requires a path that exists |
118 | 0 | size_t base_sep = path.rfind('/', final_idx); |
119 | 0 | auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1)); |
120 | 0 | status_and_enc_path.second.append(path.substr(base_sep + 1)); |
121 | 0 | return status_and_enc_path; |
122 | 0 | } |
123 | | |
124 | | std::shared_ptr<FileSystem> NewChrootFileSystem( |
125 | 0 | const std::shared_ptr<FileSystem>& base, const std::string& chroot_dir) { |
126 | 0 | auto chroot_fs = std::make_shared<ChrootFileSystem>(base, chroot_dir); |
127 | 0 | Status s = chroot_fs->PrepareOptions(ConfigOptions()); |
128 | 0 | if (s.ok()) { |
129 | 0 | return chroot_fs; |
130 | 0 | } else { |
131 | 0 | return nullptr; |
132 | 0 | } |
133 | 0 | } |
134 | | |
135 | 0 | Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) { |
136 | 0 | if (!base_env->FileExists(chroot_dir).ok()) { |
137 | 0 | return nullptr; |
138 | 0 | } |
139 | 0 | auto chroot_fs = NewChrootFileSystem(base_env->GetFileSystem(), chroot_dir); |
140 | 0 | if (chroot_fs != nullptr) { |
141 | 0 | return new CompositeEnvWrapper(base_env, chroot_fs); |
142 | 0 | } else { |
143 | 0 | return nullptr; |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | } // namespace ROCKSDB_NAMESPACE |
148 | | |
149 | | #endif // !defined(OS_WIN) |