// Copyright (c) 2016-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // This source code is also licensed under the GPLv2 license found in the // COPYING file in the root directory of this source tree. #if !defined(ROCKSDB_LITE) && !defined(OS_WIN) #include "env/env_chroot.h" #include #include #include #include #include #include #include #include "rocksdb/status.h" namespace rocksdb { class ChrootEnv : public EnvWrapper { public: ChrootEnv(Env* base_env, const std::string& chroot_dir) : EnvWrapper(base_env) { char* real_chroot_dir = realpath(chroot_dir.c_str(), nullptr); // chroot_dir must exist so realpath() returns non-nullptr. assert(real_chroot_dir != nullptr); chroot_dir_ = real_chroot_dir; free(real_chroot_dir); } virtual Status NewSequentialFile(const std::string& fname, std::unique_ptr* 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* 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* 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* 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* 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* 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* 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* 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(geteuid())); *path = buf; // Directory may already exist, so ignore return CreateDir(*path); return Status::OK(); } virtual Status NewLogger(const std::string& fname, shared_ptr* 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 EncodePath(const std::string& path) { if (path.empty() || path[0] != '/') { return {Status::InvalidArgument(path, "Not an absolute path"), ""}; } std::pair res; res.second = chroot_dir_ + path; char* normalized_path = realpath(res.second.c_str(), nullptr); 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(); } free(normalized_path); return res; } // Similar to EncodePath() except assumes the basename in the path hasn't been // created yet. std::pair 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)