4fbba9351194985ab88005f042e26cd0ebea9c3f
[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 #include <errno.h>
23
24 #include <klay/error.h>
25 #include <klay/exception.h>
26 #include <klay/filesystem.h>
27
28 #include "../../logger.h"
29 #include "../../ext4-tool.h"
30
31 #include "dmcrypt-engine.h"
32 #include "misc.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 blkcnt_t getBlockCount(const std::string &src)
48 {
49         int fd = open(src.c_str(), O_RDONLY);
50         if (fd < 0)
51                 return 0;
52
53         unsigned long long size = 0;
54         if ((ioctl(fd, BLKGETSIZE, &size)) == -1)
55                 size = 0;
56         close(fd);
57
58         return (blkcnt_t)size;
59 }
60
61 const std::string convertToHex(const BinaryData &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,
88                                                                          const std::string &mountName,
89                                                                          const BinaryData &key,
90                                                                          std::string cryptoTypeName)
91 {
92         auto blockCount = getBlockCount(realBlkDev);
93         std::string cryptoBlkDev;
94         int fd = -1;
95
96         /*
97          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
98          */
99         char dmBuf[DM_MAX_BUFFER_SIZE]; // first: for dm_io, dm_ts
100
101         // Open dm control IOCTL
102         if ((fd = open("/dev/mapper/control", O_RDWR)) < 0) {
103                 throw runtime::Exception("Cannot open device-mapper");
104         }
105
106         /*
107          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
108          * dmIo          |----------| size of dm_ioctl
109          */
110         auto dmIo = (struct dm_ioctl *)dmBuf;
111
112         // Create Device (mount_name)
113         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
114         if (ioctl(fd, DM_DEV_CREATE, dmBuf) && errno != EBUSY) {
115                 close(fd);
116                 throw runtime::Exception("Cannot create dm-crypt device");
117         }
118
119         // Get the device status, in particular, the mount_name of it's device file
120         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
121         if (ioctl(fd, DM_DEV_STATUS, dmBuf)) {
122                 close(fd);
123                 throw runtime::Exception("Cannot retrieve dm-crypt device status");
124         }
125
126         // Store created device into crypto_blkdev
127         unsigned int dmMinor = (dmIo->dev & 0xff) | ((dmIo->dev >> 12) & 0xfff00);
128         cryptoBlkDev = "/dev/dm-" + std::to_string(dmMinor);
129
130         /*
131          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
132          * dmIo          |----------| size of dm_ioctl
133          * dmTs                      |--------------| size of dm_target_spec
134          */
135         auto dmTs = (struct dm_target_spec *)(dmBuf + sizeof(struct dm_ioctl));
136
137         // Load the mapping table for this device
138
139         // Force clean-up whole dm_buffer
140         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
141         dmIo->target_count = 1;
142         dmTs->status = 0;
143         dmTs->sector_start = 0;
144         dmTs->length = blockCount;
145         ::memset(dmTs->target_type, 0, sizeof(dmTs->target_type));
146         ::strncpy(dmTs->target_type, "crypt", sizeof(dmTs->target_type) - 1);
147
148         /*
149          * dmBuf         |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
150          * dmIo          |----------| size of dm_ioctl
151          * dmTs                      |--------------| size of dm_target_spec
152          * cryptParams                               |---------------------|
153          */
154         char *cryptParams = dmBuf + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);
155
156         // Store cryptParams
157         snprintf(cryptParams, DM_MAX_BUFFER_SIZE - (cryptParams - dmBuf), "%s %s 0 %s 0", cryptoTypeName.c_str(), convertToHex(key).c_str(), realBlkDev.c_str());
158
159         cryptParams += strlen(cryptParams) + 1;
160         // Align to an 8 byte boundary
161         cryptParams = (char *)(((unsigned long)cryptParams + 7) & ~8);
162         dmTs->next = cryptParams - dmBuf;
163
164         // Table load
165         if (ioctl(fd, DM_TABLE_LOAD, dmBuf) < 0) {
166                 close(fd);
167                 throw runtime::Exception("Cannot load dm-crypt mapping table.");
168         }
169
170         // Resume this device to activate it
171         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
172         if (ioctl(fd, DM_DEV_SUSPEND, dmBuf)) {
173                 close(fd);
174                 throw runtime::Exception("Cannot resume the dm-crypt device");
175         }
176
177         close(fd);
178
179         return cryptoBlkDev;
180 }
181
182 void destroyCryptoBlkDev(const std::string &cryptoBlkDev)
183 {
184         char buf[DM_MAX_BUFFER_SIZE];
185         int fd, ret;
186
187         if ((fd = open("/dev/mapper/control", O_RDWR)) < 0)
188                 throw runtime::Exception("Cannot open device-mapper");
189
190         initDMIoctl(buf, sizeof(buf), cryptoBlkDev, 0);
191         ret = ioctl(fd, DM_DEV_REMOVE, buf);
192         int err = errno;
193         close(fd);
194
195         if (ret != 0 && err != ENXIO) {
196                 throw runtime::Exception("Cannot remove dm-crypt device");
197         }
198 }
199
200 std::string getCryptoBlkDevName(const std::string &mountName)
201 {
202         char dmBuf[DM_MAX_BUFFER_SIZE];
203         int fd;
204
205         // Open dm control IOCTL
206         if ((fd = open("/dev/mapper/control", O_RDWR)) < 0) {
207                 throw runtime::Exception("Cannot open device-mapper");
208         }
209
210         auto dmIo = (struct dm_ioctl *)dmBuf;
211
212         // Get the device status, in particular, the mount_name of it's device file
213         initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
214         int ret = ioctl(fd, DM_DEV_STATUS, dmBuf);
215         close(fd);
216
217         // No such device
218         if (ret)
219                 return std::string();
220
221         // Not opened
222         if (dmIo->open_count == 0)
223                 return std::string();
224
225         // Get the device name
226         unsigned int dmMinor = (dmIo->dev & 0xff) | ((dmIo->dev >> 12) & 0xfff00);
227         return std::string("/dev/dm-") + std::to_string(dmMinor);
228 }
229
230 BinaryData sanitizeKey(const BinaryData &key)
231 {
232         if (key.size() < DM_KEY_MIN_LEN_BYTE)
233                 throw runtime::Exception("Size of key smaller than minimum 32B");
234         BinaryData sanitized(key);
235         sanitized.resize(DM_KEY_MIN_LEN_BYTE);
236         return sanitized;
237 }
238
239 void copyInPlace(const std::string &source,     const std::string &destination,
240                                         const std::function<bool(blkcnt_t)> &isTarget,
241                                         const std::function<void(int, int)> &addProgress)
242 {
243         Ext4Tool ext4tool(source);
244         const blksize_t srcBlockSize = ext4tool.getBlockSize();
245         const blkcnt_t srcTotalBlockCount = ext4tool.getTotalBlockCount();
246         char buf[srcBlockSize];
247
248         runtime::File src(source, O_RDONLY);
249         runtime::File dst(destination, O_WRONLY);
250
251         off_t pos = 0;
252         for (blkcnt_t n = 0; n < srcTotalBlockCount; n++, pos += srcBlockSize) {
253                 if (!isTarget(n)) {
254                         continue;
255                 }
256
257                 src.lseek(pos, SEEK_SET);
258                 dst.lseek(pos, SEEK_SET);
259
260                 src.read(buf, srcBlockSize);
261                 dst.write(buf, srcBlockSize);
262
263                 addProgress(n, srcTotalBlockCount);
264         }
265 }
266
267 } // namepsace
268
269 DMCryptEngine::DMCryptEngine(const std::string &src, const std::string &dest, const ProgressBar &prgsBar) :
270         source(src), destination(dest), progress(prgsBar), start(false)
271 {
272 }
273
274 DMCryptEngine::~DMCryptEngine()
275 {
276 }
277
278 void DMCryptEngine::mount(const BinaryData &key, unsigned int options)
279 {
280         // create crypto type device mapping layer to mount the encrypted partition.
281         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
282
283         if (::mount(cryptoBlkDev.c_str(), destination.c_str(), "ext4", 0, 0) < 0)
284                 throw runtime::Exception(runtime::GetSystemErrorMessage());
285 }
286
287 void DMCryptEngine::umount()
288 {
289         if (::umount(destination.c_str()) && errno != EINVAL && errno != ENOENT)
290                 throw runtime::Exception(runtime::GetSystemErrorMessage());
291
292         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
293 }
294
295 bool DMCryptEngine::isMounted()
296 {
297         // TODO isMounted() vs. isOpened()
298         std::string cryptoDev = getCryptoBlkDevName(DM_DEFAULT_LABEL_NAME);
299         if (cryptoDev.empty())
300                 return false;
301
302         Mtab mtab;
303         struct ::mntent* entry;
304         while ((entry = mtab.next()) != NULL) {
305                 if (cryptoDev == entry->mnt_fsname && destination == entry->mnt_dir)
306                         return true;
307         }
308
309         return false;
310 }
311
312 void DMCryptEngine::encrypt(const BinaryData &key, unsigned int options)
313 {
314         // Force filesystem check via fcsf might be able to avoid fail situation during encryption.
315         Ext4Tool ext4Source(source);
316
317         for (int retry = 0; retry < 32; retry++) {
318                 try {
319                         ext4Source.forceCleanUp();
320                 } catch (runtime::Exception &e) {
321                         continue;
322                 }
323                 break;
324         }
325
326         // create crypto type device mapping layer to mount the plain partition
327         // should be encrypted here.
328         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
329         start = true;
330
331         std::function<bool(blkcnt_t)> isTarget;
332         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
333                 INFO(SINK, "FastEncryption: Disabled");
334                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4Source, _1);
335         } else {
336                 INFO(SINK, "FastEncryption: Enabled");
337                 isTarget = [](unsigned int n) {
338                         return true;
339                 };
340         }
341
342         // We always do In-place encryption
343         copyInPlace(source, cryptoBlkDev, isTarget,
344                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
345                                         &progress, _1, _2, 1));
346
347         // remove crypto type device mapper
348         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
349
350         sync();
351         progress.done();
352 }
353
354 void DMCryptEngine::decrypt(const BinaryData &key, unsigned int options)
355 {
356         // create crypto type device mapping layer to mount the plain partition
357         // should be encrypted here.
358         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
359         start = true;
360
361         // Force filesystem check via fcsf might be able to avoid fail situation during decryption.
362         Ext4Tool ext4CryptoBlkDev(cryptoBlkDev);
363
364         for (int retry = 0; retry < 32; retry++) {
365                 try {
366                         ext4CryptoBlkDev.forceCleanUp();
367                 } catch (runtime::Exception &e) {
368                         continue;
369                 }
370                 break;
371         }
372
373         std::function<bool(blkcnt_t)> isTarget;
374         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
375                 INFO(SINK, "FastEncryption: Disabled");
376                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4CryptoBlkDev, _1);
377         } else {
378                 INFO(SINK, "FastEncryption: Enabled");
379                 isTarget = [](unsigned int n) {
380                         return true;
381                 };
382         }
383
384         // We always do In-place decryption
385         copyInPlace(cryptoBlkDev, source, isTarget,
386                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
387                                         &progress, _1, _2, 1));
388
389         // remove crypto type device mapper
390         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
391
392         sync();
393         progress.done();
394 }
395
396 unsigned int DMCryptEngine::getSupportedOptions()
397 {
398         return OPTION_INCLUDE_UNUSED_REGION;
399 }
400
401 } // namespace ode