summaryrefslogtreecommitdiffstats
path: root/lib/utils_storage_wrappers.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/utils_storage_wrappers.c')
-rw-r--r--lib/utils_storage_wrappers.c394
1 files changed, 394 insertions, 0 deletions
diff --git a/lib/utils_storage_wrappers.c b/lib/utils_storage_wrappers.c
new file mode 100644
index 0000000..80d275b
--- /dev/null
+++ b/lib/utils_storage_wrappers.c
@@ -0,0 +1,394 @@
+/*
+ * Generic wrapper for storage functions
+ * (experimental only)
+ *
+ * Copyright (C) 2018-2021, Ondrej Kozina
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "utils_storage_wrappers.h"
+#include "internal.h"
+
+struct crypt_storage_wrapper {
+ crypt_storage_wrapper_type type;
+ int dev_fd;
+ int block_size;
+ size_t mem_alignment;
+ uint64_t data_offset;
+ union {
+ struct {
+ struct crypt_storage *s;
+ uint64_t iv_start;
+ } cb;
+ struct {
+ int dmcrypt_fd;
+ char name[PATH_MAX];
+ } dm;
+ } u;
+};
+
+static int crypt_storage_backend_init(struct crypt_device *cd,
+ struct crypt_storage_wrapper *w,
+ uint64_t iv_start,
+ int sector_size,
+ const char *cipher,
+ const char *cipher_mode,
+ const struct volume_key *vk,
+ uint32_t flags)
+{
+ int r;
+ struct crypt_storage *s;
+
+ /* iv_start, sector_size */
+ r = crypt_storage_init(&s, sector_size, cipher, cipher_mode, vk->key, vk->keylength, flags & LARGE_IV);
+ if (r)
+ return r;
+
+ if ((flags & DISABLE_KCAPI) && crypt_storage_kernel_only(s)) {
+ log_dbg(cd, "Could not initialize userspace block cipher and kernel fallback is disabled.");
+ crypt_storage_destroy(s);
+ return -ENOTSUP;
+ }
+
+ w->type = USPACE;
+ w->u.cb.s = s;
+ w->u.cb.iv_start = iv_start;
+
+ return 0;
+}
+
+static int crypt_storage_dmcrypt_init(
+ struct crypt_device *cd,
+ struct crypt_storage_wrapper *cw,
+ struct device *device,
+ uint64_t device_offset,
+ uint64_t iv_start,
+ int sector_size,
+ const char *cipher_spec,
+ struct volume_key *vk,
+ int open_flags)
+{
+ static int counter = 0;
+ char path[PATH_MAX];
+ struct crypt_dm_active_device dmd = {
+ .flags = CRYPT_ACTIVATE_PRIVATE,
+ };
+ int mode, r, fd = -1;
+
+ log_dbg(cd, "Using temporary dmcrypt to access data.");
+
+ if (snprintf(cw->u.dm.name, sizeof(cw->u.dm.name), "temporary-cryptsetup-%d-%d", getpid(), counter++) < 0)
+ return -ENOMEM;
+ if (snprintf(path, sizeof(path), "%s/%s", dm_get_dir(), cw->u.dm.name) < 0)
+ return -ENOMEM;
+
+ r = device_block_adjust(cd, device, DEV_OK,
+ device_offset, &dmd.size, &dmd.flags);
+ if (r < 0) {
+ log_err(cd, _("Device %s does not exist or access denied."),
+ device_path(device));
+ return -EIO;
+ }
+
+ mode = open_flags | O_DIRECT;
+ if (dmd.flags & CRYPT_ACTIVATE_READONLY)
+ mode = (open_flags & ~O_ACCMODE) | O_RDONLY;
+
+ if (vk->key_description)
+ dmd.flags |= CRYPT_ACTIVATE_KEYRING_KEY;
+
+ r = dm_crypt_target_set(&dmd.segment, 0, dmd.size,
+ device,
+ vk,
+ cipher_spec,
+ iv_start,
+ device_offset,
+ NULL,
+ 0,
+ sector_size);
+ if (r)
+ return r;
+
+ r = dm_create_device(cd, cw->u.dm.name, "TEMP", &dmd);
+ if (r < 0) {
+ if (r != -EACCES && r != -ENOTSUP)
+ log_dbg(cd, "error hint would be nice");
+ r = -EIO;
+ }
+
+ dm_targets_free(cd, &dmd);
+
+ if (r)
+ return r;
+
+ fd = open(path, mode);
+ if (fd < 0) {
+ log_dbg(cd, "Failed to open %s", path);
+ dm_remove_device(cd, cw->u.dm.name, CRYPT_DEACTIVATE_FORCE);
+ return -EINVAL;
+ }
+
+ cw->type = DMCRYPT;
+ cw->u.dm.dmcrypt_fd = fd;
+
+ return 0;
+}
+
+int crypt_storage_wrapper_init(struct crypt_device *cd,
+ struct crypt_storage_wrapper **cw,
+ struct device *device,
+ uint64_t data_offset,
+ uint64_t iv_start,
+ int sector_size,
+ const char *cipher,
+ struct volume_key *vk,
+ uint32_t flags)
+{
+ int open_flags, r;
+ char _cipher[MAX_CIPHER_LEN], mode[MAX_CIPHER_LEN];
+ struct crypt_storage_wrapper *w;
+
+ /* device-mapper restrictions */
+ if (data_offset & ((1 << SECTOR_SHIFT) - 1))
+ return -EINVAL;
+
+ if (crypt_parse_name_and_mode(cipher, _cipher, NULL, mode))
+ return -EINVAL;
+
+ open_flags = O_CLOEXEC | ((flags & OPEN_READONLY) ? O_RDONLY : O_RDWR);
+
+ w = malloc(sizeof(*w));
+ if (!w)
+ return -ENOMEM;
+
+ memset(w, 0, sizeof(*w));
+ w->data_offset = data_offset;
+ w->mem_alignment = device_alignment(device);
+ w->block_size = device_block_size(cd, device);
+ if (!w->block_size || !w->mem_alignment) {
+ log_dbg(cd, "block size or alignment error.");
+ r = -EINVAL;
+ goto err;
+ }
+
+ w->dev_fd = device_open(cd, device, open_flags);
+ if (w->dev_fd < 0) {
+ r = -EINVAL;
+ goto err;
+ }
+
+ if (crypt_is_cipher_null(_cipher)) {
+ log_dbg(cd, "Requested cipher_null, switching to noop wrapper.");
+ w->type = NONE;
+ *cw = w;
+ return 0;
+ }
+
+ if (!vk) {
+ log_dbg(cd, "no key passed.");
+ r = -EINVAL;
+ goto err;
+ }
+
+ r = crypt_storage_backend_init(cd, w, iv_start, sector_size, _cipher, mode, vk, flags);
+ if (!r) {
+ *cw = w;
+ return 0;
+ }
+
+ log_dbg(cd, "Failed to initialize userspace block cipher.");
+
+ if ((r != -ENOTSUP && r != -ENOENT) || (flags & DISABLE_DMCRYPT))
+ goto err;
+
+ r = crypt_storage_dmcrypt_init(cd, w, device, data_offset >> SECTOR_SHIFT, iv_start,
+ sector_size, cipher, vk, open_flags);
+ if (r) {
+ log_dbg(cd, "Dm-crypt backend failed to initialize.");
+ goto err;
+ }
+ *cw = w;
+ return 0;
+err:
+ crypt_storage_wrapper_destroy(w);
+ /* wrapper destroy */
+ return r;
+}
+
+/* offset is relative to sector_start */
+ssize_t crypt_storage_wrapper_read(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ return read_lseek_blockwise(cw->dev_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ cw->data_offset + offset);
+}
+
+ssize_t crypt_storage_wrapper_read_decrypt(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ int r;
+ ssize_t read;
+
+ if (cw->type == DMCRYPT)
+ return read_lseek_blockwise(cw->u.dm.dmcrypt_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ offset);
+
+ read = read_lseek_blockwise(cw->dev_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ cw->data_offset + offset);
+ if (cw->type == NONE || read < 0)
+ return read;
+
+ r = crypt_storage_decrypt(cw->u.cb.s,
+ cw->u.cb.iv_start + (offset >> SECTOR_SHIFT),
+ read,
+ buffer);
+ if (r)
+ return -EINVAL;
+
+ return read;
+}
+
+ssize_t crypt_storage_wrapper_decrypt(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ int r;
+ ssize_t read;
+
+ if (cw->type == NONE)
+ return 0;
+
+ if (cw->type == DMCRYPT) {
+ /* there's nothing we can do, just read/decrypt via dm-crypt */
+ read = crypt_storage_wrapper_read_decrypt(cw, offset, buffer, buffer_length);
+ if (read < 0 || (size_t)read != buffer_length)
+ return -EINVAL;
+ return 0;
+ }
+
+ r = crypt_storage_decrypt(cw->u.cb.s,
+ cw->u.cb.iv_start + (offset >> SECTOR_SHIFT),
+ buffer_length,
+ buffer);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+ssize_t crypt_storage_wrapper_write(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ return write_lseek_blockwise(cw->dev_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ cw->data_offset + offset);
+}
+
+ssize_t crypt_storage_wrapper_encrypt_write(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ if (cw->type == DMCRYPT)
+ return write_lseek_blockwise(cw->u.dm.dmcrypt_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ offset);
+
+ if (cw->type == USPACE &&
+ crypt_storage_encrypt(cw->u.cb.s,
+ cw->u.cb.iv_start + (offset >> SECTOR_SHIFT),
+ buffer_length, buffer))
+ return -EINVAL;
+
+ return write_lseek_blockwise(cw->dev_fd,
+ cw->block_size,
+ cw->mem_alignment,
+ buffer,
+ buffer_length,
+ cw->data_offset + offset);
+}
+
+ssize_t crypt_storage_wrapper_encrypt(struct crypt_storage_wrapper *cw,
+ off_t offset, void *buffer, size_t buffer_length)
+{
+ if (cw->type == NONE)
+ return 0;
+
+ if (cw->type == DMCRYPT)
+ return -ENOTSUP;
+
+ if (crypt_storage_encrypt(cw->u.cb.s,
+ cw->u.cb.iv_start + (offset >> SECTOR_SHIFT),
+ buffer_length,
+ buffer))
+ return -EINVAL;
+
+ return 0;
+}
+
+void crypt_storage_wrapper_destroy(struct crypt_storage_wrapper *cw)
+{
+ if (!cw)
+ return;
+
+ if (cw->type == USPACE)
+ crypt_storage_destroy(cw->u.cb.s);
+ if (cw->type == DMCRYPT) {
+ close(cw->u.dm.dmcrypt_fd);
+ dm_remove_device(NULL, cw->u.dm.name, CRYPT_DEACTIVATE_FORCE);
+ }
+
+ free(cw);
+}
+
+int crypt_storage_wrapper_datasync(const struct crypt_storage_wrapper *cw)
+{
+ if (!cw)
+ return -EINVAL;
+ if (cw->type == DMCRYPT)
+ return fdatasync(cw->u.dm.dmcrypt_fd);
+ else
+ return fdatasync(cw->dev_fd);
+}
+
+crypt_storage_wrapper_type crypt_storage_wrapper_get_type(const struct crypt_storage_wrapper *cw)
+{
+ return cw ? cw->type : NONE;
+}