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