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