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