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