1 
2 #include "cryptsetupInterface.hpp"
3 #include "estoraged.hpp"
4 #include "filesystemInterface.hpp"
5 
6 #include <unistd.h>
7 
8 #include <sdbusplus/bus.hpp>
9 #include <sdbusplus/test/sdbus_mock.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 
12 #include <exception>
13 #include <filesystem>
14 #include <fstream>
15 #include <iterator>
16 #include <string>
17 #include <vector>
18 
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21 
22 namespace estoraged_test
23 {
24 
25 class MockFilesystemInterface : public estoraged::FilesystemInterface
26 {
27   public:
28     MOCK_METHOD(int, runMkfs, (const std::string& logicalVolume), (override));
29 
30     MOCK_METHOD(int, doMount,
31                 (const char* source, const char* target,
32                  const char* filesystemtype, unsigned long mountflags,
33                  const void* data),
34                 (override));
35 
36     MOCK_METHOD(int, doUnmount, (const char* target), (override));
37 
38     MOCK_METHOD(bool, createDirectory, (const std::filesystem::path& p),
39                 (override));
40 
41     MOCK_METHOD(bool, removeDirectory, (const std::filesystem::path& p),
42                 (override));
43 };
44 
45 class MockCryptsetupInterface : public estoraged::CryptsetupInterface
46 {
47   public:
48     MOCK_METHOD(int, cryptFormat,
49                 (struct crypt_device * cd, const char* type, const char* cipher,
50                  const char* cipher_mode, const char* uuid,
51                  const char* volume_key, size_t volume_key_size, void* params),
52                 (override));
53 
54     MOCK_METHOD(int, cryptKeyslotAddByVolumeKey,
55                 (struct crypt_device * cd, int keyslot, const char* volume_key,
56                  size_t volume_key_size, const char* passphrase,
57                  size_t passphrase_size),
58                 (override));
59 
60     MOCK_METHOD(int, cryptLoad,
61                 (struct crypt_device * cd, const char* requested_type,
62                  void* params),
63                 (override));
64 
65     MOCK_METHOD(int, cryptActivateByPassphrase,
66                 (struct crypt_device * cd, const char* name, int keyslot,
67                  const char* passphrase, size_t passphrase_size,
68                  uint32_t flags),
69                 (override));
70 
71     MOCK_METHOD(int, cryptDeactivate,
72                 (struct crypt_device * cd, const char* name), (override));
73 };
74 
75 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
76 using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
77 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
78 using std::filesystem::path;
79 using ::testing::_;
80 using ::testing::ContainsRegex;
81 using ::testing::IsNull;
82 using ::testing::Return;
83 using ::testing::StrEq;
84 
85 /*
86  * This sdbus mock object gets used in the destructor of one of the parent
87  * classes for the MockeStoraged object, so this can't be part of the
88  * eStoragedTest class.
89  */
90 sdbusplus::SdBusMock sdbusMock;
91 
92 class eStoragedTest : public testing::Test
93 {
94   public:
95     static constexpr char testFileName[] = "testfile";
96     static constexpr char testLuksDevName[] = "testfile_luksDev";
97     std::ofstream testFile;
98     std::unique_ptr<estoraged::eStoraged> esObject;
99     static constexpr auto TEST_PATH = "/test/openbmc_project/storage/test_dev";
100     static constexpr auto ESTORAGED_INTERFACE =
101         "xyz.openbmc_project.Inventory.Item.Volume";
102     sdbusplus::bus::bus bus;
103     std::string passwordString;
104     std::vector<uint8_t> password;
105     MockCryptsetupInterface* mockCryptIface;
106     MockFilesystemInterface* mockFsIface;
107 
108     eStoragedTest() :
109         bus(sdbusplus::get_mocked_new(&sdbusMock)), passwordString("password"),
110         password(passwordString.begin(), passwordString.end())
111     {}
112 
113     void SetUp() override
114     {
115         /* Create an empty file that we'll pretend is a 'storage device'. */
116         testFile.open(testFileName,
117                       std::ios::out | std::ios::binary | std::ios::trunc);
118         testFile.close();
119         if (testFile.fail())
120         {
121             throw std::runtime_error("Failed to open test file");
122         }
123 
124         EXPECT_CALL(sdbusMock,
125                     sd_bus_add_object_vtable(IsNull(), _, StrEq(TEST_PATH),
126                                              StrEq(ESTORAGED_INTERFACE), _, _))
127             .WillRepeatedly(Return(0));
128 
129         EXPECT_CALL(sdbusMock,
130                     sd_bus_emit_object_added(IsNull(), StrEq(TEST_PATH)))
131             .WillRepeatedly(Return(0));
132 
133         EXPECT_CALL(sdbusMock,
134                     sd_bus_emit_object_removed(IsNull(), StrEq(TEST_PATH)))
135             .WillRepeatedly(Return(0));
136 
137         std::unique_ptr<MockCryptsetupInterface> cryptIface =
138             std::make_unique<MockCryptsetupInterface>();
139         mockCryptIface = cryptIface.get();
140         std::unique_ptr<MockFilesystemInterface> fsIface =
141             std::make_unique<MockFilesystemInterface>();
142         mockFsIface = fsIface.get();
143 
144         esObject = std::make_unique<estoraged::eStoraged>(
145             bus, TEST_PATH, std::string(testFileName),
146             std::string(testLuksDevName), std::move(cryptIface),
147             std::move(fsIface));
148     }
149 
150     void TearDown() override
151     {
152         EXPECT_EQ(0, unlink(testFileName));
153     }
154 };
155 
156 /* Test case to format and then lock the LUKS device. */
157 TEST_F(eStoragedTest, FormatPass)
158 {
159     EXPECT_CALL(sdbusMock,
160                 sd_bus_emit_properties_changed_strv(
161                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
162         .WillRepeatedly(Return(0));
163 
164     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
165 
166     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
167         .Times(1);
168 
169     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
170 
171     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
172         .Times(1);
173 
174     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
175 
176     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
177         .WillOnce(Return(true));
178 
179     EXPECT_CALL(*mockFsIface,
180                 doMount(ContainsRegex("/dev/mapper/"),
181                         StrEq(esObject->getMountPoint()), _, _, _))
182         .WillOnce(Return(0));
183 
184     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
185         .WillOnce(Return(0));
186 
187     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
188         .WillOnce(Return(true));
189 
190     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).Times(1);
191 
192     /* Format the encrypted device. */
193     esObject->formatLuks(password, Volume::FilesystemType::ext4);
194     EXPECT_FALSE(esObject->isLocked());
195 
196     esObject->lock();
197     EXPECT_TRUE(esObject->isLocked());
198 }
199 
200 /* Test case where the device/file doesn't exist. */
201 TEST_F(eStoragedTest, FormatNoDeviceFail)
202 {
203     /* Delete the test file. */
204     EXPECT_EQ(0, unlink(testFileName));
205 
206     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
207                  ResourceNotFound);
208     EXPECT_FALSE(esObject->isLocked());
209 
210     /* Create the test file again, so that the TearDown function works. */
211     testFile.open(testFileName,
212                   std::ios::out | std::ios::binary | std::ios::trunc);
213     testFile.close();
214 }
215 
216 /* Test case where we fail to format the LUKS device. */
217 TEST_F(eStoragedTest, FormatFail)
218 {
219     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _))
220         .WillOnce(Return(-1));
221 
222     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
223                  InternalFailure);
224     EXPECT_FALSE(esObject->isLocked());
225 }
226 
227 /* Test case where we fail to set the password for the LUKS device. */
228 TEST_F(eStoragedTest, AddKeyslotFail)
229 {
230     EXPECT_CALL(sdbusMock,
231                 sd_bus_emit_properties_changed_strv(
232                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
233         .WillRepeatedly(Return(0));
234 
235     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
236 
237     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
238         .WillOnce(Return(-1));
239 
240     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
241                  InternalFailure);
242     EXPECT_TRUE(esObject->isLocked());
243 }
244 
245 /* Test case where we fail to load the LUKS header. */
246 TEST_F(eStoragedTest, LoadLuksHeaderFail)
247 {
248     EXPECT_CALL(sdbusMock,
249                 sd_bus_emit_properties_changed_strv(
250                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
251         .WillRepeatedly(Return(0));
252 
253     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
254 
255     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
256         .Times(1);
257 
258     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).WillOnce(Return(-1));
259 
260     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
261                  InternalFailure);
262     EXPECT_TRUE(esObject->isLocked());
263 }
264 
265 /* Test case where we fail to activate the LUKS device. */
266 TEST_F(eStoragedTest, ActivateFail)
267 {
268     EXPECT_CALL(sdbusMock,
269                 sd_bus_emit_properties_changed_strv(
270                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
271         .WillRepeatedly(Return(0));
272 
273     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
274 
275     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
276         .Times(1);
277 
278     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
279 
280     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
281         .WillOnce(Return(-1));
282 
283     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
284                  InternalFailure);
285     EXPECT_TRUE(esObject->isLocked());
286 }
287 
288 /* Test case where we fail to create the filesystem. */
289 TEST_F(eStoragedTest, CreateFilesystemFail)
290 {
291     EXPECT_CALL(sdbusMock,
292                 sd_bus_emit_properties_changed_strv(
293                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
294         .WillRepeatedly(Return(0));
295 
296     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
297 
298     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
299         .Times(1);
300 
301     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
302 
303     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
304         .Times(1);
305 
306     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(-1));
307 
308     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
309                  InternalFailure);
310     EXPECT_FALSE(esObject->isLocked());
311 }
312 
313 /* Test case where we fail to create the mount point. */
314 TEST_F(eStoragedTest, CreateMountPointFail)
315 {
316     EXPECT_CALL(sdbusMock,
317                 sd_bus_emit_properties_changed_strv(
318                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
319         .WillRepeatedly(Return(0));
320 
321     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
322 
323     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
324         .Times(1);
325 
326     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
327 
328     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
329         .Times(1);
330 
331     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
332 
333     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
334         .WillOnce(Return(false));
335 
336     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
337                  InternalFailure);
338     EXPECT_FALSE(esObject->isLocked());
339 }
340 
341 /* Test case where we fail to mount the filesystem. */
342 TEST_F(eStoragedTest, MountFail)
343 {
344     EXPECT_CALL(sdbusMock,
345                 sd_bus_emit_properties_changed_strv(
346                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
347         .WillRepeatedly(Return(0));
348 
349     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
350 
351     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
352         .Times(1);
353 
354     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
355 
356     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
357         .Times(1);
358 
359     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
360 
361     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
362         .WillOnce(Return(true));
363 
364     EXPECT_CALL(*mockFsIface,
365                 doMount(ContainsRegex("/dev/mapper/"),
366                         StrEq(esObject->getMountPoint()), _, _, _))
367         .WillOnce(Return(-1));
368 
369     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
370         .WillOnce(Return(true));
371 
372     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
373                  InternalFailure);
374     EXPECT_FALSE(esObject->isLocked());
375 }
376 
377 /* Test case where we fail to unmount the filesystem. */
378 TEST_F(eStoragedTest, UnmountFail)
379 {
380     EXPECT_CALL(sdbusMock,
381                 sd_bus_emit_properties_changed_strv(
382                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
383         .WillRepeatedly(Return(0));
384 
385     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
386 
387     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
388         .Times(1);
389 
390     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
391 
392     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
393         .Times(1);
394 
395     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
396 
397     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
398         .WillOnce(Return(true));
399 
400     EXPECT_CALL(*mockFsIface,
401                 doMount(ContainsRegex("/dev/mapper/"),
402                         StrEq(esObject->getMountPoint()), _, _, _))
403         .WillOnce(Return(0));
404 
405     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
406         .WillOnce(Return(-1));
407 
408     esObject->formatLuks(password, Volume::FilesystemType::ext4);
409     EXPECT_FALSE(esObject->isLocked());
410 
411     EXPECT_THROW(esObject->lock(), InternalFailure);
412     EXPECT_FALSE(esObject->isLocked());
413 }
414 
415 /* Test case where we fail to remove the mount point. */
416 TEST_F(eStoragedTest, RemoveMountPointFail)
417 {
418     EXPECT_CALL(sdbusMock,
419                 sd_bus_emit_properties_changed_strv(
420                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
421         .WillRepeatedly(Return(0));
422 
423     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
424 
425     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
426         .Times(1);
427 
428     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
429 
430     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
431         .Times(1);
432 
433     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
434 
435     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
436         .WillOnce(Return(true));
437 
438     EXPECT_CALL(*mockFsIface,
439                 doMount(ContainsRegex("/dev/mapper/"),
440                         StrEq(esObject->getMountPoint()), _, _, _))
441         .WillOnce(Return(0));
442 
443     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
444         .WillOnce(Return(0));
445 
446     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
447         .WillOnce(Return(false));
448 
449     esObject->formatLuks(password, Volume::FilesystemType::ext4);
450     EXPECT_FALSE(esObject->isLocked());
451 
452     /* This will fail to remove the mount point. */
453     EXPECT_THROW(esObject->lock(), InternalFailure);
454     EXPECT_FALSE(esObject->isLocked());
455 }
456 
457 /* Test case where we fail to deactivate the LUKS device. */
458 TEST_F(eStoragedTest, DeactivateFail)
459 {
460     EXPECT_CALL(sdbusMock,
461                 sd_bus_emit_properties_changed_strv(
462                     IsNull(), StrEq(TEST_PATH), StrEq(ESTORAGED_INTERFACE), _))
463         .WillRepeatedly(Return(0));
464 
465     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
466 
467     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
468         .Times(1);
469 
470     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
471 
472     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
473         .Times(1);
474 
475     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
476 
477     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
478         .WillOnce(Return(true));
479 
480     EXPECT_CALL(*mockFsIface,
481                 doMount(ContainsRegex("/dev/mapper/"),
482                         StrEq(esObject->getMountPoint()), _, _, _))
483         .WillOnce(Return(0));
484 
485     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
486         .WillOnce(Return(0));
487 
488     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
489         .WillOnce(Return(true));
490 
491     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).WillOnce(Return(-1));
492 
493     /* Format the encrypted device. */
494     esObject->formatLuks(password, Volume::FilesystemType::ext4);
495     EXPECT_FALSE(esObject->isLocked());
496 
497     EXPECT_THROW(esObject->lock(), InternalFailure);
498     EXPECT_FALSE(esObject->isLocked());
499 }
500 
501 } // namespace estoraged_test
502