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> // errno
11 #include <stdlib.h> // realpath, free
12 #include <unistd.h> // geteuid
13
14 #include "env/composite_env_wrapper.h"
15 #include "env/fs_remap.h"
16 #include "util/string_util.h" // errnoStr
17
18 namespace ROCKSDB_NAMESPACE {
19 namespace {
20 class ChrootFileSystem : public RemapFileSystem {
21 public:
ChrootFileSystem(const std::shared_ptr<FileSystem> & base,const std::string & chroot_dir)22 ChrootFileSystem(const std::shared_ptr<FileSystem>& base,
23 const std::string& chroot_dir)
24 : RemapFileSystem(base) {
25 #if defined(OS_AIX)
26 char resolvedName[PATH_MAX];
27 char* real_chroot_dir = realpath(chroot_dir.c_str(), resolvedName);
28 #else
29 char* real_chroot_dir = realpath(chroot_dir.c_str(), nullptr);
30 #endif
31 // chroot_dir must exist so realpath() returns non-nullptr.
32 assert(real_chroot_dir != nullptr);
33 chroot_dir_ = real_chroot_dir;
34 #if !defined(OS_AIX)
35 free(real_chroot_dir);
36 #endif
37 }
38
Name() const39 const char* Name() const override { return "ChrootFS"; }
40
GetTestDirectory(const IOOptions & options,std::string * path,IODebugContext * dbg)41 IOStatus GetTestDirectory(const IOOptions& options, std::string* path,
42 IODebugContext* dbg) override {
43 // Adapted from PosixEnv's implementation since it doesn't provide a way to
44 // create directory in the chroot.
45 char buf[256];
46 snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid()));
47 *path = buf;
48
49 // Directory may already exist, so ignore return
50 return CreateDirIfMissing(*path, options, dbg);
51 }
52
53 protected:
54 // Returns status and expanded absolute path including the chroot directory.
55 // Checks whether the provided path breaks out of the chroot. If it returns
56 // non-OK status, the returned path should not be used.
EncodePath(const std::string & path)57 std::pair<IOStatus, std::string> EncodePath(
58 const std::string& path) override {
59 if (path.empty() || path[0] != '/') {
60 return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
61 }
62 std::pair<IOStatus, std::string> res;
63 res.second = chroot_dir_ + path;
64 #if defined(OS_AIX)
65 char resolvedName[PATH_MAX];
66 char* normalized_path = realpath(res.second.c_str(), resolvedName);
67 #else
68 char* normalized_path = realpath(res.second.c_str(), nullptr);
69 #endif
70 if (normalized_path == nullptr) {
71 res.first = IOStatus::NotFound(res.second, errnoStr(errno).c_str());
72 } else if (strlen(normalized_path) < chroot_dir_.size() ||
73 strncmp(normalized_path, chroot_dir_.c_str(),
74 chroot_dir_.size()) != 0) {
75 res.first = IOStatus::IOError(res.second,
76 "Attempted to access path outside chroot");
77 } else {
78 res.first = IOStatus::OK();
79 }
80 #if !defined(OS_AIX)
81 free(normalized_path);
82 #endif
83 return res;
84 }
85
86 // Similar to EncodePath() except assumes the basename in the path hasn't been
87 // created yet.
EncodePathWithNewBasename(const std::string & path)88 std::pair<IOStatus, std::string> EncodePathWithNewBasename(
89 const std::string& path) override {
90 if (path.empty() || path[0] != '/') {
91 return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
92 }
93 // Basename may be followed by trailing slashes
94 size_t final_idx = path.find_last_not_of('/');
95 if (final_idx == std::string::npos) {
96 // It's only slashes so no basename to extract
97 return EncodePath(path);
98 }
99
100 // Pull off the basename temporarily since realname(3) (used by
101 // EncodePath()) requires a path that exists
102 size_t base_sep = path.rfind('/', final_idx);
103 auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1));
104 status_and_enc_path.second.append(path.substr(base_sep + 1));
105 return status_and_enc_path;
106 }
107
108 private:
109 std::string chroot_dir_;
110 };
111 } // namespace
112
NewChrootFileSystem(const std::shared_ptr<FileSystem> & base,const std::string & chroot_dir)113 std::shared_ptr<FileSystem> NewChrootFileSystem(
114 const std::shared_ptr<FileSystem>& base, const std::string& chroot_dir) {
115 return std::make_shared<ChrootFileSystem>(base, chroot_dir);
116 }
117
NewChrootEnv(Env * base_env,const std::string & chroot_dir)118 Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) {
119 if (!base_env->FileExists(chroot_dir).ok()) {
120 return nullptr;
121 }
122 std::shared_ptr<FileSystem> chroot_fs =
123 NewChrootFileSystem(base_env->GetFileSystem(), chroot_dir);
124 return new CompositeEnvWrapper(base_env, chroot_fs);
125 }
126
127 } // namespace ROCKSDB_NAMESPACE
128
129 #endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
130