Coverage Report

Created: 2024-07-27 06:53

/src/rocksdb/env/env_chroot.cc
Line
Count
Source (jump to first uncovered line)
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(OS_WIN)
7
8
#include "env/env_chroot.h"
9
10
#include <unistd.h>  // geteuid
11
12
#include <cerrno>   // errno
13
#include <cstdlib>  // realpath, free
14
15
#include "env/composite_env_wrapper.h"
16
#include "env/fs_remap.h"
17
#include "rocksdb/utilities/options_type.h"
18
#include "util/string_util.h"  // errnoStr
19
20
namespace ROCKSDB_NAMESPACE {
21
namespace {
22
static std::unordered_map<std::string, OptionTypeInfo> chroot_fs_type_info = {
23
    {"chroot_dir", {0, OptionType::kString}}};
24
}  // namespace
25
ChrootFileSystem::ChrootFileSystem(const std::shared_ptr<FileSystem>& base,
26
                                   const std::string& chroot_dir)
27
0
    : RemapFileSystem(base), chroot_dir_(chroot_dir) {
28
0
  RegisterOptions("chroot_dir", &chroot_dir_, &chroot_fs_type_info);
29
0
}
30
31
0
Status ChrootFileSystem::PrepareOptions(const ConfigOptions& options) {
32
0
  Status s = FileSystemWrapper::PrepareOptions(options);
33
0
  if (!s.ok()) {
34
0
    return s;
35
0
  } else if (chroot_dir_.empty()) {
36
0
    s = Status::InvalidArgument("ChRootFileSystem requires a chroot dir");
37
0
  } else {
38
0
    s = target_->FileExists(chroot_dir_, IOOptions(), nullptr);
39
0
  }
40
0
  if (s.ok()) {
41
#if defined(OS_AIX)
42
    char resolvedName[PATH_MAX];
43
    char* real_chroot_dir = realpath(chroot_dir_.c_str(), resolvedName);
44
#else
45
0
    char* real_chroot_dir = realpath(chroot_dir_.c_str(), nullptr);
46
0
#endif
47
    // chroot_dir must exist so realpath() returns non-nullptr.
48
0
    assert(real_chroot_dir != nullptr);
49
0
    chroot_dir_ = real_chroot_dir;
50
0
#if !defined(OS_AIX)
51
0
    free(real_chroot_dir);
52
0
#endif
53
0
  }
54
0
  return s;
55
0
}
56
57
IOStatus ChrootFileSystem::GetTestDirectory(const IOOptions& options,
58
                                            std::string* path,
59
0
                                            IODebugContext* dbg) {
60
  // Adapted from PosixEnv's implementation since it doesn't provide a way to
61
  // create directory in the chroot.
62
0
  char buf[256];
63
0
  snprintf(buf, sizeof(buf), "/rocksdbtest-%d", static_cast<int>(geteuid()));
64
0
  *path = buf;
65
66
  // Directory may already exist, so ignore return
67
0
  return CreateDirIfMissing(*path, options, dbg);
68
0
}
69
70
// Returns status and expanded absolute path including the chroot directory.
71
// Checks whether the provided path breaks out of the chroot. If it returns
72
// non-OK status, the returned path should not be used.
73
std::pair<IOStatus, std::string> ChrootFileSystem::EncodePath(
74
0
    const std::string& path) {
75
0
  if (path.empty() || path[0] != '/') {
76
0
    return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
77
0
  }
78
0
  std::pair<IOStatus, std::string> res;
79
0
  res.second = chroot_dir_ + path;
80
#if defined(OS_AIX)
81
  char resolvedName[PATH_MAX];
82
  char* normalized_path = realpath(res.second.c_str(), resolvedName);
83
#else
84
0
  char* normalized_path = realpath(res.second.c_str(), nullptr);
85
0
#endif
86
0
  if (normalized_path == nullptr) {
87
0
    res.first = IOStatus::NotFound(res.second, errnoStr(errno).c_str());
88
0
  } else if (strlen(normalized_path) < chroot_dir_.size() ||
89
0
             strncmp(normalized_path, chroot_dir_.c_str(),
90
0
                     chroot_dir_.size()) != 0) {
91
0
    res.first = IOStatus::IOError(res.second,
92
0
                                  "Attempted to access path outside chroot");
93
0
  } else {
94
0
    res.first = IOStatus::OK();
95
0
  }
96
0
#if !defined(OS_AIX)
97
0
  free(normalized_path);
98
0
#endif
99
0
  return res;
100
0
}
101
102
// Similar to EncodePath() except assumes the basename in the path hasn't been
103
// created yet.
104
std::pair<IOStatus, std::string> ChrootFileSystem::EncodePathWithNewBasename(
105
0
    const std::string& path) {
106
0
  if (path.empty() || path[0] != '/') {
107
0
    return {IOStatus::InvalidArgument(path, "Not an absolute path"), ""};
108
0
  }
109
  // Basename may be followed by trailing slashes
110
0
  size_t final_idx = path.find_last_not_of('/');
111
0
  if (final_idx == std::string::npos) {
112
    // It's only slashes so no basename to extract
113
0
    return EncodePath(path);
114
0
  }
115
116
  // Pull off the basename temporarily since realname(3) (used by
117
  // EncodePath()) requires a path that exists
118
0
  size_t base_sep = path.rfind('/', final_idx);
119
0
  auto status_and_enc_path = EncodePath(path.substr(0, base_sep + 1));
120
0
  status_and_enc_path.second.append(path.substr(base_sep + 1));
121
0
  return status_and_enc_path;
122
0
}
123
124
std::shared_ptr<FileSystem> NewChrootFileSystem(
125
0
    const std::shared_ptr<FileSystem>& base, const std::string& chroot_dir) {
126
0
  auto chroot_fs = std::make_shared<ChrootFileSystem>(base, chroot_dir);
127
0
  Status s = chroot_fs->PrepareOptions(ConfigOptions());
128
0
  if (s.ok()) {
129
0
    return chroot_fs;
130
0
  } else {
131
0
    return nullptr;
132
0
  }
133
0
}
134
135
0
Env* NewChrootEnv(Env* base_env, const std::string& chroot_dir) {
136
0
  if (!base_env->FileExists(chroot_dir).ok()) {
137
0
    return nullptr;
138
0
  }
139
0
  auto chroot_fs = NewChrootFileSystem(base_env->GetFileSystem(), chroot_dir);
140
0
  if (chroot_fs != nullptr) {
141
0
    return new CompositeEnvWrapper(base_env, chroot_fs);
142
0
  } else {
143
0
    return nullptr;
144
0
  }
145
0
}
146
147
}  // namespace ROCKSDB_NAMESPACE
148
149
#endif  //  !defined(OS_WIN)