blob: 5f995d6a31dac2207d45c72b6d07b0fc2e8c777e [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;
30using testing::Return;
31using testing::StartsWith;
32
33const uid_t kMountUID = 200;
34const gid_t kMountGID = 201;
35const uid_t kFilesUID = 700;
36const uid_t kFilesGID = 701;
37const uid_t kFilesAccessGID = 1501;
38const char kMountUser[] = "fuse-fuse";
39const char kFUSEType[] = "fuse";
40const char kMountProgram[] = "dummy";
François Degrosac76b5a2019-12-19 15:34:06 +110041const char kSomeSource[] = "/dev/dummy";
Sergei Datsenko6907a132019-04-01 11:26:56 +110042const char kMountDir[] = "/mnt";
François Degros9eb10182020-07-13 17:08:50 +100043const int kPasswordNeededCode = 42;
Sergei Datsenko6907a132019-04-01 11:26:56 +110044
45// Mock Platform implementation for testing.
46class MockFUSEPlatform : public Platform {
47 public:
48 MockFUSEPlatform() {
49 ON_CALL(*this, GetUserAndGroupId(_, _, _))
50 .WillByDefault(Invoke(this, &MockFUSEPlatform::GetUserAndGroupIdImpl));
51 ON_CALL(*this, GetGroupId(_, _))
52 .WillByDefault(Invoke(this, &MockFUSEPlatform::GetGroupIdImpl));
53 ON_CALL(*this, PathExists(_)).WillByDefault(Return(true));
54 ON_CALL(*this, SetOwnership(_, _, _)).WillByDefault(Return(true));
55 ON_CALL(*this, SetPermissions(_, _)).WillByDefault(Return(true));
56 }
57
Ben Chanef8e6032019-09-27 08:24:56 -070058 MOCK_METHOD(bool,
59 GetUserAndGroupId,
60 (const std::string&, uid_t*, gid_t*),
61 (const, override));
62 MOCK_METHOD(bool,
63 GetGroupId,
64 (const std::string&, gid_t*),
65 (const, override));
66 MOCK_METHOD(MountErrorType,
67 Mount,
68 (const std::string&,
69 const std::string&,
70 const std::string&,
71 uint64_t,
72 const std::string&),
73 (const, override));
74 MOCK_METHOD(MountErrorType,
75 Unmount,
76 (const std::string&, int),
77 (const, override));
78 MOCK_METHOD(bool, PathExists, (const std::string&), (const, override));
79 MOCK_METHOD(bool,
80 RemoveEmptyDirectory,
81 (const std::string&),
82 (const, override));
83 MOCK_METHOD(bool,
84 SetOwnership,
85 (const std::string&, uid_t, gid_t),
86 (const, override));
87 MOCK_METHOD(bool,
88 GetOwnership,
89 (const std::string&, uid_t*, gid_t*),
90 (const, override));
91 MOCK_METHOD(bool,
92 SetPermissions,
93 (const std::string&, mode_t),
94 (const, override));
Sergei Datsenko6907a132019-04-01 11:26:56 +110095
96 private:
97 bool GetUserAndGroupIdImpl(const std::string& user,
98 uid_t* user_id,
99 gid_t* group_id) const {
100 if (user == "chronos") {
101 if (user_id)
102 *user_id = kFilesUID;
103 if (group_id)
104 *group_id = kFilesGID;
105 return true;
106 }
107 if (user == kMountUser) {
108 if (user_id)
109 *user_id = kMountUID;
110 if (group_id)
111 *group_id = kMountGID;
112 return true;
113 }
114 return false;
115 }
116
117 bool GetGroupIdImpl(const std::string& group, gid_t* group_id) const {
118 if (group == "chronos-access") {
119 if (group_id)
120 *group_id = kFilesAccessGID;
121 return true;
122 }
123 return false;
124 }
125};
126
127class MockSandboxedProcess : public SandboxedProcess {
128 public:
129 MockSandboxedProcess() = default;
Ben Chan21150af2019-09-11 17:04:07 -0700130 MOCK_METHOD(pid_t,
131 StartImpl,
François Degros1ef69942019-10-01 15:31:17 +1000132 (base::ScopedFD, base::ScopedFD, base::ScopedFD),
Ben Chan21150af2019-09-11 17:04:07 -0700133 (override));
134 MOCK_METHOD(int, WaitImpl, (), (override));
François Degros92bbea42019-09-13 10:42:52 +1000135 MOCK_METHOD(int, WaitNonBlockingImpl, (), (override));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100136};
137
138class FUSEMounterForTesting : public FUSEMounter {
139 public:
Sergei Datsenkoa910bba2019-06-18 13:31:59 +1000140 FUSEMounterForTesting(const Platform* platform,
141 brillo::ProcessReaper* process_reaper)
François Degrosa28315e2020-07-13 00:24:48 +1000142 : FUSEMounter({.filesystem_type = kFUSEType,
143 .mount_program = kMountProgram,
144 .mount_user = kMountUser,
François Degros9eb10182020-07-13 17:08:50 +1000145 .password_needed_code = kPasswordNeededCode,
François Degrosa28315e2020-07-13 00:24:48 +1000146 .platform = platform,
147 .process_reaper = process_reaper}) {}
Sergei Datsenko6907a132019-04-01 11:26:56 +1100148
Ben Chanef8e6032019-09-27 08:24:56 -0700149 MOCK_METHOD(int, InvokeMountTool, (const std::vector<std::string>&), (const));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100150
151 private:
152 std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const override {
153 auto mock = std::make_unique<MockSandboxedProcess>();
154 auto* raw_ptr = mock.get();
François Degros5593b8c2019-07-25 12:27:42 +1000155 ON_CALL(*mock, StartImpl(_, _, _)).WillByDefault(Return(123));
François Degros1ef69942019-10-01 15:31:17 +1000156 ON_CALL(*mock, WaitNonBlockingImpl())
157 .WillByDefault(Invoke([raw_ptr, this]() {
158 return InvokeMountTool(raw_ptr->arguments());
159 }));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100160 return mock;
161 }
162};
163
164} // namespace
165
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100166class FUSEMounterTest : public ::testing::Test {
167 public:
168 FUSEMounterTest() : mounter_(&platform_, &process_reaper_) {
169 ON_CALL(platform_, Mount(kSomeSource, kMountDir, _, _, _))
170 .WillByDefault(Return(MOUNT_ERROR_NONE));
171 }
Sergei Datsenko6907a132019-04-01 11:26:56 +1100172
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100173 protected:
174 // Sets up mock expectations for a successful mount.
175 void SetupMountExpectations() {
176 EXPECT_CALL(mounter_, InvokeMountTool(ElementsAre(
177 kMountProgram, "-o", MountOptions().ToString(),
178 kSomeSource, StartsWith("/dev/fd/"))))
179 .WillOnce(Return(0));
180 EXPECT_CALL(platform_, PathExists(kMountProgram)).WillOnce(Return(true));
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100181 EXPECT_CALL(platform_, SetOwnership(kSomeSource, getuid(), kMountGID))
182 .WillOnce(Return(true));
183 EXPECT_CALL(platform_, SetPermissions(kSomeSource, S_IRUSR | S_IWUSR |
184 S_IRGRP | S_IWGRP))
185 .WillOnce(Return(true));
186 EXPECT_CALL(platform_, SetOwnership(kMountDir, _, _)).Times(0);
187 EXPECT_CALL(platform_, SetPermissions(kMountDir, _)).Times(0);
188 }
189
190 MockFUSEPlatform platform_;
191 brillo::ProcessReaper process_reaper_;
192 FUSEMounterForTesting mounter_;
193};
194
195TEST_F(FUSEMounterTest, Sandboxing_Unprivileged) {
196 SetupMountExpectations();
197 // The MountPoint returned by Mount() will unmount when it is destructed.
198 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
Sergei Datsenkob362e4a2019-04-03 17:23:24 +1100199 .WillOnce(Return(MOUNT_ERROR_NONE));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100200
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100201 MountErrorType error = MOUNT_ERROR_UNKNOWN;
202 auto mount_point =
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100203 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100204 EXPECT_TRUE(mount_point);
205 EXPECT_EQ(MOUNT_ERROR_NONE, error);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100206}
207
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100208TEST_F(FUSEMounterTest, MountPoint_UnmountTwice) {
209 SetupMountExpectations();
210 // Even though Unmount() is called twice, the underlying unmount should only
211 // be done once.
212 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
Sergei Datsenko97662fc2019-06-24 14:05:11 +1000213 .WillOnce(Return(MOUNT_ERROR_NONE));
Sergei Datsenko6907a132019-04-01 11:26:56 +1100214
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100215 MountErrorType error = MOUNT_ERROR_UNKNOWN;
216 auto mount_point =
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100217 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
218 EXPECT_TRUE(mount_point);
219 EXPECT_EQ(MOUNT_ERROR_NONE, error);
220
221 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
222 EXPECT_EQ(MOUNT_ERROR_PATH_NOT_MOUNTED, mount_point->Unmount());
223}
224
225TEST_F(FUSEMounterTest, MountPoint_UnmountFailure) {
226 SetupMountExpectations();
227 // If an Unmount fails, we should be able to retry.
228 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
229 .WillOnce(Return(MOUNT_ERROR_UNKNOWN))
230 .WillOnce(Return(MOUNT_ERROR_NONE));
231
232 MountErrorType error = MOUNT_ERROR_UNKNOWN;
233 auto mount_point =
234 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
235 EXPECT_TRUE(mount_point);
236 EXPECT_EQ(MOUNT_ERROR_NONE, error);
237
238 EXPECT_EQ(MOUNT_ERROR_UNKNOWN, mount_point->Unmount());
239 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
240}
241
242TEST_F(FUSEMounterTest, MountPoint_UnmountBusy) {
243 SetupMountExpectations();
244 EXPECT_CALL(platform_, Unmount(kMountDir, 0))
245 .WillOnce(Return(MOUNT_ERROR_PATH_ALREADY_MOUNTED));
246 EXPECT_CALL(platform_, Unmount(kMountDir, MNT_FORCE | MNT_DETACH))
247 .WillOnce(Return(MOUNT_ERROR_NONE));
248
249 MountErrorType error = MOUNT_ERROR_UNKNOWN;
250 auto mount_point =
251 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
252 EXPECT_TRUE(mount_point);
253 EXPECT_EQ(MOUNT_ERROR_NONE, error);
254
255 EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
256}
257
258TEST_F(FUSEMounterTest, AppFailed) {
259 EXPECT_CALL(platform_, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE));
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100260 EXPECT_CALL(mounter_, InvokeMountTool(_)).WillOnce(Return(1));
261
262 MountErrorType error = MOUNT_ERROR_UNKNOWN;
263 auto mount_point =
264 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
Anand K Mistry9f4611e2019-12-19 16:06:39 +1100265 EXPECT_FALSE(mount_point);
266 EXPECT_EQ(MOUNT_ERROR_MOUNT_PROGRAM_FAILED, error);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100267}
268
François Degros9eb10182020-07-13 17:08:50 +1000269TEST_F(FUSEMounterTest, AppNeedsPassword) {
270 EXPECT_CALL(platform_, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE));
271 EXPECT_CALL(mounter_, InvokeMountTool(_))
272 .WillOnce(Return(kPasswordNeededCode));
273
274 MountErrorType error = MOUNT_ERROR_UNKNOWN;
275 auto mount_point =
276 mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
277 EXPECT_FALSE(mount_point);
278 EXPECT_EQ(MOUNT_ERROR_NEED_PASSWORD, error);
279}
280
Sergei Datsenko6907a132019-04-01 11:26:56 +1100281} // namespace cros_disks