2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
18 #include <linux/dm-ioctl.h>
19 #include <sys/mount.h>
25 #include <klay/error.h>
26 #include <klay/exception.h>
27 #include <klay/filesystem.h>
29 #include "../../logger.h"
30 #include "../../ext4-tool.h"
32 #include "dmcrypt-engine.h"
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"
39 #define OPTION_INCLUDE_UNUSED_REGION (1 << 0)
41 using namespace std::placeholders;
47 blkcnt_t getBlockCount(const std::string &src)
49 int fd = open(src.c_str(), O_RDONLY);
53 unsigned long long size = 0;
54 if ((ioctl(fd, BLKGETSIZE, &size)) == -1)
58 return (blkcnt_t)size;
61 const std::string convertToHex(const BinaryData &binary)
63 std::stringstream hex;
65 hex << std::hex << std::setfill('0');
66 for (unsigned int byte : binary) {
67 hex << std::setw(2) << byte;
72 void initDMIoctl(char *buf, size_t size, const std::string &name, unsigned flags)
74 struct dm_ioctl *io = (struct dm_ioctl *)buf;
76 ::memset(io, 0, size);
78 io->data_start = sizeof(struct dm_ioctl);
83 ::memset(io->name, 0, sizeof(io->name));
84 ::strncpy(io->name, name.c_str(), sizeof(io->name) - 1);
87 const std::string createCryptoBlkDev(const std::string &realBlkDev,
88 const std::string &mountName,
89 const BinaryData &key,
90 std::string cryptoTypeName)
92 auto blockCount = getBlockCount(realBlkDev);
93 std::string cryptoBlkDev;
97 * dmBuf |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
99 char dmBuf[DM_MAX_BUFFER_SIZE]; // first: for dm_io, dm_ts
101 // Open dm control IOCTL
102 if ((fd = open("/dev/mapper/control", O_RDWR)) < 0) {
103 throw runtime::Exception("Cannot open device-mapper");
107 * dmBuf |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
108 * dmIo |----------| size of dm_ioctl
110 auto dmIo = (struct dm_ioctl *)dmBuf;
112 // Create Device (mount_name)
113 initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
114 if (ioctl(fd, DM_DEV_CREATE, dmBuf)) {
116 throw runtime::Exception("Cannot create dm-crypt device");
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)) {
123 throw runtime::Exception("Cannot retrieve dm-crypt device status");
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);
131 * dmBuf |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
132 * dmIo |----------| size of dm_ioctl
133 * dmTs |--------------| size of dm_Target_spec
135 auto dmTs = (struct dm_target_spec *)(dmBuf + sizeof(struct dm_ioctl));
137 // Load the mapping table for this device
139 // Force clean-up whole dm_buffer
140 initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
141 dmIo->target_count = 1;
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);
149 * dmBuf |-------------------------------------------------| DM_MAX_BUFFER_SIZE(4096)
150 * dmIo |----------| size of dm_ioctl
151 * dmTs |--------------| size of dm_Target_spec
152 * cryptParams |---------------------|
154 char *cryptParams = dmBuf + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);
157 snprintf(cryptParams, DM_MAX_BUFFER_SIZE - (cryptParams - dmBuf), "%s %s 0 %s 0", cryptoTypeName.c_str(), convertToHex(key).c_str(), realBlkDev.c_str());
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;
165 if (ioctl(fd, DM_TABLE_LOAD, dmBuf) < 0) {
167 throw runtime::Exception("Cannot load dm-crypt mapping table.");
170 // Resume this device to activate it
171 initDMIoctl(dmBuf, DM_MAX_BUFFER_SIZE, mountName, 0);
172 if (ioctl(fd, DM_DEV_SUSPEND, dmBuf)) {
174 throw runtime::Exception("Cannot resume the dm-crypt device");
182 void destroyCryptoBlkDev(const std::string &cryptoBlkDev)
184 char buf[DM_MAX_BUFFER_SIZE];
187 if ((fd = open("/dev/mapper/control", O_RDWR)) < 0)
188 throw runtime::Exception("Cannot open device-mapper");
190 initDMIoctl(buf, sizeof(buf), cryptoBlkDev, 0);
191 ret = ioctl(fd, DM_DEV_REMOVE, buf);
195 if (ret != 0 && err != ENXIO) {
196 throw runtime::Exception("Cannot remove dm-crypt device");
200 BinaryData sanitizeKey(const BinaryData &key)
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);
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)
213 Ext4Tool ext4tool(source);
214 const blksize_t srcBlockSize = ext4tool.getBlockSize();
215 const blkcnt_t srcTotalBlockCount = ext4tool.getTotalBlockCount();
216 char buf[srcBlockSize];
218 runtime::File src(source, O_RDONLY);
219 runtime::File dst(destination, O_WRONLY);
222 for (blkcnt_t n = 0; n < srcTotalBlockCount; n++, pos += srcBlockSize) {
227 src.lseek(pos, SEEK_SET);
228 dst.lseek(pos, SEEK_SET);
230 src.read(buf, srcBlockSize);
231 dst.write(buf, srcBlockSize);
233 addProgress(n, srcTotalBlockCount);
239 DMCryptEngine::DMCryptEngine(const std::string &src, const std::string &dest, const ProgressBar &prgsBar) :
240 source(src), destination(dest), progress(prgsBar), mounted(false)
244 DMCryptEngine::~DMCryptEngine()
248 void DMCryptEngine::mount(const BinaryData &key, unsigned int options)
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);
253 if (::mount(cryptoBlkDev.c_str(), destination.c_str(), "ext4", 0, 0) < 0)
254 throw runtime::Exception(runtime::GetSystemErrorMessage());
259 void DMCryptEngine::umount()
261 if (::umount(destination.c_str()) && errno != EINVAL)
262 throw runtime::Exception(runtime::GetSystemErrorMessage());
264 destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
269 bool DMCryptEngine::isMounted()
274 void DMCryptEngine::encrypt(const BinaryData &key, unsigned int options)
276 // Force filesystem check via fcsf might be able to avoid fail situation during encryption.
277 Ext4Tool ext4Source(source);
279 for (int retry = 0; retry < 32; retry++) {
281 ext4Source.forceCleanUp();
282 } catch (runtime::Exception &e) {
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);
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);
297 INFO(SINK, "FastEncryption: Enabled");
298 isTarget = [](unsigned int n) {
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));
308 // remove crypto type device mapper
309 destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
315 void DMCryptEngine::decrypt(const BinaryData &key, unsigned int options)
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);
321 // Force filesystem check via fcsf might be able to avoid fail situation during decryption.
322 Ext4Tool ext4CryptoBlkDev(cryptoBlkDev);
324 for (int retry = 0; retry < 32; retry++) {
326 ext4CryptoBlkDev.forceCleanUp();
327 } catch (runtime::Exception &e) {
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);
338 INFO(SINK, "FastEncryption: Enabled");
339 isTarget = [](unsigned int n) {
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));
349 // remove crypto type device mapper
350 destroyCryptoBlkDev(DM_DEFAULT_LABEL_NAME);
356 unsigned int DMCryptEngine::getSupportedOptions()
358 return OPTION_INCLUDE_UNUSED_REGION;