4d57c54dce67a0ae944b6adf89d3cd4c2bb78fe3
[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
25 #include <klay/error.h>
26 #include <klay/exception.h>
27 #include <klay/filesystem.h>
28
29 #include "../../logger.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 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)) {
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 BinaryData sanitizeKey(const BinaryData &key)
201 {
202         if (key.size() < DM_KEY_MIN_LEN_BYTE)
203                 throw runtime::Exception("Size of key smaller than minimum 32B");
204         BinaryData sanitized(key);
205         sanitized.resize(DM_KEY_MIN_LEN_BYTE);
206         return sanitized;
207 }
208
209 void copyInPlace(const std::string &source,     const std::string &destination,
210                                         const std::function<bool(blkcnt_t)> &isTarget,
211                                         const std::function<void(int, int)> &addProgress)
212 {
213         Ext4Tool ext4tool(source);
214         const blksize_t srcBlockSize = ext4tool.getBlockSize();
215         const blkcnt_t srcTotalBlockCount = ext4tool.getTotalBlockCount();
216         char buf[srcBlockSize];
217
218         runtime::File src(source, O_RDONLY);
219         runtime::File dst(destination, O_WRONLY);
220
221         off_t pos = 0;
222         for (blkcnt_t n = 0; n < srcTotalBlockCount; n++, pos += srcBlockSize) {
223                 if (!isTarget(n)) {
224                         continue;
225                 }
226
227                 src.lseek(pos, SEEK_SET);
228                 dst.lseek(pos, SEEK_SET);
229
230                 src.read(buf, srcBlockSize);
231                 dst.write(buf, srcBlockSize);
232
233                 addProgress(n, srcTotalBlockCount);
234         }
235 }
236
237 } // namepsace
238
239 DMCryptEngine::DMCryptEngine(const std::string &src, const std::string &dest, const ProgressBar &prgsBar) :
240         source(src), destination(dest), progress(prgsBar), mounted(false)
241 {
242 }
243
244 DMCryptEngine::~DMCryptEngine()
245 {
246 }
247
248 void DMCryptEngine::mount(const BinaryData &key, unsigned int options)
249 {
250         // create crypto type device mapping layer to mount the encrypted partition.
251         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
252
253         if (::mount(cryptoBlkDev.c_str(), destination.c_str(), "ext4", 0, 0) < 0)
254                 throw runtime::Exception(runtime::GetSystemErrorMessage());
255
256         mounted = true;
257 }
258
259 void DMCryptEngine::umount()
260 {
261         if (::umount(destination.c_str()) && errno != EINVAL)
262                 throw runtime::Exception(runtime::GetSystemErrorMessage());
263
264         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
265
266         mounted = false;
267 }
268
269 bool DMCryptEngine::isMounted()
270 {
271         return mounted;
272 }
273
274 void DMCryptEngine::encrypt(const BinaryData &key, unsigned int options)
275 {
276         // Force filesystem check via fcsf might be able to avoid fail situation during encryption.
277         Ext4Tool ext4Source(source);
278
279         for (int retry = 0; retry < 32; retry++) {
280                 try {
281                         ext4Source.forceCleanUp();
282                 } catch (runtime::Exception &e) {
283                         continue;
284                 }
285                 break;
286         }
287
288         // create crypto type device mapping layer to mount the plain partition
289         // should be encrypted here.
290         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
291
292         std::function<bool(blkcnt_t)> isTarget;
293         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
294                 INFO(SINK, "FastEncryption: Disabled");
295                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4Source, _1);
296         } else {
297                 INFO(SINK, "FastEncryption: Enabled");
298                 isTarget = [](unsigned int n) {
299                         return true;
300                 };
301         }
302
303         // We always do In-place encryption
304         copyInPlace(source, cryptoBlkDev, isTarget,
305                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
306                                         &progress, _1, _2, 1));
307
308         // remove crypto type device mapper
309         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
310
311         sync();
312         progress.done();
313 }
314
315 void DMCryptEngine::decrypt(const BinaryData &key, unsigned int options)
316 {
317         // create crypto type device mapping layer to mount the plain partition
318         // should be encrypted here.
319         auto cryptoBlkDev = createCryptoBlkDev(source, DM_DEFAULT_LABEL_NAME, sanitizeKey(key), DM_DEFAULT_CRYPTO_NAME);
320
321         // Force filesystem check via fcsf might be able to avoid fail situation during decryption.
322         Ext4Tool ext4CryptoBlkDev(cryptoBlkDev);
323
324         for (int retry = 0; retry < 32; retry++) {
325                 try {
326                         ext4CryptoBlkDev.forceCleanUp();
327                 } catch (runtime::Exception &e) {
328                         continue;
329                 }
330                 break;
331         }
332
333         std::function<bool(blkcnt_t)> isTarget;
334         if (!(options & OPTION_INCLUDE_UNUSED_REGION)) {
335                 INFO(SINK, "FastEncryption: Disabled");
336                 isTarget = std::bind(&Ext4Tool::isUsedBlock, &ext4CryptoBlkDev, _1);
337         } else {
338                 INFO(SINK, "FastEncryption: Enabled");
339                 isTarget = [](unsigned int n) {
340                         return true;
341                 };
342         }
343
344         // We always do In-place decryption
345         copyInPlace(cryptoBlkDev, source, isTarget,
346                         std::bind((void(ProgressBar::*)(int, int, int))&ProgressBar::update,
347                                         &progress, _1, _2, 1));
348
349         // remove crypto type device mapper
350         destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
351
352         sync();
353         progress.done();
354 }
355
356 unsigned int DMCryptEngine::getSupportedOptions()
357 {
358         return OPTION_INCLUDE_UNUSED_REGION;
359 }
360
361 } // namespace ode