blob: 3ee091b365a957e913e5a1dc65c0c3bf23a250af [file] [log] [blame]
Sam McNallyc56ae312018-05-22 13:14:27 +10001// 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 Datsenkoabc6fb52021-02-11 14:39:09 +11007#include <stdlib.h>
8#include <sys/wait.h>
9#include <sysexits.h>
10#include <unistd.h>
11
François Degrosa28315e2020-07-13 00:24:48 +100012#include <utility>
13
Qijiang Fan713061e2021-03-08 15:45:12 +090014#include <base/check.h>
Sam McNallyf6b4ef82019-06-12 14:22:08 +100015#include <base/files/file_enumerator.h>
Sam McNallyc56ae312018-05-22 13:14:27 +100016#include <base/files/file_path.h>
17#include <base/logging.h>
18#include <base/strings/string_number_conversions.h>
Sergei Datsenkof5553d12020-11-25 07:51:59 +110019#include <base/strings/stringprintf.h>
Sam McNallyc56ae312018-05-22 13:14:27 +100020#include <base/strings/string_util.h>
Sergei Datsenkoabc6fb52021-02-11 14:39:09 +110021#include <brillo/files/safe_fd.h>
Sam McNallyc56ae312018-05-22 13:14:27 +100022
23#include "cros-disks/fuse_mounter.h"
24#include "cros-disks/mount_options.h"
25#include "cros-disks/platform.h"
François Degros8b4e31e2019-07-29 11:39:19 +100026#include "cros-disks/quote.h"
Sergei Datsenko88035aa2020-11-15 00:24:01 +110027#include "cros-disks/sandboxed_process.h"
Sam McNallyc56ae312018-05-22 13:14:27 +100028#include "cros-disks/system_mounter.h"
29#include "cros-disks/uri.h"
30
31namespace cros_disks {
32namespace {
33
Sergei Datsenkof5553d12020-11-25 07:51:59 +110034const char kDataDirOptionPrefix[] = "datadir";
35const char kIdentityOptionPrefix[] = "identity";
36const char kMyFilesOptionPrefix[] = "myfiles";
37const char kPathPrefixOptionPrefix[] = "prefix";
Sam McNallyc56ae312018-05-22 13:14:27 +100038
Sam McNallyc56ae312018-05-22 13:14:27 +100039const char kHelperTool[] = "/opt/google/drive-file-stream/drivefs";
40const char kType[] = "drivefs";
Sergei Datsenko1cf9f3d2019-01-02 14:39:48 +110041const char kDbusSocketPath[] = "/run/dbus";
Sergei Datsenkof5553d12020-11-25 07:51:59 +110042const char kHomeBaseDir[] = "/home";
Sam McNallyc56ae312018-05-22 13:14:27 +100043
Sergei Datsenkoabc6fb52021-02-11 14:39:09 +110044// UID of fuse-drivefs user.
45constexpr uid_t kOldDriveUID = 304;
46
Sergei Datsenkof5553d12020-11-25 07:51:59 +110047bool 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 Mistry25a5f852020-01-14 17:08:39 +110053 }
Sergei Datsenkof5553d12020-11-25 07:51:59 +110054 *path = base::FilePath(value);
55 return true;
56}
57
Sergei Datsenkoabc6fb52021-02-11 14:39:09 +110058error_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
83bool 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
133bool ValidateDirectory(const Platform* platform,
134 base::FilePath* dir,
135 bool fix_non_compliant) {
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100136 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 Tankianga57f5772021-05-18 18:41:56 +1000143 // 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 Datsenkof5553d12020-11-25 07:51:59 +1100163 return false;
164 }
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100165 *dir = base::FilePath(path_string);
Sergei Datsenkoabc6fb52021-02-11 14:39:09 +1100166
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100167 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(), &current_uid, nullptr)) {
176 LOG(ERROR) << "Cannot access datadir " << quote(*dir);
177 return false;
178 }
179
180 if (current_uid != kChronosUID) {
Sergei Datsenkoabc6fb52021-02-11 14:39:09 +1100181 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 Datsenkof5553d12020-11-25 07:51:59 +1100191 }
192
193 return true;
194}
Anand K Mistry25a5f852020-01-14 17:08:39 +1100195
Sam McNallyc56ae312018-05-22 13:14:27 +1000196} // namespace
197
Sergei Datsenkoa910bba2019-06-18 13:31:59 +1000198DrivefsHelper::DrivefsHelper(const Platform* platform,
199 brillo::ProcessReaper* process_reaper)
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100200 : 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 McNallyc56ae312018-05-22 13:14:27 +1000209
210DrivefsHelper::~DrivefsHelper() = default;
211
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100212bool 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 McNallyc56ae312018-05-22 13:14:27 +1000218
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100219 if (uri.path().empty())
220 *suggested_name = base::FilePath(kType);
221 else
222 *suggested_name = base::FilePath(uri.path());
223 return true;
Sam McNallyc56ae312018-05-22 13:14:27 +1000224}
225
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100226MountErrorType 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 Degrosc309d932021-02-04 15:12:30 +1100233 LOG(ERROR) << "Invalid source format " << quote(source);
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100234 return MOUNT_ERROR_INVALID_DEVICE_PATH;
235 }
236 if (uri.path().empty()) {
François Degrosc309d932021-02-04 15:12:30 +1100237 LOG(ERROR) << "Invalid source " << quote(source);
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100238 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 Datsenkoabc6fb52021-02-11 14:39:09 +1100246 if (!ValidateDirectory(platform(), &data_dir, true)) {
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100247 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 Datsenkoabc6fb52021-02-11 14:39:09 +1100258 if (!ValidateDirectory(platform(), &my_files, false)) {
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100259 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 McNallyc56ae312018-05-22 13:14:27 +1000265 }
266 }
Sam McNallyc56ae312018-05-22 13:14:27 +1000267
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100268 // 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 McNallyc56ae312018-05-22 13:14:27 +1000286 }
Sam McNallyc56ae312018-05-22 13:14:27 +1000287
Austin Tankiangb72ee0b2021-03-16 19:48:38 +1100288 // 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 Datsenkof5553d12020-11-25 07:51:59 +1100293 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 McNallyc56ae312018-05-22 13:14:27 +1000301 }
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100302 std::string options;
303 if (!JoinParamsIntoOptions(args, &options)) {
304 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
305 }
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100306 sandbox->AddArgument("-o");
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100307 sandbox->AddArgument(options);
Sergei Datsenko805d03c2020-08-14 15:21:19 +1000308
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100309 return MOUNT_ERROR_NONE;
Sam McNallydd0ff982019-06-12 18:18:36 +1000310}
311
Sam McNallyc56ae312018-05-22 13:14:27 +1000312} // namespace cros_disks