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