Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 1 | // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "cros-disks/drivefs_helper.h" |
| 6 | |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 7 | #include <stdlib.h> |
| 8 | #include <sys/wait.h> |
| 9 | #include <sysexits.h> |
| 10 | #include <unistd.h> |
| 11 | |
François Degros | a28315e | 2020-07-13 00:24:48 +1000 | [diff] [blame] | 12 | #include <utility> |
| 13 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 14 | #include <base/check.h> |
Sam McNally | f6b4ef8 | 2019-06-12 14:22:08 +1000 | [diff] [blame] | 15 | #include <base/files/file_enumerator.h> |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 16 | #include <base/files/file_path.h> |
| 17 | #include <base/logging.h> |
| 18 | #include <base/strings/string_number_conversions.h> |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 19 | #include <base/strings/stringprintf.h> |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 20 | #include <base/strings/string_util.h> |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 21 | #include <brillo/files/safe_fd.h> |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 22 | |
| 23 | #include "cros-disks/fuse_mounter.h" |
| 24 | #include "cros-disks/mount_options.h" |
| 25 | #include "cros-disks/platform.h" |
François Degros | 8b4e31e | 2019-07-29 11:39:19 +1000 | [diff] [blame] | 26 | #include "cros-disks/quote.h" |
Sergei Datsenko | 88035aa | 2020-11-15 00:24:01 +1100 | [diff] [blame] | 27 | #include "cros-disks/sandboxed_process.h" |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 28 | #include "cros-disks/system_mounter.h" |
| 29 | #include "cros-disks/uri.h" |
| 30 | |
| 31 | namespace cros_disks { |
| 32 | namespace { |
| 33 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 34 | const char kDataDirOptionPrefix[] = "datadir"; |
| 35 | const char kIdentityOptionPrefix[] = "identity"; |
| 36 | const char kMyFilesOptionPrefix[] = "myfiles"; |
| 37 | const char kPathPrefixOptionPrefix[] = "prefix"; |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 38 | |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 39 | const char kHelperTool[] = "/opt/google/drive-file-stream/drivefs"; |
| 40 | const char kType[] = "drivefs"; |
Sergei Datsenko | 1cf9f3d | 2019-01-02 14:39:48 +1100 | [diff] [blame] | 41 | const char kDbusSocketPath[] = "/run/dbus"; |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 42 | const char kHomeBaseDir[] = "/home"; |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 43 | |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 44 | // UID of fuse-drivefs user. |
| 45 | constexpr uid_t kOldDriveUID = 304; |
| 46 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 47 | bool FindPathOption(const std::vector<std::string>& options, |
| 48 | const std::string& name, |
| 49 | base::FilePath* path) { |
| 50 | std::string value; |
| 51 | if (!GetParamValue(options, name, &value) || value.empty()) { |
| 52 | return false; |
Anand K Mistry | 25a5f85 | 2020-01-14 17:08:39 +1100 | [diff] [blame] | 53 | } |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 54 | *path = base::FilePath(value); |
| 55 | return true; |
| 56 | } |
| 57 | |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 58 | error_t RemoveDirectory(brillo::SafeFD* parent, const base::FilePath& name) { |
| 59 | if (setresuid(kOldDriveUID, kOldDriveUID, kOldDriveUID) != 0) { |
| 60 | return errno; |
| 61 | } |
| 62 | |
| 63 | // This function accounts for various gotchas like filesystem boundaries, |
| 64 | // types, etc. If we were to chown the dir we'd have to replicate that |
| 65 | // traversal approach. But for now just nuke the dir. |
| 66 | brillo::SafeFD::Error error = parent->Rmdir(name.value(), |
| 67 | /*recursive=*/true, |
| 68 | /*max_depth=*/7, |
| 69 | /*keep_going=*/false); |
| 70 | switch (error) { |
| 71 | case brillo::SafeFD::Error::kNoError: |
| 72 | return 0; |
| 73 | case brillo::SafeFD::Error::kIOError: |
| 74 | case brillo::SafeFD::Error::kWrongType: |
| 75 | return errno; |
| 76 | case brillo::SafeFD::Error::kBoundaryDetected: |
| 77 | return EXDEV; |
| 78 | default: |
| 79 | return EINVAL; |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | bool FixDirectory(const base::FilePath& path) { |
| 84 | brillo::SafeFD::SafeFDResult root = brillo::SafeFD::Root(); |
| 85 | if (brillo::SafeFD::IsError(root.second)) { |
| 86 | PLOG(ERROR) << "Failed to open root dir: " << static_cast<int>(root.second); |
| 87 | return false; |
| 88 | } |
| 89 | brillo::SafeFD::SafeFDResult parent = root.first.OpenExistingDir( |
| 90 | path.DirName(), O_PATH | O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW); |
| 91 | if (brillo::SafeFD::IsError(parent.second)) { |
| 92 | PLOG(ERROR) << "Failed to open parent dir: " |
| 93 | << static_cast<int>(parent.second); |
| 94 | return false; |
| 95 | } |
| 96 | |
| 97 | // Permission on the datadir won't allow cros-disks to manipulate it |
| 98 | // directly, chowning it based off path is unsafe, and fchown requires |
| 99 | // an FD, which we can't obtain because permissions, so instead we fork |
| 100 | // and change child's UID to freely navigate the directory tree. |
| 101 | pid_t pid = fork(); |
| 102 | if (pid == -1) { |
| 103 | PLOG(ERROR) << "Failed to fork"; |
| 104 | return false; |
| 105 | } else if (pid == 0) { |
| 106 | _exit(RemoveDirectory(&parent.first, path.BaseName())); |
| 107 | } else { |
| 108 | int wstatus; |
| 109 | PCHECK(pid == waitpid(pid, &wstatus, 0)); |
| 110 | int status = EXIT_FAILURE; |
| 111 | if (WIFEXITED(wstatus)) { |
| 112 | status = WEXITSTATUS(wstatus); |
| 113 | errno = status; |
| 114 | } |
| 115 | if (status != EXIT_SUCCESS) { |
| 116 | PLOG(ERROR) << "Cannot remove datadir " << quote(path); |
| 117 | return false; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | brillo::SafeFD::SafeFDResult datadir = |
| 122 | parent.first.MakeDir(path.BaseName(), S_IRWXU | S_IRWXG | S_ISGID, |
| 123 | kChronosUID, kChronosAccessGID); |
| 124 | |
| 125 | if (brillo::SafeFD::IsError(datadir.second)) { |
| 126 | PLOG(ERROR) << "Cannot create datadir " << quote(path); |
| 127 | return false; |
| 128 | } |
| 129 | |
| 130 | return true; |
| 131 | } |
| 132 | |
| 133 | bool ValidateDirectory(const Platform* platform, |
| 134 | base::FilePath* dir, |
| 135 | bool fix_non_compliant) { |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 136 | if (dir->empty() || !dir->IsAbsolute() || dir->ReferencesParent()) { |
| 137 | LOG(ERROR) << "Unsafe path " << quote(*dir); |
| 138 | return false; |
| 139 | } |
| 140 | std::string path_string; |
| 141 | if (!platform->GetRealPath(dir->value(), &path_string)) { |
| 142 | LOG(ERROR) << "Unable to find real path of " << quote(*dir); |
Austin Tankiang | a57f577 | 2021-05-18 18:41:56 +1000 | [diff] [blame] | 143 | // TODO(crbug.com/1205308): Remove extra logging when root cause found. |
| 144 | LOG(ERROR) << "Checking ancestors:"; |
| 145 | for (base::FilePath p = *dir; p != p.DirName(); p = p.DirName()) { |
| 146 | if (!platform->GetRealPath(p.value(), &path_string)) { |
| 147 | LOG(ERROR) << "Unable to find real path of " << quote(p); |
| 148 | } |
| 149 | if (!platform->DirectoryExists(p.value())) { |
| 150 | LOG(ERROR) << "Dir does not exist " << quote(p); |
| 151 | continue; |
| 152 | } |
| 153 | uid_t uid; |
| 154 | gid_t gid; |
| 155 | if (platform->GetOwnership(p.value(), &uid, &gid)) { |
| 156 | LOG(ERROR) << "Path " << quote(p) << " has owner " << uid << ":" << gid; |
| 157 | } |
| 158 | mode_t mode; |
| 159 | if (platform->GetPermissions(p.value(), &mode)) { |
| 160 | LOG(ERROR) << "Path " << quote(p) << " has mode " << std::oct << mode; |
| 161 | } |
| 162 | } |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 163 | return false; |
| 164 | } |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 165 | *dir = base::FilePath(path_string); |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 166 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 167 | CHECK(dir->IsAbsolute() && !dir->ReferencesParent()); |
| 168 | |
| 169 | if (!platform->DirectoryExists(dir->value())) { |
| 170 | LOG(ERROR) << "Dir does not exist " << quote(*dir); |
| 171 | return false; |
| 172 | } |
| 173 | |
| 174 | uid_t current_uid; |
| 175 | if (!platform->GetOwnership(dir->value(), ¤t_uid, nullptr)) { |
| 176 | LOG(ERROR) << "Cannot access datadir " << quote(*dir); |
| 177 | return false; |
| 178 | } |
| 179 | |
| 180 | if (current_uid != kChronosUID) { |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 181 | if (!fix_non_compliant || current_uid != kOldDriveUID) { |
| 182 | LOG(ERROR) << "Wrong owner of datadir: " << current_uid; |
| 183 | return false; |
| 184 | } |
| 185 | |
| 186 | LOG(WARNING) << "Unmigrated drivefs datadir detected"; |
| 187 | if (!FixDirectory(*dir)) { |
| 188 | LOG(ERROR) << "Could not repair drivefs datadir ownership"; |
| 189 | return false; |
| 190 | } |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | return true; |
| 194 | } |
Anand K Mistry | 25a5f85 | 2020-01-14 17:08:39 +1100 | [diff] [blame] | 195 | |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 196 | } // namespace |
| 197 | |
Sergei Datsenko | a910bba | 2019-06-18 13:31:59 +1000 | [diff] [blame] | 198 | DrivefsHelper::DrivefsHelper(const Platform* platform, |
| 199 | brillo::ProcessReaper* process_reaper) |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 200 | : FUSEMounterHelper(platform, |
| 201 | process_reaper, |
| 202 | kType, |
| 203 | /* nosymfollow= */ false, |
| 204 | &sandbox_factory_), |
| 205 | sandbox_factory_(platform, |
| 206 | SandboxedExecutable{base::FilePath(kHelperTool)}, |
| 207 | OwnerUser{kChronosUID, kChronosGID}, |
| 208 | /* has_network_access= */ true) {} |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 209 | |
| 210 | DrivefsHelper::~DrivefsHelper() = default; |
| 211 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 212 | bool DrivefsHelper::CanMount(const std::string& source, |
| 213 | const std::vector<std::string>& params, |
| 214 | base::FilePath* suggested_name) const { |
| 215 | const Uri uri = Uri::Parse(source); |
| 216 | if (!uri.valid() || uri.scheme() != kType) |
| 217 | return false; |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 218 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 219 | if (uri.path().empty()) |
| 220 | *suggested_name = base::FilePath(kType); |
| 221 | else |
| 222 | *suggested_name = base::FilePath(uri.path()); |
| 223 | return true; |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 224 | } |
| 225 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 226 | MountErrorType DrivefsHelper::ConfigureSandbox( |
| 227 | const std::string& source, |
| 228 | const base::FilePath& target_path, |
| 229 | std::vector<std::string> params, |
| 230 | SandboxedProcess* sandbox) const { |
| 231 | const Uri uri = Uri::Parse(source); |
| 232 | if (!uri.valid() || uri.scheme() != kType) { |
François Degros | c309d93 | 2021-02-04 15:12:30 +1100 | [diff] [blame] | 233 | LOG(ERROR) << "Invalid source format " << quote(source); |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 234 | return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| 235 | } |
| 236 | if (uri.path().empty()) { |
François Degros | c309d93 | 2021-02-04 15:12:30 +1100 | [diff] [blame] | 237 | LOG(ERROR) << "Invalid source " << quote(source); |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 238 | return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| 239 | } |
| 240 | |
| 241 | base::FilePath data_dir; |
| 242 | if (!FindPathOption(params, kDataDirOptionPrefix, &data_dir)) { |
| 243 | LOG(ERROR) << "No data directory provided"; |
| 244 | return MOUNT_ERROR_INVALID_MOUNT_OPTIONS; |
| 245 | } |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 246 | if (!ValidateDirectory(platform(), &data_dir, true)) { |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 247 | return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| 248 | } |
| 249 | |
| 250 | const base::FilePath homedir(kHomeBaseDir); |
| 251 | if (!homedir.IsParent(data_dir)) { |
| 252 | LOG(ERROR) << "Unexpected location of " << quote(data_dir); |
| 253 | return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| 254 | } |
| 255 | |
| 256 | base::FilePath my_files; |
| 257 | if (FindPathOption(params, kMyFilesOptionPrefix, &my_files)) { |
Sergei Datsenko | abc6fb5 | 2021-02-11 14:39:09 +1100 | [diff] [blame] | 258 | if (!ValidateDirectory(platform(), &my_files, false)) { |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 259 | LOG(ERROR) << "User files inaccessible"; |
| 260 | return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| 261 | } |
| 262 | if (!homedir.IsParent(my_files)) { |
| 263 | LOG(ERROR) << "Unexpected location of " << quote(my_files); |
| 264 | return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 265 | } |
| 266 | } |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 267 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 268 | // Bind datadir, user files and DBus communication socket into the sandbox. |
| 269 | if (!sandbox->Mount("tmpfs", "/home", "tmpfs", "mode=0755,size=1M")) { |
| 270 | LOG(ERROR) << "Cannot mount /home"; |
| 271 | return MOUNT_ERROR_INTERNAL; |
| 272 | } |
| 273 | if (!sandbox->BindMount(data_dir.value(), data_dir.value(), true, false)) { |
| 274 | LOG(ERROR) << "Cannot bind " << quote(data_dir); |
| 275 | return MOUNT_ERROR_INTERNAL; |
| 276 | } |
| 277 | if (!sandbox->BindMount(kDbusSocketPath, kDbusSocketPath, true, false)) { |
| 278 | LOG(ERROR) << "Cannot bind " << quote(kDbusSocketPath); |
| 279 | return MOUNT_ERROR_INTERNAL; |
| 280 | } |
| 281 | if (!my_files.empty()) { |
| 282 | if (!sandbox->BindMount(my_files.value(), my_files.value(), true, true)) { |
| 283 | LOG(ERROR) << "Cannot bind " << quote(my_files); |
| 284 | return MOUNT_ERROR_INTERNAL; |
| 285 | } |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 286 | } |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 287 | |
Austin Tankiang | b72ee0b | 2021-03-16 19:48:38 +1100 | [diff] [blame] | 288 | // Sandboxed processes have their own tmpfs mount, but this mount is too small |
| 289 | // for certain sqlite operations that DriveFS does. Tell DriveFS to use the |
| 290 | // datadir for sqlite temporary file storage instead. |
| 291 | sandbox->AddEnvironmentVariable("SQLITE_TMPDIR", data_dir.value()); |
| 292 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 293 | std::vector<std::string> args; |
| 294 | SetParamValue(&args, "uid", base::NumberToString(kChronosUID)); |
| 295 | SetParamValue(&args, "gid", base::NumberToString(kChronosAccessGID)); |
| 296 | SetParamValue(&args, kDataDirOptionPrefix, data_dir.value()); |
| 297 | SetParamValue(&args, kIdentityOptionPrefix, uri.path()); |
| 298 | SetParamValue(&args, kPathPrefixOptionPrefix, target_path.value()); |
| 299 | if (!my_files.empty()) { |
| 300 | SetParamValue(&args, kMyFilesOptionPrefix, my_files.value()); |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 301 | } |
Sergei Datsenko | 1bb0bdc | 2021-02-17 11:26:43 +1100 | [diff] [blame] | 302 | std::string options; |
| 303 | if (!JoinParamsIntoOptions(args, &options)) { |
| 304 | return MOUNT_ERROR_INVALID_MOUNT_OPTIONS; |
| 305 | } |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 306 | sandbox->AddArgument("-o"); |
Sergei Datsenko | 1bb0bdc | 2021-02-17 11:26:43 +1100 | [diff] [blame] | 307 | sandbox->AddArgument(options); |
Sergei Datsenko | 805d03c | 2020-08-14 15:21:19 +1000 | [diff] [blame] | 308 | |
Sergei Datsenko | f5553d1 | 2020-11-25 07:51:59 +1100 | [diff] [blame] | 309 | return MOUNT_ERROR_NONE; |
Sam McNally | dd0ff98 | 2019-06-12 18:18:36 +1000 | [diff] [blame] | 310 | } |
| 311 | |
Sam McNally | c56ae31 | 2018-05-22 13:14:27 +1000 | [diff] [blame] | 312 | } // namespace cros_disks |