blob: f1ab67648e04861e109928069f93bbaec14c73b7 [file] [log] [blame]
Sergei Datsenko6907a132019-04-01 11:26:56 +11001// Copyright 2019 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/fuse_mounter.h"
6
Anand K Mistrya24c75b2020-01-09 17:57:25 +11007#include <sys/mount.h>
8
Sergei Datsenko6907a132019-04-01 11:26:56 +11009#include <memory>
10#include <string>
11#include <vector>
12
Simon Glass2b1da092020-05-21 12:24:16 -060013#include <brillo/process/process_reaper.h>
Sergei Datsenko6907a132019-04-01 11:26:56 +110014#include <gmock/gmock.h>
15#include <gtest/gtest.h>
16
François Degros9eb10182020-07-13 17:08:50 +100017#include "cros-disks/error_logger.h"
Sergei Datsenko6907a132019-04-01 11:26:56 +110018#include "cros-disks/mount_options.h"
Anand K Mistry9f4611e2019-12-19 16:06:39 +110019#include "cros-disks/mount_point.h"
Sergei Datsenko6907a132019-04-01 11:26:56 +110020#include "cros-disks/platform.h"
21#include "cros-disks/sandboxed_process.h"
22
23namespace cros_disks {
24
25namespace {
26
27using testing::_;
28using testing::ElementsAre;
29using testing::Invoke;
François Degros5c6d9cb2020-07-16 13:44:44 +100030using testing::IsEmpty;
Sergei Datsenko6907a132019-04-01 11:26:56 +110031using testing::Return;
32using testing::StartsWith;
33
34const uid_t kMountUID = 200;
35const gid_t kMountGID = 201;
36const uid_t kFilesUID = 700;
37const uid_t kFilesGID = 701;
38const uid_t kFilesAccessGID = 1501;
39const char kMountUser[] = "fuse-fuse";
40const char kFUSEType[] = "fuse";
41const char kMountProgram[] = "dummy";
François Degrosac76b5a2019-12-19 15:34:06 +110042const char kSomeSource[] = "/dev/dummy";
Sergei Datsenko6907a132019-04-01 11:26:56 +110043const char kMountDir[] = "/mnt";
François Degros9eb10182020-07-13 17:08:50 +100044const int kPasswordNeededCode = 42;
Sergei Datsenko6907a132019-04-01 11:26:56 +110045
46// Mock Platform implementation for testing.
47class MockFUSEPlatform : public Platform {
48 public:
49 MockFUSEPlatform() {
50 ON_CALL(*this, GetUserAndGroupId(_, _, _))
51 .WillByDefault(Invoke(this, &MockFUSEPlatform::GetUserAndGroupIdImpl));
52 ON_CALL(*this, GetGroupId(_, _))
53 .WillByDefault(Invoke(this, &MockFUSEPlatform::GetGroupIdImpl));
54 ON_CALL(*this, PathExists(_)).WillByDefault(Return(true));
55 ON_CALL(*this, SetOwnership(_, _, _)).WillByDefault(Return(true));
56 ON_CALL(*this, SetPermissions(_, _)).WillByDefault(Return(true));
57 }
58
Ben Chanef8e6032019-09-27 08:24:56 -070059 MOCK_METHOD(bool,
60 GetUserAndGroupId,
61 (const std::string&, uid_t*, gid_t*),
62 (const, override));
63 MOCK_METHOD(bool,
64 GetGroupId,
65 (const std::string&, gid_t*),
66 (const, override));
67 MOCK_METHOD(MountErrorType,
68 Mount,
69 (const std::string&,
70 const std::string&,
71 const std::string&,
72 uint64_t,
73 const std::string&),
74 (const, override));
75 MOCK_METHOD(MountErrorType,
76 Unmount,
77 (const std::string&, int),
78 (const, override));
79 MOCK_METHOD(bool, PathExists, (const std::string&), (const, override));
80 MOCK_METHOD(bool,
81 RemoveEmptyDirectory,
82 (const std::string&),
83 (const, override));
84 MOCK_METHOD(bool,
85 SetOwnership,
86 (const std::string&, uid_t, gid_t),
87 (const, override));
88 MOCK_METHOD(bool,
89 GetOwnership,
90 (const std::string&, uid_t*, gid_t*),
91 (const, override));
92 MOCK_METHOD(bool,
93 SetPermissions,
94 (const std::string&, mode_t),
95 (const, override));
Sergei Datsenko6907a132019-04-01 11:26:56 +110096
97 private:
98 bool GetUserAndGroupIdImpl(const std::string& user,
99 uid_t* user_id,
100 gid_t* group_id) const {
101 if (user == "chronos") {
102 if (user_id)
103 *user_id = kFilesUID;
104 if (group_id)
105 *group_id = kFilesGID;
106 return true;
107 }
108 if (user == kMountUser) {
109 if (user_id)
110 *user_id = kMountUID;
111 if (group_id)
112 *group_id = kMountGID;
113 return true;
114 }
115 return false;
116 }
117
118 bool GetGroupIdImpl(const std::string& group, gid_t* group_id) const {
119 if (group == "chronos-access") {
120 if (group_id)
121 *group_id = kFilesAccessGID;
122 return true;
123 }
124 return false;
125 }
126};
127
128class MockSandboxedProcess : public SandboxedProcess {
129 public:
130 MockSandboxedProcess() = default;
Ben Chan21150af2019-09-11 17:04:07 -0700131 MOCK_METHOD(pid_t,
132 StartImpl,
François Degros1ef69942019-10-01 15:31:17 +1000133 (base::ScopedFD, base::ScopedFD, base::ScopedFD),
Ben Chan21150af2019-09-11 17:04:07 -0700134 (override));
135 MOCK_METHOD(int, WaitImpl, (), (override));
François Degros92bbea42019-09-13 10:42:52 +1000136 MOCK_METHOD(int, WaitNonBlockingImpl, (), (override));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100137};
138
139class FUSEMounterForTesting : public FUSEMounter {
140 public:
Sergei Datsenkoa910bba2019-06-18 13:31:59 +1000141 FUSEMounterForTesting(const Platform* platform,
142 brillo::ProcessReaper* process_reaper)
François Degrosa28315e2020-07-13 00:24:48 +1000143 : FUSEMounter({.filesystem_type = kFUSEType,
144 .mount_program = kMountProgram,
145 .mount_user = kMountUser,
François Degros9eb10182020-07-13 17:08:50 +1000146 .password_needed_code = kPasswordNeededCode,
François Degrosa28315e2020-07-13 00:24:48 +1000147 .platform = platform,
148 .process_reaper = process_reaper}) {}
Sergei Datsenko6907a132019-04-01 11:26:56 +1100149
François Degros5c6d9cb2020-07-16 13:44:44 +1000150 MOCK_METHOD(int, OnEnvironment, (const std::vector<std::string>&), (const));
Ben Chanef8e6032019-09-27 08:24:56 -0700151 MOCK_METHOD(int, InvokeMountTool, (const std::vector<std::string>&), (const));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100152
François Degros5c6d9cb2020-07-16 13:44:44 +1000153 mutable std::vector<std::string> environment;
154
Sergei Datsenko6907a132019-04-01 11:26:56 +1100155 private:
156 std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const override {
157 auto mock = std::make_unique<MockSandboxedProcess>();
François Degros5c6d9cb2020-07-16 13:44:44 +1000158 const SandboxedProcess* const process = mock.get();
François Degros5593b8c2019-07-25 12:27:42 +1000159 ON_CALL(*mock, StartImpl(_, _, _)).WillByDefault(Return(123));
François Degros1ef69942019-10-01 15:31:17 +1000160 ON_CALL(*mock, WaitNonBlockingImpl())
François Degros5c6d9cb2020-07-16 13:44:44 +1000161 .WillByDefault(Invoke([this, process]() {
162 const auto& environment = process->environment();
163 if (!environment.empty())
164 OnEnvironment(environment);
165
166 return InvokeMountTool(process->arguments());
François Degros1ef69942019-10-01 15:31:17 +1000167 }));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100168 return mock;
169 }
170};
171
172} // namespace
173
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100174class FUSEMounterTest : public ::testing::Test {
175 public:
176 FUSEMounterTest() : mounter_(&platform_, &process_reaper_) {
177 ON_CALL(platform_, Mount(kSomeSource, kMountDir, _, _, _))
178 .WillByDefault(Return(MOUNT_ERROR_NONE));
179 }
Sergei Datsenko6907a132019-04-01 11:26:56 +1100180
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100181 protected:
182 // Sets up mock expectations for a successful mount.
183 void SetupMountExpectations() {
184 EXPECT_CALL(mounter_, InvokeMountTool(ElementsAre(
185 kMountProgram, "-o", MountOptions().ToString(),
186 kSomeSource, StartsWith("/dev/fd/"))))
187 .WillOnce(Return(0));
188 EXPECT_CALL(platform_, PathExists(kMountProgram)).WillOnce(Return(true));
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100189 EXPECT_CALL(platform_, SetOwnership(kSomeSource, getuid(), kMountGID))
190 .WillOnce(Return(true));
191 EXPECT_CALL(platform_, SetPermissions(kSomeSource, S_IRUSR | S_IWUSR |
192 S_IRGRP | S_IWGRP))
193 .WillOnce(Return(true));
194 EXPECT_CALL(platform_, SetOwnership(kMountDir, _, _)).Times(0);
195 EXPECT_CALL(platform_, SetPermissions(kMountDir, _)).Times(0);
196 }
197
198 MockFUSEPlatform platform_;
199 brillo::ProcessReaper process_reaper_;
200 FUSEMounterForTesting mounter_;
201};
202
203TEST_F(FUSEMounterTest, Sandboxing_Unprivileged) {
204 SetupMountExpectations();
205 // The MountPoint returned by Mount() will unmount when it is destructed.
206 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
Sergei Datsenkob362e4a2019-04-03 17:23:24 +1100207 .WillOnce(Return(MOUNT_ERROR_NONE));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100208
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100209 MountErrorType error = MOUNT_ERROR_UNKNOWN;
210 auto mount_point =
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100211 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100212 EXPECT_TRUE(mount_point);
213 EXPECT_EQ(MOUNT_ERROR_NONE, error);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100214}
215
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100216TEST_F(FUSEMounterTest, MountPoint_UnmountTwice) {
217 SetupMountExpectations();
218 // Even though Unmount() is called twice, the underlying unmount should only
219 // be done once.
220 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
Sergei Datsenko97662fc2019-06-24 14:05:11 +1000221 .WillOnce(Return(MOUNT_ERROR_NONE));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100222
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100223 MountErrorType error = MOUNT_ERROR_UNKNOWN;
224 auto mount_point =
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100225 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
226 EXPECT_TRUE(mount_point);
227 EXPECT_EQ(MOUNT_ERROR_NONE, error);
228
229 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
230 EXPECT_EQ(MOUNT_ERROR_PATH_NOT_MOUNTED, mount_point->Unmount());
231}
232
233TEST_F(FUSEMounterTest, MountPoint_UnmountFailure) {
234 SetupMountExpectations();
235 // If an Unmount fails, we should be able to retry.
236 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
237 .WillOnce(Return(MOUNT_ERROR_UNKNOWN))
238 .WillOnce(Return(MOUNT_ERROR_NONE));
239
240 MountErrorType error = MOUNT_ERROR_UNKNOWN;
241 auto mount_point =
242 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
243 EXPECT_TRUE(mount_point);
244 EXPECT_EQ(MOUNT_ERROR_NONE, error);
245
246 EXPECT_EQ(MOUNT_ERROR_UNKNOWN, mount_point->Unmount());
247 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
248}
249
250TEST_F(FUSEMounterTest, MountPoint_UnmountBusy) {
251 SetupMountExpectations();
252 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
253 .WillOnce(Return(MOUNT_ERROR_PATH_ALREADY_MOUNTED));
254 EXPECT_CALL(platform_, Unmount(kMountDir, MNT_FORCE | MNT_DETACH))
255 .WillOnce(Return(MOUNT_ERROR_NONE));
256
257 MountErrorType error = MOUNT_ERROR_UNKNOWN;
258 auto mount_point =
259 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
260 EXPECT_TRUE(mount_point);
261 EXPECT_EQ(MOUNT_ERROR_NONE, error);
262
263 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
264}
265
266TEST_F(FUSEMounterTest, AppFailed) {
267 EXPECT_CALL(platform_, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE));
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100268 EXPECT_CALL(mounter_, InvokeMountTool(_)).WillOnce(Return(1));
269
270 MountErrorType error = MOUNT_ERROR_UNKNOWN;
271 auto mount_point =
272 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100273 EXPECT_FALSE(mount_point);
274 EXPECT_EQ(MOUNT_ERROR_MOUNT_PROGRAM_FAILED, error);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100275}
276
François Degros9eb10182020-07-13 17:08:50 +1000277TEST_F(FUSEMounterTest, AppNeedsPassword) {
278 EXPECT_CALL(platform_, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE));
279 EXPECT_CALL(mounter_, InvokeMountTool(_))
280 .WillOnce(Return(kPasswordNeededCode));
281
282 MountErrorType error = MOUNT_ERROR_UNKNOWN;
283 auto mount_point =
284 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
285 EXPECT_FALSE(mount_point);
286 EXPECT_EQ(MOUNT_ERROR_NEED_PASSWORD, error);
287}
288
François Degros5c6d9cb2020-07-16 13:44:44 +1000289TEST_F(FUSEMounterTest, WithPassword) {
290 const std::string password = "My Password";
291
292 SetupMountExpectations();
293 EXPECT_CALL(mounter_, OnEnvironment(ElementsAre("PASSWORD=" + password)))
294 .Times(1);
295 // The MountPoint returned by Mount() will unmount when it is destructed.
296 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
297 .WillOnce(Return(MOUNT_ERROR_NONE));
298
299 MountErrorType error = MOUNT_ERROR_UNKNOWN;
300 auto mount_point = mounter_.Mount(kSomeSource, base::FilePath(kMountDir),
301 {"password=" + password}, &error);
302 EXPECT_TRUE(mount_point);
303 EXPECT_EQ(MOUNT_ERROR_NONE, error);
304}
305
306TEST(FUSEMounterPasswordTest, NoPassword) {
307 const FUSEMounter mounter({.password_needed_code = kPasswordNeededCode});
308 SandboxedProcess process;
309 mounter.CopyPassword(
310 {
311 "Password=1", // Options are case sensitive
312 "password =2", // Space is significant
313 " password=3", // Space is significant
314 "password", // Not a valid option
315 },
316 &process);
317 EXPECT_THAT(process.environment(), IsEmpty());
318}
319
320TEST(FUSEMounterPasswordTest, CopiesPassword) {
321 const FUSEMounter mounter({.password_needed_code = kPasswordNeededCode});
322 for (const std::string password : {
323 "",
324 " ",
325 "=",
326 "simple",
327 R"( !@#$%^&*()_-+={[}]|\:;"'<,>.?/ )",
328 }) {
329 SandboxedProcess process;
330 mounter.CopyPassword({"password=" + password}, &process);
331 EXPECT_THAT(process.environment(), ElementsAre("PASSWORD=" + password));
332 }
333}
334
335TEST(FUSEMounterPasswordTest, FirstPassword) {
336 const FUSEMounter mounter({.password_needed_code = kPasswordNeededCode});
337 SandboxedProcess process;
338 mounter.CopyPassword({"other1=value1", "password=1", "password=2",
339 "other2=value2", "password=3"},
340 &process);
341 EXPECT_THAT(process.environment(), ElementsAre("PASSWORD=1"));
342}
343
344TEST(FUSEMounterPasswordTest, IgnoredIfNotNeeded) {
345 const FUSEMounter mounter({});
346 SandboxedProcess process;
347 mounter.CopyPassword({"password=dummy"}, &process);
348 EXPECT_THAT(process.environment(), IsEmpty());
349}
350
Sergei Datsenko6907a132019-04-01 11:26:56 +1100351} // namespace cros_disks