| // Copyright (c) 2016-present, Facebook, Inc. All rights reserved. |
| // This source code is licensed under both the GPLv2 (found in the |
| // COPYING file in the root directory) and Apache 2.0 License |
| // (found in the LICENSE.Apache file in the root directory). |
| |
| #if !defined(ROCKSDB_LITE) && !defined(OS_WIN) |
| |
| #include "env/env_chroot.h" |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "rocksdb/status.h" |
| |
| namespace rocksdb { |
| |
| class ChrootEnv : public EnvWrapper { |
| public: |
| ChrootEnv(Env* base_env, const std::string& chroot_dir) |
| : EnvWrapper(base_env) { |
| #if defined(OS_AIX) |
| char resolvedName[PATH_MAX]; |
| char* real_chroot_dir = realpath(chroot_dir.c_str(), resolvedName); |
| #else |
| char* real_chroot_dir = realpath(chroot_dir.c_str(), nullptr); |
| #endif |
| // chroot_dir must exist so realpath() returns non-nullptr. |
| assert(real_chroot_dir != nullptr); |
| chroot_dir_ = real_chroot_dir; |
| #if !defined(OS_AIX) |
| free(real_chroot_dir); |
| #endif |
| } |
| |
| virtual Status NewSequentialFile(const std::string& fname, |
| std::unique_ptr<SequentialFile>* result, |
| const EnvOptions& options) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewSequentialFile(status_and_enc_path.second, result, |
| options); |
| } |
| |
| virtual Status NewRandomAccessFile(const std::string& fname, |
| unique_ptr<RandomAccessFile>* result, |
| const EnvOptions& options) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewRandomAccessFile(status_and_enc_path.second, result, |
| options); |
| } |
| |
| virtual Status NewWritableFile(const std::string& fname, |
| unique_ptr<WritableFile>* result, |
| const EnvOptions& options) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewWritableFile(status_and_enc_path.second, result, |
| options); |
| } |
| |
| virtual Status ReuseWritableFile(const std::string& fname, |
| const std::string& old_fname, |
| unique_ptr<WritableFile>* result, |
| const EnvOptions& options) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| auto status_and_old_enc_path = EncodePath(old_fname); |
| if (!status_and_old_enc_path.first.ok()) { |
| return status_and_old_enc_path.first; |
| } |
| return EnvWrapper::ReuseWritableFile(status_and_old_enc_path.second, |
| status_and_old_enc_path.second, result, |
| options); |
| } |
| |
| virtual Status NewRandomRWFile(const std::string& fname, |
| unique_ptr<RandomRWFile>* result, |
| const EnvOptions& options) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewRandomRWFile(status_and_enc_path.second, result, |
| options); |
| } |
| |
| virtual Status NewDirectory(const std::string& dir, |
| unique_ptr<Directory>* result) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(dir); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewDirectory(status_and_enc_path.second, result); |
| } |
| |
| virtual Status FileExists(const std::string& fname) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::FileExists(status_and_enc_path.second); |
| } |
| |
| virtual Status GetChildren(const std::string& dir, |
| std::vector<std::string>* result) override { |
| auto status_and_enc_path = EncodePath(dir); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::GetChildren(status_and_enc_path.second, result); |
| } |
| |
| virtual Status GetChildrenFileAttributes( |
| const std::string& dir, std::vector<FileAttributes>* result) override { |
| auto status_and_enc_path = EncodePath(dir); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::GetChildrenFileAttributes(status_and_enc_path.second, |
| result); |
| } |
| |
| virtual Status DeleteFile(const std::string& fname) override { |
| auto status_and_enc_path = EncodePath(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::DeleteFile(status_and_enc_path.second); |
| } |
| |
| virtual Status CreateDir(const std::string& dirname) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(dirname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::CreateDir(status_and_enc_path.second); |
| } |
| |
| virtual Status CreateDirIfMissing(const std::string& dirname) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(dirname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::CreateDirIfMissing(status_and_enc_path.second); |
| } |
| |
| virtual Status DeleteDir(const std::string& dirname) override { |
| auto status_and_enc_path = EncodePath(dirname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::DeleteDir(status_and_enc_path.second); |
| } |
| |
| virtual Status GetFileSize(const std::string& fname, |
| uint64_t* file_size) override { |
| auto status_and_enc_path = EncodePath(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::GetFileSize(status_and_enc_path.second, file_size); |
| } |
| |
| virtual Status GetFileModificationTime(const std::string& fname, |
| uint64_t* file_mtime) override { |
| auto status_and_enc_path = EncodePath(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::GetFileModificationTime(status_and_enc_path.second, |
| file_mtime); |
| } |
| |
| virtual Status RenameFile(const std::string& src, |
| const std::string& dest) override { |
| auto status_and_src_enc_path = EncodePath(src); |
| if (!status_and_src_enc_path.first.ok()) { |
| return status_and_src_enc_path.first; |
| } |
| auto status_and_dest_enc_path = EncodePathWithNewBasename(dest); |
| if (!status_and_dest_enc_path.first.ok()) { |
| return status_and_dest_enc_path.first; |
| } |
| return EnvWrapper::RenameFile(status_and_src_enc_path.second, |
| status_and_dest_enc_path.second); |
| } |
| |
| virtual Status LinkFile(const std::string& src, |
| const std::string& dest) override { |
| auto status_and_src_enc_path = EncodePath(src); |
| if (!status_and_src_enc_path.first.ok()) { |
| return status_and_src_enc_path.first; |
| } |
| auto status_and_dest_enc_path = EncodePathWithNewBasename(dest); |
| if (!status_and_dest_enc_path.first.ok()) { |
| return status_and_dest_enc_path.first; |
| } |
| return EnvWrapper::LinkFile(status_and_src_enc_path.second, |
| status_and_dest_enc_path.second); |
| } |
| |
| virtual Status LockFile(const std::string& fname, FileLock** lock) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| // FileLock subclasses may store path (e.g., PosixFileLock stores it). We |
| // can skip stripping the chroot directory from this path because callers |
| // shouldn't use it. |
| return EnvWrapper::LockFile(status_and_enc_path.second, lock); |
| } |
| |
| virtual Status GetTestDirectory(std::string* path) override { |
| // Adapted from PosixEnv's implementation since it doesn't provide a way to |
| // create directory in the chroot. |
| char buf[256]; |
| snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid())); |
| *path = buf; |
| |
| // Directory may already exist, so ignore return |
| CreateDir(*path); |
| return Status::OK(); |
| } |
| |
| virtual Status NewLogger(const std::string& fname, |
| shared_ptr<Logger>* result) override { |
| auto status_and_enc_path = EncodePathWithNewBasename(fname); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::NewLogger(status_and_enc_path.second, result); |
| } |
| |
| virtual Status GetAbsolutePath(const std::string& db_path, |
| std::string* output_path) override { |
| auto status_and_enc_path = EncodePath(db_path); |
| if (!status_and_enc_path.first.ok()) { |
| return status_and_enc_path.first; |
| } |
| return EnvWrapper::GetAbsolutePath(status_and_enc_path.second, output_path); |
| } |
| |
| private: |
| // Returns status and expanded absolute path including the chroot directory. |
| // Checks whether the provided path breaks out of the chroot. If it returns |
| // non-OK status, the returned path should not be used. |
| std::pair<Status, std::string> EncodePath(const std::string& path) { |
| if (path.empty() || path[0] != '/') { |
| return {Status::InvalidArgument(path, "Not an absolute path"), ""}; |
| } |
| std::pair<Status, std::string> res; |
| res.second = chroot_dir_ + path; |
| #if defined(OS_AIX) |
| char resolvedName[PATH_MAX]; |
| char* normalized_path = realpath(res.second.c_str(), resolvedName); |
| #else |
| char* normalized_path = realpath(res.second.c_str(), nullptr); |
| #endif |
| if (normalized_path == nullptr) { |
| res.first = Status::NotFound(res.second, strerror(errno)); |
| } else if (strlen(normalized_path) < chroot_dir_.size() || |
| strncmp(normalized_path, chroot_dir_.c_str(), |
| chroot_dir_.size()) != 0) { |
| res.first = Status::IOError(res.second, |
| "Attempted to access path outside chroot"); |
| } else { |
| res.first = Status::OK(); |
| } |
| #if !defined(OS_AIX) |
| free(normalized_path); |
| #endif |
| return res; |
| } |
| |
| // Similar to EncodePath() except assumes the basename in the path hasn't been |
| // created yet. |
| std::pair<Status, std::string> EncodePathWithNewBasename( |
| const std::string& path) { |
| if (path.empty() || path[0] != '/') { |
| return {Status::InvalidArgument(path, "Not an absolute path"), ""}; |
| } |
| // Basename may be followed by trailing slashes |
| size_t final_idx = path.find_last_not_of('/'); |
| if (final_idx == std::string::npos) { |
| // It's only slashes so no basename to extract |
| return EncodePath(path); |
| } |
| |
| // Pull off the basename temporarily since realname(3) (used by |
| // EncodePath()) requires a path that exists |
| size_t base_sep = path.rfind('/', final_idx); |
| auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1)); |
| status_and_enc_path.second.append(path.substr(base_sep + 1)); |
| return status_and_enc_path; |
| } |
| |
| std::string chroot_dir_; |
| }; |
| |
| Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) { |
| if (!base_env->FileExists(chroot_dir).ok()) { |
| return nullptr; |
| } |
| return new ChrootEnv(base_env, chroot_dir); |
| } |
| |
| } // namespace rocksdb |
| |
| #endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN) |