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(ROCKSDB_LITE) && !defined(OS_WIN)
7
8 #include "env/env_chroot.h"
9
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <unistd.h>
14
15 #include <string>
16 #include <utility>
17 #include <vector>
18
19 #include "rocksdb/status.h"
20
21 namespace ROCKSDB_NAMESPACE {
22
23 class ChrootEnv : public EnvWrapper {
24 public:
ChrootEnv(Env * base_env,const std::string & chroot_dir)25 ChrootEnv(Env* base_env, const std::string& chroot_dir)
26 : EnvWrapper(base_env) {
27 #if defined(OS_AIX)
28 char resolvedName[PATH_MAX];
29 char* real_chroot_dir = realpath(chroot_dir.c_str(), resolvedName);
30 #else
31 char* real_chroot_dir = realpath(chroot_dir.c_str(), nullptr);
32 #endif
33 // chroot_dir must exist so realpath() returns non-nullptr.
34 assert(real_chroot_dir != nullptr);
35 chroot_dir_ = real_chroot_dir;
36 #if !defined(OS_AIX)
37 free(real_chroot_dir);
38 #endif
39 }
40
RegisterDbPaths(const std::vector<std::string> & paths)41 Status RegisterDbPaths(const std::vector<std::string>& paths) override {
42 std::vector<std::string> encoded_paths;
43 encoded_paths.reserve(paths.size());
44 for (auto& path : paths) {
45 auto status_and_enc_path = EncodePathWithNewBasename(path);
46 if (!status_and_enc_path.first.ok()) {
47 return status_and_enc_path.first;
48 }
49 encoded_paths.emplace_back(status_and_enc_path.second);
50 }
51 return EnvWrapper::Env::RegisterDbPaths(encoded_paths);
52 }
53
UnregisterDbPaths(const std::vector<std::string> & paths)54 Status UnregisterDbPaths(const std::vector<std::string>& paths) override {
55 std::vector<std::string> encoded_paths;
56 encoded_paths.reserve(paths.size());
57 for (auto& path : paths) {
58 auto status_and_enc_path = EncodePathWithNewBasename(path);
59 if (!status_and_enc_path.first.ok()) {
60 return status_and_enc_path.first;
61 }
62 encoded_paths.emplace_back(status_and_enc_path.second);
63 }
64 return EnvWrapper::Env::UnregisterDbPaths(encoded_paths);
65 }
66
NewSequentialFile(const std::string & fname,std::unique_ptr<SequentialFile> * result,const EnvOptions & options)67 Status NewSequentialFile(const std::string& fname,
68 std::unique_ptr<SequentialFile>* result,
69 const EnvOptions& options) override {
70 auto status_and_enc_path = EncodePathWithNewBasename(fname);
71 if (!status_and_enc_path.first.ok()) {
72 return status_and_enc_path.first;
73 }
74 return EnvWrapper::NewSequentialFile(status_and_enc_path.second, result,
75 options);
76 }
77
NewRandomAccessFile(const std::string & fname,std::unique_ptr<RandomAccessFile> * result,const EnvOptions & options)78 Status NewRandomAccessFile(const std::string& fname,
79 std::unique_ptr<RandomAccessFile>* result,
80 const EnvOptions& options) override {
81 auto status_and_enc_path = EncodePathWithNewBasename(fname);
82 if (!status_and_enc_path.first.ok()) {
83 return status_and_enc_path.first;
84 }
85 return EnvWrapper::NewRandomAccessFile(status_and_enc_path.second, result,
86 options);
87 }
88
NewWritableFile(const std::string & fname,std::unique_ptr<WritableFile> * result,const EnvOptions & options)89 Status NewWritableFile(const std::string& fname,
90 std::unique_ptr<WritableFile>* result,
91 const EnvOptions& options) override {
92 auto status_and_enc_path = EncodePathWithNewBasename(fname);
93 if (!status_and_enc_path.first.ok()) {
94 return status_and_enc_path.first;
95 }
96 return EnvWrapper::NewWritableFile(status_and_enc_path.second, result,
97 options);
98 }
99
ReuseWritableFile(const std::string & fname,const std::string & old_fname,std::unique_ptr<WritableFile> * result,const EnvOptions & options)100 Status ReuseWritableFile(const std::string& fname,
101 const std::string& old_fname,
102 std::unique_ptr<WritableFile>* result,
103 const EnvOptions& options) override {
104 auto status_and_enc_path = EncodePathWithNewBasename(fname);
105 if (!status_and_enc_path.first.ok()) {
106 return status_and_enc_path.first;
107 }
108 auto status_and_old_enc_path = EncodePath(old_fname);
109 if (!status_and_old_enc_path.first.ok()) {
110 return status_and_old_enc_path.first;
111 }
112 return EnvWrapper::ReuseWritableFile(status_and_old_enc_path.second,
113 status_and_old_enc_path.second, result,
114 options);
115 }
116
NewRandomRWFile(const std::string & fname,std::unique_ptr<RandomRWFile> * result,const EnvOptions & options)117 Status NewRandomRWFile(const std::string& fname,
118 std::unique_ptr<RandomRWFile>* result,
119 const EnvOptions& options) override {
120 auto status_and_enc_path = EncodePathWithNewBasename(fname);
121 if (!status_and_enc_path.first.ok()) {
122 return status_and_enc_path.first;
123 }
124 return EnvWrapper::NewRandomRWFile(status_and_enc_path.second, result,
125 options);
126 }
127
NewDirectory(const std::string & dir,std::unique_ptr<Directory> * result)128 Status NewDirectory(const std::string& dir,
129 std::unique_ptr<Directory>* result) override {
130 auto status_and_enc_path = EncodePathWithNewBasename(dir);
131 if (!status_and_enc_path.first.ok()) {
132 return status_and_enc_path.first;
133 }
134 return EnvWrapper::NewDirectory(status_and_enc_path.second, result);
135 }
136
FileExists(const std::string & fname)137 Status FileExists(const std::string& fname) override {
138 auto status_and_enc_path = EncodePathWithNewBasename(fname);
139 if (!status_and_enc_path.first.ok()) {
140 return status_and_enc_path.first;
141 }
142 return EnvWrapper::FileExists(status_and_enc_path.second);
143 }
144
GetChildren(const std::string & dir,std::vector<std::string> * result)145 Status GetChildren(const std::string& dir,
146 std::vector<std::string>* result) override {
147 auto status_and_enc_path = EncodePath(dir);
148 if (!status_and_enc_path.first.ok()) {
149 return status_and_enc_path.first;
150 }
151 return EnvWrapper::GetChildren(status_and_enc_path.second, result);
152 }
153
GetChildrenFileAttributes(const std::string & dir,std::vector<FileAttributes> * result)154 Status GetChildrenFileAttributes(
155 const std::string& dir, std::vector<FileAttributes>* result) override {
156 auto status_and_enc_path = EncodePath(dir);
157 if (!status_and_enc_path.first.ok()) {
158 return status_and_enc_path.first;
159 }
160 return EnvWrapper::GetChildrenFileAttributes(status_and_enc_path.second,
161 result);
162 }
163
DeleteFile(const std::string & fname)164 Status DeleteFile(const std::string& fname) override {
165 auto status_and_enc_path = EncodePath(fname);
166 if (!status_and_enc_path.first.ok()) {
167 return status_and_enc_path.first;
168 }
169 return EnvWrapper::DeleteFile(status_and_enc_path.second);
170 }
171
CreateDir(const std::string & dirname)172 Status CreateDir(const std::string& dirname) override {
173 auto status_and_enc_path = EncodePathWithNewBasename(dirname);
174 if (!status_and_enc_path.first.ok()) {
175 return status_and_enc_path.first;
176 }
177 return EnvWrapper::CreateDir(status_and_enc_path.second);
178 }
179
CreateDirIfMissing(const std::string & dirname)180 Status CreateDirIfMissing(const std::string& dirname) override {
181 auto status_and_enc_path = EncodePathWithNewBasename(dirname);
182 if (!status_and_enc_path.first.ok()) {
183 return status_and_enc_path.first;
184 }
185 return EnvWrapper::CreateDirIfMissing(status_and_enc_path.second);
186 }
187
DeleteDir(const std::string & dirname)188 Status DeleteDir(const std::string& dirname) override {
189 auto status_and_enc_path = EncodePath(dirname);
190 if (!status_and_enc_path.first.ok()) {
191 return status_and_enc_path.first;
192 }
193 return EnvWrapper::DeleteDir(status_and_enc_path.second);
194 }
195
GetFileSize(const std::string & fname,uint64_t * file_size)196 Status GetFileSize(const std::string& fname, uint64_t* file_size) override {
197 auto status_and_enc_path = EncodePath(fname);
198 if (!status_and_enc_path.first.ok()) {
199 return status_and_enc_path.first;
200 }
201 return EnvWrapper::GetFileSize(status_and_enc_path.second, file_size);
202 }
203
GetFileModificationTime(const std::string & fname,uint64_t * file_mtime)204 Status GetFileModificationTime(const std::string& fname,
205 uint64_t* file_mtime) override {
206 auto status_and_enc_path = EncodePath(fname);
207 if (!status_and_enc_path.first.ok()) {
208 return status_and_enc_path.first;
209 }
210 return EnvWrapper::GetFileModificationTime(status_and_enc_path.second,
211 file_mtime);
212 }
213
RenameFile(const std::string & src,const std::string & dest)214 Status RenameFile(const std::string& src, const std::string& dest) override {
215 auto status_and_src_enc_path = EncodePath(src);
216 if (!status_and_src_enc_path.first.ok()) {
217 return status_and_src_enc_path.first;
218 }
219 auto status_and_dest_enc_path = EncodePathWithNewBasename(dest);
220 if (!status_and_dest_enc_path.first.ok()) {
221 return status_and_dest_enc_path.first;
222 }
223 return EnvWrapper::RenameFile(status_and_src_enc_path.second,
224 status_and_dest_enc_path.second);
225 }
226
LinkFile(const std::string & src,const std::string & dest)227 Status LinkFile(const std::string& src, const std::string& dest) override {
228 auto status_and_src_enc_path = EncodePath(src);
229 if (!status_and_src_enc_path.first.ok()) {
230 return status_and_src_enc_path.first;
231 }
232 auto status_and_dest_enc_path = EncodePathWithNewBasename(dest);
233 if (!status_and_dest_enc_path.first.ok()) {
234 return status_and_dest_enc_path.first;
235 }
236 return EnvWrapper::LinkFile(status_and_src_enc_path.second,
237 status_and_dest_enc_path.second);
238 }
239
LockFile(const std::string & fname,FileLock ** lock)240 Status LockFile(const std::string& fname, FileLock** lock) override {
241 auto status_and_enc_path = EncodePathWithNewBasename(fname);
242 if (!status_and_enc_path.first.ok()) {
243 return status_and_enc_path.first;
244 }
245 // FileLock subclasses may store path (e.g., PosixFileLock stores it). We
246 // can skip stripping the chroot directory from this path because callers
247 // shouldn't use it.
248 return EnvWrapper::LockFile(status_and_enc_path.second, lock);
249 }
250
GetTestDirectory(std::string * path)251 Status GetTestDirectory(std::string* path) override {
252 // Adapted from PosixEnv's implementation since it doesn't provide a way to
253 // create directory in the chroot.
254 char buf[256];
255 snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid()));
256 *path = buf;
257
258 // Directory may already exist, so ignore return
259 CreateDir(*path);
260 return Status::OK();
261 }
262
NewLogger(const std::string & fname,std::shared_ptr<Logger> * result)263 Status NewLogger(const std::string& fname,
264 std::shared_ptr<Logger>* result) override {
265 auto status_and_enc_path = EncodePathWithNewBasename(fname);
266 if (!status_and_enc_path.first.ok()) {
267 return status_and_enc_path.first;
268 }
269 return EnvWrapper::NewLogger(status_and_enc_path.second, result);
270 }
271
GetAbsolutePath(const std::string & db_path,std::string * output_path)272 Status GetAbsolutePath(const std::string& db_path,
273 std::string* output_path) override {
274 auto status_and_enc_path = EncodePath(db_path);
275 if (!status_and_enc_path.first.ok()) {
276 return status_and_enc_path.first;
277 }
278 return EnvWrapper::GetAbsolutePath(status_and_enc_path.second, output_path);
279 }
280
281 private:
282 // Returns status and expanded absolute path including the chroot directory.
283 // Checks whether the provided path breaks out of the chroot. If it returns
284 // non-OK status, the returned path should not be used.
EncodePath(const std::string & path)285 std::pair<Status, std::string> EncodePath(const std::string& path) {
286 if (path.empty() || path[0] != '/') {
287 return {Status::InvalidArgument(path, "Not an absolute path"), ""};
288 }
289 std::pair<Status, std::string> res;
290 res.second = chroot_dir_ + path;
291 #if defined(OS_AIX)
292 char resolvedName[PATH_MAX];
293 char* normalized_path = realpath(res.second.c_str(), resolvedName);
294 #else
295 char* normalized_path = realpath(res.second.c_str(), nullptr);
296 #endif
297 if (normalized_path == nullptr) {
298 res.first = Status::NotFound(res.second, strerror(errno));
299 } else if (strlen(normalized_path) < chroot_dir_.size() ||
300 strncmp(normalized_path, chroot_dir_.c_str(),
301 chroot_dir_.size()) != 0) {
302 res.first = Status::IOError(res.second,
303 "Attempted to access path outside chroot");
304 } else {
305 res.first = Status::OK();
306 }
307 #if !defined(OS_AIX)
308 free(normalized_path);
309 #endif
310 return res;
311 }
312
313 // Similar to EncodePath() except assumes the basename in the path hasn't been
314 // created yet.
EncodePathWithNewBasename(const std::string & path)315 std::pair<Status, std::string> EncodePathWithNewBasename(
316 const std::string& path) {
317 if (path.empty() || path[0] != '/') {
318 return {Status::InvalidArgument(path, "Not an absolute path"), ""};
319 }
320 // Basename may be followed by trailing slashes
321 size_t final_idx = path.find_last_not_of('/');
322 if (final_idx == std::string::npos) {
323 // It's only slashes so no basename to extract
324 return EncodePath(path);
325 }
326
327 // Pull off the basename temporarily since realname(3) (used by
328 // EncodePath()) requires a path that exists
329 size_t base_sep = path.rfind('/', final_idx);
330 auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1));
331 status_and_enc_path.second.append(path.substr(base_sep + 1));
332 return status_and_enc_path;
333 }
334
335 std::string chroot_dir_;
336 };
337
NewChrootEnv(Env * base_env,const std::string & chroot_dir)338 Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) {
339 if (!base_env->FileExists(chroot_dir).ok()) {
340 return nullptr;
341 }
342 return new ChrootEnv(base_env, chroot_dir);
343 }
344
345 } // namespace ROCKSDB_NAMESPACE
346
347 #endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
348