Change to allow duplicated mount/umount API calls for lockscreen
[platform/core/security/ode.git] / server / engine / encryption / dmcrypt-engine.cpp
1 /*
2  *  Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License
15  */
16 #include <iomanip>
17
18 #include <linux/dm-ioctl.h>
19 #include <sys/mount.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22
23 #include <klay/audit/logger.h>
24
25 #include <klay/error.h>
26 #include <klay/exception.h>
27 #include <klay/filesystem.h>
28
29 #include "../../file-footer.h"
30 #include "../../ext4-tool.h"
31
32 #include "dmcrypt-engine.h"
33
34 #define DM_MAX_BUFFER_SIZE              4096
35 #define DM_KEY_MIN_LEN_BYTE             32
36 #define DM_DEFAULT_LABEL_NAME   "userdata"
37 #define DM_DEFAULT_CRYPTO_NAME  "aes-cbc-essiv:sha256"
38
39 #define OPTION_INCLUDE_UNUSED_REGION (1 << 0)
40
41 using namespace std::placeholders;
42
43 namespace ode {
44
45 namespace {
46
47 size_t getFileSystemSize(const std::string &src)
48 {
49         int fd = open(src.c_str(), O_RDONLY);
50         if (fd < 0)
51                 return 0;
52
53         size_t size = 0;
54         if ((ioctl(fd, BLKGETSIZE, &size)) == -1)
55                 size = 0;
56         close(fd);
57
58         return size;
59 }
60
61 const std::string convertToHex(const DMCryptEngine::data &binary)
62 {
63         std::stringstream hex;
64
65         hex << std::hex << std::setfill('0');
66         for (unsigned int byte : binary) {
67                 hex << std::setw(2) << byte;
68         }
69         return hex.str();
70 }
71
72 void initDMIoctl(char *buf, size_t size, const std::string &name, unsigned flags)
73 {
74         struct dm_ioctl *io = (struct dm_ioctl *)buf;
75
76         ::memset(io, 0, size);
77         io->data_size = size;
78         io->data_start = sizeof(struct dm_ioctl);
79         io->version[0] = 4;
80         io->version[1] = 0;
81         io->version[2] = 0;
82         io->flags = flags;
83         ::memset(io->name, 0, sizeof(io->name));
84         ::strncpy(io->name, name.c_str(), sizeof(io->name) - 1);
85 }
86
87 const std::string createCryptoBlkDev(const std::string &realBlkDev, const std::string &mountName, const DMCryptEngine::data &key, std::string cryptoTypeName)
88 {
89         size_t fileSystemSize = getFileSystemSize(realBlkDev);
90         std::string cryptoBlkDev;
91         int fd = -1;
92
93         /*
94          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
95          */
96         char dmBuf[DM_MAX_BUFFER_SIZE]; // first: for dm_io, dm_ts
97
98         // Open dm control IOCTL
99         if ((fd = open("/dev/mapper/control", O_RDWR)) < 0) {
100                 throw runtime::Exception("Cannot open device-mapper");
101         }
102
103         /*
104          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
105          * dmIo            |----------| size of dm_ioctl
106          */
107         auto dmIo = (struct dm_ioctl *)dmBuf;
108
109         // Create Device (mount_name)
110         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
111         if (ioctl(fd, DM_DEV_CREATE, dmBuf)) {
112                 close(fd);
113                 throw runtime::Exception("Cannot create dm-crypt device");
114         }
115
116         // Get the device status, in particular, the mount_name of it's device file
117         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
118         if (ioctl(fd, DM_DEV_STATUS, dmBuf)) {
119                 close(fd);
120                 throw runtime::Exception("Cannot retrieve dm-crypt device status");
121         }
122
123         // Store created device into crypto_blkdev
124         unsigned int dmMinor = (dmIo->dev & 0xff) | ((dmIo->dev >> 12) & 0xfff00);
125         cryptoBlkDev = "/dev/dm-" + std::to_string(dmMinor);
126
127         /*
128          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
129          * dmIo            |----------| size of dm_ioctl
130          * dmTs                                |--------------| size of dm_Target_spec
131          */
132         auto dmTs = (struct dm_target_spec *)(dmBuf + sizeof(struct dm_ioctl));
133
134         // Load the mapping table for this device
135
136         // Force clean-up whole dm_buffer
137         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
138         dmIo->target_count = 1;
139         dmTs->status = 0;
140         dmTs->sector_start = 0;
141         dmTs->length = fileSystemSize;
142         ::memset(dmTs->target_type, 0, sizeof(dmTs->target_type));
143         ::strncpy(dmTs->target_type, "crypt", sizeof(dmTs->target_type) - 1);
144
145         /*
146          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
147          * dmIo            |----------| size of dm_ioctl
148          * dmTs                                |--------------| size of dm_Target_spec
149          * cryptParams                                                |---------------------|
150          */
151         char *cryptParams = dmBuf + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);
152
153         // Store cryptParams
154         snprintf(cryptParams, DM_MAX_BUFFER_SIZE - (cryptParams - dmBuf), "%s %s 0 %s 0", cryptoTypeName.c_str(), convertToHex(key).c_str(), realBlkDev.c_str());
155
156         cryptParams += strlen(cryptParams) + 1;
157         // Align to an 8 byte boundary
158         cryptParams = (char *)(((unsigned long)cryptParams + 7) & ~8);
159         dmTs->next = cryptParams - dmBuf;
160
161         // Table load
162         if (ioctl(fd, DM_TABLE_LOAD, dmBuf) < 0) {
163                 close(fd);
164                 throw runtime::Exception("Cannot load dm-crypt mapping table.");
165         }
166
167         // Resume this device to activate it
168         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
169         if (ioctl(fd, DM_DEV_SUSPEND, dmBuf)) {
170                 close(fd);
171                 throw runtime::Exception("Cannot resume the dm-crypt device");
172         }
173
174         close(fd);
175
176         return cryptoBlkDev;
177 }
178
179 void destroyCryptoBlkDev(const std::string &cryptoBlkDev)
180 {
181         char buf[DM_MAX_BUFFER_SIZE];
182         int fd, ret;
183
184         if ((fd = open("/dev/mapper/control", O_RDWR)) < 0)
185                 throw runtime::Exception("Cannot open device-mapper");
186
187         initDMIoctl(buf, sizeof(buf), cryptoBlkDev, 0);
188         ret = ioctl(fd, DM_DEV_REMOVE, buf);
189         close(fd);
190
191         if (ret != 0) {
192                 throw runtime::Exception("Cannot remove dm-crypt device");
193         }
194 }
195
196 ode::DMCryptEngine::data sanitizeKey(const ode::DMCryptEngine::data &key)
197 {
198         ode::DMCryptEngine::data sanitized(key);
199         sanitized.resize(DM_KEY_MIN_LEN_BYTE);
200         return sanitized;
201 }
202
203 void copyInPlace(const std::string &source,     const std::string &destination,
204                                         const std::function<bool(unsigned int)> &isTarget,
205                                         const std::function<void(int, int)> &addProgress)
206 {
207         Ext4Tool ext4tool(source);
208         const size_t srcBlockSize = ext4tool.getBlockSize();
209         const size_t srcTotalBlockCount = ext4tool.getTotalBlockCount();
210         char buf[srcBlockSize];
211
212         runtime::File src(source, O_RDONLY);
213         runtime::File dst(destination, O_WRONLY);
214
215         for (size_t n = 0, pos = 0; n < srcTotalBlockCount;
216                         n++, pos += srcBlockSize) {
217                 if (!isTarget(n)) {
218                         continue;
219                 }
220
221                 src.lseek(pos, SEEK_SET);
222                 dst.lseek(pos, SEEK_SET);
223
224                 src.read(buf, srcBlockSize);
225                 dst.write(buf, srcBlockSize);
226
227                 addProgress(n, srcTotalBlockCount);
228         }
229 }
230
231 } // namepsace
232
233 DMCryptEngine::DMCryptEngine(const std::string &src, const std::string &dest, const ProgressBar &prgsBar) :
234         source(src), destination(dest), progress(prgsBar)
235 {
236 }
237
238 DMCryptEngine::~DMCryptEngine()
239 {
240 }
241
242 void DMCryptEngine::mount(const DMCryptEngine::data &key, unsigned int options)
243 {
244         // create crypto type device mapping layer to mount the encrypted partition.
245         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
246
247         if (::mount(cryptoBlkDev.c_str(), destination.c_str(), "ext4", 0, 0) < 0)
248                 throw runtime::Exception(runtime::GetSystemErrorMessage());
249
250         mounted = true;
251 }
252
253 void DMCryptEngine::umount()
254 {
255         if (::umount(destination.c_str()))
256                 throw runtime::Exception(runtime::GetSystemErrorMessage());
257
258         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
259
260         mounted = false;
261 }
262
263 bool DMCryptEngine::isMounted()
264 {
265         return mounted;
266 }
267
268 void DMCryptEngine::encrypt(const DMCryptEngine::data &key, unsigned int options)
269 {
270         // Force filesystem check via fcsf might be able to avoid fail situation during encryption.
271         Ext4Tool ext4Source(source);
272         ext4Source.forceCleanUp();
273
274         // create crypto type device mapping layer to mount the plain partition
275         // should be encrypted here.
276         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
277
278         std::function<bool(unsigned int)> isTarget;
279         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
280                 INFO("FastEncryption: Disabled");
281                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4Source, _1);
282         } else {
283                 INFO("FastEncryption: Enabled");
284                 isTarget = [](unsigned int n) {
285                         return true;
286                 };
287         }
288
289         // We always do In-place encryption
290         copyInPlace(source, cryptoBlkDev, isTarget,
291                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
292                                         &progress, _1, _2, 1));
293
294         // remove crypto type device mapper
295         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
296
297         sync();
298         progress.done();
299 }
300
301 void DMCryptEngine::decrypt(const DMCryptEngine::data &key, unsigned int options)
302 {
303         // create crypto type device mapping layer to mount the plain partition
304         // should be encrypted here.
305         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
306
307         // Force filesystem check via fcsf might be able to avoid fail situation during decryption.
308         Ext4Tool ext4CryptoBlkDev(cryptoBlkDev);
309         ext4CryptoBlkDev.forceCleanUp();
310
311         std::function<bool(unsigned int)> isTarget;
312         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
313                 INFO("FastEncryption: Disabled");
314                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4CryptoBlkDev, _1);
315         } else {
316                 INFO("FastEncryption: Enabled");
317                 isTarget = [](unsigned int n) {
318                         return true;
319                 };
320         }
321
322         // We always do In-place decryption
323         copyInPlace(cryptoBlkDev, source, isTarget,
324                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
325                                         &progress, _1, _2, 1));
326
327         // remove crypto type device mapper
328         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
329
330         sync();
331         progress.done();
332 }
333
334 bool DMCryptEngine::isKeyMetaSet()
335 {
336         return FileFooter::exist(source);
337 }
338
339 const DMCryptEngine::data DMCryptEngine::getKeyMeta()
340 {
341         return FileFooter::read(source);
342 }
343
344 void DMCryptEngine::setKeyMeta(const data &meta)
345 {
346         FileFooter::write(source, meta);
347 }
348
349 void DMCryptEngine::clearKeyMeta()
350 {
351         FileFooter::clear(source);
352 }
353
354 unsigned int DMCryptEngine::getSupportedOptions()
355 {
356         return OPTION_INCLUDE_UNUSED_REGION;
357 }
358
359 } // namespace ode