From 2639af74bfc77808ff8813feada914fc4bc91fff Mon Sep 17 00:00:00 2001 From: = Date: Sun, 20 Aug 2023 16:09:37 +0200 Subject: [PATCH 01/98] feat:Write module.c --- dm-vvz/Kbuild | 36 +++++++++++++++++++++ dm-vvz/Makefile | 44 ++++++++++++++++++++++++++ dm-vvz/module.c | 71 ++++++++++++++++++++++++++++++++++++++++++ dm-vvz/utils/log.h | 31 ++++++++++++++++++ dm-vvz/vvz_constants.h | 35 +++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 dm-vvz/Kbuild create mode 100644 dm-vvz/Makefile create mode 100644 dm-vvz/module.c create mode 100644 dm-vvz/utils/log.h create mode 100644 dm-vvz/vvz_constants.h diff --git a/dm-vvz/Kbuild b/dm-vvz/Kbuild new file mode 100644 index 0000000..352db93 --- /dev/null +++ b/dm-vvz/Kbuild @@ -0,0 +1,36 @@ + # + # Copyright The Shufflecake Project Authors (2022) + # Copyright The Shufflecake Project Contributors (2022) + # Copyright Contributors to the The Shufflecake Project. + # + # See the AUTHORS file at the top-level directory of this distribution and at + # + # + # This file is part of the program shufflecake-c, which is part of the + # Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + # layer for Linux. See . + # + # This program is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the Free + # Software Foundation, either version 2 of the License, or (at your option) + # any later version. This program 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 General + # Public License for more details. You should have received a copy of the + # GNU General Public License along with this program. + # If not, see . + # + +MODULE_NAME := vvz +obj-m := $(MODULE_NAME).o + + +OBJ_LIST := module.o + +$(MODULE_NAME)-y += $(OBJ_LIST) + + +# Normal CC flags +ccflags-y := -O2 +ccflags-y += -I$(src) +ccflags-y += -Wall -Wno-declaration-after-statement diff --git a/dm-vvz/Makefile b/dm-vvz/Makefile new file mode 100644 index 0000000..45cdc18 --- /dev/null +++ b/dm-vvz/Makefile @@ -0,0 +1,44 @@ + # + # Copyright The Shufflecake Project Authors (2022) + # Copyright The Shufflecake Project Contributors (2022) + # Copyright Contributors to the The Shufflecake Project. + # + # See the AUTHORS file at the top-level directory of this distribution and at + # + # + # This file is part of the program shufflecake-c, which is part of the + # Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + # layer for Linux. See . + # + # This program is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the Free + # Software Foundation, either version 2 of the License, or (at your option) + # any later version. This program 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 General + # Public License for more details. You should have received a copy of the + # GNU General Public License along with this program. + # If not, see . + # + +KERNEL_DIR = /lib/modules/$(shell uname -r)/build +SRC_DIR = $(shell pwd) +BUILD_DIR = $(shell pwd)/bin +BUILD_DIR_MAKEFILE = $(BUILD_DIR)/Makefile + +default: $(BUILD_DIR_MAKEFILE) + make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules + +$(BUILD_DIR_MAKEFILE): $(BUILD_DIR) + echo "# This Makefile is here because of Kbuild" > $@ + +$(BUILD_DIR): + mkdir -p $@ + + +install: + make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules_install + +clean: + rm -rf $(BUILD_DIR) + diff --git a/dm-vvz/module.c b/dm-vvz/module.c new file mode 100644 index 0000000..68ca855 --- /dev/null +++ b/dm-vvz/module.c @@ -0,0 +1,71 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include "dm_target.h" +#include "vvz_constants.h" +#include "utils/log.h" + + +static struct target_type vvz_target = { + .name = "vvz", + .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, + .module = THIS_MODULE, + .ctr = vvz_ctr, + .dtr = vvz_dtr, + .map = vvz_map, + .io_hints = vvz_io_hints, + .iterate_devices = vvz_iterate_devices, +}; + + +/* Module entry point: register DM target */ +static int __init vvz_init(void) +{ + int ret; + + ret = dm_register_target(&vvz_target); + if (ret < 0) + return ret; + + DMINFO("vvz loaded"); + return 0; +} + +/* Module exit point: unregister DM target */ +static void __exit vvz_exit(void) +{ + dm_unregister_target(&vvz_target); + + DMINFO("vvz unloaded"); + return; +} + + +module_init(vvz_init); +module_exit(vvz_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("The Shufflecake Project Authors"); +MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); diff --git a/dm-vvz/utils/log.h b/dm-vvz/utils/log.h new file mode 100644 index 0000000..160454d --- /dev/null +++ b/dm-vvz/utils/log.h @@ -0,0 +1,31 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_UTILS_LOG_H_ +#define _VVZ_UTILS_LOG_H_ + + +#define DM_MSG_PREFIX "vvz" + + +#endif /* _VVZ_UTILS_LOG_H_ */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h new file mode 100644 index 0000000..aed4038 --- /dev/null +++ b/dm-vvz/vvz_constants.h @@ -0,0 +1,35 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ + + +#define VVZ_VER_MAJOR 0 +#define VVZ_VER_MINOR 4 +#define VVZ_VER_REVISION 0 +#define VVZ_VER_SPECIAL "rc1" + +#define STRINGIFY0(s) # s +#define STRINGIFY(s) STRINGIFY0(s) + +#define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL From f26edc18dc3b6426d4a6b664023dd0f3ae4c5539 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 20 Aug 2023 17:25:00 +0200 Subject: [PATCH 02/98] feat:Write target stub --- dm-vvz/dm_target.c | 118 +++++++++++++++++++++++++++++++++++++++++ dm-vvz/dm_target.h | 38 +++++++++++++ dm-vvz/module.c | 2 +- dm-vvz/vvz_constants.h | 9 ++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 dm-vvz/dm_target.c create mode 100644 dm-vvz/dm_target.h diff --git a/dm-vvz/dm_target.c b/dm-vvz/dm_target.c new file mode 100644 index 0000000..939481e --- /dev/null +++ b/dm-vvz/dm_target.c @@ -0,0 +1,118 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include "dm_target.h" +#include "vvz_constants.h" +#include "utils/log.h" + + +/* Create volume and, if needed, the underlying device */ +int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) +{ + char *bdev_path; + int vol_idx; + char *enckey_hex; + u8 enckey[VVZ_CRYPTO_KEYLEN]; + u32 tot_slices; + int err; + + /* + * Parse arguments. + * + * argv[0]: underlying block device path + * argv[1]: volume index within the device + * argv[2]: number of 1-MiB slices in the underlying device + * argv[3]: 32-byte encryption key (hex-encoded) + */ + if (argc != 4) { + ti->error = "Invalid argument count"; + return -EINVAL; + } + bdev_path = argv[0]; + sscanf(argv[1], "%d", &vol_idx); + sscanf(argv[2], "%u", &tot_slices); + enckey_hex = argv[3]; + + /* Decode the encryption key */ + if (strlen(enckey_hex) != 2 * SFLC_SK_KEY_LEN) { + ti->error = "Invalid key length"; + return -EINVAL; + } + err = hex2bin(enckey, enckey_hex, VVZ_CRYPTO_KEYLEN); + if (err) { + ti->error = "Could not decode hexadecimal encryption key"; + return err; + } + + // TODO: create device if needed + + // TODO: create volume + + /* Only accept one block per request for simplicity TODO: improve to one slice*/ + ti->max_io_len = VVZ_LOG_SECTOR_SCALE; + /* Disable REQ_OP_FLUSH bios TODO: enable for lazy posmap syncing */ + ti->num_flush_bios = 1; + /* Disable REQ_OP_DISCARD_BIOS TODO: enable for slice reclamation */ + ti->num_discard_bios = 0; + ti->num_secure_erase_bios = 0; + ti->num_write_zeroes_bios = 0; + /* TODO: set to volume handle */ + ti->private = NULL; + + return 0; +} + + +/* Destroy volume and, if needed, the underlying device */ +void vvz_dtr(struct dm_target *ti) +{ + /* TODO: implement */ + return; +} + + +int vvz_map(struct dm_target *ti, struct bio *bio) +{ + /* TODO: implement */ + return DM_MAPIO_REMAPPED; +} + + +void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) +{ + /* TODO: don't really know what to put here */ + limits->logical_block_size = VVZ_LOG_BLOCK_SIZE; + limits->physical_block_size = VVZ_LOG_BLOCK_SIZE; + limits->io_min = VVZ_LOG_BLOCK_SIZE; + limits->io_opt = VVZ_LOG_BLOCK_SIZE; + + return; +} + + +int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) +{ + /* TODO: implement */ + return 0; +} diff --git a/dm-vvz/dm_target.h b/dm-vvz/dm_target.h new file mode 100644 index 0000000..0e10f45 --- /dev/null +++ b/dm-vvz/dm_target.h @@ -0,0 +1,38 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_DM_TARGET_H_ +#define _VVZ_DM_TARGET_H_ + + +#include + + +int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv); +void vvz_dtr(struct dm_target *ti); +int vvz_map(struct dm_target *ti, struct bio *bio); +void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits); +int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data); + + +#endif /* _VVZ_DM_TARGET_H_ */ diff --git a/dm-vvz/module.c b/dm-vvz/module.c index 68ca855..08201ad 100644 --- a/dm-vvz/module.c +++ b/dm-vvz/module.c @@ -67,5 +67,5 @@ module_init(vvz_init); module_exit(vvz_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("The Shufflecake Project Authors"); +MODULE_AUTHOR("Elia Anzuoni"); MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index aed4038..39c550c 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -33,3 +33,12 @@ #define STRINGIFY(s) STRINGIFY0(s) #define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL + + +#define VVZ_CRYPTO_KEYLEN 32 /* bytes */ + + +#define VVZ_LOG_BLOCK_SIZE 4096 /* bytes */ +#define VVZ_LOG_SECTOR_SCALE 8 /* sectors in logical block */ +#define VVZ_PHYS_BLOCK_SIZE 4608 /* bytes */ +#define VVZ_PHYS_SECTOR_SCALE 9 /* sectors in physical block */ From 111b2914822011da93cc81902d9347ede9fa5808 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 20 Aug 2023 22:16:30 +0200 Subject: [PATCH 03/98] feat:Write device and volume stubs --- .gitignore | 1 + dm-vvz/device/device.h | 72 ++++++++++++++++++++++++++++++++++++++++++ dm-vvz/dm_target.c | 46 +++++++++++++++++++++++---- dm-vvz/dm_target.h | 3 ++ dm-vvz/module.c | 20 +++++++++--- dm-vvz/volume/volume.h | 66 ++++++++++++++++++++++++++++++++++++++ dm-vvz/vvz_constants.h | 5 +++ 7 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 dm-vvz/device/device.h create mode 100644 dm-vvz/volume/volume.h diff --git a/.gitignore b/.gitignore index 26f4f24..52695bf 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ dkms.conf # Eclipse project files .project .cproject +.settings/ # Shufflecake binaries shufflecake diff --git a/dm-vvz/device/device.h b/dm-vvz/device/device.h new file mode 100644 index 0000000..629c67d --- /dev/null +++ b/dm-vvz/device/device.h @@ -0,0 +1,72 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * A device represents the underlying "physical" block device, common to all + * "virtual" volumes that map onto it. + */ + +#ifndef _VVZ_DEVICE_DEVICE_H_ +#define _VVZ_DEVICE_DEVICE_H_ + + +#include +#include "vvz_constants.h" + + +struct vvz_volume; + +struct vvz_device +{ + /* Shufflecake-unique device ID */ + size_t dev_id; + + /* Underlying block device */ + struct dm_dev *dev; + + /* All volumes mapping to this device */ + struct mutex vols_lock; + struct vvz_volume *vols[VVZ_DEV_MAX_VOLUMES]; + size_t num_vols; + + /* Slices stats */ + u32 tot_slices; + u32 free_slices; + + /* Shuffled array of PSIs */ + struct mutex shuffled_psis_lock; + u32 *shuffled_psis; + u32 first_free_psi; /* in the shuffled array */ + bool *psi_taken; + + /* Header sizes */ + u32 vol_header_size; + u32 dev_header_size; +}; + + +int vvz_dev_init(struct vvz_device *sd, struct dm_dev *dev, u32 tot_slices); +void vvz_dev_destroy(struct vvz_device *sd); + + +#endif /* _VVZ_DEVICE_DEVICE_H_ */ diff --git a/dm-vvz/dm_target.c b/dm-vvz/dm_target.c index 939481e..2ceccfa 100644 --- a/dm-vvz/dm_target.c +++ b/dm-vvz/dm_target.c @@ -21,17 +21,49 @@ * If not, see . */ -#include +#include #include "dm_target.h" +#include "device/device.h" #include "vvz_constants.h" #include "utils/log.h" +/* Global array of devices, and next free index */ +static DEFINE_MUTEX(alldevs_lock); +static struct vvz_device **alldevs = NULL; +static size_t free_devid = 0; + + +/* Set up module-global variables */ +int vvz_target_init() +{ + alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*alldevs)); + if (!alldevs) { + DMERR("vzalloc failed: could not allocate alldevs"); + return -ENOMEM; + } + + return 0; +} + + +/* Tear down module-global variables */ +void vvz_target_exit() +{ + if (alldevs) { + vfree(alldevs); + alldevs = NULL; + } + + return; +} + + /* Create volume and, if needed, the underlying device */ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) { - char *bdev_path; - int vol_idx; + size_t dev_id; + size_t vol_idx; char *enckey_hex; u8 enckey[VVZ_CRYPTO_KEYLEN]; u32 tot_slices; @@ -40,7 +72,7 @@ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* * Parse arguments. * - * argv[0]: underlying block device path + * argv[0]: Shufflecake ID of the underlying block device * argv[1]: volume index within the device * argv[2]: number of 1-MiB slices in the underlying device * argv[3]: 32-byte encryption key (hex-encoded) @@ -49,13 +81,13 @@ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) ti->error = "Invalid argument count"; return -EINVAL; } - bdev_path = argv[0]; - sscanf(argv[1], "%d", &vol_idx); + sscanf(argv[0], "%u", &dev_id); + sscanf(argv[1], "%u", &vol_idx); sscanf(argv[2], "%u", &tot_slices); enckey_hex = argv[3]; /* Decode the encryption key */ - if (strlen(enckey_hex) != 2 * SFLC_SK_KEY_LEN) { + if (strlen(enckey_hex) != 2 * VVZ_CRYPTO_KEYLEN) { ti->error = "Invalid key length"; return -EINVAL; } diff --git a/dm-vvz/dm_target.h b/dm-vvz/dm_target.h index 0e10f45..f527e23 100644 --- a/dm-vvz/dm_target.h +++ b/dm-vvz/dm_target.h @@ -28,6 +28,9 @@ #include +int vvz_target_init(); +void vvz_target_exit(); + int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv); void vvz_dtr(struct dm_target *ti); int vvz_map(struct dm_target *ti, struct bio *bio); diff --git a/dm-vvz/module.c b/dm-vvz/module.c index 08201ad..689b7b9 100644 --- a/dm-vvz/module.c +++ b/dm-vvz/module.c @@ -43,20 +43,32 @@ static struct target_type vvz_target = { /* Module entry point: register DM target */ static int __init vvz_init(void) { - int ret; + int err; - ret = dm_register_target(&vvz_target); - if (ret < 0) - return ret; + err = vvz_target_init(); + if (err < 0) + goto bad_target_init; + + err = dm_register_target(&vvz_target); + if (err < 0) + goto bad_register_target; DMINFO("vvz loaded"); return 0; + + +bad_register_target: + vvz_target_exit(); +bad_target_init: + DMERR("vvz not loaded"); + return err; } /* Module exit point: unregister DM target */ static void __exit vvz_exit(void) { dm_unregister_target(&vvz_target); + vvz_target_exit(); DMINFO("vvz unloaded"); return; diff --git a/dm-vvz/volume/volume.h b/dm-vvz/volume/volume.h new file mode 100644 index 0000000..b2bed0b --- /dev/null +++ b/dm-vvz/volume/volume.h @@ -0,0 +1,66 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * A volume represents a single "virtual" block device, mapping onto + * a "physical" device represented by a device. + */ + +#ifndef _VVZ_VOLUME_VOLUME_H_ +#define _VVZ_VOLUME_VOLUME_H_ + + +#include "device/device.h" + + +/* The volume name is "vvz__" */ +#define VVZ_VOL_NAME_MAX_LEN 12 + + +struct vvz_device; + +struct sflc_volume_s +{ + /* Volume index within the device */ + size_t vol_idx; + + /* Backing device */ + struct vvz_device *sd; + + /* Name of the volume, sflc__*/ + char vol_name[VVZ_VOL_NAME_MAX_LEN + 1]; + + /* Position map */ + struct mutex pmap_lock; + u32 *pmap; + + /* Slices stats */ + u32 mapped_slices; +}; + + +int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u8 *enckey); +void vvz_vol_destroy(struct vvz_volume *sv); + + +#endif /* _VVZ_VOLUME_VOLUME_H_ */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 39c550c..e4ff1bc 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -42,3 +42,8 @@ #define VVZ_LOG_SECTOR_SCALE 8 /* sectors in logical block */ #define VVZ_PHYS_BLOCK_SIZE 4608 /* bytes */ #define VVZ_PHYS_SECTOR_SCALE 9 /* sectors in physical block */ + + +#define VVZ_DEV_MAX_VOLUMES 15 +#define VVZ_MAX_DEVS 1024 + From 5727031afb8aff360e0fb520e606c87144905396 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 21 Aug 2023 15:41:28 +0200 Subject: [PATCH 04/98] feat:Write crypto --- dm-vvz/crypto.c | 64 +++++++++++++++++++++++++ dm-vvz/crypto.h | 35 ++++++++++++++ dm-vvz/device.c | 90 ++++++++++++++++++++++++++++++++++++ dm-vvz/{device => }/device.h | 16 +++---- dm-vvz/dm_target.c | 10 ++-- dm-vvz/{utils => }/log.h | 6 +-- dm-vvz/volume.c | 75 ++++++++++++++++++++++++++++++ dm-vvz/{volume => }/volume.h | 26 ++++++----- 8 files changed, 294 insertions(+), 28 deletions(-) create mode 100644 dm-vvz/crypto.c create mode 100644 dm-vvz/crypto.h create mode 100644 dm-vvz/device.c rename dm-vvz/{device => }/device.h (91%) rename dm-vvz/{utils => }/log.h (93%) create mode 100644 dm-vvz/volume.c rename dm-vvz/{volume => }/volume.h (83%) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c new file mode 100644 index 0000000..2608074 --- /dev/null +++ b/dm-vvz/crypto.c @@ -0,0 +1,64 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include "crypto.h" +#include "vvz_constants.h" +#include "log.h" + + +int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u8 *iv, int rw) +{ + struct skcipher_request *req = NULL; + DECLARE_CRYPTO_WAIT(wait); + struct scatterlist dst, src; + int err; + + /* TODO: not too sure about the gfp_mask here */ + req = skcipher_request_alloc(tfm, GFP_NOWAIT); + if (!req) + return -ENOMEM; + + skcipher_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + + /* We assume PAGE_SIZE to equal Shufflecake's block size */ + // TODO: better document this, maybe ensure it with compile-time checks + sg_init_table(&dst, 1); + sg_set_page(&dst, dst_page, VVZ_LOG_BLOCK_SIZE, 0); + sg_init_table(&src, 1); + sg_set_page(&src, src_page, VVZ_LOG_BLOCK_SIZE, 0); + + skcipher_request_set_crypt(req, &src, &dst, VVZ_LOG_BLOCK_SIZE, iv); + if (rw == READ) + err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); + else + err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait); + skcipher_request_free(req); + + return err; +} + diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h new file mode 100644 index 0000000..8455bdc --- /dev/null +++ b/dm-vvz/crypto.h @@ -0,0 +1,35 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_CRYPTO_H_ +#define _VVZ_CRYPTO_H_ + + +#include + + +int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u8 *iv, int rw); + + +#endif /* _VVZ_CRYPTO_H_ */ diff --git a/dm-vvz/device.c b/dm-vvz/device.c new file mode 100644 index 0000000..5846c60 --- /dev/null +++ b/dm-vvz/device.c @@ -0,0 +1,90 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include "device.h" +#include "log.h" + + +int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 tot_slices) +{ + int err; + int i; + + sd->dev_id = dev_id; + sd->dev = dev; + + for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) + sd->vols[i] = NULL; + sd->num_vols = 0; + + sd->tot_slices = tot_slices; + sd->free_slices = tot_slices; + /* TODO: compute header sizes */ + + mutex_init(&sd->shuffled_psis_lock); + sd->psi_taken = vzalloc(sd->tot_slices * sizeof(bool)); + if (!sd->psi_taken) { + DMERR("Could not allocate PSI occupation bitfield\n"); + err = -ENOMEM; + goto bad; + } + sd->shuffled_psis = vmalloc(tot_slices * sizeof(u32)); + if (!sd->shuffled_psis) { + DMERR("Could not allocate shuffled PSI array\n"); + err = -ENOMEM; + goto bad; + } + sd->first_free_psi = 0; + + /* Generate a permutation */ + for (i = 0; i < tot_slices; i++) + sd->shuffled_psis[i] = i; + // TODO: shuffle + + // TODO: complete + + return 0; + + +bad: + vvz_device_destroy(sd); + return err; +} + + +/* Can only be called once per device */ +void vvz_dev_destroy(struct vvz_device *sd) +{ + if (sd->num_vols > 0) + DMCRIT("Destroying device that still has open volumes"); + + /* No locking needed here TODO: right? */ + if (sd->shuffled_psis) + vfree(sd->shuffled_psis); + if (sd->psi_taken) + vfree(sd->psi_taken); + + return; +} + diff --git a/dm-vvz/device/device.h b/dm-vvz/device.h similarity index 91% rename from dm-vvz/device/device.h rename to dm-vvz/device.h index 629c67d..d38f3bb 100644 --- a/dm-vvz/device/device.h +++ b/dm-vvz/device.h @@ -26,8 +26,8 @@ * "virtual" volumes that map onto it. */ -#ifndef _VVZ_DEVICE_DEVICE_H_ -#define _VVZ_DEVICE_DEVICE_H_ +#ifndef _VVZ_DEVICE_H_ +#define _VVZ_DEVICE_H_ #include @@ -53,20 +53,20 @@ struct vvz_device u32 tot_slices; u32 free_slices; + /* Header sizes */ + u32 posmap_size; + u32 dev_header_size; + /* Shuffled array of PSIs */ struct mutex shuffled_psis_lock; u32 *shuffled_psis; u32 first_free_psi; /* in the shuffled array */ bool *psi_taken; - - /* Header sizes */ - u32 vol_header_size; - u32 dev_header_size; }; -int vvz_dev_init(struct vvz_device *sd, struct dm_dev *dev, u32 tot_slices); +int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 tot_slices); void vvz_dev_destroy(struct vvz_device *sd); -#endif /* _VVZ_DEVICE_DEVICE_H_ */ +#endif /* _VVZ_DEVICE_H_ */ diff --git a/dm-vvz/dm_target.c b/dm-vvz/dm_target.c index 2ceccfa..2b03fcf 100644 --- a/dm-vvz/dm_target.c +++ b/dm-vvz/dm_target.c @@ -23,9 +23,9 @@ #include #include "dm_target.h" -#include "device/device.h" +#include "device.h" #include "vvz_constants.h" -#include "utils/log.h" +#include "log.h" /* Global array of devices, and next free index */ @@ -62,7 +62,7 @@ void vvz_target_exit() /* Create volume and, if needed, the underlying device */ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) { - size_t dev_id; + char *bdev_path; size_t vol_idx; char *enckey_hex; u8 enckey[VVZ_CRYPTO_KEYLEN]; @@ -72,7 +72,7 @@ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* * Parse arguments. * - * argv[0]: Shufflecake ID of the underlying block device + * argv[0]: path to underlying physical device * argv[1]: volume index within the device * argv[2]: number of 1-MiB slices in the underlying device * argv[3]: 32-byte encryption key (hex-encoded) @@ -81,7 +81,7 @@ int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) ti->error = "Invalid argument count"; return -EINVAL; } - sscanf(argv[0], "%u", &dev_id); + bdev_path = argv[0]; sscanf(argv[1], "%u", &vol_idx); sscanf(argv[2], "%u", &tot_slices); enckey_hex = argv[3]; diff --git a/dm-vvz/utils/log.h b/dm-vvz/log.h similarity index 93% rename from dm-vvz/utils/log.h rename to dm-vvz/log.h index 160454d..2ad7ea9 100644 --- a/dm-vvz/utils/log.h +++ b/dm-vvz/log.h @@ -21,11 +21,11 @@ * If not, see . */ -#ifndef _VVZ_UTILS_LOG_H_ -#define _VVZ_UTILS_LOG_H_ +#ifndef _VVZ_LOG_H_ +#define _VVZ_LOG_H_ #define DM_MSG_PREFIX "vvz" -#endif /* _VVZ_UTILS_LOG_H_ */ +#endif /* _VVZ_LOG_H_ */ diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c new file mode 100644 index 0000000..ae8d345 --- /dev/null +++ b/dm-vvz/volume.c @@ -0,0 +1,75 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include "volume.h" +#include "log.h" + + +int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u8 *enckey) +{ + int err; + + sv->sd = sd; + sv->vol_idx = vol_idx; + sprintf(sv->vol_name, "sflc_%lu_%lu", sd->dev_id, vol_idx); + + mutex_init(&sv->posmap_lock); + sv->posmap = vmalloc(sd->tot_slices * sizeof(u32)); + if (!sv->posmap) { + DMERR("Could not allocate position map"); + err = -ENOMEM; + goto bad; + } + sv->mapped_slices = 0; + + sv->tfm = crypto_alloc_skcipher("ctr(aes)", 0, 0); + if (!sv->tfm) { + DMERR("Could not allocate AES-CTR cipher handle"); + err = -EINVAL; + goto bad; + } + + // TODO: complete + + return 0; + + +bad: + vvz_vol_destroy(sv); + return err; +} + + +/* Can only be called once per volume */ +void vvz_vol_destroy(struct vvz_volume *sv) +{ + if (sv->tfm) + crypto_free_skcipher(sv->tfm); + + /* No locking needed here TODO: right? */ + if (sv->posmap) + vfree(sv->posmap); + + return; +} diff --git a/dm-vvz/volume/volume.h b/dm-vvz/volume.h similarity index 83% rename from dm-vvz/volume/volume.h rename to dm-vvz/volume.h index b2bed0b..74daada 100644 --- a/dm-vvz/volume/volume.h +++ b/dm-vvz/volume.h @@ -22,24 +22,25 @@ */ /* - * A volume represents a single "virtual" block device, mapping onto - * a "physical" device represented by a device. + * A volume represents a single "virtual" block device, mapping onto a + * "physical" device represented by a device. */ -#ifndef _VVZ_VOLUME_VOLUME_H_ -#define _VVZ_VOLUME_VOLUME_H_ +#ifndef _VVZ_VOLUME_H_ +#define _VVZ_VOLUME_H_ -#include "device/device.h" +#include +#include "device.h" -/* The volume name is "vvz__" */ +/* The volume name is "sflc__" */ #define VVZ_VOL_NAME_MAX_LEN 12 struct vvz_device; -struct sflc_volume_s +struct vvz_volume { /* Volume index within the device */ size_t vol_idx; @@ -51,11 +52,12 @@ struct sflc_volume_s char vol_name[VVZ_VOL_NAME_MAX_LEN + 1]; /* Position map */ - struct mutex pmap_lock; - u32 *pmap; - - /* Slices stats */ + struct mutex posmap_lock; + u32 *posmap; u32 mapped_slices; + + /* Crypto */ + struct crypto_skcipher *tfm; }; @@ -63,4 +65,4 @@ int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u void vvz_vol_destroy(struct vvz_volume *sv); -#endif /* _VVZ_VOLUME_VOLUME_H_ */ +#endif /* _VVZ_VOLUME_H_ */ From 94f667bef0389c144aeedad72111edaf5c049338 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 21 Aug 2023 22:02:21 +0200 Subject: [PATCH 05/98] feat:Write Fisher-Yate --- dm-vvz/device.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 5846c60..f26907c 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -22,10 +22,15 @@ */ #include +#include #include "device.h" #include "log.h" +/* Fisher-Yates shuffle */ +static void fisheryates_u32(u32 *v, u32 len); + + int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 tot_slices) { int err; @@ -60,7 +65,7 @@ int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 t /* Generate a permutation */ for (i = 0; i < tot_slices; i++) sd->shuffled_psis[i] = i; - // TODO: shuffle + fisheryates_u32(sd->shuffled_psis, tot_slices); // TODO: complete @@ -88,3 +93,21 @@ void vvz_dev_destroy(struct vvz_device *sd) return; } + +/* Fisher-Yates shuffle */ +static void fisheryates_u32(u32 *v, u32 len) +{ + u32 i; + + for (i = len-1; i >= 1; i--) { + u32 j = get_random_u32_below(i+1); + + /* Swap v[i] and v[j] (without tmp variable 'cuz we're cool) */ + v[i] ^= v[j]; // v[i] <- a XOR b + v[j] ^= v[i]; // v[j] <- b XOR (a XOR b) = a + v[i] ^= v[j]; // v[i] <- (a XOR b) XOR a = b + } + + return; +} + From 787c79a61a4e8438274154808a8118e90aa97cf6 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 22 Aug 2023 15:54:42 +0200 Subject: [PATCH 06/98] feat:Write dev_add_vol --- dm-vvz/device.c | 77 +++++++++++++--- dm-vvz/device.h | 10 +- dm-vvz/dm_target.c | 150 ------------------------------ dm-vvz/dm_target.h | 41 --------- dm-vvz/module.c | 83 ----------------- dm-vvz/volume.c | 22 +++-- dm-vvz/volume.h | 4 +- dm-vvz/vvz.c | 221 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 309 insertions(+), 299 deletions(-) delete mode 100644 dm-vvz/dm_target.c delete mode 100644 dm-vvz/dm_target.h delete mode 100644 dm-vvz/module.c create mode 100644 dm-vvz/vvz.c diff --git a/dm-vvz/device.c b/dm-vvz/device.c index f26907c..9d8a9c7 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -31,14 +31,21 @@ static void fisheryates_u32(u32 *v, u32 len); -int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 tot_slices) +struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, + u32 dev_id, u32 tot_slices) { + struct vvz_device *sd; int err; int i; + sd = kzalloc(sizeof(*sd), GFP_KERNEL); + if (!sd) { + DMERR("Could not allocate device"); + return NULL; + } sd->dev_id = dev_id; - sd->dev = dev; + mutex_init(&sd->vols_lock); for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) sd->vols[i] = NULL; sd->num_vols = 0; @@ -47,17 +54,22 @@ int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 t sd->free_slices = tot_slices; /* TODO: compute header sizes */ + err = dm_get_device(ti, bdev_path, + dm_table_get_mode(ti->table), &sd->dev); + if (err) { + DMERR("Could not get DM device"); + goto bad; + } + mutex_init(&sd->shuffled_psis_lock); sd->psi_taken = vzalloc(sd->tot_slices * sizeof(bool)); if (!sd->psi_taken) { - DMERR("Could not allocate PSI occupation bitfield\n"); - err = -ENOMEM; + DMERR("Could not allocate PSI occupation bitfield"); goto bad; } sd->shuffled_psis = vmalloc(tot_slices * sizeof(u32)); if (!sd->shuffled_psis) { - DMERR("Could not allocate shuffled PSI array\n"); - err = -ENOMEM; + DMERR("Could not allocate shuffled PSI array"); goto bad; } sd->first_free_psi = 0; @@ -69,17 +81,16 @@ int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 t // TODO: complete - return 0; + return sd; bad: - vvz_device_destroy(sd); - return err; + vvz_dev_free(sd, ti); + return NULL; } -/* Can only be called once per device */ -void vvz_dev_destroy(struct vvz_device *sd) +void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti) { if (sd->num_vols > 0) DMCRIT("Destroying device that still has open volumes"); @@ -90,6 +101,48 @@ void vvz_dev_destroy(struct vvz_device *sd) if (sd->psi_taken) vfree(sd->psi_taken); + if (sd->dev) + dm_put_device(ti, sd->dev); + + kfree(sd); + + return; +} + + +int vvz_dev_add_volume(struct vvz_device *sd, struct vvz_volume *sv, size_t vol_idx) +{ + int ret; + + if (mutex_lock_interruptible(&sd->vols_lock)) + return -EINTR; + + if (sd->vols[vol_idx]) { + DMERR("Volume slot %lu already taken", vol_idx); + ret = -EINVAL; + } else { + sd->vols[vol_idx] = sv; + sd->num_vols += 1; + ret = 0; + } + + mutex_unlock(&sd->vols_lock); + return ret; +} + + +void vvz_dev_remove_volume(struct vvz_device *sd, size_t vol_idx) +{ + if (mutex_lock_interruptible(&sd->vols_lock)) + return; + + if (sd->vols[vol_idx]) { + sd->vols[vol_idx] = NULL; + sd->num_vols -= 1; + } else + DMERR("Volume slot %lu already free", vol_idx); + + mutex_unlock(&sd->vols_lock); return; } @@ -102,7 +155,7 @@ static void fisheryates_u32(u32 *v, u32 len) for (i = len-1; i >= 1; i--) { u32 j = get_random_u32_below(i+1); - /* Swap v[i] and v[j] (without tmp variable 'cuz we're cool) */ + /* Swap v[i] and v[j] */ v[i] ^= v[j]; // v[i] <- a XOR b v[j] ^= v[i]; // v[j] <- b XOR (a XOR b) = a v[i] ^= v[j]; // v[i] <- (a XOR b) XOR a = b diff --git a/dm-vvz/device.h b/dm-vvz/device.h index d38f3bb..3c59e42 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -39,7 +39,7 @@ struct vvz_volume; struct vvz_device { /* Shufflecake-unique device ID */ - size_t dev_id; + u32 dev_id; /* Underlying block device */ struct dm_dev *dev; @@ -65,8 +65,12 @@ struct vvz_device }; -int vvz_dev_init(struct vvz_device *sd, size_t dev_id, struct dm_dev *dev, u32 tot_slices); -void vvz_dev_destroy(struct vvz_device *sd); +struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, + u32 dev_id, u32 tot_slices); +void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti); + +int vvz_dev_add_volume(struct vvz_device *sd, struct vvz_volume *sv, size_t vol_idx); +void vvz_dev_remove_volume(struct vvz_device *sd, size_t vol_idx); #endif /* _VVZ_DEVICE_H_ */ diff --git a/dm-vvz/dm_target.c b/dm-vvz/dm_target.c deleted file mode 100644 index 2b03fcf..0000000 --- a/dm-vvz/dm_target.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include "dm_target.h" -#include "device.h" -#include "vvz_constants.h" -#include "log.h" - - -/* Global array of devices, and next free index */ -static DEFINE_MUTEX(alldevs_lock); -static struct vvz_device **alldevs = NULL; -static size_t free_devid = 0; - - -/* Set up module-global variables */ -int vvz_target_init() -{ - alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*alldevs)); - if (!alldevs) { - DMERR("vzalloc failed: could not allocate alldevs"); - return -ENOMEM; - } - - return 0; -} - - -/* Tear down module-global variables */ -void vvz_target_exit() -{ - if (alldevs) { - vfree(alldevs); - alldevs = NULL; - } - - return; -} - - -/* Create volume and, if needed, the underlying device */ -int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) -{ - char *bdev_path; - size_t vol_idx; - char *enckey_hex; - u8 enckey[VVZ_CRYPTO_KEYLEN]; - u32 tot_slices; - int err; - - /* - * Parse arguments. - * - * argv[0]: path to underlying physical device - * argv[1]: volume index within the device - * argv[2]: number of 1-MiB slices in the underlying device - * argv[3]: 32-byte encryption key (hex-encoded) - */ - if (argc != 4) { - ti->error = "Invalid argument count"; - return -EINVAL; - } - bdev_path = argv[0]; - sscanf(argv[1], "%u", &vol_idx); - sscanf(argv[2], "%u", &tot_slices); - enckey_hex = argv[3]; - - /* Decode the encryption key */ - if (strlen(enckey_hex) != 2 * VVZ_CRYPTO_KEYLEN) { - ti->error = "Invalid key length"; - return -EINVAL; - } - err = hex2bin(enckey, enckey_hex, VVZ_CRYPTO_KEYLEN); - if (err) { - ti->error = "Could not decode hexadecimal encryption key"; - return err; - } - - // TODO: create device if needed - - // TODO: create volume - - /* Only accept one block per request for simplicity TODO: improve to one slice*/ - ti->max_io_len = VVZ_LOG_SECTOR_SCALE; - /* Disable REQ_OP_FLUSH bios TODO: enable for lazy posmap syncing */ - ti->num_flush_bios = 1; - /* Disable REQ_OP_DISCARD_BIOS TODO: enable for slice reclamation */ - ti->num_discard_bios = 0; - ti->num_secure_erase_bios = 0; - ti->num_write_zeroes_bios = 0; - /* TODO: set to volume handle */ - ti->private = NULL; - - return 0; -} - - -/* Destroy volume and, if needed, the underlying device */ -void vvz_dtr(struct dm_target *ti) -{ - /* TODO: implement */ - return; -} - - -int vvz_map(struct dm_target *ti, struct bio *bio) -{ - /* TODO: implement */ - return DM_MAPIO_REMAPPED; -} - - -void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) -{ - /* TODO: don't really know what to put here */ - limits->logical_block_size = VVZ_LOG_BLOCK_SIZE; - limits->physical_block_size = VVZ_LOG_BLOCK_SIZE; - limits->io_min = VVZ_LOG_BLOCK_SIZE; - limits->io_opt = VVZ_LOG_BLOCK_SIZE; - - return; -} - - -int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) -{ - /* TODO: implement */ - return 0; -} diff --git a/dm-vvz/dm_target.h b/dm-vvz/dm_target.h deleted file mode 100644 index f527e23..0000000 --- a/dm-vvz/dm_target.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _VVZ_DM_TARGET_H_ -#define _VVZ_DM_TARGET_H_ - - -#include - - -int vvz_target_init(); -void vvz_target_exit(); - -int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv); -void vvz_dtr(struct dm_target *ti); -int vvz_map(struct dm_target *ti, struct bio *bio); -void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits); -int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data); - - -#endif /* _VVZ_DM_TARGET_H_ */ diff --git a/dm-vvz/module.c b/dm-vvz/module.c deleted file mode 100644 index 689b7b9..0000000 --- a/dm-vvz/module.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include -#include "dm_target.h" -#include "vvz_constants.h" -#include "utils/log.h" - - -static struct target_type vvz_target = { - .name = "vvz", - .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, - .module = THIS_MODULE, - .ctr = vvz_ctr, - .dtr = vvz_dtr, - .map = vvz_map, - .io_hints = vvz_io_hints, - .iterate_devices = vvz_iterate_devices, -}; - - -/* Module entry point: register DM target */ -static int __init vvz_init(void) -{ - int err; - - err = vvz_target_init(); - if (err < 0) - goto bad_target_init; - - err = dm_register_target(&vvz_target); - if (err < 0) - goto bad_register_target; - - DMINFO("vvz loaded"); - return 0; - - -bad_register_target: - vvz_target_exit(); -bad_target_init: - DMERR("vvz not loaded"); - return err; -} - -/* Module exit point: unregister DM target */ -static void __exit vvz_exit(void) -{ - dm_unregister_target(&vvz_target); - vvz_target_exit(); - - DMINFO("vvz unloaded"); - return; -} - - -module_init(vvz_init); -module_exit(vvz_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Elia Anzuoni"); -MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index ae8d345..a565b43 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -26,10 +26,17 @@ #include "log.h" -int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u8 *enckey) +struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey) { + struct vvz_volume *sv; int err; + sv = kzalloc(sizeof(*sv), GFP_KERNEL); + if (!sv) { + DMERR("Could not allocate volume"); + return NULL; + } + sv->sd = sd; sv->vol_idx = vol_idx; sprintf(sv->vol_name, "sflc_%lu_%lu", sd->dev_id, vol_idx); @@ -38,7 +45,6 @@ int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u sv->posmap = vmalloc(sd->tot_slices * sizeof(u32)); if (!sv->posmap) { DMERR("Could not allocate position map"); - err = -ENOMEM; goto bad; } sv->mapped_slices = 0; @@ -46,23 +52,21 @@ int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u sv->tfm = crypto_alloc_skcipher("ctr(aes)", 0, 0); if (!sv->tfm) { DMERR("Could not allocate AES-CTR cipher handle"); - err = -EINVAL; goto bad; } // TODO: complete - return 0; + return sv; bad: - vvz_vol_destroy(sv); - return err; + vvz_vol_free(sv); + return NULL; } -/* Can only be called once per volume */ -void vvz_vol_destroy(struct vvz_volume *sv) +void vvz_vol_free(struct vvz_volume *sv) { if (sv->tfm) crypto_free_skcipher(sv->tfm); @@ -71,5 +75,7 @@ void vvz_vol_destroy(struct vvz_volume *sv) if (sv->posmap) vfree(sv->posmap); + kfree(sv); + return; } diff --git a/dm-vvz/volume.h b/dm-vvz/volume.h index 74daada..3d3dbeb 100644 --- a/dm-vvz/volume.h +++ b/dm-vvz/volume.h @@ -61,8 +61,8 @@ struct vvz_volume }; -int vvz_vol_init(struct vvz_volume *sv, struct vvz_device *sd, size_t vol_idx, u8 *enckey); -void vvz_vol_destroy(struct vvz_volume *sv); +struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey); +void vvz_vol_free(struct vvz_volume *sv); #endif /* _VVZ_VOLUME_H_ */ diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c new file mode 100644 index 0000000..90e8459 --- /dev/null +++ b/dm-vvz/vvz.c @@ -0,0 +1,221 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include +#include "device.h" +#include "vvz_constants.h" +#include "log.h" + + +/* Global array of devices, and next free index */ +static DEFINE_MUTEX(alldevs_lock); +static struct vvz_device **alldevs = NULL; +static size_t free_devid = 0; + + +/* Create volume and, if needed, the underlying device */ +static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) +{ + u32 dev_id; + char *bdev_path; + size_t vol_idx; + char *enckey_hex; + u8 enckey[VVZ_CRYPTO_KEYLEN]; + u32 tot_slices; + struct vvz_device *sd; + struct vvz_volume *sv; + int err; + + /* + * Parse arguments. + * + * argv[0]: Shufflecake-unique device ID + * argv[1]: path to underlying physical device + * argv[2]: volume index within the device + * argv[3]: number of 1-MiB slices in the underlying device + * argv[4]: 32-byte encryption key (hex-encoded) + */ + if (argc != 5) { + ti->error = "Invalid argument count"; + return -EINVAL; + } + sscanf(argv[0], "%u", &dev_id); + bdev_path = argv[1]; + sscanf(argv[2], "%u", &vol_idx); + sscanf(argv[3], "%u", &tot_slices); + enckey_hex = argv[4]; + /* Sanity checks */ + if (dev_id >= VVZ_MAX_DEVS) { + ti->error = "Invalid device ID"; + return -EINVAL; + } + if (vol_idx >= VVZ_DEV_MAX_VOLUMES) { + ti->error = "Invalid volume index"; + return -EINVAL; + } + if (strlen(enckey_hex) != 2 * VVZ_CRYPTO_KEYLEN) { + ti->error = "Invalid key length"; + return -EINVAL; + } + /* Decode the encryption key */ + err = hex2bin(enckey, enckey_hex, VVZ_CRYPTO_KEYLEN); + if (err) { + ti->error = "Could not decode hexadecimal encryption key"; + return err; + } + + /* Create device if it does not yet exist */ + if (mutex_lock_interruptible(&alldevs_lock)) + return -EINTR; + sd = alldevs[dev_id]; + if (!sd) { + sd = vvz_dev_alloc(ti, bdev_path, dev_id, tot_slices); + if (!sd) { + ti->error = "Could not instantiate device"; + mutex_unlock(&alldevs_lock); + return -ENOMEM; + } + // TODO: register to sysfs + alldevs[dev_id] = sd; + } + mutex_unlock(&alldevs_lock); + + /* Create volume */ + sv = vvz_vol_alloc(sd, vol_idx, enckey); + if (!sv) { + /* Never free sd here, for simplicity. Potential memory leak + * if vvz_vol_alloc fails on the first volume, but it's + * really a corner case. */ + ti->error = "Could not instantiate volume"; + return -ENOMEM; + } + /* TODO Insert it into the device */ + /* TODO Register to sysfs */ + + /* Only accept one block per request for simplicity TODO: improve to one slice*/ + ti->max_io_len = VVZ_LOG_SECTOR_SCALE; + ti->num_flush_bios = 1; + ti->num_discard_bios = 0; + ti->num_secure_erase_bios = 0; + ti->num_write_zeroes_bios = 0; + ti->private = sv; + + return 0; +} + + +/* Destroy volume and, if needed, the underlying device */ +static void vvz_dtr(struct dm_target *ti) +{ + /* TODO: implement */ + return; +} + + +static int vvz_map(struct dm_target *ti, struct bio *bio) +{ + /* TODO: implement */ + return DM_MAPIO_REMAPPED; +} + + +static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) +{ + /* TODO: don't really know what to put here */ + limits->logical_block_size = VVZ_LOG_BLOCK_SIZE; + limits->physical_block_size = VVZ_LOG_BLOCK_SIZE; + limits->io_min = VVZ_LOG_BLOCK_SIZE; + limits->io_opt = VVZ_LOG_BLOCK_SIZE; + + return; +} + + +static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) +{ + /* TODO: implement */ + return 0; +} + + +static struct target_type vvz_target = { + .name = "vvz", + .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, + .module = THIS_MODULE, + .ctr = vvz_ctr, + .dtr = vvz_dtr, + .map = vvz_map, + .io_hints = vvz_io_hints, + .iterate_devices = vvz_iterate_devices, +}; + + +/* Module entry point: init variables and register DM target */ +static int __init vvz_init(void) +{ + int err; + + alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*alldevs)); + if (!alldevs) { + DMERR("vzalloc failed: could not allocate alldevs"); + err = -ENOMEM; + goto bad_alldevs_alloc; + } + + err = dm_register_target(&vvz_target); + if (err < 0) { + DMERR("Could n ot register DM target"); + goto bad_register_target; + } + + DMINFO("vvz loaded"); + return 0; + + +bad_register_target: + vfree(alldevs); +bad_alldevs_alloc: + DMERR("vvz not loaded"); + return err; +} + + +/* Module exit point: de-init variables and unregister DM target */ +static void __exit vvz_exit(void) +{ + dm_unregister_target(&vvz_target); + vfree(alldevs); + + DMINFO("vvz unloaded"); + return; +} + + +module_init(vvz_init); +module_exit(vvz_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Elia Anzuoni"); +MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); From 6fad2885bac8d3c87307eefe58a0ebc307c88820 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 22 Aug 2023 20:50:54 +0200 Subject: [PATCH 07/98] feat:Write almost all ctr and dtr --- dm-vvz/vvz.c | 53 +++++++++++++++++++++++++++++++----------- dm-vvz/vvz_constants.h | 3 +++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 90e8459..c787eef 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -32,10 +32,13 @@ /* Global array of devices, and next free index */ static DEFINE_MUTEX(alldevs_lock); static struct vvz_device **alldevs = NULL; -static size_t free_devid = 0; +static size_t free_devid = 0; /* The lowest free devID */ -/* Create volume and, if needed, the underlying device */ +/* + * Create volume and, if not existent, the underlying device. + * Register to sysfs accordingly. + */ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) { u32 dev_id; @@ -102,16 +105,21 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } mutex_unlock(&alldevs_lock); - /* Create volume */ + /* Create volume. Never free sd here, for simplicity. Potential memory + * leak if vvz_vol_alloc or vvz_dev_add_volume fail on the first volume, + * but it's really a corner case. */ sv = vvz_vol_alloc(sd, vol_idx, enckey); if (!sv) { - /* Never free sd here, for simplicity. Potential memory leak - * if vvz_vol_alloc fails on the first volume, but it's - * really a corner case. */ ti->error = "Could not instantiate volume"; return -ENOMEM; } - /* TODO Insert it into the device */ + /* Insert it into the device */ + err = vvz_dev_add_volume(sd, sv, vol_idx); + if (err) { + ti->error = "Could not add volume to device"; + vvz_vol_free(sv); + return -EINVAL; + } /* TODO Register to sysfs */ /* Only accept one block per request for simplicity TODO: improve to one slice*/ @@ -129,7 +137,24 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Destroy volume and, if needed, the underlying device */ static void vvz_dtr(struct dm_target *ti) { - /* TODO: implement */ + struct vvz_volume *sv = ti->private; + struct vvz_device *sd = sv->sd; + + /* TODO Remove volume from sysfs */ + vvz_dev_remove_volume(sd, sv->vol_idx); + vvz_vol_free(sv); + + /* Pointless to take vols_lock here TODO discuss its utility in general */ + if (!sd->num_vols) { + /* TODO remove device from sysfs */ + /* Release devID */ + alldevs[sd->dev_id] = NULL; + if (sd->dev_id < free_devid) + free_devid = sd->dev_id; + + vvz_dev_free(sd); + } + return; } @@ -155,13 +180,15 @@ static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) { - /* TODO: implement */ - return 0; + struct vvz_volume *sv = ti->private; + + /* TODO: any reason to do something else? */ + return fn(ti, sv->sd->dev, 0, ti->len, data); } static struct target_type vvz_target = { - .name = "vvz", + .name = VVZ_TARGET_NAME, .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, .module = THIS_MODULE, .ctr = vvz_ctr, @@ -179,14 +206,14 @@ static int __init vvz_init(void) alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*alldevs)); if (!alldevs) { - DMERR("vzalloc failed: could not allocate alldevs"); + DMERR("Could not allocate alldevs"); err = -ENOMEM; goto bad_alldevs_alloc; } err = dm_register_target(&vvz_target); if (err < 0) { - DMERR("Could n ot register DM target"); + DMERR("Could not register DM target"); goto bad_register_target; } diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index e4ff1bc..de09965 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -24,6 +24,9 @@ /* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ +#define VVZ_TARGET_NAME "vvz" + + #define VVZ_VER_MAJOR 0 #define VVZ_VER_MINOR 4 #define VVZ_VER_REVISION 0 From c142410580ec7469bd770e2fb7568068cbee039e Mon Sep 17 00:00:00 2001 From: = Date: Wed, 23 Aug 2023 22:18:12 +0200 Subject: [PATCH 08/98] feat:Write message --- dm-vvz/device.c | 11 +++++++++- dm-vvz/device.h | 9 +++++--- dm-vvz/vvz.c | 49 +++++++++++++++++++++++++++++++----------- dm-vvz/vvz_constants.h | 1 + 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 9d8a9c7..18db897 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -22,11 +22,20 @@ */ #include +#include #include #include "device.h" #include "log.h" +/* Compute device header size, in 512-byte sectors */ +#define DEV_HEADER_SIZE_SECTORS(tot_slices) \ + (VVZ_PHYS_SECTOR_SCALE * \ + (1 + VVZ_DEV_MAX_VOLUMES * \ + (1 + DIV_ROUND_UP(tot_slices, POSMAP_ENTRIES_PER_BLOCK) \ + ))) + + /* Fisher-Yates shuffle */ static void fisheryates_u32(u32 *v, u32 len); @@ -52,7 +61,7 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, sd->tot_slices = tot_slices; sd->free_slices = tot_slices; - /* TODO: compute header sizes */ + sd->dev_header_size_sectors = DEV_HEADER_SIZE_SECTORS(tot_slices); err = dm_get_device(ti, bdev_path, dm_table_get_mode(ti->table), &sd->dev); diff --git a/dm-vvz/device.h b/dm-vvz/device.h index 3c59e42..11793c4 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -34,6 +34,10 @@ #include "vvz_constants.h" +/* PosMap entries are 4 bytes, therefore there are 1024 in a block */ +#define POSMAP_ENTRIES_PER_BLOCK 1024 + + struct vvz_volume; struct vvz_device @@ -53,9 +57,8 @@ struct vvz_device u32 tot_slices; u32 free_slices; - /* Header sizes */ - u32 posmap_size; - u32 dev_header_size; + /* Header size in sectors */ + u32 dev_header_size_sectors; /* Shuffled array of PSIs */ struct mutex shuffled_psis_lock; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index c787eef..7e95556 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -32,12 +32,12 @@ /* Global array of devices, and next free index */ static DEFINE_MUTEX(alldevs_lock); static struct vvz_device **alldevs = NULL; -static size_t free_devid = 0; /* The lowest free devID */ +static u32 free_devid = 0; /* The lowest free devID */ /* * Create volume and, if not existent, the underlying device. - * Register to sysfs accordingly. + * Register entities to sysfs accordingly. */ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) { @@ -144,7 +144,7 @@ static void vvz_dtr(struct dm_target *ti) vvz_dev_remove_volume(sd, sv->vol_idx); vvz_vol_free(sv); - /* Pointless to take vols_lock here TODO discuss its utility in general */ + /* Pointless to take vols_lock here */ if (!sd->num_vols) { /* TODO remove device from sysfs */ /* Release devID */ @@ -159,6 +159,27 @@ static void vvz_dtr(struct dm_target *ti) } +static int vvz_message(struct dm_target *ti, unsigned int argc, char **argv, + char *result, unsigned int maxlen) +{ + int i; + + if (argc != 1 || strcmp(argv[0], VVZ_TARGET_MSG_DEVID)) { + DMERR("Unrecognised message"); + return -EINVAL; + } + + if (mutex_lock_interruptible(&alldevs_lock)) + return -EINTR; + sprintf(result, "%lu", free_devid); + for (i = free_devid + 1; alldevs[i] && i < VVZ_MAX_DEVS; i++); + free_devid = i; + mutex_unlock(&alldevs_lock); + + return 0; +} + + static int vvz_map(struct dm_target *ti, struct bio *bio) { /* TODO: implement */ @@ -181,21 +202,23 @@ static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) { struct vvz_volume *sv = ti->private; + struct vvz_device *sd = sv->sd; - /* TODO: any reason to do something else? */ - return fn(ti, sv->sd->dev, 0, ti->len, data); + return fn(ti, sv->sd->dev, 0, sd->dev_header_size_sectors + + (ti->len * VVZ_PHYS_SECTOR_SCALE), data); } static struct target_type vvz_target = { - .name = VVZ_TARGET_NAME, - .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, - .module = THIS_MODULE, - .ctr = vvz_ctr, - .dtr = vvz_dtr, - .map = vvz_map, - .io_hints = vvz_io_hints, - .iterate_devices = vvz_iterate_devices, + .name = VVZ_TARGET_NAME, + .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, + .module = THIS_MODULE, + .ctr = vvz_ctr, + .dtr = vvz_dtr, + .message = vvz_message, + .map = vvz_map, + .io_hints = vvz_io_hints, + .iterate_devices = vvz_iterate_devices, }; diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index de09965..8d6993f 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -25,6 +25,7 @@ #define VVZ_TARGET_NAME "vvz" +#define VVZ_TARGET_MSG_DEVID "reserve_dev_id" #define VVZ_VER_MAJOR 0 From 5723761f15791e30d44691935ea678d47b0cb79b Mon Sep 17 00:00:00 2001 From: = Date: Thu, 24 Aug 2023 15:13:43 +0200 Subject: [PATCH 09/98] feat:Shift to lite --- dm-vvz/device.c | 2 +- dm-vvz/volume.c | 14 ++++++++++++-- dm-vvz/vvz.c | 4 ++-- dm-vvz/vvz_constants.h | 7 +++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 18db897..04b480f 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -30,7 +30,7 @@ /* Compute device header size, in 512-byte sectors */ #define DEV_HEADER_SIZE_SECTORS(tot_slices) \ - (VVZ_PHYS_SECTOR_SCALE * \ + (VVZ_SECTOR_SCALE * \ (1 + VVZ_DEV_MAX_VOLUMES * \ (1 + DIV_ROUND_UP(tot_slices, POSMAP_ENTRIES_PER_BLOCK) \ ))) diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index a565b43..c461ec7 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -29,6 +29,7 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey) { struct vvz_volume *sv; + u8 xts_key[2 * VVZ_CRYPTO_KEYLEN]; int err; sv = kzalloc(sizeof(*sv), GFP_KERNEL); @@ -49,9 +50,18 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck } sv->mapped_slices = 0; - sv->tfm = crypto_alloc_skcipher("ctr(aes)", 0, 0); + sv->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (!sv->tfm) { - DMERR("Could not allocate AES-CTR cipher handle"); + DMERR("Could not allocate AES-XTS cipher handle"); + goto bad; + } + /* HOT TAKE: 512-bit keys for AES-XTS are pointless, just duplicate the + * normal 256-bit one. See extended discussion in TODO add location */ + memcpy(xts_key, enckey, VVZ_CRYPTO_KEYLEN); + memcpy(xts_key+VVZ_CRYPTO_KEYLEN, enckey, VVZ_CRYPTO_KEYLEN); + err = crypto_skcipher_setkey(sv->tfm, xts_key, 2*VVZ_CRYPTO_KEYLEN); + if (err) { + DMERR("Could not set key in crypto transform"); goto bad; } diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 7e95556..0b6fee5 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -123,7 +123,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* TODO Register to sysfs */ /* Only accept one block per request for simplicity TODO: improve to one slice*/ - ti->max_io_len = VVZ_LOG_SECTOR_SCALE; + ti->max_io_len = VVZ_SECTOR_SCALE; ti->num_flush_bios = 1; ti->num_discard_bios = 0; ti->num_secure_erase_bios = 0; @@ -205,7 +205,7 @@ static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn struct vvz_device *sd = sv->sd; return fn(ti, sv->sd->dev, 0, sd->dev_header_size_sectors + - (ti->len * VVZ_PHYS_SECTOR_SCALE), data); + (ti->len * VVZ_SECTOR_SCALE), data); } diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 8d6993f..54230e4 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -42,10 +42,9 @@ #define VVZ_CRYPTO_KEYLEN 32 /* bytes */ -#define VVZ_LOG_BLOCK_SIZE 4096 /* bytes */ -#define VVZ_LOG_SECTOR_SCALE 8 /* sectors in logical block */ -#define VVZ_PHYS_BLOCK_SIZE 4608 /* bytes */ -#define VVZ_PHYS_SECTOR_SCALE 9 /* sectors in physical block */ +#define VVZ_BLOCK_SIZE 4096 /* bytes */ +#define VVZ_BLOCK_SHIFT 3 +#define VVZ_SECTOR_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in block */ #define VVZ_DEV_MAX_VOLUMES 15 From 1dd2835d3457700454b46e24e7299bc1e7a1b38b Mon Sep 17 00:00:00 2001 From: = Date: Fri, 25 Aug 2023 11:51:31 +0200 Subject: [PATCH 10/98] feat:Refactor and declare io.h and posmap.h --- dm-vvz/crypto.c | 17 +++++-- dm-vvz/crypto.h | 6 ++- dm-vvz/device.c | 14 +++++- dm-vvz/device.h | 6 ++- dm-vvz/io.h | 42 +++++++++++++++++ dm-vvz/posmap.h | 45 ++++++++++++++++++ dm-vvz/volume.c | 38 +++++++-------- dm-vvz/vvz.c | 102 +++++++++++++++++++---------------------- dm-vvz/vvz.h | 38 +++++++++++++++ dm-vvz/vvz_constants.h | 13 ++++-- 10 files changed, 234 insertions(+), 87 deletions(-) create mode 100644 dm-vvz/io.h create mode 100644 dm-vvz/posmap.h create mode 100644 dm-vvz/vvz.h diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index 2608074..d5df767 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -28,9 +28,14 @@ #include "log.h" +/* + * The IV is constructed as the 0-padded LE representation of the block number, + * which is exactly what dm-crypt does (IV mode "plain64") + */ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u8 *iv, int rw) + struct page *dst_page, u64 pblk_num, int rw) { + u8 iv[VVZ_CRYPTO_IVLEN]; struct skcipher_request *req = NULL; DECLARE_CRYPTO_WAIT(wait); struct scatterlist dst, src; @@ -48,11 +53,15 @@ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, /* We assume PAGE_SIZE to equal Shufflecake's block size */ // TODO: better document this, maybe ensure it with compile-time checks sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_LOG_BLOCK_SIZE, 0); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_LOG_BLOCK_SIZE, 0); + sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - skcipher_request_set_crypt(req, &src, &dst, VVZ_LOG_BLOCK_SIZE, iv); + /* Construct IV */ + memset(iv, 0, VVZ_CRYPTO_IVLEN); + *(__le64 *)iv = cpu_to_le64(pblk_num); + + skcipher_request_set_crypt(req, &src, &dst, VVZ_BLOCK_SIZE, iv); if (rw == READ) err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); else diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h index 8455bdc..d97d3c0 100644 --- a/dm-vvz/crypto.h +++ b/dm-vvz/crypto.h @@ -28,8 +28,12 @@ #include +#define VVZ_CRYPTO_IVLEN 16 /* bytes */ + + +/* Use the *physical* address of the 4096-byte block within the device */ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u8 *iv, int rw); + struct page *dst_page, u64 pblk_num, int rw); #endif /* _VVZ_CRYPTO_H_ */ diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 04b480f..9282532 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -32,7 +32,7 @@ #define DEV_HEADER_SIZE_SECTORS(tot_slices) \ (VVZ_SECTOR_SCALE * \ (1 + VVZ_DEV_MAX_VOLUMES * \ - (1 + DIV_ROUND_UP(tot_slices, POSMAP_ENTRIES_PER_BLOCK) \ + (1 + DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK) \ ))) @@ -63,6 +63,13 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, sd->free_slices = tot_slices; sd->dev_header_size_sectors = DEV_HEADER_SIZE_SECTORS(tot_slices); + /* Path to device */ + if (strlen(bdev_path) > VVZ_BDEV_PATH_LEN) { + DMERR("Device path too long"); + goto bad; + } + strcpy(sd->bdev_path, bdev_path); + /* DM handle for device */ err = dm_get_device(ti, bdev_path, dm_table_get_mode(ti->table), &sd->dev); if (err) { @@ -70,6 +77,7 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, goto bad; } + /* Shuffled PSIs */ mutex_init(&sd->shuffled_psis_lock); sd->psi_taken = vzalloc(sd->tot_slices * sizeof(bool)); if (!sd->psi_taken) { @@ -88,7 +96,7 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, sd->shuffled_psis[i] = i; fisheryates_u32(sd->shuffled_psis, tot_slices); - // TODO: complete + // TODO: init sysfs return sd; @@ -104,6 +112,8 @@ void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti) if (sd->num_vols > 0) DMCRIT("Destroying device that still has open volumes"); + /* TODO remove from sysfs */ + /* No locking needed here TODO: right? */ if (sd->shuffled_psis) vfree(sd->shuffled_psis); diff --git a/dm-vvz/device.h b/dm-vvz/device.h index 11793c4..cfb4028 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -34,8 +34,9 @@ #include "vvz_constants.h" -/* PosMap entries are 4 bytes, therefore there are 1024 in a block */ -#define POSMAP_ENTRIES_PER_BLOCK 1024 +#define VVZ_BDEV_PATH_LEN 128 +/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ +#define VVZ_POSMAP_ENTRIES_PER_BLOCK 1024 struct vvz_volume; @@ -47,6 +48,7 @@ struct vvz_device /* Underlying block device */ struct dm_dev *dev; + char bdev_path[VVZ_BDEV_PATH_LEN+1]; /* All volumes mapping to this device */ struct mutex vols_lock; diff --git a/dm-vvz/io.h b/dm-vvz/io.h new file mode 100644 index 0000000..65f8514 --- /dev/null +++ b/dm-vvz/io.h @@ -0,0 +1,42 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_IO_H_ +#define _VVZ_IO_H_ + + +#include "device.h" +#include "volume.h" + + +/* Synchronously read/write a physical block from the device. + * Only used for the position map. */ +int vvz_rwblock_sync(struct vvz_device *sd, u64 pblk_num, struct page *page, int rw); + +/* Handlers for the logical bio's submitted to a volume */ +int vvz_read(struct vvz_volume *sv, struct bio *bio); +int vvz_write(struct vvz_volume *sv, struct bio *bio); +int vvz_flush(struct vvz_volume *sv, struct bio *bio); + + +#endif /* _VVZ_IO_H_ */ diff --git a/dm-vvz/posmap.h b/dm-vvz/posmap.h new file mode 100644 index 0000000..0933492 --- /dev/null +++ b/dm-vvz/posmap.h @@ -0,0 +1,45 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_POSMAP_H_ +#define _VVZ_POSMAP_H_ + + +#include "volume.h" + + +#define VVZ_POSMAP_INVALID 0xFFFFFFFF + + +/* At volume creation */ +int vvz_load_entire_posmap(struct vvz_volume *sv); +/* On FLUSH request. Actually stores a whole block of 1024 entries. */ +int vvz_store_posmap_entry(struct vvz_volume *sv, u32 lsi); + +/* Returns INVALID (in *psi) if mapping does not exist */ +int vvz_get_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi); +/* Does not persist mapping on disk (only done on FLUSH requests) */ +int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi); + + +#endif /* _VVZ_POSMAP_H_ */ diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index c461ec7..3290c42 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -29,7 +29,6 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey) { struct vvz_volume *sv; - u8 xts_key[2 * VVZ_CRYPTO_KEYLEN]; int err; sv = kzalloc(sizeof(*sv), GFP_KERNEL); @@ -42,6 +41,19 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck sv->vol_idx = vol_idx; sprintf(sv->vol_name, "sflc_%lu_%lu", sd->dev_id, vol_idx); + /* Crypto */ + sv->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); + if (!sv->tfm) { + DMERR("Could not allocate AES-XTS cipher handle"); + goto bad; + } + err = crypto_skcipher_setkey(sv->tfm, enckey, VVZ_XTS_KEYLEN); + if (err) { + DMERR("Could not set key in crypto transform"); + goto bad; + } + + /* Position map */ mutex_init(&sv->posmap_lock); sv->posmap = vmalloc(sd->tot_slices * sizeof(u32)); if (!sv->posmap) { @@ -49,23 +61,9 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck goto bad; } sv->mapped_slices = 0; + // TODO load posmap (must have crypto tfm initialised) - sv->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); - if (!sv->tfm) { - DMERR("Could not allocate AES-XTS cipher handle"); - goto bad; - } - /* HOT TAKE: 512-bit keys for AES-XTS are pointless, just duplicate the - * normal 256-bit one. See extended discussion in TODO add location */ - memcpy(xts_key, enckey, VVZ_CRYPTO_KEYLEN); - memcpy(xts_key+VVZ_CRYPTO_KEYLEN, enckey, VVZ_CRYPTO_KEYLEN); - err = crypto_skcipher_setkey(sv->tfm, xts_key, 2*VVZ_CRYPTO_KEYLEN); - if (err) { - DMERR("Could not set key in crypto transform"); - goto bad; - } - - // TODO: complete + // TODO: init sysfs return sv; @@ -78,13 +76,15 @@ bad: void vvz_vol_free(struct vvz_volume *sv) { - if (sv->tfm) - crypto_free_skcipher(sv->tfm); + /* TODO remove from sysfs */ /* No locking needed here TODO: right? */ if (sv->posmap) vfree(sv->posmap); + if (sv->tfm) + crypto_free_skcipher(sv->tfm); + kfree(sv); return; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 0b6fee5..4572941 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -26,18 +26,18 @@ #include #include "device.h" #include "vvz_constants.h" +#include "vvz.h" #include "log.h" /* Global array of devices, and next free index */ -static DEFINE_MUTEX(alldevs_lock); -static struct vvz_device **alldevs = NULL; -static u32 free_devid = 0; /* The lowest free devID */ +DEFINE_MUTEX(vvz_alldevs_lock); +struct vvz_device **vvz_alldevs = NULL; +u32 vvz_free_devid = 0; /* The lowest free devID */ /* * Create volume and, if not existent, the underlying device. - * Register entities to sysfs accordingly. */ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) { @@ -45,7 +45,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) char *bdev_path; size_t vol_idx; char *enckey_hex; - u8 enckey[VVZ_CRYPTO_KEYLEN]; + u8 enckey[VVZ_XTS_KEYLEN]; u32 tot_slices; struct vvz_device *sd; struct vvz_volume *sv; @@ -58,7 +58,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) * argv[1]: path to underlying physical device * argv[2]: volume index within the device * argv[3]: number of 1-MiB slices in the underlying device - * argv[4]: 32-byte encryption key (hex-encoded) + * argv[4]: 64-byte encryption key (hex-encoded) */ if (argc != 5) { ti->error = "Invalid argument count"; @@ -78,36 +78,47 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) ti->error = "Invalid volume index"; return -EINVAL; } - if (strlen(enckey_hex) != 2 * VVZ_CRYPTO_KEYLEN) { + if (strlen(enckey_hex) != 2 * VVZ_XTS_KEYLEN) { ti->error = "Invalid key length"; return -EINVAL; } /* Decode the encryption key */ - err = hex2bin(enckey, enckey_hex, VVZ_CRYPTO_KEYLEN); + err = hex2bin(enckey, enckey_hex, VVZ_XTS_KEYLEN); if (err) { ti->error = "Could not decode hexadecimal encryption key"; return err; } - /* Create device if it does not yet exist */ - if (mutex_lock_interruptible(&alldevs_lock)) + /* Create device if it doesn't exist yet (or check consistency if it does) */ + if (mutex_lock_interruptible(&vvz_alldevs_lock)) return -EINTR; - sd = alldevs[dev_id]; + sd = vvz_alldevs[dev_id]; if (!sd) { + /* Create */ sd = vvz_dev_alloc(ti, bdev_path, dev_id, tot_slices); if (!sd) { ti->error = "Could not instantiate device"; - mutex_unlock(&alldevs_lock); + mutex_unlock(&vvz_alldevs_lock); return -ENOMEM; } - // TODO: register to sysfs - alldevs[dev_id] = sd; + /* Insert in alldevs, and advance free_devid */ + vvz_alldevs[dev_id] = sd; + int i; + for (i = dev_id + 1; vvz_alldevs[i] && i < VVZ_MAX_DEVS; i++); + vvz_free_devid = i; + } else { + /* Check for uniqueness */ + if (strncmp(sd->bdev_path, bdev_path, VVZ_BDEV_PATH_LEN) != 0) { + ti->error = "Device ID already used for another device"; + mutex_unlock(&vvz_alldevs_lock); + return -EINVAL; + } } - mutex_unlock(&alldevs_lock); + mutex_unlock(&vvz_alldevs_lock); /* Create volume. Never free sd here, for simplicity. Potential memory * leak if vvz_vol_alloc or vvz_dev_add_volume fail on the first volume, - * but it's really a corner case. */ + * but it's really a corner case. TODO discuss */ sv = vvz_vol_alloc(sd, vol_idx, enckey); if (!sv) { ti->error = "Could not instantiate volume"; @@ -118,9 +129,8 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) if (err) { ti->error = "Could not add volume to device"; vvz_vol_free(sv); - return -EINVAL; + return err; } - /* TODO Register to sysfs */ /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_SECTOR_SCALE; @@ -139,47 +149,28 @@ static void vvz_dtr(struct dm_target *ti) { struct vvz_volume *sv = ti->private; struct vvz_device *sd = sv->sd; + u32 dev_id = sd->dev_id; - /* TODO Remove volume from sysfs */ vvz_dev_remove_volume(sd, sv->vol_idx); vvz_vol_free(sv); /* Pointless to take vols_lock here */ if (!sd->num_vols) { - /* TODO remove device from sysfs */ - /* Release devID */ - alldevs[sd->dev_id] = NULL; - if (sd->dev_id < free_devid) - free_devid = sd->dev_id; - vvz_dev_free(sd); + + /* Release devID */ + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return; + vvz_alldevs[dev_id] = NULL; + if (dev_id < vvz_free_devid) + vvz_free_devid = dev_id; + mutex_unlock(&vvz_alldevs_lock); } return; } -static int vvz_message(struct dm_target *ti, unsigned int argc, char **argv, - char *result, unsigned int maxlen) -{ - int i; - - if (argc != 1 || strcmp(argv[0], VVZ_TARGET_MSG_DEVID)) { - DMERR("Unrecognised message"); - return -EINVAL; - } - - if (mutex_lock_interruptible(&alldevs_lock)) - return -EINTR; - sprintf(result, "%lu", free_devid); - for (i = free_devid + 1; alldevs[i] && i < VVZ_MAX_DEVS; i++); - free_devid = i; - mutex_unlock(&alldevs_lock); - - return 0; -} - - static int vvz_map(struct dm_target *ti, struct bio *bio) { /* TODO: implement */ @@ -190,10 +181,10 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) { /* TODO: don't really know what to put here */ - limits->logical_block_size = VVZ_LOG_BLOCK_SIZE; - limits->physical_block_size = VVZ_LOG_BLOCK_SIZE; - limits->io_min = VVZ_LOG_BLOCK_SIZE; - limits->io_opt = VVZ_LOG_BLOCK_SIZE; + limits->logical_block_size = VVZ_BLOCK_SIZE; + limits->physical_block_size = VVZ_BLOCK_SIZE; + limits->io_min = VVZ_BLOCK_SIZE; + limits->io_opt = VVZ_BLOCK_SIZE; return; } @@ -215,7 +206,6 @@ static struct target_type vvz_target = { .module = THIS_MODULE, .ctr = vvz_ctr, .dtr = vvz_dtr, - .message = vvz_message, .map = vvz_map, .io_hints = vvz_io_hints, .iterate_devices = vvz_iterate_devices, @@ -227,9 +217,9 @@ static int __init vvz_init(void) { int err; - alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*alldevs)); - if (!alldevs) { - DMERR("Could not allocate alldevs"); + vvz_alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*vvz_alldevs)); + if (!vvz_alldevs) { + DMERR("Could not allocate vvz_alldevs"); err = -ENOMEM; goto bad_alldevs_alloc; } @@ -245,7 +235,7 @@ static int __init vvz_init(void) bad_register_target: - vfree(alldevs); + vfree(vvz_alldevs); bad_alldevs_alloc: DMERR("vvz not loaded"); return err; @@ -256,7 +246,7 @@ bad_alldevs_alloc: static void __exit vvz_exit(void) { dm_unregister_target(&vvz_target); - vfree(alldevs); + vfree(vvz_alldevs); DMINFO("vvz unloaded"); return; diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h new file mode 100644 index 0000000..5f4e39f --- /dev/null +++ b/dm-vvz/vvz.h @@ -0,0 +1,38 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _VVZ_H +#define _VVZ_H + + +#include +#include "device.h" + + +/* Global array of devices, and next free index */ +extern struct mutex vvz_alldevs_lock; +extern struct vvz_device **vvz_alldevs; +extern u32 vvz_free_devid; /* The lowest free devID */ + + +#endif /* _VVZ_H */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 54230e4..474f545 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -21,11 +21,15 @@ * If not, see . */ -/* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ +/* This is just a placeholder for defining constants and parameters that must + * be the same across Shufflecake components (kernel module, userland tool) + * such as block size, slice size etc */ + +#ifndef _VVZ_CONSTANTS_H_ +#define _VVZ_CONSTANTS_H_ #define VVZ_TARGET_NAME "vvz" -#define VVZ_TARGET_MSG_DEVID "reserve_dev_id" #define VVZ_VER_MAJOR 0 @@ -39,7 +43,8 @@ #define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL -#define VVZ_CRYPTO_KEYLEN 32 /* bytes */ +/* XTS "requires" (per the standard, at least) doubling the key size */ +#define VVZ_XTS_KEYLEN 64 /* bytes */ #define VVZ_BLOCK_SIZE 4096 /* bytes */ @@ -50,3 +55,5 @@ #define VVZ_DEV_MAX_VOLUMES 15 #define VVZ_MAX_DEVS 1024 + +#endif /* _VVZ_CONSTANTS_H_ */ From 6c76af0a3e04d7218583c07074a9ff0d7ddf5c8b Mon Sep 17 00:00:00 2001 From: = Date: Fri, 25 Aug 2023 11:56:46 +0200 Subject: [PATCH 11/98] feat:Define posmap's dirty bit --- dm-vvz/volume.c | 7 +++++++ dm-vvz/volume.h | 1 + 2 files changed, 8 insertions(+) diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 3290c42..e7c0de1 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -60,6 +60,11 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck DMERR("Could not allocate position map"); goto bad; } + sv->pme_unsynced = vmalloc(sd->tot_slices * sizeof(u8)); + if (!sv->pme_unsynced) { + DMERR("Could not allocate dirty bitfield for position map"); + goto bad; + } sv->mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) @@ -79,6 +84,8 @@ void vvz_vol_free(struct vvz_volume *sv) /* TODO remove from sysfs */ /* No locking needed here TODO: right? */ + if (sv->pme_unsynced) + vfree(sv->pme_unsynced); if (sv->posmap) vfree(sv->posmap); diff --git a/dm-vvz/volume.h b/dm-vvz/volume.h index 3d3dbeb..67015fb 100644 --- a/dm-vvz/volume.h +++ b/dm-vvz/volume.h @@ -54,6 +54,7 @@ struct vvz_volume /* Position map */ struct mutex posmap_lock; u32 *posmap; + bool *pme_unsynced; /* Dirty bit */ u32 mapped_slices; /* Crypto */ From 18f032aaa7b1de5e9d1ed8bd8001e23b665af72b Mon Sep 17 00:00:00 2001 From: = Date: Fri, 25 Aug 2023 17:18:39 +0200 Subject: [PATCH 12/98] feat:Write first posmap functions --- dm-vvz/crypto.h | 2 +- dm-vvz/device.c | 2 +- dm-vvz/device.h | 16 +++---- dm-vvz/io.h | 2 +- dm-vvz/posmap.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ dm-vvz/volume.h | 9 ++-- dm-vvz/vvz.h | 1 - 7 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 dm-vvz/posmap.c diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h index d97d3c0..4d07521 100644 --- a/dm-vvz/crypto.h +++ b/dm-vvz/crypto.h @@ -31,7 +31,7 @@ #define VVZ_CRYPTO_IVLEN 16 /* bytes */ -/* Use the *physical* address of the 4096-byte block within the device */ +/* Use the *physical* address of the 4096-byte block, within the device, as IV */ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw); diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 9282532..6b6f493 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -89,7 +89,7 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, DMERR("Could not allocate shuffled PSI array"); goto bad; } - sd->first_free_psi = 0; + sd->first_free_psi_idx = 0; /* Generate a permutation */ for (i = 0; i < tot_slices; i++) diff --git a/dm-vvz/device.h b/dm-vvz/device.h index cfb4028..1adcd1a 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -49,29 +49,27 @@ struct vvz_device /* Underlying block device */ struct dm_dev *dev; char bdev_path[VVZ_BDEV_PATH_LEN+1]; + u32 tot_slices; + + /* Header size in 512-byte sectors */ + u32 dev_header_size_sectors; /* All volumes mapping to this device */ struct mutex vols_lock; struct vvz_volume *vols[VVZ_DEV_MAX_VOLUMES]; size_t num_vols; - /* Slices stats */ - u32 tot_slices; - u32 free_slices; - - /* Header size in sectors */ - u32 dev_header_size_sectors; - /* Shuffled array of PSIs */ struct mutex shuffled_psis_lock; u32 *shuffled_psis; - u32 first_free_psi; /* in the shuffled array */ + u32 first_free_psi_idx; /* in the shuffled array */ bool *psi_taken; + u32 free_slices; }; struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, - u32 dev_id, u32 tot_slices); + u32 dev_id, u32 tot_slices); void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti); int vvz_dev_add_volume(struct vvz_device *sd, struct vvz_volume *sv, size_t vol_idx); diff --git a/dm-vvz/io.h b/dm-vvz/io.h index 65f8514..ddc6e4e 100644 --- a/dm-vvz/io.h +++ b/dm-vvz/io.h @@ -29,7 +29,7 @@ #include "volume.h" -/* Synchronously read/write a physical block from the device. +/* Synchronously read/write a physical block from/to the device. * Only used for the position map. */ int vvz_rwblock_sync(struct vvz_device *sd, u64 pblk_num, struct page *page, int rw); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c new file mode 100644 index 0000000..2511883 --- /dev/null +++ b/dm-vvz/posmap.c @@ -0,0 +1,114 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "posmap.h" + + +/* Get the next free PSI in the device's shuffled array, and mark it taken */ +static int take_free_psi(struct vvz_device *sd, u32 *psi); + + +/* Returns INVALID (in *psi) if mapping does not exist */ +int vvz_get_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) +{ + /* Sanity check (redundant?) */ + if(unlikely(lsi >= sv->sd->tot_slices)) + return -EINVAL; + + if (mutex_lock_interruptible(&sv->posmap_lock)) + return -EINTR; + *psi = sv->posmap[lsi]; + mutex_unlock(&sv->posmap_lock); + + return 0; +} + + +/* Does not persist mapping on disk (only done on FLUSH requests) */ +int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) +{ + int err; + + /* Sanity check (redundant?) */ + if(unlikely(lsi >= sv->sd->tot_slices)) + return -EINVAL; + + if (mutex_lock_interruptible(&sv->posmap_lock)) + return -EINTR; + + /* Check mapping not existent */ + if (unlikely(sv->posmap[lsi] != VVZ_POSMAP_INVALID)){ + err = -EINVAL; + goto out; + } + + /* Create it in the device */ + err = take_free_psi(sv->sd, psi); + if (err) + goto out; + /* And in the volume */ + sv->posmap[lsi] = *psi; + sv->mapped_slices += 1; + sv->pme_unsynced[lsi] = true; + + err = 0; + +out: + mutex_unlock(&sv->posmap_lock); + return err; +} + + +/* Get the next free PSI in the device's shuffled array, and mark it taken. + * This specific algorithm, that never wraps first_free_psi_idx around, + * only works because slices are never freed up. */ +static int take_free_psi(struct vvz_device *sd, u32 *psi) +{ + int err; + + if (mutex_lock_interruptible(&sd->shuffled_psis_lock)) + return -EINTR; + + /* PSI to return */ + if (unlikely(!sd->free_slices)) { + err = -ENOSPC; + goto out; + } + *psi = sd->shuffled_psis[sd->first_free_psi_idx]; + if (unlikely(sd->psi_taken[*psi])) { + err = -EINVAL; // TODO make it more tragic + goto out; + } + + /* Update device state */ + int i; + for (i = sd->first_free_psi_idx + 1; + i < sd->tot_slices && sd->psi_taken[i]; i++); + sd->first_free_psi_idx = i; + sd->free_slices -= 1; + sd->psi_taken[*psi] = true; + +out: + mutex_unlock(&sd->shuffled_psis_lock); + return err; +} diff --git a/dm-vvz/volume.h b/dm-vvz/volume.h index 67015fb..f5ff498 100644 --- a/dm-vvz/volume.h +++ b/dm-vvz/volume.h @@ -22,8 +22,8 @@ */ /* - * A volume represents a single "virtual" block device, mapping onto a - * "physical" device represented by a device. + * A volume represents a single "logical" storage unit, mapping onto an + * underlying "physical" device. */ #ifndef _VVZ_VOLUME_H_ @@ -42,12 +42,11 @@ struct vvz_device; struct vvz_volume { - /* Volume index within the device */ - size_t vol_idx; - /* Backing device */ struct vvz_device *sd; + /* Volume index within the device */ + size_t vol_idx; /* Name of the volume, sflc__*/ char vol_name[VVZ_VOL_NAME_MAX_LEN + 1]; diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 5f4e39f..0de1e69 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -25,7 +25,6 @@ #define _VVZ_H -#include #include "device.h" From 7d10c2aad554ba4be4835dec11ff6fc6456e328e Mon Sep 17 00:00:00 2001 From: = Date: Tue, 29 Aug 2023 12:52:09 +0200 Subject: [PATCH 13/98] feat:Minor nits --- dm-vvz/crypto.c | 2 +- dm-vvz/io.h | 4 ---- dm-vvz/posmap.c | 2 +- dm-vvz/vvz.c | 23 +++++++++++++++++++---- dm-vvz/vvz_constants.h | 2 +- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index d5df767..040adde 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -42,7 +42,7 @@ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, int err; /* TODO: not too sure about the gfp_mask here */ - req = skcipher_request_alloc(tfm, GFP_NOWAIT); + req = skcipher_request_alloc(tfm, GFP_NOIO); if (!req) return -ENOMEM; diff --git a/dm-vvz/io.h b/dm-vvz/io.h index ddc6e4e..8e4e110 100644 --- a/dm-vvz/io.h +++ b/dm-vvz/io.h @@ -29,10 +29,6 @@ #include "volume.h" -/* Synchronously read/write a physical block from/to the device. - * Only used for the position map. */ -int vvz_rwblock_sync(struct vvz_device *sd, u64 pblk_num, struct page *page, int rw); - /* Handlers for the logical bio's submitted to a volume */ int vvz_read(struct vvz_volume *sv, struct bio *bio); int vvz_write(struct vvz_volume *sv, struct bio *bio); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 2511883..cacb039 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -56,7 +56,7 @@ int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) if (mutex_lock_interruptible(&sv->posmap_lock)) return -EINTR; - /* Check mapping not existent */ + /* Check mapping not existent (redundant?) */ if (unlikely(sv->posmap[lsi] != VVZ_POSMAP_INVALID)){ err = -EINVAL; goto out; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 4572941..a734528 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -25,6 +25,8 @@ #include #include #include "device.h" +#include "volume.h" +#include "io.h" #include "vvz_constants.h" #include "vvz.h" #include "log.h" @@ -89,7 +91,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) return err; } - /* Create device if it doesn't exist yet (or check consistency if it does) */ + /* Create device if it doesn't exist yet (or check uniqueness if it does) */ if (mutex_lock_interruptible(&vvz_alldevs_lock)) return -EINTR; sd = vvz_alldevs[dev_id]; @@ -133,7 +135,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* Only accept one block per request for simplicity TODO: improve to one slice*/ - ti->max_io_len = VVZ_SECTOR_SCALE; + ti->max_io_len = VVZ_BLOCK_SCALE; ti->num_flush_bios = 1; ti->num_discard_bios = 0; ti->num_secure_erase_bios = 0; @@ -173,7 +175,20 @@ static void vvz_dtr(struct dm_target *ti) static int vvz_map(struct dm_target *ti, struct bio *bio) { - /* TODO: implement */ + struct vvz_volume *sv = ti->private; + + /* Bio type checks (redundant?) */ + if (unlikely(bio_op(bio) != REQ_OP_READ && + bio_op(bio) != REQ_OP_WRITE)) + return DM_MAPIO_KILL; /* TODO log it? */ + /* Bio size and bvec alignment checks (redundant?) */ + if (unlikely(bio->bi_iter.bi_size && + (bio->bi_iter.bi_size != VVZ_BLOCK_SIZE || + bio->bi_vcnt != 1))) + return DM_MAPIO_KILL; /* TODO log it? */ + + // TODO dispatch + return DM_MAPIO_REMAPPED; } @@ -196,7 +211,7 @@ static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn struct vvz_device *sd = sv->sd; return fn(ti, sv->sd->dev, 0, sd->dev_header_size_sectors + - (ti->len * VVZ_SECTOR_SCALE), data); + (ti->len * VVZ_BLOCK_SCALE), data); } diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 474f545..a8bcfda 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -49,7 +49,7 @@ #define VVZ_BLOCK_SIZE 4096 /* bytes */ #define VVZ_BLOCK_SHIFT 3 -#define VVZ_SECTOR_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in block */ +#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in block */ #define VVZ_DEV_MAX_VOLUMES 15 From 7129b4017454ca10753aa641df61cb9d42889be2 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 15:44:50 +0200 Subject: [PATCH 14/98] feat:Make posmap load/store sync and non-granular --- dm-vvz/device.c | 10 +++++++++- dm-vvz/device.h | 3 +++ dm-vvz/io.h | 6 +++--- dm-vvz/map_flush.c | 37 +++++++++++++++++++++++++++++++++++++ dm-vvz/posmap.c | 2 +- dm-vvz/posmap.h | 6 +++--- dm-vvz/volume.c | 8 +------- dm-vvz/volume.h | 2 +- dm-vvz/vvz.c | 22 ++++++++++++++++------ 9 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 dm-vvz/map_flush.c diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 6b6f493..18dc167 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -28,9 +28,11 @@ #include "log.h" +/* Depth of the mempool backing the bio_set */ +#define VVZ_BIOSET_BIOS 64 /* Compute device header size, in 512-byte sectors */ #define DEV_HEADER_SIZE_SECTORS(tot_slices) \ - (VVZ_SECTOR_SCALE * \ + (VVZ_BLOCK_SCALE * \ (1 + VVZ_DEV_MAX_VOLUMES * \ (1 + DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK) \ ))) @@ -96,6 +98,9 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, sd->shuffled_psis[i] = i; fisheryates_u32(sd->shuffled_psis, tot_slices); + /* Bioset */ + bioset_init(&sd->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + // TODO: init sysfs return sd; @@ -114,6 +119,9 @@ void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti) /* TODO remove from sysfs */ + /* Safe to call on zeroed bio_set */ + bioset_exit(&sd->bs); + /* No locking needed here TODO: right? */ if (sd->shuffled_psis) vfree(sd->shuffled_psis); diff --git a/dm-vvz/device.h b/dm-vvz/device.h index 1adcd1a..1d1fa3f 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -65,6 +65,9 @@ struct vvz_device u32 first_free_psi_idx; /* in the shuffled array */ bool *psi_taken; u32 free_slices; + + /* TODO should this be per-device? */ + struct bio_set bs; }; diff --git a/dm-vvz/io.h b/dm-vvz/io.h index 8e4e110..3b8e87a 100644 --- a/dm-vvz/io.h +++ b/dm-vvz/io.h @@ -30,9 +30,9 @@ /* Handlers for the logical bio's submitted to a volume */ -int vvz_read(struct vvz_volume *sv, struct bio *bio); -int vvz_write(struct vvz_volume *sv, struct bio *bio); -int vvz_flush(struct vvz_volume *sv, struct bio *bio); +int vvz_map_read(struct vvz_volume *sv, struct bio *bio); +int vvz_map_write(struct vvz_volume *sv, struct bio *bio); +int vvz_map_flush(struct vvz_volume *sv, struct bio *bio); #endif /* _VVZ_IO_H_ */ diff --git a/dm-vvz/map_flush.c b/dm-vvz/map_flush.c new file mode 100644 index 0000000..6182d18 --- /dev/null +++ b/dm-vvz/map_flush.c @@ -0,0 +1,37 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/** + * We treat logical FLUSH requests as follows. + * + */ + +#include "io.h" +#include "posmap.h" +#include "log.h" + + +int vvz_map_flush(struct vvz_volume *sv, struct bio *bio) +{ + +} diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index cacb039..b3931ec 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -69,7 +69,7 @@ int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) /* And in the volume */ sv->posmap[lsi] = *psi; sv->mapped_slices += 1; - sv->pme_unsynced[lsi] = true; + sv->posmap_dirty = true; err = 0; diff --git a/dm-vvz/posmap.h b/dm-vvz/posmap.h index 0933492..451b99c 100644 --- a/dm-vvz/posmap.h +++ b/dm-vvz/posmap.h @@ -32,9 +32,9 @@ /* At volume creation */ -int vvz_load_entire_posmap(struct vvz_volume *sv); -/* On FLUSH request. Actually stores a whole block of 1024 entries. */ -int vvz_store_posmap_entry(struct vvz_volume *sv, u32 lsi); +int vvz_load_posmap(struct vvz_volume *sv); +/* On FLUSH request */ +int vvz_store_posmap(struct vvz_volume *sv); /* Returns INVALID (in *psi) if mapping does not exist */ int vvz_get_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi); diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index e7c0de1..fdb4d74 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -60,11 +60,7 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck DMERR("Could not allocate position map"); goto bad; } - sv->pme_unsynced = vmalloc(sd->tot_slices * sizeof(u8)); - if (!sv->pme_unsynced) { - DMERR("Could not allocate dirty bitfield for position map"); - goto bad; - } + sv->posmap_dirty = false; sv->mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) @@ -84,8 +80,6 @@ void vvz_vol_free(struct vvz_volume *sv) /* TODO remove from sysfs */ /* No locking needed here TODO: right? */ - if (sv->pme_unsynced) - vfree(sv->pme_unsynced); if (sv->posmap) vfree(sv->posmap); diff --git a/dm-vvz/volume.h b/dm-vvz/volume.h index f5ff498..582f363 100644 --- a/dm-vvz/volume.h +++ b/dm-vvz/volume.h @@ -53,7 +53,7 @@ struct vvz_volume /* Position map */ struct mutex posmap_lock; u32 *posmap; - bool *pme_unsynced; /* Dirty bit */ + bool posmap_dirty; u32 mapped_slices; /* Crypto */ diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index a734528..cd3da30 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -136,10 +136,13 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_BLOCK_SCALE; + ti->flush_supported = true; ti->num_flush_bios = 1; + ti->discards_supported = false; ti->num_discard_bios = 0; ti->num_secure_erase_bios = 0; ti->num_write_zeroes_bios = 0; + ti->accounts_remapped_io = true; ti->private = sv; return 0; @@ -176,20 +179,27 @@ static void vvz_dtr(struct dm_target *ti) static int vvz_map(struct dm_target *ti, struct bio *bio) { struct vvz_volume *sv = ti->private; + enum req_op op = bio_op(bio); - /* Bio type checks (redundant?) */ - if (unlikely(bio_op(bio) != REQ_OP_READ && - bio_op(bio) != REQ_OP_WRITE)) - return DM_MAPIO_KILL; /* TODO log it? */ /* Bio size and bvec alignment checks (redundant?) */ if (unlikely(bio->bi_iter.bi_size && (bio->bi_iter.bi_size != VVZ_BLOCK_SIZE || bio->bi_vcnt != 1))) return DM_MAPIO_KILL; /* TODO log it? */ - // TODO dispatch + /* Dispatch */ + if (bio->bi_opf & REQ_PREFLUSH) { + /* DM core should only send empty flush requests */ + if (unlikely(bio->bi_iter.bi_size)) + return DM_MAPIO_KILL; // TODO log it? + return vvz_map_flush(sv, bio); + } else if (op == REQ_OP_READ) + return vvz_map_read(sv, bio); + else if (op == REQ_OP_WRITE) + return vvz_map_write(sv, bio); - return DM_MAPIO_REMAPPED; + DMWARN("Unrecognised bio: bio_op(bio) = %d", op); + return DM_MAPIO_KILL; } From 3ca7fe54e6c81693f1b495a6603b8e782b233b66 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 15:49:31 +0200 Subject: [PATCH 15/98] feat:Add dm-io client --- dm-vvz/device.c | 10 ++++++++++ dm-vvz/device.h | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 18dc167..a707167 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -100,6 +100,13 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, /* Bioset */ bioset_init(&sd->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + /* Client for dm-io */ + sd->io_client = dm_io_client_create(); + if (IS_ERR(sd->io_client)) { + DMERR("Could not create dm-io client"); + sd->io_client = NULL; + goto bad; + } // TODO: init sysfs @@ -119,6 +126,9 @@ void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti) /* TODO remove from sysfs */ + /* Needs to be zeroed out in the ctor, on failure */ + if (sd->io_client) + dm_io_client_destroy(sd->io_client); /* Safe to call on zeroed bio_set */ bioset_exit(&sd->bs); diff --git a/dm-vvz/device.h b/dm-vvz/device.h index 1d1fa3f..7f03745 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -31,6 +31,7 @@ #include +#include #include "vvz_constants.h" @@ -66,8 +67,9 @@ struct vvz_device bool *psi_taken; u32 free_slices; - /* TODO should this be per-device? */ + /* TODO should these be per-device? */ struct bio_set bs; + struct dm_io_client *io_client; }; From aeb1d7145883ce93f3268aa063aa3163ffe15d62 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 16:31:03 +0200 Subject: [PATCH 16/98] feat:Add crypt_page and crypt_buf --- dm-vvz/crypto.c | 48 +++++++++++++++++++++++++++++++++++++----------- dm-vvz/crypto.h | 11 +++++++++-- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index 040adde..052db00 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -28,17 +28,50 @@ #include "log.h" +static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, + struct scatterlist *dst, u64 pblk_num, int rw); + + +/* Encrypt-decrypt a whole memory page */ +int vvz_crypt_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw) +{ + struct scatterlist dst, src; + + sg_init_table(&src, 1); + sg_set_page(&src, src_page, PAGE_SIZE, 0); + sg_init_table(&dst, 1); + sg_set_page(&dst, dst_page, PAGE_SIZE, 0); + + return crypt_sg(tfm, &src, &dst, pblk_num, rw); +} + + +/* Encrypt-decrypt a (possibly vmalloc'ed) buffer */ +int vvz_crypt_buf(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, + size_t buflen, u64 pblk_num, int rw) +{ + struct scatterlist dst, src; + + sg_init_table(&src, 1); + sg_set_buf(&src, src_buf, buflen); + sg_init_table(&dst, 1); + sg_set_buf(&dst, dst_buf, buflen); + + return crypt_sg(tfm, &src, &dst, pblk_num, rw); +} + + /* * The IV is constructed as the 0-padded LE representation of the block number, * which is exactly what dm-crypt does (IV mode "plain64") */ -int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw) +static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, + struct scatterlist *dst, u64 pblk_num, int rw) { u8 iv[VVZ_CRYPTO_IVLEN]; struct skcipher_request *req = NULL; DECLARE_CRYPTO_WAIT(wait); - struct scatterlist dst, src; int err; /* TODO: not too sure about the gfp_mask here */ @@ -50,18 +83,11 @@ int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait); - /* We assume PAGE_SIZE to equal Shufflecake's block size */ - // TODO: better document this, maybe ensure it with compile-time checks - sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); - sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - /* Construct IV */ memset(iv, 0, VVZ_CRYPTO_IVLEN); *(__le64 *)iv = cpu_to_le64(pblk_num); - skcipher_request_set_crypt(req, &src, &dst, VVZ_BLOCK_SIZE, iv); + skcipher_request_set_crypt(req, src, dst, VVZ_BLOCK_SIZE, iv); if (rw == READ) err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); else diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h index 4d07521..68ace42 100644 --- a/dm-vvz/crypto.h +++ b/dm-vvz/crypto.h @@ -21,6 +21,9 @@ * If not, see . */ +/* For AES-XTS-256, we use the *physical* address of the 4096-byte block, + * within the device, as IV (little-endian, zero-padded). */ + #ifndef _VVZ_CRYPTO_H_ #define _VVZ_CRYPTO_H_ @@ -31,9 +34,13 @@ #define VVZ_CRYPTO_IVLEN 16 /* bytes */ -/* Use the *physical* address of the 4096-byte block, within the device, as IV */ -int vvz_crypt_block(struct crypto_skcipher *tfm, struct page *src_page, +/* Encrypt-decrypt a whole memory page */ +int vvz_crypt_page(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw); +/* Encrypt-decrypt a (possibly vmalloc'ed) buffer */ +int vvz_crypt_buf(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, + size_t buflen, u64 pblk_num, int rw); + #endif /* _VVZ_CRYPTO_H_ */ From b0de8b9caed2763bfd1256381b51e4ebd92e3ab0 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 17:42:39 +0200 Subject: [PATCH 17/98] feat:Add to posmpa.c --- dm-vvz/device.c | 9 +++++++- dm-vvz/device.h | 6 ++++-- dm-vvz/{io.h => map_bio.h} | 0 dm-vvz/map_flush.c | 2 +- dm-vvz/posmap.c | 44 ++++++++++++++++++++++++++++++++++++++ dm-vvz/vvz.c | 2 +- 6 files changed, 58 insertions(+), 5 deletions(-) rename dm-vvz/{io.h => map_bio.h} (100%) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index a707167..5c6c61b 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -63,7 +63,14 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, sd->tot_slices = tot_slices; sd->free_slices = tot_slices; - sd->dev_header_size_sectors = DEV_HEADER_SIZE_SECTORS(tot_slices); + /* Enough blocks to fit all the entries */ + sd->posmap_size_sectors = VVZ_BLOCK_SCALE * + DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK); + /* PosMap + VMB */ + sd->vol_header_size_sectors = VVZ_BLOCK_SCALE + sd->posmap_size_sectors; + /* Volume headers + DMB */ + sd->dev_header_size_sectors = VVZ_BLOCK_SCALE + + VVZ_DEV_MAX_VOLUMES * sd->vol_header_size_sectors; /* Path to device */ if (strlen(bdev_path) > VVZ_BDEV_PATH_LEN) { diff --git a/dm-vvz/device.h b/dm-vvz/device.h index 7f03745..fc5b046 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -52,8 +52,10 @@ struct vvz_device char bdev_path[VVZ_BDEV_PATH_LEN+1]; u32 tot_slices; - /* Header size in 512-byte sectors */ - u32 dev_header_size_sectors; + /* Header sizes in 512-byte sectors */ + sector_t posmap_size_sectors; + sector_t vol_header_size_sectors; + sector_t dev_header_size_sectors; /* All volumes mapping to this device */ struct mutex vols_lock; diff --git a/dm-vvz/io.h b/dm-vvz/map_bio.h similarity index 100% rename from dm-vvz/io.h rename to dm-vvz/map_bio.h diff --git a/dm-vvz/map_flush.c b/dm-vvz/map_flush.c index 6182d18..fc7337f 100644 --- a/dm-vvz/map_flush.c +++ b/dm-vvz/map_flush.c @@ -26,7 +26,7 @@ * */ -#include "io.h" +#include "map_bio.h" #include "posmap.h" #include "log.h" diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index b3931ec..e345af0 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -24,6 +24,11 @@ #include "posmap.h" +/******************************************************************************* + * In-memory operations on the position map + ******************************************************************************/ + + /* Get the next free PSI in the device's shuffled array, and mark it taken */ static int take_free_psi(struct vvz_device *sd, u32 *psi); @@ -112,3 +117,42 @@ out: mutex_unlock(&sd->shuffled_psis_lock); return err; } + + +/******************************************************************************* + * On-disk operations on the position map + ******************************************************************************/ + + +/* Synchronously read/write the entire on-disk encrypted position map */ +static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw); + + +/* Read position map from disk and decrypt in place */ +int vvz_load_posmap(struct vvz_volume *sv); + + +/* Encrypt position map out of place, onto a buffer, and write it on disk */ +int vvz_store_posmap(struct vvz_volume *sv); + + +/* Synchronously read/write the entire on-disk encrypted position map */ +static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw) +{ + struct dm_io_request io_req = { + .bi_opf = (rw == READ) ? REQ_OP_READ : REQ_OP_WRITE, + .mem.type = DM_IO_VMA, + .mem.ptr.vma = buf, + .notify.fn = NULL, + .client = sv->sd->io_client + }; + struct dm_io_region io_region = { + .bdev = sv->sd->dev->bdev, + .sector = 1 + VVZ_DEV_MAX_VOLUMES + + (sv->vol_idx * sv->sd->posmap_size_sectors), + .count = sv->sd->posmap_size_sectors + }; + + return dm_io(&io_req, 1, &io_region, NULL); +} + diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index cd3da30..19301ac 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -26,7 +26,7 @@ #include #include "device.h" #include "volume.h" -#include "io.h" +#include "map_bio.h" #include "vvz_constants.h" #include "vvz.h" #include "log.h" From c78d71c756f73221ca23828a7396f3996c0fa08a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 19:05:24 +0200 Subject: [PATCH 18/98] fix:Crypto --- dm-vvz/crypto.c | 37 +++++++++++++++++++++++++------------ dm-vvz/crypto.h | 10 +++++----- dm-vvz/device.c | 8 +++----- dm-vvz/device.h | 1 - dm-vvz/posmap.c | 5 ++++- dm-vvz/volume.c | 3 ++- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index 052db00..0107b10 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -32,33 +32,46 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, struct scatterlist *dst, u64 pblk_num, int rw); -/* Encrypt-decrypt a whole memory page */ -int vvz_crypt_page(struct crypto_skcipher *tfm, struct page *src_page, +/* Encrypt-decrypt a single block (memory buffer is a page) */ +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw) { struct scatterlist dst, src; + /* We assume PAGE_SIZE == VVZ_BLOCK_SIZE TODO better document this */ sg_init_table(&src, 1); - sg_set_page(&src, src_page, PAGE_SIZE, 0); + sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, PAGE_SIZE, 0); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); return crypt_sg(tfm, &src, &dst, pblk_num, rw); } -/* Encrypt-decrypt a (possibly vmalloc'ed) buffer */ -int vvz_crypt_buf(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, - size_t buflen, u64 pblk_num, int rw) +/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ +int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, + size_t num_blocks, u64 first_pblk_num, int rw) { struct scatterlist dst, src; + size_t block; + u64 pblk_num; + int err; - sg_init_table(&src, 1); - sg_set_buf(&src, src_buf, buflen); - sg_init_table(&dst, 1); - sg_set_buf(&dst, dst_buf, buflen); + pblk_num = first_pblk_num; + for (block = 0; block < num_blocks; block++) { + sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); + sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); - return crypt_sg(tfm, &src, &dst, pblk_num, rw); + err = crypt_sg(tfm, &src, &dst, pblk_num, rw); + if (err) + return err; + + src_buf += VVZ_BLOCK_SIZE; + dst_buf += VVZ_BLOCK_SIZE; + pblk_num += 1; + } + + return 0; } diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h index 68ace42..c3a83c3 100644 --- a/dm-vvz/crypto.h +++ b/dm-vvz/crypto.h @@ -34,13 +34,13 @@ #define VVZ_CRYPTO_IVLEN 16 /* bytes */ -/* Encrypt-decrypt a whole memory page */ -int vvz_crypt_page(struct crypto_skcipher *tfm, struct page *src_page, +/* Encrypt-decrypt a single block (memory buffer is a page) */ +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw); -/* Encrypt-decrypt a (possibly vmalloc'ed) buffer */ -int vvz_crypt_buf(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, - size_t buflen, u64 pblk_num, int rw); +/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ +int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, + size_t num_blocks, u64 first_pblk_num, int rw); #endif /* _VVZ_CRYPTO_H_ */ diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 5c6c61b..b9aec7d 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -66,11 +66,9 @@ struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, /* Enough blocks to fit all the entries */ sd->posmap_size_sectors = VVZ_BLOCK_SCALE * DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK); - /* PosMap + VMB */ - sd->vol_header_size_sectors = VVZ_BLOCK_SCALE + sd->posmap_size_sectors; - /* Volume headers + DMB */ - sd->dev_header_size_sectors = VVZ_BLOCK_SCALE + - VVZ_DEV_MAX_VOLUMES * sd->vol_header_size_sectors; + /* VMBs + PosMaps + DMB */ + sd->dev_header_size_sectors = VVZ_BLOCK_SCALE + VVZ_DEV_MAX_VOLUMES * + (VVZ_BLOCK_SCALE + sd->posmap_size_sectors); /* Path to device */ if (strlen(bdev_path) > VVZ_BDEV_PATH_LEN) { diff --git a/dm-vvz/device.h b/dm-vvz/device.h index fc5b046..62231fd 100644 --- a/dm-vvz/device.h +++ b/dm-vvz/device.h @@ -54,7 +54,6 @@ struct vvz_device /* Header sizes in 512-byte sectors */ sector_t posmap_size_sectors; - sector_t vol_header_size_sectors; sector_t dev_header_size_sectors; /* All volumes mapping to this device */ diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index e345af0..2d62150 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -129,7 +129,10 @@ static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw); /* Read position map from disk and decrypt in place */ -int vvz_load_posmap(struct vvz_volume *sv); +int vvz_load_posmap(struct vvz_volume *sv) +{ + +} /* Encrypt position map out of place, onto a buffer, and write it on disk */ diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index fdb4d74..27a56b2 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -55,7 +55,8 @@ struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enck /* Position map */ mutex_init(&sv->posmap_lock); - sv->posmap = vmalloc(sd->tot_slices * sizeof(u32)); + /* Slight over-allocation, to fit a whole number of blocks */ + sv->posmap = vmalloc(sd->posmap_size_sectors * SECTOR_SIZE); if (!sv->posmap) { DMERR("Could not allocate position map"); goto bad; From d34df4f3888ee956a5690d2ae6aecc0e598631a3 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2023 19:18:02 +0200 Subject: [PATCH 19/98] feat:Write load_posmap --- dm-vvz/posmap.c | 19 +++++++++++++++++-- dm-vvz/posmap.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 2d62150..2244588 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -22,6 +22,7 @@ */ #include "posmap.h" +#include "crypto.h" /******************************************************************************* @@ -131,7 +132,22 @@ static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw); /* Read position map from disk and decrypt in place */ int vvz_load_posmap(struct vvz_volume *sv) { + int err; + /* No locking needed (we are in the constructor) */ + err = rw_posmap(sv, sv->posmap, READ); + if (err) + return err; + + /* Decrypt in place */ + err = vvz_crypt_blocks_vm(sv->tfm, sv->posmap, sv->posmap, + sv->sd->posmap_size_sectors >> VVZ_BLOCK_SHIFT, + VVZ_POSMAP_START_SECTOR(sv) >> VVZ_BLOCK_SHIFT, + READ); + if (err) + return err; + + return 0; } @@ -151,8 +167,7 @@ static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw) }; struct dm_io_region io_region = { .bdev = sv->sd->dev->bdev, - .sector = 1 + VVZ_DEV_MAX_VOLUMES + - (sv->vol_idx * sv->sd->posmap_size_sectors), + .sector = VVZ_POSMAP_START_SECTOR(sv), .count = sv->sd->posmap_size_sectors }; diff --git a/dm-vvz/posmap.h b/dm-vvz/posmap.h index 451b99c..df6c3a3 100644 --- a/dm-vvz/posmap.h +++ b/dm-vvz/posmap.h @@ -29,6 +29,10 @@ #define VVZ_POSMAP_INVALID 0xFFFFFFFF +/* Starting sector of position map */ +#define VVZ_POSMAP_START_SECTOR(sv) \ + (VVZ_BLOCK_SCALE * (1 + VVZ_DEV_MAX_VOLUMES) + \ + (sv)->vol_idx * (sv)->sd->posmap_size_sectors) /* At volume creation */ From 00037ee89eb629581dc62bf88fd239c59e536cdc Mon Sep 17 00:00:00 2001 From: = Date: Sat, 4 Nov 2023 11:04:09 +0100 Subject: [PATCH 20/98] feat:Implement storePosmap --- dm-vvz/map_flush.c | 6 ++++-- dm-vvz/posmap.c | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dm-vvz/map_flush.c b/dm-vvz/map_flush.c index fc7337f..f4dd419 100644 --- a/dm-vvz/map_flush.c +++ b/dm-vvz/map_flush.c @@ -22,8 +22,10 @@ */ /** - * We treat logical FLUSH requests as follows. - * + * We process FLUSH requests synchronously, for simplicity. + * First we store the position map, if it has been updated since the last FLUSH, + * and we wait for the operation to finish. Then, we forward the FLUSH to the + * underlying device. */ #include "map_bio.h" diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 2244588..0eae38e 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -152,7 +152,26 @@ int vvz_load_posmap(struct vvz_volume *sv) /* Encrypt position map out of place, onto a buffer, and write it on disk */ -int vvz_store_posmap(struct vvz_volume *sv); +int vvz_store_posmap(struct vvz_volume *sv) +{ + u8 *enc_posmap; + int err; + + /* Allocate it every time TODO evaluate alternatives */ + enc_posmap = vmalloc(sv->sd->posmap_size_sectors * SECTOR_SIZE); + if (!enc_posmap) + return -ENOMEM; + + if (mutex_lock_interruptible&sv->posmap_lock) { + err = -EINTR; + goto out_vfree; + } + + +out_vfree: + vfree(enc_posmap); + return err; +} /* Synchronously read/write the entire on-disk encrypted position map */ From 898834850cc7708c38e6b060b2d405924f8ca6d9 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 2 Jan 2024 18:21:01 +0100 Subject: [PATCH 21/98] Add notes --- dm-vvz/SCHOLIA.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 dm-vvz/SCHOLIA.md diff --git a/dm-vvz/SCHOLIA.md b/dm-vvz/SCHOLIA.md new file mode 100644 index 0000000..e15e14a --- /dev/null +++ b/dm-vvz/SCHOLIA.md @@ -0,0 +1,10 @@ +Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, dm-crypt uses a bound io_queue for bio mapping; it also has a crypt_queue for encryption/decryption, that is WQ_CPU_INTENSIVE: that wq is either bound or unbound, based on the flag DM_CRYPT_SAME_CPU. Both these workqueues are WQ_MEM_RECLAIM, and we probably need that for Shufflecake too. +As for granularity, almost no DM target uses the kernel-global wq (with schedule_work()); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by max_active) in the future, so that's one thing to look out for. +All in all, we can use a single module-wide bound wq, that is WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, and whose work items process bio's start-to-finish. + +Let's talk about flush requests. Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see REQ_OP_FLUSH bio's incoming: rather, we'll receive an empty bio with REQ_OP_WRITE | REQ_PREFLUSH. Indeed, that's what dm-crypt checks for (there's also a comment that reads "for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight", although I couldn't confirm that myself). REQ_FUA might be set on some bio's, and we should pass it down. +Now, how to handle them? In this first implementation, we have nothing to flush, so we just pass them down. This is because there is no volatile cache: the in-memory view of the position map is write-through. This is unlikely to change in the future, so it's relatively safe to say we'll always be able to just passthrough the flush requests. + +About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the posmap_lock until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. +There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a u32) to signal that the entry "is being written to disk": this limits the range of usable PSIs to 0x00000000 to 0x7FFFFFFE (with 0x7FFFFFFF and 0xFFFFFFFF being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. +The optimised posmap would work as follows. The function create_slice_mapping() takes the posmap_lock, then samples a new PSIs, then writes it in the posmap **with the MSB set**, then releases the posmap_lock (so other bio's can use the posmap), then writes the updated posmap block on the disk. If a new incoming bio falls in the same LSI, it can still proceed even if the MSB of the entry is set ("speculating" that the update of the posmap block will succeed) and use the slice mapping normally to launch the mapped bio; then, just before returning, it waits for the MSB to clear using a waitqueue (as explained in [LDD](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s02.html)). The waitqueue is declared in the volume struct alongside the posmap, and is woken up by create_slice_mapping() once the update of the posmap block finishes successfully, after clearing the MSB. From fa0b61f821357abc7b3edde154341a01f4479814 Mon Sep 17 00:00:00 2001 From: toninov Date: Tue, 2 Jan 2024 17:25:38 +0000 Subject: [PATCH 22/98] Update dm-vvz/SCHOLIA.md --- dm-vvz/SCHOLIA.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dm-vvz/SCHOLIA.md b/dm-vvz/SCHOLIA.md index e15e14a..bb305ec 100644 --- a/dm-vvz/SCHOLIA.md +++ b/dm-vvz/SCHOLIA.md @@ -1,10 +1,10 @@ -Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, dm-crypt uses a bound io_queue for bio mapping; it also has a crypt_queue for encryption/decryption, that is WQ_CPU_INTENSIVE: that wq is either bound or unbound, based on the flag DM_CRYPT_SAME_CPU. Both these workqueues are WQ_MEM_RECLAIM, and we probably need that for Shufflecake too. -As for granularity, almost no DM target uses the kernel-global wq (with schedule_work()); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by max_active) in the future, so that's one thing to look out for. -All in all, we can use a single module-wide bound wq, that is WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, and whose work items process bio's start-to-finish. +Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, `dm-crypt` uses a bound `io_queue` for bio mapping; it also has a `crypt_queue` for encryption/decryption, that is `WQ_CPU_INTENSIVE`: that wq is either bound or unbound, based on the flag `DM_CRYPT_SAME_CPU`. Both these workqueues are `WQ_MEM_RECLAIM`, and we probably need that for Shufflecake too. +As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. +All in all, we can use a single module-wide bound wq, that is `WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE`, and whose work items process bio's start-to-finish. -Let's talk about flush requests. Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see REQ_OP_FLUSH bio's incoming: rather, we'll receive an empty bio with REQ_OP_WRITE | REQ_PREFLUSH. Indeed, that's what dm-crypt checks for (there's also a comment that reads "for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight", although I couldn't confirm that myself). REQ_FUA might be set on some bio's, and we should pass it down. +Let's talk about flush requests. Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see `REQ_OP_FLUSH` bio's incoming: rather, we'll receive an empty bio with `REQ_OP_WRITE | REQ_PREFLUSH`. Indeed, that's what `dm-crypt` checks for (there's also a comment that reads "for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight", although I couldn't confirm that myself). `REQ_FUA` might be set on some bio's, and we should pass it down. Now, how to handle them? In this first implementation, we have nothing to flush, so we just pass them down. This is because there is no volatile cache: the in-memory view of the position map is write-through. This is unlikely to change in the future, so it's relatively safe to say we'll always be able to just passthrough the flush requests. -About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the posmap_lock until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. -There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a u32) to signal that the entry "is being written to disk": this limits the range of usable PSIs to 0x00000000 to 0x7FFFFFFE (with 0x7FFFFFFF and 0xFFFFFFFF being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. -The optimised posmap would work as follows. The function create_slice_mapping() takes the posmap_lock, then samples a new PSIs, then writes it in the posmap **with the MSB set**, then releases the posmap_lock (so other bio's can use the posmap), then writes the updated posmap block on the disk. If a new incoming bio falls in the same LSI, it can still proceed even if the MSB of the entry is set ("speculating" that the update of the posmap block will succeed) and use the slice mapping normally to launch the mapped bio; then, just before returning, it waits for the MSB to clear using a waitqueue (as explained in [LDD](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s02.html)). The waitqueue is declared in the volume struct alongside the posmap, and is woken up by create_slice_mapping() once the update of the posmap block finishes successfully, after clearing the MSB. +About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the `posmap_lock` until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. +There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a `u32`) to signal that the entry "is being written to disk": this limits the range of usable PSIs to 0x00000000 to 0x7FFFFFFE (with 0x7FFFFFFF and 0xFFFFFFFF being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. +The optimised posmap would work as follows. The function `create_slice_mapping()` takes the `posmap_lock`, then samples a new PSIs, then writes it in the posmap **with the MSB set**, then releases the `posmap_lock` (so other bio's can use the posmap), then writes the updated posmap block on the disk. If a new incoming bio falls in the same LSI, it can still proceed even if the MSB of the entry is set ("speculating" that the update of the posmap block will succeed) and use the slice mapping normally to launch the mapped bio; then, just before returning, it waits for the MSB to clear using a waitqueue (as explained in [LDD](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s02.html)). The waitqueue is declared in the volume struct alongside the posmap, and is woken up by `create_slice_mapping()` once the update of the posmap block finishes successfully, after clearing the MSB. From ac84ffade8d7a5bcb2c283cf314fd7fa03ebcc78 Mon Sep 17 00:00:00 2001 From: toninov Date: Tue, 2 Jan 2024 22:16:58 +0000 Subject: [PATCH 23/98] Update dm-vvz/SCHOLIA.md --- dm-vvz/SCHOLIA.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dm-vvz/SCHOLIA.md b/dm-vvz/SCHOLIA.md index bb305ec..f32f15d 100644 --- a/dm-vvz/SCHOLIA.md +++ b/dm-vvz/SCHOLIA.md @@ -1,10 +1,18 @@ -Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, `dm-crypt` uses a bound `io_queue` for bio mapping; it also has a `crypt_queue` for encryption/decryption, that is `WQ_CPU_INTENSIVE`: that wq is either bound or unbound, based on the flag `DM_CRYPT_SAME_CPU`. Both these workqueues are `WQ_MEM_RECLAIM`, and we probably need that for Shufflecake too. -As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. +#### Workqueues +Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, `dm-crypt` uses a bound `io_queue` for bio mapping; it also has a `crypt_queue` for encryption/decryption, that is `WQ_CPU_INTENSIVE`: that wq is either bound or unbound, based on the flag `DM_CRYPT_SAME_CPU`. Both these workqueues are `WQ_MEM_RECLAIM`, and we probably need that for Shufflecake too. +As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. All in all, we can use a single module-wide bound wq, that is `WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE`, and whose work items process bio's start-to-finish. -Let's talk about flush requests. Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see `REQ_OP_FLUSH` bio's incoming: rather, we'll receive an empty bio with `REQ_OP_WRITE | REQ_PREFLUSH`. Indeed, that's what `dm-crypt` checks for (there's also a comment that reads "for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight", although I couldn't confirm that myself). `REQ_FUA` might be set on some bio's, and we should pass it down. -Now, how to handle them? In this first implementation, we have nothing to flush, so we just pass them down. This is because there is no volatile cache: the in-memory view of the position map is write-through. This is unlikely to change in the future, so it's relatively safe to say we'll always be able to just passthrough the flush requests. +#### Position map: write-back or write-through? +A first idea was to have slice allocation happen exclusively in memory, and only synchronise the position map to disk upon FLUSH requests (write-back posmap), but this gives the scheme a weird and inconsistent *crash behaviour*. +Suppose, for example, that the upper-layer FS keeps an `index` block with a list of all allocated data blocks; suppose that a new data block is written at a position falling in a yet-unmapped slice (triggering slice allocation); the `index` block is consequently updated so that the list also contains a pointer to the new data block; suppose that, by Satan's inscrutable will, the update of the `index` block gets persisted to disk but the system crashes right afterwards, before the on-disk position map is updated; when the system boots back up, the FS will legitimately believe that the data block is allocated (because that's what the `index` block says) and contains all zeros (because READ requests to blocks in unmapped slices return all-zeros, for good reasons). This is bad. +On the other hand, one might possibly argue that the upper layer employed an unsafe sequence of operations, and should have only updated the `index` after a FLUSH request. After all, the all-zeros was just the previous (logical) content of the data block, before it was written by the FS, so it's just as if our driver reordered the two WRITE requests and failed in the middle, which is perfectly legit. Right now, I can't think of a more compelling argument against a write-back position map, but the fact that a WRITE request can complete, only for the affected block to then become "unreachable" (i.e. contain all-zeros) again, is fishy. +Anyway, to avoid headaches, we'll probably go for a write-through position map at first, at least until we discuss this more accurately with someone. Also, this simplifies handling of FLUSH requests: we have nothing to flush, so we just pass them down. This is because there is no volatile cache, since the in-memory view of the position map is write-through. -About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the `posmap_lock` until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. -There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a `u32`) to signal that the entry "is being written to disk": this limits the range of usable PSIs to 0x00000000 to 0x7FFFFFFE (with 0x7FFFFFFF and 0xFFFFFFFF being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. +#### FLUSH requests and where to find them +How do we even know if a bio is a FLUSH? Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see `REQ_OP_FLUSH` bio's incoming: rather, we'll receive an empty bio with `REQ_OP_WRITE | REQ_PREFLUSH`. Indeed, that's what `dm-crypt` checks for; there's also a comment there that reads `for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight` although I couldn't confirm that myself. `REQ_FUA` might be set on some bio's, and we should pass it down; I've read somewhere that the device-mapper core will strip it and just send an emtpy FLUSH afterwards, but I haven't found many confirmations of that. + +#### Locking of write-through posmap +About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the `posmap_lock` for dear life until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. +There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a `u32`) to signal that the entry "is being written to disk": this limits the range of usable PSIs to `0x00000000` to `0x7FFFFFFE` (with `0x7FFFFFFF` and `0xFFFFFFFF` being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. The optimised posmap would work as follows. The function `create_slice_mapping()` takes the `posmap_lock`, then samples a new PSIs, then writes it in the posmap **with the MSB set**, then releases the `posmap_lock` (so other bio's can use the posmap), then writes the updated posmap block on the disk. If a new incoming bio falls in the same LSI, it can still proceed even if the MSB of the entry is set ("speculating" that the update of the posmap block will succeed) and use the slice mapping normally to launch the mapped bio; then, just before returning, it waits for the MSB to clear using a waitqueue (as explained in [LDD](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s02.html)). The waitqueue is declared in the volume struct alongside the posmap, and is woken up by `create_slice_mapping()` once the update of the posmap block finishes successfully, after clearing the MSB. From e8f162cbab6519defa185e381a7b1fbb22703a1f Mon Sep 17 00:00:00 2001 From: = Date: Sun, 7 Jan 2024 18:14:11 +0100 Subject: [PATCH 24/98] Use a single .h --- dm-vvz/device.c | 171 +++++++++++++++++++---------------------- dm-vvz/device.h | 85 -------------------- dm-vvz/log.h | 31 -------- dm-vvz/map_bio.h | 38 --------- dm-vvz/map_flush.c | 39 ---------- dm-vvz/posmap.c | 4 +- dm-vvz/volume.c | 54 ++++++------- dm-vvz/volume.h | 68 ---------------- dm-vvz/vvz.c | 153 +++++++++++++++++++++++------------- dm-vvz/vvz.h | 106 ++++++++++++++++++++++++- dm-vvz/vvz_constants.h | 6 +- 11 files changed, 312 insertions(+), 443 deletions(-) delete mode 100644 dm-vvz/device.h delete mode 100644 dm-vvz/log.h delete mode 100644 dm-vvz/map_bio.h delete mode 100644 dm-vvz/map_flush.c delete mode 100644 dm-vvz/volume.h diff --git a/dm-vvz/device.c b/dm-vvz/device.c index b9aec7d..b3c3e4c 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -24,8 +24,7 @@ #include #include #include -#include "device.h" -#include "log.h" +#include "vvz.h" /* Depth of the mempool backing the bio_set */ @@ -39,170 +38,154 @@ /* Fisher-Yates shuffle */ -static void fisheryates_u32(u32 *v, u32 len); - - -struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, - u32 dev_id, u32 tot_slices) +static void fisheryates_u32(u32 *v, u32 len) { - struct vvz_device *sd; + u32 i, j, tmp; + + for (i = len-1; i >= 1; i--) { + j = get_random_u32_below(i+1); + + /* Swap v[i] and v[j] */ + tmp = v[i]; + v[i] = v[j]; + v[j] = tmp; + } + + return; +} + +struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) +{ + struct vvz_device *sdev; int err; int i; - sd = kzalloc(sizeof(*sd), GFP_KERNEL); - if (!sd) { + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) { DMERR("Could not allocate device"); - return NULL; + return ERR_PTR(-ENOMEM); } - sd->dev_id = dev_id; + sdev->dev_id = dev_id; - mutex_init(&sd->vols_lock); + mutex_init(&sdev->volumes_lock); for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) - sd->vols[i] = NULL; - sd->num_vols = 0; + sdev->volumes[i] = NULL; + sdev->num_volumes = 0; - sd->tot_slices = tot_slices; - sd->free_slices = tot_slices; - /* Enough blocks to fit all the entries */ - sd->posmap_size_sectors = VVZ_BLOCK_SCALE * + /* Compute sizes */ + sdev->tot_slices = tot_slices; + sdev->free_slices = tot_slices; + /* Enough posmap blocks to fit all the entries */ + sdev->posmap_size_sectors = VVZ_BLOCK_SCALE * DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK); - /* VMBs + PosMaps + DMB */ - sd->dev_header_size_sectors = VVZ_BLOCK_SCALE + VVZ_DEV_MAX_VOLUMES * - (VVZ_BLOCK_SCALE + sd->posmap_size_sectors); + /* DMB + VMBs + PosMaps */ + sdev->dev_header_size_sectors = VVZ_BLOCK_SCALE + + (VVZ_DEV_MAX_VOLUMES * VVZ_BLOCK_SCALE) + + (VVZ_DEV_MAX_VOLUMES * sdev->posmap_size_sectors); - /* Path to device */ - if (strlen(bdev_path) > VVZ_BDEV_PATH_LEN) { - DMERR("Device path too long"); - goto bad; - } - strcpy(sd->bdev_path, bdev_path); - /* DM handle for device */ - err = dm_get_device(ti, bdev_path, - dm_table_get_mode(ti->table), &sd->dev); - if (err) { - DMERR("Could not get DM device"); - goto bad; - } + /* The calling ->ctr() will set sdev->dm_dev */ /* Shuffled PSIs */ - mutex_init(&sd->shuffled_psis_lock); - sd->psi_taken = vzalloc(sd->tot_slices * sizeof(bool)); - if (!sd->psi_taken) { + mutex_init(&sdev->slices_lock); + sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); + if (!sdev->slices_ofld) { DMERR("Could not allocate PSI occupation bitfield"); + err = -ENOMEM; goto bad; } - sd->shuffled_psis = vmalloc(tot_slices * sizeof(u32)); - if (!sd->shuffled_psis) { + sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); + if (!sdev->prmslices) { DMERR("Could not allocate shuffled PSI array"); + err = -ENOMEM; goto bad; } - sd->first_free_psi_idx = 0; - + sdev->prmslices_octr = 0; /* Generate a permutation */ for (i = 0; i < tot_slices; i++) - sd->shuffled_psis[i] = i; - fisheryates_u32(sd->shuffled_psis, tot_slices); + sdev->prmslices[i] = i; + fisheryates_u32(sdev->prmslices, tot_slices); /* Bioset */ - bioset_init(&sd->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + bioset_init(&sdev->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + /* Client for dm-io */ - sd->io_client = dm_io_client_create(); - if (IS_ERR(sd->io_client)) { + sdev->io_client = dm_io_client_create(); + if (IS_ERR(sdev->io_client)) { DMERR("Could not create dm-io client"); - sd->io_client = NULL; + err = PTR_ERR(sdev->io_client); + sdev->io_client = NULL; goto bad; } // TODO: init sysfs - return sd; + return sdev; bad: - vvz_dev_free(sd, ti); - return NULL; + vvz_dev_destroy(sdev); + return ERR_PTR(err); } -void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti) +void vvz_dev_destroy(struct vvz_device *sdev) { - if (sd->num_vols > 0) + if (sdev->num_volumes > 0) DMCRIT("Destroying device that still has open volumes"); /* TODO remove from sysfs */ /* Needs to be zeroed out in the ctor, on failure */ - if (sd->io_client) - dm_io_client_destroy(sd->io_client); + if (sdev->io_client) + dm_io_client_destroy(sdev->io_client); /* Safe to call on zeroed bio_set */ - bioset_exit(&sd->bs); + bioset_exit(&sdev->bs); /* No locking needed here TODO: right? */ - if (sd->shuffled_psis) - vfree(sd->shuffled_psis); - if (sd->psi_taken) - vfree(sd->psi_taken); + if (sdev->prmslices) + vfree(sdev->prmslices); + if (sdev->slices_ofld) + vfree(sdev->slices_ofld); - if (sd->dev) - dm_put_device(ti, sd->dev); - - kfree(sd); + kfree(sdev); return; } -int vvz_dev_add_volume(struct vvz_device *sd, struct vvz_volume *sv, size_t vol_idx) +int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *svol, size_t vol_idx) { int ret; - if (mutex_lock_interruptible(&sd->vols_lock)) + if (mutex_lock_interruptible(&sdev->volumes_lock)) return -EINTR; - if (sd->vols[vol_idx]) { + if (sdev->volumes[vol_idx]) { DMERR("Volume slot %lu already taken", vol_idx); ret = -EINVAL; } else { - sd->vols[vol_idx] = sv; - sd->num_vols += 1; + sdev->volumes[vol_idx] = svol; + sdev->num_volumes += 1; ret = 0; } - mutex_unlock(&sd->vols_lock); + mutex_unlock(&sdev->volumes_lock); return ret; } -void vvz_dev_remove_volume(struct vvz_device *sd, size_t vol_idx) +void vvz_dev_remove_volume(struct vvz_device *sdev, size_t vol_idx) { - if (mutex_lock_interruptible(&sd->vols_lock)) + if (mutex_lock_interruptible(&sdev->volumes_lock)) return; - if (sd->vols[vol_idx]) { - sd->vols[vol_idx] = NULL; - sd->num_vols -= 1; + if (sdev->volumes[vol_idx]) { + sdev->volumes[vol_idx] = NULL; + sdev->num_volumes -= 1; } else DMERR("Volume slot %lu already free", vol_idx); - mutex_unlock(&sd->vols_lock); - return; -} - - -/* Fisher-Yates shuffle */ -static void fisheryates_u32(u32 *v, u32 len) -{ - u32 i; - - for (i = len-1; i >= 1; i--) { - u32 j = get_random_u32_below(i+1); - - /* Swap v[i] and v[j] */ - v[i] ^= v[j]; // v[i] <- a XOR b - v[j] ^= v[i]; // v[j] <- b XOR (a XOR b) = a - v[i] ^= v[j]; // v[i] <- (a XOR b) XOR a = b - } - + mutex_unlock(&sdev->volumes_lock); return; } diff --git a/dm-vvz/device.h b/dm-vvz/device.h deleted file mode 100644 index 62231fd..0000000 --- a/dm-vvz/device.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * A device represents the underlying "physical" block device, common to all - * "virtual" volumes that map onto it. - */ - -#ifndef _VVZ_DEVICE_H_ -#define _VVZ_DEVICE_H_ - - -#include -#include -#include "vvz_constants.h" - - -#define VVZ_BDEV_PATH_LEN 128 -/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ -#define VVZ_POSMAP_ENTRIES_PER_BLOCK 1024 - - -struct vvz_volume; - -struct vvz_device -{ - /* Shufflecake-unique device ID */ - u32 dev_id; - - /* Underlying block device */ - struct dm_dev *dev; - char bdev_path[VVZ_BDEV_PATH_LEN+1]; - u32 tot_slices; - - /* Header sizes in 512-byte sectors */ - sector_t posmap_size_sectors; - sector_t dev_header_size_sectors; - - /* All volumes mapping to this device */ - struct mutex vols_lock; - struct vvz_volume *vols[VVZ_DEV_MAX_VOLUMES]; - size_t num_vols; - - /* Shuffled array of PSIs */ - struct mutex shuffled_psis_lock; - u32 *shuffled_psis; - u32 first_free_psi_idx; /* in the shuffled array */ - bool *psi_taken; - u32 free_slices; - - /* TODO should these be per-device? */ - struct bio_set bs; - struct dm_io_client *io_client; -}; - - -struct vvz_device *vvz_dev_alloc(struct dm_target *ti, char *bdev_path, - u32 dev_id, u32 tot_slices); -void vvz_dev_free(struct vvz_device *sd, struct dm_target *ti); - -int vvz_dev_add_volume(struct vvz_device *sd, struct vvz_volume *sv, size_t vol_idx); -void vvz_dev_remove_volume(struct vvz_device *sd, size_t vol_idx); - - -#endif /* _VVZ_DEVICE_H_ */ diff --git a/dm-vvz/log.h b/dm-vvz/log.h deleted file mode 100644 index 2ad7ea9..0000000 --- a/dm-vvz/log.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _VVZ_LOG_H_ -#define _VVZ_LOG_H_ - - -#define DM_MSG_PREFIX "vvz" - - -#endif /* _VVZ_LOG_H_ */ diff --git a/dm-vvz/map_bio.h b/dm-vvz/map_bio.h deleted file mode 100644 index 3b8e87a..0000000 --- a/dm-vvz/map_bio.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _VVZ_IO_H_ -#define _VVZ_IO_H_ - - -#include "device.h" -#include "volume.h" - - -/* Handlers for the logical bio's submitted to a volume */ -int vvz_map_read(struct vvz_volume *sv, struct bio *bio); -int vvz_map_write(struct vvz_volume *sv, struct bio *bio); -int vvz_map_flush(struct vvz_volume *sv, struct bio *bio); - - -#endif /* _VVZ_IO_H_ */ diff --git a/dm-vvz/map_flush.c b/dm-vvz/map_flush.c deleted file mode 100644 index f4dd419..0000000 --- a/dm-vvz/map_flush.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/** - * We process FLUSH requests synchronously, for simplicity. - * First we store the position map, if it has been updated since the last FLUSH, - * and we wait for the operation to finish. Then, we forward the FLUSH to the - * underlying device. - */ - -#include "map_bio.h" -#include "posmap.h" -#include "log.h" - - -int vvz_map_flush(struct vvz_volume *sv, struct bio *bio) -{ - -} diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 0eae38e..e5526df 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -92,7 +92,7 @@ static int take_free_psi(struct vvz_device *sd, u32 *psi) { int err; - if (mutex_lock_interruptible(&sd->shuffled_psis_lock)) + if (mutex_lock_interruptible(&sd->prmslices_lock)) return -EINTR; /* PSI to return */ @@ -115,7 +115,7 @@ static int take_free_psi(struct vvz_device *sd, u32 *psi) sd->psi_taken[*psi] = true; out: - mutex_unlock(&sd->shuffled_psis_lock); + mutex_unlock(&sd->prmslices_lock); return err; } diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 27a56b2..9344d49 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -22,72 +22,72 @@ */ #include -#include "volume.h" -#include "log.h" +#include "vvz.h" -struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey) +struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *enckey) { - struct vvz_volume *sv; + struct vvz_volume *svol; int err; - sv = kzalloc(sizeof(*sv), GFP_KERNEL); - if (!sv) { + svol = kzalloc(sizeof(*svol), GFP_KERNEL); + if (!svol) { DMERR("Could not allocate volume"); - return NULL; + return ERR_PTR(-ENOMEM); } - sv->sd = sd; - sv->vol_idx = vol_idx; - sprintf(sv->vol_name, "sflc_%lu_%lu", sd->dev_id, vol_idx); + svol->sdev = sdev; + svol->vol_idx = vol_idx; /* Crypto */ - sv->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); - if (!sv->tfm) { + svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); + if (IS_ERR(svol->tfm)) { DMERR("Could not allocate AES-XTS cipher handle"); + err = ERR_PTR(svol->tfm); + svol->tfm = NULL; goto bad; } - err = crypto_skcipher_setkey(sv->tfm, enckey, VVZ_XTS_KEYLEN); + err = crypto_skcipher_setkey(svol->tfm, enckey, VVZ_XTS_KEYLEN); if (err) { DMERR("Could not set key in crypto transform"); goto bad; } /* Position map */ - mutex_init(&sv->posmap_lock); + mutex_init(&svol->posmap_lock); /* Slight over-allocation, to fit a whole number of blocks */ - sv->posmap = vmalloc(sd->posmap_size_sectors * SECTOR_SIZE); - if (!sv->posmap) { + svol->posmap = vmalloc(sdev->posmap_size_sectors * SECTOR_SIZE); + if (!svol->posmap) { DMERR("Could not allocate position map"); + err = -ENOMEM; goto bad; } - sv->posmap_dirty = false; - sv->mapped_slices = 0; + svol->mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) // TODO: init sysfs - return sv; + return svol; bad: - vvz_vol_free(sv); - return NULL; + vvz_vol_free(svol); + return ERR_PTR(err); } -void vvz_vol_free(struct vvz_volume *sv) +void vvz_vol_free(struct vvz_volume *svol) { /* TODO remove from sysfs */ /* No locking needed here TODO: right? */ - if (sv->posmap) - vfree(sv->posmap); + if (svol->posmap) + vfree(svol->posmap); - if (sv->tfm) - crypto_free_skcipher(sv->tfm); + if (svol->tfm) + crypto_free_skcipher(svol->tfm); - kfree(sv); + kfree(svol); return; } diff --git a/dm-vvz/volume.h b/dm-vvz/volume.h deleted file mode 100644 index 582f363..0000000 --- a/dm-vvz/volume.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * A volume represents a single "logical" storage unit, mapping onto an - * underlying "physical" device. - */ - -#ifndef _VVZ_VOLUME_H_ -#define _VVZ_VOLUME_H_ - - -#include -#include "device.h" - - -/* The volume name is "sflc__" */ -#define VVZ_VOL_NAME_MAX_LEN 12 - - -struct vvz_device; - -struct vvz_volume -{ - /* Backing device */ - struct vvz_device *sd; - - /* Volume index within the device */ - size_t vol_idx; - /* Name of the volume, sflc__*/ - char vol_name[VVZ_VOL_NAME_MAX_LEN + 1]; - - /* Position map */ - struct mutex posmap_lock; - u32 *posmap; - bool posmap_dirty; - u32 mapped_slices; - - /* Crypto */ - struct crypto_skcipher *tfm; -}; - - -struct vvz_volume *vvz_vol_alloc(struct vvz_device *sd, size_t vol_idx, u8 *enckey); -void vvz_vol_free(struct vvz_volume *sv); - - -#endif /* _VVZ_VOLUME_H_ */ diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 19301ac..25e0dc2 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -24,20 +24,27 @@ #include #include #include -#include "device.h" -#include "volume.h" -#include "map_bio.h" #include "vvz_constants.h" #include "vvz.h" -#include "log.h" -/* Global array of devices, and next free index */ +/* + *---------------------------- + * Global variables + *---------------------------- + */ + DEFINE_MUTEX(vvz_alldevs_lock); struct vvz_device **vvz_alldevs = NULL; u32 vvz_free_devid = 0; /* The lowest free devID */ +/* + *---------------------------- + * Device mapper target + *---------------------------- + */ + /* * Create volume and, if not existent, the underlying device. */ @@ -49,8 +56,8 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) char *enckey_hex; u8 enckey[VVZ_XTS_KEYLEN]; u32 tot_slices; - struct vvz_device *sd; - struct vvz_volume *sv; + struct vvz_device *sdev; + struct vvz_volume *svol; int err; /* @@ -73,11 +80,11 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) enckey_hex = argv[4]; /* Sanity checks */ if (dev_id >= VVZ_MAX_DEVS) { - ti->error = "Invalid device ID"; + ti->error = "Device ID out of bounds"; return -EINVAL; } if (vol_idx >= VVZ_DEV_MAX_VOLUMES) { - ti->error = "Invalid volume index"; + ti->error = "Volume index out of bounds"; return -EINVAL; } if (strlen(enckey_hex) != 2 * VVZ_XTS_KEYLEN) { @@ -91,47 +98,53 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) return err; } - /* Create device if it doesn't exist yet (or check uniqueness if it does) */ + /* Create device, only if this is the first volume */ if (mutex_lock_interruptible(&vvz_alldevs_lock)) return -EINTR; - sd = vvz_alldevs[dev_id]; - if (!sd) { - /* Create */ - sd = vvz_dev_alloc(ti, bdev_path, dev_id, tot_slices); - if (!sd) { - ti->error = "Could not instantiate device"; - mutex_unlock(&vvz_alldevs_lock); - return -ENOMEM; - } - /* Insert in alldevs, and advance free_devid */ - vvz_alldevs[dev_id] = sd; - int i; - for (i = dev_id + 1; vvz_alldevs[i] && i < VVZ_MAX_DEVS; i++); - vvz_free_devid = i; - } else { - /* Check for uniqueness */ - if (strncmp(sd->bdev_path, bdev_path, VVZ_BDEV_PATH_LEN) != 0) { - ti->error = "Device ID already used for another device"; + sdev = vvz_alldevs[dev_id]; + if (vol_idx == 0) { + /* Check for dev_id conflict, possible if next_dev_id is read + * from sysfs again before it is advanced here */ + if (vvz_alldevs[dev_id]) { mutex_unlock(&vvz_alldevs_lock); + ti->error = "A device with this ID already exists. Retry"; return -EINVAL; } + /* Create device */ + sdev = vvz_dev_create(ti, bdev_path, dev_id, tot_slices); + if (IS_ERR(sdev)) { + mutex_unlock(&vvz_alldevs_lock); + ti->error = "Could not instantiate device"; + return PTR_ERR(sdev); + } + /* Set dm_dev */ + err = dm_get_device(ti, bdev_path, dm_table_get_mode(ti->table), &sdev->dm_dev); + if (err) { + mutex_unlock(&vvz_alldevs_lock); + vvz_dev_destroy(sdev); + ti->error = "Device lookup failed"; + return err; + } + /* Insert in alldevs, and advance free_devid */ + vvz_alldevs[dev_id] = sdev; + int i; + for (i = vvz_free_devid; i < VVZ_MAX_DEVS && vvz_alldevs[i]; i++); + vvz_free_devid = i; } mutex_unlock(&vvz_alldevs_lock); - /* Create volume. Never free sd here, for simplicity. Potential memory - * leak if vvz_vol_alloc or vvz_dev_add_volume fail on the first volume, - * but it's really a corner case. TODO discuss */ - sv = vvz_vol_alloc(sd, vol_idx, enckey); - if (!sv) { + /* Create volume */ + svol = vvz_vol_create(sdev, vol_idx, enckey); + if (IS_ERR(svol)) { ti->error = "Could not instantiate volume"; - return -ENOMEM; + err = PTR_ERR(svol); + goto bad_vol_create; } /* Insert it into the device */ - err = vvz_dev_add_volume(sd, sv, vol_idx); + err = vvz_dev_add_volume(sdev, svol, vol_idx); if (err) { ti->error = "Could not add volume to device"; - vvz_vol_free(sv); - return err; + goto bad_add_volume; } /* Only accept one block per request for simplicity TODO: improve to one slice*/ @@ -143,26 +156,41 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) ti->num_secure_erase_bios = 0; ti->num_write_zeroes_bios = 0; ti->accounts_remapped_io = true; - ti->private = sv; + ti->private = svol; return 0; + + +bad_add_volume: + vvz_vol_destroy(svol); +bad_vol_create: + if (vol_idx == 0) { + /* Release devID */ + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return -EINTR; + vvz_alldevs[dev_id] = NULL; + if (dev_id < vvz_free_devid) + vvz_free_devid = dev_id; + mutex_unlock(&vvz_alldevs_lock); + /* Destroy device */ + vvz_dev_destroy(sdev); + } + return err; } /* Destroy volume and, if needed, the underlying device */ static void vvz_dtr(struct dm_target *ti) { - struct vvz_volume *sv = ti->private; - struct vvz_device *sd = sv->sd; - u32 dev_id = sd->dev_id; + struct vvz_volume *svol = ti->private; + struct vvz_device *sdev = svol->sdev; + u32 dev_id = sdev->dev_id; - vvz_dev_remove_volume(sd, sv->vol_idx); - vvz_vol_free(sv); - - /* Pointless to take vols_lock here */ - if (!sd->num_vols) { - vvz_dev_free(sd); + vvz_dev_remove_volume(sdev, svol->vol_idx); + vvz_vol_destroy(svol); + /* Pointless to take volumes_lock here */ + if (!sdev->num_volumes) { /* Release devID */ if (mutex_lock_interruptible(&vvz_alldevs_lock)) return; @@ -170,6 +198,8 @@ static void vvz_dtr(struct dm_target *ti) if (dev_id < vvz_free_devid) vvz_free_devid = dev_id; mutex_unlock(&vvz_alldevs_lock); + /* Destroy device */ + vvz_dev_destroy(sdev); } return; @@ -217,14 +247,20 @@ static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) { - struct vvz_volume *sv = ti->private; - struct vvz_device *sd = sv->sd; + struct vvz_volume *svol = ti->private; + struct vvz_device *sdev = svol->sdev; - return fn(ti, sv->sd->dev, 0, sd->dev_header_size_sectors + + return fn(ti, svol->sdev->dm_dev, 0, sdev->dev_header_size_sectors + (ti->len * VVZ_BLOCK_SCALE), data); } +/* + *---------------------------- + * Kernel module + *---------------------------- + */ + static struct target_type vvz_target = { .name = VVZ_TARGET_NAME, .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, @@ -249,20 +285,28 @@ static int __init vvz_init(void) goto bad_alldevs_alloc; } + err = vvz_sysfs_init(); + if (err) { + DMERR("Could not init sysfs; error %d", err); + goto bad_sysfs_init; + } + err = dm_register_target(&vvz_target); if (err < 0) { DMERR("Could not register DM target"); goto bad_register_target; } - DMINFO("vvz loaded"); + DMINFO("loaded"); return 0; bad_register_target: + vvz_sysfs_exit(); +bad_sysfs_init: vfree(vvz_alldevs); bad_alldevs_alloc: - DMERR("vvz not loaded"); + DMERR("not loaded"); return err; } @@ -271,9 +315,10 @@ bad_alldevs_alloc: static void __exit vvz_exit(void) { dm_unregister_target(&vvz_target); + vvz_sysfs_exit(); vfree(vvz_alldevs); - DMINFO("vvz unloaded"); + DMINFO("unloaded"); return; } @@ -282,5 +327,5 @@ module_init(vvz_init); module_exit(vvz_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Elia Anzuoni"); +MODULE_AUTHOR("Shufflecake authors"); MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 0de1e69..94f4d27 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -25,13 +25,115 @@ #define _VVZ_H -#include "device.h" +#include +#include +#include +#include "vvz_constants.h" -/* Global array of devices, and next free index */ +/* + *---------------------------- + * Constants + *---------------------------- + */ + +#define DM_MSG_PREFIX "vvz" + +/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ +#define VVZ_POSMAP_ENTRIES_PER_BLOCK 1024 + + +/* + *---------------------------- + * Structs + *---------------------------- + */ + +struct vvz_volume; + +struct vvz_device +{ + /* Shufflecake-unique device ID */ + u32 dev_id; + + /* Underlying block device */ + struct dm_dev *dm_dev; + u32 tot_slices; + + /* Header sizes in 512-byte sectors */ + sector_t posmap_size_sectors; + sector_t dev_header_size_sectors; + + /* All volumes mapping to this device */ + struct mutex volumes_lock; // TODO can ctr/dtr be actually called concurrently? + struct vvz_volume *volumes[VVZ_DEV_MAX_VOLUMES]; + size_t num_volumes; + + /* Shuffled array of PSIs */ + struct mutex slices_lock; + u32 *prmslices; + u32 prmslices_octr; + bool *slices_ofld; + u32 free_slices; + + /* TODO should these be per-device? */ + struct bio_set bs; + struct dm_io_client *io_client; +}; + +struct vvz_volume +{ + /* Backing device */ + struct vvz_device *sdev; + + /* Volume index within the device */ + size_t vol_idx; + + /* Position map */ + struct mutex posmap_lock; + u32 *posmap; + u32 mapped_slices; + + /* Crypto */ + struct crypto_skcipher *tfm; +}; + + +/* + *---------------------------- + * Global variables + *---------------------------- + */ + +/* Array of devices, and next free id */ extern struct mutex vvz_alldevs_lock; extern struct vvz_device **vvz_alldevs; extern u32 vvz_free_devid; /* The lowest free devID */ +/* + *---------------------------- + * Functions + *---------------------------- + */ + +/* Device */ +struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices); +void vvz_dev_destroy(struct vvz_device *sdev, struct dm_target *ti); +int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *sv, size_t vol_idx); +void vvz_dev_remove_volume(struct vvz_device *sdev, size_t vol_idx); + +/* Volume */ +struct vvz_volume *vvz_vol_create(struct vvz_device *sd, size_t vol_idx, u8 *enckey); +void vvz_vol_destroy(struct vvz_volume *sv); + +/* Sysfs */ +int vvz_sysfs_init(); +void vvz_sysfs_exit(); +int vvz_sysfs_add_device(struct vvz_device *sdev); +void vvz_sysfs_remove_device(struct vvz_device *sdev); +int vvz_sysfs_add_volume(struct vvz_volume *svol); +void vvz_sysfs_remove_volume(struct vvz_volume *svol); + + #endif /* _VVZ_H */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index a8bcfda..544f9b5 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -43,12 +43,12 @@ #define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL -/* XTS "requires" (per the standard, at least) doubling the key size */ +/* XTS requires doubling the key size */ #define VVZ_XTS_KEYLEN 64 /* bytes */ -#define VVZ_BLOCK_SIZE 4096 /* bytes */ -#define VVZ_BLOCK_SHIFT 3 +#define VVZ_BLOCK_SIZE 4096 /* bytes */ +#define VVZ_BLOCK_SHIFT 3 #define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in block */ From 1760c8cf6ba6fdc7afc5d71aa8054ab049eb7c7e Mon Sep 17 00:00:00 2001 From: = Date: Tue, 9 Jan 2024 19:22:38 +0100 Subject: [PATCH 25/98] Begin sysfs --- .gitignore | 6 ++++ dm-vvz/Kbuild | 4 +-- dm-vvz/Makefile | 9 ++--- dm-vvz/device.c | 2 +- dm-vvz/posmap.c | 6 ++-- dm-vvz/sysfs.c | 82 ++++++++++++++++++++++++++++++++++++++++++ dm-vvz/volume.c | 8 ++--- dm-vvz/vvz.c | 25 ++----------- dm-vvz/vvz.h | 23 +++++++----- dm-vvz/vvz_constants.h | 16 ++++++--- 10 files changed, 129 insertions(+), 52 deletions(-) create mode 100644 dm-vvz/sysfs.c diff --git a/.gitignore b/.gitignore index 52695bf..d56891f 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,9 @@ dkms.conf # Shufflecake binaries shufflecake +# Build directory +bin/ + +# Test images +disks/ + diff --git a/dm-vvz/Kbuild b/dm-vvz/Kbuild index 352db93..d9ab37a 100644 --- a/dm-vvz/Kbuild +++ b/dm-vvz/Kbuild @@ -21,11 +21,11 @@ # If not, see . # -MODULE_NAME := vvz +MODULE_NAME := dm_vvz obj-m := $(MODULE_NAME).o -OBJ_LIST := module.o +OBJ_LIST := vvz.o device.o volume.o sysfs.o $(MODULE_NAME)-y += $(OBJ_LIST) diff --git a/dm-vvz/Makefile b/dm-vvz/Makefile index 45cdc18..982f8ab 100644 --- a/dm-vvz/Makefile +++ b/dm-vvz/Makefile @@ -29,16 +29,13 @@ BUILD_DIR_MAKEFILE = $(BUILD_DIR)/Makefile default: $(BUILD_DIR_MAKEFILE) make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules -$(BUILD_DIR_MAKEFILE): $(BUILD_DIR) - echo "# This Makefile is here because of Kbuild" > $@ +$(BUILD_DIR_MAKEFILE): | $(BUILD_DIR) + echo "# This Makefile does nothing, but Kbuild needs one in this directory" > $@ $(BUILD_DIR): - mkdir -p $@ + mkdir $@ -install: - make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules_install - clean: rm -rf $(BUILD_DIR) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index b3c3e4c..81e9d40 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -74,7 +74,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) /* Compute sizes */ sdev->tot_slices = tot_slices; - sdev->free_slices = tot_slices; + sdev->nr_free_slices = tot_slices; /* Enough posmap blocks to fit all the entries */ sdev->posmap_size_sectors = VVZ_BLOCK_SCALE * DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index e5526df..17bc965 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -74,7 +74,7 @@ int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) goto out; /* And in the volume */ sv->posmap[lsi] = *psi; - sv->mapped_slices += 1; + sv->nr_mapped_slices += 1; sv->posmap_dirty = true; err = 0; @@ -96,7 +96,7 @@ static int take_free_psi(struct vvz_device *sd, u32 *psi) return -EINTR; /* PSI to return */ - if (unlikely(!sd->free_slices)) { + if (unlikely(!sd->nr_free_slices)) { err = -ENOSPC; goto out; } @@ -111,7 +111,7 @@ static int take_free_psi(struct vvz_device *sd, u32 *psi) for (i = sd->first_free_psi_idx + 1; i < sd->tot_slices && sd->psi_taken[i]; i++); sd->first_free_psi_idx = i; - sd->free_slices -= 1; + sd->nr_free_slices -= 1; sd->psi_taken[*psi] = true; out: diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c new file mode 100644 index 0000000..9a1d583 --- /dev/null +++ b/dm-vvz/sysfs.c @@ -0,0 +1,82 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "vvz.h" + + +/* + *---------------------------- + * Top-level entries + *---------------------------- + */ + +static ssize_t devid_show(struct module_attribute *mattr, struct module_kobject *mkobj, char *buf) +{ + ssize_t ret; + + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", vvz_free_devid); + mutex_unlock(&vvz_alldevs_lock); + + return ret; +} + + +static struct kobject *bdevs_kobj; +static struct module_attribute devid_mattr = __ATTR(VVZ_SYSFS_DEVID_RAW, 0444, devid_show, NULL); + + +int vvz_sysfs_init() +{ + int err; + + bdevs_kobj = kobject_create_and_add(VVZ_SYSFS_BDEVS, &THIS_MODULE->mkobj.kobj); + if (!bdevs_kobj) { + err = -ENOMEM; + DMERR("Could not create %s kobject", VVZ_SYSFS_BDEVS); + goto bad_bdevs; + } + + err = sysfs_create_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); + if (err) { + DMERR("Could not create %s file", VVZ_SYSFS_DEVID); + goto bad_devid; + } + + return 0; + + +bad_devid: + kobject_put(bdevs_kobj); +bad_bdevs: + return err; +} + + +void vvz_sysfs_exit() +{ + sysfs_remove_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); + kobject_put(bdevs_kobj); +} + diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 9344d49..9a7b7ee 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -43,7 +43,7 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(svol->tfm)) { DMERR("Could not allocate AES-XTS cipher handle"); - err = ERR_PTR(svol->tfm); + err = PTR_ERR(svol->tfm); svol->tfm = NULL; goto bad; } @@ -62,7 +62,7 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e err = -ENOMEM; goto bad; } - svol->mapped_slices = 0; + svol->nr_mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) // TODO: init sysfs @@ -71,12 +71,12 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e bad: - vvz_vol_free(svol); + vvz_vol_destroy(svol); return ERR_PTR(err); } -void vvz_vol_free(struct vvz_volume *svol) +void vvz_vol_destroy(struct vvz_volume *svol) { /* TODO remove from sysfs */ diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 25e0dc2..b97ff97 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -75,7 +75,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } sscanf(argv[0], "%u", &dev_id); bdev_path = argv[1]; - sscanf(argv[2], "%u", &vol_idx); + sscanf(argv[2], "%lu", &vol_idx); sscanf(argv[3], "%u", &tot_slices); enckey_hex = argv[4]; /* Sanity checks */ @@ -111,7 +111,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) return -EINVAL; } /* Create device */ - sdev = vvz_dev_create(ti, bdev_path, dev_id, tot_slices); + sdev = vvz_dev_create(dev_id, tot_slices); if (IS_ERR(sdev)) { mutex_unlock(&vvz_alldevs_lock); ti->error = "Could not instantiate device"; @@ -208,27 +208,6 @@ static void vvz_dtr(struct dm_target *ti) static int vvz_map(struct dm_target *ti, struct bio *bio) { - struct vvz_volume *sv = ti->private; - enum req_op op = bio_op(bio); - - /* Bio size and bvec alignment checks (redundant?) */ - if (unlikely(bio->bi_iter.bi_size && - (bio->bi_iter.bi_size != VVZ_BLOCK_SIZE || - bio->bi_vcnt != 1))) - return DM_MAPIO_KILL; /* TODO log it? */ - - /* Dispatch */ - if (bio->bi_opf & REQ_PREFLUSH) { - /* DM core should only send empty flush requests */ - if (unlikely(bio->bi_iter.bi_size)) - return DM_MAPIO_KILL; // TODO log it? - return vvz_map_flush(sv, bio); - } else if (op == REQ_OP_READ) - return vvz_map_read(sv, bio); - else if (op == REQ_OP_WRITE) - return vvz_map_write(sv, bio); - - DMWARN("Unrecognised bio: bio_op(bio) = %d", op); return DM_MAPIO_KILL; } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 94f4d27..499618b 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -27,6 +27,7 @@ #include #include +#include #include #include "vvz_constants.h" @@ -74,7 +75,10 @@ struct vvz_device u32 *prmslices; u32 prmslices_octr; bool *slices_ofld; - u32 free_slices; + u32 nr_free_slices; + + /* Sysfs */ + struct kobject kobj; /* TODO should these be per-device? */ struct bio_set bs; @@ -92,7 +96,10 @@ struct vvz_volume /* Position map */ struct mutex posmap_lock; u32 *posmap; - u32 mapped_slices; + u32 nr_mapped_slices; + + /* Sysfs */ + struct kobject kobj; /* Crypto */ struct crypto_skcipher *tfm; @@ -119,17 +126,17 @@ extern u32 vvz_free_devid; /* The lowest free devID */ /* Device */ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices); -void vvz_dev_destroy(struct vvz_device *sdev, struct dm_target *ti); -int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *sv, size_t vol_idx); +void vvz_dev_destroy(struct vvz_device *sdev); +int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *svol, size_t vol_idx); void vvz_dev_remove_volume(struct vvz_device *sdev, size_t vol_idx); /* Volume */ -struct vvz_volume *vvz_vol_create(struct vvz_device *sd, size_t vol_idx, u8 *enckey); -void vvz_vol_destroy(struct vvz_volume *sv); +struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *enckey); +void vvz_vol_destroy(struct vvz_volume *svol); /* Sysfs */ -int vvz_sysfs_init(); -void vvz_sysfs_exit(); +int vvz_sysfs_init(void); +void vvz_sysfs_exit(void); int vvz_sysfs_add_device(struct vvz_device *sdev); void vvz_sysfs_remove_device(struct vvz_device *sdev); int vvz_sysfs_add_volume(struct vvz_volume *svol); diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 544f9b5..f93cf35 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -43,17 +43,23 @@ #define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL -/* XTS requires doubling the key size */ -#define VVZ_XTS_KEYLEN 64 /* bytes */ - - #define VVZ_BLOCK_SIZE 4096 /* bytes */ #define VVZ_BLOCK_SHIFT 3 -#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in block */ +#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in a block */ + + +/* XTS requires doubling the key size */ +#define VVZ_XTS_KEYLEN 64 /* bytes */ #define VVZ_DEV_MAX_VOLUMES 15 #define VVZ_MAX_DEVS 1024 +/* Sysfs entries under /sys/module/dm_vvz/ */ +#define VVZ_SYSFS_BDEVS "bdevs" +#define VVZ_SYSFS_DEVID_RAW next_dev_id +#define VVZ_SYSFS_DEVID STRINGIFY(VVZ_SYSFS_DEVID_RAW) + + #endif /* _VVZ_CONSTANTS_H_ */ From 437316db74e0262201797231388e0ef503620009 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 10 Jan 2024 00:01:06 +0100 Subject: [PATCH 26/98] Use kset --- dm-vvz/sysfs.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index 9a1d583..e226c38 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -43,7 +43,7 @@ static ssize_t devid_show(struct module_attribute *mattr, struct module_kobject } -static struct kobject *bdevs_kobj; +static struct kset *bdevs_kset; static struct module_attribute devid_mattr = __ATTR(VVZ_SYSFS_DEVID_RAW, 0444, devid_show, NULL); @@ -51,10 +51,10 @@ int vvz_sysfs_init() { int err; - bdevs_kobj = kobject_create_and_add(VVZ_SYSFS_BDEVS, &THIS_MODULE->mkobj.kobj); - if (!bdevs_kobj) { + bdevs_kset = kset_create_and_add(VVZ_SYSFS_BDEVS, NULL, &THIS_MODULE->mkobj.kobj); + if (!bdevs_kset) { err = -ENOMEM; - DMERR("Could not create %s kobject", VVZ_SYSFS_BDEVS); + DMERR("Could not create %s kset", VVZ_SYSFS_BDEVS); goto bad_bdevs; } @@ -68,7 +68,7 @@ int vvz_sysfs_init() bad_devid: - kobject_put(bdevs_kobj); + kset_unregister(bdevs_kset); bad_bdevs: return err; } From 409e6da1b4cf199c01da0a8443c4941e000ff38c Mon Sep 17 00:00:00 2001 From: = Date: Wed, 10 Jan 2024 00:11:12 +0100 Subject: [PATCH 27/98] Use kset --- dm-vvz/sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index e226c38..5d6032c 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -77,6 +77,6 @@ bad_bdevs: void vvz_sysfs_exit() { sysfs_remove_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); - kobject_put(bdevs_kobj); + kset_unregister(bdevs_kset); } From 95b813769ebcc9f56aabce3a05913029df1dd46a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 11:47:17 +0100 Subject: [PATCH 28/98] Begin sysfs --- dm-vvz/device.c | 96 ++++++++------------------------- dm-vvz/sysfs.c | 5 +- dm-vvz/vvz.c | 139 +++++++++++++++++++++++++----------------------- dm-vvz/vvz.h | 17 +++--- 4 files changed, 105 insertions(+), 152 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 81e9d40..3845066 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -29,12 +29,6 @@ /* Depth of the mempool backing the bio_set */ #define VVZ_BIOSET_BIOS 64 -/* Compute device header size, in 512-byte sectors */ -#define DEV_HEADER_SIZE_SECTORS(tot_slices) \ - (VVZ_BLOCK_SCALE * \ - (1 + VVZ_DEV_MAX_VOLUMES * \ - (1 + DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK) \ - ))) /* Fisher-Yates shuffle */ @@ -57,8 +51,8 @@ static void fisheryates_u32(u32 *v, u32 len) struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) { struct vvz_device *sdev; - int err; int i; + int err; sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { @@ -67,10 +61,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) } sdev->dev_id = dev_id; - mutex_init(&sdev->volumes_lock); - for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) - sdev->volumes[i] = NULL; - sdev->num_volumes = 0; + /* The calling ->ctr() will set sdev->dm_dev */ /* Compute sizes */ sdev->tot_slices = tot_slices; @@ -83,38 +74,39 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) (VVZ_DEV_MAX_VOLUMES * VVZ_BLOCK_SCALE) + (VVZ_DEV_MAX_VOLUMES * sdev->posmap_size_sectors); - /* The calling ->ctr() will set sdev->dm_dev */ - /* Shuffled PSIs */ mutex_init(&sdev->slices_lock); sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); if (!sdev->slices_ofld) { DMERR("Could not allocate PSI occupation bitfield"); err = -ENOMEM; - goto bad; + goto bad_ofld; } sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); if (!sdev->prmslices) { DMERR("Could not allocate shuffled PSI array"); err = -ENOMEM; - goto bad; + goto bad_prmslices; } - sdev->prmslices_octr = 0; /* Generate a permutation */ for (i = 0; i < tot_slices; i++) sdev->prmslices[i] = i; fisheryates_u32(sdev->prmslices, tot_slices); + sdev->prmslices_octr = 0; /* Bioset */ - bioset_init(&sdev->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + err = bioset_init(&sdev->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + if (err) { + DMERR("Could not init bioset; error %d", err); + goto bad_bioset; + } /* Client for dm-io */ sdev->io_client = dm_io_client_create(); if (IS_ERR(sdev->io_client)) { - DMERR("Could not create dm-io client"); err = PTR_ERR(sdev->io_client); - sdev->io_client = NULL; - goto bad; + DMERR("Could not create dm-io client; error %d", err); + goto bad_dmio_client; } // TODO: init sysfs @@ -122,70 +114,28 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) return sdev; -bad: - vvz_dev_destroy(sdev); +bad_dmio_client: + bioset_exit(sdev->bs); +bad_bioset: + vfree(sdev->prmslices); +bad_prmslices: + vfree(sdev->slices_ofld); +bad_ofld: + kfree(sdev); return ERR_PTR(err); } void vvz_dev_destroy(struct vvz_device *sdev) { - if (sdev->num_volumes > 0) - DMCRIT("Destroying device that still has open volumes"); - /* TODO remove from sysfs */ - /* Needs to be zeroed out in the ctor, on failure */ - if (sdev->io_client) - dm_io_client_destroy(sdev->io_client); - /* Safe to call on zeroed bio_set */ + dm_io_client_destroy(sdev->io_client); bioset_exit(&sdev->bs); - - /* No locking needed here TODO: right? */ - if (sdev->prmslices) - vfree(sdev->prmslices); - if (sdev->slices_ofld) - vfree(sdev->slices_ofld); - + vfree(sdev->prmslices); + vfree(sdev->slices_ofld); kfree(sdev); return; } - -int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *svol, size_t vol_idx) -{ - int ret; - - if (mutex_lock_interruptible(&sdev->volumes_lock)) - return -EINTR; - - if (sdev->volumes[vol_idx]) { - DMERR("Volume slot %lu already taken", vol_idx); - ret = -EINVAL; - } else { - sdev->volumes[vol_idx] = svol; - sdev->num_volumes += 1; - ret = 0; - } - - mutex_unlock(&sdev->volumes_lock); - return ret; -} - - -void vvz_dev_remove_volume(struct vvz_device *sdev, size_t vol_idx) -{ - if (mutex_lock_interruptible(&sdev->volumes_lock)) - return; - - if (sdev->volumes[vol_idx]) { - sdev->volumes[vol_idx] = NULL; - sdev->num_volumes -= 1; - } else - DMERR("Volume slot %lu already free", vol_idx); - - mutex_unlock(&sdev->volumes_lock); - return; -} - diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index 5d6032c..0112c0a 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -30,7 +30,7 @@ *---------------------------- */ -static ssize_t devid_show(struct module_attribute *mattr, struct module_kobject *mkobj, char *buf) +static ssize_t next_dev_id_show(struct module_attribute *mattr, struct module_kobject *mkobj, char *buf) { ssize_t ret; @@ -44,7 +44,8 @@ static ssize_t devid_show(struct module_attribute *mattr, struct module_kobject static struct kset *bdevs_kset; -static struct module_attribute devid_mattr = __ATTR(VVZ_SYSFS_DEVID_RAW, 0444, devid_show, NULL); +static struct module_attribute devid_mattr = + __ATTR(VVZ_SYSFS_DEVID_RAW, 0444, next_dev_id_show, NULL); int vvz_sysfs_init() diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index b97ff97..3a2cfc3 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -30,7 +30,7 @@ /* *---------------------------- - * Global variables + * Device mapper target *---------------------------- */ @@ -39,11 +39,42 @@ struct vvz_device **vvz_alldevs = NULL; u32 vvz_free_devid = 0; /* The lowest free devID */ -/* - *---------------------------- - * Device mapper target - *---------------------------- - */ +/* Add a device to the global array, and advance next_dev_id */ +static int vvz_add_device_global(u32 dev_id, struct vvz_device *sdev) +{ + int i; + + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return -ERESTARTSYS; + + /* Check for dev_id conflict, possible because a read() to next_dev_id + * in sysfs can return the same value to two processes */ + if (vvz_alldevs[dev_id]) { + mutex_unlock(&vvz_alldevs_lock); + DMERR("A device with this ID already exists. Retry"); + return -EINVAL; + } + // Add to the global array, and advance free_devid + vvz_alldevs[dev_id] = sdev; + for (i = vvz_free_devid; i < VVZ_MAX_DEVS && vvz_alldevs[i]; i++); + vvz_free_devid = i; + + mutex_unlock(&vvz_alldevs_lock); + + return 0; +} + +/* Remove a device from the global array, and update next_dev_id */ +static void vvz_remove_device_global(u32 dev_id) +{ + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return; + vvz_alldevs[dev_id] = NULL; + if (dev_id < vvz_free_devid) + vvz_free_devid = dev_id; + mutex_unlock(&vvz_alldevs_lock); +} + /* * Create volume and, if not existent, the underlying device. @@ -60,15 +91,15 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) struct vvz_volume *svol; int err; - /* - * Parse arguments. - * - * argv[0]: Shufflecake-unique device ID - * argv[1]: path to underlying physical device - * argv[2]: volume index within the device - * argv[3]: number of 1-MiB slices in the underlying device - * argv[4]: 64-byte encryption key (hex-encoded) - */ + /** + * Parse arguments. + * + * argv[0]: Shufflecake-unique device ID + * argv[1]: path to underlying physical device + * argv[2]: volume index within the device + * argv[3]: number of 1-MiB slices in the underlying device + * argv[4]: 64-byte encryption key (hex-encoded) + */ if (argc != 5) { ti->error = "Invalid argument count"; return -EINVAL; @@ -98,40 +129,37 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) return err; } - /* Create device, only if this is the first volume */ - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return -EINTR; - sdev = vvz_alldevs[dev_id]; + /* Create device, if this is the first volume, otherwise retrieve it */ if (vol_idx == 0) { - /* Check for dev_id conflict, possible if next_dev_id is read - * from sysfs again before it is advanced here */ - if (vvz_alldevs[dev_id]) { - mutex_unlock(&vvz_alldevs_lock); - ti->error = "A device with this ID already exists. Retry"; - return -EINVAL; - } - /* Create device */ sdev = vvz_dev_create(dev_id, tot_slices); if (IS_ERR(sdev)) { - mutex_unlock(&vvz_alldevs_lock); ti->error = "Could not instantiate device"; return PTR_ERR(sdev); } /* Set dm_dev */ err = dm_get_device(ti, bdev_path, dm_table_get_mode(ti->table), &sdev->dm_dev); if (err) { - mutex_unlock(&vvz_alldevs_lock); - vvz_dev_destroy(sdev); ti->error = "Device lookup failed"; - return err; + goto bad_dm_dev; + } + /* Insert in global array */ + err = vvz_add_device_global(dev_id, sdev); + if (err) { + ti->error = "Could not add device to global array"; + goto bad_dev_global; + } + + } else { + if (mutex_lock_interruptible(&vvz_alldevs_lock)) + return -ERESTARTSYS; + sdev = vvz_alldevs[dev_id]; + mutex_unlock(&vvz_alldevs_lock); + + if (!sdev) { + ti->error = "Could not find device"; + return -EINVAL; } - /* Insert in alldevs, and advance free_devid */ - vvz_alldevs[dev_id] = sdev; - int i; - for (i = vvz_free_devid; i < VVZ_MAX_DEVS && vvz_alldevs[i]; i++); - vvz_free_devid = i; } - mutex_unlock(&vvz_alldevs_lock); /* Create volume */ svol = vvz_vol_create(sdev, vol_idx, enckey); @@ -140,12 +168,6 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) err = PTR_ERR(svol); goto bad_vol_create; } - /* Insert it into the device */ - err = vvz_dev_add_volume(sdev, svol, vol_idx); - if (err) { - ti->error = "Could not add volume to device"; - goto bad_add_volume; - } /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_BLOCK_SCALE; @@ -161,18 +183,12 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) return 0; -bad_add_volume: - vvz_vol_destroy(svol); bad_vol_create: if (vol_idx == 0) { - /* Release devID */ - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return -EINTR; - vvz_alldevs[dev_id] = NULL; - if (dev_id < vvz_free_devid) - vvz_free_devid = dev_id; - mutex_unlock(&vvz_alldevs_lock); - /* Destroy device */ + vvz_remove_device_global(dev_id); +bad_dev_global: + dm_put_device(ti, sdev->dm_dev); +bad_dm_dev: vvz_dev_destroy(sdev); } return err; @@ -184,21 +200,12 @@ static void vvz_dtr(struct dm_target *ti) { struct vvz_volume *svol = ti->private; struct vvz_device *sdev = svol->sdev; - u32 dev_id = sdev->dev_id; + size_t vol_idx = svol->vol_idx; - vvz_dev_remove_volume(sdev, svol->vol_idx); vvz_vol_destroy(svol); - - /* Pointless to take volumes_lock here */ - if (!sdev->num_volumes) { - /* Release devID */ - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return; - vvz_alldevs[dev_id] = NULL; - if (dev_id < vvz_free_devid) - vvz_free_devid = dev_id; - mutex_unlock(&vvz_alldevs_lock); - /* Destroy device */ + if (vol_idx == 0) { + vvz_remove_device_global(sdev->dev_id); + dm_put_device(ti, sdev->dm_dev); vvz_dev_destroy(sdev); } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 499618b..b8e8083 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -65,11 +65,6 @@ struct vvz_device sector_t posmap_size_sectors; sector_t dev_header_size_sectors; - /* All volumes mapping to this device */ - struct mutex volumes_lock; // TODO can ctr/dtr be actually called concurrently? - struct vvz_volume *volumes[VVZ_DEV_MAX_VOLUMES]; - size_t num_volumes; - /* Shuffled array of PSIs */ struct mutex slices_lock; u32 *prmslices; @@ -79,6 +74,7 @@ struct vvz_device /* Sysfs */ struct kobject kobj; + struct completion kobj_released; /* TODO should these be per-device? */ struct bio_set bs; @@ -100,6 +96,7 @@ struct vvz_volume /* Sysfs */ struct kobject kobj; + struct completion kobj_released; /* Crypto */ struct crypto_skcipher *tfm; @@ -127,8 +124,6 @@ extern u32 vvz_free_devid; /* The lowest free devID */ /* Device */ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices); void vvz_dev_destroy(struct vvz_device *sdev); -int vvz_dev_add_volume(struct vvz_device *sdev, struct vvz_volume *svol, size_t vol_idx); -void vvz_dev_remove_volume(struct vvz_device *sdev, size_t vol_idx); /* Volume */ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *enckey); @@ -137,10 +132,10 @@ void vvz_vol_destroy(struct vvz_volume *svol); /* Sysfs */ int vvz_sysfs_init(void); void vvz_sysfs_exit(void); -int vvz_sysfs_add_device(struct vvz_device *sdev); -void vvz_sysfs_remove_device(struct vvz_device *sdev); -int vvz_sysfs_add_volume(struct vvz_volume *svol); -void vvz_sysfs_remove_volume(struct vvz_volume *svol); +int vvz_sysfs_register_device(struct vvz_device *sdev); +void vvz_sysfs_unregister_device(struct vvz_device *sdev); +int vvz_sysfs_register_volume(struct vvz_volume *svol); +void vvz_sysfs_unregister_volume(struct vvz_volume *svol); #endif /* _VVZ_H */ From be34b3909262fe9ea848c0d07529c70cf2cda648 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 11:56:27 +0100 Subject: [PATCH 29/98] Fix volume --- dm-vvz/volume.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 9a7b7ee..cbe155c 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -42,15 +42,14 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e /* Crypto */ svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(svol->tfm)) { - DMERR("Could not allocate AES-XTS cipher handle"); err = PTR_ERR(svol->tfm); - svol->tfm = NULL; - goto bad; + DMERR("Could not allocate AES-XTS cipher handle; error %d", err); + goto bad_tfm_alloc; } err = crypto_skcipher_setkey(svol->tfm, enckey, VVZ_XTS_KEYLEN); if (err) { - DMERR("Could not set key in crypto transform"); - goto bad; + DMERR("Could not set key in crypto transform; error %d", err); + goto bad_tfm_setkey; } /* Position map */ @@ -60,7 +59,7 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e if (!svol->posmap) { DMERR("Could not allocate position map"); err = -ENOMEM; - goto bad; + goto bad_posmap_alloc; } svol->nr_mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) @@ -70,8 +69,11 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e return svol; -bad: - vvz_vol_destroy(svol); +bad_posmap_alloc: +bad_tfm_setkey: + crypto_free_skcipher(svol->tfm); +bad_tfm_alloc: + kfree(svol); return ERR_PTR(err); } @@ -80,13 +82,8 @@ void vvz_vol_destroy(struct vvz_volume *svol) { /* TODO remove from sysfs */ - /* No locking needed here TODO: right? */ - if (svol->posmap) - vfree(svol->posmap); - - if (svol->tfm) - crypto_free_skcipher(svol->tfm); - + vfree(svol->posmap); + crypto_free_skcipher(svol->tfm); kfree(svol); return; From 7599ede924460a6ed4ce15c80d38510804af1a7d Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 15:20:56 +0100 Subject: [PATCH 30/98] Add sysfs device --- dm-vvz/sysfs.c | 95 +++++++++++++++++++++++++++++++++++++++--- dm-vvz/vvz_constants.h | 3 +- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index 0112c0a..08e075f 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -42,11 +42,8 @@ static ssize_t next_dev_id_show(struct module_attribute *mattr, struct module_ko return ret; } - static struct kset *bdevs_kset; -static struct module_attribute devid_mattr = - __ATTR(VVZ_SYSFS_DEVID_RAW, 0444, next_dev_id_show, NULL); - +static struct module_attribute devid_mattr = __ATTR_RO(next_dev_id); int vvz_sysfs_init() { @@ -74,10 +71,98 @@ bad_bdevs: return err; } - void vvz_sysfs_exit() { sysfs_remove_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); kset_unregister(bdevs_kset); } + +/* + *---------------------------- + * Device entries + *---------------------------- + */ + +static ssize_t dev_id_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); + + return sysfs_emit(buf, "%u\n", sdev->dev_id); +} + +static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); + + return sysfs_emit(buf, "%u\n", sdev->tot_slices); +} + +static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); + int ret; + + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", sdev->nr_free_slices); + mutex_unlock(&sdev->slices_lock); + + return ret; +} + +static struct kobj_attribute dev_id_kattr = __ATTR_RO(dev_id); +static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); +static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); +static struct attribute *vvz_device_default_attrs[] = { + &dev_id_kattr.attr, + &tot_slices_kattr.attr, + &free_slices_kattr.attr, + NULL +}; +ATTRIBUTE_GROUPS(vvz_device_default); + +static void vvz_device_kobj_release(struct kobject *kobj) +{ + struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); + + complete(&sdev->kobj_released); +} + +static struct kobj_type vvz_device_ktype = { + .release = vvz_device_kobj_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = vvz_device_default_groups +}; + +int vvz_sysfs_register_device(struct vvz_device *sdev) +{ + int err; + + /* Completion */ + init_completion(&sdev->kobj_released); + + /* Register directory :/ under bdevs/ */ + sdev->kobj.kset = bdevs_kset; + err = kobject_init_and_add(&sdev->kobj, &vvz_device_ktype, NULL, + "%s", sdev->dm_dev->name); + if (err) + goto bad; + /* Emit uevent */ + kobject_uevent(&sdev->kobj, KOBJ_ADD); + + return 0; + + +bad: + kobject_put(&sdev->kobj); + wait_for_completion(&sdev->kobj_released); + return err; +} + +void vvz_sysfs_unregister_device(struct vvz_device *sdev) +{ + kobject_put(&sdev->kobj); + wait_for_completion(&sdev->kobj_released); +} + diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index f93cf35..b2bfe0c 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -58,8 +58,7 @@ /* Sysfs entries under /sys/module/dm_vvz/ */ #define VVZ_SYSFS_BDEVS "bdevs" -#define VVZ_SYSFS_DEVID_RAW next_dev_id -#define VVZ_SYSFS_DEVID STRINGIFY(VVZ_SYSFS_DEVID_RAW) +#define VVZ_SYSFS_DEVID "next_dev_id" #endif /* _VVZ_CONSTANTS_H_ */ From 11db7f252661fca41659b8c97e1c3bb756310a87 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 15:37:35 +0100 Subject: [PATCH 31/98] Add sysfs volume --- dm-vvz/sysfs.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ dm-vvz/volume.c | 1 + dm-vvz/vvz.h | 2 ++ 3 files changed, 72 insertions(+) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index 08e075f..d234331 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -166,3 +166,72 @@ void vvz_sysfs_unregister_device(struct vvz_device *sdev) wait_for_completion(&sdev->kobj_released); } + +/* + *---------------------------- + * Volume entries + *---------------------------- + */ + +static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct vvz_volume *svol = container_of(kobj, struct vvz_volume, kobj); + int ret; + + if (mutex_lock_interruptible(&svol->posmap_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", svol->nr_mapped_slices); + mutex_unlock(&svol->posmap_lock); + + return ret; +} + +static struct kobj_attribute mapped_slices_kattr = __ATTR_RO(mapped_slices); +static struct attribute *vvz_volume_default_attrs[] = { + &mapped_slices_kattr.attr, + NULL +}; +ATTRIBUTE_GROUPS(vvz_volume_default); + +static void vvz_volume_kobj_release(struct kobject *kobj) +{ + struct vvz_volume *svol = container_of(kobj, struct vvz_volume, kobj); + + complete(&svol->kobj_released); +} + +static struct kobj_type vvz_volume_ktype = { + .release = vvz_volume_kobj_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = vvz_volume_default_groups +}; + +int vvz_sysfs_register_volume(struct vvz_volume *svol) +{ + int err; + + /* Completion */ + init_completion(&svol->kobj_released); + + /* Register directory name>/ under device directory */ + err = kobject_init_and_add(&svol->kobj, &vvz_volume_ktype, &svol->sdev->kobj, + "%s", svol->name); + if (err) + goto bad; + /* Emit uevent */ + kobject_uevent(&svol->kobj, KOBJ_ADD); + + return 0; + + +bad: + kobject_put(&svol->kobj); + wait_for_completion(&svol->kobj_released); + return err; +} + +void vvz_sysfs_unregister_volume(struct vvz_volume *svol) +{ + kobject_put(&svol->kobj); + wait_for_completion(&svol->kobj_released); +} diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index cbe155c..982d9a2 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -38,6 +38,7 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e svol->sdev = sdev; svol->vol_idx = vol_idx; + sprintf(svol->name, "vvz_%u_%lu", sdev->dev_id, vol_idx); /* Crypto */ svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index b8e8083..85ae622 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -88,6 +88,8 @@ struct vvz_volume /* Volume index within the device */ size_t vol_idx; + /* Name: vvz__ */ + char name[16]; /* Position map */ struct mutex posmap_lock; From 73664e349c8d54b9337544b6821e1d07b818eea6 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 15:42:26 +0100 Subject: [PATCH 32/98] Utilise sysfs --- dm-vvz/device.c | 12 +++++++++--- dm-vvz/volume.c | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 3845066..b1a742d 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -109,11 +109,18 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) goto bad_dmio_client; } - // TODO: init sysfs + /* Register to sysfs, once initialised */ + err = vvz_sysfs_register_device(sdev); + if (err) { + DMERR("Could not register device with sysfs; error %d", err); + goto bad_sysfs; + } return sdev; +bad_sysfs: + dm_io_client_destroy(sdev->io_client); bad_dmio_client: bioset_exit(sdev->bs); bad_bioset: @@ -128,8 +135,7 @@ bad_ofld: void vvz_dev_destroy(struct vvz_device *sdev) { - /* TODO remove from sysfs */ - + vvz_sysfs_unregister_device(sdev); dm_io_client_destroy(sdev->io_client); bioset_exit(&sdev->bs); vfree(sdev->prmslices); diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 982d9a2..110a7aa 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -65,11 +65,18 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e svol->nr_mapped_slices = 0; // TODO load posmap (must have crypto tfm initialised) - // TODO: init sysfs + /* Register to sysfs, once initialised */ + err = vvz_sysfs_register_volume(svol); + if (err) { + DMERR("Could not register volume with sysfs; error %d", err); + goto bad_sysfs; + } return svol; +bad_sysfs: + vfree(svol->posmap); bad_posmap_alloc: bad_tfm_setkey: crypto_free_skcipher(svol->tfm); @@ -81,8 +88,7 @@ bad_tfm_alloc: void vvz_vol_destroy(struct vvz_volume *svol) { - /* TODO remove from sysfs */ - + vvz_sysfs_unregister_volume(svol); vfree(svol->posmap); crypto_free_skcipher(svol->tfm); kfree(svol); From c563841e4ea0210e1768069f413433647a10f315 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 16:36:36 +0100 Subject: [PATCH 33/98] Update wq in scholia --- dm-vvz/SCHOLIA.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dm-vvz/SCHOLIA.md b/dm-vvz/SCHOLIA.md index f32f15d..a8b2439 100644 --- a/dm-vvz/SCHOLIA.md +++ b/dm-vvz/SCHOLIA.md @@ -1,7 +1,7 @@ #### Workqueues Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, `dm-crypt` uses a bound `io_queue` for bio mapping; it also has a `crypt_queue` for encryption/decryption, that is `WQ_CPU_INTENSIVE`: that wq is either bound or unbound, based on the flag `DM_CRYPT_SAME_CPU`. Both these workqueues are `WQ_MEM_RECLAIM`, and we probably need that for Shufflecake too. -As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. This latter choice is probably the easiest, so it's a good first attempt; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. -All in all, we can use a single module-wide bound wq, that is `WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE`, and whose work items process bio's start-to-finish. +As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. At this first stage, we use a per-device wq, since we imagine the device as the unit of resource sharing; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. +All in all, we can use a per-device bound wq, that is `WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE`, and whose work items process bio's start-to-finish. #### Position map: write-back or write-through? A first idea was to have slice allocation happen exclusively in memory, and only synchronise the position map to disk upon FLUSH requests (write-back posmap), but this gives the scheme a weird and inconsistent *crash behaviour*. From 9d159d62520870b44bb2475f921c4b023c44b4f7 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 11 Jan 2024 16:53:25 +0100 Subject: [PATCH 34/98] Stub bio map --- dm-vvz/device.c | 11 +++++++++++ dm-vvz/vvz.c | 19 ++++++++++++++++++- dm-vvz/vvz.h | 15 ++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index b1a742d..e81d9f3 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -109,6 +109,14 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) goto bad_dmio_client; } + /* Workqueue */ + sdev->wq = alloc_workqueue("vvz_%s", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->dm_dev->name); + if (!sdev->wq) { + err = -ENOMEM; + DMERR("Could not allocate workqueue"); + goto bad_wq; + } + /* Register to sysfs, once initialised */ err = vvz_sysfs_register_device(sdev); if (err) { @@ -120,6 +128,8 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) bad_sysfs: + destroy_workqueue(sdev->wq); +bad_wq: dm_io_client_destroy(sdev->io_client); bad_dmio_client: bioset_exit(sdev->bs); @@ -136,6 +146,7 @@ bad_ofld: void vvz_dev_destroy(struct vvz_device *sdev) { vvz_sysfs_unregister_device(sdev); + destroy_workqueue(sdev->wq); dm_io_client_destroy(sdev->io_client); bioset_exit(&sdev->bs); vfree(sdev->prmslices); diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 3a2cfc3..9461493 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -178,6 +178,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) ti->num_secure_erase_bios = 0; ti->num_write_zeroes_bios = 0; ti->accounts_remapped_io = true; + ti->per_io_data_size = sizeof(struct vvz_io); ti->private = svol; return 0; @@ -215,7 +216,23 @@ static void vvz_dtr(struct dm_target *ti) static int vvz_map(struct dm_target *ti, struct bio *bio) { - return DM_MAPIO_KILL; + struct vvz_io *sio = dm_per_bio_data(bio, sizeof(struct vvz_io)); + struct vvz_volume *svol = ti->private; + + // TODO check flush + + /* Set fields */ + sio->orig_bio = bio; + sio->svol = svol; + + /* Enqueue */ + if (bio_data_dir(bio) == READ) + INIT_WORK(&sio->work, vvz_read_work_fn); + else + INIT_WORK(&sio->work, vvz_write_work_fn); + queue_work(svol->sdev->wq, &sio->work); + + return DM_MAPIO_SUBMITTED; } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 85ae622..6241b77 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -76,9 +76,10 @@ struct vvz_device struct kobject kobj; struct completion kobj_released; - /* TODO should these be per-device? */ + /* Resource sharing */ struct bio_set bs; struct dm_io_client *io_client; + struct workqueue_struct *wq; }; struct vvz_volume @@ -104,6 +105,14 @@ struct vvz_volume struct crypto_skcipher *tfm; }; +struct vvz_io +{ + struct vvz_volume *svol; + struct bio *orig_bio; + + struct work_struct work; +}; + /* *---------------------------- @@ -139,5 +148,9 @@ void vvz_sysfs_unregister_device(struct vvz_device *sdev); int vvz_sysfs_register_volume(struct vvz_volume *svol); void vvz_sysfs_unregister_volume(struct vvz_volume *svol); +/* Bio mapping */ +void vvz_read_work_fn(struct work_struct *work); +void vvz_write_work_fn(struct work_struct *work); + #endif /* _VVZ_H */ From b49865d004c51e9e593b440262f27a2e9cf992a9 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 19 Jan 2024 17:53:20 +0100 Subject: [PATCH 35/98] Conclude posmap.c --- dm-vvz/crypto.c | 106 +++++----- dm-vvz/crypto.h | 46 ----- dm-vvz/device.c | 6 +- dm-vvz/posmap.c | 426 ++++++++++++++++++++++++++++------------- dm-vvz/posmap.h | 49 ----- dm-vvz/vvz.c | 2 +- dm-vvz/vvz.h | 22 ++- dm-vvz/vvz_constants.h | 5 + 8 files changed, 374 insertions(+), 288 deletions(-) delete mode 100644 dm-vvz/crypto.h delete mode 100644 dm-vvz/posmap.h diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index 0107b10..cdf0528 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -23,71 +23,26 @@ #include #include -#include "crypto.h" #include "vvz_constants.h" -#include "log.h" -static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, - struct scatterlist *dst, u64 pblk_num, int rw); - - -/* Encrypt-decrypt a single block (memory buffer is a page) */ -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw) -{ - struct scatterlist dst, src; - - /* We assume PAGE_SIZE == VVZ_BLOCK_SIZE TODO better document this */ - sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); - - return crypt_sg(tfm, &src, &dst, pblk_num, rw); -} - - -/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ -int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, - size_t num_blocks, u64 first_pblk_num, int rw) -{ - struct scatterlist dst, src; - size_t block; - u64 pblk_num; - int err; - - pblk_num = first_pblk_num; - for (block = 0; block < num_blocks; block++) { - sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); - sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); - - err = crypt_sg(tfm, &src, &dst, pblk_num, rw); - if (err) - return err; - - src_buf += VVZ_BLOCK_SIZE; - dst_buf += VVZ_BLOCK_SIZE; - pblk_num += 1; - } - - return 0; -} - - -/* - * The IV is constructed as the 0-padded LE representation of the block number, - * which is exactly what dm-crypt does (IV mode "plain64") +/** + * Encrypt/decrypt exactly one block, already encoded in the scatterlist. + * All other crypto functions reduce to this one. + * The IV is constructed as the right-0-padded LE representation of the + * physical block number, which is exactly what dm-crypt does when using the + * IV mode "plain64". */ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, struct scatterlist *dst, u64 pblk_num, int rw) { - u8 iv[VVZ_CRYPTO_IVLEN]; + u8 iv[VVZ_XTS_IVLEN]; struct skcipher_request *req = NULL; DECLARE_CRYPTO_WAIT(wait); int err; - /* TODO: not too sure about the gfp_mask here */ + // TODO not too sure about the gfp_mask here + // TODO move @req into struct vvz_io? req = skcipher_request_alloc(tfm, GFP_NOIO); if (!req) return -ENOMEM; @@ -97,7 +52,7 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, crypto_req_done, &wait); /* Construct IV */ - memset(iv, 0, VVZ_CRYPTO_IVLEN); + memset(iv, 0, VVZ_XTS_IVLEN); *(__le64 *)iv = cpu_to_le64(pblk_num); skcipher_request_set_crypt(req, src, dst, VVZ_BLOCK_SIZE, iv); @@ -110,3 +65,44 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, return err; } +/* Encrypt-decrypt a single block (memory buffer is a page) */ +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw) +{ + struct scatterlist dst, src; + + /* We assume PAGE_SIZE >= VVZ_BLOCK_SIZE TODO better document this */ + sg_init_table(&src, 1); + sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); + sg_init_table(&dst, 1); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); + + return crypt_sg(tfm, &src, &dst, pblk_num, rw); +} + + +/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ +int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw) +{ + struct scatterlist dst, src; + u64 pblk_num; + int err; + + for (pblk_num = first_pblk_num; + pblk_num < first_pblk_num + num_blocks; + pblk_num++) { + sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); + sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); + + err = crypt_sg(tfm, &src, &dst, pblk_num, rw); + if (err) + return err; + + src_buf += VVZ_BLOCK_SIZE; + dst_buf += VVZ_BLOCK_SIZE; + } + + return 0; +} + diff --git a/dm-vvz/crypto.h b/dm-vvz/crypto.h deleted file mode 100644 index c3a83c3..0000000 --- a/dm-vvz/crypto.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* For AES-XTS-256, we use the *physical* address of the 4096-byte block, - * within the device, as IV (little-endian, zero-padded). */ - -#ifndef _VVZ_CRYPTO_H_ -#define _VVZ_CRYPTO_H_ - - -#include - - -#define VVZ_CRYPTO_IVLEN 16 /* bytes */ - - -/* Encrypt-decrypt a single block (memory buffer is a page) */ -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw); - -/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ -int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, u8 *src_buf, u8 *dst_buf, - size_t num_blocks, u64 first_pblk_num, int rw); - - -#endif /* _VVZ_CRYPTO_H_ */ diff --git a/dm-vvz/device.c b/dm-vvz/device.c index e81d9f3..3681fff 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -95,7 +95,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) sdev->prmslices_octr = 0; /* Bioset */ - err = bioset_init(&sdev->bs, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + err = bioset_init(&sdev->bioset, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); if (err) { DMERR("Could not init bioset; error %d", err); goto bad_bioset; @@ -132,7 +132,7 @@ bad_sysfs: bad_wq: dm_io_client_destroy(sdev->io_client); bad_dmio_client: - bioset_exit(sdev->bs); + bioset_exit(sdev->bioset); bad_bioset: vfree(sdev->prmslices); bad_prmslices: @@ -148,7 +148,7 @@ void vvz_dev_destroy(struct vvz_device *sdev) vvz_sysfs_unregister_device(sdev); destroy_workqueue(sdev->wq); dm_io_client_destroy(sdev->io_client); - bioset_exit(&sdev->bs); + bioset_exit(&sdev->bioset); vfree(sdev->prmslices); vfree(sdev->slices_ofld); kfree(sdev); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 17bc965..e5ca102 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -21,175 +21,337 @@ * If not, see . */ -#include "posmap.h" -#include "crypto.h" +#include "vvz.h" -/******************************************************************************* - * In-memory operations on the position map - ******************************************************************************/ +/* + *---------------------------- + * Create slice mapping + *---------------------------- + */ - -/* Get the next free PSI in the device's shuffled array, and mark it taken */ -static int take_free_psi(struct vvz_device *sd, u32 *psi); - - -/* Returns INVALID (in *psi) if mapping does not exist */ -int vvz_get_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) +/** + * Grab the specified PSI in the device: mark as occupied, decrease the number + * of free slices, advance the occupation counter. This specific algorithm, + * that never wraps @prmslices_octr around, only works because slices are never + * freed up. + * Sanity checks to be performed by the caller. + * + * MUTEX: @sdev->slices_lock must be held. + */ +static void _grab_psi(struct vvz_device *sdev, u32 psi) { - /* Sanity check (redundant?) */ - if(unlikely(lsi >= sv->sd->tot_slices)) - return -EINVAL; + u32 i; - if (mutex_lock_interruptible(&sv->posmap_lock)) - return -EINTR; - *psi = sv->posmap[lsi]; - mutex_unlock(&sv->posmap_lock); + sdev->slices_ofld[*psi] = true; + sdev->nr_free_slices--; + + /* Preserve the invariant: @prmslices_octr must point to a free slice */ + for (i = sdev->prmslices_octr; + i < sdev->tot_slices && sdev->slices_ofld[i]; + i++); + sdev->prmslices_octr = i; +} + +/** + * Return the next free PSI in the device's shuffled array, but don't modify + * the device state. + * + * MUTEX: @sdev->slices_lock must be held. + */ +static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) +{ + int err; + + if (unlikely(!sdev->nr_free_slices)) + return -ENOSPC; + if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) + return -ENOTRECOVERABLE; // Grave inconsistency + + /* Invariant: @prmslices_octr points to a free slice */ + *psi = sdev->prmslices[sdev->prmslices_octr]; + if (unlikely(sdev->slices_ofld[*psi])) + return -ENOTRECOVERABLE; // Grave inconsistency return 0; } - -/* Does not persist mapping on disk (only done on FLUSH requests) */ -int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi) +/** + * Map LSI => PSI, only in memory. + * Sanity checks to be performed by the caller. + * + * MUTEX: @sdev->slices_lock must be held. + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static void _create_local_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 psi) { - int err; + /* Grab it from the device */ + _grab_psi(svol->sdev, psi); + /* Insert in the volume */ + svol->posmap[lsi] = *psi; + svol->nr_mapped_slices++; - /* Sanity check (redundant?) */ - if(unlikely(lsi >= sv->sd->tot_slices)) - return -EINVAL; - - if (mutex_lock_interruptible(&sv->posmap_lock)) - return -EINTR; - - /* Check mapping not existent (redundant?) */ - if (unlikely(sv->posmap[lsi] != VVZ_POSMAP_INVALID)){ - err = -EINVAL; - goto out; - } - - /* Create it in the device */ - err = take_free_psi(sv->sd, psi); - if (err) - goto out; - /* And in the volume */ - sv->posmap[lsi] = *psi; - sv->nr_mapped_slices += 1; - sv->posmap_dirty = true; - - err = 0; - -out: - mutex_unlock(&sv->posmap_lock); - return err; + return; } - -/* Get the next free PSI in the device's shuffled array, and mark it taken. - * This specific algorithm, that never wraps first_free_psi_idx around, - * only works because slices are never freed up. */ -static int take_free_psi(struct vvz_device *sd, u32 *psi) +/** + * Delete mapping for the given LSI, only in memory. + * Sanity checks to be performed by the caller. + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static void _delete_local_slice_mapping(struct vvz_volume *svol, u32 lsi) { - int err; + /* Delete mapping in the volume */ + svol->posmap[lsi] = VVZ_POSMAP_INVALID; + svol->nr_mapped_slices--; - if (mutex_lock_interruptible(&sd->prmslices_lock)) - return -EINTR; - - /* PSI to return */ - if (unlikely(!sd->nr_free_slices)) { - err = -ENOSPC; - goto out; - } - *psi = sd->shuffled_psis[sd->first_free_psi_idx]; - if (unlikely(sd->psi_taken[*psi])) { - err = -EINVAL; // TODO make it more tragic - goto out; - } - - /* Update device state */ - int i; - for (i = sd->first_free_psi_idx + 1; - i < sd->tot_slices && sd->psi_taken[i]; i++); - sd->first_free_psi_idx = i; - sd->nr_free_slices -= 1; - sd->psi_taken[*psi] = true; - -out: - mutex_unlock(&sd->prmslices_lock); - return err; + /* Don't do anything in the device though, leave it there: we don't yet + * have an obvious way to release PSIs. + * This means a PSI will be incorrectly marked as occupied, but that's + * not too bad: the PSI shuffling and its occupation counter are + * ephemeral, so they reset if you close and reopen all the volumes. */ + return; } - -/******************************************************************************* - * On-disk operations on the position map - ******************************************************************************/ - - -/* Synchronously read/write the entire on-disk encrypted position map */ -static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw); - - -/* Read position map from disk and decrypt in place */ -int vvz_load_posmap(struct vvz_volume *sv) +/** + * Synchronously store (and flush) the posmap block containing the given LSI. + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static int store_posmap_block(struct vvz_volume *svol, u32 lsi) { + struct vvz_device *sdev = svol->sdev; + u32 posmap_block_num = lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK; + u32 first_lsi = posmap_block_num * VVZ_POSMAP_ENTRIES_PER_BLOCK; + struct page *page; + struct bio *bio; int err; - /* No locking needed (we are in the constructor) */ - err = rw_posmap(sv, sv->posmap, READ); - if (err) - return err; - - /* Decrypt in place */ - err = vvz_crypt_blocks_vm(sv->tfm, sv->posmap, sv->posmap, - sv->sd->posmap_size_sectors >> VVZ_BLOCK_SHIFT, - VVZ_POSMAP_START_SECTOR(sv) >> VVZ_BLOCK_SHIFT, - READ); - if (err) - return err; - - return 0; -} - - -/* Encrypt position map out of place, onto a buffer, and write it on disk */ -int vvz_store_posmap(struct vvz_volume *sv) -{ - u8 *enc_posmap; - int err; - - /* Allocate it every time TODO evaluate alternatives */ - enc_posmap = vmalloc(sv->sd->posmap_size_sectors * SECTOR_SIZE); - if (!enc_posmap) + /* Sync + flush TODO GFP mask ok? */ + bio = bio_alloc_bioset(sdev->dm_dev->bdev, 1, + REQ_OP_WRITE | REQ_SYNC | REQ_FUA, GFP_NOIO, + &sdev->bioset); + if (!bio) { + DMERR("Could not allocate posmap block bio"); return -ENOMEM; + } + bio->bi_iter.bi_sector = VVZ_POSMAP_START_SECTOR(svol) + + (posmap_block_num << VVZ_BLOCK_SHIFT); - if (mutex_lock_interruptible&sv->posmap_lock) { - err = -EINTR; - goto out_vfree; + /* Alloc and add page TODO GFP mask */ + page = alloc_page(GFP_NOIO); + if (!page) { + DMERR("Could not allocate posmap block page"); + err = -ENOMEM; + goto bad_alloc_page; + } + // TODO remove this error check + if (unlikely(!bio_add_page(bio, page, VVZ_BLOCK_SIZE, 0))) { + DMCRIT("Could not add posmap block page to bio!"); + err = -ENOTRECOVERABLE; + goto bad_add_page; } + /* Serialise posmap block onto the page */ + void *page_ptr = kmap_local_page(page); + u32 lsi_iter; + for (lsi_iter = first_lsi; + lsi_iter < first_lsi + VVZ_POSMAP_ENTRIES_PER_BLOCK; + lsi_iter++) { + u32 psi = svol->posmap[lsi_iter]; + __be32 *be_psi = (__be32*) (page_ptr + (lsi_iter * sizeof(__be32))); + *be_psi = cpu_to_be32(psi); + } + kunmap_local(page_ptr); -out_vfree: - vfree(enc_posmap); + /* Encrypt the block in place */ + err = vvz_crypt_block_page(svol->tfm, page, page, + bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); + if (err) { + DMERR("Could not encrypt posmap block; error %d", err); + goto bad_encrypt; + } + + /* Submit */ + err = submit_bio_wait(bio); + DMERR("Could not complete posmap block bio; error %d", err); + +bad_encrypt: +bad_add_page: + __free_page(page); +bad_alloc_page: + bio_put(bio); return err; } -/* Synchronously read/write the entire on-disk encrypted position map */ -static int rw_posmap(struct vvz_volume *sv, u8 *buf, int rw) +/** + * Create a new mapping for the given LSI, and synchronise back to disk. + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + * MUTEX: takes @sdev->slices_lock. + * + * Syncing to disk means the posmap lock will be held (by the caller) for a long + * time thus blocking out all the other incoming bio's, even unrelated ones + * (falling in different slices). Several strategies are possible to avoid this + * problem, but for now we keep this simple implementation. + */ +int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *psi) +{ + struct vvz_device *sdev = svol->sdev; + int err; + + /* Bounds check TODO redundant? */ + if(unlikely(lsi >= svol->sdev->tot_slices)) + return -EINVAL; + /* Check mapping not existent TODO redundant? */ + if (unlikely(svol->posmap[lsi] != VVZ_POSMAP_INVALID)) + return -EINVAL; + + /* Create mapping */ + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + err = peek_next_free_psi(sdev, psi); + if (err) { + mutex_unlock(&sdev->slices_lock); + return err; + } + _create_local_slice_mapping(svol, lsi, *psi); + mutex_unlock(&sdev->slices_lock); + + /* Write posmap block to disk */ + err = store_posmap_block(svol, lsi); + if (err) { + DMERR("Could not store posmap block; error %d", err); + _delete_local_slice_mapping(svol, lsi); + return err; + } + + return 0; +} + + +/* + *---------------------------- + * Load position map + *---------------------------- + */ + +/** + * Synchronously read the entire on-disk encrypted position map + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + */ +static int read_encrypted_posmap(struct vvz_volume *svol) { struct dm_io_request io_req = { - .bi_opf = (rw == READ) ? REQ_OP_READ : REQ_OP_WRITE, + .bi_opf = REQ_OP_READ | REQ_SYNC, .mem.type = DM_IO_VMA, - .mem.ptr.vma = buf, + .mem.ptr.vma = svol->posmap, .notify.fn = NULL, - .client = sv->sd->io_client + .client = svol->sdev->io_client }; struct dm_io_region io_region = { - .bdev = sv->sd->dev->bdev, - .sector = VVZ_POSMAP_START_SECTOR(sv), - .count = sv->sd->posmap_size_sectors + .bdev = svol->sdev->dm_dev->bdev, + .sector = VVZ_POSMAP_START_SECTOR(svol), + .count = svol->sdev->posmap_size_sectors }; return dm_io(&io_req, 1, &io_region, NULL); } +/** + * De-serialise the position map entries. On the fly, if a conflict is detected, + * resolve it by sampling a new PSI, and sync to disk (block by block). + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + * MUTEX: @sdev->slices_lock must be held. + */ +static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) +{ + struct vvz_device *sdev = svol->sdev; + void *posmap_ptr = svol->posmap; + u32 lsi; + bool posmap_block_dirty; + int err; + + for (lsi = 0; lsi < sdev->tot_slices; lsi++) { + /* Reset dirty bit at the start of every posmap block */ + if (lsi % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) + posmap_block_dirty = false; + + /* De-serialise posmap entry */ + __be32 *be_psi = (__be32*) (posmap_ptr + (lsi * sizeof(__be32))); + u32 psi = be32_to_cpu(*be_psi); + + /* If LSI unmapped, just continue */ + if (psi == VVZ_POSMAP_INVALID) { + svol->posmap[lsi] = psi; + continue; + } + + /* If PSI already taken, sample a new one */ + if (sdev->slices_ofld[psi]) { + DMWARN("Corruption of volume %d: LSI %u was evicted from PSI %u", + svol->vol_idx, lsi, psi); + err = peek_next_free_psi(sdev, &psi); + if (err) + return err; + posmap_block_dirty = true; + } + /* Either way, create the mapping locally */ + _create_local_slice_mapping(svol, lsi, psi); + + /* Only check dirty bit at the end of the posmap block */ + if (posmap_block_dirty && + ( ((lsi + 1) % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) || + (lsi == sdev->tot_slices) )) { + err = store_posmap_block(svol, lsi); + if (err) + return err; + } + } + + return 0; +} + + +/** + * Load the volume's position map from the disk. If some conflicts are present + * (i.e. an LSI is mapped to a PSI that's already taken), then resolve them + * (i.e. re-sample a free PSI for the "unlucky" LSI) and sync back to disk. + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + * MUTEX: takes @sdev->slices_lock. + */ +int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) +{ + int err; + struct vvz_device *sdev = svol->sdev; + u32 lsi; + + /* Read raw posmap from disk */ + err = read_encrypted_posmap(svol); + if (err) + return err; + + /* Decrypt in place */ + err = vvz_crypt_blocks_vm(svol->tfm, svol->posmap, svol->posmap, + svol->sdev->posmap_size_sectors >> VVZ_BLOCK_SHIFT, + VVZ_POSMAP_START_SECTOR(svol) >> VVZ_BLOCK_SHIFT, + READ); + if (err) + return err; + + /* Deserialise and sanitise as you go */ + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + err = _deserialise_and_sanitise_posmap(svol); + mutex_unlock(&sdev->slices_lock); + if (err) + return err; + + return 0; +} + diff --git a/dm-vvz/posmap.h b/dm-vvz/posmap.h deleted file mode 100644 index df6c3a3..0000000 --- a/dm-vvz/posmap.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _VVZ_POSMAP_H_ -#define _VVZ_POSMAP_H_ - - -#include "volume.h" - - -#define VVZ_POSMAP_INVALID 0xFFFFFFFF -/* Starting sector of position map */ -#define VVZ_POSMAP_START_SECTOR(sv) \ - (VVZ_BLOCK_SCALE * (1 + VVZ_DEV_MAX_VOLUMES) + \ - (sv)->vol_idx * (sv)->sd->posmap_size_sectors) - - -/* At volume creation */ -int vvz_load_posmap(struct vvz_volume *sv); -/* On FLUSH request */ -int vvz_store_posmap(struct vvz_volume *sv); - -/* Returns INVALID (in *psi) if mapping does not exist */ -int vvz_get_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi); -/* Does not persist mapping on disk (only done on FLUSH requests) */ -int vvz_create_slice_mapping(struct vvz_volume *sv, u32 lsi, u32 *psi); - - -#endif /* _VVZ_POSMAP_H_ */ diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 9461493..2b8f844 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -238,7 +238,7 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) { - /* TODO: don't really know what to put here */ + // Currently, we only handle one block at a time limits->logical_block_size = VVZ_BLOCK_SIZE; limits->physical_block_size = VVZ_BLOCK_SIZE; limits->io_min = VVZ_BLOCK_SIZE; diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 6241b77..24048c0 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -24,7 +24,6 @@ #ifndef _VVZ_H #define _VVZ_H - #include #include #include @@ -77,7 +76,7 @@ struct vvz_device struct completion kobj_released; /* Resource sharing */ - struct bio_set bs; + struct bio_set bioset; struct dm_io_client *io_client; struct workqueue_struct *wq; }; @@ -114,6 +113,18 @@ struct vvz_io }; +/* + *---------------------------- + * Macros + *---------------------------- + */ + +/* Starting sector of position map */ +#define VVZ_POSMAP_START_SECTOR(svol) \ + (VVZ_BLOCK_SCALE * (1 + VVZ_DEV_MAX_VOLUMES) + \ + (svol)->vol_idx * (svol)->sdev->posmap_size_sectors) + + /* *---------------------------- * Global variables @@ -152,5 +163,12 @@ void vvz_sysfs_unregister_volume(struct vvz_volume *svol); void vvz_read_work_fn(struct work_struct *work); void vvz_write_work_fn(struct work_struct *work); +/* Position map */ +int vvz_load_and_sanitise_posmap(struct vvz_volume *svol); +int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *psi); + +/* Crypto */ +int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw); #endif /* _VVZ_H */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index b2bfe0c..24a9265 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -50,12 +50,17 @@ /* XTS requires doubling the key size */ #define VVZ_XTS_KEYLEN 64 /* bytes */ +/* The IV is the right-0-padded LE physical block number */ +#define VVZ_XTS_IVLEN 16 /* bytes */ #define VVZ_DEV_MAX_VOLUMES 15 #define VVZ_MAX_DEVS 1024 +#define VVZ_POSMAP_INVALID 0xFFFFFFFF + + /* Sysfs entries under /sys/module/dm_vvz/ */ #define VVZ_SYSFS_BDEVS "bdevs" #define VVZ_SYSFS_DEVID "next_dev_id" From 5cbdbbb614c4f8535efea9fb29def577b1646447 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 20 Jan 2024 12:43:35 +0100 Subject: [PATCH 36/98] fix posmap.c --- dm-vvz/posmap.c | 64 ++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index e5ca102..4fb2111 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -21,6 +21,8 @@ * If not, see . */ +#include + #include "vvz.h" @@ -31,30 +33,7 @@ */ /** - * Grab the specified PSI in the device: mark as occupied, decrease the number - * of free slices, advance the occupation counter. This specific algorithm, - * that never wraps @prmslices_octr around, only works because slices are never - * freed up. - * Sanity checks to be performed by the caller. - * - * MUTEX: @sdev->slices_lock must be held. - */ -static void _grab_psi(struct vvz_device *sdev, u32 psi) -{ - u32 i; - - sdev->slices_ofld[*psi] = true; - sdev->nr_free_slices--; - - /* Preserve the invariant: @prmslices_octr must point to a free slice */ - for (i = sdev->prmslices_octr; - i < sdev->tot_slices && sdev->slices_ofld[i]; - i++); - sdev->prmslices_octr = i; -} - -/** - * Return the next free PSI in the device's shuffled array, but don't modify + * Return the next free PSI in the device's shuffled array, without modifying * the device state. * * MUTEX: @sdev->slices_lock must be held. @@ -85,10 +64,20 @@ static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) */ static void _create_local_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 psi) { + struct vvz_device *sdev = svol->sdev; + u32 i; + /* Grab it from the device */ - _grab_psi(svol->sdev, psi); + sdev->slices_ofld[psi] = true; + sdev->nr_free_slices--; + // Preserve the invariant: @prmslices_octr must point to a free slice + for (i = sdev->prmslices_octr; + i < sdev->tot_slices && sdev->slices_ofld[i]; + i++); + sdev->prmslices_octr = i; + /* Insert in the volume */ - svol->posmap[lsi] = *psi; + svol->posmap[lsi] = psi; svol->nr_mapped_slices++; return; @@ -115,15 +104,13 @@ static void _delete_local_slice_mapping(struct vvz_volume *svol, u32 lsi) } /** - * Synchronously store (and flush) the posmap block containing the given LSI. + * Synchronously store (and flush) the given posmap block * * MUTEX: @svol->posmap_lock must be held, except under volume ctor. */ -static int store_posmap_block(struct vvz_volume *svol, u32 lsi) +static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) { struct vvz_device *sdev = svol->sdev; - u32 posmap_block_num = lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK; - u32 first_lsi = posmap_block_num * VVZ_POSMAP_ENTRIES_PER_BLOCK; struct page *page; struct bio *bio; int err; @@ -155,12 +142,12 @@ static int store_posmap_block(struct vvz_volume *svol, u32 lsi) /* Serialise posmap block onto the page */ void *page_ptr = kmap_local_page(page); - u32 lsi_iter; - for (lsi_iter = first_lsi; - lsi_iter < first_lsi + VVZ_POSMAP_ENTRIES_PER_BLOCK; - lsi_iter++) { - u32 psi = svol->posmap[lsi_iter]; - __be32 *be_psi = (__be32*) (page_ptr + (lsi_iter * sizeof(__be32))); + u32 first_lsi = posmap_block_num * VVZ_POSMAP_ENTRIES_PER_BLOCK; + u32 last_lsi = min(first_lsi + VVZ_POSMAP_ENTRIES_PER_BLOCK, sdev->tot_slices); + u32 lsi; + for (lsi = first_lsi; lsi < last_lsi; lsi++) { + u32 psi = svol->posmap[lsi]; + __be32 *be_psi = (__be32*) (page_ptr + (lsi * sizeof(__be32))); *be_psi = cpu_to_be32(psi); } kunmap_local(page_ptr); @@ -175,6 +162,7 @@ static int store_posmap_block(struct vvz_volume *svol, u32 lsi) /* Submit */ err = submit_bio_wait(bio); + if (err) DMERR("Could not complete posmap block bio; error %d", err); bad_encrypt: @@ -305,8 +293,8 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) /* Only check dirty bit at the end of the posmap block */ if (posmap_block_dirty && - ( ((lsi + 1) % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) || - (lsi == sdev->tot_slices) )) { + (((lsi+1) % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) || + ((lsi+1) == sdev->tot_slices))) { err = store_posmap_block(svol, lsi); if (err) return err; From cc3fcf6a7b6ad5620b44f344cda4b7dd22dd1d98 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 20 Jan 2024 13:14:03 +0100 Subject: [PATCH 37/98] fix posmap.c and update volume.c --- dm-vvz/device.c | 12 +++++------- dm-vvz/posmap.c | 5 ++--- dm-vvz/volume.c | 8 +++++++- dm-vvz/vvz.c | 2 +- dm-vvz/vvz.h | 2 ++ 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 3681fff..46d68d2 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "vvz.h" @@ -34,15 +35,11 @@ /* Fisher-Yates shuffle */ static void fisheryates_u32(u32 *v, u32 len) { - u32 i, j, tmp; + u32 i, j; for (i = len-1; i >= 1; i--) { j = get_random_u32_below(i+1); - - /* Swap v[i] and v[j] */ - tmp = v[i]; - v[i] = v[j]; - v[j] = tmp; + swap(v[i], v[j]); } return; @@ -110,7 +107,8 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) } /* Workqueue */ - sdev->wq = alloc_workqueue("vvz_%s", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->dm_dev->name); + sdev->wq = alloc_workqueue("vvz_%s", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, + 0, sdev->dm_dev->name); if (!sdev->wq) { err = -ENOMEM; DMERR("Could not allocate workqueue"); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 4fb2111..62cd007 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -22,7 +22,6 @@ */ #include - #include "vvz.h" @@ -209,7 +208,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p mutex_unlock(&sdev->slices_lock); /* Write posmap block to disk */ - err = store_posmap_block(svol, lsi); + err = store_posmap_block(svol, lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK); if (err) { DMERR("Could not store posmap block; error %d", err); _delete_local_slice_mapping(svol, lsi); @@ -295,7 +294,7 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) if (posmap_block_dirty && (((lsi+1) % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) || ((lsi+1) == sdev->tot_slices))) { - err = store_posmap_block(svol, lsi); + err = store_posmap_block(svol, lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK); if (err) return err; } diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 110a7aa..51ad740 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -63,7 +63,12 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e goto bad_posmap_alloc; } svol->nr_mapped_slices = 0; - // TODO load posmap (must have crypto tfm initialised) + /* Load from disk */ + err = vvz_load_and_sanitise_posmap(svol); + if (err) { + DMERR("Could not load position map from disk; error %d", err); + goto bad_posmap_load; + } /* Register to sysfs, once initialised */ err = vvz_sysfs_register_volume(svol); @@ -76,6 +81,7 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e bad_sysfs: +bad_posmap_load: vfree(svol->posmap); bad_posmap_alloc: bad_tfm_setkey: diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 2b8f844..0fbdbaa 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -98,7 +98,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) * argv[1]: path to underlying physical device * argv[2]: volume index within the device * argv[3]: number of 1-MiB slices in the underlying device - * argv[4]: 64-byte encryption key (hex-encoded) + * argv[4]: 64-byte encryption key (hex-encoded, so 128 chars) */ if (argc != 5) { ti->error = "Invalid argument count"; diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 24048c0..0ccb72f 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -170,5 +170,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p /* Crypto */ int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, u64 num_blocks, u64 first_pblk_num, int rw); +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw); #endif /* _VVZ_H */ From 30a303a90f488d7983c6f2b66d1a556096cacf33 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 20 Jan 2024 13:22:54 +0100 Subject: [PATCH 38/98] fix compilation errors --- dm-vvz/Kbuild | 2 +- dm-vvz/crypto.c | 1 + dm-vvz/device.c | 4 ++-- dm-vvz/posmap.c | 23 ++++++++++------------- dm-vvz/vvz.h | 3 --- dm-vvz/vvz_constants.h | 4 +++- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/dm-vvz/Kbuild b/dm-vvz/Kbuild index d9ab37a..1ed4b30 100644 --- a/dm-vvz/Kbuild +++ b/dm-vvz/Kbuild @@ -25,7 +25,7 @@ MODULE_NAME := dm_vvz obj-m := $(MODULE_NAME).o -OBJ_LIST := vvz.o device.o volume.o sysfs.o +OBJ_LIST := vvz.o device.o volume.o sysfs.o crypto.o posmap.o read.o write.o $(MODULE_NAME)-y += $(OBJ_LIST) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index cdf0528..f318340 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -23,6 +23,7 @@ #include #include +#include #include "vvz_constants.h" diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 46d68d2..2320d93 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -65,7 +65,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) sdev->nr_free_slices = tot_slices; /* Enough posmap blocks to fit all the entries */ sdev->posmap_size_sectors = VVZ_BLOCK_SCALE * - DIV_ROUND_UP(tot_slices, VVZ_POSMAP_ENTRIES_PER_BLOCK); + DIV_ROUND_UP(tot_slices, VVZ_PSIS_PER_BLOCK); /* DMB + VMBs + PosMaps */ sdev->dev_header_size_sectors = VVZ_BLOCK_SCALE + (VVZ_DEV_MAX_VOLUMES * VVZ_BLOCK_SCALE) + @@ -130,7 +130,7 @@ bad_sysfs: bad_wq: dm_io_client_destroy(sdev->io_client); bad_dmio_client: - bioset_exit(sdev->bioset); + bioset_exit(&sdev->bioset); bad_bioset: vfree(sdev->prmslices); bad_prmslices: diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 62cd007..fe2785d 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -39,8 +39,6 @@ */ static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) { - int err; - if (unlikely(!sdev->nr_free_slices)) return -ENOSPC; if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) @@ -91,7 +89,7 @@ static void _create_local_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 ps static void _delete_local_slice_mapping(struct vvz_volume *svol, u32 lsi) { /* Delete mapping in the volume */ - svol->posmap[lsi] = VVZ_POSMAP_INVALID; + svol->posmap[lsi] = VVZ_PSI_INVALID; svol->nr_mapped_slices--; /* Don't do anything in the device though, leave it there: we don't yet @@ -141,8 +139,8 @@ static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) /* Serialise posmap block onto the page */ void *page_ptr = kmap_local_page(page); - u32 first_lsi = posmap_block_num * VVZ_POSMAP_ENTRIES_PER_BLOCK; - u32 last_lsi = min(first_lsi + VVZ_POSMAP_ENTRIES_PER_BLOCK, sdev->tot_slices); + u32 first_lsi = posmap_block_num * VVZ_PSIS_PER_BLOCK; + u32 last_lsi = min(first_lsi + VVZ_PSIS_PER_BLOCK, sdev->tot_slices); u32 lsi; for (lsi = first_lsi; lsi < last_lsi; lsi++) { u32 psi = svol->posmap[lsi]; @@ -193,7 +191,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p if(unlikely(lsi >= svol->sdev->tot_slices)) return -EINVAL; /* Check mapping not existent TODO redundant? */ - if (unlikely(svol->posmap[lsi] != VVZ_POSMAP_INVALID)) + if (unlikely(svol->posmap[lsi] != VVZ_PSI_INVALID)) return -EINVAL; /* Create mapping */ @@ -208,7 +206,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p mutex_unlock(&sdev->slices_lock); /* Write posmap block to disk */ - err = store_posmap_block(svol, lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK); + err = store_posmap_block(svol, lsi / VVZ_PSIS_PER_BLOCK); if (err) { DMERR("Could not store posmap block; error %d", err); _delete_local_slice_mapping(svol, lsi); @@ -265,7 +263,7 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) for (lsi = 0; lsi < sdev->tot_slices; lsi++) { /* Reset dirty bit at the start of every posmap block */ - if (lsi % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) + if (lsi % VVZ_PSIS_PER_BLOCK == 0) posmap_block_dirty = false; /* De-serialise posmap entry */ @@ -273,14 +271,14 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) u32 psi = be32_to_cpu(*be_psi); /* If LSI unmapped, just continue */ - if (psi == VVZ_POSMAP_INVALID) { + if (psi == VVZ_PSI_INVALID) { svol->posmap[lsi] = psi; continue; } /* If PSI already taken, sample a new one */ if (sdev->slices_ofld[psi]) { - DMWARN("Corruption of volume %d: LSI %u was evicted from PSI %u", + DMWARN("Corruption of volume %lu: LSI %u was evicted from PSI %u", svol->vol_idx, lsi, psi); err = peek_next_free_psi(sdev, &psi); if (err) @@ -292,9 +290,9 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) /* Only check dirty bit at the end of the posmap block */ if (posmap_block_dirty && - (((lsi+1) % VVZ_POSMAP_ENTRIES_PER_BLOCK == 0) || + (((lsi+1) % VVZ_PSIS_PER_BLOCK == 0) || ((lsi+1) == sdev->tot_slices))) { - err = store_posmap_block(svol, lsi / VVZ_POSMAP_ENTRIES_PER_BLOCK); + err = store_posmap_block(svol, lsi / VVZ_PSIS_PER_BLOCK); if (err) return err; } @@ -316,7 +314,6 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) { int err; struct vvz_device *sdev = svol->sdev; - u32 lsi; /* Read raw posmap from disk */ err = read_encrypted_posmap(svol); diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 0ccb72f..f1799db 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -39,9 +39,6 @@ #define DM_MSG_PREFIX "vvz" -/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ -#define VVZ_POSMAP_ENTRIES_PER_BLOCK 1024 - /* *---------------------------- diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 24a9265..310bbd0 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -58,7 +58,9 @@ #define VVZ_MAX_DEVS 1024 -#define VVZ_POSMAP_INVALID 0xFFFFFFFF +#define VVZ_PSI_INVALID 0xFFFFFFFF +/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ +#define VVZ_PSIS_PER_BLOCK 1024 /* Sysfs entries under /sys/module/dm_vvz/ */ From 221d1337ee9162db3ee53b36b3977c769a38f291 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 20 Jan 2024 13:40:21 +0100 Subject: [PATCH 39/98] fix bug in posmap.c --- dm-vvz/posmap.c | 10 ++++++---- dm-vvz/vvz.h | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index fe2785d..03e194a 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -206,7 +206,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p mutex_unlock(&sdev->slices_lock); /* Write posmap block to disk */ - err = store_posmap_block(svol, lsi / VVZ_PSIS_PER_BLOCK); + err = store_posmap_block(svol, lsi/VVZ_PSIS_PER_BLOCK); if (err) { DMERR("Could not store posmap block; error %d", err); _delete_local_slice_mapping(svol, lsi); @@ -270,10 +270,10 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) __be32 *be_psi = (__be32*) (posmap_ptr + (lsi * sizeof(__be32))); u32 psi = be32_to_cpu(*be_psi); - /* If LSI unmapped, just continue */ + /* If LSI unmapped, skip mapping creation */ if (psi == VVZ_PSI_INVALID) { svol->posmap[lsi] = psi; - continue; + goto skip_create_mapping; } /* If PSI already taken, sample a new one */ @@ -288,11 +288,13 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) /* Either way, create the mapping locally */ _create_local_slice_mapping(svol, lsi, psi); +skip_create_mapping: + /* Only check dirty bit at the end of the posmap block */ if (posmap_block_dirty && (((lsi+1) % VVZ_PSIS_PER_BLOCK == 0) || ((lsi+1) == sdev->tot_slices))) { - err = store_posmap_block(svol, lsi / VVZ_PSIS_PER_BLOCK); + err = store_posmap_block(svol, lsi/VVZ_PSIS_PER_BLOCK); if (err) return err; } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index f1799db..dae2f87 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -46,8 +46,6 @@ *---------------------------- */ -struct vvz_volume; - struct vvz_device { /* Shufflecake-unique device ID */ From f2d3abd155dc792964aa022f308b3ec5e5d2f5bd Mon Sep 17 00:00:00 2001 From: = Date: Mon, 4 Mar 2024 22:11:21 +0100 Subject: [PATCH 40/98] Implement read --- dm-vvz/crypto.c | 8 +-- dm-vvz/device.c | 29 ++++++--- dm-vvz/posmap.c | 2 +- dm-vvz/read.c | 129 +++++++++++++++++++++++++++++++++++++++++ dm-vvz/vvz.c | 7 ++- dm-vvz/vvz.h | 18 +++++- dm-vvz/vvz_constants.h | 4 +- dm-vvz/write.c | 31 ++++++++++ 8 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 dm-vvz/read.c create mode 100644 dm-vvz/write.c diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index f318340..909cb50 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -67,16 +67,16 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, } /* Encrypt-decrypt a single block (memory buffer is a page) */ -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw) +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, unsigned src_off, + struct page *dst_page, unsigned dst_off, u64 pblk_num, int rw) { struct scatterlist dst, src; /* We assume PAGE_SIZE >= VVZ_BLOCK_SIZE TODO better document this */ sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); + sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, src_off); sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, dst_off); return crypt_sg(tfm, &src, &dst, pblk_num, rw); } diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 2320d93..cff41dc 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -106,13 +106,23 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) goto bad_dmio_client; } - /* Workqueue */ - sdev->wq = alloc_workqueue("vvz_%s", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, + /* I/O workqueue */ + sdev->io_queue = alloc_workqueue("vvz_%s_io", + WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->dm_dev->name); - if (!sdev->wq) { + if (!sdev->io_queue) { err = -ENOMEM; - DMERR("Could not allocate workqueue"); - goto bad_wq; + DMERR("Could not allocate I/O workqueue"); + goto bad_io_wq; + } + /* Decryption workqueue */ + sdev->crypt_queue = alloc_workqueue("vvz_%s_crypt", + WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, + 0, sdev->dm_dev->name); + if (!sdev->crypt_queue) { + err = -ENOMEM; + DMERR("Could not allocate decryption workqueue"); + goto bad_crypt_wq; } /* Register to sysfs, once initialised */ @@ -126,8 +136,10 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) bad_sysfs: - destroy_workqueue(sdev->wq); -bad_wq: + destroy_workqueue(sdev->crypt_queue); +bad_crypt_wq: + destroy_workqueue(sdev->io_queue); +bad_io_wq: dm_io_client_destroy(sdev->io_client); bad_dmio_client: bioset_exit(&sdev->bioset); @@ -144,7 +156,8 @@ bad_ofld: void vvz_dev_destroy(struct vvz_device *sdev) { vvz_sysfs_unregister_device(sdev); - destroy_workqueue(sdev->wq); + destroy_workqueue(sdev->crypt_queue); + destroy_workqueue(sdev->io_queue); dm_io_client_destroy(sdev->io_client); bioset_exit(&sdev->bioset); vfree(sdev->prmslices); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 03e194a..acb2c96 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -150,7 +150,7 @@ static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) kunmap_local(page_ptr); /* Encrypt the block in place */ - err = vvz_crypt_block_page(svol->tfm, page, page, + err = vvz_crypt_block_page(svol->tfm, page, 0, page, 0, bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); if (err) { DMERR("Could not encrypt posmap block; error %d", err); diff --git a/dm-vvz/read.c b/dm-vvz/read.c new file mode 100644 index 0000000..b86e452 --- /dev/null +++ b/dm-vvz/read.c @@ -0,0 +1,129 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "vvz.h" + + +static void vvz_read_endio(struct bio *phys_bio); +static void vvz_decrypt_work_fn(struct work_struct *work); + + +void vvz_read_work_fn(struct work_struct *work) +{ + struct vvz_io *sio = container_of(work, struct vvz_io, work); + struct vvz_volume *svol = sio->svol; + struct vvz_device *sdev = svol->sdev; + struct bio *orig_bio = sio->orig_bio; + struct bio *phys_bio; + u32 lsi = sio->lsi; + u32 block_offset = sio->block_offset; + u32 psi; + + /* Read position map */ + if (mutex_lock_interruptible(&svol->posmap_lock)) { + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + psi = svol->posmap[lsi]; + mutex_unlock(&svol->posmap_lock); + + /* If LSI is unmapped, short-circuit and return all zeros */ + if (psi == VVZ_PSI_INVALID) { + zero_fill_bio(orig_bio); + orig_bio->bi_status = BLK_STS_OK; + goto endio; + } + + /* Shallow-copy the bio and submit it (different bi_endio). + We can shallow-copy because we don't need to own the pages, + we can decrypt in place. */ + + /* Shallow copy */ + phys_bio = bio_alloc_clone(sdev->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); + if (!phys_bio) { + DMERR("Could not clone original bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + /* Insert in the I/O struct */ + sio->phys_bio = phys_bio; + + /* Remap sector */ + phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); + /* Set fields for the endio */ + phys_bio->bi_private = sio; + phys_bio->bi_end_io = vvz_read_endio; + /* Submit */ + dm_submit_bio_remap(orig_bio, phys_bio); + + return; + + +endio: + bio_endio(orig_bio); + return; +} + + +static void vvz_read_endio(struct bio *phys_bio) +{ + struct vvz_io *sio = phys_bio->bi_private; + + /* Can't decrypt here in ISR: submit to decryption workqueue. + * Can reuse the same work item, though, since it was popped out of the + * io_queue already */ + INIT_WORK(&sio->work, vvz_decrypt_work_fn); + queue_work(sio->svol->sdev->crypt_queue, &sio->work); +} + + +static void vvz_decrypt_work_fn(struct work_struct *work) +{ + struct vvz_io *sio = container_of(work, struct vvz_io, work); + struct vvz_volume *svol = sio->svol; + struct bio *orig_bio = sio->orig_bio; + struct bio *phys_bio = sio->phys_bio; + struct bio_vec bvl = bio_iovec(orig_bio); + blk_status_t status; + int err; + + /* Decrypt page in-place */ + err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_offset, bvl.bv_page, + bvl.bv_offset, phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, READ); + if (err) { + DMERR("Could not decrypt bio; error %d", err); + status = BLK_STS_IOERR; + goto endio; + } + + /* Advance original bio by one block */ + bio_advance(orig_bio, VVZ_BLOCK_SIZE); + +endio: + orig_bio->bi_status = status; + bio_endio(orig_bio); + /* Free the physical bio */ + bio_put(phys_bio); + + return; +} diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 0fbdbaa..9c0d00c 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -218,19 +218,22 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) { struct vvz_io *sio = dm_per_bio_data(bio, sizeof(struct vvz_io)); struct vvz_volume *svol = ti->private; + sector_t lblk_num = bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT; // TODO check flush /* Set fields */ - sio->orig_bio = bio; sio->svol = svol; + sio->orig_bio = bio; + sio->lsi = lblk_num >> VVZ_SLICE_SHIFT; + sio->block_offset = lblk_num & GENMASK(VVZ_SLICE_SHIFT - 1, 0); /* Enqueue */ if (bio_data_dir(bio) == READ) INIT_WORK(&sio->work, vvz_read_work_fn); else INIT_WORK(&sio->work, vvz_write_work_fn); - queue_work(svol->sdev->wq, &sio->work); + queue_work(svol->sdev->io_queue, &sio->work); return DM_MAPIO_SUBMITTED; } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index dae2f87..c8e3dc8 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -73,7 +73,8 @@ struct vvz_device /* Resource sharing */ struct bio_set bioset; struct dm_io_client *io_client; - struct workqueue_struct *wq; + struct workqueue_struct *io_queue; + struct workqueue_struct *crypt_queue; }; struct vvz_volume @@ -102,7 +103,11 @@ struct vvz_volume struct vvz_io { struct vvz_volume *svol; + struct bio *orig_bio; + struct bio *phys_bio; + u32 lsi; + u32 block_offset; struct work_struct work; }; @@ -120,6 +125,13 @@ struct vvz_io (svol)->vol_idx * (svol)->sdev->posmap_size_sectors) +/* Physical sector of a remapped bio */ +#define VVZ_PHYS_BIO_SECTOR(sdev, psi, off) ( \ + (sdev)->dev_header_size_sectors + ( \ + ((psi << VVZ_SLICE_SHIFT) + off) << VVZ_BLOCK_SHIFT \ + ) \ +) + /* *---------------------------- * Global variables @@ -165,7 +177,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p /* Crypto */ int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, u64 num_blocks, u64 first_pblk_num, int rw); -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw); +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, unsigned src_off, + struct page *dst_page, unsigned dst_off, u64 pblk_num, int rw); #endif /* _VVZ_H */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 310bbd0..16b1f73 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -45,7 +45,9 @@ #define VVZ_BLOCK_SIZE 4096 /* bytes */ #define VVZ_BLOCK_SHIFT 3 -#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* sectors in a block */ +#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* 8 sectors in a block */ +#define VVZ_SLICE_SHIFT 8 +#define VVZ_SLICE_SCALE (1 << VVZ_SLICE_SHIFT) /* 256 blocks in a slice */ /* XTS requires doubling the key size */ diff --git a/dm-vvz/write.c b/dm-vvz/write.c new file mode 100644 index 0000000..cefd0e6 --- /dev/null +++ b/dm-vvz/write.c @@ -0,0 +1,31 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "vvz.h" + + +void vvz_write_work_fn(struct work_struct *work) +{ + // TODO stub + return; +} From ad00a4b3fb50c0678b9c4fd5addd51fdc885eca0 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 5 Mar 2024 23:40:52 +0100 Subject: [PATCH 41/98] Implement write --- dm-vvz/read.c | 19 ++++++--- dm-vvz/write.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 6 deletions(-) diff --git a/dm-vvz/read.c b/dm-vvz/read.c index b86e452..8066f79 100644 --- a/dm-vvz/read.c +++ b/dm-vvz/read.c @@ -28,6 +28,7 @@ static void vvz_read_endio(struct bio *phys_bio); static void vvz_decrypt_work_fn(struct work_struct *work); +/* Landing here from ->map() through the io_queue */ void vvz_read_work_fn(struct work_struct *work) { struct vvz_io *sio = container_of(work, struct vvz_io, work); @@ -85,18 +86,20 @@ endio: } +/* ISR for the phys_bio */ static void vvz_read_endio(struct bio *phys_bio) { struct vvz_io *sio = phys_bio->bi_private; /* Can't decrypt here in ISR: submit to decryption workqueue. * Can reuse the same work item, though, since it was popped out of the - * io_queue already */ + * io_queue already */ INIT_WORK(&sio->work, vvz_decrypt_work_fn); queue_work(sio->svol->sdev->crypt_queue, &sio->work); } +/* Decrypt and endio */ static void vvz_decrypt_work_fn(struct work_struct *work) { struct vvz_io *sio = container_of(work, struct vvz_io, work); @@ -104,26 +107,32 @@ static void vvz_decrypt_work_fn(struct work_struct *work) struct bio *orig_bio = sio->orig_bio; struct bio *phys_bio = sio->phys_bio; struct bio_vec bvl = bio_iovec(orig_bio); - blk_status_t status; int err; + /* If physical bio failed, then fail-fast */ + if (phys_bio->bi_status != BLK_STS_OK) { + orig_bio->bi_status = phys_bio->bi_status; + goto endio; + } + /* Decrypt page in-place */ err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_offset, bvl.bv_page, bvl.bv_offset, phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, READ); if (err) { DMERR("Could not decrypt bio; error %d", err); - status = BLK_STS_IOERR; + orig_bio->bi_status = BLK_STS_IOERR; goto endio; } /* Advance original bio by one block */ bio_advance(orig_bio, VVZ_BLOCK_SIZE); + orig_bio->bi_status = BLK_STS_OK; endio: - orig_bio->bi_status = status; - bio_endio(orig_bio); /* Free the physical bio */ bio_put(phys_bio); + /* End original bio */ + bio_endio(orig_bio); return; } diff --git a/dm-vvz/write.c b/dm-vvz/write.c index cefd0e6..6bccc7c 100644 --- a/dm-vvz/write.c +++ b/dm-vvz/write.c @@ -24,8 +24,113 @@ #include "vvz.h" +static void vvz_write_endio(struct bio *phys_bio); + + void vvz_write_work_fn(struct work_struct *work) { - // TODO stub + struct vvz_io *sio = container_of(work, struct vvz_io, work); + struct vvz_volume *svol = sio->svol; + struct vvz_device *sdev = svol->sdev; + struct bio *orig_bio = sio->orig_bio; + struct bio_vec bvl = bio_iovec(orig_bio); + struct bio *phys_bio; + struct page *page; + u32 lsi = sio->lsi; + u32 block_offset = sio->block_offset; + u32 psi; + int err; + + /* Read existing mapping, or create new one */ + if (mutex_lock_interruptible(&svol->posmap_lock)) { + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + psi = svol->posmap[lsi]; + /* If LSI unmapped, create new mapping, while holding the lock */ + if (psi == VVZ_PSI_INVALID) { + err = vvz_create_persistent_slice_mapping(svol, lsi, &psi); + if (err){ + DMERR("Could not create slice mapping; error %d", err); + mutex_unlock(&svol->posmap_lock); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + } + mutex_unlock(&svol->posmap_lock); + + /* Allocate physical bio */ + phys_bio = bio_alloc_bioset(sdev->dm_dev->bdev, 1, orig_bio->bi_opf, + GFP_NOIO, &sdev->bioset); + if (!phys_bio) { + DMERR("Could not allocate physical bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + /* Insert in the I/O struct */ + sio->phys_bio = phys_bio; + + /* Physical bio needs its own page */ + page = alloc_pages(GFP_NOIO, 0); + if (!page) { + DMERR("Could not allocate page for physical bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto bad_alloc_page; + } + + /* Remap sector */ + phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); + /* Encrypt */ + err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_offset, page, 0, + phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); + if (err) { + DMERR("Could not encrypt bio; error %d", err); + orig_bio->bi_status = BLK_STS_IOERR; + goto bad_encrypt; + } + + /* Add page to bio */ + __bio_add_page(phys_bio, page, VVZ_BLOCK_SIZE, 0); + /* Set fields for the endio */ + phys_bio->bi_private = sio; + phys_bio->bi_end_io = vvz_write_endio; + /* Submit */ + dm_submit_bio_remap(orig_bio, phys_bio); + + return; + + +bad_encrypt: + __free_page(page); +bad_alloc_page: + bio_put(phys_bio); +endio: + bio_endio(orig_bio); return; } + +static void vvz_write_endio(struct bio *phys_bio) +{ + struct vvz_io *sio = phys_bio->bi_private; + struct bio *orig_bio = sio->orig_bio; + + /* If physical bio failed, then fail-fast */ + if (phys_bio->bi_status != BLK_STS_OK) { + orig_bio->bi_status = phys_bio->bi_status; + goto endio; + } + + /* Advance original bio by one block */ + bio_advance(orig_bio, VVZ_BLOCK_SIZE); + orig_bio->bi_status = BLK_STS_OK; + +endio: + /* Free the physical bio and its page */ + bio_free_pages(phys_bio); + bio_put(phys_bio); + /* End original bio */ + bio_endio(orig_bio); + + return; +} + From 8ac7b58e4017c174b389bd007880b6e0a3f33aa4 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 29 Mar 2024 23:32:09 +0100 Subject: [PATCH 42/98] Finish dm-vvz --- dm-vvz/crypto.c | 11 ++++++----- dm-vvz/posmap.c | 2 +- dm-vvz/read.c | 4 ++-- dm-vvz/vvz.c | 34 +++++++++++++++++++++++++++++++--- dm-vvz/vvz.h | 4 ++-- dm-vvz/write.c | 2 +- 6 files changed, 43 insertions(+), 14 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index 909cb50..be1e687 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -67,16 +67,17 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, } /* Encrypt-decrypt a single block (memory buffer is a page) */ -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, unsigned src_off, - struct page *dst_page, unsigned dst_off, u64 pblk_num, int rw) +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw) { struct scatterlist dst, src; - /* We assume PAGE_SIZE >= VVZ_BLOCK_SIZE TODO better document this */ + /* We assume PAGE_SIZE == VVZ_BLOCK_SIZE */ + /* And orig_bio to start at offset 0 within the page */ sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, src_off); + sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, dst_off); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); return crypt_sg(tfm, &src, &dst, pblk_num, rw); } diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index acb2c96..03e194a 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -150,7 +150,7 @@ static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) kunmap_local(page_ptr); /* Encrypt the block in place */ - err = vvz_crypt_block_page(svol->tfm, page, 0, page, 0, + err = vvz_crypt_block_page(svol->tfm, page, page, bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); if (err) { DMERR("Could not encrypt posmap block; error %d", err); diff --git a/dm-vvz/read.c b/dm-vvz/read.c index 8066f79..36f7761 100644 --- a/dm-vvz/read.c +++ b/dm-vvz/read.c @@ -116,8 +116,8 @@ static void vvz_decrypt_work_fn(struct work_struct *work) } /* Decrypt page in-place */ - err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_offset, bvl.bv_page, - bvl.bv_offset, phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, READ); + err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_page, + phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, READ); if (err) { DMERR("Could not decrypt bio; error %d", err); orig_bio->bi_status = BLK_STS_IOERR; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 9c0d00c..4367d95 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -220,9 +220,29 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) struct vvz_volume *svol = ti->private; sector_t lblk_num = bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT; - // TODO check flush + /* Accept one block at a time TODO improve */ + if (unlikely(bio->bi_iter.bi_size > VVZ_BLOCK_SIZE)) + dm_accept_partial_bio(bio, VVZ_BLOCK_SCALE); + /* Only one segment, single page, starting at 0 TODO improve */ + if (unlikely(bio_segments(bio) > 1 || + bio_offset(bio) != 0)) { + DMWARN("Unaligned bio!"); + return DM_MAPIO_KILL; + } - /* Set fields */ + /* Flush requests are just passed down, since our position map is + * currently write-through, so we have no volatile cache */ + if (unlikely(bio->bi_opf & REQ_PREFLUSH)) { + /* Has to be empty though */ + if (bio_sectors(bio)) { + DMWARN("Non-empty flush request!"); + return DM_MAPIO_KILL; + } + bio_set_dev(bio, svol->sdev->dm_dev->bdev); + return DM_MAPIO_REMAPPED; + } + + /* Init I/O struct */ sio->svol = svol; sio->orig_bio = bio; sio->lsi = lblk_num >> VVZ_SLICE_SHIFT; @@ -241,7 +261,7 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) { - // Currently, we only handle one block at a time + // Currently, we only handle one block at a time TODO improve limits->logical_block_size = VVZ_BLOCK_SIZE; limits->physical_block_size = VVZ_BLOCK_SIZE; limits->io_min = VVZ_BLOCK_SIZE; @@ -284,6 +304,13 @@ static int __init vvz_init(void) { int err; + /* For the moment, we assume PAGE_SIZE == VVZ_BLOCK_SIZE TODO improve */ + if (VVZ_BLOCK_SIZE != PAGE_SIZE) { + DMERR("Error, PAGE_SIZE != %d bytes not yet supported", VVZ_BLOCK_SIZE); + err = -ENOTRECOVERABLE; + goto bad_page_size; + } + vvz_alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*vvz_alldevs)); if (!vvz_alldevs) { DMERR("Could not allocate vvz_alldevs"); @@ -312,6 +339,7 @@ bad_register_target: bad_sysfs_init: vfree(vvz_alldevs); bad_alldevs_alloc: +bad_page_size: DMERR("not loaded"); return err; } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index c8e3dc8..a954761 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -177,7 +177,7 @@ int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *p /* Crypto */ int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, u64 num_blocks, u64 first_pblk_num, int rw); -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, unsigned src_off, - struct page *dst_page, unsigned dst_off, u64 pblk_num, int rw); +int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw); #endif /* _VVZ_H */ diff --git a/dm-vvz/write.c b/dm-vvz/write.c index 6bccc7c..79e5e7e 100644 --- a/dm-vvz/write.c +++ b/dm-vvz/write.c @@ -81,7 +81,7 @@ void vvz_write_work_fn(struct work_struct *work) /* Remap sector */ phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); /* Encrypt */ - err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_offset, page, 0, + err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, page, phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); if (err) { DMERR("Could not encrypt bio; error %d", err); From ccc05bee51954e88a0fb380198ce5e073a4dc420 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 30 Mar 2024 00:08:41 +0100 Subject: [PATCH 43/98] Copy new version of userland tool from main --- vuvuzela-userland/.gitignore | 11 + vuvuzela-userland/Makefile | 131 ++++++ vuvuzela-userland/Makefile.sources | 49 ++ vuvuzela-userland/include/cli.h | 65 +++ vuvuzela-userland/include/commands.h | 113 +++++ vuvuzela-userland/include/header.h | 161 +++++++ vuvuzela-userland/include/operations.h | 84 ++++ vuvuzela-userland/include/sflc_constants.h | 1 + vuvuzela-userland/include/utils/crypto.h | 103 +++++ vuvuzela-userland/include/utils/disk.h | 107 +++++ vuvuzela-userland/include/utils/dm.h | 56 +++ vuvuzela-userland/include/utils/file.h | 36 ++ vuvuzela-userland/include/utils/input.h | 46 ++ vuvuzela-userland/include/utils/log.h | 136 ++++++ vuvuzela-userland/include/utils/math.h | 36 ++ vuvuzela-userland/include/utils/sflc.h | 89 ++++ vuvuzela-userland/include/utils/string.h | 39 ++ vuvuzela-userland/src/cli/changepwd.c | 112 +++++ vuvuzela-userland/src/cli/close.c | 74 +++ vuvuzela-userland/src/cli/dispatch.c | 245 ++++++++++ vuvuzela-userland/src/cli/init.c | 118 +++++ vuvuzela-userland/src/cli/open.c | 77 ++++ vuvuzela-userland/src/cli/testpwd.c | 87 ++++ vuvuzela-userland/src/commands/change_pwd.c | 58 +++ vuvuzela-userland/src/commands/close.c | 178 ++++++++ vuvuzela-userland/src/commands/init.c | 202 +++++++++ vuvuzela-userland/src/commands/open.c | 183 ++++++++ vuvuzela-userland/src/commands/test_pwd.c | 59 +++ .../src/header/device_master_block.c | 276 ++++++++++++ vuvuzela-userland/src/header/position_map.c | 134 ++++++ .../src/header/volume_master_block.c | 223 +++++++++ vuvuzela-userland/src/main.c | 44 ++ vuvuzela-userland/src/operations/devmapper.c | 111 +++++ vuvuzela-userland/src/operations/dmb.c | 167 +++++++ .../src/operations/volume_header.c | 165 +++++++ vuvuzela-userland/src/utils/crypto.c | 424 ++++++++++++++++++ vuvuzela-userland/src/utils/disk.c | 257 +++++++++++ vuvuzela-userland/src/utils/dm.c | 203 +++++++++ vuvuzela-userland/src/utils/file.c | 81 ++++ vuvuzela-userland/src/utils/input.c | 101 +++++ vuvuzela-userland/src/utils/string.c | 74 +++ .../test/crypto/test_aes256ctr.c | 178 ++++++++ .../test/crypto/test_aes256ctr.h | 85 ++++ .../test/crypto/test_aes256gcm.c | 160 +++++++ .../test/crypto/test_aes256gcm.h | 91 ++++ vuvuzela-userland/test/crypto/test_argon2id.c | 83 ++++ vuvuzela-userland/test/crypto/test_argon2id.h | 43 ++ vuvuzela-userland/test/main.c | 80 ++++ vuvuzela-userland/test/minunit.h | 10 + 49 files changed, 5646 insertions(+) create mode 100644 vuvuzela-userland/.gitignore create mode 100644 vuvuzela-userland/Makefile create mode 100644 vuvuzela-userland/Makefile.sources create mode 100644 vuvuzela-userland/include/cli.h create mode 100644 vuvuzela-userland/include/commands.h create mode 100644 vuvuzela-userland/include/header.h create mode 100644 vuvuzela-userland/include/operations.h create mode 120000 vuvuzela-userland/include/sflc_constants.h create mode 100644 vuvuzela-userland/include/utils/crypto.h create mode 100644 vuvuzela-userland/include/utils/disk.h create mode 100644 vuvuzela-userland/include/utils/dm.h create mode 100644 vuvuzela-userland/include/utils/file.h create mode 100644 vuvuzela-userland/include/utils/input.h create mode 100644 vuvuzela-userland/include/utils/log.h create mode 100644 vuvuzela-userland/include/utils/math.h create mode 100644 vuvuzela-userland/include/utils/sflc.h create mode 100644 vuvuzela-userland/include/utils/string.h create mode 100644 vuvuzela-userland/src/cli/changepwd.c create mode 100644 vuvuzela-userland/src/cli/close.c create mode 100644 vuvuzela-userland/src/cli/dispatch.c create mode 100644 vuvuzela-userland/src/cli/init.c create mode 100644 vuvuzela-userland/src/cli/open.c create mode 100644 vuvuzela-userland/src/cli/testpwd.c create mode 100644 vuvuzela-userland/src/commands/change_pwd.c create mode 100644 vuvuzela-userland/src/commands/close.c create mode 100644 vuvuzela-userland/src/commands/init.c create mode 100644 vuvuzela-userland/src/commands/open.c create mode 100644 vuvuzela-userland/src/commands/test_pwd.c create mode 100644 vuvuzela-userland/src/header/device_master_block.c create mode 100644 vuvuzela-userland/src/header/position_map.c create mode 100644 vuvuzela-userland/src/header/volume_master_block.c create mode 100644 vuvuzela-userland/src/main.c create mode 100644 vuvuzela-userland/src/operations/devmapper.c create mode 100644 vuvuzela-userland/src/operations/dmb.c create mode 100644 vuvuzela-userland/src/operations/volume_header.c create mode 100644 vuvuzela-userland/src/utils/crypto.c create mode 100644 vuvuzela-userland/src/utils/disk.c create mode 100644 vuvuzela-userland/src/utils/dm.c create mode 100644 vuvuzela-userland/src/utils/file.c create mode 100644 vuvuzela-userland/src/utils/input.c create mode 100644 vuvuzela-userland/src/utils/string.c create mode 100644 vuvuzela-userland/test/crypto/test_aes256ctr.c create mode 100644 vuvuzela-userland/test/crypto/test_aes256ctr.h create mode 100644 vuvuzela-userland/test/crypto/test_aes256gcm.c create mode 100644 vuvuzela-userland/test/crypto/test_aes256gcm.h create mode 100644 vuvuzela-userland/test/crypto/test_argon2id.c create mode 100644 vuvuzela-userland/test/crypto/test_argon2id.h create mode 100644 vuvuzela-userland/test/main.c create mode 100644 vuvuzela-userland/test/minunit.h diff --git a/vuvuzela-userland/.gitignore b/vuvuzela-userland/.gitignore new file mode 100644 index 0000000..ca8a718 --- /dev/null +++ b/vuvuzela-userland/.gitignore @@ -0,0 +1,11 @@ +proj_build/ +test_build/ +.proj_deps/ +.test_deps/ +build/ +.cproject +.project +.settings/ +tests +shufflecake +disks/ diff --git a/vuvuzela-userland/Makefile b/vuvuzela-userland/Makefile new file mode 100644 index 0000000..ee59323 --- /dev/null +++ b/vuvuzela-userland/Makefile @@ -0,0 +1,131 @@ +# Copyright The Shufflecake Project Authors (2022) +# Copyright The Shufflecake Project Contributors (2022) +# Copyright Contributors to the The Shufflecake Project. + +# See the AUTHORS file at the top-level directory of this distribution and at +# + +# This file is part of the program shufflecake-c, which is part of the Shufflecake +# Project. Shufflecake is a plausible deniability (hidden storage) layer for +# Linux. See . + +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 2 of the License, or (at your option) +# any later version. This program 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 General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program. +# If not, see . + +############################################################################# +# Makefile with dependency auto-generation, taken and adapted from +# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +############################################################################# + +# Output dirs for binaries +BIN_DIR := bin +PROJ_OUT_DIR := $(BIN_DIR)/proj_build +TEST_OUT_DIR := $(BIN_DIR)/test_build +# Output dirs for dependency files +PROJ_DEP_DIR := $(BIN_DIR)/.proj_deps +TEST_DEP_DIR := $(BIN_DIR)/.test_deps + +# Include directories for compilation +INCLUDE := include test + +# Use gcc +CC := gcc +# All warnings, add includes (other options may be supplied on command line) +override CFLAGS += -Wall $(addprefix -I,$(INCLUDE)) +# Flags for dependency file auto-generation (deferred evaluation) +PROJ_DEPFLAGS = -MT $@ -MMD -MP -MF $(PROJ_DEP_DIR)/$*.d +TEST_DEPFLAGS = -MT $@ -MMD -MP -MF $(TEST_DEP_DIR)/$*.d +# Linker flags +LDFLAGS := -lgcrypt -ldevmapper + +# The variables PROJ_SRCS (and PROJ_ROOT) and TEST_SRCS (and TEST_ROOT) are defined in this Makefile +include Makefile.sources +# Create the three lists of object files +PROJ_OBJS := $(PROJ_SRCS:$(PROJ_ROOT)/%.c=$(PROJ_OUT_DIR)/%.o) +TEST_OBJS := $(TEST_SRCS:$(TEST_ROOT)/%.c=$(TEST_OUT_DIR)/%.o) +PROJ_OBJS_NO_MAIN := $(filter-out $(PROJ_OUT_DIR)/main.o,$(PROJ_OBJS)) +# Create the two lists of dependency files +PROJ_DEPS := $(PROJ_SRCS:$(PROJ_ROOT)/%.c=$(PROJ_DEP_DIR)/%.d) +TEST_DEPS := $(TEST_SRCS:$(TEST_ROOT)/%.c=$(TEST_DEP_DIR)/%.d) +# Put them together +DEPS := $(PROJ_DEPS) $(TEST_DEPS) + +# All directories to be created if non-existing (sort to remove duplicates) +DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) + +# The target binaries +MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake +TEST_BIN := $(TEST_OUT_DIR)/tests +# Their symlink +MAIN_LINK := shufflecake +TEST_LINK := tests + + + +#### +#### RULES +#### + + +.PHONY: main +main: $(MAIN_BIN) + +.PHONY: test +test: $(TEST_BIN) + +.PHONY: link_msg +link_msg: + @echo "Linking object files" + +.PHONY: compile_msg +compile_msg: + @echo "Compiling source files" + +# Link project object files +$(MAIN_BIN): $(PROJ_OBJS) | link_msg + @echo "\t---> $@" + @$(CC) $^ -o $@ $(LDFLAGS) + @rm -f $(MAIN_LINK) + @ln -s $@ $(MAIN_LINK) + +# Link test object files +$(TEST_BIN): $(PROJ_OBJS_NO_MAIN) $(TEST_OBJS) | link_msg + @echo "\t---> $@" + @$(CC) $^ -o $@ $(LDFLAGS) + @rm -f $(TEST_LINK) + @ln -s $@ $(TEST_LINK) + @echo "Done, launching tests" + @./$(TEST_LINK) + +# Cancel implicit rule +%.o : %.c + +# Build project object file +$(PROJ_OUT_DIR)/%.o : $(PROJ_ROOT)/%.c $(PROJ_DEP_DIR)/%.d | $(DIRS) compile_msg + @echo "\t---> $@" + @$(CC) $(PROJ_DEPFLAGS) $(CFLAGS) -c -o $@ $< + +# Build test object file +$(TEST_OUT_DIR)/%.o : $(TEST_ROOT)/%.c $(TEST_DEP_DIR)/%.d | $(DIRS) compile_msg + @echo "\t---> $@" + @$(CC) $(TEST_DEPFLAGS) $(CFLAGS) -c -o $@ $< + +# Create needed directories +$(DIRS): + @mkdir -p $@ + +.PHONY: clean +clean: + rm -rf $(PROJ_OUT_DIR) $(TEST_OUT_DIR) $(PROJ_DEP_DIR) $(TEST_DEP_DIR) + rm -rf $(BIN_DIR) + @rm -f $(MAIN_LINK) $(TEST_LINK) + +$(DEPS): +include $(wildcard $(DEPS)) diff --git a/vuvuzela-userland/Makefile.sources b/vuvuzela-userland/Makefile.sources new file mode 100644 index 0000000..0e2a80b --- /dev/null +++ b/vuvuzela-userland/Makefile.sources @@ -0,0 +1,49 @@ +# Copyright The Shufflecake Project Authors (2022) +# Copyright The Shufflecake Project Contributors (2022) +# Copyright Contributors to the The Shufflecake Project. + +# See the AUTHORS file at the top-level directory of this distribution and at +# + +# This file is part of the program shufflecake-c, which is part of the Shufflecake +# Project. Shufflecake is a plausible deniability (hidden storage) layer for +# Linux. See . + +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 2 of the License, or (at your option) +# any later version. This program 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 General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program. +# If not, see . + +######################################### +# Only define the sources to be compiled +######################################### + +#### +#### Main files +#### + +PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c) +PROJ_SRCS += $(addprefix header/,position_map.c volume_master_block.c device_master_block.c) +PROJ_SRCS += $(addprefix operations/,volume_header.c devmapper.c dmb.c) +PROJ_SRCS += $(addprefix commands/,init.c open.c close.c test_pwd.c change_pwd.c) +PROJ_SRCS += $(addprefix cli/,dispatch.c init.c open.c close.c testpwd.c changepwd.c) +PROJ_SRCS += main.c + +PROJ_ROOT := src +PROJ_SRCS := $(addprefix $(PROJ_ROOT)/,$(PROJ_SRCS)) + + +#### +#### Test files +#### + +TEST_SRCS := $(addprefix crypto/,test_aes256ctr.c test_aes256gcm.c test_argon2id.c) +TEST_SRCS += main.c + +TEST_ROOT := test +TEST_SRCS := $(addprefix $(TEST_ROOT)/,$(TEST_SRCS)) diff --git a/vuvuzela-userland/include/cli.h b/vuvuzela-userland/include/cli.h new file mode 100644 index 0000000..838f618 --- /dev/null +++ b/vuvuzela-userland/include/cli.h @@ -0,0 +1,65 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _CLI_H_ +#define _CLI_H_ + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* Action to create volumes */ +#define SFLC_CLI_INITACT "init" +/* Action to open volumes */ +#define SFLC_CLI_OPENACT "open" +/* Action to close volumes */ +#define SFLC_CLI_CLOSEACT "close" +/* Action to test password */ +#define SFLC_CLI_TESTPWDACT "testpwd" +/* Action to change password */ +#define SFLC_CLI_CHANGEPWDACT "changepwd" + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Called by the main to parse the arguments and dispatch to the right command */ +int sflc_cli_dispatch(int argc, char **argv); + +/* Initializes device and create empty volumes */ +int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill); +/* Open volumes */ +int sflc_cli_open(char *block_device); +/* Close volumes */ +int sflc_cli_close(char *block_device); +/* Test password */ +int sflc_cli_testPwd(char *block_device); +/* Change password */ +int sflc_cli_changePwd(char *block_device); + + +#endif /* _CLI_H_ */ + + diff --git a/vuvuzela-userland/include/commands.h b/vuvuzela-userland/include/commands.h new file mode 100644 index 0000000..68a14ac --- /dev/null +++ b/vuvuzela-userland/include/commands.h @@ -0,0 +1,113 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _COMMANDS_H_ +#define _COMMANDS_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "header.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + + +/***************************************************** + * STRUCTS * + *****************************************************/ + +/* Parameters for the init command */ +typedef struct +{ + /* Underlying block device */ + char *bdev_path; + + /* Number of volumes */ + size_t nr_vols; + /* Volumes' passwords */ + char **pwds; + size_t *pwd_lens; + + /* Option to skip random filling */ + bool no_randfill; + +} sflc_cmd_InitArgs; + + +/* Parameters for the open command */ +typedef struct +{ + /* Underlying block device */ + char *bdev_path; + + /* The only password provided */ + char *pwd; + size_t pwd_len; + +} sflc_cmd_OpenArgs; + +typedef struct +{ + /* Underlying block device */ + char *bdev_path; + + /* Content of the DMB cell */ + sflc_DmbCell *dmb_cell; + + /* The new password */ + char *new_pwd; + size_t new_pwd_len; + +} sflc_cmd_ChangePwdArgs; + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Create N volumes (only formats the device header, does not open the volumes) */ +int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args); + +/* Open M volumes, from the first down to the one whose pwd is provided */ +int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args); + +/* Close all volumes on the device (reads the list from sysfs) */ +int sflc_cmd_closeVolumes(char *bdev_path); + +/* Tests which volume is unlocked by the given password */ +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell); + +/* Changes the specified volume's password */ +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args); + + +#endif /* _COMMANDS_H_ */ diff --git a/vuvuzela-userland/include/header.h b/vuvuzela-userland/include/header.h new file mode 100644 index 0000000..869b868 --- /dev/null +++ b/vuvuzela-userland/include/header.h @@ -0,0 +1,161 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _HEADER_H_ +#define _HEADER_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "utils/crypto.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* The DMB contains one IV + one VMB key + one MAC for each volume */ +#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_CRYPTO_KEYLEN + SFLC_AESGCM_TAGLEN) + +/* Let us enforce that the one DMB can fit cells for all volumes */ +#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL) > SFLC_SECTOR_SIZE +#error "Invalid combination of parameters: probably SFLC_DEV_MAX_VOLUMES is too big" +#endif + + +// The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) +#define SFLC_CLEAR_VMB_LEN (SFLC_SECTOR_SIZE - \ + SFLC_AESGCM_PADDED_IVLEN - \ + SFLC_AESGCM_TAGLEN) + + + +/***************************************************** + * STRUCTS * + *****************************************************/ + +/** + * The on-disk master block of a device contains lots of crypto stuff + * (a KDF salt, IVs, MACs...) used to properly hide the VMB keys. + * This struct only contains such useful info, in the clear. + */ +typedef struct { + // Each volume's VMB key + char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_CRYPTO_KEYLEN]; + + // How many of these need actually be encrypted + size_t nr_vols; + +} sflc_Dmb; + + +/** + * When unsealing a DMB, only one VMB key can be unlocked with a password. + * An invalid value for vol_idx means no VMB key could be unlocked (wrong pwd) + */ +typedef struct { + // The unlocked VMB key + char vmb_key[SFLC_CRYPTO_KEYLEN]; + + // The index of the volume opened by this VMB key + size_t vol_idx; + +} sflc_DmbCell; + + +/** + * The on-disk master block of a volume contains crypto stuff + * (an IV) used to properly hide the useful info. This struct + * only contains the useful info, in the clear. + */ +typedef struct { + // The key that encrypts the volume's data section + char volume_key[SFLC_CRYPTO_KEYLEN]; + + // The key that encrypts the previous volume's master block + char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; + + // The total number of logical slices virtually available to this volume + size_t nr_slices; + +} sflc_Vmb; + + +/** + * This struct represents an encrypted empty position map. + * On-disk, the layout interleaves one IV block with 256 PosMap blocks (each + * encrypted by an IV in the IV block). Many such "runs" can be concatenated, + * until the position map is big enough to index the desired number of slices. + * The last "run" might be incomplete, in that it could have less than 256 + * PosMap blocks, if not all of them are needed. + * In the struct, there are as many IV blocks as there are PosMapBlock arrays + * (equal to the number of "runs"). The m-th IV of the n-th IV block encrypts + * the m-th block of the n-th array. The PosMapBlocks in an array are stored + * contiguously in RAM, so a PosMapBlock array is just a char array of length + * multiple of 4096. All the arrays are full (256 PosMapBlocks, 1 MiB) except + * for the last one, which may hold fewer blocks. + */ +typedef struct { + // The number of PosMapBlock arrays (and of IV blocks) + size_t nr_arrays; + + // The sequence of IV blocks + char **iv_blocks; + // The sequence of (encrypted) PosMapBlock arrays + char **pmb_arrays; + + // The number of PosMapBlocks in the last array + size_t nr_last_pmbs; + +} sflc_EncPosMap; + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); +/* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +/* Re-encrypt the content of a single DMB cell */ +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len); + + +/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ +int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +/* "Decrypt" a VMB coming from the disk, directly using its key */ +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); + + +/* Create an encrypted empty position map for the given number of slices (allocates memory) */ +int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm); + + + +#endif /* _HEADER_H_ */ diff --git a/vuvuzela-userland/include/operations.h b/vuvuzela-userland/include/operations.h new file mode 100644 index 0000000..8de9ab6 --- /dev/null +++ b/vuvuzela-userland/include/operations.h @@ -0,0 +1,84 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _OPERATIONS_H_ +#define _OPERATIONS_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "header.h" +#include "utils/crypto.h" +#include "utils/math.h" + + +/***************************************************** + * INLINE FUNCTIONS * + *****************************************************/ + +// Size, in 4096-byte blocks, of a whole volume header (VMB+PM) +static inline size_t sflc_volHeaderSize(size_t nr_slices) +{ + // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) + size_t nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + // Each array holds up to 256 PosMapBlocks + size_t nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); + + // 1 VMB, the PMBs, and the IV blocks + return 1 + nr_pmbs + nr_arrays; +} + +// Position of the VMB for the given volume +static inline uint64_t sflc_vmbPosition(size_t vol_idx, size_t nr_slices) +{ + return 1 + ((uint64_t) vol_idx) * ((uint64_t) sflc_volHeaderSize(nr_slices)); +} + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Encrypts and writes the DMB to disk */ +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb); +/* Reads the DMB from disk and outputs the unlocked VMB key */ +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +/* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); + +/* Encrypts and writes a volume header (VMB+PM) on-disk */ +int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +/* Reads a VMB from disk and unlocks it */ +int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); + +/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device */ +int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); +/* Close the volume via the appropriate ioctl to DM */ +int sflc_ops_closeVolume(char *label); + +#endif /* _OPERATIONS_H_ */ diff --git a/vuvuzela-userland/include/sflc_constants.h b/vuvuzela-userland/include/sflc_constants.h new file mode 120000 index 0000000..fab9e42 --- /dev/null +++ b/vuvuzela-userland/include/sflc_constants.h @@ -0,0 +1 @@ +../../dm-sflc/sflc_constants.h \ No newline at end of file diff --git a/vuvuzela-userland/include/utils/crypto.h b/vuvuzela-userland/include/utils/crypto.h new file mode 100644 index 0000000..15ac111 --- /dev/null +++ b/vuvuzela-userland/include/utils/crypto.h @@ -0,0 +1,103 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_CRYPTO_H_ +#define _UTILS_CRYPTO_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "utils/sflc.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +// Key length, for input into AES-CTR and AES-GCM, and for output from Argon +#define SFLC_CRYPTO_KEYLEN 32 /* bytes */ + +// IV length for AES-CTR +#define SFLC_AESCTR_IVLEN 16 /* bytes */ + +// IV length for AES-GCM +#define SFLC_AESGCM_IVLEN 12 /* bytes */ + +// IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM +#define SFLC_AESGCM_PADDED_IVLEN 16 /* bytes */ + +// MAC length for AES-GCM +#define SFLC_AESGCM_TAGLEN 16 /* bytes */ + +// Content of output plaintext upon MAC verification failure +#define SFLC_AESGCM_POISON_PT 0xFF + + +/* Argon parameters */ + +// Argon salt length +#define SFLC_ARGON_SALTLEN 16 /* bytes */ + +// Argon memory parameter +// We assume machines with at least 128 MiB available RAM, so 2^17 kiB +#define SFLC_ARGON_M (1 << 17) /* kibibytes */ + +// Argon iterations count +// We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) +#define SFLC_ARGON_T 3 + +// Argon parallelism parameter (recommended to be 2 * CPU cores) +// We assume use even on single core devices +#define SFLC_ARGON_P 2 + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Get slow, true random bytes (suited for keys) */ +int sflc_rand_getStrongBytes(char *buf, size_t buflen); +/* Get fast, pseudo random bytes (suited for IVs and padding) */ +int sflc_rand_getWeakBytes(char *buf, size_t buflen); + +/* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ +int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +/* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ +int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); + +/* AES256-GCM encryption, does not touch the IV */ +int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); +/* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ +int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); + +/* Compute Argon KDF */ +int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); + + +#endif /* _UTILS_CRYPTO_H_ */ diff --git a/vuvuzela-userland/include/utils/disk.h b/vuvuzela-userland/include/utils/disk.h new file mode 100644 index 0000000..7666609 --- /dev/null +++ b/vuvuzela-userland/include/utils/disk.h @@ -0,0 +1,107 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * Disk helper functions + */ + +#ifndef _UTILS_DISK_H_ +#define _UTILS_DISK_H_ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "utils/sflc.h" + + +/***************************************************** + * MACROS * + *****************************************************/ + +/** + * Max slices for given disk size (in 4096-byte blocks). + * + * The bigger a disk is, the more slices it can host. However, the more slices we format it with, + * the bigger the position map needed to index them: the header size grows with the number of slices, + * taking up part of the space that's supposed to host those slices. + * To settle the matter, let us derive an upper bound on the header size, yielding a "safe" value + * for the number of slices (given a disk size). + * + * To index s slices, we need pm := ceil(s/1024) <= s/1024 + 1 PosMap blocks, since each PosMap + * block (4096 bytes) can host 1024 slice indices (4 bytes each). + * + * To encrypt those PosMap blocks, we need iv := ceil(pm/256) <= pm IV blocks, since each IV block + * (4096 bytes) can encrypt 256 data blocks (IVs are 16 bytes). + * + * Therefore, a position map indexing s slices occupies pm+iv <= 2*pm <= 2*s/1024 + 2 blocks. + * + * A single volume's header contains the Volume Master Block and the position map, therefore it + * occupies 1+pm+iv <= 2*s/1024 + 3 blocks. + * + * The entire device's header simply contains 15 volume headers of the same size, therefore it + * occupies h := 15 * (1+pm+iv) <= 15*2*s/1024 + 3*15 <= s + 3*15 blocks. + * The last inequality follows from 15*2/1024 <= 1 (we need to enforce this on the symbolic values). + * + * To actually host the s slices, the data section needs 257*s blocks (256 data blocks + 1 IV block + * per slice). + * + * Therefore, in order to format a disk with s slices, we need at most (s + 3*15) + 257*s = + * = (1 + 257)*s + 3*15 blocks. + * + * If we are given d blocks on the disk, a safe value for s is one that satisfies + * (1 + 257)*s + 3*15 <= d <==> s <= (d - 3*15) / (1 + 257) + */ +#define sflc_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLC_BLOCKS_PER_PHYS_SLICE) + + +/* Let us enforce, on the symbolic values, the inequality used in the previous bound */ +#if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK +#error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" +#endif + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Checks whether the given path points to a block device */ +bool sflc_disk_isBlockDevice(char *path); + +/* Returns the size in 4096-byte sectors (or < 0 if error) */ +int64_t sflc_disk_getSize(char * bdev_path); + +/* Reads a single 4096-byte sector from the disk */ +int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf); + +/* Writes a single 4096-byte sector to the disk */ +int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf); + +/* Writes many 4096-byte sectors to the disk */ +int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors); + + +#endif /* _UTILS_DISK_H_ */ diff --git a/vuvuzela-userland/include/utils/dm.h b/vuvuzela-userland/include/utils/dm.h new file mode 100644 index 0000000..3334852 --- /dev/null +++ b/vuvuzela-userland/include/utils/dm.h @@ -0,0 +1,56 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * Interface to the device mapper + */ + +#ifndef _UTILS_DM_H_ +#define _UTILS_DM_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include + +#include "utils/sflc.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Create a new Shufflecake virtual device (volume) under /dev/mapper */ +int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); +/* Destroy the virtual device under /dev/mapper */ +int sflc_dm_destroy(char * virt_dev_name); + + +#endif /* _UTILS_DM_H_ */ diff --git a/vuvuzela-userland/include/utils/file.h b/vuvuzela-userland/include/utils/file.h new file mode 100644 index 0000000..fb03078 --- /dev/null +++ b/vuvuzela-userland/include/utils/file.h @@ -0,0 +1,36 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_FILE_H_ +#define _UTILS_FILE_H_ + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Malloc's the buffer for the file contents */ +char *sflc_readFile(char *path); + + +#endif /* _UTILS_FILE_H_ */ diff --git a/vuvuzela-userland/include/utils/input.h b/vuvuzela-userland/include/utils/input.h new file mode 100644 index 0000000..9463999 --- /dev/null +++ b/vuvuzela-userland/include/utils/input.h @@ -0,0 +1,46 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_INPUT_H_ +#define _UTILS_INPUT_H_ + + +/***************************************************** + * MACROS * + *****************************************************/ + +/* Clear a line from stdin, to use after a failed scanf (it didn't actually read input) */ +#define sflc_ignoreLine() scanf("%*[^\n]") + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Reads a line (discarding the newline) from stdin. No buffer overflow */ +int sflc_safeReadLine(char *buf, size_t bufsize); + +/* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ +int sflc_safeReadPassphrase(char *buf, size_t bufsize); + +#endif /* _UTILS_FILE_H_ */ diff --git a/vuvuzela-userland/include/utils/log.h b/vuvuzela-userland/include/utils/log.h new file mode 100644 index 0000000..2088fbb --- /dev/null +++ b/vuvuzela-userland/include/utils/log.h @@ -0,0 +1,136 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_LOG_H_ +#define _UTILS_LOG_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +// Printf colours (regular text) +#define SFLC_LOG_BLK "\033[0;30m" +#define SFLC_LOG_RED "\033[0;31m" +#define SFLC_LOG_GRN "\033[0;32m" +#define SFLC_LOG_YEL "\033[0;33m" +#define SFLC_LOG_BLU "\033[0;34m" +#define SFLC_LOG_MAG "\033[0;35m" +#define SFLC_LOG_CYN "\033[0;36m" +#define SFLC_LOG_WHT "\033[0;37m" +// Printf colours (bold text) +#define SFLC_LOG_BBLK "\033[1;30m" +#define SFLC_LOG_BRED "\033[1;31m" +#define SFLC_LOG_BGRN "\033[1;32m" +#define SFLC_LOG_BYEL "\033[1;33m" +#define SFLC_LOG_BBLU "\033[1;34m" +#define SFLC_LOG_BMAG "\033[1;35m" +#define SFLC_LOG_BCYN "\033[1;36m" +#define SFLC_LOG_BWHT "\033[1;37m" +// Reset colour +#define SFLC_LOG_RESET "\033[0m" + +// Log level: debug implies detailed logs +#ifdef CONFIG_SFLC_LOG_DEBUG +#define CONFIG_SFLC_LOG_DETAILED +#endif + + +/***************************************************** + * MACROS * + *****************************************************/ + +// Gives the point in the code where it was called +#define sflc_log_detailed(col, ...) do{ \ + printf(SFLC_LOG_GRN "FUNC " SFLC_LOG_RESET "%s() " \ + SFLC_LOG_GRN "FILE " SFLC_LOG_RESET "%s " \ + SFLC_LOG_GRN "LINE " SFLC_LOG_RESET "%d | ", \ + __func__, __FILE__, __LINE__); \ + sflc_log_concise(col, __VA_ARGS__); \ +}while(0) + +// Only writes using the given colour +#define sflc_log_concise(col, ...) do{ \ + printf(col); \ + printf(__VA_ARGS__); \ + printf(SFLC_LOG_RESET "\n"); \ +}while(0) + +// Maps to one or the other, based on a Makefile switch +#ifdef CONFIG_SFLC_LOG_DETAILED + #define sflc_log_colour(...) sflc_log_detailed(__VA_ARGS__) +#else + #define sflc_log_colour(...) sflc_log_concise(__VA_ARGS__) +#endif + +// Using specific colours +#define sflc_log_green(...) sflc_log_colour(SFLC_LOG_GRN, __VA_ARGS__) +#define sflc_log_red(...) sflc_log_colour(SFLC_LOG_RED, __VA_ARGS__) +#define sflc_log_yellow(...) sflc_log_colour(SFLC_LOG_YEL, __VA_ARGS__) +#define sflc_log_blue(...) sflc_log_colour(SFLC_LOG_BLU, __VA_ARGS__) +#define sflc_log_normal(...) sflc_log_colour(SFLC_LOG_RESET, __VA_ARGS__) + +// With log levels +#define sflc_log_error(...) sflc_log_colour(SFLC_LOG_RED, "[ERROR] " __VA_ARGS__) +#define sflc_log_warn(...) sflc_log_colour(SFLC_LOG_MAG, "[WARN] " __VA_ARGS__) +#ifdef CONFIG_SFLC_LOG_DEBUG + #define sflc_log_debug(...) sflc_log_colour(SFLC_LOG_CYN, "[DEBUG] " __VA_ARGS__) +#else + #define sflc_log_debug(...) +#endif + + +/***************************************************** + * INLINE FUNCTIONS * + *****************************************************/ + +// Log a hex string +static inline void sflc_log_hex(char *str, size_t len) +{ + int i; + unsigned char *s = (unsigned char *) str; + + for (i = 0; i < len; i++) { + printf("%02x ", s[i]); + // Nice aligned wrapping + if (i % 16 == 15) { + printf("\n"); + } + } + + // Always end with a newline + if (i % 16 != 0) { + printf("\n"); + } + + return; +} + +#endif /* _UTILS_LOG_H_ */ diff --git a/vuvuzela-userland/include/utils/math.h b/vuvuzela-userland/include/utils/math.h new file mode 100644 index 0000000..db5e397 --- /dev/null +++ b/vuvuzela-userland/include/utils/math.h @@ -0,0 +1,36 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_MATH_H_ +#define _UTILS_MATH_H_ + + +/***************************************************** + * MACROS * + *****************************************************/ + +// Function ceil(a/b) = floor((a+b-1)/b) +#define ceil(a, b) ( ((a)+(b)-1) / (b) ) + + +#endif /* _UTILS_MATH_H_ */ diff --git a/vuvuzela-userland/include/utils/sflc.h b/vuvuzela-userland/include/utils/sflc.h new file mode 100644 index 0000000..e8135e4 --- /dev/null +++ b/vuvuzela-userland/include/utils/sflc.h @@ -0,0 +1,89 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflc_constans.h + */ + +#ifndef _UTILS_SFLC_H_ +#define _UTILS_SFLC_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* Name of the DM target in the kernel */ +#define SFLC_DM_TARGET_NAME "shufflecake" + +/* Disk constants */ +#define SFLC_SECTOR_SIZE 4096 /* bytes */ +#define KERNEL_SECTOR_SIZE 512 /* bytes */ +#define SFLC_SECTOR_SCALE (SFLC_SECTOR_SIZE / KERNEL_SECTOR_SIZE) + +/* Max number of volumes in a device */ +#define SFLC_DEV_MAX_VOLUMES 15 + +/* Max total number of open devices at any given time */ +#define SFLC_TOT_MAX_DEVICES 1024 +/* A volume name is sflc__ */ +#define SFLC_MAX_VOL_NAME_LEN 15 + +/* A slice index is represented over 32 bits */ +#define SFLC_SLICE_IDX_WIDTH 4 /* bytes */ +/* A position map block contains 1024 slice indices */ +#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_SECTOR_SIZE / SFLC_SLICE_IDX_WIDTH) + +// IV length for AES-CTR +#define SFLC_AESCTR_IVLEN 16 /* bytes */ + +/* An IV block can encrypt 256 data blocks */ +#define SFLC_DATA_BLOCKS_PER_IV_BLOCK (SFLC_SECTOR_SIZE / SFLC_AESCTR_IVLEN) +/* A logical slice spans 256 blocks of data (1 MiB) */ +#define SFLC_BLOCKS_PER_LOG_SLICE SFLC_DATA_BLOCKS_PER_IV_BLOCK +/* A physical slice also includes the IV block */ +#define SFLC_BLOCKS_PER_PHYS_SLICE (1 + SFLC_BLOCKS_PER_LOG_SLICE) + +/* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ +#define SFLC_EPM_FILLER 0xFF + +/* The sysfs file containing the next available device ID */ +#define SFLC_SYSFS_NEXTDEVID "/sys/devices/sflc/next_dev_id" +/* The sysfs directory containing a subdir for each (underlying) block device */ +#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" +/* Within each bdev's subdir, this file lists its open volumes */ +#define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" + +/* TODO: reasonable? */ +#define SFLC_BDEV_PATH_MAX_LEN 1024 + +/* For when you can't be bothered to upper-bound a buffer size */ +#define SFLC_BIGBUFSIZE 4096 + + +#endif /* _UTILS_SFLC_H_ */ diff --git a/vuvuzela-userland/include/utils/string.h b/vuvuzela-userland/include/utils/string.h new file mode 100644 index 0000000..ca42c0e --- /dev/null +++ b/vuvuzela-userland/include/utils/string.h @@ -0,0 +1,39 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _UTILS_STRING_H_ +#define _UTILS_STRING_H_ + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Malloc's the buffer for the hex string */ +char *sflc_toHex(char *buf, size_t len); + +/* Replaces all occurrences of character in-place */ +void sflc_str_replaceAll(char *str, char old, char new); + + +#endif /* _UTILS_STRING_H_ */ diff --git a/vuvuzela-userland/src/cli/changepwd.c b/vuvuzela-userland/src/cli/changepwd.c new file mode 100644 index 0000000..de37779 --- /dev/null +++ b/vuvuzela-userland/src/cli/changepwd.c @@ -0,0 +1,112 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Change volume password + * + * @return Error code, 0 on success + */ +int sflc_cli_changePwd(char *block_device) +{ // Requires: block_device is a correct block device path + sflc_cmd_OpenArgs open_args; + sflc_cmd_ChangePwdArgs change_pwd_args; + sflc_DmbCell dmb_cell; + char old_pwd[SFLC_BIGBUFSIZE]; + size_t old_pwd_len; + char new_pwd[SFLC_BIGBUFSIZE]; + size_t new_pwd_len; + int err; + + open_args.bdev_path = block_device; + + /* Gather password */ + printf("Enter the password you want to change: "); + err = sflc_safeReadPassphrase(old_pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + old_pwd_len = strlen(old_pwd); + /* Assign fields */ + open_args.pwd = old_pwd; + open_args.pwd_len = old_pwd_len; + + /* Test the password */ + err = sflc_cmd_testPwd(&open_args, &dmb_cell); + if (err) { + sflc_log_error("Could not test password; error %d", err); + return err; + } + + /* Does this password open any volumes? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + printf("This password does not unlock any volume.\n"); + return 0; + } + + /* Gather new password (no secure shell) */ + printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); + printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); + err = sflc_safeReadLine(new_pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read new password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + new_pwd_len = strlen(new_pwd); + + /* Assign fields */ + change_pwd_args.bdev_path = block_device; + change_pwd_args.dmb_cell = &dmb_cell; + change_pwd_args.new_pwd = new_pwd; + change_pwd_args.new_pwd_len = new_pwd_len; + + /* Change password */ + err = sflc_cmd_changePwd(&change_pwd_args); + if (err) { + sflc_log_error("Could not change password; error %d", err); + return err; + } + printf("Password changed successfully.\n"); + return err; +} diff --git a/vuvuzela-userland/src/cli/close.c b/vuvuzela-userland/src/cli/close.c new file mode 100644 index 0000000..fdbaa28 --- /dev/null +++ b/vuvuzela-userland/src/cli/close.c @@ -0,0 +1,74 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Close volumes + * + * @return Error code, 0 on success + */ +int sflc_cli_close(char *block_device) +{ // Requires: block_device is a correct block device path +// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; +// int err; + +// /* Gather (absolute) path to underlying block device */ +// printf("Enter the absolute path to the underlying block device containing the Shufflecake volumes to close: "); +// err = sflc_safeReadLine(bdev_path, SFLC_BDEV_PATH_MAX_LEN + 2); +// if (err) { +// sflc_log_error("Could not read path to underlying block device; error %d", err); +// return err; +// } +// /* Check that it is absolute */ +// if (bdev_path[0] != '/') { +// printf("The path to the block device must be absolute"); +// return EINVAL; +// } +// + + + /* Actually perform the command */ +// return sflc_cmd_closeVolumes(bdev_path); + + + return sflc_cmd_closeVolumes(block_device); + +} diff --git a/vuvuzela-userland/src/cli/dispatch.c b/vuvuzela-userland/src/cli/dispatch.c new file mode 100644 index 0000000..01da250 --- /dev/null +++ b/vuvuzela-userland/src/cli/dispatch.c @@ -0,0 +1,245 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include + +#include "cli.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/log.h" +#include "sflc_constants.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* Used by argp to provide the automatic "-V" option */ +const char *argp_program_version = SFLC_VERSION; +/* Used by argp to provide the automatic "--help" option */ +const char *argp_program_bug_address = ""; + +/* Signed integer values representing a handle for each option */ +#define SFLC_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option +#define SFLC_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this + + +/***************************************************** + * TYPES * + *****************************************************/ + +enum sflc_cli_action { + SFLC_ACT_INIT, + SFLC_ACT_OPEN, + SFLC_ACT_CLOSE, + SFLC_ACT_TESTPWD, + SFLC_ACT_CHANGEPWD +}; + +struct sflc_cli_arguments { + enum sflc_cli_action act; + char *block_device; + int num_volumes; + bool skip_randfill; +}; + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +static error_t _parseArgpKey(int key, char *arg, struct argp_state *state); + + +/***************************************************** + * PRIVATE VARIABLES * + *****************************************************/ + +/* Doc strings */ +static char args_doc[] = "ACTION "; +static char doc[] = + "Shufflecake is a plausible deniability (hidden storage) layer for Linux.\n" + "See official website at for more info.\n" + "Possible values for mandatory ACTION are:\n\n" + + "\tinit:\t\tInitialise a block device for Shufflecake use, formatting\n" + "\t\t\theaders with provided passwords and overwriting with random\n" + "\t\t\tdata. WARNING: THIS WILL ERASE THE CONTENT OF THE DEVICE.\n\n" + + "\topen:\t\tOpen a hierarchy of Shufflecake volumes within a device by\n" + "\t\t\tasking a single user password. Virtual devices will appear\n" + "\t\t\tin /dev/mapper. Notice: freshly created device must be\n" + "\t\t\tuser-formatted and mounted in order to be used.\n\n" + + "\tclose:\t\tClose all open Shufflecake volumes supported by given\n" + "\t\t\tdevice.\n\n" + + "\ttestpwd:\tTest whether a given password unlocks any volume within\n" + "\t\t\tthe block device and, if so, show its index.\n\n" + + "\tchangepwd:\tChange the password unlocking a certain volume.\n\n" + + "Possible options are:"; + +/* Description of each option */ +static struct argp_option options[] = { + {"num-volumes", SFLC_OPT_NUMVOLS_KEY, "num", 0, + "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 + {"skip-randfill", SFLC_OPT_SKIPRAND_KEY, 0, 0, + "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, + {0} +}; + +/* Wrapper containing everything */ +static struct argp argp = {options, _parseArgpKey, args_doc, doc}; + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * The following is the main dispatch function called by the main to parse + * the arguments and dispatch to the right command. + * + * @param argc The number of command-line arguments supplied to the main + * @param argv The arguments + * + * @return Error code, 0 on success + */ + +int sflc_cli_dispatch(int argc, char **argv) { + struct sflc_cli_arguments arguments; + + arguments.act = -1; + arguments.block_device = NULL; + arguments.num_volumes = 0; + arguments.skip_randfill = false; + + /* Parse */ + argp_parse(&argp, argc, argv, 0, 0, &arguments); + + /* Check options consistency */ + if (arguments.num_volumes && arguments.act != SFLC_ACT_INIT) { + printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); + return EINVAL; + } + /* Check options consistency */ + if (arguments.skip_randfill && arguments.act != SFLC_ACT_INIT) { + printf("Error: --skip-randfill can only be combined with `init'.\n"); + return EINVAL; + } + /* Check that input is actually a block device */ + if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflc_disk_isBlockDevice(arguments.block_device)) { + printf("Error: '%s' is not a valid block device.\n", arguments.block_device); + return EINVAL; + } + + /* Dispatch to specific command */ + if (arguments.act == SFLC_ACT_INIT) { + return sflc_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); + } + if (arguments.act == SFLC_ACT_OPEN) { + return sflc_cli_open(arguments.block_device); + } + if (arguments.act == SFLC_ACT_CLOSE) { + return sflc_cli_close(arguments.block_device); + } + if (arguments.act == SFLC_ACT_TESTPWD) { + return sflc_cli_testPwd(arguments.block_device); + } + if (arguments.act == SFLC_ACT_CHANGEPWD) { + return sflc_cli_changePwd(arguments.block_device); + } + + printf("\n"); + + return EINVAL; +} + + +/***************************************************** + * PRIVATE FUNCTIONS DEFINITIONS * + *****************************************************/ + +static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { + struct sflc_cli_arguments *arguments = state->input; + + switch (key) { + /* We are parsing an argument (not an option) */ + case ARGP_KEY_ARG: + /* We are parsing the command */ + if (state->arg_num == 0) { + if (strcmp(arg, SFLC_CLI_INITACT) == 0) { + arguments->act = SFLC_ACT_INIT; + } else if (strcmp(arg, SFLC_CLI_OPENACT) == 0) { + arguments->act = SFLC_ACT_OPEN; + } else if (strcmp(arg, SFLC_CLI_CLOSEACT) == 0) { + arguments->act = SFLC_ACT_CLOSE; + } else if (strcmp(arg, SFLC_CLI_TESTPWDACT) == 0) { + arguments->act = SFLC_ACT_TESTPWD; + } else if (strcmp(arg, SFLC_CLI_CHANGEPWDACT) == 0) { + arguments->act = SFLC_ACT_CHANGEPWD; + } else { + argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", + SFLC_CLI_INITACT, SFLC_CLI_OPENACT, SFLC_CLI_CLOSEACT, SFLC_CLI_TESTPWDACT, SFLC_CLI_CHANGEPWDACT); + } + /* We are parsing the block device */ + } else if (state->arg_num == 1) { + arguments->block_device = arg; + /* Too many arguments */ + } else { + argp_usage(state); + } + break; + + /* We are parsing an option */ + case SFLC_OPT_NUMVOLS_KEY: + arguments->num_volumes = atoi(arg); + break; + case SFLC_OPT_SKIPRAND_KEY: + arguments->skip_randfill = true; + break; + + /* End of arg list */ + case ARGP_KEY_END: + if (state->arg_num < 2) { + argp_usage(state); + } + break; + + /* Unrecognised key */ + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} diff --git a/vuvuzela-userland/src/cli/init.c b/vuvuzela-userland/src/cli/init.c new file mode 100644 index 0000000..302a96d --- /dev/null +++ b/vuvuzela-userland/src/cli/init.c @@ -0,0 +1,118 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Create volumes + * + * @return Error code, 0 on success + */ +int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) +{ // Requires: block_device is a correct block device path + sflc_cmd_InitArgs args; + char str_nrvols[SFLC_BIGBUFSIZE]; + char *pwds[SFLC_DEV_MAX_VOLUMES]; + size_t pwd_lens[SFLC_DEV_MAX_VOLUMES]; + int err; + + args.bdev_path = block_device; + + // Check if number of volumes was nonzero passed by command line already + if (num_volumes) { + args.nr_vols = num_volumes; + } else { + // If not, ask user for number of volumes + printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLC_DEV_MAX_VOLUMES); + err = sflc_safeReadLine(str_nrvols, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Error: could not read number of volumes; error %d", err); + return err; + } + /* Parse string */ + if (sscanf(str_nrvols, "%lu\n", &args.nr_vols) != 1) { + sflc_log_error("Error: could not parse number of volumes"); + return EINVAL; + } + } + + /* Bounds check */ + if (args.nr_vols <= 0) { + printf("Error: number of volumes must be a positive integer"); + return EINVAL; + } + if (args.nr_vols > SFLC_DEV_MAX_VOLUMES) { + printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLC_DEV_MAX_VOLUMES); + return EINVAL; + } + + /* Collects the passwords */ + printf("\nNow you will be asked to insert the passwords for all the volumes you want to create, \nfrom " + "volume 0 (the least secret) to volume %lu (the most secret).\n\n", args.nr_vols - 1); + size_t i; + for (i = 0; i < args.nr_vols; i++) { + // Allocate pwd + pwds[i] = malloc(SFLC_BIGBUFSIZE); + + /* Read it */ + printf("Choose password for volume %lu (must not be empty): ", i); + err = sflc_safeReadLine(pwds[i], SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password for volume %lu; error %d", i, err); + return err; + } + + /* You can trust the length of strings input this way */ + pwd_lens[i] = strlen(pwds[i]); + /* Check non-empty */ + if (pwd_lens[i] == 0) { + sflc_log_error("Password cannot be empty!"); + return EINVAL; + } + } + /* Assign them */ + args.pwds = pwds; + args.pwd_lens = pwd_lens; + + args.no_randfill = skip_randfill; + + /* Actually perform the command */ + return sflc_cmd_initVolumes(&args); +} diff --git a/vuvuzela-userland/src/cli/open.c b/vuvuzela-userland/src/cli/open.c new file mode 100644 index 0000000..45464c9 --- /dev/null +++ b/vuvuzela-userland/src/cli/open.c @@ -0,0 +1,77 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Open volumes + * + * @return Error code, 0 on success + */ +int sflc_cli_open(char *block_device) +{ // Requires: block_device is a correct block device path + sflc_cmd_OpenArgs args; + char pwd[SFLC_BIGBUFSIZE]; + size_t pwd_len; + int err; + + args.bdev_path = block_device; + + /* Gather password */ + printf("Enter the password for the most secret volume you want to open: "); + err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + pwd_len = strlen(pwd); + /* Check non-empty */ + if (pwd_len == 0) { + sflc_log_error("Password cannot be empty!"); + return EINVAL; + } + /* Assign them */ + args.pwd = pwd; + args.pwd_len = pwd_len; + + /* Actually perform the command */ + return sflc_cmd_openVolumes(&args); +} diff --git a/vuvuzela-userland/src/cli/testpwd.c b/vuvuzela-userland/src/cli/testpwd.c new file mode 100644 index 0000000..550df25 --- /dev/null +++ b/vuvuzela-userland/src/cli/testpwd.c @@ -0,0 +1,87 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Test volume password + * + * @return Error code, 0 on success + */ +int sflc_cli_testPwd(char *block_device) +{ // Requires: block_device is a correct block device path + sflc_cmd_OpenArgs args; + sflc_DmbCell dmb_cell; +// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; + char pwd[SFLC_BIGBUFSIZE]; + size_t pwd_len; + int err; + + args.bdev_path = block_device; + + /* Gather password */ + printf("Enter the password you want to test: "); + err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + pwd_len = strlen(pwd); + /* Assign them */ + args.pwd = pwd; + args.pwd_len = pwd_len; + + /* Actually perform the command */ + err = sflc_cmd_testPwd(&args, &dmb_cell); + if (err) { + sflc_log_error("Could not test password; error %d", err); + return err; + } + + /* Does this password open any volumes? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + printf("This password does not unlock any volume.\n"); + } else { + printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); + } + + return 0; +} diff --git a/vuvuzela-userland/src/commands/change_pwd.c b/vuvuzela-userland/src/commands/change_pwd.c new file mode 100644 index 0000000..2ef64e0 --- /dev/null +++ b/vuvuzela-userland/src/commands/change_pwd.c @@ -0,0 +1,58 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/file.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Changes the specified volume's password. + * + * @param args->bdev_path The underlying block device + * @param args->dmb_cell The DMB cell to re-encrypt + * @param args->new_pwd The new password + * @param args->new_pwd_len Its length + * + * @return Error code, 0 on success + */ +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args) +{ + /* Delegate entirely to the function reading the DMB */ + return sflc_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); +} diff --git a/vuvuzela-userland/src/commands/close.c b/vuvuzela-userland/src/commands/close.c new file mode 100644 index 0000000..4c180f6 --- /dev/null +++ b/vuvuzela-userland/src/commands/close.c @@ -0,0 +1,178 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/string.h" +#include "utils/file.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Reads the list of volumes from sysfs */ +static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols); + +/* Close them all (in reverse order of opening) */ +static int _closeVolumes(char **labels, size_t nr_vols); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Close all volumes on the device (reads the list from sysfs) + * + * @param bdev_path The path to the underlying block device + * + * @return Error code, 0 on success + */ +int sflc_cmd_closeVolumes(char *bdev_path) +{ + char *labels[SFLC_DEV_MAX_VOLUMES]; + size_t nr_vols; + int err; + + /* Allocate labels */ + size_t i; + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + labels[i] = malloc(SFLC_MAX_VOL_NAME_LEN + 1); + if (!labels[1]) { + sflc_log_error("Could not allocate volume label %lu", i); + return ENOMEM; // Do not free the ones already allocated + } + } + + /* Read them */ + err = _readVolumesList(bdev_path, labels, &nr_vols); + if (err) { + sflc_log_error("Could not read volume list from sysfs; error %d", err); + goto out; + } + + /* Close the volumes (in reverse order of opening) */ + err = _closeVolumes(labels, nr_vols); + if (err) { + sflc_log_error("Could not close volumes; error %d", err); + goto out; + } + + /* No prob */ + err = 0; + + +out: + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + free(labels[i]); + } + return err; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Reads the list of volumes from sysfs */ +static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) +{ + char bdev_path_noslash[SFLC_BDEV_PATH_MAX_LEN + 1]; + char openvolumes_path[SFLC_BIGBUFSIZE]; + char *str_openvolumes; + + /* Remove the slashes from the bdev_path (replace with underscores) */ + strcpy(bdev_path_noslash, bdev_path); + sflc_str_replaceAll(bdev_path_noslash, '/', '_'); + /* Build path to sysfsy file containing open volumes list */ + sprintf(openvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_path_noslash, SFLC_SYSFS_OPENVOLUMES_FILENAME); + + /* Read the sysfs file */ + str_openvolumes = sflc_readFile(openvolumes_path); + if (!str_openvolumes) { + sflc_log_error("Could not read file %s", openvolumes_path); + return EBADF; + } + + /* Parse the number of volumes */ + char *endptr; + *nr_vols = strtoul(str_openvolumes, &endptr, 10); + /* Skip past the number of volumes (lands on a whitespace before the first label) */ + str_openvolumes = endptr; + + /* Just to be sure */ + if (*nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); + return EBADF; + } + + /* Read labels */ + size_t i; + for (i = 0; i < *nr_vols; i++) { + /* Trust the content of the sysfs file */ + if (sscanf(str_openvolumes, " %s", labels[i]) != 1) { + sflc_log_error("Could not read volume label %lu. Sysfs content:\n%s", i, str_openvolumes); + return EBADF; + } + sflc_log_debug("Label %lu to close: %s", i, labels[i]); + + /* Skip past the whitespace and the label */ + str_openvolumes += 1 + strlen(labels[i]); + } + + return 0; +} + + +/* Close them all (in reverse order of opening) */ +static int _closeVolumes(char **labels, size_t nr_vols) +{ + int err; + + /* Eazy peazy */ + int i; + for (i = nr_vols-1; i >= 0; i--) { + err = sflc_ops_closeVolume(labels[i]); + if (err) { + sflc_log_error("Could not close volume %s; error %d", labels[i], err); + return err; + } + sflc_log_debug("Closed volume %s", labels[i]); + printf("Closed volume /dev/mapper/%s\n", labels[i]); + } + + return 0; +} + diff --git a/vuvuzela-userland/src/commands/init.c b/vuvuzela-userland/src/commands/init.c new file mode 100644 index 0000000..cd21b03 --- /dev/null +++ b/vuvuzela-userland/src/commands/init.c @@ -0,0 +1,202 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* The device is randomised in chunks of 1024 blocks (arbitrary number) */ +#define SFLC_BLOCKS_IN_RAND_CHUNK 1024 +/* That's 4 MiB */ +#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_SECTOR_SIZE) + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Fill the device with random data */ +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Create N volumes (only formats the device header, does not open the volumes). + * Creates them in order from 0 to N-1, so as to induce a back-linked list on the device. + * + * @param args->bdev_path The path to the underlying block device + * @param args->nr_vols The number of volumes to create + * @param args->pwds The array of passwords for the various volumes + * @param args->pwd_lens The length of each password + * @param args->no_randfill A boolean switch indicating that the volume should not + * be filled entirely with random data prior to formatting. + * + * @return Error code, 0 on success + */ +int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) +{ + sflc_Dmb dmb; + sflc_Vmb vmb; + int64_t dev_size; + size_t nr_slices; + int err; + + /* Sanity check */ + if (args->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create %lu volumes on a single device", args->nr_vols); + return EINVAL; + } + + /* Get device size */ + dev_size = sflc_disk_getSize(args->bdev_path); + if (dev_size < 0) { + err = -dev_size; + sflc_log_error("Could not get device size for %s; error %d", args->bdev_path, err); + return err; + } + /* Convert to number of slices */ + nr_slices = sflc_disk_maxSlices(dev_size); + sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); + + /* Fill disk with random bytes, if requested */ + if (!args->no_randfill) { + err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); + if (err) { + sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); + return err; + } + } + + /* Fill the DMB */ + dmb.nr_vols = args->nr_vols; + /* Sample the VMB keys */ + size_t i; + for (i = 0; i < dmb.nr_vols; i++) { + err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); + return err; + } + } + /* And write (encrypted) to disk */ + err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + if (err) { + sflc_log_error("Could not create DMB and write it to disk; error %d", err); + return err; + } + + /* Write the volume headers */ + vmb.nr_slices = nr_slices; + for (i = 0; i < args->nr_vols; i++) { + /* This volume's prev_vmb_key */ + if (i > 0) { + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_CRYPTO_KEYLEN); + } + /* Sample this volume's VEK */ + sflc_rand_getStrongBytes(vmb.volume_key, SFLC_CRYPTO_KEYLEN); + + /* Write complete volume header (VMB + PM) */ + err = sflc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); + if (err) { + sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); + return err; + } + } + printf("Created %lu volumes on device %s\n", args->nr_vols, args->bdev_path); + + return 0; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Fill the device with random data */ +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) +{ + char *rand_chunk; + int err; + + /* Allocate chunk */ + rand_chunk = malloc(SFLC_RAND_CHUNK_SIZE); + if (!rand_chunk) { + sflc_log_error("Could not allocate %d bytes for chunk of random data", SFLC_RAND_CHUNK_SIZE); + return ENOMEM; + } + + /* Loop to write random data in chunks */ + uint64_t blocks_remaining = dev_size; + uint64_t sector = 0; + while (blocks_remaining > 0) { + uint64_t blocks_to_write = + (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; + uint64_t bytes_to_write = blocks_to_write * SFLC_SECTOR_SIZE; + + /* Sample random bytes */ + err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); + if (err) { + sflc_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); + goto out; + } + + /* Write on disk */ + err = sflc_disk_writeManySectors(bdev_path, sector, rand_chunk, blocks_to_write); + if (err) { + sflc_log_error("Could not write random bytes on disk; error %d", err); + goto out; + } + + /* Advance loop */ + sector += blocks_to_write; + blocks_remaining -= blocks_to_write; + } + + /* No prob */ + err = 0; + + +out: + free(rand_chunk); + return err; +} + diff --git a/vuvuzela-userland/src/commands/open.c b/vuvuzela-userland/src/commands/open.c new file mode 100644 index 0000000..6c58225 --- /dev/null +++ b/vuvuzela-userland/src/commands/open.c @@ -0,0 +1,183 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/disk.h" +#include "utils/file.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Read the next device ID in sysfs */ +static int _getNextDevId(size_t *next_dev_id); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Open M volumes, from the first one down to the one whose pwd is provided. + * Scans the DMB cells to find which one is unlocked by the provided pwd; then, + * using the decrypted VMB key, unlocks the M-th VMB; then, iteratively using + * the prev_vmb_key field, unlocks all the previous VMBs; then, using the + * decrypted VMB keys, opens the volumes "in order" from 1 to M. + * + * @param args->bdev_path The underlying block device + * @param args->pwd The password + * @param args->pwd_len The password length + * + * @return Error code (also if no volume could be opened), 0 on success + */ +int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) +{ + int64_t dev_size; + size_t nr_slices; + sflc_DmbCell dmb_cell; + sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; + size_t dev_id; + int err; + + /* Get number of slices */ + dev_size = sflc_disk_getSize(args->bdev_path); + if (dev_size < 0) { + err = -dev_size; + sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); + return err; + } + nr_slices = sflc_disk_maxSlices(dev_size); + + /* Find volume opened by the pwd */ + err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); + if (err) { + sflc_log_error("Could not read DMB; error %d", err); + return err; + } + /* Was there one? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("The provided password opens no volume on the device"); + return EINVAL; + } + printf("Password is correct! Opening volumes...\n"); + + /* Unlock VMBs "backwards" */ + int i; // Needs sign, because loop ends on i>=0 + for (i = dmb_cell.vol_idx; i >= 0; i--) { + /* Which VMB key to use? */ + char *vmb_key; + if (i == dmb_cell.vol_idx) { + // The one unlocked by pwd + vmb_key = dmb_cell.vmb_key; + } else { + // Or the prev_vmb_key from last iteration + vmb_key = vmbs[i+1].prev_vmb_key; + } + + /* Read and unlock VMB */ + err = sflc_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not read VMB %d on device %s; error %d", + i, args->bdev_path, err); + return err; + } + } + + /* Get the ID that will be assigned to the block device */ + err = _getNextDevId(&dev_id); + if (err) { + sflc_log_error("Could not get next device ID; error %d", err); + return err; + } + sflc_log_debug("Next device ID is %lu", dev_id); + + /* Open volumes "in order" */ + for (i = 0; i <= dmb_cell.vol_idx; i++) { + err = sflc_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not open volume %d; error %d. " + "Previous volumes on the device might have already " + "been opened, it's recommended you close them", + i, err); + return err; + } + sflc_log_debug("Successfully opened volume %d with VMB key", i); + printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); + } + + return 0; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Read the next device ID in sysfs */ +static int _getNextDevId(size_t *next_dev_id) +{ + char *str_nextdevid; + int err; + + /* Read sysfs entry */ + str_nextdevid = sflc_readFile(SFLC_SYSFS_NEXTDEVID); + if (!str_nextdevid) { + sflc_log_error("Could not read sysfs entry %s", SFLC_SYSFS_NEXTDEVID); + return EINVAL; + } + + /* Parse integer */ + if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { + sflc_log_error("Error parsing content of file %s", SFLC_SYSFS_NEXTDEVID); + err = EINVAL; + goto err_devid; + } + /* Sanity check */ + if (*next_dev_id >= SFLC_TOT_MAX_DEVICES) { + sflc_log_error("There are already %d open devices, this is the maximum allowed", SFLC_TOT_MAX_DEVICES); + err = E2BIG; + goto err_devid; + } + + /* All good */ + err = 0; + + +err_devid: + free(str_nextdevid); + return err; +} + diff --git a/vuvuzela-userland/src/commands/test_pwd.c b/vuvuzela-userland/src/commands/test_pwd.c new file mode 100644 index 0000000..f794347 --- /dev/null +++ b/vuvuzela-userland/src/commands/test_pwd.c @@ -0,0 +1,59 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/file.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Tests which volume is unlocked by the given password + * + * @param args->bdev_path The underlying block device + * @param pwd The password + * @param pwd_len The password length + * + * @output dmb_cell The unlocked DMB cell + * + * @return Error code, 0 on success + */ +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell) +{ + /* Delegate entirely to the function reading the DMB */ + return sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); +} diff --git a/vuvuzela-userland/src/header/device_master_block.c b/vuvuzela-userland/src/header/device_master_block.c new file mode 100644 index 0000000..cdd1f8c --- /dev/null +++ b/vuvuzela-userland/src/header/device_master_block.c @@ -0,0 +1,276 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "header.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* AES-GCM-encrypt a VMB key with the KDF-generated key */ +static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vmb_key, char *dmb_cell); + +/* AES-GCM-decrypt a VMB key with the KDF-generated key */ +static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool *match); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Builds the on-disk device master block (indistinguishable from random). + * + * @param dmb The list of VMB keys + * @param pwds The passwords of the volumes + * @param pwd_lens Their lengths + * @param disk_block The 4096-byte buffer that will contain the random-looking + * bytes to be written on-disk + * + * @return The error code, 0 on success + */ +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) +{ + char *salt; + int err; + + /* Sanity check */ + if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); + return EINVAL; + } + + /* Randomise whole block */ + err = sflc_rand_getWeakBytes(disk_block, SFLC_SECTOR_SIZE); + if (err) { + sflc_log_error("Could not randomise DMB; error %d", err); + return err; + } + + /* Assign salt */ + salt = disk_block; + + /* Loop over all VMB keys to encrypt them */ + size_t i; + for (i = 0; i < dmb->nr_vols; i++) { + char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); + + /* Encrypt it */ + err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); + if (err) { + sflc_log_error("Could not encrypt VMB key number %lu; error %d", i, err); + return err; + } + } + + return 0; +} + + +/** + * "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF). + * + * @param disk_block The on-disk sealed DMB + * @param pwd The password locking the VMB key + * @param pwd_len Its length + * + * @output dmb_cell->vmb_key The unlocked VMB key + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * + * @return Error code, 0 on success + */ +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell) +{ + // KDF salt + char *salt; + // The KDF-derived key + char kek[SFLC_CRYPTO_KEYLEN]; + // The unlocked VMB key + char vmb_key[SFLC_CRYPTO_KEYLEN]; + // Error code + int err; + + /* Derive KEK once and for all */ + salt = disk_block; + err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); + if (err) { + sflc_log_error("Could not perform KDF: error %d", err); + goto bad_kdf; + } + sflc_log_debug("Successfully derived key-encryption-key with KDF"); + + /* Init dmb->vol_idx to invalid */ + dmb_cell->vol_idx = SFLC_DEV_MAX_VOLUMES; + /* Try all DMB cells */ + size_t i; + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + char *enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); + bool match; + + /* Try to decrypt this one */ + err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); + if (err) { + sflc_log_error("Error decrypting DMB cell number %lu; error %d", i, err); + goto bad_decrypt; + } + + /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ + if (match) { + sflc_log_debug("The provided password unlocks volume %lu", i); + dmb_cell->vol_idx = i; + memcpy(dmb_cell->vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); + } + } + + // No prob + err = 0; + + +bad_decrypt: +bad_kdf: + /* Always wipe the key from memory, even on success */ + memset(kek, 0, SFLC_CRYPTO_KEYLEN); + return err; +} + +/** + * Re-encrypt the content of the specified DMB cell. + * + * @param disk_block The on-disk sealed DMB + * @param dmb_cell The DMB cell to re-encrypt + * @param pwd The new password + * @param pwd_len The password's length + * + * @return Error code, 0 on success + */ +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len) +{ + char *salt; + char *enc_dmb_cell; + int err; + + /* Sanity check */ + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + return EINVAL; + } + + /* Pointers inside DMB */ + salt = disk_block; + enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLC_DMB_CELL_SIZE); + /* Encrypt with KDF-derived key */ + err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); + if (err) { + sflc_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); + return err; + } + sflc_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); + + return 0; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* AES-GCM-encrypt a VMB key with the KDF-generated key */ +static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vmb_key, char *dmb_cell) +{ + // Pointers inside the block + char *iv = dmb_cell; + char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; + // Key-encryption-key derived from KDF + char kek[SFLC_CRYPTO_KEYLEN]; + // Error code + int err; + + /* Derive KEK */ + err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); + if (err) { + sflc_log_error("Could not perform KDF: error %d", err); + goto bad_kdf; + } + sflc_log_debug("Successfully derived key-encryption-key with KDF"); + + /* Sample VMB_IV */ + err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); + if (err) { + sflc_log_error("Could not sample prologue IV: error %d", err); + goto bad_sample_iv; + } + sflc_log_debug("Successfully sampled prologue IV"); + + /* Encrypt the VMB key */ + err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_CRYPTO_KEYLEN, iv, enc_vmb_key, mac); + if (err) { + sflc_log_error("Could not encrypt the VMB key: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted VMB key with key-encryption-key"); + + // No prob + err = 0; + + +bad_encrypt: +bad_sample_iv: +bad_kdf: + /* Always wipe the key from memory, even on success */ + memset(kek, 0, SFLC_CRYPTO_KEYLEN); + return err; +} + +/* AES-GCM-decrypt a VMB key with the KDF-generated key */ +static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool *match) +{ + // Pointers inside the block + char *iv = dmb_cell; + char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; + // Error code + int err; + + /* Decrypt the VMB key */ + err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_CRYPTO_KEYLEN, mac, iv, vmb_key, match); + if (err) { + sflc_log_error("Error while decrypting VMB key: error %d", err); + return err; + } + sflc_log_debug("Decrypted VMB key: MAC match = %d", *match); + + return 0; +} + diff --git a/vuvuzela-userland/src/header/position_map.c b/vuvuzela-userland/src/header/position_map.c new file mode 100644 index 0000000..f2b8144 --- /dev/null +++ b/vuvuzela-userland/src/header/position_map.c @@ -0,0 +1,134 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "header.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/math.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* Create an encrypted empty position map for the given number of slices. + * Allocates the internal pointers of the EPM structure. + * On failure, does not free the allocated memory. + * + * @param nr_slices The number of slices the device will be composed of. + * @param volume_key The volume's data section encryption key, used to encrypt the + * position map as well. + * @param epm The EncPosMap struct to be initialised. + * + * @return Error code, 0 on success */ +int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm) +{ + size_t nr_pmbs; + size_t nr_arrays; + int err; + + // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) + nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + // Each array holds up to 256 PosMapBlocks + nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); + + // Fill the EPM numeric fields + epm->nr_arrays = nr_arrays; + // All arrays are full except the last one + epm->nr_last_pmbs = nr_pmbs - (SFLC_BLOCKS_PER_LOG_SLICE * (nr_arrays - 1)); + + // Allocate array of IV blocks + epm->iv_blocks = malloc(nr_arrays * sizeof(char *)); + if (!epm->iv_blocks) { + sflc_log_error("Could not malloc array of IV blocks"); + err = ENOMEM; + goto out; + } + // Allocate array of PosMapBlock arrays + epm->pmb_arrays = malloc(nr_arrays * sizeof(char *)); + if (!epm->pmb_arrays) { + sflc_log_error("Could not malloc array of PosMapBlock arrays"); + err = ENOMEM; + goto out; + } + + // Loop to allocate and encrypt each array + int i; + for (i = 0; i < nr_arrays; i++) { + // The last PMB array might be smaller + size_t nr_pmbs_here = ((i == nr_arrays - 1) ? epm->nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); + size_t pmb_array_size = SFLC_SECTOR_SIZE * nr_pmbs_here; + char *iv_block; + char *pmb_array; + + // Allocate IV block + epm->iv_blocks[i] = malloc(SFLC_SECTOR_SIZE); + if (!epm->iv_blocks[i]) { + sflc_log_error("Could not allocate IV block number %d", i); + err = ENOMEM; + goto out; + } + // Allocate PosMapBlock array + epm->pmb_arrays[i] = malloc(pmb_array_size); + if (!epm->pmb_arrays[i]) { + sflc_log_error("Could not allocate PMB array number %d", i); + err = ENOMEM; + goto out; + } + // Shorthand + iv_block = epm->iv_blocks[i]; + pmb_array = epm->pmb_arrays[i]; + + // Fill the IV block with random data (can ignore return value) + sflc_rand_getWeakBytes(iv_block, SFLC_SECTOR_SIZE); + // Fill the PMB array with 0xFF + memset(pmb_array, SFLC_EPM_FILLER, pmb_array_size); + + // Loop to encrypt each PMB separately with its IV + int j; + for (j = 0; j < nr_pmbs_here; j++) { + char *iv = iv_block + (j * SFLC_AESCTR_IVLEN); + char *pmb = pmb_array + (j * SFLC_SECTOR_SIZE); + + // Encrypt in-place + err = sflc_aes256ctr_encrypt(volume_key, pmb, SFLC_SECTOR_SIZE, iv, NULL); + if (err) { + sflc_log_error("Could not encrypt PMB %d of array %d", j, i); + goto out; + } + } + } + + +out: + return err; +} diff --git a/vuvuzela-userland/src/header/volume_master_block.c b/vuvuzela-userland/src/header/volume_master_block.c new file mode 100644 index 0000000..9347582 --- /dev/null +++ b/vuvuzela-userland/src/header/volume_master_block.c @@ -0,0 +1,223 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include // Network byte order + +#include "header.h" +#include "utils/crypto.h" +#include "utils/string.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Serialise the VMB before encrypting it */ +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); + +/* Deserialise the VMB after decrypting it */ +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Builds the on-disk master block (indistinguishable from random). + * + * @param vmb The useful information stored in this volume master block + * @param vmb_key The key encrypting the VMB + * @param disk_block The 4096-byte buffer that will contain the random-looking + * bytes to be written on-disk + * + * @return The error code, 0 on success + */ +int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) +{ + // Pointers inside the block + char *iv = disk_block; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + // Serialised VMB (dynamically allocated), to be encrypted + char *clear_vmb; + // Error code + int err; + + /* Allocate large buffer on the heap */ + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + if (!clear_vmb) { + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + err = ENOMEM; + goto bad_clear_alloc; + } + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + + /* Serialise the struct */ + _serialiseVmb(vmb, clear_vmb); + sflc_log_debug("Serialised VMB struct"); + + /* Sample VMB IV */ + err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); + if (err) { + sflc_log_error("Could not sample VMB IV: error %d", err); + goto bad_sample_iv; + } + sflc_log_debug("Successfully sampled VMB IV"); + + /* Encrypt the VMB */ + err = sflc_aes256ctr_encrypt(vmb_key, clear_vmb, SFLC_CLEAR_VMB_LEN, iv, enc_vmb); + if (err) { + sflc_log_error("Could not encrypt VMB: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted VMB"); + + // No prob + err = 0; + + +bad_encrypt: +bad_sample_iv: + /* Always wipe and free the cleartext VMB, even on success */ + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + free(clear_vmb); +bad_clear_alloc: + return err; +} + + +/** + * Decrypt the VMB payload using the VMB key. + * + * @param disk_block The content of the on-disk encrypted VMB + * @param vmb_key The proposed VMB key to unseal its payload + * @param vmb A pointer to the output struct that will contain all the VMB fields + * + * @return An error code, 0 on success + */ +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +{ + // Pointers inside the block + char *iv = disk_block; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + // Decrypted VMB (dynamically allocated), to be deserialised + char *clear_vmb; + // Error code + int err; + + /* Allocate large buffer on the heap */ + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + if (!clear_vmb) { + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + err = ENOMEM; + goto bad_clear_alloc; + } + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + + /* Decrypt the VMB */ + err = sflc_aes256ctr_decrypt(vmb_key, enc_vmb, SFLC_CLEAR_VMB_LEN, iv, clear_vmb); + if (err) { + sflc_log_error("Error while decrypting VMB: error %d", err); + goto bad_decrypt; + } + sflc_log_debug("Successfully decrypted VMB"); + + /* Deserialise the struct */ + err = _deserialiseVmb(clear_vmb, vmb); + if (err) { + sflc_log_error("Error while deserialising VMB: error %d", err); + goto bad_deserialise; + } + sflc_log_debug("Deserialised VMB struct"); + + // No prob + err = 0; + + +bad_deserialise: +bad_decrypt: + /* Always wipe and free the VMB cleartext, even on success */ + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + free(clear_vmb); +bad_clear_alloc: + return err; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Serialise the payload before encrypting it */ +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) +{ + // Pointers inside the VMB + char *p_vol_key = clear_vmb; + char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; + + /* Copy the volume key */ + memcpy(p_vol_key, vmb->volume_key, SFLC_CRYPTO_KEYLEN); + + /* Copy the previous volume's VMB key */ + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_CRYPTO_KEYLEN); + + /* Write the number of slices (network byte order) */ + *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); + + // Leave the rest uninitialised + + return; +} + + +/* Deserialise the VMB after decrypting it */ +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) +{ + // Pointers inside the VMB + char *p_vol_key = clear_vmb; + char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; + + /* Copy the volume key */ + memcpy(vmb->volume_key, p_vol_key, SFLC_CRYPTO_KEYLEN); + + /* Copy the previous volume's VMB key */ + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_CRYPTO_KEYLEN); + + /* Read number of slices (network byte order) */ + vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); + + // Ignore the rest + + return 0; +} + diff --git a/vuvuzela-userland/src/main.c b/vuvuzela-userland/src/main.c new file mode 100644 index 0000000..737b57e --- /dev/null +++ b/vuvuzela-userland/src/main.c @@ -0,0 +1,44 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + + +/* + * Shufflecake userland tool + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include "cli.h" + + +/***************************************************** + * MAIN * + *****************************************************/ + +int main(int argc, char **argv) +{ + return sflc_cli_dispatch(argc, argv); +} + diff --git a/vuvuzela-userland/src/operations/devmapper.c b/vuvuzela-userland/src/operations/devmapper.c new file mode 100644 index 0000000..99a66a2 --- /dev/null +++ b/vuvuzela-userland/src/operations/devmapper.c @@ -0,0 +1,111 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include + +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/file.h" +#include "utils/string.h" +#include "utils/dm.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Build parameter list for ctor in dm_sflc, and send DM ioctl to create + * virtual block device. + * + * @param bdev_path The path to the underlying device + * @param dev_id The ID of the underlying block device + * @param vol_idx The index of the volume within the device + * @param vmb Volume metadata + * + * @return Error code, 0 on success + */ +int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) +{ + char label[SFLC_BIGBUFSIZE]; + char *hex_key; + char params[SFLC_BIGBUFSIZE]; + uint64_t num_sectors; + int err; + + /* Build volume label */ + sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); + + /* Get the hex version of the volume's data section key */ + hex_key = sflc_toHex(vmb->volume_key, SFLC_CRYPTO_KEYLEN); + if (!hex_key) { + sflc_log_error("Could not encode volume key to hexadecimal"); + err = ENOMEM; + goto err_hexkey; + } + + /* Get the number of logical 512-byte sectors composing the volume */ + num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; + + /* Build param list */ + sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); + + /* Issue ioctl */ + err = sflc_dm_create(label, num_sectors, params); + if (err) { + sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + goto err_dmcreate; + } + err = 0; + + +err_dmcreate: + free(hex_key); +err_hexkey: + return err; +} + + +/** + * Close the volume by issuing the appropriate ioctl to the DM. + * + * @param label The only needed parameter: the ID of the volume. + * + * @return Error code, 0 on success + */ +int sflc_ops_closeVolume(char *label) +{ + /* Issue ioctl */ + return sflc_dm_destroy(label); +} diff --git a/vuvuzela-userland/src/operations/dmb.c b/vuvuzela-userland/src/operations/dmb.c new file mode 100644 index 0000000..d529966 --- /dev/null +++ b/vuvuzela-userland/src/operations/dmb.c @@ -0,0 +1,167 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include + +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Encrypts and writes the DMB to disk. + * + * @param bdev_path The path to the underlying block device + * @param pwds The array of passwords + * @param pwd_lens Their lengths + * @param dmb->nr_vols + * + * + * @return Error code, 0 on success + */ +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) +{ + /* On-disk DMB */ + char enc_dmb[SFLC_SECTOR_SIZE]; + /* Error code */ + int err; + + /* Sanity check */ + if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); + return EINVAL; + } + + /* Seal DMB */ + err = sflc_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); + if (err) { + sflc_log_error("Coul dnot seal DMB; error %d", err); + return err; + } + + /* Write it to disk (at sector 0) */ + err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not write DMB to disk; error %d", err); + return err; + } + + return 0; +} + + +/** + * Reads the DMB from disk and outputs the unlocked VMB key. + * + * @param bdev_path The path to the underlying block device + * @param pwd The user-provided password + * @param pwd_len Its length + * + * @output dmb_cell->vmb_key The unlocked VMB key + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * + * @return Error code, 0 on success + */ +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) +{ + char enc_dmb[SFLC_SECTOR_SIZE]; + int err; + + /* Read DMB from disk (at sector 0) */ + err = sflc_disk_readSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not read DMB from disk; error %d", err); + return err; + } + + /* Unseal it */ + err = sflc_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); + if (err) { + sflc_log_error("Could not unseal DMB; error %d", err); + return err; + } + + return 0; +} + +/** + * Reads the DMB from disk, changes the relevant DMB cell, and writes it back. + * + * @param bdev_path The path to the underlying block device + * @param dmb_cell The index and vmb_key of the DMB cell to re-encrypt + * @param new_pwd The new password of the DMB cell to re-encrypt + * @param new_pwd_len Its length + * + * @return Error code, 0 on success + */ +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) +{ + char enc_dmb[SFLC_SECTOR_SIZE]; + int err; + + /* Sanity check */ + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + return EINVAL; + } + + /* Read DMB from disk (at sector 0) */ + err = sflc_disk_readSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not read DMB from disk; error %d", err); + return err; + } + + /* Update the relevant cell */ + err = sflc_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); + if (err) { + sflc_log_error("Could not update DMB cell; error %d", err); + return err; + } + + /* Write it to disk (at sector 0) */ + err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not write DMB to disk; error %d", err); + return err; + } + + return 0; +} + + diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c new file mode 100644 index 0000000..1088290 --- /dev/null +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -0,0 +1,165 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include + +#include "header.h" +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Writes a volume header (VMB+PM) on-disk. + * + * @param bdev_path The underlying block device to write the volume header + * @param vmb_key The key to encrypt the VMB + * @param vmb The VMB to encrypt + * @param vol_idx The index of the volume within the device + * + * @return Error code, 0 on success + */ +int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) +{ + char enc_vmb[SFLC_SECTOR_SIZE]; + sflc_EncPosMap epm; + uint64_t sector; + int err; + + // Encrypt VMB + err = sflc_vmb_seal(vmb, vmb_key, enc_vmb); + if (err) { + sflc_log_error("Could not seal VMB; error %d", err); + goto out; + } + + // Write it to disk + sector = sflc_vmbPosition(vol_idx, vmb->nr_slices); + err = sflc_disk_writeSector(bdev_path, sector, enc_vmb); + if (err) { + sflc_log_error("Could not write VMB to disk; error %d", err); + goto out; + } + sector += 1; + + // Create encrypted empty position map + err = sflc_epm_create(vmb->nr_slices, vmb->volume_key, &epm); + if (err) { + sflc_log_error("Could not create encrypted empty position map; error %d", err); + goto out; + } + + // Loop over PMB arrays to write them to disk + int i; + for (i = 0; i < epm.nr_arrays; i++) { + char *iv_block = epm.iv_blocks[i]; + char *pmb_array = epm.pmb_arrays[i]; + size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); + + // First write the IV block + err = sflc_disk_writeSector(bdev_path, sector, iv_block); + if (err) { + sflc_log_error("Could not write IV block to disk; error %d", err); + goto out; + } + sector += 1; + + // Then the whole PMB array + err = sflc_disk_writeManySectors(bdev_path, sector, pmb_array, nr_pmbs); + if (err) { + sflc_log_error("Could not write PMB array to disk; error %d", err); + goto out; + } + sector += nr_pmbs; + + // Free them both + free(iv_block); + free(pmb_array); + } + + // Free containers + free(epm.iv_blocks); + free(epm.pmb_arrays); + + +out: + return err; +} + + +/** + * Reads a VMB from disk and unlocks it + * + * @param bdev_path The underlying block device + * @param vmb_key The key to decrypt the VMB + * @param nr_slices The number of slices in the device + * @param vol_idx The index of the volume within the device + * + * @output vmb The decrypted VMB + * + * @return Error code, 0 on success + */ +int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) +{ + char enc_vmb[SFLC_SECTOR_SIZE]; + uint64_t sector; + int err; + + /* Read encrypted VMB from disk */ + sector = sflc_vmbPosition(vol_idx, nr_slices); + err = sflc_disk_readSector(bdev_path, sector, enc_vmb); + if (err) { + sflc_log_error("Could not read VMB from disk; error %d", err); + return err; + } + + /* Unseal it */ + err = sflc_vmb_unseal(enc_vmb, vmb_key, vmb); + if (err) { + sflc_log_error("Could not unseal VMB; error %d", err); + return err; + } + + /* Compare the number of slices */ + if (nr_slices != vmb->nr_slices) { + sflc_log_error("Incompatible header size: the device size was different when the volumes" + "were created. Did you resize the device %s since last time?", bdev_path); + return EINVAL; + } + + return 0; +} diff --git a/vuvuzela-userland/src/utils/crypto.c b/vuvuzela-userland/src/utils/crypto.c new file mode 100644 index 0000000..b42f50f --- /dev/null +++ b/vuvuzela-userland/src/utils/crypto.c @@ -0,0 +1,424 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include + +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + *Fills the given buffer with strong random bytes, suitable for long-term + *cryptographic keys (uses the entropy pool). Always succeeds. + * + *@param buf The buffer to fill + *@param buflen The number of desired random bytes + * + *@return The error code (0 on success) + */ +int sflc_rand_getStrongBytes(char *buf, size_t buflen) +{ + gcry_randomize(buf, buflen, GCRY_VERY_STRONG_RANDOM); + return 0; +} + + +/** + *Faster than the previous one, fills the given buffer with weak random bytes, + *suitable for IVs or block filling (does not use the entropy pool). + *Always succeeds. + * + *@param buf The buffer to fill + *@param buflen The number of desired random bytes + * + *@return The error code (0 on success) + */ +int sflc_rand_getWeakBytes(char *buf, size_t buflen) +{ + gcry_create_nonce(buf, buflen); + return 0; +} + + +/** + *AES-CTR encryption, does not touch the IV. Set ct = NULL for in-place. + * + *@param key The 32-byte AES key + *@param pt The plaintext + *@param pt_len The length of the plaintext, must be a multiple of the AES + * block size (16 bytes) + *@param iv The 16-byte AES-CTR IV + *@param ct A caller-allocated buffer that will contain the output ciphertext, + * cannot overlap with pt. If NULL, in-place encryption. + * + *@return The error code (0 on success) + */ +int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +{ + gcry_cipher_hd_t hd; + gcry_error_t err; + + // Instantiate the handle + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); + if (err) { + sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); + + // Set the key + err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not set AES key: error %d", err); + goto bad_setkey; + } + sflc_log_debug("Successfully set the AES key"); + + // Set the counter (not an IV, as per Gcrypt docs) + err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); + if (err) { + sflc_log_error("Could not set AES-CTR IV: error %d", err); + goto bad_setctr; + } + sflc_log_debug("Successfully set the IV"); + + // Encrypt + if (ct == NULL) { // In-place + err = gcry_cipher_encrypt(hd, pt, pt_len, NULL, 0); + } + else { // Out-of-place + err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); + } + // Error check + if (err) { + sflc_log_error("Could not encrypt: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted"); + + // No prob? + err = 0; + + +bad_encrypt: +bad_setctr: +bad_setkey: + gcry_cipher_close(hd); +bad_open: + return err; +} + +/** + *AES-CTR decryption, does not touch the IV. Set pt = NULL for in-place. + * + *@param key The 32-byte AES key + *@param ct The ciphertext + *@param ct_len The length of the ciphertext, must be a multiple of the AES + * block size (16 bytes) + *@param iv The 16-byte AES-CTR IV + *@param pt A caller-allocated buffer that will contain the output plaintext, + * cannot overlap with ct. If NULL, in-place decryption. + * + *@return The error code (0 on success) + */ +int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) +{ + gcry_cipher_hd_t hd; + gcry_error_t err; + + // Instantiate the handle + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); + if (err) { + sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); + + // Set the key + err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not set AES key: error %d", err); + goto bad_setkey; + } + sflc_log_debug("Successfully set AES key"); + + // Set the counter (not an IV, as per Gcrypt docs) + err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); + if (err) { + sflc_log_error("Could not set AES-CTR IV: error %d", err); + goto bad_setctr; + } + sflc_log_debug("Successfully set IV"); + + // Decrypt + if (pt == NULL) { // In-place + err = gcry_cipher_decrypt(hd, ct, ct_len, NULL, 0); + } + else { // Out-of-place + err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); + } + // Error check + if (err) { + sflc_log_error("Could not decrypt: error %d", err); + goto bad_decrypt; + } + sflc_log_debug("Successfully decrypted"); + + // No prob + err = 0; + + +bad_decrypt: +bad_setctr: +bad_setkey: + gcry_cipher_close(hd); +bad_open: + return err; +} + + +/** + *AES-GCM encryption, does not touch the IV. + * + *@param key The 32-byte AES key + *@param pt The plaintext + *@param pt_len The length of the plaintext, must be a multiple of the AES + * block size (16 bytes) + *@param iv The 12-byte AES-GCM IV + *@param ct A caller-allocated buffer that will contain the output ciphertext, + * cannot overlap with pt + *@param tag A caller-allocated buffer that will contain the 16-byte output MAC + * + *@return The error code (0 on success) + */ +int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) +{ + gcry_cipher_hd_t hd; + gcry_error_t err; + + // Instantiate the handle + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); + if (err) { + sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); + + // Set the key + err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not set AES key: error %d", err); + goto bad_setkey; + } + sflc_log_debug("Successfully set the AES key"); + + // Set the IV + err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); + if (err) { + sflc_log_error("Could not set AES-GCM IV: error %d", err); + goto bad_setiv; + } + sflc_log_debug("Successfully set the IV"); + + // Encrypt + err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); + if (err) { + sflc_log_error("Could not encrypt: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted"); + + // Get MAC + err = gcry_cipher_gettag(hd, tag, SFLC_AESGCM_TAGLEN); + if (err) { + sflc_log_error("Could not get MAC: error %d", err); + goto bad_gettag; + } + sflc_log_debug("Successfully gotten MAC"); + + // No prob? + err = 0; + + +bad_gettag: +bad_encrypt: +bad_setiv: +bad_setkey: + gcry_cipher_close(hd); +bad_open: + return err; +} + + +/** + *AES-GCM decryption, does not touch the IV. + * + *@param key The 32-byte AES key + *@param ct The ciphertext + *@param ct_len The length of the ciphertext, must be a multiple of the AES + * block size (16 bytes) + *@param tag The 16-byte MAC + *@param iv The 12-byte AES-GCM IV + *@param pt A caller-allocated buffer that will contain the output plaintext, + * cannot overlap with ct + *@param match A pointer to a single output boolean, indicating MAC verification + * success or failure. On failure, pt is filled with poison bytes. + * + *@return The error code (0 on success) + */ +int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) +{ + gcry_cipher_hd_t hd; + gcry_error_t err; + + // Instantiate the handle + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); + if (err) { + sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); + + // Set the key + err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not set AES key: error %d", err); + goto bad_setkey; + } + sflc_log_debug("Successfully set AES key"); + + // Set the IV + err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); + if (err) { + sflc_log_error("Could not set AES-GCM IV: error %d", err); + goto bad_setiv; + } + sflc_log_debug("Successfully set IV"); + + // Decrypt + err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); + if (err) { + sflc_log_error("Could not decrypt: error %d", err); + goto bad_decrypt; + } + sflc_log_debug("Successfully decrypted"); + + // Check MAC + err = gcry_cipher_checktag(hd, tag, SFLC_AESGCM_TAGLEN); + if (gcry_err_code(err) == GPG_ERR_CHECKSUM) { + // Undo decryption + memset(pt, SFLC_AESGCM_POISON_PT, ct_len); + // Flag it + *match = false; + } + else if (err) { + sflc_log_error("Could not check MAC: error %d", err); + goto bad_checktag; + } + else { + // Flag MAC verification success + *match = true; + } + sflc_log_debug("Successfully checked MAC: match = %d", *match); + + // No prob, whether MAC verified or not + err = 0; + + +bad_checktag: +bad_decrypt: +bad_setiv: +bad_setkey: + gcry_cipher_close(hd); +bad_open: + return err; +} + + +/** + * Argon2id KDF. + * + * @param pwd The password + * @param pwd_len The length of the password + * @param salt The 16-byte KDF salt + * @param hash A caller-allocated 32-byte output buffer that will contain the + * password hash + * + * @return The error code (0 on success) + */ +int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) +{ + gcry_kdf_hd_t hd; + const unsigned long argon_params[4] = + {SFLC_CRYPTO_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; + gcry_error_t err; + + // Instantiate Argon2id handle + err = gcry_kdf_open( + &hd, GCRY_KDF_ARGON2, GCRY_KDF_ARGON2ID, + argon_params, 4, + pwd, pwd_len, + salt, SFLC_ARGON_SALTLEN, + NULL, 0, /* Optional secret value K */ + NULL, 0 /* Optional associated data X */ + ); + if (err) { + sflc_log_error("Could not open Argon2id handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully opened Argon2id handle"); + + // Run the computation + err = gcry_kdf_compute(hd, NULL); + if (err) { + sflc_log_error("Could not run Argon2id computation: error %d", err); + goto bad_compute; + } + sflc_log_debug("Successfully run Argon2id computation"); + + // Finalise hash + err = gcry_kdf_final(hd, SFLC_CRYPTO_KEYLEN, hash); + if (err) { + sflc_log_error("Could not finalise Argon2id hash: error %d", err); + goto bad_final; + } + sflc_log_debug("Successfully finalised Argon2id hash"); + + // All in order + err = 0; + + +bad_final: +bad_compute: + gcry_kdf_close(hd); +bad_open: + return err; +} diff --git a/vuvuzela-userland/src/utils/disk.c b/vuvuzela-userland/src/utils/disk.c new file mode 100644 index 0000000..2f78598 --- /dev/null +++ b/vuvuzela-userland/src/utils/disk.c @@ -0,0 +1,257 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * Disk helper functions + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/disk.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + + +/** + * Checks whether the given path points to a block device. + * + * @param path The path to check + * + * @return true iff it's a block device + */ +bool sflc_disk_isBlockDevice(char *path) +{ + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return false; + } + return S_ISBLK(path_stat.st_mode); +} + +/** + * Returns the size in 4096-byte sectors (or < 0 if error). + * + * @param bdev_path The path of the block device + * + * @return The size (in 4096-byte sectors) of the disk, or -errno if error + */ +int64_t sflc_disk_getSize(char * bdev_path) +{ + int fd; + uint64_t size_bytes; + int64_t ret; + + /* Open file */ + fd = open(bdev_path, O_RDONLY); + if (fd < 0) { + sflc_log_error("Could not open file %s", bdev_path); + perror("Cause: "); + ret = -errno; + goto bad_open; + } + sflc_log_debug("Opened file %s", bdev_path); + + /* Get size in bytes */ + if (ioctl(fd, BLKGETSIZE64, &size_bytes) < 0) { + sflc_log_error("Could not ioctl file %s", bdev_path); + perror("Cause: "); + ret = -errno; + goto bad_ioctl; + } + sflc_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); + + /* Compute size in SFLC sectors */ + ret = (size_bytes / SFLC_SECTOR_SIZE); + +bad_ioctl: + close(fd); +bad_open: + return ret; +} + +/** + * Reads a single 4096-byte sector from the disk. + * + * @param bdev_path The path of the block device + * @param sector The index of the desired sector + * @param The caller-allocated buffer (must hold 4096 bytes) where the data + * from the disk will go + * + * @return The error code (0 on success) + */ +int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf) +{ + int fd; + int err; + + /* Open file */ + fd = open(bdev_path, O_RDONLY); + if (fd < 0) { + sflc_log_error("Could not open file %s", bdev_path); + perror("Cause: "); + err = errno; + goto bad_open; + } + sflc_log_debug("Opened file %s", bdev_path); + + /* Set offset in bytes */ + if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { + sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + perror("Cause: "); + err = errno; + goto bad_lseek; + } + sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + + /* Read in a loop */ + size_t bytes_to_read = SFLC_SECTOR_SIZE; + while (bytes_to_read > 0) { + /* Read syscall */ + ssize_t bytes_read = read(fd, buf, bytes_to_read); + if (bytes_read < 0) { + sflc_log_error("Could not read file %s at sector %lu", bdev_path, sector); + perror("Cause: "); + err = errno; + goto bad_read; + } + + /* Partial read? No problem just log */ + if (bytes_read < bytes_to_read) { + sflc_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", + bdev_path, sector, bytes_read, bytes_to_read); + } + + /* Advance loop */ + bytes_to_read -= bytes_read; + buf += bytes_read; + } + + // No prob + err = 0; + +bad_read: +bad_lseek: + close(fd); +bad_open: + return err; +} + + +/** + * Writes a single 4096-byte sector to the disk. + * + * @param bdev_path The path of the block device + * @param sector The index of the desired sector + * @param The caller-allocated buffer (must hold 4096 bytes) where the data + * comes from + * + * @return The error code (0 on success) + */ +int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf) +{ + return sflc_disk_writeManySectors(bdev_path, sector, buf, 1); +} + + +/** + * Writes many 4096-byte sectors to the disk. + * + * @param bdev_path The path of the block device + * @param sector The index of the starting sector + * @param The caller-allocated buffer where the data + * comes from + * @param num_sectors The number of sectors to write + * + * @return The error code (0 on success) + */ +int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) +{ + int fd; + int err; + + /* Open file */ + fd = open(bdev_path, O_WRONLY); + if (fd < 0) { + sflc_log_error("Could not open file %s", bdev_path); + perror("Cause: "); + err = errno; + goto bad_open; + } + sflc_log_debug("Opened file %s", bdev_path); + + /* Set offset in bytes */ + if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { + sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + perror("Cause: "); + err = errno; + goto bad_lseek; + } + sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + + /* Write in a loop */ + size_t bytes_to_write = SFLC_SECTOR_SIZE * num_sectors; + while (bytes_to_write > 0) { + /* Write syscall */ + ssize_t bytes_written = write(fd, buf, bytes_to_write); + if (bytes_written < 0) { + sflc_log_red("Could not write file %s at sector %lu", bdev_path, sector); + perror("Cause: "); + err = errno; + goto bad_write; + } + + /* Partial write? No problem just log */ + if (bytes_written < bytes_to_write) { + sflc_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", + bdev_path, sector, bytes_written, bytes_to_write); + } + + /* Advance loop */ + bytes_to_write -= bytes_written; + buf += bytes_written; + } + + // No prob + err = 0; + +bad_write: +bad_lseek: + close(fd); +bad_open: + return err; +} diff --git a/vuvuzela-userland/src/utils/dm.c b/vuvuzela-userland/src/utils/dm.c new file mode 100644 index 0000000..6c2d054 --- /dev/null +++ b/vuvuzela-userland/src/utils/dm.c @@ -0,0 +1,203 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + + +/** + * Interface to the device mapper. The only example I could find is the + * cryptsetup source, at + * https://kernel.googlesource.com/pub/scm/utils/cryptsetup/cryptsetup/+/v1_6_3/lib/libdevmapper.c + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include + +#include "utils/dm.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Create a new Shufflecake virtual device (volume) under /dev/mapper. + * + * @param virt_dev_name The name of the new virtual device, as it will appear + * under /dev/mapper + * @param num_sectors The size of the virtual device, in 512-byte sectors + * @param params The string containing the space-separated paramters that will + * be passed to the Shufflecake constructor in the kernel module + * + * @return The error code (0 on success) + */ +int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) +{ + struct dm_task *dmt; + uint32_t cookie = 0; + uint16_t udev_flags = 0; + int err; + + /* Just to be sure, let's get them on the heap */ + char * dup_virt_dev_name = strdup(virt_dev_name); + char * dup_params = strdup(params); + + sflc_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); + + /* Instantiate the DM task (with the CREATE ioctl command) */ + if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { + sflc_log_error("Cannot create dm_task"); + err = 1; + goto dup_free; + } + sflc_log_debug("Successfully created dm_task"); + + /* Set the name of the target device (to be created) */ + if (!dm_task_set_name(dmt, dup_virt_dev_name)) { + sflc_log_error("Cannot set device name"); + err = 2; + goto out; + } + sflc_log_debug("Successfully set device name"); + + /* State that it is a Shufflecake device, pass the start and size, and the + * constructor parameters */ + if (!dm_task_add_target(dmt, 0, num_sectors, SFLC_DM_TARGET_NAME, dup_params)) { + sflc_log_error("Cannot add DM target and parameters"); + err = 3; + goto out; + } + sflc_log_debug("Successfully added DM target and parameters"); + + /* Say that we want a new node under /dev/mapper */ + if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { + sflc_log_error("Cannot add /dev/mapper node"); + err = 4; + goto out; + } + sflc_log_debug("Successfully set the ADD_NODE flag"); + + /* Get a cookie (request ID, basically) to wait for task completion */ + if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { + sflc_log_error("Cannot get cookie"); + err = 5; + goto out; + } + sflc_log_debug("Successfully got a cookie"); + + /* Run the task */ + if (!dm_task_run(dmt)) { + sflc_log_error("Cannot issue ioctl"); + err = 6; + goto out; + } + sflc_log_debug("Successfully run DM task"); + + /* Wait for completion */ + dm_udev_wait(cookie); + sflc_log_debug("Task completed"); + + // No prob + err = 0; + +out: + dm_task_destroy(dmt); +dup_free: + free(dup_virt_dev_name); + free(dup_params); + + return err; +} + +/** + * Close a Shufflecake virtual device (volume) under /dev/mapper. + * + * @param virt_dev_name the name of the virtual device, as it appears under + * /dev/mapper + * + * @return error code (0 on success) + */ +int sflc_dm_destroy(char * virt_dev_name) +{ + struct dm_task *dmt; + uint32_t cookie = 0; + uint16_t udev_flags = 0; + int err = 0; + + /* Just to be sure, let's get it on the heap */ + char * dup_virt_dev_name = strdup(virt_dev_name); + + sflc_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); + + /* Instantiate the DM task (with the REMOVE ioctl command) */ + if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) { + sflc_log_error("Cannot create dm_task"); + err = 1; + goto dup_free; + } + sflc_log_debug("Successfully created dm_task"); + + /* Set the name of the target device (to be closed) */ + if (!dm_task_set_name(dmt, dup_virt_dev_name)) { + sflc_log_error("Cannot set device name"); + err = 2; + goto out; + } + sflc_log_debug("Successfully set device name"); + + /* Get a cookie (request ID, basically) to wait for task completion */ + if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { + sflc_log_error("Cannot set cookie"); + err = 3; + goto out; + } + sflc_log_debug("Successfully got a cookie"); + + /* Needed for some reason */ + dm_task_retry_remove(dmt); + sflc_log_debug("Successful retry_remove"); + + /* Run the task */ + if (!dm_task_run(dmt)) { + sflc_log_error("Cannot issue ioctl"); + err = 4; + goto out; + } + sflc_log_debug("Successfully run task"); + + /* Wait for completion */ + dm_udev_wait(cookie); + sflc_log_debug("Task completed"); + + // No prob + err = 0; + +out: + dm_task_destroy(dmt); +dup_free: + free(dup_virt_dev_name); + + return err; +} diff --git a/vuvuzela-userland/src/utils/file.c b/vuvuzela-userland/src/utils/file.c new file mode 100644 index 0000000..a8e70e2 --- /dev/null +++ b/vuvuzela-userland/src/utils/file.c @@ -0,0 +1,81 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "utils/file.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* Reads the entire content of a file in a malloc-ed string */ +char *sflc_readFile(char *path) +{ + int filesize; + FILE *fp; + char *content; + + /* Open file */ + fp = fopen(path, "r"); + if (fp == NULL) { + sflc_log_error("Could not open file %s", path); + perror("Reason: "); + goto bad_fopen; + } + + /* Get size (overestimated) */ + fseek(fp, 0L, SEEK_END); + filesize = ftell(fp); + rewind(fp); + + /* Allocate */ + content = malloc(filesize + 1); + if (content == NULL) { + sflc_log_error("Could not malloc %d bytes for file content", filesize); + goto bad_malloc; + } + + /* Read (adjust filesize) */ + filesize = fread(content, 1, filesize, fp); + content[filesize] = '\0'; + + /* Close */ + fclose(fp); + + return content; + + +bad_malloc: + fclose(fp); +bad_fopen: + return NULL; +} diff --git a/vuvuzela-userland/src/utils/input.c b/vuvuzela-userland/src/utils/input.c new file mode 100644 index 0000000..ffc0b02 --- /dev/null +++ b/vuvuzela-userland/src/utils/input.c @@ -0,0 +1,101 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* Reads a line (discarding the newline) from stdin. No buffer overflow */ +int sflc_safeReadLine(char *buf, size_t bufsize) +{ + size_t len; + + /* Read from stdin */ + if (fgets(buf, bufsize, stdin) == NULL) { + sflc_log_error("Could not read from stdin"); + return EBADFD; + } + + /* Discard newline */ + len = strlen(buf); + if (buf[len - 1] == '\n') { + buf[len - 1] = '\0'; + } + + return 0; +} + + +/* Reads a password/passphrase in a secure way (no echo) */ +int sflc_safeReadPassphrase(char *buf, size_t bufsize) +{ + size_t len; + struct termios old, new; + + fflush(stdout); // Ensure stdout is flushed is printed immediately + + // Turn off echoing + tcgetattr(STDIN_FILENO, &old); + new = old; + new.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); + + /* Read from stdin */ + if (fgets(buf, bufsize, stdin) == NULL) { + // If reading the password failed, ensure echoing is turned back on + tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + sflc_log_error("Could not read from stdin"); + return EBADFD; + } + + // Turn echoing back on + tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + + /* Discard newline */ + len = strlen(buf); + if (buf[len - 1] == '\n') { + buf[len - 1] = '\0'; + } + + /* Newline on screen */ + printf("\n"); + + return 0; +} + + diff --git a/vuvuzela-userland/src/utils/string.c b/vuvuzela-userland/src/utils/string.c new file mode 100644 index 0000000..c6cf998 --- /dev/null +++ b/vuvuzela-userland/src/utils/string.c @@ -0,0 +1,74 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "utils/string.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* Malloc's the buffer for the hex string */ +char *sflc_toHex(char *buf, size_t len) +{ + unsigned char *u = (unsigned char *) buf; + char *hex; + + /* Allocate buffer */ + hex = malloc((len * 2) + 1); + if (!hex) { + sflc_log_error("Could not allocate buffer for hex string"); + return NULL; + } + + /* To hex */ + int i; + for(i = 0; i < len; i++) { + sprintf(hex + (i * 2), "%02x", u[i]); + } + hex[len*2] = '\0'; + + return hex; +} + + +void sflc_str_replaceAll(char * str, char old, char new) +{ + int i; + for (i = 0; str[i] != '\0'; i++) { + if (str[i] == old) { + str[i] = new; + } + } + + return; +} diff --git a/vuvuzela-userland/test/crypto/test_aes256ctr.c b/vuvuzela-userland/test/crypto/test_aes256ctr.c new file mode 100644 index 0000000..700a910 --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_aes256ctr.c @@ -0,0 +1,178 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "utils/crypto.h" +#include "test_aes256ctr.h" +#include "minunit.h" +#include "utils/log.h" + + +/***************************************************** + * CONSTANT VARIABLES * + *****************************************************/ + +static const char KEY[] = AES256CTR_TEST_KEY; +static const char IV[] = AES256CTR_TEST_IV; +static const char PT[] = AES256CTR_TEST_PT; +static const char CT[] = AES256CTR_TEST_CT; + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +char *test_aes256ctr_encrypt_inplace() +{ + char msg[] = AES256CTR_TEST_PT; + size_t msg_len = sizeof(msg); + char key[] = AES256CTR_TEST_KEY; + char iv[] = AES256CTR_TEST_IV; + int err; + + sflc_log_blue("Testing AES256-CTR encryption in-place"); + + // Encrypt in-place + err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); + + // Check error + mu_assert("Error while encrypting", !err); + + // Check outcome + mu_assert("Ciphertext mismatch", memcmp(msg, CT, msg_len) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + +char *test_aes256ctr_encrypt_outofplace() +{ + char msg[] = AES256CTR_TEST_PT; + char ct[sizeof(msg)]; + size_t msg_len = sizeof(msg); + char key[] = AES256CTR_TEST_KEY; + char iv[] = AES256CTR_TEST_IV; + int err; + + sflc_log_blue("Testing AES256-CTR encryption out-of-place"); + + // Encrypt out-of-place + err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, ct); + + // Check error + mu_assert("Error while encrypting", !err); + + // Check outcome + mu_assert("Ciphertext mismatch", memcmp(ct, CT, msg_len) == 0); + + // Check msg untouched + mu_assert("Plaintext changed", memcmp(msg, PT, sizeof(key)) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + +char *test_aes256ctr_decrypt_inplace() +{ + char msg[] = AES256CTR_TEST_CT; + size_t msg_len = sizeof(msg); + char key[] = AES256CTR_TEST_KEY; + char iv[] = AES256CTR_TEST_IV; + int err; + + sflc_log_blue("Testing AES256-CTR decryption in-place"); + + // Decrypt in-place + err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); + + // Check error + mu_assert("Error while decrypting", !err); + + // Check outcome + mu_assert("Plaintext mismatch", memcmp(msg, PT, msg_len) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + +char *test_aes256ctr_decrypt_outofplace() +{ + char msg[] = AES256CTR_TEST_CT; + char pt[sizeof(msg)]; + size_t msg_len = sizeof(msg); + char key[] = AES256CTR_TEST_KEY; + char iv[] = AES256CTR_TEST_IV; + int err; + + sflc_log_blue("Testing AES256-CTR decryption out-of-place"); + + // Decrypt out-of-place + err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, pt); + + // Check error + mu_assert("Error while decrypting", !err); + + // Check outcome + mu_assert("Plaintext mismatch", memcmp(pt, PT, msg_len) == 0); + + // Check msg untouched + mu_assert("Ciphertext changed", memcmp(msg, CT, sizeof(key)) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + diff --git a/vuvuzela-userland/test/crypto/test_aes256ctr.h b/vuvuzela-userland/test/crypto/test_aes256ctr.h new file mode 100644 index 0000000..c1070f8 --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_aes256ctr.h @@ -0,0 +1,85 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +// Test vectors taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + +#ifndef _TEST_CRYPTO_AES256CTR_H_ +#define _TEST_CRYPTO_AES256CTR_H_ + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +#define AES256CTR_TEST_KEY \ +{ \ + 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, \ + 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, \ + 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, \ + 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 \ +} + +#define AES256CTR_TEST_IV \ +{ \ + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, \ + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff \ +} + +#define AES256CTR_TEST_PT \ +{ \ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, \ + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, \ + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, \ + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, \ + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, \ + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, \ + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, \ + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 \ +} + +#define AES256CTR_TEST_CT \ +{ \ + 0x60, 0x1e, 0xc3, 0x13, 0x77, 0x57, 0x89, 0xa5, \ + 0xb7, 0xa7, 0xf5, 0x04, 0xbb, 0xf3, 0xd2, 0x28, \ + 0xf4, 0x43, 0xe3, 0xca, 0x4d, 0x62, 0xb5, 0x9a, \ + 0xca, 0x84, 0xe9, 0x90, 0xca, 0xca, 0xf5, 0xc5, \ + 0x2b, 0x09, 0x30, 0xda, 0xa2, 0x3d, 0xe9, 0x4c, \ + 0xe8, 0x70, 0x17, 0xba, 0x2d, 0x84, 0x98, 0x8d, \ + 0xdf, 0xc9, 0xc5, 0x8d, 0xb6, 0x7a, 0xad, 0xa6, \ + 0x13, 0xc2, 0xdd, 0x08, 0x45, 0x79, 0x41, 0xa6 \ +} + + +/***************************************************** + * PUBLIC FUNCTIONS DECLARATIONS * + *****************************************************/ + +// Exported test cases + +char *test_aes256ctr_encrypt_inplace(); +char *test_aes256ctr_encrypt_outofplace(); +char *test_aes256ctr_decrypt_inplace(); +char *test_aes256ctr_decrypt_outofplace(); + + +#endif /* _TEST_CRYPTO_AES256CTR_H_ */ diff --git a/vuvuzela-userland/test/crypto/test_aes256gcm.c b/vuvuzela-userland/test/crypto/test_aes256gcm.c new file mode 100644 index 0000000..b332c51 --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_aes256gcm.c @@ -0,0 +1,160 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "utils/crypto.h" +#include "test_aes256gcm.h" +#include "minunit.h" +#include "utils/log.h" + + +/***************************************************** + * CONSTANT VARIABLES * + *****************************************************/ + +static const char KEY[] = AES256GCM_TEST_KEY; +static const char IV[] = AES256GCM_TEST_IV; +static const char PT[] = AES256GCM_TEST_PT; +static const char CT[] = AES256GCM_TEST_CT; +static const char TAG[] = AES256GCM_TEST_TAG; + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +char *test_aes256gcm_encrypt() +{ + char pt[] = AES256GCM_TEST_PT; + size_t pt_len = sizeof(pt); + char key[] = AES256GCM_TEST_KEY; + char iv[] = AES256GCM_TEST_IV; + char ct[sizeof(pt)]; + char tag[SFLC_AESGCM_TAGLEN]; + int err; + + sflc_log_blue("Testing AES256-GCM encryption"); + + // Encrypt + err = sflc_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); + + // Check error + mu_assert("Error while encrypting", !err); + + // Check outcome + mu_assert("Ciphertext mismatch", memcmp(ct, CT, pt_len) == 0); + mu_assert("MAC mismatch", memcmp(tag, TAG, SFLC_AESGCM_TAGLEN) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + +char *test_aes256gcm_decrypt_good() +{ + char ct[] = AES256GCM_TEST_CT; + size_t ct_len = sizeof(ct); + char tag[] = AES256GCM_TEST_TAG; + char key[] = AES256GCM_TEST_KEY; + char iv[] = AES256GCM_TEST_IV; + bool match; + char pt[sizeof(ct)]; + int err; + + sflc_log_blue("Testing AES256-GCM decryption with the proper MAC"); + + // Decrypt + err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + + // Check error + mu_assert("Error while decrypting", !err); + + // Check outcome + mu_assert("MAC verification failed", match); + mu_assert("Plaintext mismatch", memcmp(pt, PT, ct_len) == 0); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + // Check MAC untouched + mu_assert("MAC changed", memcmp(tag, TAG, sizeof(tag)) == 0); + + sflc_log_green("OK"); + + return NULL; +} + +char *test_aes256gcm_decrypt_fail() +{ + char ct[] = AES256GCM_TEST_CT; + size_t ct_len = sizeof(ct); + char tag[] = AES256GCM_TEST_TAG; + char key[] = AES256GCM_TEST_KEY; + char iv[] = AES256GCM_TEST_IV; + bool match; + char pt[sizeof(ct)]; + int err; + + sflc_log_blue("Testing AES256-GCM decryption without the proper MAC"); + + // Corrupt the MAC + tag[0] += 1; + + // Decrypt + err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + + // Check error + mu_assert("Error while decrypting", !err); + + // Check outcome + mu_assert("MAC verification succeeded", !match); + + // Check key untouched + mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); + + // Check IV untouched + mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); + + // Check MAC untouched + mu_assert("Tail of MAC changed", memcmp(tag+1, TAG+1, sizeof(tag)-1) == 0); + mu_assert("Head of MAC changed", tag[0] == TAG[0]+1); + + sflc_log_green("OK"); + + return NULL; +} diff --git a/vuvuzela-userland/test/crypto/test_aes256gcm.h b/vuvuzela-userland/test/crypto/test_aes256gcm.h new file mode 100644 index 0000000..9ad445b --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_aes256gcm.h @@ -0,0 +1,91 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +// Test vectors taken from test case 15 at +// https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/gcm-spec.pdf + +#ifndef _TEST_CRYPTO_AES256GCM_H_ +#define _TEST_CRYPTO_AES256GCM_H_ + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +#define AES256GCM_TEST_KEY \ +{ \ + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, \ + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, \ + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, \ + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 \ +} + +#define AES256GCM_TEST_IV \ +{ \ + 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, \ + 0xde, 0xca, 0xf8, 0x88 \ +} + +#define AES256GCM_TEST_PT \ +{ \ + 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, \ + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, \ + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, \ + 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, \ + 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, \ + 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, \ + 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, \ + 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 \ +} + +#define AES256GCM_TEST_CT \ +{ \ + 0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, \ + 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d, \ + 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, \ + 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa, \ + 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, \ + 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, \ + 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a, \ + 0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad, \ +} + +#define AES256GCM_TEST_TAG \ +{ \ + 0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd, \ + 0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3, 0xcc, 0x6c, \ +} + + +/***************************************************** + * PUBLIC FUNCTIONS DECLARATIONS * + *****************************************************/ + +// Exported test cases + +char *test_aes256gcm_encrypt(); +char *test_aes256gcm_decrypt_good(); +char *test_aes256gcm_decrypt_fail(); + + +#endif /* _TEST_CRYPTO_AES256GCM_H_ */ diff --git a/vuvuzela-userland/test/crypto/test_argon2id.c b/vuvuzela-userland/test/crypto/test_argon2id.c new file mode 100644 index 0000000..0ded58b --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_argon2id.c @@ -0,0 +1,83 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include + +#include "utils/crypto.h" +#include "test_argon2id.h" +#include "minunit.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * CONSTANT VARIABLES * + *****************************************************/ + +char SALT[SFLC_ARGON_SALTLEN+1] = "Poor Petrol Pump"; + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +char *test_argon2id() +{ + char pwd[SFLC_BIGBUFSIZE]; + char key[SFLC_CRYPTO_KEYLEN]; + int err; + + sflc_log_blue("Testing Argon2id interactively with a fixed salt"); + + // Loop until user breaks out + while (true) { + /* Collect password */ + printf("Choose password to hash (empty to skip): "); + err = sflc_safeReadLine(pwd, SFLC_BIGBUFSIZE); + mu_assert("Could not read password", !err); + + /* Check if empty */ + if (strlen(pwd) == 0) { + break; + } + + /* Hash it */ + err = sflc_argon2id_derive(pwd, strlen(pwd), SALT, key); + mu_assert("Could not hash password", !err); + + /* Print it */ + printf("Salt used ASCII: \"%s\". Hex:\n", SALT); + sflc_log_hex(SALT, SFLC_ARGON_SALTLEN); + printf("Argon2id hash (m = %d, t = %d, p = %d):\n", + SFLC_ARGON_M, SFLC_ARGON_T, SFLC_ARGON_P); + sflc_log_hex(key, SFLC_CRYPTO_KEYLEN); + printf("Go check the result online\n\n"); + } + + return NULL; +} diff --git a/vuvuzela-userland/test/crypto/test_argon2id.h b/vuvuzela-userland/test/crypto/test_argon2id.h new file mode 100644 index 0000000..12bd2c0 --- /dev/null +++ b/vuvuzela-userland/test/crypto/test_argon2id.h @@ -0,0 +1,43 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + + +#ifndef _TEST_CRYPTO_ARGON2ID_H_ +#define _TEST_CRYPTO_ARGON2ID_H_ + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + + +/***************************************************** + * PUBLIC FUNCTIONS DECLARATIONS * + *****************************************************/ + +// Exported test cases + +char *test_argon2id(); + + +#endif /* _TEST_CRYPTO_ARGON2ID_H_ */ diff --git a/vuvuzela-userland/test/main.c b/vuvuzela-userland/test/main.c new file mode 100644 index 0000000..565aa2c --- /dev/null +++ b/vuvuzela-userland/test/main.c @@ -0,0 +1,80 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + + +#include "crypto/test_aes256ctr.h" +#include "crypto/test_aes256gcm.h" +#include "crypto/test_argon2id.h" +#include "minunit.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +static char *all_tests(); + + +/***************************************************** + * MAIN * + *****************************************************/ + +int main() +{ + char *result = all_tests(); + + if (result != NULL) { + sflc_log_red("\nTEST FAILED: %s", result); + } + else { + sflc_log_green("\nALL TESTS PASSED"); + } + + return result != NULL; +} + + +/***************************************************** + * PRIVATE FUNCTIONS DEFINITIONS * + *****************************************************/ + +static char *all_tests() +{ + sflc_log_yellow("Running crypto tests"); + mu_run_test(test_aes256ctr_encrypt_inplace); + mu_run_test(test_aes256ctr_encrypt_outofplace); + mu_run_test(test_aes256ctr_decrypt_inplace); + mu_run_test(test_aes256ctr_decrypt_outofplace); + mu_run_test(test_aes256gcm_encrypt); + mu_run_test(test_aes256gcm_decrypt_good); + mu_run_test(test_aes256gcm_decrypt_fail); + mu_run_test(test_argon2id); + + return 0; +} + diff --git a/vuvuzela-userland/test/minunit.h b/vuvuzela-userland/test/minunit.h new file mode 100644 index 0000000..4181aa1 --- /dev/null +++ b/vuvuzela-userland/test/minunit.h @@ -0,0 +1,10 @@ +// MinUnit -- a minimal unit testing framework for C. Slightly modified. +// https://jera.com/techinfo/jtns/jtn002 + +#ifndef _MINUNIT_H_ +#define _MINUNIT_H_ + +#define mu_assert(message, test) do { if (!(test)) return message; } while (0) +#define mu_run_test(test) do { char *message = test(); if (message) return message; } while (0) + +#endif // _MINUNIT_H_ From 2eefd23e189ea74f4271fbc6f31be6840706a525 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 7 Apr 2024 23:54:42 +0200 Subject: [PATCH 44/98] Fix EPM --- vuvuzela-userland/include/header.h | 51 ++++----- vuvuzela-userland/include/operations.h | 23 ---- vuvuzela-userland/include/utils/crypto.h | 17 +-- vuvuzela-userland/include/utils/disk.h | 68 +++++------- vuvuzela-userland/include/utils/input.h | 2 +- vuvuzela-userland/include/utils/sflc.h | 27 ++--- vuvuzela-userland/src/commands/init.c | 12 +-- .../src/header/device_master_block.c | 22 ++-- vuvuzela-userland/src/header/position_map.c | 100 +++++------------- .../src/header/volume_master_block.c | 16 +-- vuvuzela-userland/src/operations/devmapper.c | 4 +- vuvuzela-userland/src/operations/dmb.c | 14 +-- .../src/operations/volume_header.c | 12 +-- vuvuzela-userland/src/utils/crypto.c | 82 ++++++++++++-- vuvuzela-userland/src/utils/disk.c | 30 ++---- vuvuzela-userland/test/crypto/test_argon2id.c | 4 +- 16 files changed, 219 insertions(+), 265 deletions(-) diff --git a/vuvuzela-userland/include/header.h b/vuvuzela-userland/include/header.h index 869b868..32d5596 100644 --- a/vuvuzela-userland/include/header.h +++ b/vuvuzela-userland/include/header.h @@ -40,16 +40,16 @@ *****************************************************/ /* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_CRYPTO_KEYLEN + SFLC_AESGCM_TAGLEN) +#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_STANDARD_KEYLEN + SFLC_AESGCM_TAGLEN) /* Let us enforce that the one DMB can fit cells for all volumes */ -#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL) > SFLC_SECTOR_SIZE +#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL_SIZE) > SFLC_BLOCK_SIZE #error "Invalid combination of parameters: probably SFLC_DEV_MAX_VOLUMES is too big" #endif // The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define SFLC_CLEAR_VMB_LEN (SFLC_SECTOR_SIZE - \ +#define SFLC_CLEAR_VMB_LEN (SFLC_BLOCK_SIZE - \ SFLC_AESGCM_PADDED_IVLEN - \ SFLC_AESGCM_TAGLEN) @@ -66,7 +66,7 @@ */ typedef struct { // Each volume's VMB key - char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_CRYPTO_KEYLEN]; + char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_STANDARD_KEYLEN]; // How many of these need actually be encrypted size_t nr_vols; @@ -80,7 +80,7 @@ typedef struct { */ typedef struct { // The unlocked VMB key - char vmb_key[SFLC_CRYPTO_KEYLEN]; + char vmb_key[SFLC_STANDARD_KEYLEN]; // The index of the volume opened by this VMB key size_t vol_idx; @@ -95,10 +95,10 @@ typedef struct { */ typedef struct { // The key that encrypts the volume's data section - char volume_key[SFLC_CRYPTO_KEYLEN]; + char volume_key[SFLC_STANDARD_KEYLEN]; // The key that encrypts the previous volume's master block - char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; + char prev_vmb_key[SFLC_STANDARD_KEYLEN]; // The total number of logical slices virtually available to this volume size_t nr_slices; @@ -106,33 +106,18 @@ typedef struct { } sflc_Vmb; -/** - * This struct represents an encrypted empty position map. - * On-disk, the layout interleaves one IV block with 256 PosMap blocks (each - * encrypted by an IV in the IV block). Many such "runs" can be concatenated, - * until the position map is big enough to index the desired number of slices. - * The last "run" might be incomplete, in that it could have less than 256 - * PosMap blocks, if not all of them are needed. - * In the struct, there are as many IV blocks as there are PosMapBlock arrays - * (equal to the number of "runs"). The m-th IV of the n-th IV block encrypts - * the m-th block of the n-th array. The PosMapBlocks in an array are stored - * contiguously in RAM, so a PosMapBlock array is just a char array of length - * multiple of 4096. All the arrays are full (256 PosMapBlocks, 1 MiB) except - * for the last one, which may hold fewer blocks. - */ -typedef struct { - // The number of PosMapBlock arrays (and of IV blocks) - size_t nr_arrays; +/***************************************************** + * INLINE FUNCTIONS * + *****************************************************/ - // The sequence of IV blocks - char **iv_blocks; - // The sequence of (encrypted) PosMapBlock arrays - char **pmb_arrays; - // The number of PosMapBlocks in the last array - size_t nr_last_pmbs; - -} sflc_EncPosMap; +// Starting block of a volume's position map +static inline uint64_t sflc_pmStartBlock(size_t vol_idx, size_t nr_slices) +{ + return 1 + + SFLC_DEV_MAX_VOLUMES + + vol_idx*ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); +} /***************************************************** @@ -154,7 +139,7 @@ int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); /* Create an encrypted empty position map for the given number of slices (allocates memory) */ -int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm); +void *sflc_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); diff --git a/vuvuzela-userland/include/operations.h b/vuvuzela-userland/include/operations.h index 8de9ab6..13bebde 100644 --- a/vuvuzela-userland/include/operations.h +++ b/vuvuzela-userland/include/operations.h @@ -37,29 +37,6 @@ #include "utils/math.h" -/***************************************************** - * INLINE FUNCTIONS * - *****************************************************/ - -// Size, in 4096-byte blocks, of a whole volume header (VMB+PM) -static inline size_t sflc_volHeaderSize(size_t nr_slices) -{ - // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - size_t nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); - // Each array holds up to 256 PosMapBlocks - size_t nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); - - // 1 VMB, the PMBs, and the IV blocks - return 1 + nr_pmbs + nr_arrays; -} - -// Position of the VMB for the given volume -static inline uint64_t sflc_vmbPosition(size_t vol_idx, size_t nr_slices) -{ - return 1 + ((uint64_t) vol_idx) * ((uint64_t) sflc_volHeaderSize(nr_slices)); -} - - /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ diff --git a/vuvuzela-userland/include/utils/crypto.h b/vuvuzela-userland/include/utils/crypto.h index 15ac111..8e5f4d8 100644 --- a/vuvuzela-userland/include/utils/crypto.h +++ b/vuvuzela-userland/include/utils/crypto.h @@ -41,20 +41,21 @@ *****************************************************/ // Key length, for input into AES-CTR and AES-GCM, and for output from Argon -#define SFLC_CRYPTO_KEYLEN 32 /* bytes */ +#define SFLC_STANDARD_KEYLEN 32 /* bytes */ +// Key length for AES-XTS +#define SFLC_AESXTS_KEYLEN 64 /* bytes */ // IV length for AES-CTR #define SFLC_AESCTR_IVLEN 16 /* bytes */ +// IV length for AES-XTS +#define SFLC_AESXTS_IVLEN 16 /* bytes */ // IV length for AES-GCM #define SFLC_AESGCM_IVLEN 12 /* bytes */ - // IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM #define SFLC_AESGCM_PADDED_IVLEN 16 /* bytes */ - // MAC length for AES-GCM #define SFLC_AESGCM_TAGLEN 16 /* bytes */ - // Content of output plaintext upon MAC verification failure #define SFLC_AESGCM_POISON_PT 0xFF @@ -81,9 +82,9 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -/* Get slow, true random bytes (suited for keys) */ +/* Get slow, strong random bytes (suited for keys) */ int sflc_rand_getStrongBytes(char *buf, size_t buflen); -/* Get fast, pseudo random bytes (suited for IVs and padding) */ +/* Get fast, weak(er) random bytes (suited for IVs and padding) */ int sflc_rand_getWeakBytes(char *buf, size_t buflen); /* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ @@ -91,6 +92,10 @@ int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c /* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); +/* AES256-XTS encryption. Set ct = NULL for in-place. + * The IV is intepreted as a little-endian "sector number", and incremented by 1 */ +int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); + /* AES256-GCM encryption, does not touch the IV */ int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); /* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ diff --git a/vuvuzela-userland/include/utils/disk.h b/vuvuzela-userland/include/utils/disk.h index 7666609..00dd91c 100644 --- a/vuvuzela-userland/include/utils/disk.h +++ b/vuvuzela-userland/include/utils/disk.h @@ -35,6 +35,7 @@ #include #include +#include "utils/math.h" #include "utils/sflc.h" @@ -44,44 +45,27 @@ /** * Max slices for given disk size (in 4096-byte blocks). - * - * The bigger a disk is, the more slices it can host. However, the more slices we format it with, - * the bigger the position map needed to index them: the header size grows with the number of slices, - * taking up part of the space that's supposed to host those slices. - * To settle the matter, let us derive an upper bound on the header size, yielding a "safe" value - * for the number of slices (given a disk size). - * - * To index s slices, we need pm := ceil(s/1024) <= s/1024 + 1 PosMap blocks, since each PosMap - * block (4096 bytes) can host 1024 slice indices (4 bytes each). - * - * To encrypt those PosMap blocks, we need iv := ceil(pm/256) <= pm IV blocks, since each IV block - * (4096 bytes) can encrypt 256 data blocks (IVs are 16 bytes). - * - * Therefore, a position map indexing s slices occupies pm+iv <= 2*pm <= 2*s/1024 + 2 blocks. - * - * A single volume's header contains the Volume Master Block and the position map, therefore it - * occupies 1+pm+iv <= 2*s/1024 + 3 blocks. - * - * The entire device's header simply contains 15 volume headers of the same size, therefore it - * occupies h := 15 * (1+pm+iv) <= 15*2*s/1024 + 3*15 <= s + 3*15 blocks. - * The last inequality follows from 15*2/1024 <= 1 (we need to enforce this on the symbolic values). - * - * To actually host the s slices, the data section needs 257*s blocks (256 data blocks + 1 IV block - * per slice). - * - * Therefore, in order to format a disk with s slices, we need at most (s + 3*15) + 257*s = - * = (1 + 257)*s + 3*15 blocks. - * - * If we are given d blocks on the disk, a safe value for s is one that satisfies - * (1 + 257)*s + 3*15 <= d <==> s <= (d - 3*15) / (1 + 257) */ -#define sflc_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLC_BLOCKS_PER_PHYS_SLICE) +static inline uint32_t sflc_disk_maxSlices(uint64_t size) { + uint64_t nr_slices; + // Start from upper bound + nr_slices = size / SFLC_SLICE_SCALE; + while(true) { + if (nr_slices == 0) + break; -/* Let us enforce, on the symbolic values, the inequality used in the previous bound */ -#if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK -#error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" -#endif + // Stop when this nr_slices can fit in size, including the header + uint64_t posmap_blocks = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + uint64_t header_size = 1 + SFLC_DEV_MAX_VOLUMES * (1 + posmap_blocks); + if (header_size + nr_slices*SFLC_SLICE_SCALE <= size) + break; + + nr_slices--; + } + + return nr_slices > SFLC_MAX_SLICES ? SFLC_MAX_SLICES : (uint32_t) nr_slices; +} /***************************************************** @@ -92,16 +76,16 @@ bool sflc_disk_isBlockDevice(char *path); /* Returns the size in 4096-byte sectors (or < 0 if error) */ -int64_t sflc_disk_getSize(char * bdev_path); +int64_t sflc_disk_getSize(char *bdev_path); -/* Reads a single 4096-byte sector from the disk */ -int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf); +/* Reads a single 4096-byte block from the disk */ +int sflc_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); -/* Writes a single 4096-byte sector to the disk */ -int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf); +/* Writes many 4096-byte blocks to the disk */ +int sflc_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); -/* Writes many 4096-byte sectors to the disk */ -int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors); +/* Writes a single 4096-byte block to the disk */ +#define sflc_disk_writeBlock(bdev_path, bnum, buf) sflc_disk_writeManyBlocks(bdev_path, bnum, buf, 1) #endif /* _UTILS_DISK_H_ */ diff --git a/vuvuzela-userland/include/utils/input.h b/vuvuzela-userland/include/utils/input.h index 9463999..52e07fb 100644 --- a/vuvuzela-userland/include/utils/input.h +++ b/vuvuzela-userland/include/utils/input.h @@ -43,4 +43,4 @@ int sflc_safeReadLine(char *buf, size_t bufsize); /* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ int sflc_safeReadPassphrase(char *buf, size_t bufsize); -#endif /* _UTILS_FILE_H_ */ +#endif /* _UTILS_INPUT_H_ */ diff --git a/vuvuzela-userland/include/utils/sflc.h b/vuvuzela-userland/include/utils/sflc.h index e8135e4..99d0243 100644 --- a/vuvuzela-userland/include/utils/sflc.h +++ b/vuvuzela-userland/include/utils/sflc.h @@ -39,12 +39,14 @@ *****************************************************/ /* Name of the DM target in the kernel */ -#define SFLC_DM_TARGET_NAME "shufflecake" +#define SFLC_DM_TARGET_NAME "vvz" -/* Disk constants */ -#define SFLC_SECTOR_SIZE 4096 /* bytes */ -#define KERNEL_SECTOR_SIZE 512 /* bytes */ -#define SFLC_SECTOR_SCALE (SFLC_SECTOR_SIZE / KERNEL_SECTOR_SIZE) +/* Sizes */ +#define KERNEL_SECTOR_SIZE 512 /* bytes */ +#define SFLC_BLOCK_SIZE 4096 /* bytes */ +#define SFLC_BLOCK_SCALE (SFLC_BLOCK_SIZE / KERNEL_SECTOR_SIZE) +#define SFLC_SLICE_SCALE 256 /* blocks in a slice */ +#define SFLC_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ /* Max number of volumes in a device */ #define SFLC_DEV_MAX_VOLUMES 15 @@ -57,25 +59,16 @@ /* A slice index is represented over 32 bits */ #define SFLC_SLICE_IDX_WIDTH 4 /* bytes */ /* A position map block contains 1024 slice indices */ -#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_SECTOR_SIZE / SFLC_SLICE_IDX_WIDTH) +#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_BLOCK_SIZE / SFLC_SLICE_IDX_WIDTH) -// IV length for AES-CTR -#define SFLC_AESCTR_IVLEN 16 /* bytes */ - -/* An IV block can encrypt 256 data blocks */ -#define SFLC_DATA_BLOCKS_PER_IV_BLOCK (SFLC_SECTOR_SIZE / SFLC_AESCTR_IVLEN) -/* A logical slice spans 256 blocks of data (1 MiB) */ -#define SFLC_BLOCKS_PER_LOG_SLICE SFLC_DATA_BLOCKS_PER_IV_BLOCK -/* A physical slice also includes the IV block */ -#define SFLC_BLOCKS_PER_PHYS_SLICE (1 + SFLC_BLOCKS_PER_LOG_SLICE) /* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ #define SFLC_EPM_FILLER 0xFF /* The sysfs file containing the next available device ID */ -#define SFLC_SYSFS_NEXTDEVID "/sys/devices/sflc/next_dev_id" +#define SFLC_SYSFS_NEXTDEVID "/sys/module/dm_vvz/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ -#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" +#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_vvz/bdevs" /* Within each bdev's subdir, this file lists its open volumes */ #define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" diff --git a/vuvuzela-userland/src/commands/init.c b/vuvuzela-userland/src/commands/init.c index cd21b03..e27e840 100644 --- a/vuvuzela-userland/src/commands/init.c +++ b/vuvuzela-userland/src/commands/init.c @@ -44,7 +44,7 @@ /* The device is randomised in chunks of 1024 blocks (arbitrary number) */ #define SFLC_BLOCKS_IN_RAND_CHUNK 1024 /* That's 4 MiB */ -#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_SECTOR_SIZE) +#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_BLOCK_SIZE) /***************************************************** @@ -111,7 +111,7 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) /* Sample the VMB keys */ size_t i; for (i = 0; i < dmb.nr_vols; i++) { - err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_CRYPTO_KEYLEN); + err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_STANDARD_KEYLEN); if (err) { sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); return err; @@ -129,10 +129,10 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) for (i = 0; i < args->nr_vols; i++) { /* This volume's prev_vmb_key */ if (i > 0) { - memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_CRYPTO_KEYLEN); + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_STANDARD_KEYLEN); } /* Sample this volume's VEK */ - sflc_rand_getStrongBytes(vmb.volume_key, SFLC_CRYPTO_KEYLEN); + sflc_rand_getStrongBytes(vmb.volume_key, SFLC_STANDARD_KEYLEN); /* Write complete volume header (VMB + PM) */ err = sflc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); @@ -170,7 +170,7 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) while (blocks_remaining > 0) { uint64_t blocks_to_write = (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; - uint64_t bytes_to_write = blocks_to_write * SFLC_SECTOR_SIZE; + uint64_t bytes_to_write = blocks_to_write * SFLC_BLOCK_SIZE; /* Sample random bytes */ err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); @@ -180,7 +180,7 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) } /* Write on disk */ - err = sflc_disk_writeManySectors(bdev_path, sector, rand_chunk, blocks_to_write); + err = sflc_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); if (err) { sflc_log_error("Could not write random bytes on disk; error %d", err); goto out; diff --git a/vuvuzela-userland/src/header/device_master_block.c b/vuvuzela-userland/src/header/device_master_block.c index cdd1f8c..1cfcd9b 100644 --- a/vuvuzela-userland/src/header/device_master_block.c +++ b/vuvuzela-userland/src/header/device_master_block.c @@ -72,7 +72,7 @@ int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block } /* Randomise whole block */ - err = sflc_rand_getWeakBytes(disk_block, SFLC_SECTOR_SIZE); + err = sflc_rand_getWeakBytes(disk_block, SFLC_BLOCK_SIZE); if (err) { sflc_log_error("Could not randomise DMB; error %d", err); return err; @@ -115,9 +115,9 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *d // KDF salt char *salt; // The KDF-derived key - char kek[SFLC_CRYPTO_KEYLEN]; + char kek[SFLC_STANDARD_KEYLEN]; // The unlocked VMB key - char vmb_key[SFLC_CRYPTO_KEYLEN]; + char vmb_key[SFLC_STANDARD_KEYLEN]; // Error code int err; @@ -149,7 +149,7 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *d if (match) { sflc_log_debug("The provided password unlocks volume %lu", i); dmb_cell->vol_idx = i; - memcpy(dmb_cell->vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); + memcpy(dmb_cell->vmb_key, vmb_key, SFLC_STANDARD_KEYLEN); } } @@ -160,7 +160,7 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *d bad_decrypt: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_CRYPTO_KEYLEN); + memset(kek, 0, SFLC_STANDARD_KEYLEN); return err; } @@ -211,9 +211,9 @@ static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vm // Pointers inside the block char *iv = dmb_cell; char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; + char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; // Key-encryption-key derived from KDF - char kek[SFLC_CRYPTO_KEYLEN]; + char kek[SFLC_STANDARD_KEYLEN]; // Error code int err; @@ -234,7 +234,7 @@ static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vm sflc_log_debug("Successfully sampled prologue IV"); /* Encrypt the VMB key */ - err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_CRYPTO_KEYLEN, iv, enc_vmb_key, mac); + err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_STANDARD_KEYLEN, iv, enc_vmb_key, mac); if (err) { sflc_log_error("Could not encrypt the VMB key: error %d", err); goto bad_encrypt; @@ -249,7 +249,7 @@ bad_encrypt: bad_sample_iv: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_CRYPTO_KEYLEN); + memset(kek, 0, SFLC_STANDARD_KEYLEN); return err; } @@ -259,12 +259,12 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool // Pointers inside the block char *iv = dmb_cell; char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; + char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; // Error code int err; /* Decrypt the VMB key */ - err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_CRYPTO_KEYLEN, mac, iv, vmb_key, match); + err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_STANDARD_KEYLEN, mac, iv, vmb_key, match); if (err) { sflc_log_error("Error while decrypting VMB key: error %d", err); return err; diff --git a/vuvuzela-userland/src/header/position_map.c b/vuvuzela-userland/src/header/position_map.c index f2b8144..051161f 100644 --- a/vuvuzela-userland/src/header/position_map.c +++ b/vuvuzela-userland/src/header/position_map.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "header.h" #include "utils/sflc.h" @@ -41,94 +42,49 @@ *****************************************************/ /* Create an encrypted empty position map for the given number of slices. - * Allocates the internal pointers of the EPM structure. - * On failure, does not free the allocated memory. + * Allocates the memory (whole number of blocks) needed to host it. * * @param nr_slices The number of slices the device will be composed of. * @param volume_key The volume's data section encryption key, used to encrypt the * position map as well. - * @param epm The EncPosMap struct to be initialised. * - * @return Error code, 0 on success */ -int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm) + * @return The memory buffer containing the position map */ +void *sflc_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) { + char pmb[SFLC_BLOCK_SIZE]; + char iv[SFLC_AESXTS_IVLEN]; + void *epm; size_t nr_pmbs; - size_t nr_arrays; int err; // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); - // Each array holds up to 256 PosMapBlocks - nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); - // Fill the EPM numeric fields - epm->nr_arrays = nr_arrays; - // All arrays are full except the last one - epm->nr_last_pmbs = nr_pmbs - (SFLC_BLOCKS_PER_LOG_SLICE * (nr_arrays - 1)); - - // Allocate array of IV blocks - epm->iv_blocks = malloc(nr_arrays * sizeof(char *)); - if (!epm->iv_blocks) { - sflc_log_error("Could not malloc array of IV blocks"); - err = ENOMEM; - goto out; - } - // Allocate array of PosMapBlock arrays - epm->pmb_arrays = malloc(nr_arrays * sizeof(char *)); - if (!epm->pmb_arrays) { - sflc_log_error("Could not malloc array of PosMapBlock arrays"); - err = ENOMEM; - goto out; + // Allocate EPM + epm = malloc(nr_pmbs * SFLC_BLOCK_SIZE); + if (!epm) { + sflc_log_error("Could not malloc EPM array"); + return NULL; } - // Loop to allocate and encrypt each array + // Fill cleartext PMB with 0xFF + memset(pmb, SFLC_EPM_FILLER, SFLC_BLOCK_SIZE); + // Set the IV for the first encryption + memset(iv, 0, SFLC_AESXTS_IVLEN); + *((uint64_t)iv) = htole64(sflc_pmStartBlock(vol_idx, nr_slices)); + + // Loop to encrypt each PMB int i; - for (i = 0; i < nr_arrays; i++) { - // The last PMB array might be smaller - size_t nr_pmbs_here = ((i == nr_arrays - 1) ? epm->nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); - size_t pmb_array_size = SFLC_SECTOR_SIZE * nr_pmbs_here; - char *iv_block; - char *pmb_array; - - // Allocate IV block - epm->iv_blocks[i] = malloc(SFLC_SECTOR_SIZE); - if (!epm->iv_blocks[i]) { - sflc_log_error("Could not allocate IV block number %d", i); - err = ENOMEM; - goto out; - } - // Allocate PosMapBlock array - epm->pmb_arrays[i] = malloc(pmb_array_size); - if (!epm->pmb_arrays[i]) { - sflc_log_error("Could not allocate PMB array number %d", i); - err = ENOMEM; - goto out; - } - // Shorthand - iv_block = epm->iv_blocks[i]; - pmb_array = epm->pmb_arrays[i]; - - // Fill the IV block with random data (can ignore return value) - sflc_rand_getWeakBytes(iv_block, SFLC_SECTOR_SIZE); - // Fill the PMB array with 0xFF - memset(pmb_array, SFLC_EPM_FILLER, pmb_array_size); - - // Loop to encrypt each PMB separately with its IV - int j; - for (j = 0; j < nr_pmbs_here; j++) { - char *iv = iv_block + (j * SFLC_AESCTR_IVLEN); - char *pmb = pmb_array + (j * SFLC_SECTOR_SIZE); - - // Encrypt in-place - err = sflc_aes256ctr_encrypt(volume_key, pmb, SFLC_SECTOR_SIZE, iv, NULL); - if (err) { - sflc_log_error("Could not encrypt PMB %d of array %d", j, i); - goto out; - } + for (i = 0; i < nr_pmbs; i++) { + // Encrypt. Auto-increment IV for next encryption + err = sflc_aes256xts_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, + epm + i*SFLC_BLOCK_SIZE); + if (err) { + sflc_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); + free(epm); + return NULL; } } - -out: - return err; + return epm; } diff --git a/vuvuzela-userland/src/header/volume_master_block.c b/vuvuzela-userland/src/header/volume_master_block.c index 9347582..d73d98d 100644 --- a/vuvuzela-userland/src/header/volume_master_block.c +++ b/vuvuzela-userland/src/header/volume_master_block.c @@ -181,14 +181,14 @@ static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, SFLC_CRYPTO_KEYLEN); + memcpy(p_vol_key, vmb->volume_key, SFLC_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_CRYPTO_KEYLEN); + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_STANDARD_KEYLEN); /* Write the number of slices (network byte order) */ *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); @@ -204,14 +204,14 @@ static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, SFLC_CRYPTO_KEYLEN); + memcpy(vmb->volume_key, p_vol_key, SFLC_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_CRYPTO_KEYLEN); + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_STANDARD_KEYLEN); /* Read number of slices (network byte order) */ vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); diff --git a/vuvuzela-userland/src/operations/devmapper.c b/vuvuzela-userland/src/operations/devmapper.c index 99a66a2..91b3b51 100644 --- a/vuvuzela-userland/src/operations/devmapper.c +++ b/vuvuzela-userland/src/operations/devmapper.c @@ -68,7 +68,7 @@ int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vmb->volume_key, SFLC_CRYPTO_KEYLEN); + hex_key = sflc_toHex(vmb->volume_key, SFLC_STANDARD_KEYLEN); if (!hex_key) { sflc_log_error("Could not encode volume key to hexadecimal"); err = ENOMEM; @@ -76,7 +76,7 @@ int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb } /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; + num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_BLOCK_SCALE; /* Build param list */ sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); diff --git a/vuvuzela-userland/src/operations/dmb.c b/vuvuzela-userland/src/operations/dmb.c index d529966..662742e 100644 --- a/vuvuzela-userland/src/operations/dmb.c +++ b/vuvuzela-userland/src/operations/dmb.c @@ -57,7 +57,7 @@ int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) { /* On-disk DMB */ - char enc_dmb[SFLC_SECTOR_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; /* Error code */ int err; @@ -75,7 +75,7 @@ int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb * } /* Write it to disk (at sector 0) */ - err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); + err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { sflc_log_error("Could not write DMB to disk; error %d", err); return err; @@ -99,11 +99,11 @@ int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb * */ int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) { - char enc_dmb[SFLC_SECTOR_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; int err; /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readSector(bdev_path, 0, enc_dmb); + err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { sflc_log_error("Could not read DMB from disk; error %d", err); return err; @@ -131,7 +131,7 @@ int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *d */ int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) { - char enc_dmb[SFLC_SECTOR_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; int err; /* Sanity check */ @@ -141,7 +141,7 @@ int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_p } /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readSector(bdev_path, 0, enc_dmb); + err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { sflc_log_error("Could not read DMB from disk; error %d", err); return err; @@ -155,7 +155,7 @@ int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_p } /* Write it to disk (at sector 0) */ - err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); + err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { sflc_log_error("Could not write DMB to disk; error %d", err); return err; diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c index 1088290..d35acdd 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -55,7 +55,7 @@ */ int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) { - char enc_vmb[SFLC_SECTOR_SIZE]; + char enc_vmb[SFLC_BLOCK_SIZE]; sflc_EncPosMap epm; uint64_t sector; int err; @@ -69,7 +69,7 @@ int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, si // Write it to disk sector = sflc_vmbPosition(vol_idx, vmb->nr_slices); - err = sflc_disk_writeSector(bdev_path, sector, enc_vmb); + err = sflc_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { sflc_log_error("Could not write VMB to disk; error %d", err); goto out; @@ -91,7 +91,7 @@ int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, si size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); // First write the IV block - err = sflc_disk_writeSector(bdev_path, sector, iv_block); + err = sflc_disk_writeBlock(bdev_path, sector, iv_block); if (err) { sflc_log_error("Could not write IV block to disk; error %d", err); goto out; @@ -99,7 +99,7 @@ int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, si sector += 1; // Then the whole PMB array - err = sflc_disk_writeManySectors(bdev_path, sector, pmb_array, nr_pmbs); + err = sflc_disk_writeManyBlocks(bdev_path, sector, pmb_array, nr_pmbs); if (err) { sflc_log_error("Could not write PMB array to disk; error %d", err); goto out; @@ -135,13 +135,13 @@ out: */ int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) { - char enc_vmb[SFLC_SECTOR_SIZE]; + char enc_vmb[SFLC_BLOCK_SIZE]; uint64_t sector; int err; /* Read encrypted VMB from disk */ sector = sflc_vmbPosition(vol_idx, nr_slices); - err = sflc_disk_readSector(bdev_path, sector, enc_vmb); + err = sflc_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { sflc_log_error("Could not read VMB from disk; error %d", err); return err; diff --git a/vuvuzela-userland/src/utils/crypto.c b/vuvuzela-userland/src/utils/crypto.c index b42f50f..dfa6f95 100644 --- a/vuvuzela-userland/src/utils/crypto.c +++ b/vuvuzela-userland/src/utils/crypto.c @@ -95,7 +95,7 @@ int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; @@ -163,7 +163,7 @@ int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *p sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; @@ -205,6 +205,76 @@ bad_open: } +/** + * AES256-XTS encryption. Set ct = NULL for in-place. + * The IV is intepreted as a little-endian "sector number", and incremented by 1. + * + * @param key The 64-byte AES-XTS key + * @param pt The plaintext + * @param pt_len The length of the plaintext, must be a multiple of the AES + * block size (16 bytes) + * @param iv The 16-byte AES-XTS IV + * @param ct A caller-allocated buffer that will contain the output ciphertext, + * cannot overlap with pt. If NULL, in-place encryption. + * + * @return The error code (0 on success) + */ +int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +{ + gcry_cipher_hd_t hd; + gcry_error_t err; + + // Instantiate the handle + err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_XTS, GCRY_CIPHER_SECURE); + if (err) { + sflc_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); + goto bad_open; + } + sflc_log_debug("Successfully instantiated AES256-XTS cipher handle"); + + // Set the key + err = gcry_cipher_setkey(hd, key, SFLC_AESXTS_KEYLEN); + if (err) { + sflc_log_error("Could not set AES-XTS key: error %d", err); + goto bad_setkey; + } + sflc_log_debug("Successfully set the AES-XTS key"); + + // Set the IV (not a counter, as per Gcrypt docs) + err = gcry_cipher_setiv(hd, iv, SFLC_AESXTS_IVLEN); + if (err) { + sflc_log_error("Could not set AES-XTS IV: error %d", err); + goto bad_setiv; + } + sflc_log_debug("Successfully set the IV"); + + // Encrypt + if (ct == NULL) { // In-place + err = gcry_cipher_encrypt(hd, pt, pt_len, NULL, 0); + } + else { // Out-of-place + err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); + } + // Error check + if (err) { + sflc_log_error("Could not encrypt: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted"); + + // No prob? + err = 0; + + +bad_encrypt: +bad_setiv: +bad_setkey: + gcry_cipher_close(hd); +bad_open: + return err; +} + + /** *AES-GCM encryption, does not touch the IV. * @@ -233,7 +303,7 @@ int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; @@ -308,7 +378,7 @@ int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char * sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; @@ -378,7 +448,7 @@ int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) { gcry_kdf_hd_t hd; const unsigned long argon_params[4] = - {SFLC_CRYPTO_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; + {SFLC_STANDARD_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; gcry_error_t err; // Instantiate Argon2id handle @@ -405,7 +475,7 @@ int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) sflc_log_debug("Successfully run Argon2id computation"); // Finalise hash - err = gcry_kdf_final(hd, SFLC_CRYPTO_KEYLEN, hash); + err = gcry_kdf_final(hd, SFLC_STANDARD_KEYLEN, hash); if (err) { sflc_log_error("Could not finalise Argon2id hash: error %d", err); goto bad_final; diff --git a/vuvuzela-userland/src/utils/disk.c b/vuvuzela-userland/src/utils/disk.c index 2f78598..40368e6 100644 --- a/vuvuzela-userland/src/utils/disk.c +++ b/vuvuzela-userland/src/utils/disk.c @@ -96,7 +96,7 @@ int64_t sflc_disk_getSize(char * bdev_path) sflc_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); /* Compute size in SFLC sectors */ - ret = (size_bytes / SFLC_SECTOR_SIZE); + ret = (size_bytes / SFLC_BLOCK_SIZE); bad_ioctl: close(fd); @@ -114,7 +114,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf) +int sflc_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) { int fd; int err; @@ -130,7 +130,7 @@ int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf) sflc_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { + if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; @@ -139,7 +139,7 @@ int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf) sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Read in a loop */ - size_t bytes_to_read = SFLC_SECTOR_SIZE; + size_t bytes_to_read = SFLC_BLOCK_SIZE; while (bytes_to_read > 0) { /* Read syscall */ ssize_t bytes_read = read(fd, buf, bytes_to_read); @@ -172,22 +172,6 @@ bad_open: } -/** - * Writes a single 4096-byte sector to the disk. - * - * @param bdev_path The path of the block device - * @param sector The index of the desired sector - * @param The caller-allocated buffer (must hold 4096 bytes) where the data - * comes from - * - * @return The error code (0 on success) - */ -int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf) -{ - return sflc_disk_writeManySectors(bdev_path, sector, buf, 1); -} - - /** * Writes many 4096-byte sectors to the disk. * @@ -199,7 +183,7 @@ int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf) * * @return The error code (0 on success) */ -int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) +int sflc_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) { int fd; int err; @@ -215,7 +199,7 @@ int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, si sflc_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { + if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; @@ -224,7 +208,7 @@ int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, si sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Write in a loop */ - size_t bytes_to_write = SFLC_SECTOR_SIZE * num_sectors; + size_t bytes_to_write = SFLC_BLOCK_SIZE * num_sectors; while (bytes_to_write > 0) { /* Write syscall */ ssize_t bytes_written = write(fd, buf, bytes_to_write); diff --git a/vuvuzela-userland/test/crypto/test_argon2id.c b/vuvuzela-userland/test/crypto/test_argon2id.c index 0ded58b..7e1180d 100644 --- a/vuvuzela-userland/test/crypto/test_argon2id.c +++ b/vuvuzela-userland/test/crypto/test_argon2id.c @@ -49,7 +49,7 @@ char SALT[SFLC_ARGON_SALTLEN+1] = "Poor Petrol Pump"; char *test_argon2id() { char pwd[SFLC_BIGBUFSIZE]; - char key[SFLC_CRYPTO_KEYLEN]; + char key[SFLC_STANDARD_KEYLEN]; int err; sflc_log_blue("Testing Argon2id interactively with a fixed salt"); @@ -75,7 +75,7 @@ char *test_argon2id() sflc_log_hex(SALT, SFLC_ARGON_SALTLEN); printf("Argon2id hash (m = %d, t = %d, p = %d):\n", SFLC_ARGON_M, SFLC_ARGON_T, SFLC_ARGON_P); - sflc_log_hex(key, SFLC_CRYPTO_KEYLEN); + sflc_log_hex(key, SFLC_STANDARD_KEYLEN); printf("Go check the result online\n\n"); } From 8ecc634c1f3a050992348dfec98bb0055ca53c1a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 17 Apr 2024 20:51:16 +0200 Subject: [PATCH 45/98] Change sflc to vvz --- vuvuzela-userland/include/cli.h | 22 +-- vuvuzela-userland/include/commands.h | 18 +-- vuvuzela-userland/include/header.h | 44 +++--- vuvuzela-userland/include/operations.h | 16 +- vuvuzela-userland/include/sflc_constants.h | 1 - vuvuzela-userland/include/utils/crypto.h | 42 ++--- vuvuzela-userland/include/utils/disk.h | 24 +-- vuvuzela-userland/include/utils/dm.h | 6 +- vuvuzela-userland/include/utils/file.h | 2 +- vuvuzela-userland/include/utils/input.h | 6 +- vuvuzela-userland/include/utils/log.h | 80 +++++----- vuvuzela-userland/include/utils/string.h | 4 +- .../include/utils/{sflc.h => vvz.h} | 42 ++--- vuvuzela-userland/include/vvz_constants.h | 41 +++++ vuvuzela-userland/src/cli/changepwd.c | 32 ++-- vuvuzela-userland/src/cli/close.c | 14 +- vuvuzela-userland/src/cli/dispatch.c | 88 +++++------ vuvuzela-userland/src/cli/init.c | 34 ++-- vuvuzela-userland/src/cli/open.c | 16 +- vuvuzela-userland/src/cli/testpwd.c | 22 +-- vuvuzela-userland/src/commands/change_pwd.c | 6 +- vuvuzela-userland/src/commands/close.c | 44 +++--- vuvuzela-userland/src/commands/init.c | 58 +++---- vuvuzela-userland/src/commands/open.c | 48 +++--- vuvuzela-userland/src/commands/test_pwd.c | 6 +- .../src/header/device_master_block.c | 90 +++++------ vuvuzela-userland/src/header/position_map.c | 26 +-- .../src/header/volume_master_block.c | 72 ++++----- vuvuzela-userland/src/main.c | 2 +- vuvuzela-userland/src/operations/devmapper.c | 26 +-- vuvuzela-userland/src/operations/dmb.c | 52 +++--- .../src/operations/volume_header.c | 48 +++--- vuvuzela-userland/src/utils/crypto.c | 148 +++++++++--------- vuvuzela-userland/src/utils/disk.c | 52 +++--- vuvuzela-userland/src/utils/dm.c | 56 +++---- vuvuzela-userland/src/utils/file.c | 6 +- vuvuzela-userland/src/utils/input.c | 8 +- vuvuzela-userland/src/utils/string.c | 6 +- .../test/crypto/test_aes256ctr.c | 24 +-- .../test/crypto/test_aes256gcm.c | 22 +-- vuvuzela-userland/test/crypto/test_argon2id.c | 18 +-- vuvuzela-userland/test/main.c | 6 +- 42 files changed, 709 insertions(+), 669 deletions(-) delete mode 120000 vuvuzela-userland/include/sflc_constants.h rename vuvuzela-userland/include/utils/{sflc.h => vvz.h} (71%) create mode 100644 vuvuzela-userland/include/vvz_constants.h diff --git a/vuvuzela-userland/include/cli.h b/vuvuzela-userland/include/cli.h index 838f618..e8b495b 100644 --- a/vuvuzela-userland/include/cli.h +++ b/vuvuzela-userland/include/cli.h @@ -30,15 +30,15 @@ *****************************************************/ /* Action to create volumes */ -#define SFLC_CLI_INITACT "init" +#define VVZ_CLI_INITACT "init" /* Action to open volumes */ -#define SFLC_CLI_OPENACT "open" +#define VVZ_CLI_OPENACT "open" /* Action to close volumes */ -#define SFLC_CLI_CLOSEACT "close" +#define VVZ_CLI_CLOSEACT "close" /* Action to test password */ -#define SFLC_CLI_TESTPWDACT "testpwd" +#define VVZ_CLI_TESTPWDACT "testpwd" /* Action to change password */ -#define SFLC_CLI_CHANGEPWDACT "changepwd" +#define VVZ_CLI_CHANGEPWDACT "changepwd" /***************************************************** @@ -46,18 +46,18 @@ *****************************************************/ /* Called by the main to parse the arguments and dispatch to the right command */ -int sflc_cli_dispatch(int argc, char **argv); +int vvz_cli_dispatch(int argc, char **argv); /* Initializes device and create empty volumes */ -int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill); +int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill); /* Open volumes */ -int sflc_cli_open(char *block_device); +int vvz_cli_open(char *block_device); /* Close volumes */ -int sflc_cli_close(char *block_device); +int vvz_cli_close(char *block_device); /* Test password */ -int sflc_cli_testPwd(char *block_device); +int vvz_cli_testPwd(char *block_device); /* Change password */ -int sflc_cli_changePwd(char *block_device); +int vvz_cli_changePwd(char *block_device); #endif /* _CLI_H_ */ diff --git a/vuvuzela-userland/include/commands.h b/vuvuzela-userland/include/commands.h index 68a14ac..a15ad14 100644 --- a/vuvuzela-userland/include/commands.h +++ b/vuvuzela-userland/include/commands.h @@ -60,7 +60,7 @@ typedef struct /* Option to skip random filling */ bool no_randfill; -} sflc_cmd_InitArgs; +} vvz_cmd_InitArgs; /* Parameters for the open command */ @@ -73,7 +73,7 @@ typedef struct char *pwd; size_t pwd_len; -} sflc_cmd_OpenArgs; +} vvz_cmd_OpenArgs; typedef struct { @@ -81,13 +81,13 @@ typedef struct char *bdev_path; /* Content of the DMB cell */ - sflc_DmbCell *dmb_cell; + vvz_DmbCell *dmb_cell; /* The new password */ char *new_pwd; size_t new_pwd_len; -} sflc_cmd_ChangePwdArgs; +} vvz_cmd_ChangePwdArgs; /***************************************************** @@ -95,19 +95,19 @@ typedef struct *****************************************************/ /* Create N volumes (only formats the device header, does not open the volumes) */ -int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args); +int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args); /* Open M volumes, from the first down to the one whose pwd is provided */ -int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args); +int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args); /* Close all volumes on the device (reads the list from sysfs) */ -int sflc_cmd_closeVolumes(char *bdev_path); +int vvz_cmd_closeVolumes(char *bdev_path); /* Tests which volume is unlocked by the given password */ -int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell); +int vvz_cmd_testPwd(vvz_cmd_OpenArgs *args, vvz_DmbCell *dmb_cell); /* Changes the specified volume's password */ -int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args); +int vvz_cmd_changePwd(vvz_cmd_ChangePwdArgs *args); #endif /* _COMMANDS_H_ */ diff --git a/vuvuzela-userland/include/header.h b/vuvuzela-userland/include/header.h index 32d5596..6d5181c 100644 --- a/vuvuzela-userland/include/header.h +++ b/vuvuzela-userland/include/header.h @@ -40,18 +40,18 @@ *****************************************************/ /* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_STANDARD_KEYLEN + SFLC_AESGCM_TAGLEN) +#define VVZ_DMB_CELL_SIZE (VVZ_AESGCM_PADDED_IVLEN + VVZ_STANDARD_KEYLEN + VVZ_AESGCM_TAGLEN) /* Let us enforce that the one DMB can fit cells for all volumes */ -#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL_SIZE) > SFLC_BLOCK_SIZE -#error "Invalid combination of parameters: probably SFLC_DEV_MAX_VOLUMES is too big" +#if VVZ_ARGON_SALTLEN + (VVZ_DEV_MAX_VOLUMES * VVZ_DMB_CELL_SIZE) > VVZ_BLOCK_SIZE +#error "Invalid combination of parameters: probably VVZ_DEV_MAX_VOLUMES is too big" #endif // The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define SFLC_CLEAR_VMB_LEN (SFLC_BLOCK_SIZE - \ - SFLC_AESGCM_PADDED_IVLEN - \ - SFLC_AESGCM_TAGLEN) +#define VVZ_CLEAR_VMB_LEN (VVZ_BLOCK_SIZE - \ + VVZ_AESGCM_PADDED_IVLEN - \ + VVZ_AESGCM_TAGLEN) @@ -66,12 +66,12 @@ */ typedef struct { // Each volume's VMB key - char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_STANDARD_KEYLEN]; + char vmb_keys[VVZ_DEV_MAX_VOLUMES][VVZ_STANDARD_KEYLEN]; // How many of these need actually be encrypted size_t nr_vols; -} sflc_Dmb; +} vvz_Dmb; /** @@ -80,12 +80,12 @@ typedef struct { */ typedef struct { // The unlocked VMB key - char vmb_key[SFLC_STANDARD_KEYLEN]; + char vmb_key[VVZ_STANDARD_KEYLEN]; // The index of the volume opened by this VMB key size_t vol_idx; -} sflc_DmbCell; +} vvz_DmbCell; /** @@ -95,15 +95,15 @@ typedef struct { */ typedef struct { // The key that encrypts the volume's data section - char volume_key[SFLC_STANDARD_KEYLEN]; + char volume_key[VVZ_STANDARD_KEYLEN]; // The key that encrypts the previous volume's master block - char prev_vmb_key[SFLC_STANDARD_KEYLEN]; + char prev_vmb_key[VVZ_STANDARD_KEYLEN]; // The total number of logical slices virtually available to this volume size_t nr_slices; -} sflc_Vmb; +} vvz_Vmb; /***************************************************** @@ -112,11 +112,11 @@ typedef struct { // Starting block of a volume's position map -static inline uint64_t sflc_pmStartBlock(size_t vol_idx, size_t nr_slices) +static inline uint64_t vvz_pmStartBlock(size_t vol_idx, size_t nr_slices) { return 1 + - SFLC_DEV_MAX_VOLUMES + - vol_idx*ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + VVZ_DEV_MAX_VOLUMES + + vol_idx*ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); } @@ -125,21 +125,21 @@ static inline uint64_t sflc_pmStartBlock(size_t vol_idx, size_t nr_slices) *****************************************************/ /* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ -int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); +int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); /* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +int vvz_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell); /* Re-encrypt the content of a single DMB cell */ -int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len); +int vvz_dmb_setCell(char *disk_block, vvz_DmbCell *dmb_cell, char *pwd, size_t pwd_len); /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ -int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +int vvz_vmb_seal(vvz_Vmb *vmb, char *vmb_key, char *disk_block); /* "Decrypt" a VMB coming from the disk, directly using its key */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); +int vvz_vmb_unseal(char *disk_block, char *vmb_key, vvz_Vmb *vmb); /* Create an encrypted empty position map for the given number of slices (allocates memory) */ -void *sflc_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); +void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); diff --git a/vuvuzela-userland/include/operations.h b/vuvuzela-userland/include/operations.h index 13bebde..dbcfd45 100644 --- a/vuvuzela-userland/include/operations.h +++ b/vuvuzela-userland/include/operations.h @@ -42,20 +42,20 @@ *****************************************************/ /* Encrypts and writes the DMB to disk */ -int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb); +int vvz_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, vvz_Dmb *dmb); /* Reads the DMB from disk and outputs the unlocked VMB key */ -int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +int vvz_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell); /* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ -int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); +int vvz_ops_rewriteDmbCell(char *bdev_path, vvz_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); /* Encrypts and writes a volume header (VMB+PM) on-disk */ -int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size_t vol_idx); /* Reads a VMB from disk and unlocks it */ -int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); +int vvz_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, vvz_Vmb *vmb); -/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device */ -int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); +/* Build parameter list for ctor in dm_vvz, and send DM ioctl to create virtual block device */ +int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb *vmb); /* Close the volume via the appropriate ioctl to DM */ -int sflc_ops_closeVolume(char *label); +int vvz_ops_closeVolume(char *label); #endif /* _OPERATIONS_H_ */ diff --git a/vuvuzela-userland/include/sflc_constants.h b/vuvuzela-userland/include/sflc_constants.h deleted file mode 120000 index fab9e42..0000000 --- a/vuvuzela-userland/include/sflc_constants.h +++ /dev/null @@ -1 +0,0 @@ -../../dm-sflc/sflc_constants.h \ No newline at end of file diff --git a/vuvuzela-userland/include/utils/crypto.h b/vuvuzela-userland/include/utils/crypto.h index 8e5f4d8..e9d1ed9 100644 --- a/vuvuzela-userland/include/utils/crypto.h +++ b/vuvuzela-userland/include/utils/crypto.h @@ -33,7 +33,7 @@ #include #include -#include "utils/sflc.h" +#include "utils/vvz.h" /***************************************************** @@ -41,41 +41,41 @@ *****************************************************/ // Key length, for input into AES-CTR and AES-GCM, and for output from Argon -#define SFLC_STANDARD_KEYLEN 32 /* bytes */ +#define VVZ_STANDARD_KEYLEN 32 /* bytes */ // Key length for AES-XTS -#define SFLC_AESXTS_KEYLEN 64 /* bytes */ +#define VVZ_AESXTS_KEYLEN 64 /* bytes */ // IV length for AES-CTR -#define SFLC_AESCTR_IVLEN 16 /* bytes */ +#define VVZ_AESCTR_IVLEN 16 /* bytes */ // IV length for AES-XTS -#define SFLC_AESXTS_IVLEN 16 /* bytes */ +#define VVZ_AESXTS_IVLEN 16 /* bytes */ // IV length for AES-GCM -#define SFLC_AESGCM_IVLEN 12 /* bytes */ +#define VVZ_AESGCM_IVLEN 12 /* bytes */ // IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM -#define SFLC_AESGCM_PADDED_IVLEN 16 /* bytes */ +#define VVZ_AESGCM_PADDED_IVLEN 16 /* bytes */ // MAC length for AES-GCM -#define SFLC_AESGCM_TAGLEN 16 /* bytes */ +#define VVZ_AESGCM_TAGLEN 16 /* bytes */ // Content of output plaintext upon MAC verification failure -#define SFLC_AESGCM_POISON_PT 0xFF +#define VVZ_AESGCM_POISON_PT 0xFF /* Argon parameters */ // Argon salt length -#define SFLC_ARGON_SALTLEN 16 /* bytes */ +#define VVZ_ARGON_SALTLEN 16 /* bytes */ // Argon memory parameter // We assume machines with at least 128 MiB available RAM, so 2^17 kiB -#define SFLC_ARGON_M (1 << 17) /* kibibytes */ +#define VVZ_ARGON_M (1 << 17) /* kibibytes */ // Argon iterations count // We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) -#define SFLC_ARGON_T 3 +#define VVZ_ARGON_T 3 // Argon parallelism parameter (recommended to be 2 * CPU cores) // We assume use even on single core devices -#define SFLC_ARGON_P 2 +#define VVZ_ARGON_P 2 /***************************************************** @@ -83,26 +83,26 @@ *****************************************************/ /* Get slow, strong random bytes (suited for keys) */ -int sflc_rand_getStrongBytes(char *buf, size_t buflen); +int vvz_rand_getStrongBytes(char *buf, size_t buflen); /* Get fast, weak(er) random bytes (suited for IVs and padding) */ -int sflc_rand_getWeakBytes(char *buf, size_t buflen); +int vvz_rand_getWeakBytes(char *buf, size_t buflen); /* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ -int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ -int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); +int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); /* AES256-XTS encryption. Set ct = NULL for in-place. * The IV is intepreted as a little-endian "sector number", and incremented by 1 */ -int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-GCM encryption, does not touch the IV */ -int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); +int vvz_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); /* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ -int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); +int vvz_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); /* Compute Argon KDF */ -int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); +int vvz_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); #endif /* _UTILS_CRYPTO_H_ */ diff --git a/vuvuzela-userland/include/utils/disk.h b/vuvuzela-userland/include/utils/disk.h index 00dd91c..b36b6a2 100644 --- a/vuvuzela-userland/include/utils/disk.h +++ b/vuvuzela-userland/include/utils/disk.h @@ -36,7 +36,7 @@ #include #include "utils/math.h" -#include "utils/sflc.h" +#include "utils/vvz.h" /***************************************************** @@ -46,25 +46,25 @@ /** * Max slices for given disk size (in 4096-byte blocks). */ -static inline uint32_t sflc_disk_maxSlices(uint64_t size) { +static inline uint32_t vvz_disk_maxSlices(uint64_t size) { uint64_t nr_slices; // Start from upper bound - nr_slices = size / SFLC_SLICE_SCALE; + nr_slices = size / VVZ_SLICE_SCALE; while(true) { if (nr_slices == 0) break; // Stop when this nr_slices can fit in size, including the header - uint64_t posmap_blocks = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); - uint64_t header_size = 1 + SFLC_DEV_MAX_VOLUMES * (1 + posmap_blocks); - if (header_size + nr_slices*SFLC_SLICE_SCALE <= size) + uint64_t posmap_blocks = ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); + uint64_t header_size = 1 + VVZ_DEV_MAX_VOLUMES * (1 + posmap_blocks); + if (header_size + nr_slices*VVZ_SLICE_SCALE <= size) break; nr_slices--; } - return nr_slices > SFLC_MAX_SLICES ? SFLC_MAX_SLICES : (uint32_t) nr_slices; + return nr_slices > VVZ_MAX_SLICES ? VVZ_MAX_SLICES : (uint32_t) nr_slices; } @@ -73,19 +73,19 @@ static inline uint32_t sflc_disk_maxSlices(uint64_t size) { *****************************************************/ /* Checks whether the given path points to a block device */ -bool sflc_disk_isBlockDevice(char *path); +bool vvz_disk_isBlockDevice(char *path); /* Returns the size in 4096-byte sectors (or < 0 if error) */ -int64_t sflc_disk_getSize(char *bdev_path); +int64_t vvz_disk_getSize(char *bdev_path); /* Reads a single 4096-byte block from the disk */ -int sflc_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); +int vvz_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); /* Writes many 4096-byte blocks to the disk */ -int sflc_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); +int vvz_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); /* Writes a single 4096-byte block to the disk */ -#define sflc_disk_writeBlock(bdev_path, bnum, buf) sflc_disk_writeManyBlocks(bdev_path, bnum, buf, 1) +#define vvz_disk_writeBlock(bdev_path, bnum, buf) vvz_disk_writeManyBlocks(bdev_path, bnum, buf, 1) #endif /* _UTILS_DISK_H_ */ diff --git a/vuvuzela-userland/include/utils/dm.h b/vuvuzela-userland/include/utils/dm.h index 3334852..1670a73 100644 --- a/vuvuzela-userland/include/utils/dm.h +++ b/vuvuzela-userland/include/utils/dm.h @@ -35,7 +35,7 @@ #include -#include "utils/sflc.h" +#include "utils/vvz.h" /***************************************************** @@ -48,9 +48,9 @@ *****************************************************/ /* Create a new Shufflecake virtual device (volume) under /dev/mapper */ -int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); +int vvz_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); /* Destroy the virtual device under /dev/mapper */ -int sflc_dm_destroy(char * virt_dev_name); +int vvz_dm_destroy(char * virt_dev_name); #endif /* _UTILS_DM_H_ */ diff --git a/vuvuzela-userland/include/utils/file.h b/vuvuzela-userland/include/utils/file.h index fb03078..b4760c3 100644 --- a/vuvuzela-userland/include/utils/file.h +++ b/vuvuzela-userland/include/utils/file.h @@ -30,7 +30,7 @@ *****************************************************/ /* Malloc's the buffer for the file contents */ -char *sflc_readFile(char *path); +char *vvz_readFile(char *path); #endif /* _UTILS_FILE_H_ */ diff --git a/vuvuzela-userland/include/utils/input.h b/vuvuzela-userland/include/utils/input.h index 52e07fb..068d376 100644 --- a/vuvuzela-userland/include/utils/input.h +++ b/vuvuzela-userland/include/utils/input.h @@ -30,7 +30,7 @@ *****************************************************/ /* Clear a line from stdin, to use after a failed scanf (it didn't actually read input) */ -#define sflc_ignoreLine() scanf("%*[^\n]") +#define vvz_ignoreLine() scanf("%*[^\n]") /***************************************************** @@ -38,9 +38,9 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflc_safeReadLine(char *buf, size_t bufsize); +int vvz_safeReadLine(char *buf, size_t bufsize); /* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ -int sflc_safeReadPassphrase(char *buf, size_t bufsize); +int vvz_safeReadPassphrase(char *buf, size_t bufsize); #endif /* _UTILS_INPUT_H_ */ diff --git a/vuvuzela-userland/include/utils/log.h b/vuvuzela-userland/include/utils/log.h index 2088fbb..6790ae5 100644 --- a/vuvuzela-userland/include/utils/log.h +++ b/vuvuzela-userland/include/utils/log.h @@ -37,29 +37,29 @@ *****************************************************/ // Printf colours (regular text) -#define SFLC_LOG_BLK "\033[0;30m" -#define SFLC_LOG_RED "\033[0;31m" -#define SFLC_LOG_GRN "\033[0;32m" -#define SFLC_LOG_YEL "\033[0;33m" -#define SFLC_LOG_BLU "\033[0;34m" -#define SFLC_LOG_MAG "\033[0;35m" -#define SFLC_LOG_CYN "\033[0;36m" -#define SFLC_LOG_WHT "\033[0;37m" +#define VVZ_LOG_BLK "\033[0;30m" +#define VVZ_LOG_RED "\033[0;31m" +#define VVZ_LOG_GRN "\033[0;32m" +#define VVZ_LOG_YEL "\033[0;33m" +#define VVZ_LOG_BLU "\033[0;34m" +#define VVZ_LOG_MAG "\033[0;35m" +#define VVZ_LOG_CYN "\033[0;36m" +#define VVZ_LOG_WHT "\033[0;37m" // Printf colours (bold text) -#define SFLC_LOG_BBLK "\033[1;30m" -#define SFLC_LOG_BRED "\033[1;31m" -#define SFLC_LOG_BGRN "\033[1;32m" -#define SFLC_LOG_BYEL "\033[1;33m" -#define SFLC_LOG_BBLU "\033[1;34m" -#define SFLC_LOG_BMAG "\033[1;35m" -#define SFLC_LOG_BCYN "\033[1;36m" -#define SFLC_LOG_BWHT "\033[1;37m" +#define VVZ_LOG_BBLK "\033[1;30m" +#define VVZ_LOG_BRED "\033[1;31m" +#define VVZ_LOG_BGRN "\033[1;32m" +#define VVZ_LOG_BYEL "\033[1;33m" +#define VVZ_LOG_BBLU "\033[1;34m" +#define VVZ_LOG_BMAG "\033[1;35m" +#define VVZ_LOG_BCYN "\033[1;36m" +#define VVZ_LOG_BWHT "\033[1;37m" // Reset colour -#define SFLC_LOG_RESET "\033[0m" +#define VVZ_LOG_RESET "\033[0m" // Log level: debug implies detailed logs -#ifdef CONFIG_SFLC_LOG_DEBUG -#define CONFIG_SFLC_LOG_DETAILED +#ifdef CONFIG_VVZ_LOG_DEBUG +#define CONFIG_VVZ_LOG_DETAILED #endif @@ -68,42 +68,42 @@ *****************************************************/ // Gives the point in the code where it was called -#define sflc_log_detailed(col, ...) do{ \ - printf(SFLC_LOG_GRN "FUNC " SFLC_LOG_RESET "%s() " \ - SFLC_LOG_GRN "FILE " SFLC_LOG_RESET "%s " \ - SFLC_LOG_GRN "LINE " SFLC_LOG_RESET "%d | ", \ +#define vvz_log_detailed(col, ...) do{ \ + printf(VVZ_LOG_GRN "FUNC " VVZ_LOG_RESET "%s() " \ + VVZ_LOG_GRN "FILE " VVZ_LOG_RESET "%s " \ + VVZ_LOG_GRN "LINE " VVZ_LOG_RESET "%d | ", \ __func__, __FILE__, __LINE__); \ - sflc_log_concise(col, __VA_ARGS__); \ + vvz_log_concise(col, __VA_ARGS__); \ }while(0) // Only writes using the given colour -#define sflc_log_concise(col, ...) do{ \ +#define vvz_log_concise(col, ...) do{ \ printf(col); \ printf(__VA_ARGS__); \ - printf(SFLC_LOG_RESET "\n"); \ + printf(VVZ_LOG_RESET "\n"); \ }while(0) // Maps to one or the other, based on a Makefile switch -#ifdef CONFIG_SFLC_LOG_DETAILED - #define sflc_log_colour(...) sflc_log_detailed(__VA_ARGS__) +#ifdef CONFIG_VVZ_LOG_DETAILED + #define vvz_log_colour(...) vvz_log_detailed(__VA_ARGS__) #else - #define sflc_log_colour(...) sflc_log_concise(__VA_ARGS__) + #define vvz_log_colour(...) vvz_log_concise(__VA_ARGS__) #endif // Using specific colours -#define sflc_log_green(...) sflc_log_colour(SFLC_LOG_GRN, __VA_ARGS__) -#define sflc_log_red(...) sflc_log_colour(SFLC_LOG_RED, __VA_ARGS__) -#define sflc_log_yellow(...) sflc_log_colour(SFLC_LOG_YEL, __VA_ARGS__) -#define sflc_log_blue(...) sflc_log_colour(SFLC_LOG_BLU, __VA_ARGS__) -#define sflc_log_normal(...) sflc_log_colour(SFLC_LOG_RESET, __VA_ARGS__) +#define vvz_log_green(...) vvz_log_colour(VVZ_LOG_GRN, __VA_ARGS__) +#define vvz_log_red(...) vvz_log_colour(VVZ_LOG_RED, __VA_ARGS__) +#define vvz_log_yellow(...) vvz_log_colour(VVZ_LOG_YEL, __VA_ARGS__) +#define vvz_log_blue(...) vvz_log_colour(VVZ_LOG_BLU, __VA_ARGS__) +#define vvz_log_normal(...) vvz_log_colour(VVZ_LOG_RESET, __VA_ARGS__) // With log levels -#define sflc_log_error(...) sflc_log_colour(SFLC_LOG_RED, "[ERROR] " __VA_ARGS__) -#define sflc_log_warn(...) sflc_log_colour(SFLC_LOG_MAG, "[WARN] " __VA_ARGS__) -#ifdef CONFIG_SFLC_LOG_DEBUG - #define sflc_log_debug(...) sflc_log_colour(SFLC_LOG_CYN, "[DEBUG] " __VA_ARGS__) +#define vvz_log_error(...) vvz_log_colour(VVZ_LOG_RED, "[ERROR] " __VA_ARGS__) +#define vvz_log_warn(...) vvz_log_colour(VVZ_LOG_MAG, "[WARN] " __VA_ARGS__) +#ifdef CONFIG_VVZ_LOG_DEBUG + #define vvz_log_debug(...) vvz_log_colour(VVZ_LOG_CYN, "[DEBUG] " __VA_ARGS__) #else - #define sflc_log_debug(...) + #define vvz_log_debug(...) #endif @@ -112,7 +112,7 @@ *****************************************************/ // Log a hex string -static inline void sflc_log_hex(char *str, size_t len) +static inline void vvz_log_hex(char *str, size_t len) { int i; unsigned char *s = (unsigned char *) str; diff --git a/vuvuzela-userland/include/utils/string.h b/vuvuzela-userland/include/utils/string.h index ca42c0e..99f057b 100644 --- a/vuvuzela-userland/include/utils/string.h +++ b/vuvuzela-userland/include/utils/string.h @@ -30,10 +30,10 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *sflc_toHex(char *buf, size_t len); +char *vvz_toHex(char *buf, size_t len); /* Replaces all occurrences of character in-place */ -void sflc_str_replaceAll(char *str, char old, char new); +void vvz_str_replaceAll(char *str, char old, char new); #endif /* _UTILS_STRING_H_ */ diff --git a/vuvuzela-userland/include/utils/sflc.h b/vuvuzela-userland/include/utils/vvz.h similarity index 71% rename from vuvuzela-userland/include/utils/sflc.h rename to vuvuzela-userland/include/utils/vvz.h index 99d0243..3263dca 100644 --- a/vuvuzela-userland/include/utils/sflc.h +++ b/vuvuzela-userland/include/utils/vvz.h @@ -22,11 +22,11 @@ */ /* - * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflc_constans.h + * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO vvz_constans.h */ -#ifndef _UTILS_SFLC_H_ -#define _UTILS_SFLC_H_ +#ifndef _UTILS_VVZ_H_ +#define _UTILS_VVZ_H_ /***************************************************** @@ -39,44 +39,44 @@ *****************************************************/ /* Name of the DM target in the kernel */ -#define SFLC_DM_TARGET_NAME "vvz" +#define VVZ_DM_TARGET_NAME "vvz" /* Sizes */ #define KERNEL_SECTOR_SIZE 512 /* bytes */ -#define SFLC_BLOCK_SIZE 4096 /* bytes */ -#define SFLC_BLOCK_SCALE (SFLC_BLOCK_SIZE / KERNEL_SECTOR_SIZE) -#define SFLC_SLICE_SCALE 256 /* blocks in a slice */ -#define SFLC_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ +#define VVZ_BLOCK_SIZE 4096 /* bytes */ +#define VVZ_BLOCK_SCALE (VVZ_BLOCK_SIZE / KERNEL_SECTOR_SIZE) +#define VVZ_SLICE_SCALE 256 /* blocks in a slice */ +#define VVZ_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ /* Max number of volumes in a device */ -#define SFLC_DEV_MAX_VOLUMES 15 +#define VVZ_DEV_MAX_VOLUMES 15 /* Max total number of open devices at any given time */ -#define SFLC_TOT_MAX_DEVICES 1024 -/* A volume name is sflc__ */ -#define SFLC_MAX_VOL_NAME_LEN 15 +#define VVZ_TOT_MAX_DEVICES 1024 +/* A volume name is vvz__ */ +#define VVZ_MAX_VOL_NAME_LEN 15 /* A slice index is represented over 32 bits */ -#define SFLC_SLICE_IDX_WIDTH 4 /* bytes */ +#define VVZ_SLICE_IDX_WIDTH 4 /* bytes */ /* A position map block contains 1024 slice indices */ -#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_BLOCK_SIZE / SFLC_SLICE_IDX_WIDTH) +#define VVZ_SLICE_IDX_PER_BLOCK (VVZ_BLOCK_SIZE / VVZ_SLICE_IDX_WIDTH) /* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ -#define SFLC_EPM_FILLER 0xFF +#define VVZ_EPM_FILLER 0xFF /* The sysfs file containing the next available device ID */ -#define SFLC_SYSFS_NEXTDEVID "/sys/module/dm_vvz/next_dev_id" +#define VVZ_SYSFS_NEXTDEVID "/sys/module/dm_vvz/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ -#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_vvz/bdevs" +#define VVZ_SYSFS_BDEVS_DIR "/sys/module/dm_vvz/bdevs" /* Within each bdev's subdir, this file lists its open volumes */ -#define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" +#define VVZ_SYSFS_OPENVOLUMES_FILENAME "volumes" /* TODO: reasonable? */ -#define SFLC_BDEV_PATH_MAX_LEN 1024 +#define VVZ_BDEV_PATH_MAX_LEN 1024 /* For when you can't be bothered to upper-bound a buffer size */ -#define SFLC_BIGBUFSIZE 4096 +#define VVZ_BIGBUFSIZE 4096 -#endif /* _UTILS_SFLC_H_ */ +#endif /* _UTILS_VVZ_H_ */ diff --git a/vuvuzela-userland/include/vvz_constants.h b/vuvuzela-userland/include/vvz_constants.h new file mode 100644 index 0000000..9f27092 --- /dev/null +++ b/vuvuzela-userland/include/vvz_constants.h @@ -0,0 +1,41 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * PLACEHOLDER * + *****************************************************/ + +/* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ + + +#define VVZ_VER_MAJOR 0 +#define VVZ_VER_MINOR 4 +#define VVZ_VER_REVISION 0 +#define VVZ_VER_SPECIAL "rc1" + +#define STRINGIFY0(s) # s +#define STRINGIFY(s) STRINGIFY0(s) + +#define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL + + diff --git a/vuvuzela-userland/src/cli/changepwd.c b/vuvuzela-userland/src/cli/changepwd.c index de37779..e0f691f 100644 --- a/vuvuzela-userland/src/cli/changepwd.c +++ b/vuvuzela-userland/src/cli/changepwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/input.h" #include "utils/log.h" @@ -45,14 +45,14 @@ * * @return Error code, 0 on success */ -int sflc_cli_changePwd(char *block_device) +int vvz_cli_changePwd(char *block_device) { // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs open_args; - sflc_cmd_ChangePwdArgs change_pwd_args; - sflc_DmbCell dmb_cell; - char old_pwd[SFLC_BIGBUFSIZE]; + vvz_cmd_OpenArgs open_args; + vvz_cmd_ChangePwdArgs change_pwd_args; + vvz_DmbCell dmb_cell; + char old_pwd[VVZ_BIGBUFSIZE]; size_t old_pwd_len; - char new_pwd[SFLC_BIGBUFSIZE]; + char new_pwd[VVZ_BIGBUFSIZE]; size_t new_pwd_len; int err; @@ -60,9 +60,9 @@ int sflc_cli_changePwd(char *block_device) /* Gather password */ printf("Enter the password you want to change: "); - err = sflc_safeReadPassphrase(old_pwd, SFLC_BIGBUFSIZE); + err = vvz_safeReadPassphrase(old_pwd, VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Could not read password; error %d", err); + vvz_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -72,14 +72,14 @@ int sflc_cli_changePwd(char *block_device) open_args.pwd_len = old_pwd_len; /* Test the password */ - err = sflc_cmd_testPwd(&open_args, &dmb_cell); + err = vvz_cmd_testPwd(&open_args, &dmb_cell); if (err) { - sflc_log_error("Could not test password; error %d", err); + vvz_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); return 0; } @@ -87,9 +87,9 @@ int sflc_cli_changePwd(char *block_device) /* Gather new password (no secure shell) */ printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); - err = sflc_safeReadLine(new_pwd, SFLC_BIGBUFSIZE); + err = vvz_safeReadLine(new_pwd, VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Could not read new password; error %d", err); + vvz_log_error("Could not read new password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -102,9 +102,9 @@ int sflc_cli_changePwd(char *block_device) change_pwd_args.new_pwd_len = new_pwd_len; /* Change password */ - err = sflc_cmd_changePwd(&change_pwd_args); + err = vvz_cmd_changePwd(&change_pwd_args); if (err) { - sflc_log_error("Could not change password; error %d", err); + vvz_log_error("Could not change password; error %d", err); return err; } printf("Password changed successfully.\n"); diff --git a/vuvuzela-userland/src/cli/close.c b/vuvuzela-userland/src/cli/close.c index fdbaa28..a4c8bbd 100644 --- a/vuvuzela-userland/src/cli/close.c +++ b/vuvuzela-userland/src/cli/close.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/input.h" #include "utils/log.h" @@ -45,16 +45,16 @@ * * @return Error code, 0 on success */ -int sflc_cli_close(char *block_device) +int vvz_cli_close(char *block_device) { // Requires: block_device is a correct block device path -// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; +// char bdev_path[VVZ_BDEV_PATH_MAX_LEN + 2]; // int err; // /* Gather (absolute) path to underlying block device */ // printf("Enter the absolute path to the underlying block device containing the Shufflecake volumes to close: "); -// err = sflc_safeReadLine(bdev_path, SFLC_BDEV_PATH_MAX_LEN + 2); +// err = vvz_safeReadLine(bdev_path, VVZ_BDEV_PATH_MAX_LEN + 2); // if (err) { -// sflc_log_error("Could not read path to underlying block device; error %d", err); +// vvz_log_error("Could not read path to underlying block device; error %d", err); // return err; // } // /* Check that it is absolute */ @@ -66,9 +66,9 @@ int sflc_cli_close(char *block_device) /* Actually perform the command */ -// return sflc_cmd_closeVolumes(bdev_path); +// return vvz_cmd_closeVolumes(bdev_path); - return sflc_cmd_closeVolumes(block_device); + return vvz_cmd_closeVolumes(block_device); } diff --git a/vuvuzela-userland/src/cli/dispatch.c b/vuvuzela-userland/src/cli/dispatch.c index 01da250..ad170fa 100644 --- a/vuvuzela-userland/src/cli/dispatch.c +++ b/vuvuzela-userland/src/cli/dispatch.c @@ -32,10 +32,10 @@ #include #include "cli.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/disk.h" #include "utils/log.h" -#include "sflc_constants.h" +#include "vvz_constants.h" /***************************************************** @@ -43,29 +43,29 @@ *****************************************************/ /* Used by argp to provide the automatic "-V" option */ -const char *argp_program_version = SFLC_VERSION; +const char *argp_program_version = VVZ_VERSION; /* Used by argp to provide the automatic "--help" option */ const char *argp_program_bug_address = ""; /* Signed integer values representing a handle for each option */ -#define SFLC_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option -#define SFLC_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this +#define VVZ_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option +#define VVZ_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this /***************************************************** * TYPES * *****************************************************/ -enum sflc_cli_action { - SFLC_ACT_INIT, - SFLC_ACT_OPEN, - SFLC_ACT_CLOSE, - SFLC_ACT_TESTPWD, - SFLC_ACT_CHANGEPWD +enum vvz_cli_action { + VVZ_ACT_INIT, + VVZ_ACT_OPEN, + VVZ_ACT_CLOSE, + VVZ_ACT_TESTPWD, + VVZ_ACT_CHANGEPWD }; -struct sflc_cli_arguments { - enum sflc_cli_action act; +struct vvz_cli_arguments { + enum vvz_cli_action act; char *block_device; int num_volumes; bool skip_randfill; @@ -111,9 +111,9 @@ static char doc[] = /* Description of each option */ static struct argp_option options[] = { - {"num-volumes", SFLC_OPT_NUMVOLS_KEY, "num", 0, + {"num-volumes", VVZ_OPT_NUMVOLS_KEY, "num", 0, "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 - {"skip-randfill", SFLC_OPT_SKIPRAND_KEY, 0, 0, + {"skip-randfill", VVZ_OPT_SKIPRAND_KEY, 0, 0, "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, {0} }; @@ -136,8 +136,8 @@ static struct argp argp = {options, _parseArgpKey, args_doc, doc}; * @return Error code, 0 on success */ -int sflc_cli_dispatch(int argc, char **argv) { - struct sflc_cli_arguments arguments; +int vvz_cli_dispatch(int argc, char **argv) { + struct vvz_cli_arguments arguments; arguments.act = -1; arguments.block_device = NULL; @@ -148,36 +148,36 @@ int sflc_cli_dispatch(int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, &arguments); /* Check options consistency */ - if (arguments.num_volumes && arguments.act != SFLC_ACT_INIT) { + if (arguments.num_volumes && arguments.act != VVZ_ACT_INIT) { printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); return EINVAL; } /* Check options consistency */ - if (arguments.skip_randfill && arguments.act != SFLC_ACT_INIT) { + if (arguments.skip_randfill && arguments.act != VVZ_ACT_INIT) { printf("Error: --skip-randfill can only be combined with `init'.\n"); return EINVAL; } /* Check that input is actually a block device */ - if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflc_disk_isBlockDevice(arguments.block_device)) { + if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !vvz_disk_isBlockDevice(arguments.block_device)) { printf("Error: '%s' is not a valid block device.\n", arguments.block_device); return EINVAL; } /* Dispatch to specific command */ - if (arguments.act == SFLC_ACT_INIT) { - return sflc_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); + if (arguments.act == VVZ_ACT_INIT) { + return vvz_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); } - if (arguments.act == SFLC_ACT_OPEN) { - return sflc_cli_open(arguments.block_device); + if (arguments.act == VVZ_ACT_OPEN) { + return vvz_cli_open(arguments.block_device); } - if (arguments.act == SFLC_ACT_CLOSE) { - return sflc_cli_close(arguments.block_device); + if (arguments.act == VVZ_ACT_CLOSE) { + return vvz_cli_close(arguments.block_device); } - if (arguments.act == SFLC_ACT_TESTPWD) { - return sflc_cli_testPwd(arguments.block_device); + if (arguments.act == VVZ_ACT_TESTPWD) { + return vvz_cli_testPwd(arguments.block_device); } - if (arguments.act == SFLC_ACT_CHANGEPWD) { - return sflc_cli_changePwd(arguments.block_device); + if (arguments.act == VVZ_ACT_CHANGEPWD) { + return vvz_cli_changePwd(arguments.block_device); } printf("\n"); @@ -191,26 +191,26 @@ int sflc_cli_dispatch(int argc, char **argv) { *****************************************************/ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { - struct sflc_cli_arguments *arguments = state->input; + struct vvz_cli_arguments *arguments = state->input; switch (key) { /* We are parsing an argument (not an option) */ case ARGP_KEY_ARG: /* We are parsing the command */ if (state->arg_num == 0) { - if (strcmp(arg, SFLC_CLI_INITACT) == 0) { - arguments->act = SFLC_ACT_INIT; - } else if (strcmp(arg, SFLC_CLI_OPENACT) == 0) { - arguments->act = SFLC_ACT_OPEN; - } else if (strcmp(arg, SFLC_CLI_CLOSEACT) == 0) { - arguments->act = SFLC_ACT_CLOSE; - } else if (strcmp(arg, SFLC_CLI_TESTPWDACT) == 0) { - arguments->act = SFLC_ACT_TESTPWD; - } else if (strcmp(arg, SFLC_CLI_CHANGEPWDACT) == 0) { - arguments->act = SFLC_ACT_CHANGEPWD; + if (strcmp(arg, VVZ_CLI_INITACT) == 0) { + arguments->act = VVZ_ACT_INIT; + } else if (strcmp(arg, VVZ_CLI_OPENACT) == 0) { + arguments->act = VVZ_ACT_OPEN; + } else if (strcmp(arg, VVZ_CLI_CLOSEACT) == 0) { + arguments->act = VVZ_ACT_CLOSE; + } else if (strcmp(arg, VVZ_CLI_TESTPWDACT) == 0) { + arguments->act = VVZ_ACT_TESTPWD; + } else if (strcmp(arg, VVZ_CLI_CHANGEPWDACT) == 0) { + arguments->act = VVZ_ACT_CHANGEPWD; } else { argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", - SFLC_CLI_INITACT, SFLC_CLI_OPENACT, SFLC_CLI_CLOSEACT, SFLC_CLI_TESTPWDACT, SFLC_CLI_CHANGEPWDACT); + VVZ_CLI_INITACT, VVZ_CLI_OPENACT, VVZ_CLI_CLOSEACT, VVZ_CLI_TESTPWDACT, VVZ_CLI_CHANGEPWDACT); } /* We are parsing the block device */ } else if (state->arg_num == 1) { @@ -222,10 +222,10 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { break; /* We are parsing an option */ - case SFLC_OPT_NUMVOLS_KEY: + case VVZ_OPT_NUMVOLS_KEY: arguments->num_volumes = atoi(arg); break; - case SFLC_OPT_SKIPRAND_KEY: + case VVZ_OPT_SKIPRAND_KEY: arguments->skip_randfill = true; break; diff --git a/vuvuzela-userland/src/cli/init.c b/vuvuzela-userland/src/cli/init.c index 302a96d..fc065d9 100644 --- a/vuvuzela-userland/src/cli/init.c +++ b/vuvuzela-userland/src/cli/init.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/input.h" #include "utils/log.h" @@ -45,12 +45,12 @@ * * @return Error code, 0 on success */ -int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) +int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) { // Requires: block_device is a correct block device path - sflc_cmd_InitArgs args; - char str_nrvols[SFLC_BIGBUFSIZE]; - char *pwds[SFLC_DEV_MAX_VOLUMES]; - size_t pwd_lens[SFLC_DEV_MAX_VOLUMES]; + vvz_cmd_InitArgs args; + char str_nrvols[VVZ_BIGBUFSIZE]; + char *pwds[VVZ_DEV_MAX_VOLUMES]; + size_t pwd_lens[VVZ_DEV_MAX_VOLUMES]; int err; args.bdev_path = block_device; @@ -60,15 +60,15 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) args.nr_vols = num_volumes; } else { // If not, ask user for number of volumes - printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLC_DEV_MAX_VOLUMES); - err = sflc_safeReadLine(str_nrvols, SFLC_BIGBUFSIZE); + printf("\nHow many volumes do you want to create (maximum is %d)? ", VVZ_DEV_MAX_VOLUMES); + err = vvz_safeReadLine(str_nrvols, VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Error: could not read number of volumes; error %d", err); + vvz_log_error("Error: could not read number of volumes; error %d", err); return err; } /* Parse string */ if (sscanf(str_nrvols, "%lu\n", &args.nr_vols) != 1) { - sflc_log_error("Error: could not parse number of volumes"); + vvz_log_error("Error: could not parse number of volumes"); return EINVAL; } } @@ -78,8 +78,8 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) printf("Error: number of volumes must be a positive integer"); return EINVAL; } - if (args.nr_vols > SFLC_DEV_MAX_VOLUMES) { - printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLC_DEV_MAX_VOLUMES); + if (args.nr_vols > VVZ_DEV_MAX_VOLUMES) { + printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", VVZ_DEV_MAX_VOLUMES); return EINVAL; } @@ -89,13 +89,13 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) size_t i; for (i = 0; i < args.nr_vols; i++) { // Allocate pwd - pwds[i] = malloc(SFLC_BIGBUFSIZE); + pwds[i] = malloc(VVZ_BIGBUFSIZE); /* Read it */ printf("Choose password for volume %lu (must not be empty): ", i); - err = sflc_safeReadLine(pwds[i], SFLC_BIGBUFSIZE); + err = vvz_safeReadLine(pwds[i], VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Could not read password for volume %lu; error %d", i, err); + vvz_log_error("Could not read password for volume %lu; error %d", i, err); return err; } @@ -103,7 +103,7 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) pwd_lens[i] = strlen(pwds[i]); /* Check non-empty */ if (pwd_lens[i] == 0) { - sflc_log_error("Password cannot be empty!"); + vvz_log_error("Password cannot be empty!"); return EINVAL; } } @@ -114,5 +114,5 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) args.no_randfill = skip_randfill; /* Actually perform the command */ - return sflc_cmd_initVolumes(&args); + return vvz_cmd_initVolumes(&args); } diff --git a/vuvuzela-userland/src/cli/open.c b/vuvuzela-userland/src/cli/open.c index 45464c9..3cfe4dd 100644 --- a/vuvuzela-userland/src/cli/open.c +++ b/vuvuzela-userland/src/cli/open.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/input.h" #include "utils/log.h" @@ -45,10 +45,10 @@ * * @return Error code, 0 on success */ -int sflc_cli_open(char *block_device) +int vvz_cli_open(char *block_device) { // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs args; - char pwd[SFLC_BIGBUFSIZE]; + vvz_cmd_OpenArgs args; + char pwd[VVZ_BIGBUFSIZE]; size_t pwd_len; int err; @@ -56,16 +56,16 @@ int sflc_cli_open(char *block_device) /* Gather password */ printf("Enter the password for the most secret volume you want to open: "); - err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); + err = vvz_safeReadPassphrase(pwd, VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Could not read password; error %d", err); + vvz_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ pwd_len = strlen(pwd); /* Check non-empty */ if (pwd_len == 0) { - sflc_log_error("Password cannot be empty!"); + vvz_log_error("Password cannot be empty!"); return EINVAL; } /* Assign them */ @@ -73,5 +73,5 @@ int sflc_cli_open(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - return sflc_cmd_openVolumes(&args); + return vvz_cmd_openVolumes(&args); } diff --git a/vuvuzela-userland/src/cli/testpwd.c b/vuvuzela-userland/src/cli/testpwd.c index 550df25..48cf710 100644 --- a/vuvuzela-userland/src/cli/testpwd.c +++ b/vuvuzela-userland/src/cli/testpwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/input.h" #include "utils/log.h" @@ -45,12 +45,12 @@ * * @return Error code, 0 on success */ -int sflc_cli_testPwd(char *block_device) +int vvz_cli_testPwd(char *block_device) { // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs args; - sflc_DmbCell dmb_cell; -// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; - char pwd[SFLC_BIGBUFSIZE]; + vvz_cmd_OpenArgs args; + vvz_DmbCell dmb_cell; +// char bdev_path[VVZ_BDEV_PATH_MAX_LEN + 2]; + char pwd[VVZ_BIGBUFSIZE]; size_t pwd_len; int err; @@ -58,9 +58,9 @@ int sflc_cli_testPwd(char *block_device) /* Gather password */ printf("Enter the password you want to test: "); - err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); + err = vvz_safeReadPassphrase(pwd, VVZ_BIGBUFSIZE); if (err) { - sflc_log_error("Could not read password; error %d", err); + vvz_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -70,14 +70,14 @@ int sflc_cli_testPwd(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - err = sflc_cmd_testPwd(&args, &dmb_cell); + err = vvz_cmd_testPwd(&args, &dmb_cell); if (err) { - sflc_log_error("Could not test password; error %d", err); + vvz_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); } else { printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); diff --git a/vuvuzela-userland/src/commands/change_pwd.c b/vuvuzela-userland/src/commands/change_pwd.c index 2ef64e0..a56b3dc 100644 --- a/vuvuzela-userland/src/commands/change_pwd.c +++ b/vuvuzela-userland/src/commands/change_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -51,8 +51,8 @@ * * @return Error code, 0 on success */ -int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args) +int vvz_cmd_changePwd(vvz_cmd_ChangePwdArgs *args) { /* Delegate entirely to the function reading the DMB */ - return sflc_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); + return vvz_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); } diff --git a/vuvuzela-userland/src/commands/close.c b/vuvuzela-userland/src/commands/close.c index 4c180f6..e3784bc 100644 --- a/vuvuzela-userland/src/commands/close.c +++ b/vuvuzela-userland/src/commands/close.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/string.h" #include "utils/file.h" @@ -60,18 +60,18 @@ static int _closeVolumes(char **labels, size_t nr_vols); * * @return Error code, 0 on success */ -int sflc_cmd_closeVolumes(char *bdev_path) +int vvz_cmd_closeVolumes(char *bdev_path) { - char *labels[SFLC_DEV_MAX_VOLUMES]; + char *labels[VVZ_DEV_MAX_VOLUMES]; size_t nr_vols; int err; /* Allocate labels */ size_t i; - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - labels[i] = malloc(SFLC_MAX_VOL_NAME_LEN + 1); + for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { + labels[i] = malloc(VVZ_MAX_VOL_NAME_LEN + 1); if (!labels[1]) { - sflc_log_error("Could not allocate volume label %lu", i); + vvz_log_error("Could not allocate volume label %lu", i); return ENOMEM; // Do not free the ones already allocated } } @@ -79,14 +79,14 @@ int sflc_cmd_closeVolumes(char *bdev_path) /* Read them */ err = _readVolumesList(bdev_path, labels, &nr_vols); if (err) { - sflc_log_error("Could not read volume list from sysfs; error %d", err); + vvz_log_error("Could not read volume list from sysfs; error %d", err); goto out; } /* Close the volumes (in reverse order of opening) */ err = _closeVolumes(labels, nr_vols); if (err) { - sflc_log_error("Could not close volumes; error %d", err); + vvz_log_error("Could not close volumes; error %d", err); goto out; } @@ -95,7 +95,7 @@ int sflc_cmd_closeVolumes(char *bdev_path) out: - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { free(labels[i]); } return err; @@ -109,20 +109,20 @@ out: /* Reads the list of volumes from sysfs */ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) { - char bdev_path_noslash[SFLC_BDEV_PATH_MAX_LEN + 1]; - char openvolumes_path[SFLC_BIGBUFSIZE]; + char bdev_path_noslash[VVZ_BDEV_PATH_MAX_LEN + 1]; + char openvolumes_path[VVZ_BIGBUFSIZE]; char *str_openvolumes; /* Remove the slashes from the bdev_path (replace with underscores) */ strcpy(bdev_path_noslash, bdev_path); - sflc_str_replaceAll(bdev_path_noslash, '/', '_'); + vvz_str_replaceAll(bdev_path_noslash, '/', '_'); /* Build path to sysfsy file containing open volumes list */ - sprintf(openvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_path_noslash, SFLC_SYSFS_OPENVOLUMES_FILENAME); + sprintf(openvolumes_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_path_noslash, VVZ_SYSFS_OPENVOLUMES_FILENAME); /* Read the sysfs file */ - str_openvolumes = sflc_readFile(openvolumes_path); + str_openvolumes = vvz_readFile(openvolumes_path); if (!str_openvolumes) { - sflc_log_error("Could not read file %s", openvolumes_path); + vvz_log_error("Could not read file %s", openvolumes_path); return EBADF; } @@ -133,8 +133,8 @@ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) str_openvolumes = endptr; /* Just to be sure */ - if (*nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); + if (*nr_vols > VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); return EBADF; } @@ -143,10 +143,10 @@ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) for (i = 0; i < *nr_vols; i++) { /* Trust the content of the sysfs file */ if (sscanf(str_openvolumes, " %s", labels[i]) != 1) { - sflc_log_error("Could not read volume label %lu. Sysfs content:\n%s", i, str_openvolumes); + vvz_log_error("Could not read volume label %lu. Sysfs content:\n%s", i, str_openvolumes); return EBADF; } - sflc_log_debug("Label %lu to close: %s", i, labels[i]); + vvz_log_debug("Label %lu to close: %s", i, labels[i]); /* Skip past the whitespace and the label */ str_openvolumes += 1 + strlen(labels[i]); @@ -164,12 +164,12 @@ static int _closeVolumes(char **labels, size_t nr_vols) /* Eazy peazy */ int i; for (i = nr_vols-1; i >= 0; i--) { - err = sflc_ops_closeVolume(labels[i]); + err = vvz_ops_closeVolume(labels[i]); if (err) { - sflc_log_error("Could not close volume %s; error %d", labels[i], err); + vvz_log_error("Could not close volume %s; error %d", labels[i], err); return err; } - sflc_log_debug("Closed volume %s", labels[i]); + vvz_log_debug("Closed volume %s", labels[i]); printf("Closed volume /dev/mapper/%s\n", labels[i]); } diff --git a/vuvuzela-userland/src/commands/init.c b/vuvuzela-userland/src/commands/init.c index e27e840..69eda0d 100644 --- a/vuvuzela-userland/src/commands/init.c +++ b/vuvuzela-userland/src/commands/init.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -42,9 +42,9 @@ *****************************************************/ /* The device is randomised in chunks of 1024 blocks (arbitrary number) */ -#define SFLC_BLOCKS_IN_RAND_CHUNK 1024 +#define VVZ_BLOCKS_IN_RAND_CHUNK 1024 /* That's 4 MiB */ -#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_BLOCK_SIZE) +#define VVZ_RAND_CHUNK_SIZE (VVZ_BLOCKS_IN_RAND_CHUNK * VVZ_BLOCK_SIZE) /***************************************************** @@ -72,36 +72,36 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); * * @return Error code, 0 on success */ -int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) +int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args) { - sflc_Dmb dmb; - sflc_Vmb vmb; + vvz_Dmb dmb; + vvz_Vmb vmb; int64_t dev_size; size_t nr_slices; int err; /* Sanity check */ - if (args->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create %lu volumes on a single device", args->nr_vols); + if (args->nr_vols > VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Cannot create %lu volumes on a single device", args->nr_vols); return EINVAL; } /* Get device size */ - dev_size = sflc_disk_getSize(args->bdev_path); + dev_size = vvz_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - sflc_log_error("Could not get device size for %s; error %d", args->bdev_path, err); + vvz_log_error("Could not get device size for %s; error %d", args->bdev_path, err); return err; } /* Convert to number of slices */ - nr_slices = sflc_disk_maxSlices(dev_size); - sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); + nr_slices = vvz_disk_maxSlices(dev_size); + vvz_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); /* Fill disk with random bytes, if requested */ if (!args->no_randfill) { err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); if (err) { - sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); + vvz_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); return err; } } @@ -111,16 +111,16 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) /* Sample the VMB keys */ size_t i; for (i = 0; i < dmb.nr_vols; i++) { - err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_STANDARD_KEYLEN); + err = vvz_rand_getStrongBytes(dmb.vmb_keys[i], VVZ_STANDARD_KEYLEN); if (err) { - sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); + vvz_log_error("Could not sample VMB key number %lu; error %d", i , err); return err; } } /* And write (encrypted) to disk */ - err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + err = vvz_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); if (err) { - sflc_log_error("Could not create DMB and write it to disk; error %d", err); + vvz_log_error("Could not create DMB and write it to disk; error %d", err); return err; } @@ -129,15 +129,15 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) for (i = 0; i < args->nr_vols; i++) { /* This volume's prev_vmb_key */ if (i > 0) { - memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_STANDARD_KEYLEN); + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], VVZ_STANDARD_KEYLEN); } /* Sample this volume's VEK */ - sflc_rand_getStrongBytes(vmb.volume_key, SFLC_STANDARD_KEYLEN); + vvz_rand_getStrongBytes(vmb.volume_key, VVZ_STANDARD_KEYLEN); /* Write complete volume header (VMB + PM) */ - err = sflc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); + err = vvz_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); if (err) { - sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); + vvz_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); return err; } } @@ -158,9 +158,9 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) int err; /* Allocate chunk */ - rand_chunk = malloc(SFLC_RAND_CHUNK_SIZE); + rand_chunk = malloc(VVZ_RAND_CHUNK_SIZE); if (!rand_chunk) { - sflc_log_error("Could not allocate %d bytes for chunk of random data", SFLC_RAND_CHUNK_SIZE); + vvz_log_error("Could not allocate %d bytes for chunk of random data", VVZ_RAND_CHUNK_SIZE); return ENOMEM; } @@ -169,20 +169,20 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) uint64_t sector = 0; while (blocks_remaining > 0) { uint64_t blocks_to_write = - (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; - uint64_t bytes_to_write = blocks_to_write * SFLC_BLOCK_SIZE; + (blocks_remaining > VVZ_BLOCKS_IN_RAND_CHUNK) ? VVZ_BLOCKS_IN_RAND_CHUNK : blocks_remaining; + uint64_t bytes_to_write = blocks_to_write * VVZ_BLOCK_SIZE; /* Sample random bytes */ - err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); + err = vvz_rand_getWeakBytes(rand_chunk, bytes_to_write); if (err) { - sflc_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); + vvz_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); goto out; } /* Write on disk */ - err = sflc_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); + err = vvz_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); if (err) { - sflc_log_error("Could not write random bytes on disk; error %d", err); + vvz_log_error("Could not write random bytes on disk; error %d", err); goto out; } diff --git a/vuvuzela-userland/src/commands/open.c b/vuvuzela-userland/src/commands/open.c index 6c58225..6b789bd 100644 --- a/vuvuzela-userland/src/commands/open.c +++ b/vuvuzela-userland/src/commands/open.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/disk.h" #include "utils/file.h" @@ -63,33 +63,33 @@ static int _getNextDevId(size_t *next_dev_id); * * @return Error code (also if no volume could be opened), 0 on success */ -int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) +int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args) { int64_t dev_size; size_t nr_slices; - sflc_DmbCell dmb_cell; - sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; + vvz_DmbCell dmb_cell; + vvz_Vmb vmbs[VVZ_DEV_MAX_VOLUMES]; size_t dev_id; int err; /* Get number of slices */ - dev_size = sflc_disk_getSize(args->bdev_path); + dev_size = vvz_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); + vvz_log_error("Could not read device size for %s; error %d", args->bdev_path, err); return err; } - nr_slices = sflc_disk_maxSlices(dev_size); + nr_slices = vvz_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ - err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); + err = vvz_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); if (err) { - sflc_log_error("Could not read DMB; error %d", err); + vvz_log_error("Could not read DMB; error %d", err); return err; } /* Was there one? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("The provided password opens no volume on the device"); + if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("The provided password opens no volume on the device"); return EINVAL; } printf("Password is correct! Opening volumes...\n"); @@ -108,9 +108,9 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) } /* Read and unlock VMB */ - err = sflc_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + err = vvz_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); if (err) { - sflc_log_error("Could not read VMB %d on device %s; error %d", + vvz_log_error("Could not read VMB %d on device %s; error %d", i, args->bdev_path, err); return err; } @@ -119,23 +119,23 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) /* Get the ID that will be assigned to the block device */ err = _getNextDevId(&dev_id); if (err) { - sflc_log_error("Could not get next device ID; error %d", err); + vvz_log_error("Could not get next device ID; error %d", err); return err; } - sflc_log_debug("Next device ID is %lu", dev_id); + vvz_log_debug("Next device ID is %lu", dev_id); /* Open volumes "in order" */ for (i = 0; i <= dmb_cell.vol_idx; i++) { - err = sflc_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + err = vvz_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); if (err) { - sflc_log_error("Could not open volume %d; error %d. " + vvz_log_error("Could not open volume %d; error %d. " "Previous volumes on the device might have already " "been opened, it's recommended you close them", i, err); return err; } - sflc_log_debug("Successfully opened volume %d with VMB key", i); - printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); + vvz_log_debug("Successfully opened volume %d with VMB key", i); + printf("Opened volume /dev/mapper/vvz_%lu_%d\n", dev_id, i); } return 0; @@ -153,21 +153,21 @@ static int _getNextDevId(size_t *next_dev_id) int err; /* Read sysfs entry */ - str_nextdevid = sflc_readFile(SFLC_SYSFS_NEXTDEVID); + str_nextdevid = vvz_readFile(VVZ_SYSFS_NEXTDEVID); if (!str_nextdevid) { - sflc_log_error("Could not read sysfs entry %s", SFLC_SYSFS_NEXTDEVID); + vvz_log_error("Could not read sysfs entry %s", VVZ_SYSFS_NEXTDEVID); return EINVAL; } /* Parse integer */ if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { - sflc_log_error("Error parsing content of file %s", SFLC_SYSFS_NEXTDEVID); + vvz_log_error("Error parsing content of file %s", VVZ_SYSFS_NEXTDEVID); err = EINVAL; goto err_devid; } /* Sanity check */ - if (*next_dev_id >= SFLC_TOT_MAX_DEVICES) { - sflc_log_error("There are already %d open devices, this is the maximum allowed", SFLC_TOT_MAX_DEVICES); + if (*next_dev_id >= VVZ_TOT_MAX_DEVICES) { + vvz_log_error("There are already %d open devices, this is the maximum allowed", VVZ_TOT_MAX_DEVICES); err = E2BIG; goto err_devid; } diff --git a/vuvuzela-userland/src/commands/test_pwd.c b/vuvuzela-userland/src/commands/test_pwd.c index f794347..1d011c7 100644 --- a/vuvuzela-userland/src/commands/test_pwd.c +++ b/vuvuzela-userland/src/commands/test_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -52,8 +52,8 @@ * * @return Error code, 0 on success */ -int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell) +int vvz_cmd_testPwd(vvz_cmd_OpenArgs *args, vvz_DmbCell *dmb_cell) { /* Delegate entirely to the function reading the DMB */ - return sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); + return vvz_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); } diff --git a/vuvuzela-userland/src/header/device_master_block.c b/vuvuzela-userland/src/header/device_master_block.c index 1cfcd9b..a6522d6 100644 --- a/vuvuzela-userland/src/header/device_master_block.c +++ b/vuvuzela-userland/src/header/device_master_block.c @@ -60,21 +60,21 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool * * @return The error code, 0 on success */ -int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) +int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) { char *salt; int err; /* Sanity check */ - if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Randomise whole block */ - err = sflc_rand_getWeakBytes(disk_block, SFLC_BLOCK_SIZE); + err = vvz_rand_getWeakBytes(disk_block, VVZ_BLOCK_SIZE); if (err) { - sflc_log_error("Could not randomise DMB; error %d", err); + vvz_log_error("Could not randomise DMB; error %d", err); return err; } @@ -84,12 +84,12 @@ int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block /* Loop over all VMB keys to encrypt them */ size_t i; for (i = 0; i < dmb->nr_vols; i++) { - char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); + char *dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (i * VVZ_DMB_CELL_SIZE); /* Encrypt it */ err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); if (err) { - sflc_log_error("Could not encrypt VMB key number %lu; error %d", i, err); + vvz_log_error("Could not encrypt VMB key number %lu; error %d", i, err); return err; } } @@ -106,50 +106,50 @@ int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= VVZ_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell) +int vvz_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell) { // KDF salt char *salt; // The KDF-derived key - char kek[SFLC_STANDARD_KEYLEN]; + char kek[VVZ_STANDARD_KEYLEN]; // The unlocked VMB key - char vmb_key[SFLC_STANDARD_KEYLEN]; + char vmb_key[VVZ_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK once and for all */ salt = disk_block; - err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); + err = vvz_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - sflc_log_error("Could not perform KDF: error %d", err); + vvz_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - sflc_log_debug("Successfully derived key-encryption-key with KDF"); + vvz_log_debug("Successfully derived key-encryption-key with KDF"); /* Init dmb->vol_idx to invalid */ - dmb_cell->vol_idx = SFLC_DEV_MAX_VOLUMES; + dmb_cell->vol_idx = VVZ_DEV_MAX_VOLUMES; /* Try all DMB cells */ size_t i; - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - char *enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); + for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { + char *enc_dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (i * VVZ_DMB_CELL_SIZE); bool match; /* Try to decrypt this one */ err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); if (err) { - sflc_log_error("Error decrypting DMB cell number %lu; error %d", i, err); + vvz_log_error("Error decrypting DMB cell number %lu; error %d", i, err); goto bad_decrypt; } /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ if (match) { - sflc_log_debug("The provided password unlocks volume %lu", i); + vvz_log_debug("The provided password unlocks volume %lu", i); dmb_cell->vol_idx = i; - memcpy(dmb_cell->vmb_key, vmb_key, SFLC_STANDARD_KEYLEN); + memcpy(dmb_cell->vmb_key, vmb_key, VVZ_STANDARD_KEYLEN); } } @@ -160,7 +160,7 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *d bad_decrypt: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_STANDARD_KEYLEN); + memset(kek, 0, VVZ_STANDARD_KEYLEN); return err; } @@ -174,28 +174,28 @@ bad_kdf: * * @return Error code, 0 on success */ -int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len) +int vvz_dmb_setCell(char *disk_block, vvz_DmbCell *dmb_cell, char *pwd, size_t pwd_len) { char *salt; char *enc_dmb_cell; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Pointers inside DMB */ salt = disk_block; - enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLC_DMB_CELL_SIZE); + enc_dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (dmb_cell->vol_idx * VVZ_DMB_CELL_SIZE); /* Encrypt with KDF-derived key */ err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); if (err) { - sflc_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); + vvz_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); return err; } - sflc_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); + vvz_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); return 0; } @@ -210,36 +210,36 @@ static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vm { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; + char *enc_vmb_key = iv + VVZ_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + VVZ_STANDARD_KEYLEN; // Key-encryption-key derived from KDF - char kek[SFLC_STANDARD_KEYLEN]; + char kek[VVZ_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK */ - err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); + err = vvz_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - sflc_log_error("Could not perform KDF: error %d", err); + vvz_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - sflc_log_debug("Successfully derived key-encryption-key with KDF"); + vvz_log_debug("Successfully derived key-encryption-key with KDF"); /* Sample VMB_IV */ - err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); + err = vvz_rand_getWeakBytes(iv, VVZ_AESGCM_PADDED_IVLEN); if (err) { - sflc_log_error("Could not sample prologue IV: error %d", err); + vvz_log_error("Could not sample prologue IV: error %d", err); goto bad_sample_iv; } - sflc_log_debug("Successfully sampled prologue IV"); + vvz_log_debug("Successfully sampled prologue IV"); /* Encrypt the VMB key */ - err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_STANDARD_KEYLEN, iv, enc_vmb_key, mac); + err = vvz_aes256gcm_encrypt(kek, vmb_key, VVZ_STANDARD_KEYLEN, iv, enc_vmb_key, mac); if (err) { - sflc_log_error("Could not encrypt the VMB key: error %d", err); + vvz_log_error("Could not encrypt the VMB key: error %d", err); goto bad_encrypt; } - sflc_log_debug("Successfully encrypted VMB key with key-encryption-key"); + vvz_log_debug("Successfully encrypted VMB key with key-encryption-key"); // No prob err = 0; @@ -249,7 +249,7 @@ bad_encrypt: bad_sample_iv: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_STANDARD_KEYLEN); + memset(kek, 0, VVZ_STANDARD_KEYLEN); return err; } @@ -258,18 +258,18 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; + char *enc_vmb_key = iv + VVZ_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + VVZ_STANDARD_KEYLEN; // Error code int err; /* Decrypt the VMB key */ - err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_STANDARD_KEYLEN, mac, iv, vmb_key, match); + err = vvz_aes256gcm_decrypt(kek, enc_vmb_key, VVZ_STANDARD_KEYLEN, mac, iv, vmb_key, match); if (err) { - sflc_log_error("Error while decrypting VMB key: error %d", err); + vvz_log_error("Error while decrypting VMB key: error %d", err); return err; } - sflc_log_debug("Decrypted VMB key: MAC match = %d", *match); + vvz_log_debug("Decrypted VMB key: MAC match = %d", *match); return 0; } diff --git a/vuvuzela-userland/src/header/position_map.c b/vuvuzela-userland/src/header/position_map.c index 051161f..e48f304 100644 --- a/vuvuzela-userland/src/header/position_map.c +++ b/vuvuzela-userland/src/header/position_map.c @@ -31,7 +31,7 @@ #include #include "header.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/math.h" #include "utils/log.h" @@ -49,38 +49,38 @@ * position map as well. * * @return The memory buffer containing the position map */ -void *sflc_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) +void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) { - char pmb[SFLC_BLOCK_SIZE]; - char iv[SFLC_AESXTS_IVLEN]; + char pmb[VVZ_BLOCK_SIZE]; + char iv[VVZ_AESXTS_IVLEN]; void *epm; size_t nr_pmbs; int err; // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + nr_pmbs = ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); // Allocate EPM - epm = malloc(nr_pmbs * SFLC_BLOCK_SIZE); + epm = malloc(nr_pmbs * VVZ_BLOCK_SIZE); if (!epm) { - sflc_log_error("Could not malloc EPM array"); + vvz_log_error("Could not malloc EPM array"); return NULL; } // Fill cleartext PMB with 0xFF - memset(pmb, SFLC_EPM_FILLER, SFLC_BLOCK_SIZE); + memset(pmb, VVZ_EPM_FILLER, VVZ_BLOCK_SIZE); // Set the IV for the first encryption - memset(iv, 0, SFLC_AESXTS_IVLEN); - *((uint64_t)iv) = htole64(sflc_pmStartBlock(vol_idx, nr_slices)); + memset(iv, 0, VVZ_AESXTS_IVLEN); + *((uint64_t)iv) = htole64(vvz_pmStartBlock(vol_idx, nr_slices)); // Loop to encrypt each PMB int i; for (i = 0; i < nr_pmbs; i++) { // Encrypt. Auto-increment IV for next encryption - err = sflc_aes256xts_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, - epm + i*SFLC_BLOCK_SIZE); + err = vvz_aes256xts_encrypt(volume_key, pmb, VVZ_BLOCK_SIZE, iv, + epm + i*VVZ_BLOCK_SIZE); if (err) { - sflc_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); + vvz_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); free(epm); return NULL; } diff --git a/vuvuzela-userland/src/header/volume_master_block.c b/vuvuzela-userland/src/header/volume_master_block.c index d73d98d..5f153c9 100644 --- a/vuvuzela-userland/src/header/volume_master_block.c +++ b/vuvuzela-userland/src/header/volume_master_block.c @@ -41,10 +41,10 @@ *****************************************************/ /* Serialise the VMB before encrypting it */ -static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); +static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb); /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); +static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb); /***************************************************** @@ -61,44 +61,44 @@ static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); * * @return The error code, 0 on success */ -int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) +int vvz_vmb_seal(vvz_Vmb *vmb, char *vmb_key, char *disk_block) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + char *enc_vmb = iv + VVZ_AESCTR_IVLEN; // Serialised VMB (dynamically allocated), to be encrypted char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + clear_vmb = malloc(VVZ_CLEAR_VMB_LEN); if (!clear_vmb) { - sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + vvz_log_error("Could not allocate %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + vvz_log_debug("Successfully allocated %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); /* Serialise the struct */ _serialiseVmb(vmb, clear_vmb); - sflc_log_debug("Serialised VMB struct"); + vvz_log_debug("Serialised VMB struct"); /* Sample VMB IV */ - err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); + err = vvz_rand_getWeakBytes(iv, VVZ_AESGCM_PADDED_IVLEN); if (err) { - sflc_log_error("Could not sample VMB IV: error %d", err); + vvz_log_error("Could not sample VMB IV: error %d", err); goto bad_sample_iv; } - sflc_log_debug("Successfully sampled VMB IV"); + vvz_log_debug("Successfully sampled VMB IV"); /* Encrypt the VMB */ - err = sflc_aes256ctr_encrypt(vmb_key, clear_vmb, SFLC_CLEAR_VMB_LEN, iv, enc_vmb); + err = vvz_aes256ctr_encrypt(vmb_key, clear_vmb, VVZ_CLEAR_VMB_LEN, iv, enc_vmb); if (err) { - sflc_log_error("Could not encrypt VMB: error %d", err); + vvz_log_error("Could not encrypt VMB: error %d", err); goto bad_encrypt; } - sflc_log_debug("Successfully encrypted VMB"); + vvz_log_debug("Successfully encrypted VMB"); // No prob err = 0; @@ -107,7 +107,7 @@ int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) bad_encrypt: bad_sample_iv: /* Always wipe and free the cleartext VMB, even on success */ - memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + memset(clear_vmb, 0, VVZ_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -123,40 +123,40 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +int vvz_vmb_unseal(char *disk_block, char *vmb_key, vvz_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + char *enc_vmb = iv + VVZ_AESCTR_IVLEN; // Decrypted VMB (dynamically allocated), to be deserialised char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + clear_vmb = malloc(VVZ_CLEAR_VMB_LEN); if (!clear_vmb) { - sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + vvz_log_error("Could not allocate %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + vvz_log_debug("Successfully allocated %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); /* Decrypt the VMB */ - err = sflc_aes256ctr_decrypt(vmb_key, enc_vmb, SFLC_CLEAR_VMB_LEN, iv, clear_vmb); + err = vvz_aes256ctr_decrypt(vmb_key, enc_vmb, VVZ_CLEAR_VMB_LEN, iv, clear_vmb); if (err) { - sflc_log_error("Error while decrypting VMB: error %d", err); + vvz_log_error("Error while decrypting VMB: error %d", err); goto bad_decrypt; } - sflc_log_debug("Successfully decrypted VMB"); + vvz_log_debug("Successfully decrypted VMB"); /* Deserialise the struct */ err = _deserialiseVmb(clear_vmb, vmb); if (err) { - sflc_log_error("Error while deserialising VMB: error %d", err); + vvz_log_error("Error while deserialising VMB: error %d", err); goto bad_deserialise; } - sflc_log_debug("Deserialised VMB struct"); + vvz_log_debug("Deserialised VMB struct"); // No prob err = 0; @@ -165,7 +165,7 @@ int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) bad_deserialise: bad_decrypt: /* Always wipe and free the VMB cleartext, even on success */ - memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + memset(clear_vmb, 0, VVZ_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -177,18 +177,18 @@ bad_clear_alloc: *****************************************************/ /* Serialise the payload before encrypting it */ -static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) +static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + VVZ_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, SFLC_STANDARD_KEYLEN); + memcpy(p_vol_key, vmb->volume_key, VVZ_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_STANDARD_KEYLEN); + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, VVZ_STANDARD_KEYLEN); /* Write the number of slices (network byte order) */ *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); @@ -200,18 +200,18 @@ static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) +static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + VVZ_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, SFLC_STANDARD_KEYLEN); + memcpy(vmb->volume_key, p_vol_key, VVZ_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_STANDARD_KEYLEN); + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, VVZ_STANDARD_KEYLEN); /* Read number of slices (network byte order) */ vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); diff --git a/vuvuzela-userland/src/main.c b/vuvuzela-userland/src/main.c index 737b57e..08a4b12 100644 --- a/vuvuzela-userland/src/main.c +++ b/vuvuzela-userland/src/main.c @@ -39,6 +39,6 @@ int main(int argc, char **argv) { - return sflc_cli_dispatch(argc, argv); + return vvz_cli_dispatch(argc, argv); } diff --git a/vuvuzela-userland/src/operations/devmapper.c b/vuvuzela-userland/src/operations/devmapper.c index 91b3b51..2f622b5 100644 --- a/vuvuzela-userland/src/operations/devmapper.c +++ b/vuvuzela-userland/src/operations/devmapper.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/string.h" @@ -46,7 +46,7 @@ *****************************************************/ /** - * Build parameter list for ctor in dm_sflc, and send DM ioctl to create + * Build parameter list for ctor in dm_vvz, and send DM ioctl to create * virtual block device. * * @param bdev_path The path to the underlying device @@ -56,35 +56,35 @@ * * @return Error code, 0 on success */ -int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) +int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb *vmb) { - char label[SFLC_BIGBUFSIZE]; + char label[VVZ_BIGBUFSIZE]; char *hex_key; - char params[SFLC_BIGBUFSIZE]; + char params[VVZ_BIGBUFSIZE]; uint64_t num_sectors; int err; /* Build volume label */ - sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); + sprintf(label, "vvz_%lu_%lu", dev_id, vol_idx); /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vmb->volume_key, SFLC_STANDARD_KEYLEN); + hex_key = vvz_toHex(vmb->volume_key, VVZ_STANDARD_KEYLEN); if (!hex_key) { - sflc_log_error("Could not encode volume key to hexadecimal"); + vvz_log_error("Could not encode volume key to hexadecimal"); err = ENOMEM; goto err_hexkey; } /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_BLOCK_SCALE; + num_sectors = ((uint64_t) vmb->nr_slices) * VVZ_BLOCKS_PER_LOG_SLICE * VVZ_BLOCK_SCALE; /* Build param list */ sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); /* Issue ioctl */ - err = sflc_dm_create(label, num_sectors, params); + err = vvz_dm_create(label, num_sectors, params); if (err) { - sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + vvz_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); goto err_dmcreate; } err = 0; @@ -104,8 +104,8 @@ err_hexkey: * * @return Error code, 0 on success */ -int sflc_ops_closeVolume(char *label) +int vvz_ops_closeVolume(char *label) { /* Issue ioctl */ - return sflc_dm_destroy(label); + return vvz_dm_destroy(label); } diff --git a/vuvuzela-userland/src/operations/dmb.c b/vuvuzela-userland/src/operations/dmb.c index 662742e..b15e583 100644 --- a/vuvuzela-userland/src/operations/dmb.c +++ b/vuvuzela-userland/src/operations/dmb.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -54,30 +54,30 @@ * * @return Error code, 0 on success */ -int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) +int vvz_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, vvz_Dmb *dmb) { /* On-disk DMB */ - char enc_dmb[SFLC_BLOCK_SIZE]; + char enc_dmb[VVZ_BLOCK_SIZE]; /* Error code */ int err; /* Sanity check */ - if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Seal DMB */ - err = sflc_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); + err = vvz_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); if (err) { - sflc_log_error("Coul dnot seal DMB; error %d", err); + vvz_log_error("Coul dnot seal DMB; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); + err = vvz_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - sflc_log_error("Could not write DMB to disk; error %d", err); + vvz_log_error("Could not write DMB to disk; error %d", err); return err; } @@ -93,26 +93,26 @@ int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb * * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= VVZ_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) +int vvz_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, vvz_DmbCell *dmb) { - char enc_dmb[SFLC_BLOCK_SIZE]; + char enc_dmb[VVZ_BLOCK_SIZE]; int err; /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); + err = vvz_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - sflc_log_error("Could not read DMB from disk; error %d", err); + vvz_log_error("Could not read DMB from disk; error %d", err); return err; } /* Unseal it */ - err = sflc_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); + err = vvz_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); if (err) { - sflc_log_error("Could not unseal DMB; error %d", err); + vvz_log_error("Could not unseal DMB; error %d", err); return err; } @@ -129,35 +129,35 @@ int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *d * * @return Error code, 0 on success */ -int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) +int vvz_ops_rewriteDmbCell(char *bdev_path, vvz_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) { - char enc_dmb[SFLC_BLOCK_SIZE]; + char enc_dmb[VVZ_BLOCK_SIZE]; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= VVZ_DEV_MAX_VOLUMES) { + vvz_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); + err = vvz_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - sflc_log_error("Could not read DMB from disk; error %d", err); + vvz_log_error("Could not read DMB from disk; error %d", err); return err; } /* Update the relevant cell */ - err = sflc_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); + err = vvz_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); if (err) { - sflc_log_error("Could not update DMB cell; error %d", err); + vvz_log_error("Could not update DMB cell; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); + err = vvz_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - sflc_log_error("Could not write DMB to disk; error %d", err); + vvz_log_error("Could not write DMB to disk; error %d", err); return err; } diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c index d35acdd..0ea3ed1 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -33,7 +33,7 @@ #include "header.h" #include "header.h" #include "operations.h" -#include "utils/sflc.h" +#include "utils/vvz.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -53,33 +53,33 @@ * * @return Error code, 0 on success */ -int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) +int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size_t vol_idx) { - char enc_vmb[SFLC_BLOCK_SIZE]; - sflc_EncPosMap epm; + char enc_vmb[VVZ_BLOCK_SIZE]; + vvz_EncPosMap epm; uint64_t sector; int err; // Encrypt VMB - err = sflc_vmb_seal(vmb, vmb_key, enc_vmb); + err = vvz_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { - sflc_log_error("Could not seal VMB; error %d", err); + vvz_log_error("Could not seal VMB; error %d", err); goto out; } // Write it to disk - sector = sflc_vmbPosition(vol_idx, vmb->nr_slices); - err = sflc_disk_writeBlock(bdev_path, sector, enc_vmb); + sector = vvz_vmbPosition(vol_idx, vmb->nr_slices); + err = vvz_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { - sflc_log_error("Could not write VMB to disk; error %d", err); + vvz_log_error("Could not write VMB to disk; error %d", err); goto out; } sector += 1; // Create encrypted empty position map - err = sflc_epm_create(vmb->nr_slices, vmb->volume_key, &epm); + err = vvz_epm_create(vmb->nr_slices, vmb->volume_key, &epm); if (err) { - sflc_log_error("Could not create encrypted empty position map; error %d", err); + vvz_log_error("Could not create encrypted empty position map; error %d", err); goto out; } @@ -88,20 +88,20 @@ int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, si for (i = 0; i < epm.nr_arrays; i++) { char *iv_block = epm.iv_blocks[i]; char *pmb_array = epm.pmb_arrays[i]; - size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); + size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : VVZ_BLOCKS_PER_LOG_SLICE); // First write the IV block - err = sflc_disk_writeBlock(bdev_path, sector, iv_block); + err = vvz_disk_writeBlock(bdev_path, sector, iv_block); if (err) { - sflc_log_error("Could not write IV block to disk; error %d", err); + vvz_log_error("Could not write IV block to disk; error %d", err); goto out; } sector += 1; // Then the whole PMB array - err = sflc_disk_writeManyBlocks(bdev_path, sector, pmb_array, nr_pmbs); + err = vvz_disk_writeManyBlocks(bdev_path, sector, pmb_array, nr_pmbs); if (err) { - sflc_log_error("Could not write PMB array to disk; error %d", err); + vvz_log_error("Could not write PMB array to disk; error %d", err); goto out; } sector += nr_pmbs; @@ -133,30 +133,30 @@ out: * * @return Error code, 0 on success */ -int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) +int vvz_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, vvz_Vmb *vmb) { - char enc_vmb[SFLC_BLOCK_SIZE]; + char enc_vmb[VVZ_BLOCK_SIZE]; uint64_t sector; int err; /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol_idx, nr_slices); - err = sflc_disk_readBlock(bdev_path, sector, enc_vmb); + sector = vvz_vmbPosition(vol_idx, nr_slices); + err = vvz_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); + vvz_log_error("Could not read VMB from disk; error %d", err); return err; } /* Unseal it */ - err = sflc_vmb_unseal(enc_vmb, vmb_key, vmb); + err = vvz_vmb_unseal(enc_vmb, vmb_key, vmb); if (err) { - sflc_log_error("Could not unseal VMB; error %d", err); + vvz_log_error("Could not unseal VMB; error %d", err); return err; } /* Compare the number of slices */ if (nr_slices != vmb->nr_slices) { - sflc_log_error("Incompatible header size: the device size was different when the volumes" + vvz_log_error("Incompatible header size: the device size was different when the volumes" "were created. Did you resize the device %s since last time?", bdev_path); return EINVAL; } diff --git a/vuvuzela-userland/src/utils/crypto.c b/vuvuzela-userland/src/utils/crypto.c index dfa6f95..d3b7c46 100644 --- a/vuvuzela-userland/src/utils/crypto.c +++ b/vuvuzela-userland/src/utils/crypto.c @@ -44,7 +44,7 @@ * *@return The error code (0 on success) */ -int sflc_rand_getStrongBytes(char *buf, size_t buflen) +int vvz_rand_getStrongBytes(char *buf, size_t buflen) { gcry_randomize(buf, buflen, GCRY_VERY_STRONG_RANDOM); return 0; @@ -61,7 +61,7 @@ int sflc_rand_getStrongBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int sflc_rand_getWeakBytes(char *buf, size_t buflen) +int vvz_rand_getWeakBytes(char *buf, size_t buflen) { gcry_create_nonce(buf, buflen); return 0; @@ -81,7 +81,7 @@ int sflc_rand_getWeakBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -89,26 +89,26 @@ int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + vvz_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); + vvz_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); if (err) { - sflc_log_error("Could not set AES key: error %d", err); + vvz_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflc_log_debug("Successfully set the AES key"); + vvz_log_debug("Successfully set the AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, VVZ_AESCTR_IVLEN); if (err) { - sflc_log_error("Could not set AES-CTR IV: error %d", err); + vvz_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - sflc_log_debug("Successfully set the IV"); + vvz_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -119,10 +119,10 @@ int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c } // Error check if (err) { - sflc_log_error("Could not encrypt: error %d", err); + vvz_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflc_log_debug("Successfully encrypted"); + vvz_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -149,7 +149,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) +int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -157,26 +157,26 @@ int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *p // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + vvz_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); + vvz_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); if (err) { - sflc_log_error("Could not set AES key: error %d", err); + vvz_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflc_log_debug("Successfully set AES key"); + vvz_log_debug("Successfully set AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, VVZ_AESCTR_IVLEN); if (err) { - sflc_log_error("Could not set AES-CTR IV: error %d", err); + vvz_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - sflc_log_debug("Successfully set IV"); + vvz_log_debug("Successfully set IV"); // Decrypt if (pt == NULL) { // In-place @@ -187,10 +187,10 @@ int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *p } // Error check if (err) { - sflc_log_error("Could not decrypt: error %d", err); + vvz_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - sflc_log_debug("Successfully decrypted"); + vvz_log_debug("Successfully decrypted"); // No prob err = 0; @@ -219,7 +219,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -227,26 +227,26 @@ int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_XTS, GCRY_CIPHER_SECURE); if (err) { - sflc_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); + vvz_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully instantiated AES256-XTS cipher handle"); + vvz_log_debug("Successfully instantiated AES256-XTS cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_AESXTS_KEYLEN); + err = gcry_cipher_setkey(hd, key, VVZ_AESXTS_KEYLEN); if (err) { - sflc_log_error("Could not set AES-XTS key: error %d", err); + vvz_log_error("Could not set AES-XTS key: error %d", err); goto bad_setkey; } - sflc_log_debug("Successfully set the AES-XTS key"); + vvz_log_debug("Successfully set the AES-XTS key"); // Set the IV (not a counter, as per Gcrypt docs) - err = gcry_cipher_setiv(hd, iv, SFLC_AESXTS_IVLEN); + err = gcry_cipher_setiv(hd, iv, VVZ_AESXTS_IVLEN); if (err) { - sflc_log_error("Could not set AES-XTS IV: error %d", err); + vvz_log_error("Could not set AES-XTS IV: error %d", err); goto bad_setiv; } - sflc_log_debug("Successfully set the IV"); + vvz_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -257,10 +257,10 @@ int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c } // Error check if (err) { - sflc_log_error("Could not encrypt: error %d", err); + vvz_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflc_log_debug("Successfully encrypted"); + vvz_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -289,7 +289,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) +int vvz_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -297,42 +297,42 @@ int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *c // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + vvz_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); + vvz_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); if (err) { - sflc_log_error("Could not set AES key: error %d", err); + vvz_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflc_log_debug("Successfully set the AES key"); + vvz_log_debug("Successfully set the AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, VVZ_AESGCM_IVLEN); if (err) { - sflc_log_error("Could not set AES-GCM IV: error %d", err); + vvz_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - sflc_log_debug("Successfully set the IV"); + vvz_log_debug("Successfully set the IV"); // Encrypt err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); if (err) { - sflc_log_error("Could not encrypt: error %d", err); + vvz_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflc_log_debug("Successfully encrypted"); + vvz_log_debug("Successfully encrypted"); // Get MAC - err = gcry_cipher_gettag(hd, tag, SFLC_AESGCM_TAGLEN); + err = gcry_cipher_gettag(hd, tag, VVZ_AESGCM_TAGLEN); if (err) { - sflc_log_error("Could not get MAC: error %d", err); + vvz_log_error("Could not get MAC: error %d", err); goto bad_gettag; } - sflc_log_debug("Successfully gotten MAC"); + vvz_log_debug("Successfully gotten MAC"); // No prob? err = 0; @@ -364,7 +364,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) +int vvz_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -372,52 +372,52 @@ int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char * // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + vvz_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); + vvz_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); if (err) { - sflc_log_error("Could not set AES key: error %d", err); + vvz_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflc_log_debug("Successfully set AES key"); + vvz_log_debug("Successfully set AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, VVZ_AESGCM_IVLEN); if (err) { - sflc_log_error("Could not set AES-GCM IV: error %d", err); + vvz_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - sflc_log_debug("Successfully set IV"); + vvz_log_debug("Successfully set IV"); // Decrypt err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); if (err) { - sflc_log_error("Could not decrypt: error %d", err); + vvz_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - sflc_log_debug("Successfully decrypted"); + vvz_log_debug("Successfully decrypted"); // Check MAC - err = gcry_cipher_checktag(hd, tag, SFLC_AESGCM_TAGLEN); + err = gcry_cipher_checktag(hd, tag, VVZ_AESGCM_TAGLEN); if (gcry_err_code(err) == GPG_ERR_CHECKSUM) { // Undo decryption - memset(pt, SFLC_AESGCM_POISON_PT, ct_len); + memset(pt, VVZ_AESGCM_POISON_PT, ct_len); // Flag it *match = false; } else if (err) { - sflc_log_error("Could not check MAC: error %d", err); + vvz_log_error("Could not check MAC: error %d", err); goto bad_checktag; } else { // Flag MAC verification success *match = true; } - sflc_log_debug("Successfully checked MAC: match = %d", *match); + vvz_log_debug("Successfully checked MAC: match = %d", *match); // No prob, whether MAC verified or not err = 0; @@ -444,11 +444,11 @@ bad_open: * * @return The error code (0 on success) */ -int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) +int vvz_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) { gcry_kdf_hd_t hd; const unsigned long argon_params[4] = - {SFLC_STANDARD_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; + {VVZ_STANDARD_KEYLEN, VVZ_ARGON_T, VVZ_ARGON_M, VVZ_ARGON_P}; gcry_error_t err; // Instantiate Argon2id handle @@ -456,31 +456,31 @@ int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) &hd, GCRY_KDF_ARGON2, GCRY_KDF_ARGON2ID, argon_params, 4, pwd, pwd_len, - salt, SFLC_ARGON_SALTLEN, + salt, VVZ_ARGON_SALTLEN, NULL, 0, /* Optional secret value K */ NULL, 0 /* Optional associated data X */ ); if (err) { - sflc_log_error("Could not open Argon2id handle: error %d", err); + vvz_log_error("Could not open Argon2id handle: error %d", err); goto bad_open; } - sflc_log_debug("Successfully opened Argon2id handle"); + vvz_log_debug("Successfully opened Argon2id handle"); // Run the computation err = gcry_kdf_compute(hd, NULL); if (err) { - sflc_log_error("Could not run Argon2id computation: error %d", err); + vvz_log_error("Could not run Argon2id computation: error %d", err); goto bad_compute; } - sflc_log_debug("Successfully run Argon2id computation"); + vvz_log_debug("Successfully run Argon2id computation"); // Finalise hash - err = gcry_kdf_final(hd, SFLC_STANDARD_KEYLEN, hash); + err = gcry_kdf_final(hd, VVZ_STANDARD_KEYLEN, hash); if (err) { - sflc_log_error("Could not finalise Argon2id hash: error %d", err); + vvz_log_error("Could not finalise Argon2id hash: error %d", err); goto bad_final; } - sflc_log_debug("Successfully finalised Argon2id hash"); + vvz_log_debug("Successfully finalised Argon2id hash"); // All in order err = 0; diff --git a/vuvuzela-userland/src/utils/disk.c b/vuvuzela-userland/src/utils/disk.c index 40368e6..3b4d392 100644 --- a/vuvuzela-userland/src/utils/disk.c +++ b/vuvuzela-userland/src/utils/disk.c @@ -54,7 +54,7 @@ * * @return true iff it's a block device */ -bool sflc_disk_isBlockDevice(char *path) +bool vvz_disk_isBlockDevice(char *path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { @@ -70,7 +70,7 @@ bool sflc_disk_isBlockDevice(char *path) * * @return The size (in 4096-byte sectors) of the disk, or -errno if error */ -int64_t sflc_disk_getSize(char * bdev_path) +int64_t vvz_disk_getSize(char * bdev_path) { int fd; uint64_t size_bytes; @@ -79,24 +79,24 @@ int64_t sflc_disk_getSize(char * bdev_path) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); + vvz_log_error("Could not open file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_open; } - sflc_log_debug("Opened file %s", bdev_path); + vvz_log_debug("Opened file %s", bdev_path); /* Get size in bytes */ if (ioctl(fd, BLKGETSIZE64, &size_bytes) < 0) { - sflc_log_error("Could not ioctl file %s", bdev_path); + vvz_log_error("Could not ioctl file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_ioctl; } - sflc_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); + vvz_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); - /* Compute size in SFLC sectors */ - ret = (size_bytes / SFLC_BLOCK_SIZE); + /* Compute size in VVZ sectors */ + ret = (size_bytes / VVZ_BLOCK_SIZE); bad_ioctl: close(fd); @@ -114,7 +114,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflc_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) +int vvz_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) { int fd; int err; @@ -122,29 +122,29 @@ int sflc_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); + vvz_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - sflc_log_debug("Opened file %s", bdev_path); + vvz_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { - sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * VVZ_BLOCK_SIZE, SEEK_SET) < 0) { + vvz_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + vvz_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Read in a loop */ - size_t bytes_to_read = SFLC_BLOCK_SIZE; + size_t bytes_to_read = VVZ_BLOCK_SIZE; while (bytes_to_read > 0) { /* Read syscall */ ssize_t bytes_read = read(fd, buf, bytes_to_read); if (bytes_read < 0) { - sflc_log_error("Could not read file %s at sector %lu", bdev_path, sector); + vvz_log_error("Could not read file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_read; @@ -152,7 +152,7 @@ int sflc_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Partial read? No problem just log */ if (bytes_read < bytes_to_read) { - sflc_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", + vvz_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_read, bytes_to_read); } @@ -183,7 +183,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflc_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) +int vvz_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) { int fd; int err; @@ -191,29 +191,29 @@ int sflc_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, siz /* Open file */ fd = open(bdev_path, O_WRONLY); if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); + vvz_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - sflc_log_debug("Opened file %s", bdev_path); + vvz_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { - sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * VVZ_BLOCK_SIZE, SEEK_SET) < 0) { + vvz_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + vvz_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Write in a loop */ - size_t bytes_to_write = SFLC_BLOCK_SIZE * num_sectors; + size_t bytes_to_write = VVZ_BLOCK_SIZE * num_sectors; while (bytes_to_write > 0) { /* Write syscall */ ssize_t bytes_written = write(fd, buf, bytes_to_write); if (bytes_written < 0) { - sflc_log_red("Could not write file %s at sector %lu", bdev_path, sector); + vvz_log_red("Could not write file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_write; @@ -221,7 +221,7 @@ int sflc_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, siz /* Partial write? No problem just log */ if (bytes_written < bytes_to_write) { - sflc_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", + vvz_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_written, bytes_to_write); } diff --git a/vuvuzela-userland/src/utils/dm.c b/vuvuzela-userland/src/utils/dm.c index 6c2d054..31e3f14 100644 --- a/vuvuzela-userland/src/utils/dm.c +++ b/vuvuzela-userland/src/utils/dm.c @@ -53,7 +53,7 @@ * * @return The error code (0 on success) */ -int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) +int vvz_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) { struct dm_task *dmt; uint32_t cookie = 0; @@ -64,60 +64,60 @@ int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) char * dup_virt_dev_name = strdup(virt_dev_name); char * dup_params = strdup(params); - sflc_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); + vvz_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the CREATE ioctl command) */ if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { - sflc_log_error("Cannot create dm_task"); + vvz_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - sflc_log_debug("Successfully created dm_task"); + vvz_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be created) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflc_log_error("Cannot set device name"); + vvz_log_error("Cannot set device name"); err = 2; goto out; } - sflc_log_debug("Successfully set device name"); + vvz_log_debug("Successfully set device name"); /* State that it is a Shufflecake device, pass the start and size, and the * constructor parameters */ - if (!dm_task_add_target(dmt, 0, num_sectors, SFLC_DM_TARGET_NAME, dup_params)) { - sflc_log_error("Cannot add DM target and parameters"); + if (!dm_task_add_target(dmt, 0, num_sectors, VVZ_DM_TARGET_NAME, dup_params)) { + vvz_log_error("Cannot add DM target and parameters"); err = 3; goto out; } - sflc_log_debug("Successfully added DM target and parameters"); + vvz_log_debug("Successfully added DM target and parameters"); /* Say that we want a new node under /dev/mapper */ if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { - sflc_log_error("Cannot add /dev/mapper node"); + vvz_log_error("Cannot add /dev/mapper node"); err = 4; goto out; } - sflc_log_debug("Successfully set the ADD_NODE flag"); + vvz_log_debug("Successfully set the ADD_NODE flag"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflc_log_error("Cannot get cookie"); + vvz_log_error("Cannot get cookie"); err = 5; goto out; } - sflc_log_debug("Successfully got a cookie"); + vvz_log_debug("Successfully got a cookie"); /* Run the task */ if (!dm_task_run(dmt)) { - sflc_log_error("Cannot issue ioctl"); + vvz_log_error("Cannot issue ioctl"); err = 6; goto out; } - sflc_log_debug("Successfully run DM task"); + vvz_log_debug("Successfully run DM task"); /* Wait for completion */ dm_udev_wait(cookie); - sflc_log_debug("Task completed"); + vvz_log_debug("Task completed"); // No prob err = 0; @@ -139,7 +139,7 @@ dup_free: * * @return error code (0 on success) */ -int sflc_dm_destroy(char * virt_dev_name) +int vvz_dm_destroy(char * virt_dev_name) { struct dm_task *dmt; uint32_t cookie = 0; @@ -149,47 +149,47 @@ int sflc_dm_destroy(char * virt_dev_name) /* Just to be sure, let's get it on the heap */ char * dup_virt_dev_name = strdup(virt_dev_name); - sflc_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); + vvz_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the REMOVE ioctl command) */ if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) { - sflc_log_error("Cannot create dm_task"); + vvz_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - sflc_log_debug("Successfully created dm_task"); + vvz_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be closed) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflc_log_error("Cannot set device name"); + vvz_log_error("Cannot set device name"); err = 2; goto out; } - sflc_log_debug("Successfully set device name"); + vvz_log_debug("Successfully set device name"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflc_log_error("Cannot set cookie"); + vvz_log_error("Cannot set cookie"); err = 3; goto out; } - sflc_log_debug("Successfully got a cookie"); + vvz_log_debug("Successfully got a cookie"); /* Needed for some reason */ dm_task_retry_remove(dmt); - sflc_log_debug("Successful retry_remove"); + vvz_log_debug("Successful retry_remove"); /* Run the task */ if (!dm_task_run(dmt)) { - sflc_log_error("Cannot issue ioctl"); + vvz_log_error("Cannot issue ioctl"); err = 4; goto out; } - sflc_log_debug("Successfully run task"); + vvz_log_debug("Successfully run task"); /* Wait for completion */ dm_udev_wait(cookie); - sflc_log_debug("Task completed"); + vvz_log_debug("Task completed"); // No prob err = 0; diff --git a/vuvuzela-userland/src/utils/file.c b/vuvuzela-userland/src/utils/file.c index a8e70e2..84b13ff 100644 --- a/vuvuzela-userland/src/utils/file.c +++ b/vuvuzela-userland/src/utils/file.c @@ -38,7 +38,7 @@ *****************************************************/ /* Reads the entire content of a file in a malloc-ed string */ -char *sflc_readFile(char *path) +char *vvz_readFile(char *path) { int filesize; FILE *fp; @@ -47,7 +47,7 @@ char *sflc_readFile(char *path) /* Open file */ fp = fopen(path, "r"); if (fp == NULL) { - sflc_log_error("Could not open file %s", path); + vvz_log_error("Could not open file %s", path); perror("Reason: "); goto bad_fopen; } @@ -60,7 +60,7 @@ char *sflc_readFile(char *path) /* Allocate */ content = malloc(filesize + 1); if (content == NULL) { - sflc_log_error("Could not malloc %d bytes for file content", filesize); + vvz_log_error("Could not malloc %d bytes for file content", filesize); goto bad_malloc; } diff --git a/vuvuzela-userland/src/utils/input.c b/vuvuzela-userland/src/utils/input.c index ffc0b02..8a7b776 100644 --- a/vuvuzela-userland/src/utils/input.c +++ b/vuvuzela-userland/src/utils/input.c @@ -41,13 +41,13 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflc_safeReadLine(char *buf, size_t bufsize) +int vvz_safeReadLine(char *buf, size_t bufsize) { size_t len; /* Read from stdin */ if (fgets(buf, bufsize, stdin) == NULL) { - sflc_log_error("Could not read from stdin"); + vvz_log_error("Could not read from stdin"); return EBADFD; } @@ -62,7 +62,7 @@ int sflc_safeReadLine(char *buf, size_t bufsize) /* Reads a password/passphrase in a secure way (no echo) */ -int sflc_safeReadPassphrase(char *buf, size_t bufsize) +int vvz_safeReadPassphrase(char *buf, size_t bufsize) { size_t len; struct termios old, new; @@ -79,7 +79,7 @@ int sflc_safeReadPassphrase(char *buf, size_t bufsize) if (fgets(buf, bufsize, stdin) == NULL) { // If reading the password failed, ensure echoing is turned back on tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - sflc_log_error("Could not read from stdin"); + vvz_log_error("Could not read from stdin"); return EBADFD; } diff --git a/vuvuzela-userland/src/utils/string.c b/vuvuzela-userland/src/utils/string.c index c6cf998..555a127 100644 --- a/vuvuzela-userland/src/utils/string.c +++ b/vuvuzela-userland/src/utils/string.c @@ -38,7 +38,7 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *sflc_toHex(char *buf, size_t len) +char *vvz_toHex(char *buf, size_t len) { unsigned char *u = (unsigned char *) buf; char *hex; @@ -46,7 +46,7 @@ char *sflc_toHex(char *buf, size_t len) /* Allocate buffer */ hex = malloc((len * 2) + 1); if (!hex) { - sflc_log_error("Could not allocate buffer for hex string"); + vvz_log_error("Could not allocate buffer for hex string"); return NULL; } @@ -61,7 +61,7 @@ char *sflc_toHex(char *buf, size_t len) } -void sflc_str_replaceAll(char * str, char old, char new) +void vvz_str_replaceAll(char * str, char old, char new) { int i; for (i = 0; str[i] != '\0'; i++) { diff --git a/vuvuzela-userland/test/crypto/test_aes256ctr.c b/vuvuzela-userland/test/crypto/test_aes256ctr.c index 700a910..356cd15 100644 --- a/vuvuzela-userland/test/crypto/test_aes256ctr.c +++ b/vuvuzela-userland/test/crypto/test_aes256ctr.c @@ -56,10 +56,10 @@ char *test_aes256ctr_encrypt_inplace() char iv[] = AES256CTR_TEST_IV; int err; - sflc_log_blue("Testing AES256-CTR encryption in-place"); + vvz_log_blue("Testing AES256-CTR encryption in-place"); // Encrypt in-place - err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); + err = vvz_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); // Check error mu_assert("Error while encrypting", !err); @@ -73,7 +73,7 @@ char *test_aes256ctr_encrypt_inplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } @@ -87,10 +87,10 @@ char *test_aes256ctr_encrypt_outofplace() char iv[] = AES256CTR_TEST_IV; int err; - sflc_log_blue("Testing AES256-CTR encryption out-of-place"); + vvz_log_blue("Testing AES256-CTR encryption out-of-place"); // Encrypt out-of-place - err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, ct); + err = vvz_aes256ctr_encrypt(key, msg, msg_len, iv, ct); // Check error mu_assert("Error while encrypting", !err); @@ -107,7 +107,7 @@ char *test_aes256ctr_encrypt_outofplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } @@ -120,10 +120,10 @@ char *test_aes256ctr_decrypt_inplace() char iv[] = AES256CTR_TEST_IV; int err; - sflc_log_blue("Testing AES256-CTR decryption in-place"); + vvz_log_blue("Testing AES256-CTR decryption in-place"); // Decrypt in-place - err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); + err = vvz_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); // Check error mu_assert("Error while decrypting", !err); @@ -137,7 +137,7 @@ char *test_aes256ctr_decrypt_inplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } @@ -151,10 +151,10 @@ char *test_aes256ctr_decrypt_outofplace() char iv[] = AES256CTR_TEST_IV; int err; - sflc_log_blue("Testing AES256-CTR decryption out-of-place"); + vvz_log_blue("Testing AES256-CTR decryption out-of-place"); // Decrypt out-of-place - err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, pt); + err = vvz_aes256ctr_decrypt(key, msg, msg_len, iv, pt); // Check error mu_assert("Error while decrypting", !err); @@ -171,7 +171,7 @@ char *test_aes256ctr_decrypt_outofplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } diff --git a/vuvuzela-userland/test/crypto/test_aes256gcm.c b/vuvuzela-userland/test/crypto/test_aes256gcm.c index b332c51..dfc8dfd 100644 --- a/vuvuzela-userland/test/crypto/test_aes256gcm.c +++ b/vuvuzela-userland/test/crypto/test_aes256gcm.c @@ -56,20 +56,20 @@ char *test_aes256gcm_encrypt() char key[] = AES256GCM_TEST_KEY; char iv[] = AES256GCM_TEST_IV; char ct[sizeof(pt)]; - char tag[SFLC_AESGCM_TAGLEN]; + char tag[VVZ_AESGCM_TAGLEN]; int err; - sflc_log_blue("Testing AES256-GCM encryption"); + vvz_log_blue("Testing AES256-GCM encryption"); // Encrypt - err = sflc_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); + err = vvz_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); // Check error mu_assert("Error while encrypting", !err); // Check outcome mu_assert("Ciphertext mismatch", memcmp(ct, CT, pt_len) == 0); - mu_assert("MAC mismatch", memcmp(tag, TAG, SFLC_AESGCM_TAGLEN) == 0); + mu_assert("MAC mismatch", memcmp(tag, TAG, VVZ_AESGCM_TAGLEN) == 0); // Check key untouched mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); @@ -77,7 +77,7 @@ char *test_aes256gcm_encrypt() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } @@ -93,10 +93,10 @@ char *test_aes256gcm_decrypt_good() char pt[sizeof(ct)]; int err; - sflc_log_blue("Testing AES256-GCM decryption with the proper MAC"); + vvz_log_blue("Testing AES256-GCM decryption with the proper MAC"); // Decrypt - err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + err = vvz_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); // Check error mu_assert("Error while decrypting", !err); @@ -114,7 +114,7 @@ char *test_aes256gcm_decrypt_good() // Check MAC untouched mu_assert("MAC changed", memcmp(tag, TAG, sizeof(tag)) == 0); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } @@ -130,13 +130,13 @@ char *test_aes256gcm_decrypt_fail() char pt[sizeof(ct)]; int err; - sflc_log_blue("Testing AES256-GCM decryption without the proper MAC"); + vvz_log_blue("Testing AES256-GCM decryption without the proper MAC"); // Corrupt the MAC tag[0] += 1; // Decrypt - err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + err = vvz_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); // Check error mu_assert("Error while decrypting", !err); @@ -154,7 +154,7 @@ char *test_aes256gcm_decrypt_fail() mu_assert("Tail of MAC changed", memcmp(tag+1, TAG+1, sizeof(tag)-1) == 0); mu_assert("Head of MAC changed", tag[0] == TAG[0]+1); - sflc_log_green("OK"); + vvz_log_green("OK"); return NULL; } diff --git a/vuvuzela-userland/test/crypto/test_argon2id.c b/vuvuzela-userland/test/crypto/test_argon2id.c index 7e1180d..7a26ab9 100644 --- a/vuvuzela-userland/test/crypto/test_argon2id.c +++ b/vuvuzela-userland/test/crypto/test_argon2id.c @@ -39,7 +39,7 @@ * CONSTANT VARIABLES * *****************************************************/ -char SALT[SFLC_ARGON_SALTLEN+1] = "Poor Petrol Pump"; +char SALT[VVZ_ARGON_SALTLEN+1] = "Poor Petrol Pump"; /***************************************************** @@ -48,17 +48,17 @@ char SALT[SFLC_ARGON_SALTLEN+1] = "Poor Petrol Pump"; char *test_argon2id() { - char pwd[SFLC_BIGBUFSIZE]; - char key[SFLC_STANDARD_KEYLEN]; + char pwd[VVZ_BIGBUFSIZE]; + char key[VVZ_STANDARD_KEYLEN]; int err; - sflc_log_blue("Testing Argon2id interactively with a fixed salt"); + vvz_log_blue("Testing Argon2id interactively with a fixed salt"); // Loop until user breaks out while (true) { /* Collect password */ printf("Choose password to hash (empty to skip): "); - err = sflc_safeReadLine(pwd, SFLC_BIGBUFSIZE); + err = vvz_safeReadLine(pwd, VVZ_BIGBUFSIZE); mu_assert("Could not read password", !err); /* Check if empty */ @@ -67,15 +67,15 @@ char *test_argon2id() } /* Hash it */ - err = sflc_argon2id_derive(pwd, strlen(pwd), SALT, key); + err = vvz_argon2id_derive(pwd, strlen(pwd), SALT, key); mu_assert("Could not hash password", !err); /* Print it */ printf("Salt used ASCII: \"%s\". Hex:\n", SALT); - sflc_log_hex(SALT, SFLC_ARGON_SALTLEN); + vvz_log_hex(SALT, VVZ_ARGON_SALTLEN); printf("Argon2id hash (m = %d, t = %d, p = %d):\n", - SFLC_ARGON_M, SFLC_ARGON_T, SFLC_ARGON_P); - sflc_log_hex(key, SFLC_STANDARD_KEYLEN); + VVZ_ARGON_M, VVZ_ARGON_T, VVZ_ARGON_P); + vvz_log_hex(key, VVZ_STANDARD_KEYLEN); printf("Go check the result online\n\n"); } diff --git a/vuvuzela-userland/test/main.c b/vuvuzela-userland/test/main.c index 565aa2c..ee4e805 100644 --- a/vuvuzela-userland/test/main.c +++ b/vuvuzela-userland/test/main.c @@ -49,10 +49,10 @@ int main() char *result = all_tests(); if (result != NULL) { - sflc_log_red("\nTEST FAILED: %s", result); + vvz_log_red("\nTEST FAILED: %s", result); } else { - sflc_log_green("\nALL TESTS PASSED"); + vvz_log_green("\nALL TESTS PASSED"); } return result != NULL; @@ -65,7 +65,7 @@ int main() static char *all_tests() { - sflc_log_yellow("Running crypto tests"); + vvz_log_yellow("Running crypto tests"); mu_run_test(test_aes256ctr_encrypt_inplace); mu_run_test(test_aes256ctr_encrypt_outofplace); mu_run_test(test_aes256ctr_decrypt_inplace); From f67fa1197cb36d360aa3f2ed9782a2801f41d4e0 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 17 Apr 2024 21:24:55 +0200 Subject: [PATCH 46/98] Finish init --- vuvuzela-userland/Makefile | 4 +- vuvuzela-userland/include/header.h | 3 +- vuvuzela-userland/src/commands/init.c | 2 +- vuvuzela-userland/src/header/position_map.c | 4 +- .../src/header/volume_master_block.c | 8 +-- vuvuzela-userland/src/operations/devmapper.c | 2 +- .../src/operations/volume_header.c | 51 +++++-------------- 7 files changed, 26 insertions(+), 48 deletions(-) diff --git a/vuvuzela-userland/Makefile b/vuvuzela-userland/Makefile index ee59323..19abeb7 100644 --- a/vuvuzela-userland/Makefile +++ b/vuvuzela-userland/Makefile @@ -61,10 +61,10 @@ DEPS := $(PROJ_DEPS) $(TEST_DEPS) DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) # The target binaries -MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake +MAIN_BIN := $(PROJ_OUT_DIR)/vuvuzela TEST_BIN := $(TEST_OUT_DIR)/tests # Their symlink -MAIN_LINK := shufflecake +MAIN_LINK := vuvuzela TEST_LINK := tests diff --git a/vuvuzela-userland/include/header.h b/vuvuzela-userland/include/header.h index 6d5181c..2423b40 100644 --- a/vuvuzela-userland/include/header.h +++ b/vuvuzela-userland/include/header.h @@ -33,6 +33,7 @@ #include #include "utils/crypto.h" +#include "utils/math.h" /***************************************************** @@ -95,7 +96,7 @@ typedef struct { */ typedef struct { // The key that encrypts the volume's data section - char volume_key[VVZ_STANDARD_KEYLEN]; + char volume_key[VVZ_AESXTS_KEYLEN]; // The key that encrypts the previous volume's master block char prev_vmb_key[VVZ_STANDARD_KEYLEN]; diff --git a/vuvuzela-userland/src/commands/init.c b/vuvuzela-userland/src/commands/init.c index 69eda0d..592eb3c 100644 --- a/vuvuzela-userland/src/commands/init.c +++ b/vuvuzela-userland/src/commands/init.c @@ -132,7 +132,7 @@ int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args) memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], VVZ_STANDARD_KEYLEN); } /* Sample this volume's VEK */ - vvz_rand_getStrongBytes(vmb.volume_key, VVZ_STANDARD_KEYLEN); + vvz_rand_getStrongBytes(vmb.volume_key, VVZ_AESXTS_KEYLEN); /* Write complete volume header (VMB + PM) */ err = vvz_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); diff --git a/vuvuzela-userland/src/header/position_map.c b/vuvuzela-userland/src/header/position_map.c index e48f304..7d72f1d 100644 --- a/vuvuzela-userland/src/header/position_map.c +++ b/vuvuzela-userland/src/header/position_map.c @@ -63,7 +63,7 @@ void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) // Allocate EPM epm = malloc(nr_pmbs * VVZ_BLOCK_SIZE); if (!epm) { - vvz_log_error("Could not malloc EPM array"); + vvz_log_error("Could not malloc EPM"); return NULL; } @@ -71,7 +71,7 @@ void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) memset(pmb, VVZ_EPM_FILLER, VVZ_BLOCK_SIZE); // Set the IV for the first encryption memset(iv, 0, VVZ_AESXTS_IVLEN); - *((uint64_t)iv) = htole64(vvz_pmStartBlock(vol_idx, nr_slices)); + *((uint64_t*)iv) = htole64(vvz_pmStartBlock(vol_idx, nr_slices)); // Loop to encrypt each PMB int i; diff --git a/vuvuzela-userland/src/header/volume_master_block.c b/vuvuzela-userland/src/header/volume_master_block.c index 5f153c9..72c36b5 100644 --- a/vuvuzela-userland/src/header/volume_master_block.c +++ b/vuvuzela-userland/src/header/volume_master_block.c @@ -181,11 +181,11 @@ static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + VVZ_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + VVZ_AESXTS_KEYLEN; char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, VVZ_STANDARD_KEYLEN); + memcpy(p_vol_key, vmb->volume_key, VVZ_AESXTS_KEYLEN); /* Copy the previous volume's VMB key */ memcpy(p_prev_vmb_key, vmb->prev_vmb_key, VVZ_STANDARD_KEYLEN); @@ -204,11 +204,11 @@ static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + VVZ_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + VVZ_AESXTS_KEYLEN; char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, VVZ_STANDARD_KEYLEN); + memcpy(vmb->volume_key, p_vol_key, VVZ_AESXTS_KEYLEN); /* Copy the previous volume's VMB key */ memcpy(vmb->prev_vmb_key, p_prev_vmb_key, VVZ_STANDARD_KEYLEN); diff --git a/vuvuzela-userland/src/operations/devmapper.c b/vuvuzela-userland/src/operations/devmapper.c index 2f622b5..4df1c62 100644 --- a/vuvuzela-userland/src/operations/devmapper.c +++ b/vuvuzela-userland/src/operations/devmapper.c @@ -76,7 +76,7 @@ int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb * } /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * VVZ_BLOCKS_PER_LOG_SLICE * VVZ_BLOCK_SCALE; + num_sectors = ((uint64_t) vmb->nr_slices) * VVZ_SLICE_SCALE * VVZ_BLOCK_SCALE; /* Build param list */ sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c index 0ea3ed1..84922f2 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -56,7 +56,7 @@ int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size_t vol_idx) { char enc_vmb[VVZ_BLOCK_SIZE]; - vvz_EncPosMap epm; + void *epm; uint64_t sector; int err; @@ -68,52 +68,29 @@ int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size } // Write it to disk - sector = vvz_vmbPosition(vol_idx, vmb->nr_slices); + sector = 1 + vol_idx; err = vvz_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { vvz_log_error("Could not write VMB to disk; error %d", err); goto out; } - sector += 1; // Create encrypted empty position map - err = vvz_epm_create(vmb->nr_slices, vmb->volume_key, &epm); - if (err) { - vvz_log_error("Could not create encrypted empty position map; error %d", err); + epm = vvz_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key); + if (!epm) { + vvz_log_error("Could not create encrypted empty position map."); + err = -ENOMEM; goto out; } - // Loop over PMB arrays to write them to disk - int i; - for (i = 0; i < epm.nr_arrays; i++) { - char *iv_block = epm.iv_blocks[i]; - char *pmb_array = epm.pmb_arrays[i]; - size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : VVZ_BLOCKS_PER_LOG_SLICE); - - // First write the IV block - err = vvz_disk_writeBlock(bdev_path, sector, iv_block); - if (err) { - vvz_log_error("Could not write IV block to disk; error %d", err); - goto out; - } - sector += 1; - - // Then the whole PMB array - err = vvz_disk_writeManyBlocks(bdev_path, sector, pmb_array, nr_pmbs); - if (err) { - vvz_log_error("Could not write PMB array to disk; error %d", err); - goto out; - } - sector += nr_pmbs; - - // Free them both - free(iv_block); - free(pmb_array); + // Write to disk + sector = vvz_pmStartBlock(vol_idx, sector); + err = vvz_disk_writeManyBlocks(bdev_path, sector, epm, + ceil(vmb->nr_slices, VVZ_SLICE_IDX_PER_BLOCK)); + if (err) { + vvz_log_error("Could not write encrypted PosMap; error %d", err); } - - // Free containers - free(epm.iv_blocks); - free(epm.pmb_arrays); + free(epm); out: @@ -140,7 +117,7 @@ int vvz_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol int err; /* Read encrypted VMB from disk */ - sector = vvz_vmbPosition(vol_idx, nr_slices); + sector = 1 + vol_idx; err = vvz_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { vvz_log_error("Could not read VMB from disk; error %d", err); From 2bbe3672900541d08a11d24f0d993b70c1889619 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 17 Apr 2024 22:35:03 +0200 Subject: [PATCH 47/98] Finish open --- vuvuzela-userland/include/utils/disk.h | 3 +++ vuvuzela-userland/src/operations/devmapper.c | 5 ++-- .../src/operations/volume_header.c | 2 +- vuvuzela-userland/src/utils/disk.c | 26 +++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/vuvuzela-userland/include/utils/disk.h b/vuvuzela-userland/include/utils/disk.h index b36b6a2..a007f39 100644 --- a/vuvuzela-userland/include/utils/disk.h +++ b/vuvuzela-userland/include/utils/disk.h @@ -72,6 +72,9 @@ static inline uint32_t vvz_disk_maxSlices(uint64_t size) { * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ +/* Returns a malloc'ed string formatted as : */ +char *vvz_disk_getDeviceName(char *bdev_path); + /* Checks whether the given path points to a block device */ bool vvz_disk_isBlockDevice(char *path); diff --git a/vuvuzela-userland/src/operations/devmapper.c b/vuvuzela-userland/src/operations/devmapper.c index 4df1c62..4e54af2 100644 --- a/vuvuzela-userland/src/operations/devmapper.c +++ b/vuvuzela-userland/src/operations/devmapper.c @@ -68,7 +68,7 @@ int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb * sprintf(label, "vvz_%lu_%lu", dev_id, vol_idx); /* Get the hex version of the volume's data section key */ - hex_key = vvz_toHex(vmb->volume_key, VVZ_STANDARD_KEYLEN); + hex_key = vvz_toHex(vmb->volume_key, VVZ_AESXTS_KEYLEN); if (!hex_key) { vvz_log_error("Could not encode volume key to hexadecimal"); err = ENOMEM; @@ -79,7 +79,7 @@ int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb * num_sectors = ((uint64_t) vmb->nr_slices) * VVZ_SLICE_SCALE * VVZ_BLOCK_SCALE; /* Build param list */ - sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); + sprintf(params, "%lu %s %lu %lu %s", dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); /* Issue ioctl */ err = vvz_dm_create(label, num_sectors, params); @@ -91,6 +91,7 @@ int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb * err_dmcreate: + memset(hex_key, 0, strlen(hex_key)); free(hex_key); err_hexkey: return err; diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c index 84922f2..a0c0176 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -79,7 +79,7 @@ int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size epm = vvz_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key); if (!epm) { vvz_log_error("Could not create encrypted empty position map."); - err = -ENOMEM; + err = ENOMEM; goto out; } diff --git a/vuvuzela-userland/src/utils/disk.c b/vuvuzela-userland/src/utils/disk.c index 3b4d392..795d0d9 100644 --- a/vuvuzela-userland/src/utils/disk.c +++ b/vuvuzela-userland/src/utils/disk.c @@ -30,11 +30,13 @@ *****************************************************/ #include +#include #include #include #include #include #include +#include #include #include @@ -47,6 +49,30 @@ *****************************************************/ +/** + * Returns a malloc'ed string formatted as : + * + * @param bdev_path The path to the block device + * + * @return A string version of the device ID + */ +char *vvz_disk_getDeviceName(char *bdev_path) +{ + struct stat sb; + char *dev_name; + + if (stat(bdev_path, &sb) != 0) + return NULL; + dev_name = malloc(VVZ_BIGBUFSIZE); + if (!dev_name) + return NULL; + + sprintf(dev_name, "%u:%u", major(sb.st_dev), minor(sb.st_dev)); + + return dev_name; +} + + /** * Checks whether the given path points to a block device. * From 4b1dc837af689366d76a5bccd21edb0752168c4f Mon Sep 17 00:00:00 2001 From: = Date: Wed, 17 Apr 2024 23:22:36 +0200 Subject: [PATCH 48/98] Add sysfs entry for number of volumes --- dm-vvz/device.c | 1 + dm-vvz/sysfs.c | 9 +++++++++ dm-vvz/vvz.c | 5 +++++ dm-vvz/vvz.h | 3 +++ 4 files changed, 18 insertions(+) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index cff41dc..1eb195f 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -57,6 +57,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) return ERR_PTR(-ENOMEM); } sdev->dev_id = dev_id; + sdev->nr_volumes = 0; /* The calling ->ctr() will set sdev->dm_dev */ diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index d234331..c0efb8b 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -91,6 +91,13 @@ static ssize_t dev_id_show(struct kobject *kobj, struct kobj_attribute *kattr, c return sysfs_emit(buf, "%u\n", sdev->dev_id); } +static ssize_t volumes_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); + + return sysfs_emit(buf, "%lu\n", sdev->nr_volumes); +} + static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); @@ -112,10 +119,12 @@ static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kat } static struct kobj_attribute dev_id_kattr = __ATTR_RO(dev_id); +static struct kobj_attribute volumes_kattr = __ATTR_RO(volumes); static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); static struct attribute *vvz_device_default_attrs[] = { &dev_id_kattr.attr, + &volumes_kattr.attr, &tot_slices_kattr.attr, &free_slices_kattr.attr, NULL diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 4367d95..aef3c45 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -168,6 +168,8 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) err = PTR_ERR(svol); goto bad_vol_create; } + /* We expect ->ctr() calls to be strictly sequential */ + sdev->nr_volumes++; /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_BLOCK_SCALE; @@ -204,6 +206,9 @@ static void vvz_dtr(struct dm_target *ti) size_t vol_idx = svol->vol_idx; vvz_vol_destroy(svol); + /* We expect ->dtr() calls to be strictly sequential */ + sdev->nr_volumes--; + if (vol_idx == 0) { vvz_remove_device_global(sdev->dev_id); dm_put_device(ti, sdev->dm_dev); diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index a954761..f73ef8d 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -59,6 +59,9 @@ struct vvz_device sector_t posmap_size_sectors; sector_t dev_header_size_sectors; + /* Number of volumes */ + size_t nr_volumes; + /* Shuffled array of PSIs */ struct mutex slices_lock; u32 *prmslices; From 4e2344674b8bffeb86b939187c4aa1e59a34723b Mon Sep 17 00:00:00 2001 From: = Date: Wed, 17 Apr 2024 23:22:49 +0200 Subject: [PATCH 49/98] Finish close --- vuvuzela-userland/include/utils/vvz.h | 4 +- vuvuzela-userland/src/commands/close.c | 72 +++++++++++++++----------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/vuvuzela-userland/include/utils/vvz.h b/vuvuzela-userland/include/utils/vvz.h index 3263dca..71bc399 100644 --- a/vuvuzela-userland/include/utils/vvz.h +++ b/vuvuzela-userland/include/utils/vvz.h @@ -69,8 +69,10 @@ #define VVZ_SYSFS_NEXTDEVID "/sys/module/dm_vvz/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ #define VVZ_SYSFS_BDEVS_DIR "/sys/module/dm_vvz/bdevs" -/* Within each bdev's subdir, this file lists its open volumes */ +/* Within each bdev's subdir, this file shows its number of open volumes */ #define VVZ_SYSFS_OPENVOLUMES_FILENAME "volumes" +/* Within each bdev's subdir, this file shows its Shufflecake device ID */ +#define VVZ_SYSFS_DEVID_FILENAME "dev_id" /* TODO: reasonable? */ #define VVZ_BDEV_PATH_MAX_LEN 1024 diff --git a/vuvuzela-userland/src/commands/close.c b/vuvuzela-userland/src/commands/close.c index e3784bc..e14ee21 100644 --- a/vuvuzela-userland/src/commands/close.c +++ b/vuvuzela-userland/src/commands/close.c @@ -35,6 +35,7 @@ #include "utils/crypto.h" #include "utils/string.h" #include "utils/file.h" +#include "utils/disk.h" #include "utils/log.h" @@ -43,7 +44,7 @@ *****************************************************/ /* Reads the list of volumes from sysfs */ -static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols); +static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols); /* Close them all (in reverse order of opening) */ static int _closeVolumes(char **labels, size_t nr_vols); @@ -77,7 +78,7 @@ int vvz_cmd_closeVolumes(char *bdev_path) } /* Read them */ - err = _readVolumesList(bdev_path, labels, &nr_vols); + err = _buildVolumesList(bdev_path, labels, &nr_vols); if (err) { vvz_log_error("Could not read volume list from sysfs; error %d", err); goto out; @@ -106,31 +107,50 @@ out: * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -/* Reads the list of volumes from sysfs */ -static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) +/* Reads from sysfs to build the volumes list */ +static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols) { - char bdev_path_noslash[VVZ_BDEV_PATH_MAX_LEN + 1]; - char openvolumes_path[VVZ_BIGBUFSIZE]; - char *str_openvolumes; + char *bdev_name; + char devid_path[VVZ_BIGBUFSIZE]; + char *str_devid; + size_t dev_id; + char nrvolumes_path[VVZ_BIGBUFSIZE]; + char *str_nrvolumes; - /* Remove the slashes from the bdev_path (replace with underscores) */ - strcpy(bdev_path_noslash, bdev_path); - vvz_str_replaceAll(bdev_path_noslash, '/', '_'); - /* Build path to sysfsy file containing open volumes list */ - sprintf(openvolumes_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_path_noslash, VVZ_SYSFS_OPENVOLUMES_FILENAME); + /* Get device name as : */ + bdev_name = vvz_disk_getDeviceName(bdev_path); + if(!bdev_name) { + vvz_log_error("Could not allocate device name"); + return ENOMEM; + } + /* Build path to sysfs file containing device ID */ + sprintf(devid_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_name, VVZ_SYSFS_DEVID_FILENAME); + /* Build path to sysfs file containing number of open volumes */ + sprintf(nrvolumes_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_name, VVZ_SYSFS_OPENVOLUMES_FILENAME); - /* Read the sysfs file */ - str_openvolumes = vvz_readFile(openvolumes_path); - if (!str_openvolumes) { - vvz_log_error("Could not read file %s", openvolumes_path); + /* Read the device ID */ + str_devid = vvz_readFile(devid_path); + if (!str_devid) { + vvz_log_error("Could not read file %s", devid_path); + return EBADF; + } + /* Parse the device ID */ + if (sscanf(str_devid, "%lu", &dev_id) != 1) { + vvz_log_error("Could not parse device ID:\n%s", str_devid); return EBADF; } + /* Read the number of volumes */ + str_nrvolumes = vvz_readFile(nrvolumes_path); + if (!str_nrvolumes) { + vvz_log_error("Could not read file %s", nrvolumes_path); + return EBADF; + } /* Parse the number of volumes */ - char *endptr; - *nr_vols = strtoul(str_openvolumes, &endptr, 10); - /* Skip past the number of volumes (lands on a whitespace before the first label) */ - str_openvolumes = endptr; + if (sscanf(str_nrvolumes, "%lu", nr_vols) != 1) { + vvz_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); + return EBADF; + } /* Just to be sure */ if (*nr_vols > VVZ_DEV_MAX_VOLUMES) { @@ -138,18 +158,10 @@ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) return EBADF; } - /* Read labels */ + /* Build labels */ size_t i; for (i = 0; i < *nr_vols; i++) { - /* Trust the content of the sysfs file */ - if (sscanf(str_openvolumes, " %s", labels[i]) != 1) { - vvz_log_error("Could not read volume label %lu. Sysfs content:\n%s", i, str_openvolumes); - return EBADF; - } - vvz_log_debug("Label %lu to close: %s", i, labels[i]); - - /* Skip past the whitespace and the label */ - str_openvolumes += 1 + strlen(labels[i]); + sprintf(labels[i], "vvz_%lu_%lu", dev_id, i); } return 0; From ec5e83a23e469f3ad787fd4057c41f6bbc0c1974 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 23 Apr 2024 21:15:00 +0200 Subject: [PATCH 50/98] Move dm_dev to volume --- dm-vvz/device.c | 38 ++++++++++++++++++++++++++++++++++---- dm-vvz/posmap.c | 4 ++-- dm-vvz/read.c | 2 +- dm-vvz/sysfs.c | 16 ++++++++++++++-- dm-vvz/volume.c | 14 +++++++++++++- dm-vvz/vvz.c | 29 +++++++++++++---------------- dm-vvz/vvz.h | 22 ++++++++++++++-------- dm-vvz/write.c | 2 +- 8 files changed, 92 insertions(+), 35 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 1eb195f..ef641c5 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -27,6 +27,8 @@ #include #include "vvz.h" +#include + /* Depth of the mempool backing the bio_set */ #define VVZ_BIOSET_BIOS 64 @@ -45,12 +47,15 @@ static void fisheryates_u32(u32 *v, u32 len) return; } -struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) +struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) { struct vvz_device *sdev; + dev_t devt; int i; int err; + DMWARN("ALLOCATING DEVICE"); + mdelay(5000); sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { DMERR("Could not allocate device"); @@ -59,7 +64,13 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) sdev->dev_id = dev_id; sdev->nr_volumes = 0; - /* The calling ->ctr() will set sdev->dm_dev */ + /* Look up block device and set name */ + err = lookup_bdev(bdev_path, &devt); + if (err) { + DMERR("Could not look up block device"); + goto bad_lookup; + } + format_dev_t(sdev->name, devt); /* Compute sizes */ sdev->tot_slices = tot_slices; @@ -74,12 +85,17 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) /* Shuffled PSIs */ mutex_init(&sdev->slices_lock); + DMWARN("ALLOCATING SLICES_OFLD: %u bytes", tot_slices); + mdelay(5000); sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); if (!sdev->slices_ofld) { DMERR("Could not allocate PSI occupation bitfield"); err = -ENOMEM; goto bad_ofld; } + + DMWARN("ALLOCATING PRMSLICES: %u bytes", tot_slices*4); + mdelay(5000); sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); if (!sdev->prmslices) { DMERR("Could not allocate shuffled PSI array"); @@ -89,10 +105,14 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) /* Generate a permutation */ for (i = 0; i < tot_slices; i++) sdev->prmslices[i] = i; + DMWARN("FISHER-YATES"); + mdelay(5000); fisheryates_u32(sdev->prmslices, tot_slices); sdev->prmslices_octr = 0; /* Bioset */ + DMWARN("BIOSET"); + mdelay(5000); err = bioset_init(&sdev->bioset, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); if (err) { DMERR("Could not init bioset; error %d", err); @@ -100,6 +120,8 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) } /* Client for dm-io */ + DMWARN("DM-IO"); + mdelay(5000); sdev->io_client = dm_io_client_create(); if (IS_ERR(sdev->io_client)) { err = PTR_ERR(sdev->io_client); @@ -108,18 +130,22 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) } /* I/O workqueue */ + DMWARN("IOQUEUE"); + mdelay(5000); sdev->io_queue = alloc_workqueue("vvz_%s_io", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, - 0, sdev->dm_dev->name); + 0, sdev->name); if (!sdev->io_queue) { err = -ENOMEM; DMERR("Could not allocate I/O workqueue"); goto bad_io_wq; } /* Decryption workqueue */ + DMWARN("CRYPTQUEUE"); + mdelay(5000); sdev->crypt_queue = alloc_workqueue("vvz_%s_crypt", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, - 0, sdev->dm_dev->name); + 0, sdev->name); if (!sdev->crypt_queue) { err = -ENOMEM; DMERR("Could not allocate decryption workqueue"); @@ -127,9 +153,12 @@ struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices) } /* Register to sysfs, once initialised */ + DMWARN("SYSFS"); + mdelay(5000); err = vvz_sysfs_register_device(sdev); if (err) { DMERR("Could not register device with sysfs; error %d", err); + mdelay(5000); goto bad_sysfs; } @@ -149,6 +178,7 @@ bad_bioset: bad_prmslices: vfree(sdev->slices_ofld); bad_ofld: +bad_lookup: kfree(sdev); return ERR_PTR(err); } diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 03e194a..4d6f538 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -113,7 +113,7 @@ static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) int err; /* Sync + flush TODO GFP mask ok? */ - bio = bio_alloc_bioset(sdev->dm_dev->bdev, 1, + bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, REQ_OP_WRITE | REQ_SYNC | REQ_FUA, GFP_NOIO, &sdev->bioset); if (!bio) { @@ -238,7 +238,7 @@ static int read_encrypted_posmap(struct vvz_volume *svol) .client = svol->sdev->io_client }; struct dm_io_region io_region = { - .bdev = svol->sdev->dm_dev->bdev, + .bdev = svol->dm_dev->bdev, .sector = VVZ_POSMAP_START_SECTOR(svol), .count = svol->sdev->posmap_size_sectors }; diff --git a/dm-vvz/read.c b/dm-vvz/read.c index 36f7761..0170a2c 100644 --- a/dm-vvz/read.c +++ b/dm-vvz/read.c @@ -60,7 +60,7 @@ void vvz_read_work_fn(struct work_struct *work) we can decrypt in place. */ /* Shallow copy */ - phys_bio = bio_alloc_clone(sdev->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); + phys_bio = bio_alloc_clone(svol->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); if (!phys_bio) { DMERR("Could not clone original bio"); orig_bio->bi_status = BLK_STS_IOERR; diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index c0efb8b..f28343b 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -23,6 +23,7 @@ #include "vvz.h" +#include /* *---------------------------- @@ -134,7 +135,8 @@ ATTRIBUTE_GROUPS(vvz_device_default); static void vvz_device_kobj_release(struct kobject *kobj) { struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - + DMWARN("COMPLETE"); + mdelay(5000); complete(&sdev->kobj_released); } @@ -149,22 +151,32 @@ int vvz_sysfs_register_device(struct vvz_device *sdev) int err; /* Completion */ + DMWARN("INIT COMPLETION"); + mdelay(5000); init_completion(&sdev->kobj_released); /* Register directory :/ under bdevs/ */ sdev->kobj.kset = bdevs_kset; + DMWARN("INIT AND ADD"); + mdelay(5000); err = kobject_init_and_add(&sdev->kobj, &vvz_device_ktype, NULL, - "%s", sdev->dm_dev->name); + "%s", sdev->name); if (err) goto bad; /* Emit uevent */ + DMWARN("UEVENT"); + mdelay(5000); kobject_uevent(&sdev->kobj, KOBJ_ADD); return 0; bad: + DMWARN("PUT"); + mdelay(5000); kobject_put(&sdev->kobj); + DMWARN("WAIT COMPLETION"); + mdelay(5000); wait_for_completion(&sdev->kobj_released); return err; } diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 51ad740..2f7fe60 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -25,7 +25,8 @@ #include "vvz.h" -struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *enckey) +struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, + u8 *enckey, struct dm_target *ti) { struct vvz_volume *svol; int err; @@ -40,6 +41,14 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *e svol->vol_idx = vol_idx; sprintf(svol->name, "vvz_%u_%lu", sdev->dev_id, vol_idx); + svol->ti = ti; + err = dm_get_device(ti, sdev->name, + dm_table_get_mode(ti->table), &svol->dm_dev); + if (err) { + ti->error = "Device lookup failed"; + goto bad_dm_dev; + } + /* Crypto */ svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(svol->tfm)) { @@ -87,6 +96,8 @@ bad_posmap_alloc: bad_tfm_setkey: crypto_free_skcipher(svol->tfm); bad_tfm_alloc: + dm_put_device(ti, svol->dm_dev); +bad_dm_dev: kfree(svol); return ERR_PTR(err); } @@ -97,6 +108,7 @@ void vvz_vol_destroy(struct vvz_volume *svol) vvz_sysfs_unregister_volume(svol); vfree(svol->posmap); crypto_free_skcipher(svol->tfm); + dm_put_device(svol->ti, svol->dm_dev); kfree(svol); return; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index aef3c45..3e5a0a8 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -27,6 +27,7 @@ #include "vvz_constants.h" #include "vvz.h" +#include /* *---------------------------- @@ -131,18 +132,16 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Create device, if this is the first volume, otherwise retrieve it */ if (vol_idx == 0) { - sdev = vvz_dev_create(dev_id, tot_slices); + DMWARN("CREATING DEVICE"); + mdelay(2000); + sdev = vvz_dev_create(dev_id, bdev_path, tot_slices); if (IS_ERR(sdev)) { ti->error = "Could not instantiate device"; return PTR_ERR(sdev); } - /* Set dm_dev */ - err = dm_get_device(ti, bdev_path, dm_table_get_mode(ti->table), &sdev->dm_dev); - if (err) { - ti->error = "Device lookup failed"; - goto bad_dm_dev; - } /* Insert in global array */ + DMWARN("INSERTING DEVICE GLOBAL"); + mdelay(2000); err = vvz_add_device_global(dev_id, sdev); if (err) { ti->error = "Could not add device to global array"; @@ -162,7 +161,9 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* Create volume */ - svol = vvz_vol_create(sdev, vol_idx, enckey); + DMWARN("CREATING VOLUME"); + mdelay(2000); + svol = vvz_vol_create(sdev, vol_idx, enckey, ti); if (IS_ERR(svol)) { ti->error = "Could not instantiate volume"; err = PTR_ERR(svol); @@ -190,8 +191,6 @@ bad_vol_create: if (vol_idx == 0) { vvz_remove_device_global(dev_id); bad_dev_global: - dm_put_device(ti, sdev->dm_dev); -bad_dm_dev: vvz_dev_destroy(sdev); } return err; @@ -203,15 +202,13 @@ static void vvz_dtr(struct dm_target *ti) { struct vvz_volume *svol = ti->private; struct vvz_device *sdev = svol->sdev; - size_t vol_idx = svol->vol_idx; vvz_vol_destroy(svol); /* We expect ->dtr() calls to be strictly sequential */ sdev->nr_volumes--; - if (vol_idx == 0) { + if (sdev->nr_volumes == 0) { vvz_remove_device_global(sdev->dev_id); - dm_put_device(ti, sdev->dm_dev); vvz_dev_destroy(sdev); } @@ -243,7 +240,7 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) DMWARN("Non-empty flush request!"); return DM_MAPIO_KILL; } - bio_set_dev(bio, svol->sdev->dm_dev->bdev); + bio_set_dev(bio, svol->dm_dev->bdev); return DM_MAPIO_REMAPPED; } @@ -251,7 +248,7 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) sio->svol = svol; sio->orig_bio = bio; sio->lsi = lblk_num >> VVZ_SLICE_SHIFT; - sio->block_offset = lblk_num & GENMASK(VVZ_SLICE_SHIFT - 1, 0); + sio->block_offset = lblk_num & ((1U << VVZ_SLICE_SHIFT) - 1); /* Enqueue */ if (bio_data_dir(bio) == READ) @@ -281,7 +278,7 @@ static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn struct vvz_volume *svol = ti->private; struct vvz_device *sdev = svol->sdev; - return fn(ti, svol->sdev->dm_dev, 0, sdev->dev_header_size_sectors + + return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + (ti->len * VVZ_BLOCK_SCALE), data); } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index f73ef8d..9867610 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -50,16 +50,16 @@ struct vvz_device { /* Shufflecake-unique device ID */ u32 dev_id; + /* : */ + char name[16]; - /* Underlying block device */ - struct dm_dev *dm_dev; + /* Logical size of each volume */ u32 tot_slices; - /* Header sizes in 512-byte sectors */ sector_t posmap_size_sectors; sector_t dev_header_size_sectors; - /* Number of volumes */ + /* Number of volumes. No need for an atomic_t */ size_t nr_volumes; /* Shuffled array of PSIs */ @@ -85,10 +85,15 @@ struct vvz_volume /* Backing device */ struct vvz_device *sdev; + /* Underlying block device. This can't go in the vvz_device struct, + * because each ti grabs its own reference. */ + struct dm_dev *dm_dev; + struct dm_target *ti; + /* Volume index within the device */ size_t vol_idx; - /* Name: vvz__ */ - char name[16]; + /* Volume name: vvz__ */ + char name[32]; /* Position map */ struct mutex posmap_lock; @@ -154,11 +159,12 @@ extern u32 vvz_free_devid; /* The lowest free devID */ */ /* Device */ -struct vvz_device *vvz_dev_create(u32 dev_id, u32 tot_slices); +struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices); void vvz_dev_destroy(struct vvz_device *sdev); /* Volume */ -struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, u8 *enckey); +struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, + u8 *enckey, struct dm_target *ti); void vvz_vol_destroy(struct vvz_volume *svol); /* Sysfs */ diff --git a/dm-vvz/write.c b/dm-vvz/write.c index 79e5e7e..4fc1964 100644 --- a/dm-vvz/write.c +++ b/dm-vvz/write.c @@ -60,7 +60,7 @@ void vvz_write_work_fn(struct work_struct *work) mutex_unlock(&svol->posmap_lock); /* Allocate physical bio */ - phys_bio = bio_alloc_bioset(sdev->dm_dev->bdev, 1, orig_bio->bi_opf, + phys_bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, orig_bio->bi_opf, GFP_NOIO, &sdev->bioset); if (!phys_bio) { DMERR("Could not allocate physical bio"); From 40c7cbdc6c484f02139cf219a5f24ce540c2602a Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Apr 2024 00:47:17 +0200 Subject: [PATCH 51/98] Fix major:minor --- vuvuzela-userland/src/utils/disk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuvuzela-userland/src/utils/disk.c b/vuvuzela-userland/src/utils/disk.c index 795d0d9..8b35caa 100644 --- a/vuvuzela-userland/src/utils/disk.c +++ b/vuvuzela-userland/src/utils/disk.c @@ -67,7 +67,7 @@ char *vvz_disk_getDeviceName(char *bdev_path) if (!dev_name) return NULL; - sprintf(dev_name, "%u:%u", major(sb.st_dev), minor(sb.st_dev)); + sprintf(dev_name, "%u:%u", major(sb.st_rdev), minor(sb.st_rdev)); return dev_name; } From 3e78cff0e1b9400498137c3a5ed99628899de7e5 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Apr 2024 00:47:46 +0200 Subject: [PATCH 52/98] Add PSI sanity check --- dm-vvz/device.c | 26 ++++++++++++++------------ dm-vvz/posmap.c | 15 ++++++++++++++- dm-vvz/sysfs.c | 22 ++++++++++++++-------- dm-vvz/volume.c | 16 ++++++++++++++++ dm-vvz/vvz.c | 11 ++++++----- 5 files changed, 64 insertions(+), 26 deletions(-) diff --git a/dm-vvz/device.c b/dm-vvz/device.c index ef641c5..33910ef 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -55,7 +55,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) int err; DMWARN("ALLOCATING DEVICE"); - mdelay(5000); + msleep(2000); sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { DMERR("Could not allocate device"); @@ -86,7 +86,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) /* Shuffled PSIs */ mutex_init(&sdev->slices_lock); DMWARN("ALLOCATING SLICES_OFLD: %u bytes", tot_slices); - mdelay(5000); + msleep(2000); sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); if (!sdev->slices_ofld) { DMERR("Could not allocate PSI occupation bitfield"); @@ -95,7 +95,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } DMWARN("ALLOCATING PRMSLICES: %u bytes", tot_slices*4); - mdelay(5000); + msleep(2000); sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); if (!sdev->prmslices) { DMERR("Could not allocate shuffled PSI array"); @@ -106,13 +106,13 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) for (i = 0; i < tot_slices; i++) sdev->prmslices[i] = i; DMWARN("FISHER-YATES"); - mdelay(5000); + msleep(2000); fisheryates_u32(sdev->prmslices, tot_slices); sdev->prmslices_octr = 0; /* Bioset */ DMWARN("BIOSET"); - mdelay(5000); + msleep(2000); err = bioset_init(&sdev->bioset, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); if (err) { DMERR("Could not init bioset; error %d", err); @@ -120,8 +120,8 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* Client for dm-io */ - DMWARN("DM-IO"); - mdelay(5000); + DMWARN("DMIO"); + msleep(2000); sdev->io_client = dm_io_client_create(); if (IS_ERR(sdev->io_client)) { err = PTR_ERR(sdev->io_client); @@ -131,7 +131,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) /* I/O workqueue */ DMWARN("IOQUEUE"); - mdelay(5000); + msleep(2000); sdev->io_queue = alloc_workqueue("vvz_%s_io", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->name); @@ -142,7 +142,7 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* Decryption workqueue */ DMWARN("CRYPTQUEUE"); - mdelay(5000); + msleep(2000); sdev->crypt_queue = alloc_workqueue("vvz_%s_crypt", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->name); @@ -153,14 +153,16 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* Register to sysfs, once initialised */ - DMWARN("SYSFS"); - mdelay(5000); + DMWARN("DEVICE SYSFS"); + msleep(2000); err = vvz_sysfs_register_device(sdev); if (err) { DMERR("Could not register device with sysfs; error %d", err); - mdelay(5000); + msleep(2000); goto bad_sysfs; } + DMWARN("DEVICE DONE"); + msleep(2000); return sdev; diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 4d6f538..3a4b77c 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -24,6 +24,7 @@ #include #include "vvz.h" +#include /* *---------------------------- @@ -276,6 +277,12 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) goto skip_create_mapping; } + /* If PSI out of bounds, something's seriously wrong */ + if (psi >= sdev->tot_slices) { + DMERR("Decrypted PSI out of bounds: %lu >= %lu", psi, sdev->tot_slices); + return -EDOM; + } + /* If PSI already taken, sample a new one */ if (sdev->slices_ofld[psi]) { DMWARN("Corruption of volume %lu: LSI %u was evicted from PSI %u", @@ -285,7 +292,7 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) return err; posmap_block_dirty = true; } - /* Either way, create the mapping locally */ + /* Whether sanitised or not, create the mapping locally */ _create_local_slice_mapping(svol, lsi, psi); skip_create_mapping: @@ -318,11 +325,15 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) struct vvz_device *sdev = svol->sdev; /* Read raw posmap from disk */ + DMWARN("READ ENC POSMAP"); + msleep(2000); err = read_encrypted_posmap(svol); if (err) return err; /* Decrypt in place */ + DMWARN("DECRYPT POSMAP"); + msleep(2000); err = vvz_crypt_blocks_vm(svol->tfm, svol->posmap, svol->posmap, svol->sdev->posmap_size_sectors >> VVZ_BLOCK_SHIFT, VVZ_POSMAP_START_SECTOR(svol) >> VVZ_BLOCK_SHIFT, @@ -333,6 +344,8 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) /* Deserialise and sanitise as you go */ if (mutex_lock_interruptible(&sdev->slices_lock)) return -ERESTARTSYS; + DMWARN("DESER SANIT POSMAP"); + msleep(2000); err = _deserialise_and_sanitise_posmap(svol); mutex_unlock(&sdev->slices_lock); if (err) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index f28343b..f82ab59 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -151,21 +151,21 @@ int vvz_sysfs_register_device(struct vvz_device *sdev) int err; /* Completion */ - DMWARN("INIT COMPLETION"); - mdelay(5000); + DMWARN("DEV INIT COMPLETION"); + msleep(2000); init_completion(&sdev->kobj_released); /* Register directory :/ under bdevs/ */ sdev->kobj.kset = bdevs_kset; - DMWARN("INIT AND ADD"); - mdelay(5000); + DMWARN("DEV INIT AND ADD"); + msleep(2000); err = kobject_init_and_add(&sdev->kobj, &vvz_device_ktype, NULL, "%s", sdev->name); if (err) goto bad; /* Emit uevent */ - DMWARN("UEVENT"); - mdelay(5000); + DMWARN("DEV UEVENT"); + msleep(2000); kobject_uevent(&sdev->kobj, KOBJ_ADD); return 0; @@ -173,10 +173,10 @@ int vvz_sysfs_register_device(struct vvz_device *sdev) bad: DMWARN("PUT"); - mdelay(5000); + msleep(2000); kobject_put(&sdev->kobj); DMWARN("WAIT COMPLETION"); - mdelay(5000); + msleep(2000); wait_for_completion(&sdev->kobj_released); return err; } @@ -232,14 +232,20 @@ int vvz_sysfs_register_volume(struct vvz_volume *svol) int err; /* Completion */ + DMWARN("VOL INIT COMPLETION"); + msleep(2000); init_completion(&svol->kobj_released); /* Register directory name>/ under device directory */ + DMWARN("VOL INIT AND ADD"); + msleep(2000); err = kobject_init_and_add(&svol->kobj, &vvz_volume_ktype, &svol->sdev->kobj, "%s", svol->name); if (err) goto bad; /* Emit uevent */ + DMWARN("VOL UEVENT"); + msleep(2000); kobject_uevent(&svol->kobj, KOBJ_ADD); return 0; diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 2f7fe60..0cfd471 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -23,6 +23,7 @@ #include #include "vvz.h" +#include struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, @@ -31,6 +32,9 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, struct vvz_volume *svol; int err; + + DMWARN("ALLOCATING VOLUME"); + msleep(2000); svol = kzalloc(sizeof(*svol), GFP_KERNEL); if (!svol) { DMERR("Could not allocate volume"); @@ -42,6 +46,8 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, sprintf(svol->name, "vvz_%u_%lu", sdev->dev_id, vol_idx); svol->ti = ti; + DMWARN("DMDEV"); + msleep(2000); err = dm_get_device(ti, sdev->name, dm_table_get_mode(ti->table), &svol->dm_dev); if (err) { @@ -50,12 +56,16 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } /* Crypto */ + DMWARN("SKCIPHER"); + msleep(2000); svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(svol->tfm)) { err = PTR_ERR(svol->tfm); DMERR("Could not allocate AES-XTS cipher handle; error %d", err); goto bad_tfm_alloc; } + DMWARN("SETKEY"); + msleep(2000); err = crypto_skcipher_setkey(svol->tfm, enckey, VVZ_XTS_KEYLEN); if (err) { DMERR("Could not set key in crypto transform; error %d", err); @@ -65,6 +75,8 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, /* Position map */ mutex_init(&svol->posmap_lock); /* Slight over-allocation, to fit a whole number of blocks */ + DMWARN("ALLOCATING POSMAP"); + msleep(2000); svol->posmap = vmalloc(sdev->posmap_size_sectors * SECTOR_SIZE); if (!svol->posmap) { DMERR("Could not allocate position map"); @@ -73,6 +85,8 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } svol->nr_mapped_slices = 0; /* Load from disk */ + DMWARN("LOAD POSMAP"); + msleep(2000); err = vvz_load_and_sanitise_posmap(svol); if (err) { DMERR("Could not load position map from disk; error %d", err); @@ -80,6 +94,8 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } /* Register to sysfs, once initialised */ + DMWARN("VOLUME SYSFS"); + msleep(2000); err = vvz_sysfs_register_volume(svol); if (err) { DMERR("Could not register volume with sysfs; error %d", err); diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 3e5a0a8..3ebf783 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -133,7 +133,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Create device, if this is the first volume, otherwise retrieve it */ if (vol_idx == 0) { DMWARN("CREATING DEVICE"); - mdelay(2000); + msleep(2000); sdev = vvz_dev_create(dev_id, bdev_path, tot_slices); if (IS_ERR(sdev)) { ti->error = "Could not instantiate device"; @@ -141,7 +141,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* Insert in global array */ DMWARN("INSERTING DEVICE GLOBAL"); - mdelay(2000); + msleep(2000); err = vvz_add_device_global(dev_id, sdev); if (err) { ti->error = "Could not add device to global array"; @@ -162,7 +162,7 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Create volume */ DMWARN("CREATING VOLUME"); - mdelay(2000); + msleep(2000); svol = vvz_vol_create(sdev, vol_idx, enckey, ti); if (IS_ERR(svol)) { ti->error = "Could not instantiate volume"; @@ -171,6 +171,8 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* We expect ->ctr() calls to be strictly sequential */ sdev->nr_volumes++; + DMWARN("ALL DONE"); + msleep(2000); /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_BLOCK_SCALE; @@ -278,8 +280,7 @@ static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn struct vvz_volume *svol = ti->private; struct vvz_device *sdev = svol->sdev; - return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + - (ti->len * VVZ_BLOCK_SCALE), data); + return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + ti->len, data); } From c9106426b386fc11f27891c5a7e13ee1768f4e38 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 1 May 2024 17:18:52 +0200 Subject: [PATCH 53/98] fix: epm generation and writing --- vuvuzela-userland/include/utils/crypto.h | 2 +- vuvuzela-userland/include/utils/log.h | 4 ++-- vuvuzela-userland/src/header/position_map.c | 15 +++++++++++---- vuvuzela-userland/src/operations/volume_header.c | 2 +- vuvuzela-userland/src/utils/crypto.c | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/vuvuzela-userland/include/utils/crypto.h b/vuvuzela-userland/include/utils/crypto.h index e9d1ed9..c47ca9b 100644 --- a/vuvuzela-userland/include/utils/crypto.h +++ b/vuvuzela-userland/include/utils/crypto.h @@ -93,7 +93,7 @@ int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); /* AES256-XTS encryption. Set ct = NULL for in-place. - * The IV is intepreted as a little-endian "sector number", and incremented by 1 */ + * The IV is intepreted as a little-endian "sector number" */ int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-GCM encryption, does not touch the IV */ diff --git a/vuvuzela-userland/include/utils/log.h b/vuvuzela-userland/include/utils/log.h index 6790ae5..60a251f 100644 --- a/vuvuzela-userland/include/utils/log.h +++ b/vuvuzela-userland/include/utils/log.h @@ -120,13 +120,13 @@ static inline void vvz_log_hex(char *str, size_t len) for (i = 0; i < len; i++) { printf("%02x ", s[i]); // Nice aligned wrapping - if (i % 16 == 15) { + if (i % 32 == 31) { printf("\n"); } } // Always end with a newline - if (i % 16 != 0) { + if (i % 32 != 0) { printf("\n"); } diff --git a/vuvuzela-userland/src/header/position_map.c b/vuvuzela-userland/src/header/position_map.c index 7d72f1d..4abdab3 100644 --- a/vuvuzela-userland/src/header/position_map.c +++ b/vuvuzela-userland/src/header/position_map.c @@ -55,6 +55,7 @@ void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) char iv[VVZ_AESXTS_IVLEN]; void *epm; size_t nr_pmbs; + uint64_t pblk_num; int err; // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) @@ -69,14 +70,17 @@ void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) // Fill cleartext PMB with 0xFF memset(pmb, VVZ_EPM_FILLER, VVZ_BLOCK_SIZE); - // Set the IV for the first encryption - memset(iv, 0, VVZ_AESXTS_IVLEN); - *((uint64_t*)iv) = htole64(vvz_pmStartBlock(vol_idx, nr_slices)); + // First physical block number + pblk_num = vvz_pmStartBlock(vol_idx, nr_slices); // Loop to encrypt each PMB int i; for (i = 0; i < nr_pmbs; i++) { - // Encrypt. Auto-increment IV for next encryption + // Set IV + memset(iv, 0, VVZ_AESXTS_IVLEN); + *((uint64_t*)iv) = htole64(pblk_num); + + // Encrypt err = vvz_aes256xts_encrypt(volume_key, pmb, VVZ_BLOCK_SIZE, iv, epm + i*VVZ_BLOCK_SIZE); if (err) { @@ -84,6 +88,9 @@ void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) free(epm); return NULL; } + + // Increment block number + pblk_num++; } return epm; diff --git a/vuvuzela-userland/src/operations/volume_header.c b/vuvuzela-userland/src/operations/volume_header.c index a0c0176..9c5e611 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/vuvuzela-userland/src/operations/volume_header.c @@ -84,7 +84,7 @@ int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size } // Write to disk - sector = vvz_pmStartBlock(vol_idx, sector); + sector = vvz_pmStartBlock(vol_idx, vmb->nr_slices); err = vvz_disk_writeManyBlocks(bdev_path, sector, epm, ceil(vmb->nr_slices, VVZ_SLICE_IDX_PER_BLOCK)); if (err) { diff --git a/vuvuzela-userland/src/utils/crypto.c b/vuvuzela-userland/src/utils/crypto.c index d3b7c46..6f93c4a 100644 --- a/vuvuzela-userland/src/utils/crypto.c +++ b/vuvuzela-userland/src/utils/crypto.c @@ -207,7 +207,7 @@ bad_open: /** * AES256-XTS encryption. Set ct = NULL for in-place. - * The IV is intepreted as a little-endian "sector number", and incremented by 1. + * The IV is intepreted as a little-endian "sector number". * * @param key The 64-byte AES-XTS key * @param pt The plaintext From a3178d9753557709a68007269089f6ffde4fb4b9 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 1 May 2024 17:20:06 +0200 Subject: [PATCH 54/98] Open kinda works i'd say --- dm-vvz/crypto.c | 29 ++++++++++++++++++++++------- dm-vvz/device.c | 27 +-------------------------- dm-vvz/posmap.c | 10 +--------- dm-vvz/sysfs.c | 20 -------------------- dm-vvz/volume.c | 19 ++----------------- dm-vvz/vvz.c | 10 ---------- dm-vvz/vvz.h | 1 + 7 files changed, 27 insertions(+), 89 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index be1e687..b5f7c4d 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -26,6 +26,8 @@ #include #include "vvz_constants.h" +#include "vvz.h" + /** * Encrypt/decrypt exactly one block, already encoded in the scatterlist. @@ -70,16 +72,23 @@ static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw) { - struct scatterlist dst, src; + struct scatterlist dst, src, *p_dst; + bool is_inplace; + + /* Use same scatterlist if in-place */ + is_inplace = (src_page == dst_page); + p_dst = is_inplace ? &src : &dst; /* We assume PAGE_SIZE == VVZ_BLOCK_SIZE */ /* And orig_bio to start at offset 0 within the page */ sg_init_table(&src, 1); sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); + if (is_inplace) { + sg_init_table(&dst, 1); + sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); + } - return crypt_sg(tfm, &src, &dst, pblk_num, rw); + return crypt_sg(tfm, &src, p_dst, pblk_num, rw); } @@ -87,17 +96,23 @@ int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, u64 num_blocks, u64 first_pblk_num, int rw) { - struct scatterlist dst, src; + struct scatterlist dst, src, *p_dst; u64 pblk_num; + bool is_inplace; int err; + /* Use same scatterlist if in-place */ + is_inplace = (src_buf == dst_buf); + p_dst = is_inplace ? &src : &dst; + for (pblk_num = first_pblk_num; pblk_num < first_pblk_num + num_blocks; pblk_num++) { sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); - sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); + if (is_inplace) + sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); - err = crypt_sg(tfm, &src, &dst, pblk_num, rw); + err = crypt_sg(tfm, &src, p_dst, pblk_num, rw); if (err) return err; diff --git a/dm-vvz/device.c b/dm-vvz/device.c index 33910ef..fd7cab3 100644 --- a/dm-vvz/device.c +++ b/dm-vvz/device.c @@ -27,8 +27,6 @@ #include #include "vvz.h" -#include - /* Depth of the mempool backing the bio_set */ #define VVZ_BIOSET_BIOS 64 @@ -54,8 +52,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) int i; int err; - DMWARN("ALLOCATING DEVICE"); - msleep(2000); sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) { DMERR("Could not allocate device"); @@ -85,8 +81,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) /* Shuffled PSIs */ mutex_init(&sdev->slices_lock); - DMWARN("ALLOCATING SLICES_OFLD: %u bytes", tot_slices); - msleep(2000); sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); if (!sdev->slices_ofld) { DMERR("Could not allocate PSI occupation bitfield"); @@ -94,8 +88,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) goto bad_ofld; } - DMWARN("ALLOCATING PRMSLICES: %u bytes", tot_slices*4); - msleep(2000); sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); if (!sdev->prmslices) { DMERR("Could not allocate shuffled PSI array"); @@ -105,14 +97,10 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) /* Generate a permutation */ for (i = 0; i < tot_slices; i++) sdev->prmslices[i] = i; - DMWARN("FISHER-YATES"); - msleep(2000); fisheryates_u32(sdev->prmslices, tot_slices); sdev->prmslices_octr = 0; /* Bioset */ - DMWARN("BIOSET"); - msleep(2000); err = bioset_init(&sdev->bioset, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); if (err) { DMERR("Could not init bioset; error %d", err); @@ -120,8 +108,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* Client for dm-io */ - DMWARN("DMIO"); - msleep(2000); sdev->io_client = dm_io_client_create(); if (IS_ERR(sdev->io_client)) { err = PTR_ERR(sdev->io_client); @@ -130,8 +116,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* I/O workqueue */ - DMWARN("IOQUEUE"); - msleep(2000); sdev->io_queue = alloc_workqueue("vvz_%s_io", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->name); @@ -141,8 +125,6 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) goto bad_io_wq; } /* Decryption workqueue */ - DMWARN("CRYPTQUEUE"); - msleep(2000); sdev->crypt_queue = alloc_workqueue("vvz_%s_crypt", WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, 0, sdev->name); @@ -153,16 +135,9 @@ struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) } /* Register to sysfs, once initialised */ - DMWARN("DEVICE SYSFS"); - msleep(2000); err = vvz_sysfs_register_device(sdev); - if (err) { - DMERR("Could not register device with sysfs; error %d", err); - msleep(2000); + if (err) goto bad_sysfs; - } - DMWARN("DEVICE DONE"); - msleep(2000); return sdev; diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 3a4b77c..0c5e1e8 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -24,8 +24,6 @@ #include #include "vvz.h" -#include - /* *---------------------------- * Create slice mapping @@ -279,7 +277,7 @@ static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) /* If PSI out of bounds, something's seriously wrong */ if (psi >= sdev->tot_slices) { - DMERR("Decrypted PSI out of bounds: %lu >= %lu", psi, sdev->tot_slices); + DMERR("Decrypted PSI out of bounds: %u >= %u", psi, sdev->tot_slices); return -EDOM; } @@ -325,15 +323,11 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) struct vvz_device *sdev = svol->sdev; /* Read raw posmap from disk */ - DMWARN("READ ENC POSMAP"); - msleep(2000); err = read_encrypted_posmap(svol); if (err) return err; /* Decrypt in place */ - DMWARN("DECRYPT POSMAP"); - msleep(2000); err = vvz_crypt_blocks_vm(svol->tfm, svol->posmap, svol->posmap, svol->sdev->posmap_size_sectors >> VVZ_BLOCK_SHIFT, VVZ_POSMAP_START_SECTOR(svol) >> VVZ_BLOCK_SHIFT, @@ -344,8 +338,6 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) /* Deserialise and sanitise as you go */ if (mutex_lock_interruptible(&sdev->slices_lock)) return -ERESTARTSYS; - DMWARN("DESER SANIT POSMAP"); - msleep(2000); err = _deserialise_and_sanitise_posmap(svol); mutex_unlock(&sdev->slices_lock); if (err) diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c index f82ab59..efd3df2 100644 --- a/dm-vvz/sysfs.c +++ b/dm-vvz/sysfs.c @@ -23,8 +23,6 @@ #include "vvz.h" -#include - /* *---------------------------- * Top-level entries @@ -135,8 +133,6 @@ ATTRIBUTE_GROUPS(vvz_device_default); static void vvz_device_kobj_release(struct kobject *kobj) { struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - DMWARN("COMPLETE"); - mdelay(5000); complete(&sdev->kobj_released); } @@ -151,32 +147,22 @@ int vvz_sysfs_register_device(struct vvz_device *sdev) int err; /* Completion */ - DMWARN("DEV INIT COMPLETION"); - msleep(2000); init_completion(&sdev->kobj_released); /* Register directory :/ under bdevs/ */ sdev->kobj.kset = bdevs_kset; - DMWARN("DEV INIT AND ADD"); - msleep(2000); err = kobject_init_and_add(&sdev->kobj, &vvz_device_ktype, NULL, "%s", sdev->name); if (err) goto bad; /* Emit uevent */ - DMWARN("DEV UEVENT"); - msleep(2000); kobject_uevent(&sdev->kobj, KOBJ_ADD); return 0; bad: - DMWARN("PUT"); - msleep(2000); kobject_put(&sdev->kobj); - DMWARN("WAIT COMPLETION"); - msleep(2000); wait_for_completion(&sdev->kobj_released); return err; } @@ -232,20 +218,14 @@ int vvz_sysfs_register_volume(struct vvz_volume *svol) int err; /* Completion */ - DMWARN("VOL INIT COMPLETION"); - msleep(2000); init_completion(&svol->kobj_released); /* Register directory name>/ under device directory */ - DMWARN("VOL INIT AND ADD"); - msleep(2000); err = kobject_init_and_add(&svol->kobj, &vvz_volume_ktype, &svol->sdev->kobj, "%s", svol->name); if (err) goto bad; /* Emit uevent */ - DMWARN("VOL UEVENT"); - msleep(2000); kobject_uevent(&svol->kobj, KOBJ_ADD); return 0; diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c index 0cfd471..7de135b 100644 --- a/dm-vvz/volume.c +++ b/dm-vvz/volume.c @@ -23,7 +23,6 @@ #include #include "vvz.h" -#include struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, @@ -32,9 +31,6 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, struct vvz_volume *svol; int err; - - DMWARN("ALLOCATING VOLUME"); - msleep(2000); svol = kzalloc(sizeof(*svol), GFP_KERNEL); if (!svol) { DMERR("Could not allocate volume"); @@ -46,8 +42,6 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, sprintf(svol->name, "vvz_%u_%lu", sdev->dev_id, vol_idx); svol->ti = ti; - DMWARN("DMDEV"); - msleep(2000); err = dm_get_device(ti, sdev->name, dm_table_get_mode(ti->table), &svol->dm_dev); if (err) { @@ -56,17 +50,14 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } /* Crypto */ - DMWARN("SKCIPHER"); - msleep(2000); svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); if (IS_ERR(svol->tfm)) { err = PTR_ERR(svol->tfm); DMERR("Could not allocate AES-XTS cipher handle; error %d", err); goto bad_tfm_alloc; } - DMWARN("SETKEY"); - msleep(2000); - err = crypto_skcipher_setkey(svol->tfm, enckey, VVZ_XTS_KEYLEN); + memcpy(svol->enckey, enckey, VVZ_XTS_KEYLEN); + err = crypto_skcipher_setkey(svol->tfm, svol->enckey, VVZ_XTS_KEYLEN); if (err) { DMERR("Could not set key in crypto transform; error %d", err); goto bad_tfm_setkey; @@ -75,8 +66,6 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, /* Position map */ mutex_init(&svol->posmap_lock); /* Slight over-allocation, to fit a whole number of blocks */ - DMWARN("ALLOCATING POSMAP"); - msleep(2000); svol->posmap = vmalloc(sdev->posmap_size_sectors * SECTOR_SIZE); if (!svol->posmap) { DMERR("Could not allocate position map"); @@ -85,8 +74,6 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } svol->nr_mapped_slices = 0; /* Load from disk */ - DMWARN("LOAD POSMAP"); - msleep(2000); err = vvz_load_and_sanitise_posmap(svol); if (err) { DMERR("Could not load position map from disk; error %d", err); @@ -94,8 +81,6 @@ struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, } /* Register to sysfs, once initialised */ - DMWARN("VOLUME SYSFS"); - msleep(2000); err = vvz_sysfs_register_volume(svol); if (err) { DMERR("Could not register volume with sysfs; error %d", err); diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 3ebf783..9024dd4 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -27,8 +27,6 @@ #include "vvz_constants.h" #include "vvz.h" -#include - /* *---------------------------- * Device mapper target @@ -132,16 +130,12 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) /* Create device, if this is the first volume, otherwise retrieve it */ if (vol_idx == 0) { - DMWARN("CREATING DEVICE"); - msleep(2000); sdev = vvz_dev_create(dev_id, bdev_path, tot_slices); if (IS_ERR(sdev)) { ti->error = "Could not instantiate device"; return PTR_ERR(sdev); } /* Insert in global array */ - DMWARN("INSERTING DEVICE GLOBAL"); - msleep(2000); err = vvz_add_device_global(dev_id, sdev); if (err) { ti->error = "Could not add device to global array"; @@ -161,8 +155,6 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* Create volume */ - DMWARN("CREATING VOLUME"); - msleep(2000); svol = vvz_vol_create(sdev, vol_idx, enckey, ti); if (IS_ERR(svol)) { ti->error = "Could not instantiate volume"; @@ -171,8 +163,6 @@ static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) } /* We expect ->ctr() calls to be strictly sequential */ sdev->nr_volumes++; - DMWARN("ALL DONE"); - msleep(2000); /* Only accept one block per request for simplicity TODO: improve to one slice*/ ti->max_io_len = VVZ_BLOCK_SCALE; diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 9867610..3aa66b2 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -105,6 +105,7 @@ struct vvz_volume struct completion kobj_released; /* Crypto */ + u8 enckey[VVZ_XTS_KEYLEN]; struct crypto_skcipher *tfm; }; From 76ea163f433ac27f017920aee90f096dfa81c23b Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 30 Jun 2024 23:27:53 +0200 Subject: [PATCH 55/98] feat: Add CLI flag and VVZ constants for legacy VS lite mode --- dm-vvz/vvz_constants.h | 5 +++ vuvuzela-userland/include/cli.h | 8 ++--- vuvuzela-userland/include/commands.h | 11 +++--- vuvuzela-userland/include/vvz_constants.h | 42 +---------------------- vuvuzela-userland/src/cli/dispatch.c | 18 ++++++++-- 5 files changed, 31 insertions(+), 53 deletions(-) mode change 100644 => 120000 vuvuzela-userland/include/vvz_constants.h diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h index 16b1f73..7ed62d1 100644 --- a/dm-vvz/vvz_constants.h +++ b/dm-vvz/vvz_constants.h @@ -43,6 +43,11 @@ #define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL +#define VVZ_MODE_LEGACY 0 +#define VVZ_MODE_LITE 1 +#define VVZ_MODE_FULL 2 + + #define VVZ_BLOCK_SIZE 4096 /* bytes */ #define VVZ_BLOCK_SHIFT 3 #define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* 8 sectors in a block */ diff --git a/vuvuzela-userland/include/cli.h b/vuvuzela-userland/include/cli.h index e8b495b..b58b313 100644 --- a/vuvuzela-userland/include/cli.h +++ b/vuvuzela-userland/include/cli.h @@ -49,15 +49,15 @@ int vvz_cli_dispatch(int argc, char **argv); /* Initializes device and create empty volumes */ -int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill); +int vvz_cli_init(char *block_device, int vvz_mode, int num_volumes, int skip_randfill); /* Open volumes */ -int vvz_cli_open(char *block_device); +int vvz_cli_open(char *block_device, int vvz_mode); /* Close volumes */ int vvz_cli_close(char *block_device); /* Test password */ -int vvz_cli_testPwd(char *block_device); +int vvz_cli_testPwd(char *block_device, int vvz_mode); /* Change password */ -int vvz_cli_changePwd(char *block_device); +int vvz_cli_changePwd(char *block_device, int vvz_mode); #endif /* _CLI_H_ */ diff --git a/vuvuzela-userland/include/commands.h b/vuvuzela-userland/include/commands.h index a15ad14..1cdab7d 100644 --- a/vuvuzela-userland/include/commands.h +++ b/vuvuzela-userland/include/commands.h @@ -50,13 +50,13 @@ typedef struct { /* Underlying block device */ char *bdev_path; - + /* Shufflecake mode (legacy,lite,full) */ + int vvz_mode; /* Number of volumes */ size_t nr_vols; /* Volumes' passwords */ char **pwds; size_t *pwd_lens; - /* Option to skip random filling */ bool no_randfill; @@ -68,7 +68,8 @@ typedef struct { /* Underlying block device */ char *bdev_path; - + /* Shufflecake mode (legacy,lite,full) */ + int vvz_mode; /* The only password provided */ char *pwd; size_t pwd_len; @@ -79,10 +80,10 @@ typedef struct { /* Underlying block device */ char *bdev_path; - + /* Shufflecake mode (legacy,lite,full) */ + int vvz_mode; /* Content of the DMB cell */ vvz_DmbCell *dmb_cell; - /* The new password */ char *new_pwd; size_t new_pwd_len; diff --git a/vuvuzela-userland/include/vvz_constants.h b/vuvuzela-userland/include/vvz_constants.h deleted file mode 100644 index 9f27092..0000000 --- a/vuvuzela-userland/include/vvz_constants.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * PLACEHOLDER * - *****************************************************/ - -/* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ - - -#define VVZ_VER_MAJOR 0 -#define VVZ_VER_MINOR 4 -#define VVZ_VER_REVISION 0 -#define VVZ_VER_SPECIAL "rc1" - -#define STRINGIFY0(s) # s -#define STRINGIFY(s) STRINGIFY0(s) - -#define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL - - diff --git a/vuvuzela-userland/include/vvz_constants.h b/vuvuzela-userland/include/vvz_constants.h new file mode 120000 index 0000000..5c10997 --- /dev/null +++ b/vuvuzela-userland/include/vvz_constants.h @@ -0,0 +1 @@ +../../dm-vvz/vvz_constants.h \ No newline at end of file diff --git a/vuvuzela-userland/src/cli/dispatch.c b/vuvuzela-userland/src/cli/dispatch.c index ad170fa..3debf68 100644 --- a/vuvuzela-userland/src/cli/dispatch.c +++ b/vuvuzela-userland/src/cli/dispatch.c @@ -67,6 +67,7 @@ enum vvz_cli_action { struct vvz_cli_arguments { enum vvz_cli_action act; char *block_device; + int sflc_mode; int num_volumes; bool skip_randfill; }; @@ -115,6 +116,8 @@ static struct argp_option options[] = { "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 {"skip-randfill", VVZ_OPT_SKIPRAND_KEY, 0, 0, "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, + {"legacy", VVZ_OPT_LEGACY, 0, 0, + "Use the old (pre-v0.5.0) Shufflecake format. Only valid with `init`, `open', `testpwd', `changepwd'. Use of this option is not recommended. This mode is going to be deprecated in future versions."}, {0} }; @@ -141,6 +144,7 @@ int vvz_cli_dispatch(int argc, char **argv) { arguments.act = -1; arguments.block_device = NULL; + arguments.vvz_mode = VVZ_MODE_LITE; arguments.num_volumes = 0; arguments.skip_randfill = false; @@ -152,6 +156,11 @@ int vvz_cli_dispatch(int argc, char **argv) { printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); return EINVAL; } + /* Legacy mode should not be specified with `close' action */ + if (arguments.vvz_mode && arguments.act == VVZ_ACT_CLOSE) { + printf("Error: --legacy should not be used with `close'.\n"); + return EINVAL; + } /* Check options consistency */ if (arguments.skip_randfill && arguments.act != VVZ_ACT_INIT) { printf("Error: --skip-randfill can only be combined with `init'.\n"); @@ -168,16 +177,16 @@ int vvz_cli_dispatch(int argc, char **argv) { return vvz_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); } if (arguments.act == VVZ_ACT_OPEN) { - return vvz_cli_open(arguments.block_device); + return vvz_cli_open(arguments.block_device arguments.vvz_mode,); } if (arguments.act == VVZ_ACT_CLOSE) { return vvz_cli_close(arguments.block_device); } if (arguments.act == VVZ_ACT_TESTPWD) { - return vvz_cli_testPwd(arguments.block_device); + return vvz_cli_testPwd(arguments.block_device arguments.vvz_mode,); } if (arguments.act == VVZ_ACT_CHANGEPWD) { - return vvz_cli_changePwd(arguments.block_device); + return vvz_cli_changePwd(arguments.block_device arguments.vvz_mode,); } printf("\n"); @@ -225,6 +234,9 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { case VVZ_OPT_NUMVOLS_KEY: arguments->num_volumes = atoi(arg); break; + case VVZ_OPT_LEGACY: + arguments->vvz_mode = VVZ_MODE_LEGACY; + break; case VVZ_OPT_SKIPRAND_KEY: arguments->skip_randfill = true; break; From ae3805df5e1a5f848f20561f155c021ecb4bbce4 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 28 Jul 2024 18:19:48 +0200 Subject: [PATCH 56/98] fix: all errors it seems --- dm-vvz/crypto.c | 4 ++-- dm-vvz/posmap.c | 50 ++++++++++++++++++++++++++++++++++++++----------- dm-vvz/read.c | 32 ++++++++++++++++++++++++++++++- dm-vvz/vvz.c | 42 ++++++++++++++++++++++++++++++++--------- dm-vvz/vvz.h | 1 + dm-vvz/write.c | 12 ++++++++++++ 6 files changed, 118 insertions(+), 23 deletions(-) diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c index b5f7c4d..e4a2c0d 100644 --- a/dm-vvz/crypto.c +++ b/dm-vvz/crypto.c @@ -83,7 +83,7 @@ int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, /* And orig_bio to start at offset 0 within the page */ sg_init_table(&src, 1); sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - if (is_inplace) { + if (!is_inplace) { sg_init_table(&dst, 1); sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); } @@ -109,7 +109,7 @@ int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_bu pblk_num < first_pblk_num + num_blocks; pblk_num++) { sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); - if (is_inplace) + if (!is_inplace) sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); err = crypt_sg(tfm, &src, p_dst, pblk_num, rw); diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c index 0c5e1e8..6d8711c 100644 --- a/dm-vvz/posmap.c +++ b/dm-vvz/posmap.c @@ -22,8 +22,18 @@ */ #include +#include #include "vvz.h" + +/* Helpers */ + +#define IS_PSI_TAKEN(sdev, psi) ( (sdev)->slices_ofld[(psi)] ) +#define NEXT_RANDOM_PSI(sdev) ( (sdev)->prmslices[(sdev)->prmslices_octr] ) +#define IS_LAST_LSI_IN_BLOCK(lsi, sdev) ( (((lsi)+1) % VVZ_PSIS_PER_BLOCK == 0) || \ + (((lsi)+1) == (sdev)->tot_slices) ) + + /* *---------------------------- * Create slice mapping @@ -40,13 +50,26 @@ static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) { if (unlikely(!sdev->nr_free_slices)) return -ENOSPC; - if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) + if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) { + DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); + print_hex_dump(KERN_CRIT, "prmslices(REV) ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); + msleep(10000); + print_hex_dump(KERN_CRIT, "ofld(REV) ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); + msleep(10000); return -ENOTRECOVERABLE; // Grave inconsistency + } /* Invariant: @prmslices_octr points to a free slice */ - *psi = sdev->prmslices[sdev->prmslices_octr]; - if (unlikely(sdev->slices_ofld[*psi])) + *psi = NEXT_RANDOM_PSI(sdev); + if (unlikely(IS_PSI_TAKEN(sdev, *psi))){ + DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); + DMCRIT("PSI %u is occupied", *psi); + print_hex_dump(KERN_CRIT, "prmslices ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); + msleep(10000); + print_hex_dump(KERN_CRIT, "ofld ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); + msleep(10000); return -ENOTRECOVERABLE; // Grave inconsistency + } return 0; } @@ -61,16 +84,15 @@ static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) static void _create_local_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 psi) { struct vvz_device *sdev = svol->sdev; - u32 i; /* Grab it from the device */ sdev->slices_ofld[psi] = true; sdev->nr_free_slices--; // Preserve the invariant: @prmslices_octr must point to a free slice - for (i = sdev->prmslices_octr; - i < sdev->tot_slices && sdev->slices_ofld[i]; - i++); - sdev->prmslices_octr = i; + while(sdev->prmslices_octr < sdev->tot_slices && + IS_PSI_TAKEN(sdev, NEXT_RANDOM_PSI(sdev))) { + sdev->prmslices_octr++; + } /* Insert in the volume */ svol->posmap[lsi] = psi; @@ -143,9 +165,13 @@ static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) u32 lsi; for (lsi = first_lsi; lsi < last_lsi; lsi++) { u32 psi = svol->posmap[lsi]; - __be32 *be_psi = (__be32*) (page_ptr + (lsi * sizeof(__be32))); + __be32 *be_psi = (__be32*) (page_ptr + ((lsi-first_lsi) * sizeof(__be32))); *be_psi = cpu_to_be32(psi); } + +// print_hex_dump(KERN_WARNING, "page_ptr(REV) ", DUMP_PREFIX_OFFSET, 32, 4, page_ptr, VVZ_BLOCK_SIZE, false); +// msleep(100); + kunmap_local(page_ptr); /* Encrypt the block in place */ @@ -297,8 +323,7 @@ skip_create_mapping: /* Only check dirty bit at the end of the posmap block */ if (posmap_block_dirty && - (((lsi+1) % VVZ_PSIS_PER_BLOCK == 0) || - ((lsi+1) == sdev->tot_slices))) { + IS_LAST_LSI_IN_BLOCK(lsi, sdev)) { err = store_posmap_block(svol, lsi/VVZ_PSIS_PER_BLOCK); if (err) return err; @@ -343,6 +368,9 @@ int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) if (err) return err; +// print_hex_dump(KERN_CRIT, "posmap(REV) ", DUMP_PREFIX_OFFSET, 32, 4, svol->posmap, 4*sdev->tot_slices, false); +// msleep(2000); + return 0; } diff --git a/dm-vvz/read.c b/dm-vvz/read.c index 0170a2c..cd61934 100644 --- a/dm-vvz/read.c +++ b/dm-vvz/read.c @@ -22,6 +22,7 @@ */ #include "vvz.h" +#include static void vvz_read_endio(struct bio *phys_bio); @@ -50,15 +51,24 @@ void vvz_read_work_fn(struct work_struct *work) /* If LSI is unmapped, short-circuit and return all zeros */ if (psi == VVZ_PSI_INVALID) { + zero_fill_bio(orig_bio); orig_bio->bi_status = BLK_STS_OK; goto endio; } + sio->psi = psi; + +// DMWARN("READ: LSI=%u, PSI=%u, offset=%u", lsi, psi, sio->block_offset); +// msleep(100); /* Shallow-copy the bio and submit it (different bi_endio). We can shallow-copy because we don't need to own the pages, we can decrypt in place. */ + + //DMWARN("READ: shallow copying"); + //msleep(500); + /* Shallow copy */ phys_bio = bio_alloc_clone(svol->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); if (!phys_bio) { @@ -69,6 +79,9 @@ void vvz_read_work_fn(struct work_struct *work) /* Insert in the I/O struct */ sio->phys_bio = phys_bio; +// DMWARN("READ: submitting bio"); +// msleep(500); + /* Remap sector */ phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); /* Set fields for the endio */ @@ -91,6 +104,9 @@ static void vvz_read_endio(struct bio *phys_bio) { struct vvz_io *sio = phys_bio->bi_private; +// DMWARN("READ ENDIO: queueing decryption"); +// //msleep(500); + /* Can't decrypt here in ISR: submit to decryption workqueue. * Can reuse the same work item, though, since it was popped out of the * io_queue already */ @@ -115,23 +131,37 @@ static void vvz_decrypt_work_fn(struct work_struct *work) goto endio; } +// DMWARN("DECRYPT FN: decrypting page in place"); +// msleep(2000); + /* Decrypt page in-place */ err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_page, - phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, READ); + VVZ_PHYS_BIO_SECTOR(svol->sdev, sio->psi, sio->block_offset) >> VVZ_BLOCK_SHIFT, + READ); if (err) { DMERR("Could not decrypt bio; error %d", err); orig_bio->bi_status = BLK_STS_IOERR; goto endio; } +// print_hex_dump(KERN_WARNING, "readpage ", DUMP_PREFIX_OFFSET, 32, 1, bvl.bv_page, VVZ_BLOCK_SIZE, true); +// msleep(2000); + +// DMWARN("DECRYPT FN: bio_advance"); +// msleep(300); + /* Advance original bio by one block */ bio_advance(orig_bio, VVZ_BLOCK_SIZE); orig_bio->bi_status = BLK_STS_OK; endio: /* Free the physical bio */ +// DMWARN("DECRYPT FN: bio_put"); +// msleep(300); bio_put(phys_bio); /* End original bio */ +// DMWARN("DECRYPT FN: bio_endio\n\n\n\n"); +// msleep(300); bio_endio(orig_bio); return; diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index 9024dd4..b8a6693 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -27,6 +27,8 @@ #include "vvz_constants.h" #include "vvz.h" +#include + /* *---------------------------- * Device mapper target @@ -214,15 +216,10 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) struct vvz_volume *svol = ti->private; sector_t lblk_num = bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT; - /* Accept one block at a time TODO improve */ - if (unlikely(bio->bi_iter.bi_size > VVZ_BLOCK_SIZE)) - dm_accept_partial_bio(bio, VVZ_BLOCK_SCALE); - /* Only one segment, single page, starting at 0 TODO improve */ - if (unlikely(bio_segments(bio) > 1 || - bio_offset(bio) != 0)) { - DMWARN("Unaligned bio!"); - return DM_MAPIO_KILL; - } + if (unlikely(!bio_has_data(bio))) { + DMWARN("No-data bio: bio_op() = %d, bi_opf = %u, bi_io_vec = %p, bi_idx = %u", bio_op(bio), bio->bi_opf, bio->bi_io_vec, bio->bi_iter.bi_idx); + msleep(100); + } /* Flush requests are just passed down, since our position map is * currently write-through, so we have no volatile cache */ @@ -230,12 +227,34 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) /* Has to be empty though */ if (bio_sectors(bio)) { DMWARN("Non-empty flush request!"); + msleep(100); return DM_MAPIO_KILL; } + DMWARN("REQ_PREFLUSH empty (phew), sector: %llu", bio->bi_iter.bi_sector); + msleep(3000); bio_set_dev(bio, svol->dm_dev->bdev); return DM_MAPIO_REMAPPED; } + /* Accept one block at a time TODO improve */ + if (unlikely(bio->bi_iter.bi_size > VVZ_BLOCK_SIZE)) { + DMWARN("Big bio: %u", bio->bi_iter.bi_size); + msleep(300); + dm_accept_partial_bio(bio, VVZ_BLOCK_SCALE); + } + /* Only one segment, single page, starting at 0 TODO improve */ + if (unlikely(bio_segments(bio) > 1 || + bio_offset(bio) != 0)) { + DMWARN("Unaligned bio!"); + msleep(3000); + return DM_MAPIO_KILL; + } + if (unlikely(bio->bi_iter.bi_size != VVZ_BLOCK_SIZE)) { + DMWARN("Wrong bio size: %u", bio->bi_iter.bi_size); + msleep(3000); + return DM_MAPIO_KILL; + } + /* Init I/O struct */ sio->svol = svol; sio->orig_bio = bio; @@ -270,6 +289,11 @@ static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn struct vvz_volume *svol = ti->private; struct vvz_device *sdev = svol->sdev; + if (!fn) { + dump_stack(); + msleep(2000); + return -EINVAL; + } return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + ti->len, data); } diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h index 3aa66b2..de4ec73 100644 --- a/dm-vvz/vvz.h +++ b/dm-vvz/vvz.h @@ -117,6 +117,7 @@ struct vvz_io struct bio *phys_bio; u32 lsi; u32 block_offset; + u32 psi; struct work_struct work; }; diff --git a/dm-vvz/write.c b/dm-vvz/write.c index 4fc1964..36ee151 100644 --- a/dm-vvz/write.c +++ b/dm-vvz/write.c @@ -22,6 +22,7 @@ */ #include "vvz.h" +#include static void vvz_write_endio(struct bio *phys_bio); @@ -41,6 +42,10 @@ void vvz_write_work_fn(struct work_struct *work) u32 psi; int err; +// DMWARN("WRITE: dequeued. Sector = %llu", orig_bio->bi_iter.bi_sector); +// msleep(100); + + /* Read existing mapping, or create new one */ if (mutex_lock_interruptible(&svol->posmap_lock)) { orig_bio->bi_status = BLK_STS_IOERR; @@ -49,6 +54,9 @@ void vvz_write_work_fn(struct work_struct *work) psi = svol->posmap[lsi]; /* If LSI unmapped, create new mapping, while holding the lock */ if (psi == VVZ_PSI_INVALID) { +// DMWARN("WRITE: unmapped LSI %u, sampling PSI", lsi); +// msleep(100); + err = vvz_create_persistent_slice_mapping(svol, lsi, &psi); if (err){ DMERR("Could not create slice mapping; error %d", err); @@ -56,8 +64,11 @@ void vvz_write_work_fn(struct work_struct *work) orig_bio->bi_status = BLK_STS_IOERR; goto endio; } +// DMWARN("WRITE: sampled PSI %u for LSI %u", psi, lsi); +// msleep(100); } mutex_unlock(&svol->posmap_lock); + sio->psi = psi; /* Allocate physical bio */ phys_bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, orig_bio->bi_opf, @@ -117,6 +128,7 @@ static void vvz_write_endio(struct bio *phys_bio) /* If physical bio failed, then fail-fast */ if (phys_bio->bi_status != BLK_STS_OK) { orig_bio->bi_status = phys_bio->bi_status; + DMWARN("WRITE ENDIO: phys_bio failed"); goto endio; } From 57cd8f0c13ade6bbecb786bbc9919eca3e134329 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 28 Jul 2024 18:30:46 +0200 Subject: [PATCH 57/98] Comment out prints --- dm-vvz/vvz.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c index b8a6693..a61b688 100644 --- a/dm-vvz/vvz.c +++ b/dm-vvz/vvz.c @@ -217,8 +217,8 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) sector_t lblk_num = bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT; if (unlikely(!bio_has_data(bio))) { - DMWARN("No-data bio: bio_op() = %d, bi_opf = %u, bi_io_vec = %p, bi_idx = %u", bio_op(bio), bio->bi_opf, bio->bi_io_vec, bio->bi_iter.bi_idx); - msleep(100); +// DMWARN("No-data bio: bio_op() = %d, bi_opf = %u, bi_io_vec = %p, bi_idx = %u", bio_op(bio), bio->bi_opf, bio->bi_io_vec, bio->bi_iter.bi_idx); +// msleep(100); } /* Flush requests are just passed down, since our position map is @@ -227,11 +227,11 @@ static int vvz_map(struct dm_target *ti, struct bio *bio) /* Has to be empty though */ if (bio_sectors(bio)) { DMWARN("Non-empty flush request!"); - msleep(100); + msleep(3000); return DM_MAPIO_KILL; } - DMWARN("REQ_PREFLUSH empty (phew), sector: %llu", bio->bi_iter.bi_sector); - msleep(3000); +// DMWARN("REQ_PREFLUSH empty (phew), sector: %llu", bio->bi_iter.bi_sector); +// msleep(100); bio_set_dev(bio, svol->dm_dev->bdev); return DM_MAPIO_REMAPPED; } From 8755ba0014f578548f387a1bb9a7acfcbaef2d68 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 2 Aug 2024 17:47:49 +0200 Subject: [PATCH 58/98] Begin unification --- dm-sflc/Kbuild | 19 +- dm-sflc/crypto/rand/selftest.c | 114 ------- dm-sflc/crypto/symkey/selftest.c | 261 ---------------- dm-sflc/dev_vol.c | 185 ++++++++++++ dm-sflc/{ => old}/crypto/rand/rand.c | 54 ++-- dm-sflc/{ => old}/crypto/rand/rand.h | 17 +- dm-sflc/{ => old}/crypto/symkey/skreq_pool.c | 18 +- dm-sflc/{ => old}/crypto/symkey/skreq_pool.h | 10 +- dm-sflc/{ => old}/crypto/symkey/symkey.c | 50 +-- dm-sflc/{ => old}/crypto/symkey/symkey.h | 29 +- dm-sflc/{ => old}/device/device.c | 211 ++++--------- dm-sflc/{ => old}/device/device.h | 93 +++--- dm-sflc/{ => old}/device/iv.c | 67 +++-- dm-sflc/{ => old}/device/rawio.c | 14 +- dm-sflc/{ => old}/device/rmap.c | 14 +- dm-sflc/{ => old}/device/volumes.c | 43 +-- dm-sflc/{ => old}/log/log.h | 6 +- dm-sflc/{module.c => old/sflc_old.c} | 96 +----- dm-sflc/{target/target.h => old/sflc_old.h} | 40 +-- dm-sflc/old/sysfs.c | 142 +++++++++ dm-sflc/old/target.c | 150 +++++++++ dm-sflc/{ => old}/utils/bio.c | 12 +- dm-sflc/{ => old}/utils/bio.h | 10 +- dm-sflc/{ => old}/utils/pools.c | 94 +++--- dm-sflc/{ => old}/utils/pools.h | 22 +- dm-sflc/{ => old}/utils/string.c | 8 +- dm-sflc/{ => old}/utils/string.h | 10 +- dm-sflc/{ => old}/utils/vector.c | 10 +- dm-sflc/{ => old}/utils/vector.h | 8 +- dm-sflc/{ => old}/utils/workqueues.c | 30 +- dm-sflc/{ => old}/utils/workqueues.h | 14 +- dm-sflc/{ => old}/volume/fmap.c | 86 +++--- dm-sflc/{ => old}/volume/io.c | 24 +- dm-sflc/{ => old}/volume/read.c | 82 ++--- dm-sflc/{ => old}/volume/volume.c | 94 ++++-- dm-sflc/{ => old}/volume/volume.h | 69 ++--- dm-sflc/{ => old}/volume/write.c | 66 ++-- dm-sflc/sflc.c | 270 +++++++++++++++++ dm-sflc/sflc.h | 117 +++++++ dm-sflc/sflc_constants.h | 30 +- dm-sflc/sysfs.c | 206 +++++++++++++ dm-sflc/sysfs/devices.c | 301 ------------------- dm-sflc/sysfs/sysfs.c | 132 -------- dm-sflc/sysfs/sysfs.h | 125 -------- dm-sflc/sysfs/volumes.c | 151 ---------- dm-sflc/target/target.c | 280 ----------------- 46 files changed, 1714 insertions(+), 2170 deletions(-) delete mode 100644 dm-sflc/crypto/rand/selftest.c delete mode 100644 dm-sflc/crypto/symkey/selftest.c create mode 100644 dm-sflc/dev_vol.c rename dm-sflc/{ => old}/crypto/rand/rand.c (77%) rename dm-sflc/{ => old}/crypto/rand/rand.h (85%) rename dm-sflc/{ => old}/crypto/symkey/skreq_pool.c (82%) rename dm-sflc/{ => old}/crypto/symkey/skreq_pool.h (89%) rename dm-sflc/{ => old}/crypto/symkey/symkey.c (75%) rename dm-sflc/{ => old}/crypto/symkey/symkey.h (78%) rename dm-sflc/{ => old}/device/device.c (53%) rename dm-sflc/{ => old}/device/device.h (71%) rename dm-sflc/{ => old}/device/iv.c (82%) rename dm-sflc/{ => old}/device/rawio.c (89%) rename dm-sflc/{ => old}/device/rmap.c (89%) rename dm-sflc/{ => old}/device/volumes.c (70%) rename dm-sflc/{ => old}/log/log.h (95%) rename dm-sflc/{module.c => old/sflc_old.c} (52%) rename dm-sflc/{target/target.h => old/sflc_old.h} (69%) create mode 100644 dm-sflc/old/sysfs.c create mode 100644 dm-sflc/old/target.c rename dm-sflc/{ => old}/utils/bio.c (88%) rename dm-sflc/{ => old}/utils/bio.h (91%) rename dm-sflc/{ => old}/utils/pools.c (56%) rename dm-sflc/{ => old}/utils/pools.h (81%) rename dm-sflc/{ => old}/utils/string.c (92%) rename dm-sflc/{ => old}/utils/string.h (88%) rename dm-sflc/{ => old}/utils/vector.c (92%) rename dm-sflc/{ => old}/utils/vector.h (92%) rename dm-sflc/{ => old}/utils/workqueues.c (76%) rename dm-sflc/{ => old}/utils/workqueues.h (86%) rename dm-sflc/{ => old}/volume/fmap.c (81%) rename dm-sflc/{ => old}/volume/io.c (83%) rename dm-sflc/{ => old}/volume/read.c (72%) rename dm-sflc/{ => old}/volume/volume.c (57%) rename dm-sflc/{ => old}/volume/volume.h (70%) rename dm-sflc/{ => old}/volume/write.c (77%) create mode 100644 dm-sflc/sflc.c create mode 100644 dm-sflc/sflc.h create mode 100644 dm-sflc/sysfs.c delete mode 100644 dm-sflc/sysfs/devices.c delete mode 100644 dm-sflc/sysfs/sysfs.c delete mode 100644 dm-sflc/sysfs/sysfs.h delete mode 100644 dm-sflc/sysfs/volumes.c delete mode 100644 dm-sflc/target/target.c diff --git a/dm-sflc/Kbuild b/dm-sflc/Kbuild index a86d8b9..5c5c705 100644 --- a/dm-sflc/Kbuild +++ b/dm-sflc/Kbuild @@ -21,18 +21,18 @@ # If not, see . # -MODULE_NAME := dm-sflc +MODULE_NAME := dm_sflc obj-m := $(MODULE_NAME).o -OBJ_LIST := module.o -OBJ_LIST += sysfs/sysfs.o sysfs/devices.o sysfs/volumes.o -OBJ_LIST += target/target.o -OBJ_LIST += device/device.o device/volumes.o device/rawio.o device/rmap.o device/iv.o -OBJ_LIST += volume/volume.o volume/io.o volume/read.o volume/write.o volume/fmap.o -OBJ_LIST += utils/string.o utils/bio.o utils/pools.o utils/workqueues.o utils/vector.o -OBJ_LIST += crypto/rand/rand.o crypto/rand/selftest.o -OBJ_LIST += crypto/symkey/symkey.o crypto/symkey/skreq_pool.o crypto/symkey/selftest.o +OBJ_LIST := sflc.o dev_vol.o sysfs.o + +OBJ_LIST += old/sflc_old.o old/target.o old/sysfs.o +OBJ_LIST += old/device/device.o old/device/volumes.o old/device/rawio.o old/device/rmap.o old/device/iv.o +OBJ_LIST += old/volume/volume.o old/volume/io.o old/volume/read.o old/volume/write.o old/volume/fmap.o +OBJ_LIST += old/utils/string.o old/utils/bio.o old/utils/pools.o old/utils/workqueues.o old/utils/vector.o +OBJ_LIST += old/crypto/rand/rand.o +OBJ_LIST += old/crypto/symkey/symkey.o old/crypto/symkey/skreq_pool.o $(MODULE_NAME)-y += $(OBJ_LIST) @@ -41,7 +41,6 @@ $(MODULE_NAME)-y += $(OBJ_LIST) ccflags-y := -O2 ccflags-y += -I$(src) ccflags-y += -Wall -Wno-declaration-after-statement -ccflags-y += -fmacro-prefix-map=$(src)/= # Strip the non-project directories from the filename used in the logs # Debug CC flags ccflags-$(CONFIG_SFLC_DEBUG) += -DDEBUG diff --git a/dm-sflc/crypto/rand/selftest.c b/dm-sflc/crypto/rand/selftest.c deleted file mode 100644 index f0705b9..0000000 --- a/dm-sflc/crypto/rand/selftest.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -#include "rand.h" -#include "log/log.h" - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/***************************************************** - * PRIVATE VARIABLES * - *****************************************************/ - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static void dumpHex(u8 * buf, unsigned count); - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Selftest to see (by eye :D) if generated bytes are actually random */ -int sflc_rand_selftest(void) -{ - u8 * buf; - int err; - - buf = kmalloc(32, GFP_KERNEL); - if (!buf) { - pr_err("Could not allocate random scratchpad\n"); - return -ENOMEM; - } - - /* Get random bytes the first time */ - err = sflc_rand_getBytes(buf, 32); - if (err) { - pr_err("Got error when trying to read random bytes the first time: %d\n", err); - kfree(buf); - return err; - } - pr_debug("Here's 32 random bytes, fresh fresh!\n"); - dumpHex(buf, 32); - - /* Do it again */ - err = sflc_rand_getBytes(buf, 32); - if (err) { - pr_err("Got error when trying to read random bytes the second time: %d\n", err); - kfree(buf); - return err; - } - pr_debug("Here's another 32 random bytes, fresh fresh pure questi!\n"); - dumpHex(buf, 32); - - pr_info("All well in the crypto rand self test? (Check random bytes)\n"); - kfree(buf); - - return 0; -} - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -static void dumpHex(u8 * buf, unsigned count) -{ - char * hex; - - hex = kmalloc(6*count + 1, GFP_KERNEL); - if (!hex) { - pr_err("Could not allocate hex dump string\n"); - return; - } - - int i; - for (i = 0; i < count; i++) { - sprintf(hex+6*i, "0x%02X, ", buf[i]); - } - - pr_notice("---- Hex dump ----\n"); - pr_notice("%s", hex); - pr_notice("---- End of hex dump ----\n"); - - kfree(hex); - return; -} diff --git a/dm-sflc/crypto/symkey/selftest.c b/dm-sflc/crypto/symkey/selftest.c deleted file mode 100644 index 332250d..0000000 --- a/dm-sflc/crypto/symkey/selftest.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "symkey.h" -#include "log/log.h" - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define KEYS \ -{ \ - {0xF6, 0xD6, 0x6D, 0x6B, 0xD5, 0x2D, 0x59, 0xBB, 0x07, 0x96, 0x36, 0x58, 0x79, 0xEF, 0xF8, 0x86, \ - 0xC6, 0x6D, 0xD5, 0x1A, 0x5B, 0x6A, 0x99, 0x74, 0x4B, 0x50, 0x59, 0x0C, 0x87, 0xA2, 0x38, 0x84}, \ - \ - {0xFF, 0x7A, 0x61, 0x7C, 0xE6, 0x91, 0x48, 0xE4, 0xF1, 0x72, 0x6E, 0x2F, 0x43, 0x58, 0x1D, 0xE2, \ - 0xAA, 0x62, 0xD9, 0xF8, 0x05, 0x53, 0x2E, 0xDF, 0xF1, 0xEE, 0xD6, 0x87, 0xFB, 0x54, 0x15, 0x3D}, \ -} - -#define IVS \ -{ \ - {0x00, 0xFA, 0xAC, 0x24, 0xC1, 0x58, 0x5E, 0xF1, 0x5A, 0x43, 0xD8, 0x75, 0x00, 0x00, 0x00, 0x01}, \ - \ - {0x00, 0x1C, 0xC5, 0xB7, 0x51, 0xA5, 0x1D, 0x70, 0xA1, 0xC1, 0x11, 0x48, 0x00, 0x00, 0x00, 0x01}, \ -} - -#define PLAINTEXTS \ -{ \ - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, \ - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}, \ - \ - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, \ - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}, \ -} - -#define CIPHERTEXTS \ -{ \ - {0xF0, 0x5E, 0x23, 0x1B, 0x38, 0x94, 0x61, 0x2C, 0x49, 0xEE, 0x00, 0x0B, 0x80, 0x4E, 0xB2, 0xA9, \ - 0xB8, 0x30, 0x6B, 0x50, 0x8F, 0x83, 0x9D, 0x6A, 0x55, 0x30, 0x83, 0x1D, 0x93, 0x44, 0xAF, 0x1C}, \ - \ - {0xEB, 0x6C, 0x52, 0x82, 0x1D, 0x0B, 0xBB, 0xF7, 0xCE, 0x75, 0x94, 0x46, 0x2A, 0xCA, 0x4F, 0xAA, \ - 0xB4, 0x07, 0xDF, 0x86, 0x65, 0x69, 0xFD, 0x07, 0xF4, 0x8C, 0xC0, 0xB5, 0x83, 0xD6, 0x07, 0x1F}, \ -} - -/***************************************************** - * PRIVATE VARIABLES * - *****************************************************/ - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Test encryption or decryption with known test vectors */ -static int testEncdec(bool encrypt); -/* Test that encryption and decryption invert each other */ -static int testRand(void); -static void dumpHex(u8 * buf, unsigned count); - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Self test using known test vectors and random inputs */ -int sflc_sk_selftest(void) -{ - int err; - - /* Test encryption */ - err = testEncdec(true); - if (err) { - pr_err("Error in encryption test: %d\n", err); - return err; - } - - /* Test decryption */ - err = testEncdec(false); - if (err) { - pr_err("Error in decryption test: %d\n", err); - return err; - } - - /* Test with random inputs */ - err = testRand(); - if (err) { - pr_err("Error in random test: %d\n", err); - return err; - } - - pr_info("All good in crypto symkey selftest\n"); - return 0; -} - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Test encryption or decryption with known test vectors */ -static int testEncdec(bool encrypt) -{ - sflc_sk_Context * ctx[2]; - u8 scratchpad[32]; - u8 key[2][32] = KEYS; - u8 iv[2][16] = IVS; - u8 pt[2][32] = PLAINTEXTS; - u8 ct[2][32] = CIPHERTEXTS; - int err; - - int i; - for (i = 0; i < 2; i++) { - memset(scratchpad, 0xa7, 32); - - ctx[i] = sflc_sk_createContext(key[i]); - if (IS_ERR(ctx)) { - err = PTR_ERR(ctx[i]); - pr_err("Could not create sk context; error %d\n", err); - return err; - } - - if (encrypt) { - err = sflc_sk_encrypt(ctx[i], pt[i], scratchpad, 32, iv[i]); - if (err) { - pr_err("Failure during encryption %d; error %d\n", i, err); - sflc_sk_destroyContext(ctx[i]); - return err; - } - if(memcmp(scratchpad, ct[i], 32) != 0) { - pr_err("Mismatch for encryption %d\n", i); - dumpHex(scratchpad, 16); - sflc_sk_destroyContext(ctx[i]); - return -EINVAL; - } - } - else /* decrypt*/ { - err = sflc_sk_decrypt(ctx[i], ct[i], scratchpad, 32, iv[i]); - if (err) { - pr_err("Failure during decryption %d; error %d\n", i, err); - sflc_sk_destroyContext(ctx[i]); - return err; - } - if (memcmp(scratchpad, pt[i], 32) != 0) { - pr_err("Mismatch for decryption %d\n", i); - dumpHex(scratchpad, 32); - sflc_sk_destroyContext(ctx[i]); - return -EINVAL; - } - } - - sflc_sk_destroyContext(ctx[i]); - } - - return 0; -} - -/* Test that encryption and decryption invert each other */ -static int testRand(void) -{ - u8 pt[48]; - u8 scratchpad[48]; - u8 key[32]; - u8 iv[16]; - sflc_sk_Context * ctx; - int err; - - get_random_bytes(key, 32); - ctx = sflc_sk_createContext(key); - if (IS_ERR(ctx)) { - err = PTR_ERR(ctx); - pr_err("Could not create context; error %d\n", err); - return err; - } - memset(iv, 0, 16); - - int i; - for (i = 0; i < 200; i++) { - get_random_bytes(pt, 48); - err = sflc_sk_encrypt(ctx, pt, scratchpad, 48, iv); - if (err) { - pr_err("Could not encrypt; error %d\n", err); - sflc_sk_destroyContext(ctx); - return err; - } - if (memcmp(pt, scratchpad, 48) == 0) { - pr_err("Random iteration %d; pt=scratchpad\n", i); - sflc_sk_destroyContext(ctx); - return -EINVAL; - } - - /* Reset IV */ - iv[15] = 0; - - err = sflc_sk_decrypt(ctx, scratchpad, scratchpad, 48, iv); - if (err) { - pr_err("Could not decrypt; error %d\n", err); - sflc_sk_destroyContext(ctx); - return err; - } - if (memcmp(pt, scratchpad, 48) != 0) { - pr_err("Random iteration %d; mismatch. Dumping plaintext and scratchpad\n", i); - dumpHex(pt, 48); - dumpHex(scratchpad, 48); - sflc_sk_destroyContext(ctx); - return -EINVAL; - } - - /* Reset IV */ - iv[15] = 0; - } - - sflc_sk_destroyContext(ctx); - return 0; -} - -static void dumpHex(u8 * buf, unsigned count) -{ - char * hex; - - hex = kmalloc(6*count + 1, GFP_KERNEL); - if (!hex) { - pr_err("Could not allocate hex dump string\n"); - return; - } - - int i; - for (i = 0; i < count; i++) { - sprintf(hex+6*i, "0x%02X, ", buf[i]); - } - - pr_notice("---- Hex dump ----\n"); - pr_notice("%s", hex); - pr_notice("---- End of hex dump ----\n"); - - kfree(hex); - return; -} diff --git a/dm-sflc/dev_vol.c b/dm-sflc/dev_vol.c new file mode 100644 index 0000000..422831f --- /dev/null +++ b/dm-sflc/dev_vol.c @@ -0,0 +1,185 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "sflc.h" +#include "old/sflc_old.h" + + +/* Create a sflc_device containing the appropriate mode-specific struct */ +struct sflc_device *sflc_dev_create(struct dm_target *ti, int argc, char **argv) +{ + struct sflc_device *sdev; + u32 dev_id; + dev_t devt; + int mode; + int err; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) { + DMERR("Could not allocate device"); + return ERR_PTR(-ENOMEM); + } + + /* Parse arguments */ + if (sscanf(argv[0], "%d", &mode) != 1) { + err = -EINVAL; + goto bad_parse; + } + sscanf(argv[1], "%u", &dev_id); + err = lookup_bdev(argv[2], &devt); + if (err) { + DMERR("Could not look up block device"); + goto bad_parse; + } + + /* Assign fields */ + sdev->dev_id = dev_id; + sdev->nr_volumes = 0; + sdev->mode = mode; + format_dev_t(sdev->name, devt); + + /* Register with sysfs */ + err = sflc_sysfs_register_device(sdev); + if (err) + goto bad_sysfs; + + /* Instantiate inner device. Sysfs has to be inited by now */ + switch (mode) + { + case SFLC_MODE_LEGACY: + sdev->sfold_dev = sfold_dev_create(ti, argc, argv, &sdev->kobj); + if (IS_ERR(sdev->sfold_dev)) { + err = PTR_ERR(sdev->sfold_dev); + goto bad_inner; + } + break; + default: + DMERR("Invalid Shufflecake mode %d", mode); + err = -EINVAL; + goto bad_mode; + } + + return sdev; + + +bad_mode: +bad_inner: + sflc_sysfs_unregister_device(sdev); +bad_sysfs: +bad_parse: + kfree(sdev); + return ERR_PTR(err); +} + + +void sflc_dev_destroy(struct sflc_device *sdev) +{ + switch (sdev->mode) + { + case SFLC_MODE_LEGACY: + sfold_dev_destroy(sdev->sfold_dev); + break; + default: + DMCRIT("Destroying device with invalid Shufflecake mode %d", sdev->mode); + return; + } + + sflc_sysfs_unregister_device(sdev); + kfree(sdev); +} + +/* Create a sflc_volume containing the appropriate mode-specific struct */ +struct sflc_volume *sflc_vol_create(struct sflc_device *sdev, struct dm_target *ti, + int argc, char **argv) +{ + struct sflc_volume *svol; + u32 vol_idx; + int mode; + int err; + + svol = kzalloc(sizeof(*svol), GFP_KERNEL); + if (!svol) { + DMERR("Could not allocate volume"); + return ERR_PTR(-ENOMEM); + } + + /* Parse arguments */ + if (sscanf(argv[0], "%d", &mode) != 1) { + err = -EINVAL; + goto bad_parse; + } + sscanf(argv[3], "%u", &vol_idx); + + /* Assign fields */ + svol->mode = mode; + sprintf(svol->name, "sflc_%u_%u", sdev->dev_id, vol_idx); + + /* Register with sysfs */ + err = sflc_sysfs_register_volume(svol); + if (err) + goto bad_sysfs; + + /* Instantiate inner volume. Sysfs has to be inited by now */ + switch (mode) + { + case SFLC_MODE_LEGACY: + svol->sfold_vol = sfold_vol_create(ti, sdev->sfold_dev, argc, argv, &svol->kobj); + if (IS_ERR(svol->sfold_vol)) { + err = PTR_ERR(svol->sfold_vol); + goto bad_inner; + } + svol->tt = &sfold_target_type; + break; + default: + DMERR("Invalid Shufflecake mode %d", mode); + err = -EINVAL; + goto bad_mode; + } + + return svol; + + +bad_mode: +bad_inner: + sflc_sysfs_unregister_volume(svol); +bad_sysfs: +bad_parse: + kfree(svol); + return ERR_PTR(err); +} + +void sflc_vol_destroy(struct sflc_volume *svol) +{ + switch (svol->mode) + { + case SFLC_MODE_LEGACY: + sfold_vol_destroy(svol->sfold_vol); + break; + default: + DMCRIT("Destroying volume with invalid Shufflecake mode %d", svol->mode); + return; + } + + sflc_sysfs_unregister_volume(svol); + kfree(svol); +} diff --git a/dm-sflc/crypto/rand/rand.c b/dm-sflc/old/crypto/rand/rand.c similarity index 77% rename from dm-sflc/crypto/rand/rand.c rename to dm-sflc/old/crypto/rand/rand.c index 150a909..4981967 100644 --- a/dm-sflc/crypto/rand/rand.c +++ b/dm-sflc/old/crypto/rand/rand.c @@ -28,55 +28,55 @@ #include #include -#include "rand.h" -#include "log/log.h" +#include "old/crypto/rand/rand.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFLC_RAND_RNG_NAME "drbg_nopr_sha256" +#define SFOLD_RAND_RNG_NAME "drbg_nopr_sha256" /***************************************************** * PRIVATE VARIABLES * *****************************************************/ -static struct mutex sflc_rand_tfm_lock; -static struct crypto_rng * sflc_rand_tfm = NULL; +static struct mutex sfold_rand_tfm_lock; +static struct crypto_rng * sfold_rand_tfm = NULL; /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ /* Flexible to accommodate for both required and non-required reseeding */ -static int sflc_rand_reseed(void); +static int sfold_rand_reseed(void); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ /* Init the submodule */ -int sflc_rand_init(void) +int sfold_rand_init(void) { int err; /* Init the lock governing the SFLC RNG */ - mutex_init(&sflc_rand_tfm_lock); + mutex_init(&sfold_rand_tfm_lock); /* Allocate module-wide RNG */ - sflc_rand_tfm = crypto_alloc_rng(SFLC_RAND_RNG_NAME, CRYPTO_ALG_TYPE_RNG, 0); - if (IS_ERR(sflc_rand_tfm)) { - err = PTR_ERR(sflc_rand_tfm); - sflc_rand_tfm = NULL; - pr_err("Could not allocate RNG %s; error %d\n", SFLC_RAND_RNG_NAME, err); + sfold_rand_tfm = crypto_alloc_rng(SFOLD_RAND_RNG_NAME, CRYPTO_ALG_TYPE_RNG, 0); + if (IS_ERR(sfold_rand_tfm)) { + err = PTR_ERR(sfold_rand_tfm); + sfold_rand_tfm = NULL; + pr_err("Could not allocate RNG %s; error %d\n", SFOLD_RAND_RNG_NAME, err); return err; } /* The new RNG comes not seeded, right? */ - err = sflc_rand_reseed(); + err = sfold_rand_reseed(); if (err) { pr_err("Could not seed the RNG; error %d\n", err); - sflc_rand_exit(); + sfold_rand_exit(); return err; } @@ -84,26 +84,26 @@ int sflc_rand_init(void) } /* Get random bytes. Might sleep for re-seeding (not implemented yet), or for contention (mutex). */ -int sflc_rand_getBytes(u8 * buf, unsigned count) +int sfold_rand_getBytes(u8 * buf, unsigned count) { int ret; /* Acquire lock */ - if (mutex_lock_interruptible(&sflc_rand_tfm_lock)) { + if (mutex_lock_interruptible(&sfold_rand_tfm_lock)) { pr_err("Got error while waiting for SFLC RNG\n"); return -EINTR; } - ret = crypto_rng_get_bytes(sflc_rand_tfm, buf, count); + ret = crypto_rng_get_bytes(sfold_rand_tfm, buf, count); /* End of critical region */ - mutex_unlock(&sflc_rand_tfm_lock); + mutex_unlock(&sfold_rand_tfm_lock); return ret; } /* Get a random s32 from 0 (inclusive) to max (exclusive). Returns < 0 if error. */ -s32 sflc_rand_uniform(s32 max) +s32 sfold_rand_uniform(s32 max) { s32 rand; s32 thresh; @@ -120,7 +120,7 @@ s32 sflc_rand_uniform(s32 max) thresh = S32_MAX - (S32_MAX % max); do { /* Sample a random signed integer, then make it positive */ - int err = sflc_rand_getBytes((void *) &rand, sizeof(rand)); + int err = sfold_rand_getBytes((void *) &rand, sizeof(rand)); /* Can't make it positive if it's all 1's */ if (rand == S32_MIN) { continue; @@ -140,11 +140,11 @@ s32 sflc_rand_uniform(s32 max) } /* Tear down the submodule */ -void sflc_rand_exit(void) +void sfold_rand_exit(void) { - if (sflc_rand_tfm) { - crypto_free_rng(sflc_rand_tfm); - sflc_rand_tfm = NULL; + if (sfold_rand_tfm) { + crypto_free_rng(sfold_rand_tfm); + sfold_rand_tfm = NULL; } return; @@ -155,12 +155,12 @@ void sflc_rand_exit(void) *****************************************************/ /* Flexible to accommodate for both required and non-required reseeding */ -static int sflc_rand_reseed(void) +static int sfold_rand_reseed(void) { int err; /* Reseed the RNG */ - err = crypto_rng_reset(sflc_rand_tfm, NULL, crypto_rng_seedsize(sflc_rand_tfm)); + err = crypto_rng_reset(sfold_rand_tfm, NULL, crypto_rng_seedsize(sfold_rand_tfm)); if (err) { pr_err("Could not feed seed to the RNG; error %d\n", err); return err; diff --git a/dm-sflc/crypto/rand/rand.h b/dm-sflc/old/crypto/rand/rand.h similarity index 85% rename from dm-sflc/crypto/rand/rand.h rename to dm-sflc/old/crypto/rand/rand.h index 4d5f8a7..d7de86a 100644 --- a/dm-sflc/crypto/rand/rand.h +++ b/dm-sflc/old/crypto/rand/rand.h @@ -27,8 +27,8 @@ * no need to make it more fine grained. */ -#ifndef _SFLC_CRYPTO_RAND_RAND_H_ -#define _SFLC_CRYPTO_RAND_RAND_H_ +#ifndef _SFOLD_CRYPTO_RAND_RAND_H_ +#define _SFOLD_CRYPTO_RAND_RAND_H_ /***************************************************** * INCLUDE SECTION * @@ -40,20 +40,17 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -/* Selftest to see (by eye :D) if generated bytes are actually random */ -int sflc_rand_selftest(void); - /* Init the submodule */ -int sflc_rand_init(void); +int sfold_rand_init(void); /* Get random bytes. Might sleep for re-seeding (not implemented yet), or for contention (mutex). */ -int sflc_rand_getBytes(u8 * buf, unsigned count); +int sfold_rand_getBytes(u8 * buf, unsigned count); /* Get a random s32 from 0 (inclusive) to max (exclusive). Returns < 0 if error. */ -s32 sflc_rand_uniform(s32 max); +s32 sfold_rand_uniform(s32 max); /* Tear down the submodule */ -void sflc_rand_exit(void); +void sfold_rand_exit(void); -#endif /* _SFLC_CRYPTO_RAND_RAND_H_ */ +#endif /* _SFOLD_CRYPTO_RAND_RAND_H_ */ diff --git a/dm-sflc/crypto/symkey/skreq_pool.c b/dm-sflc/old/crypto/symkey/skreq_pool.c similarity index 82% rename from dm-sflc/crypto/symkey/skreq_pool.c rename to dm-sflc/old/crypto/symkey/skreq_pool.c index d77c9e1..a91fbf8 100644 --- a/dm-sflc/crypto/symkey/skreq_pool.c +++ b/dm-sflc/old/crypto/symkey/skreq_pool.c @@ -25,8 +25,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "skreq_pool.h" -#include "log/log.h" +#include "old/crypto/symkey/skreq_pool.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * @@ -37,17 +37,17 @@ *****************************************************/ /* A mempool_alloc_t using skcipher_request_alloc as backend */ -static void * sflc_sk_allocRequest(gfp_t gfp_mask, void * pool_data); +static void * sfold_sk_allocRequest(gfp_t gfp_mask, void * pool_data); /* A mempool_free_t using skcipher_request_free as backend */ -static void sflc_sk_freeRequest(void * element, void * pool_data); +static void sfold_sk_freeRequest(void * element, void * pool_data); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -mempool_t * sflc_sk_createReqPool(int min_nr, sflc_sk_Context * ctx) +mempool_t * sfold_sk_createReqPool(int min_nr, sfold_sk_Context * ctx) { - return mempool_create(min_nr, sflc_sk_allocRequest, sflc_sk_freeRequest, (void *) ctx); + return mempool_create(min_nr, sfold_sk_allocRequest, sfold_sk_freeRequest, (void *) ctx); } /***************************************************** @@ -55,9 +55,9 @@ mempool_t * sflc_sk_createReqPool(int min_nr, sflc_sk_Context * ctx) *****************************************************/ /* A mempool_alloc_t using skcipher_request_alloc as backend */ -static void * sflc_sk_allocRequest(gfp_t gfp_mask, void * pool_data) +static void * sfold_sk_allocRequest(gfp_t gfp_mask, void * pool_data) { - sflc_sk_Context * ctx = pool_data; + sfold_sk_Context * ctx = pool_data; struct skcipher_request * skreq; skreq = skcipher_request_alloc(ctx->tfm, gfp_mask); @@ -70,7 +70,7 @@ static void * sflc_sk_allocRequest(gfp_t gfp_mask, void * pool_data) } /* A mempool_free_t using skcipher_request_free as backend */ -static void sflc_sk_freeRequest(void * element, void * pool_data) +static void sfold_sk_freeRequest(void * element, void * pool_data) { struct skcipher_request * skreq = element; diff --git a/dm-sflc/crypto/symkey/skreq_pool.h b/dm-sflc/old/crypto/symkey/skreq_pool.h similarity index 89% rename from dm-sflc/crypto/symkey/skreq_pool.h rename to dm-sflc/old/crypto/symkey/skreq_pool.h index cb57f53..5c5ebba 100644 --- a/dm-sflc/crypto/symkey/skreq_pool.h +++ b/dm-sflc/old/crypto/symkey/skreq_pool.h @@ -26,8 +26,8 @@ * functions to the mempool interface. */ -#ifndef _SFLC_CRYPTO_SYMKEY_SKREQ_POOL_H_ -#define _SFLC_CRYPTO_SYMKEY_SKREQ_POOL_H_ +#ifndef _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ +#define _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ /***************************************************** * INCLUDE SECTION * @@ -35,7 +35,7 @@ #include -#include "symkey.h" +#include "old/crypto/symkey/symkey.h" /***************************************************** * CONSTANTS * @@ -49,7 +49,7 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -mempool_t * sflc_sk_createReqPool(int min_nr, sflc_sk_Context * ctx); +mempool_t * sfold_sk_createReqPool(int min_nr, sfold_sk_Context * ctx); -#endif /* _SFLC_CRYPTO_SYMKEY_SKREQ_POOL_H_ */ +#endif /* _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ */ diff --git a/dm-sflc/crypto/symkey/symkey.c b/dm-sflc/old/crypto/symkey/symkey.c similarity index 75% rename from dm-sflc/crypto/symkey/symkey.c rename to dm-sflc/old/crypto/symkey/symkey.c index 2a78085..668e1ec 100644 --- a/dm-sflc/crypto/symkey/symkey.c +++ b/dm-sflc/old/crypto/symkey/symkey.c @@ -27,66 +27,66 @@ #include -#include "symkey.h" -#include "skreq_pool.h" -#include "log/log.h" +#include "old/crypto/symkey/symkey.h" +#include "old/crypto/symkey/skreq_pool.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFLC_SK_REQ_POOL_SIZE 1024 +#define SFOLD_SK_REQ_POOL_SIZE 1024 -#define SFLC_SK_ENCRYPT 0 -#define SFLC_SK_DECRYPT 1 +#define SFOLD_SK_ENCRYPT 0 +#define SFOLD_SK_DECRYPT 1 /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static int sflc_sk_encdec(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op); +static int sfold_sk_encdec(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ /* Create a new context with the given key. Returns an ERR_PTR() on failure. */ -sflc_sk_Context * sflc_sk_createContext(u8 * key) +sfold_sk_Context * sfold_sk_createContext(u8 * key) { - sflc_sk_Context * ctx; + sfold_sk_Context * ctx; int err; /* Allocate context */ - ctx = kzalloc(sizeof(sflc_sk_Context), GFP_KERNEL); + ctx = kzalloc(sizeof(sfold_sk_Context), GFP_KERNEL); if (!ctx) { - pr_err("Could not allocate %lu bytes for the sflc_sk_Context\n", sizeof(sflc_sk_Context)); + pr_err("Could not allocate %lu bytes for the sfold_sk_Context\n", sizeof(sfold_sk_Context)); return ERR_PTR(-ENOMEM); } /* Allocate crypto transform */ - ctx->tfm = crypto_alloc_skcipher(SFLC_SK_CIPHER_NAME, CRYPTO_ALG_ASYNC, 0); + ctx->tfm = crypto_alloc_skcipher(SFOLD_SK_CIPHER_NAME, CRYPTO_ALG_ASYNC, 0); if (IS_ERR(ctx->tfm)) { err = PTR_ERR(ctx->tfm); ctx->tfm = NULL; pr_err("Could not allocate skcipher handle: error %d\n", err); - sflc_sk_destroyContext(ctx); + sfold_sk_destroyContext(ctx); return ERR_PTR(err); } /* Copy and set key */ - memcpy(ctx->key, key, SFLC_SK_KEY_LEN); - err = crypto_skcipher_setkey(ctx->tfm, ctx->key, SFLC_SK_KEY_LEN); + memcpy(ctx->key, key, SFOLD_SK_KEY_LEN); + err = crypto_skcipher_setkey(ctx->tfm, ctx->key, SFOLD_SK_KEY_LEN); if (err) { pr_err("Could not set key in crypto transform: error %d\n", err); - sflc_sk_destroyContext(ctx); + sfold_sk_destroyContext(ctx); return ERR_PTR(err); } /* Create request memory pool */ - ctx->sk_req_pool = sflc_sk_createReqPool(SFLC_SK_REQ_POOL_SIZE, ctx); + ctx->sk_req_pool = sfold_sk_createReqPool(SFOLD_SK_REQ_POOL_SIZE, ctx); if (!ctx->sk_req_pool) { pr_err("Could not allocate skcipher_request memory pool\n"); - sflc_sk_destroyContext(ctx); + sfold_sk_destroyContext(ctx); return ERR_PTR(-ENOMEM); } @@ -94,7 +94,7 @@ sflc_sk_Context * sflc_sk_createContext(u8 * key) } /* Destroy the given context */ -void sflc_sk_destroyContext(sflc_sk_Context * ctx) +void sfold_sk_destroyContext(sfold_sk_Context * ctx) { if (!ctx) { return; @@ -116,21 +116,21 @@ void sflc_sk_destroyContext(sflc_sk_Context * ctx) } /* Encrypt synchronously. Provide src = dst for in-place operation. */ -int sflc_sk_encrypt(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) +int sfold_sk_encrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) { - return sflc_sk_encdec(ctx, src, dst, len, iv, SFLC_SK_ENCRYPT); + return sfold_sk_encdec(ctx, src, dst, len, iv, SFOLD_SK_ENCRYPT); } -int sflc_sk_decrypt(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) +int sfold_sk_decrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) { - return sflc_sk_encdec(ctx, src, dst, len, iv, SFLC_SK_DECRYPT); + return sfold_sk_encdec(ctx, src, dst, len, iv, SFOLD_SK_DECRYPT); } /***************************************************** * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static int sflc_sk_encdec(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op) +static int sfold_sk_encdec(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op) { struct skcipher_request * skreq; struct scatterlist srcsg; @@ -162,7 +162,7 @@ static int sflc_sk_encdec(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned in crypto_req_done, &skreq_wait); /* Do it */ - if (op == SFLC_SK_ENCRYPT) { + if (op == SFOLD_SK_ENCRYPT) { ret = crypto_skcipher_encrypt(skreq); } else { ret = crypto_skcipher_decrypt(skreq); diff --git a/dm-sflc/crypto/symkey/symkey.h b/dm-sflc/old/crypto/symkey/symkey.h similarity index 78% rename from dm-sflc/crypto/symkey/symkey.h rename to dm-sflc/old/crypto/symkey/symkey.h index 1b6d40b..3f4d440 100644 --- a/dm-sflc/crypto/symkey/symkey.h +++ b/dm-sflc/old/crypto/symkey/symkey.h @@ -25,8 +25,8 @@ * A thin wrapper around the kernel's synchronous block cipher API. */ -#ifndef _SFLC_CRYPTO_SYMKEY_SYMKEY_H_ -#define _SFLC_CRYPTO_SYMKEY_SYMKEY_H_ +#ifndef _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ +#define _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ /***************************************************** * INCLUDE SECTION * @@ -39,9 +39,9 @@ * CONSTANTS * *****************************************************/ -#define SFLC_SK_CIPHER_NAME "ctr(aes)" -#define SFLC_SK_KEY_LEN 32 -#define SFLC_SK_IV_LEN 16 +#define SFOLD_SK_CIPHER_NAME "ctr(aes)" +#define SFOLD_SK_KEY_LEN 32 +#define SFOLD_SK_IV_LEN 16 /***************************************************** * TYPES * @@ -51,34 +51,31 @@ * There is one of these Context's for each volume. * No need for locking, methods can be called in parallel. */ -typedef struct sflc_sk_context_s +typedef struct sfold_sk_context_s { /* Only one transform for now */ struct crypto_skcipher * tfm; /* 32-byte key */ - u8 key[SFLC_SK_KEY_LEN]; + u8 key[SFOLD_SK_KEY_LEN]; /* Memory pool for skcipher_request's */ mempool_t * sk_req_pool; -} sflc_sk_Context; +} sfold_sk_Context; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -/* Self test using known test vectors and random inputs */ -int sflc_sk_selftest(void); - /* Create a new context with the given key. Returns an ERR_PTR() on failure. */ -sflc_sk_Context * sflc_sk_createContext(u8 * key); +sfold_sk_Context * sfold_sk_createContext(u8 * key); /* Destroy the given context */ -void sflc_sk_destroyContext(sflc_sk_Context * ctx); +void sfold_sk_destroyContext(sfold_sk_Context * ctx); /* Encrypt/decrypt synchronously. Provide src = dst for in-place operation. */ -int sflc_sk_encrypt(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); -int sflc_sk_decrypt(sflc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); +int sfold_sk_encrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); +int sfold_sk_decrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); -#endif /* _SFLC_CRYPTO_SYMKEY_SYMKEY_H_ */ +#endif /* _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ */ diff --git a/dm-sflc/device/device.c b/dm-sflc/old/device/device.c similarity index 53% rename from dm-sflc/device/device.c rename to dm-sflc/old/device/device.c index 4356626..1795575 100644 --- a/dm-sflc/device/device.c +++ b/dm-sflc/old/device/device.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "device.h" -#include "sysfs/sysfs.h" -#include "utils/vector.h" -#include "log/log.h" +#include "old/sflc_old.h" +#include "old/device/device.h" +#include "old/utils/vector.h" +#include "old/log/log.h" #include @@ -44,106 +44,73 @@ * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -/* The next available device ID */ -size_t sflc_dev_nextId; - -LIST_HEAD(sflc_dev_list); -DEFINE_SEMAPHORE(sflc_dev_mutex); - - -/***************************************************** - * PRIVATE VARIABLES * - *****************************************************/ - -/* Array tracking occupation of device IDs */ -static bool *sflc_dev_occupiedIds; - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Acquire device ID, returns false if not possible */ -static bool sflc_dev_acquireId(size_t id); - -/* Release device ID */ -static void sflc_dev_releaseId(size_t id); /* Initialises and pre-shuffles the PSI array */ -static int sflc_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); +static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -/* Inits global variables */ -int sflc_dev_init(void) +/** + * Creates Device and adds it to the list. Returns an ERR_PTR() if unsuccessful. + * Arguments: + * argv[0]: Shufflecake mode: legacy/lite + * argv[1]: Shufflecake-unique device ID + * argv[2]: path to underlying physical device + * argv[3]: volume index within the device + * argv[4]: number of 1 MB slices in the underlying device + * argv[5]: 32-byte encryption key (hex-encoded) + */ +sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj) { - /* Allocate occupation array */ - sflc_dev_occupiedIds = kzalloc(SFLC_DEV_MAX_DEVICES_TOT, GFP_KERNEL); - if (!sflc_dev_occupiedIds) { - pr_err("Could not allocate array to track deviceID occupation"); - return -ENOMEM; - } - - /* First available ID is 0 */ - sflc_dev_nextId = 0; - - return 0; -} - -/* Tears down global variables */ -void sflc_dev_exit(void) -{ - kfree(sflc_dev_occupiedIds); -} - -/* Creates Device and adds it to the list. Returns an ERR_PTR() if unsuccessful. */ -sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 tot_slices) -{ - sflc_Device * dev; + sfold_Device * dev; + u32 tot_slices; + u32 dev_id; int err; int i; - pr_debug("Called to create sflc_Device on %s\n", real_dev_path); - /* Allocate device */ - dev = kzalloc(sizeof(sflc_Device), GFP_KERNEL); + dev = kzalloc(sizeof(sfold_Device), GFP_KERNEL); if (!dev) { - pr_err("Could not allocate %lu bytes for sflc_Device\n", sizeof(sflc_Device)); + pr_err("Could not allocate %lu bytes for sfold_Device\n", sizeof(sfold_Device)); err = -ENOMEM; goto err_alloc_dev; } + /* Parse args */ + sscanf(argv[1], "%u", &dev_id); + if (sscanf(argv[4], "%u", &tot_slices) != 1) { + pr_err("Could not decode tot_slices\n"); + err = -EINVAL; + goto err_parse; + } + /* Init list node here, so it's always safe to list_del() */ INIT_LIST_HEAD(&dev->list_node); /* Set device ID */ - dev->dev_id = sflc_dev_nextId; - if (!sflc_dev_acquireId(sflc_dev_nextId)) { - pr_err("Could not create Device: max number of open devices reached"); - err = -EINVAL; - goto err_dev_id; - } + dev->dev_id = dev_id; /* Set backing real device */ - err = dm_get_device(ti, real_dev_path, dm_table_get_mode(ti->table), &dev->bdev); + err = dm_get_device(ti, argv[2], dm_table_get_mode(ti->table), &dev->bdev); if (err) { pr_err("Could not dm_get_device: error %d\n", err); goto err_dm_get_dev; } + dev->ti = ti; /* And its path */ - dev->bdev_path = kmalloc(strlen(real_dev_path) + 1, GFP_KERNEL); + dev->bdev_path = kmalloc(strlen(argv[2]) + 1, GFP_KERNEL); if (!dev->bdev_path) { - pr_err("Could not allocate %lu bytes for dev->real_dev_path\n", strlen(real_dev_path) + 1); + pr_err("Could not allocate %lu bytes for dev->real_dev_path\n", strlen(argv[2]) + 1); err = -ENOMEM; goto err_alloc_real_dev_path; } - strcpy(dev->bdev_path, real_dev_path); + strcpy(dev->bdev_path, argv[2]); /* Init volumes */ - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; ++i) { + for (i = 0; i < SFOLD_DEV_MAX_VOLUMES; ++i) { dev->vol[i] = NULL; } dev->vol_cnt = 0; @@ -152,10 +119,10 @@ sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 t dev->tot_slices = tot_slices; dev->free_slices = tot_slices; /* Compute header info (like in userland tool) */ - u32 nr_pmbs_per_vol = DIV_ROUND_UP(tot_slices, SFLC_VOL_HEADER_MAPPINGS_PER_BLOCK); - dev->vol_header_nr_iv_blocks = DIV_ROUND_UP(nr_pmbs_per_vol, SFLC_VOL_LOG_SLICE_SIZE); + u32 nr_pmbs_per_vol = DIV_ROUND_UP(tot_slices, SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK); + dev->vol_header_nr_iv_blocks = DIV_ROUND_UP(nr_pmbs_per_vol, SFOLD_VOL_LOG_SLICE_SIZE); dev->vol_header_size = 1 + nr_pmbs_per_vol + dev->vol_header_nr_iv_blocks; - dev->dev_header_size = 1 + (SFLC_DEV_MAX_VOLUMES * dev->vol_header_size); + dev->dev_header_size = 1 + (SFOLD_DEV_MAX_VOLUMES * dev->vol_header_size); /* Init slices lock */ mutex_init(&dev->slices_lock); @@ -167,7 +134,7 @@ sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 t goto err_alloc_rmap; } /* Initialise it */ - memset(dev->rmap, SFLC_DEV_RMAP_INVALID_VOL, dev->tot_slices * sizeof(u8)); + memset(dev->rmap, SFOLD_DEV_RMAP_INVALID_VOL, dev->tot_slices * sizeof(u8)); /* Allocate PSI array */ dev->shuffled_psi_array = vmalloc(dev->tot_slices * sizeof(u32)); if (!dev->shuffled_psi_array) { @@ -176,7 +143,7 @@ sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 t goto err_alloc_psi_array; } /* Initialise it and pre-shuffle it */ - err = sflc_dev_initAndShufflePsiArray(dev->shuffled_psi_array, dev->tot_slices); + err = sfold_dev_initAndShufflePsiArray(dev->shuffled_psi_array, dev->tot_slices); if (err) { pr_err("Could not init-and-shuffle PSI array: error %d", err); goto err_initshuffle_psi_array; @@ -189,7 +156,7 @@ sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 t /* Init IV cache waitqueue */ init_waitqueue_head(&dev->iv_cache_waitqueue); /* Allocate IV cache */ - dev->iv_cache = kzalloc(dev->tot_slices * sizeof(sflc_dev_IvCacheEntry *), GFP_KERNEL); + dev->iv_cache = kzalloc(dev->tot_slices * sizeof(sfold_dev_IvCacheEntry *), GFP_KERNEL); if (!dev->iv_cache) { pr_err("Could not allocate IV cache\n"); err = -ENOMEM; @@ -200,17 +167,14 @@ sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 t /* Init list head */ INIT_LIST_HEAD(&dev->iv_lru_list); - /* Create kobject */ - dev->kobj = sflc_sysfs_devCreateAndAdd(dev); - if (IS_ERR(dev->kobj)) { - err = PTR_ERR(dev->kobj); - pr_err("Could not create kobject; error %d\n", err); + /* Add to sysfs */ + dev->kobj_parent = kobj; + err = sfold_sysfs_add_device(dev); + if (err) { + pr_err("Could not add device to sysfs; error %d\n", err); goto err_sysfs; } - /* Add to device list */ - list_add_tail(&dev->list_node, &sflc_dev_list); - return dev; @@ -226,30 +190,15 @@ err_alloc_rmap: err_alloc_real_dev_path: dm_put_device(ti, dev->bdev); err_dm_get_dev: - sflc_dev_releaseId(dev->dev_id); -err_dev_id: +err_parse: kfree(dev); err_alloc_dev: return ERR_PTR(err); } -/* Returns NULL if not found */ -sflc_Device * sflc_dev_lookupByPath(char * real_dev_path) -{ - sflc_Device * dev; - - /* Sweep the list of devices */ - list_for_each_entry(dev, &sflc_dev_list, list_node) { - if (strcmp(real_dev_path, dev->bdev_path) == 0) { - return dev; - } - } - - return NULL; -} /* Returns false if still busy (not all volumes have been removed). Frees the Device. */ -bool sflc_dev_destroy(struct dm_target * ti, sflc_Device * dev) +bool sfold_dev_destroy(sfold_Device * dev) { /* Check if we actually have to put this device */ if (!dev) { @@ -261,13 +210,13 @@ bool sflc_dev_destroy(struct dm_target * ti, sflc_Device * dev) } /* Flush all IVs */ - sflc_dev_flushIvs(dev); + sfold_dev_flushIvs(dev); /* List */ list_del(&dev->list_node); /* Sysfs */ - sflc_sysfs_putDev(dev->kobj); + sfold_sysfs_remove_device(dev); /* IV cache */ kfree(dev->iv_cache); @@ -279,12 +228,9 @@ bool sflc_dev_destroy(struct dm_target * ti, sflc_Device * dev) vfree(dev->rmap); /* Backing device */ - dm_put_device(ti, dev->bdev); + dm_put_device(dev->ti, dev->bdev); kfree(dev->bdev_path); - /* Release device ID */ - sflc_dev_releaseId(dev->dev_id); - /* Free the device itself */ kfree(dev); @@ -292,57 +238,8 @@ bool sflc_dev_destroy(struct dm_target * ti, sflc_Device * dev) } -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Acquire device ID, returns false if not possible */ -static bool sflc_dev_acquireId(size_t id) -{ - /* Sanity check */ - if (id >= SFLC_DEV_MAX_DEVICES_TOT) { - return false; - } - /* Check occupation */ - if (sflc_dev_occupiedIds[id]) { - return false; - } - - /* Mark as occupied */ - sflc_dev_occupiedIds[id] = true; - - /* Update the nextId if necessary */ - if (id == sflc_dev_nextId) { - /* Jump to the next unoccupied ID */ - for (; id < SFLC_DEV_MAX_DEVICES_TOT && sflc_dev_occupiedIds[id]; id++); - sflc_dev_nextId = id; - } - - return true; -} - -/* Release volume ID */ -static void sflc_dev_releaseId(size_t id) -{ - /* Sanity check */ - if (id >= SFLC_DEV_MAX_DEVICES_TOT) { - return; - } - - /* Mark as unoccupied */ - sflc_dev_occupiedIds[id] = false; - - /* Update the nextId if necessary */ - if (id < sflc_dev_nextId) { - sflc_dev_nextId = id; - } - - return; -} - - /* Initialises and pre-shuffles the PSI array */ -static int sflc_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) +static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) { u32 i; @@ -352,6 +249,6 @@ static int sflc_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) } /* Permute */ - return sflc_vec_u32shuffle(psi_array, len); + return sfold_vec_u32shuffle(psi_array, len); } diff --git a/dm-sflc/device/device.h b/dm-sflc/old/device/device.h similarity index 71% rename from dm-sflc/device/device.h rename to dm-sflc/old/device/device.h index cc8b0a0..f893772 100644 --- a/dm-sflc/device/device.h +++ b/dm-sflc/old/device/device.h @@ -30,8 +30,8 @@ * are stored in increasing degree of "secrecy"). */ -#ifndef _SFLC_DEVICE_DEVICE_H_ -#define _SFLC_DEVICE_DEVICE_H_ +#ifndef _SFOLD_DEVICE_DEVICE_H_ +#define _SFOLD_DEVICE_DEVICE_H_ /***************************************************** @@ -40,8 +40,8 @@ /* Necessary since device.h, volume.h, and sysfs.h all include each other */ -typedef struct sflc_device_s sflc_Device; -typedef struct sflc_dev_iv_cache_entry_s sflc_dev_IvCacheEntry; +typedef struct sfold_device_s sfold_Device; +typedef struct sfold_dev_iv_cache_entry_s sfold_dev_IvCacheEntry; /***************************************************** @@ -50,9 +50,9 @@ typedef struct sflc_dev_iv_cache_entry_s sflc_dev_IvCacheEntry; #include -#include "volume/volume.h" -#include "crypto/symkey/symkey.h" -#include "sysfs/sysfs.h" +#include "old/sflc_old.h" +#include "old/volume/volume.h" +#include "old/crypto/symkey/symkey.h" /***************************************************** @@ -60,30 +60,30 @@ typedef struct sflc_dev_iv_cache_entry_s sflc_dev_IvCacheEntry; *****************************************************/ /* We need 4096-byte sectors to amortise the space overhead of the IVs */ -#define SFLC_DEV_SECTOR_SIZE 4096 +#define SFOLD_DEV_SECTOR_SIZE 4096 /* A SFLC sector encompasses 8 kernel sectors */ -#define SFLC_DEV_SECTOR_SCALE (SFLC_DEV_SECTOR_SIZE / SECTOR_SIZE) +#define SFOLD_DEV_SECTOR_SCALE (SFOLD_DEV_SECTOR_SIZE / SECTOR_SIZE) /* An IV block holds IVs for 256 data blocks */ -#define SFLC_DEV_SECTOR_TO_IV_RATIO (SFLC_DEV_SECTOR_SIZE / SFLC_SK_IV_LEN) +#define SFOLD_DEV_SECTOR_TO_IV_RATIO (SFOLD_DEV_SECTOR_SIZE / SFOLD_SK_IV_LEN) /* Max number of volumes linked to a single device */ -#define SFLC_DEV_MAX_VOLUMES 15 +#define SFOLD_DEV_MAX_VOLUMES 15 /* A physical slice contains the 256 encrypted data blocks and the IV block */ -#define SFLC_DEV_PHYS_SLICE_SIZE (SFLC_VOL_LOG_SLICE_SIZE + (SFLC_VOL_LOG_SLICE_SIZE / SFLC_DEV_SECTOR_TO_IV_RATIO)) +#define SFOLD_DEV_PHYS_SLICE_SIZE (SFOLD_VOL_LOG_SLICE_SIZE + (SFOLD_VOL_LOG_SLICE_SIZE / SFOLD_DEV_SECTOR_TO_IV_RATIO)) /* Value marking a PSI as unassigned */ -#define SFLC_DEV_RMAP_INVALID_VOL 0xFFU +#define SFOLD_DEV_RMAP_INVALID_VOL 0xFFU /* Maximum number of open devices in total across shufflecake */ -#define SFLC_DEV_MAX_DEVICES_TOT 1024 +#define SFOLD_DEV_MAX_DEVICES_TOT 1024 /***************************************************** * TYPES * *****************************************************/ -struct sflc_dev_iv_cache_entry_s +struct sfold_dev_iv_cache_entry_s { /* The PSI it refers to */ u32 psi; @@ -99,16 +99,18 @@ struct sflc_dev_iv_cache_entry_s struct list_head lru_node; }; -struct sflc_device_s +struct sfold_device_s { /* Underlying block device */ struct dm_dev * bdev; char * bdev_path; + /* Target instance that owns the bdev reference */ + struct dm_target *ti; /* Shufflecake-unique numeric ID of this device */ - size_t dev_id; + u32 dev_id; /* All volumes linked to this device */ - sflc_Volume * vol[SFLC_DEV_MAX_VOLUMES]; + sfold_Volume * vol[SFOLD_DEV_MAX_VOLUMES]; int vol_cnt; /* Reverse slice map, associating PSIs to volume indices */ @@ -128,82 +130,57 @@ struct sflc_device_s u32 vol_header_size; u32 dev_header_size; + /* Parent sysfs directory */ + struct kobject *kobj_parent; + /* LRU cache of IV blocks */ struct mutex iv_cache_lock; wait_queue_head_t iv_cache_waitqueue; - sflc_dev_IvCacheEntry ** iv_cache; + sfold_dev_IvCacheEntry ** iv_cache; u32 iv_cache_nr_entries; struct list_head iv_lru_list; - /* Sysfs stuff */ - sflc_sysfs_Device * kobj; - /* We keep all devices in a list */ struct list_head list_node; }; -/***************************************************** - * PUBLIC VARIABLES DECLARATIONS * - *****************************************************/ - -/* The next available device ID */ -extern size_t sflc_dev_nextId; - -/* List of all devices */ -extern struct list_head sflc_dev_list; -/* Big, coarse-grained lock for all modifying operations on any device or the device list */ -extern struct semaphore sflc_dev_mutex; - - /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ - -/* Inits global variables */ -int sflc_dev_init(void); -/* Tears down global variables */ -void sflc_dev_exit(void); - /* * None of these functions acquire the big device lock: it must be held * by the caller. */ /* Creates Device and adds it to the list. Returns an ERR_PTR() if unsuccessful. */ -sflc_Device * sflc_dev_create(struct dm_target * ti, char * real_dev_path, u32 tot_slices); - -/* Returns NULL if not found */ -sflc_Device * sflc_dev_lookupByPath(char * real_dev_path); +sfold_Device * sfold_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj); /* Returns false if still busy (not all volumes have been removed) Frees the Device. */ -bool sflc_dev_destroy(struct dm_target * ti, sflc_Device * dev); +bool sfold_dev_destroy(sfold_Device * dev); /* Returns false if volume index was already occupied. */ -bool sflc_dev_addVolume(sflc_Device * dev, sflc_Volume * vol, int vol_idx); - -/* Looks at all volumes in all devices. Returns NULL if not found */ -sflc_Volume * sflc_dev_lookupVolumeByName(char * vol_name); +bool sfold_dev_addVolume(sfold_Device * dev, sfold_Volume * vol, int vol_idx); /* Does not put the volume. Returns false if was already NULL. */ -bool sflc_dev_removeVolume(sflc_Device * dev, int vol_idx); +bool sfold_dev_removeVolume(sfold_Device * dev, int vol_idx); /* Synchronously reads/writes one 4096-byte sector from/to the underlying device to/from the provided page */ -int sflc_dev_rwSector(sflc_Device * dev, struct page * page, sector_t sector, int rw); +int sfold_dev_rwSector(sfold_Device * dev, struct page * page, sector_t sector, int rw); /* The caller needs to hold slices_lock to call these functions */ /* Sets the PSI as owned by the given volume (also decreases free_slices). * Returns < 0 if already taken. */ -int sflc_dev_markPsiTaken(sflc_Device * dev, u32 psi, u8 vol_idx); +int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx); /* Returns a random free physical slice, or < 0 if error */ -s32 sflc_dev_getNextRandomFreePsi(sflc_Device * dev); +s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev); /* These functions provide concurrent-safe access to the entries of the IV cache. @@ -214,13 +191,13 @@ s32 sflc_dev_getNextRandomFreePsi(sflc_Device * dev); When the refcount reaches 0, the IV block is flushed. */ /* Get a pointer to the specified IV block. Increases the refcount and possibly the dirtyness (if WRITE). */ -u8 * sflc_dev_getIvBlockRef(sflc_Device * dev, u32 psi, int rw); +u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw); /* Signal end of usage of an IV block. Decreases the refcount. */ -int sflc_dev_putIvBlockRef(sflc_Device * dev, u32 psi); +int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi); /* Flush all dirty IV blocks */ -void sflc_dev_flushIvs(sflc_Device * dev); +void sfold_dev_flushIvs(sfold_Device * dev); -#endif /* _SFLC_DEVICE_DEVICE_H_ */ +#endif /* _SFOLD_DEVICE_DEVICE_H_ */ diff --git a/dm-sflc/device/iv.c b/dm-sflc/old/device/iv.c similarity index 82% rename from dm-sflc/device/iv.c rename to dm-sflc/old/device/iv.c index ad1b3b5..c2c0fb1 100644 --- a/dm-sflc/device/iv.c +++ b/dm-sflc/old/device/iv.c @@ -25,29 +25,30 @@ * INCLUDE SECTION * *****************************************************/ -#include "device.h" -#include "utils/pools.h" -#include "log/log.h" + +#include "old/device/device.h" +#include "old/utils/pools.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ /* Capacity of IV cache */ -#define SFLC_DEV_IV_CACHE_CAPACITY 1024 +#define SFOLD_DEV_IV_CACHE_CAPACITY 1024 /***************************************************** * MACROS * *****************************************************/ -#define sflc_dev_psiToIvBlockSector(dev, psi) (dev->dev_header_size + (sector_t)(psi) * SFLC_DEV_PHYS_SLICE_SIZE) +#define sfold_dev_psiToIvBlockSector(dev, psi) (dev->dev_header_size + (sector_t)(psi) * SFOLD_DEV_PHYS_SLICE_SIZE) /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static sflc_dev_IvCacheEntry * sflc_dev_newIvCacheEntry(sflc_Device * dev, u32 psi); -static int sflc_dev_destroyIvCacheEntry(sflc_Device * dev, sflc_dev_IvCacheEntry * entry); +static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u32 psi); +static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEntry * entry); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * @@ -55,9 +56,9 @@ static int sflc_dev_destroyIvCacheEntry(sflc_Device * dev, sflc_dev_IvCacheEntry /* Get a read/write pointer to the specified IV block. Increases the refcount. Returns an ERR_PTR() if error. */ -u8 * sflc_dev_getIvBlockRef(sflc_Device * dev, u32 psi, int rw) +u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw) { - sflc_dev_IvCacheEntry * entry; + sfold_dev_IvCacheEntry * entry; int err; /* Lock + waitqueue pattern */ @@ -70,13 +71,13 @@ u8 * sflc_dev_getIvBlockRef(sflc_Device * dev, u32 psi, int rw) } /* Check for either of two conditions in order to go through */ - while (dev->iv_cache[psi] == NULL && dev->iv_cache_nr_entries >= SFLC_DEV_IV_CACHE_CAPACITY) { + while (dev->iv_cache[psi] == NULL && dev->iv_cache_nr_entries >= SFOLD_DEV_IV_CACHE_CAPACITY) { /* We can't go through, yield the lock */ mutex_unlock(&dev->iv_cache_lock); /* Sleep in the waitqueue (same conditions) */ if (wait_event_interruptible(dev->iv_cache_waitqueue, dev->iv_cache[psi] != NULL || - dev->iv_cache_nr_entries < SFLC_DEV_IV_CACHE_CAPACITY)) { + dev->iv_cache_nr_entries < SFOLD_DEV_IV_CACHE_CAPACITY)) { err = -EINTR; pr_err("Interrupted while waiting in waitqueue\n"); goto err_wait_queue; @@ -98,7 +99,7 @@ u8 * sflc_dev_getIvBlockRef(sflc_Device * dev, u32 psi, int rw) entry = dev->iv_cache[psi]; if (!entry) { /* Create it */ - entry = sflc_dev_newIvCacheEntry(dev, psi); + entry = sfold_dev_newIvCacheEntry(dev, psi); if (IS_ERR(entry)) { err = PTR_ERR(entry); pr_err("Could not create new cache entry; error %d\n", err); @@ -138,9 +139,9 @@ err_lock_cache: } /* Signal end of usage of an IV block. Decreases the refcount. */ -int sflc_dev_putIvBlockRef(sflc_Device * dev, u32 psi) +int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi) { - sflc_dev_IvCacheEntry * entry; + sfold_dev_IvCacheEntry * entry; int err; /* No condition needed besides mutual exclusion: just grab the lock (no waitqueue) */ @@ -161,12 +162,12 @@ int sflc_dev_putIvBlockRef(sflc_Device * dev, u32 psi) list_add(&entry->lru_node, &dev->iv_lru_list); /* If cache is not full, we can return now */ - if (dev->iv_cache_nr_entries < SFLC_DEV_IV_CACHE_CAPACITY) { + if (dev->iv_cache_nr_entries < SFOLD_DEV_IV_CACHE_CAPACITY) { goto out; } /* Otherwise, let's look for the least recent unreffed entry, and evict it */ - sflc_dev_IvCacheEntry * evicted; + sfold_dev_IvCacheEntry * evicted; bool found = false; list_for_each_entry_reverse(evicted, &dev->iv_lru_list, lru_node) { if (evicted->refcnt == 0) { @@ -187,7 +188,7 @@ int sflc_dev_putIvBlockRef(sflc_Device * dev, u32 psi) /* Pull it out of the LRU list */ __list_del_entry(&evicted->lru_node); /* Destroy it (free and flush to disk) */ - err = sflc_dev_destroyIvCacheEntry(dev, evicted); + err = sfold_dev_destroyIvCacheEntry(dev, evicted); if (err) { pr_err("Could not evict cache entry for PSI %u; error %d\n", evicted->psi, err); goto err_destroy_entry; @@ -215,9 +216,9 @@ err_lock_cache: } /* Flush all dirty IV blocks */ -void sflc_dev_flushIvs(sflc_Device * dev) +void sfold_dev_flushIvs(sfold_Device * dev) { - sflc_dev_IvCacheEntry * entry, * _next; + sfold_dev_IvCacheEntry * entry, * _next; int err; /* Iterate over all entries */ @@ -226,7 +227,7 @@ void sflc_dev_flushIvs(sflc_Device * dev) __list_del_entry(&entry->lru_node); /* Destroy it */ - err = sflc_dev_destroyIvCacheEntry(dev, entry); + err = sfold_dev_destroyIvCacheEntry(dev, entry); if (err) { pr_err("Could not destroy IV cache entry for PSI %u; error %d\n", entry->psi, err); } @@ -237,16 +238,16 @@ void sflc_dev_flushIvs(sflc_Device * dev) * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static sflc_dev_IvCacheEntry * sflc_dev_newIvCacheEntry(sflc_Device * dev, u32 psi) +static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u32 psi) { - sflc_dev_IvCacheEntry * entry; + sfold_dev_IvCacheEntry * entry; int err; sector_t sector; /* Allocate and init structure */ /* Allocate structure */ - entry = kmem_cache_alloc(sflc_pools_ivSlab, GFP_NOIO); + entry = kmem_cache_alloc(sfold_pools_ivSlab, GFP_NOIO); if (!entry) { pr_err("Could not allocate IvCacheEntry structure\n"); err = -ENOMEM; @@ -256,7 +257,7 @@ static sflc_dev_IvCacheEntry * sflc_dev_newIvCacheEntry(sflc_Device * dev, u32 p /* Set PSI */ entry->psi = psi; /* Allocate page */ - entry->iv_page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + entry->iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!entry->iv_page) { pr_err("Could not allocate IV page\n"); err = -ENOMEM; @@ -276,10 +277,10 @@ static sflc_dev_IvCacheEntry * sflc_dev_newIvCacheEntry(sflc_Device * dev, u32 p /* Read from disk */ /* Position on disk */ - sector = sflc_dev_psiToIvBlockSector(dev, psi); + sector = sfold_dev_psiToIvBlockSector(dev, psi); /* Read */ - err = sflc_dev_rwSector(dev, entry->iv_page, sector, READ); + err = sfold_dev_rwSector(dev, entry->iv_page, sector, READ); if (err) { pr_err("Could not read IV block from disk; error %d\n", err); goto err_read; @@ -290,14 +291,14 @@ static sflc_dev_IvCacheEntry * sflc_dev_newIvCacheEntry(sflc_Device * dev, u32 p err_read: kunmap(entry->iv_page); - mempool_free(entry->iv_page, sflc_pools_pagePool); + mempool_free(entry->iv_page, sfold_pools_pagePool); err_alloc_page: - kmem_cache_free(sflc_pools_ivSlab, entry); + kmem_cache_free(sfold_pools_ivSlab, entry); err_alloc_entry: return ERR_PTR(err); } -static int sflc_dev_destroyIvCacheEntry(sflc_Device * dev, sflc_dev_IvCacheEntry * entry) +static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEntry * entry) { int err; sector_t sector; @@ -305,11 +306,11 @@ static int sflc_dev_destroyIvCacheEntry(sflc_Device * dev, sflc_dev_IvCacheEntry /* Write to disk */ /* Position on disk */ - sector = sflc_dev_psiToIvBlockSector(dev, entry->psi); + sector = sfold_dev_psiToIvBlockSector(dev, entry->psi); /* Write (if necessary) */ if (entry->dirtyness) { - err = sflc_dev_rwSector(dev, entry->iv_page, sector, WRITE); + err = sfold_dev_rwSector(dev, entry->iv_page, sector, WRITE); if (err) { pr_err("Could not write IV block to disk; error %d\n", err); return err; @@ -322,10 +323,10 @@ static int sflc_dev_destroyIvCacheEntry(sflc_Device * dev, sflc_dev_IvCacheEntry /* Kunmap page */ kunmap(entry->iv_page); /* Free it */ - mempool_free(entry->iv_page, sflc_pools_pagePool); + mempool_free(entry->iv_page, sfold_pools_pagePool); /* Free structure */ - kmem_cache_free(sflc_pools_ivSlab, entry); + kmem_cache_free(sfold_pools_ivSlab, entry); return 0; } diff --git a/dm-sflc/device/rawio.c b/dm-sflc/old/device/rawio.c similarity index 89% rename from dm-sflc/device/rawio.c rename to dm-sflc/old/device/rawio.c index 91cc682..ec70147 100644 --- a/dm-sflc/device/rawio.c +++ b/dm-sflc/old/device/rawio.c @@ -25,9 +25,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "device.h" -#include "utils/pools.h" -#include "log/log.h" +#include "old/device/device.h" +#include "old/utils/pools.h" +#include "old/log/log.h" #include #include @@ -46,7 +46,7 @@ /* Synchronously reads/writes one 4096-byte sector from/to the underlying device to/from the provided page */ -int sflc_dev_rwSector(sflc_Device * dev, struct page * page, sector_t sector, int rw) +int sfold_dev_rwSector(sfold_Device * dev, struct page * page, sector_t sector, int rw) { struct bio *bio; blk_opf_t opf; @@ -57,16 +57,16 @@ int sflc_dev_rwSector(sflc_Device * dev, struct page * page, sector_t sector, in opf |= REQ_SYNC; /* Allocate bio */ - bio = bio_alloc_bioset(dev->bdev->bdev, 1, opf, GFP_NOIO, &sflc_pools_bioset); + bio = bio_alloc_bioset(dev->bdev->bdev, 1, opf, GFP_NOIO, &sfold_pools_bioset); if (!bio) { pr_err("Could not allocate bio\n"); return -ENOMEM; } /* Set sector */ - bio->bi_iter.bi_sector = sector * SFLC_DEV_SECTOR_SCALE; + bio->bi_iter.bi_sector = sector * SFOLD_DEV_SECTOR_SCALE; /* Add page */ - if (!bio_add_page(bio, page, SFLC_DEV_SECTOR_SIZE, 0)) { + if (!bio_add_page(bio, page, SFOLD_DEV_SECTOR_SIZE, 0)) { pr_err("Catastrophe: could not add page to bio! WTF?\n"); err = EINVAL; goto out; diff --git a/dm-sflc/device/rmap.c b/dm-sflc/old/device/rmap.c similarity index 89% rename from dm-sflc/device/rmap.c rename to dm-sflc/old/device/rmap.c index e309211..d196462 100644 --- a/dm-sflc/device/rmap.c +++ b/dm-sflc/old/device/rmap.c @@ -29,9 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "device.h" -#include "crypto/rand/rand.h" -#include "log/log.h" +#include "old/device/device.h" +#include "old/crypto/rand/rand.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * @@ -43,7 +43,7 @@ /* Sets the PSI as owned by the given volume (also decreases free_slices). * Returns < 0 if already taken. */ -int sflc_dev_markPsiTaken(sflc_Device * dev, u32 psi, u8 vol_idx) +int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx) { u8 prev_vol_idx; @@ -55,7 +55,7 @@ int sflc_dev_markPsiTaken(sflc_Device * dev, u32 psi, u8 vol_idx) /* Check that it's free */ prev_vol_idx = dev->rmap[psi]; - if (prev_vol_idx != SFLC_DEV_RMAP_INVALID_VOL) { + if (prev_vol_idx != SFOLD_DEV_RMAP_INVALID_VOL) { pr_err("Requested to set ownership for already-owned PSI\n"); return -EINVAL; } @@ -69,7 +69,7 @@ int sflc_dev_markPsiTaken(sflc_Device * dev, u32 psi, u8 vol_idx) /* Returns a random free physical slice, or < 0 if error */ -s32 sflc_dev_getNextRandomFreePsi(sflc_Device * dev) +s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev) { u32 psi; @@ -89,7 +89,7 @@ s32 sflc_dev_getNextRandomFreePsi(sflc_Device * dev) pr_err("Double catastrophe! No free PSIs on the device, and didn't catch it before!\n"); return -ENOSPC; } - } while (dev->rmap[psi] != SFLC_DEV_RMAP_INVALID_VOL); + } while (dev->rmap[psi] != SFOLD_DEV_RMAP_INVALID_VOL); return psi; } diff --git a/dm-sflc/device/volumes.c b/dm-sflc/old/device/volumes.c similarity index 70% rename from dm-sflc/device/volumes.c rename to dm-sflc/old/device/volumes.c index b6ec553..7b12ab2 100644 --- a/dm-sflc/device/volumes.c +++ b/dm-sflc/old/device/volumes.c @@ -29,8 +29,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "device.h" -#include "log/log.h" +#include "old/device/device.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * @@ -41,22 +41,13 @@ *****************************************************/ /* Returns false if volume index was already occupied. */ -bool sflc_dev_addVolume(sflc_Device * dev, sflc_Volume * vol, int vol_idx) +bool sfold_dev_addVolume(sfold_Device * dev, sfold_Volume * vol, int vol_idx) { - int err; - if (dev->vol[vol_idx]) { pr_err("Something's wrong, asked to set volume number %d, already occupied\n", vol_idx); return false; } - /* Update sysfs */ - err = sflc_sysfs_addVolumeToDevice(dev->kobj, vol->kvol); - if (err) { - pr_err("Could not add volume symlink in sysfs device subdir; error %d\n", err); - return false; - } - /* Update fields */ dev->vol[vol_idx] = vol; dev->vol_cnt += 1; @@ -64,39 +55,15 @@ bool sflc_dev_addVolume(sflc_Device * dev, sflc_Volume * vol, int vol_idx) return true; } -/* Looks at all volumes in all devices. Returns NULL if not found */ -sflc_Volume * sflc_dev_lookupVolumeByName(char * vol_name) -{ - sflc_Device * dev; - sflc_Volume * vol; - - /* Sweep all devices */ - list_for_each_entry(dev, &sflc_dev_list, list_node) { - /* Sweep all volumes */ - int i; - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; ++i) { - vol = dev->vol[i]; - if (vol && (strcmp(vol_name, vol->vol_name) == 0)) { - return vol; - } - } - } - - return NULL; -} /* Does not put the volume. Returns false if was already NULL. */ -bool sflc_dev_removeVolume(sflc_Device * dev, int vol_idx) +bool sfold_dev_removeVolume(sfold_Device * dev, int vol_idx) { if (!dev->vol[vol_idx]) { pr_err("Something's wrong, asked to unset volume number %d, already NULL\n", vol_idx); return false; } - - /* Remove sysfs entry */ - if (dev->vol[vol_idx]->kvol) { - sflc_sysfs_removeVolumeFromDevice(dev->kobj, dev->vol[vol_idx]->kvol); - } + /* Update fields */ dev->vol[vol_idx] = NULL; diff --git a/dm-sflc/log/log.h b/dm-sflc/old/log/log.h similarity index 95% rename from dm-sflc/log/log.h rename to dm-sflc/old/log/log.h index 7ef7729..8386d98 100644 --- a/dm-sflc/log/log.h +++ b/dm-sflc/old/log/log.h @@ -25,8 +25,8 @@ * Logging format */ -#ifndef _SFLC_LOG_LOG_H_ -#define _SFLC_LOG_LOG_H_ +#ifndef _SFOLD_LOG_LOG_H_ +#define _SFOLD_LOG_LOG_H_ /***************************************************** * INCLUDE SECTION * @@ -41,4 +41,4 @@ #undef pr_fmt #define pr_fmt(fmt) "[%s] %s in %s:%d: " fmt, KBUILD_MODNAME, __func__, __FILE__, __LINE__ -#endif /* _SFLC_LOG_LOG_H_ */ +#endif /* _SFOLD_LOG_LOG_H_ */ diff --git a/dm-sflc/module.c b/dm-sflc/old/sflc_old.c similarity index 52% rename from dm-sflc/module.c rename to dm-sflc/old/sflc_old.c index 230e60c..4f1a839 100644 --- a/dm-sflc/module.c +++ b/dm-sflc/old/sflc_old.c @@ -28,124 +28,60 @@ #include #include -#include "sysfs/sysfs.h" -#include "target/target.h" -#include "crypto/symkey/symkey.h" -#include "crypto/rand/rand.h" -#include "utils/pools.h" -#include "utils/workqueues.h" -#include "log/log.h" +#include "old/sflc_old.h" +#include "old/crypto/symkey/symkey.h" +#include "old/crypto/rand/rand.h" +#include "old/utils/pools.h" +#include "old/utils/workqueues.h" +#include "old/log/log.h" -/***************************************************** - * MODULE FUNCTION PROTOTYPES * - *****************************************************/ - -static int sflc_init(void); -static void sflc_exit(void); /***************************************************** * MODULE FUNCTIONS DEFINITIONS * *****************************************************/ /* Module entry point, called just once, at module-load time */ -static int sflc_init(void) +int sfold_init(void) { int ret; - ret = sflc_dev_init(); - if (ret) { - pr_err("Could not init device module; error %d\n", ret); - goto err_dev_init; - } - - ret = sflc_rand_init(); + ret = sfold_rand_init(); if (ret) { pr_err("Could not init rand; error %d\n", ret); goto err_rand_init; } - /* Run crypto symkey self test */ - ret = sflc_sk_selftest(); - if (ret) { - pr_err("Error in crypto symkey self test: %d\n", ret); - goto err_sk; - } - /* Run crypto rand self test */ - ret = sflc_rand_selftest(); - if (ret) { - pr_err("Error in crypto rand self test: %d\n", ret); - goto err_rand_selftest; - } - - /* Create the first sysfs entries */ - ret = sflc_sysfs_init(); - if (ret) { - pr_err("Could not init sysfs; error %d\n", ret); - goto err_sysfs; - } - /* Init the memory pools */ - ret = sflc_pools_init(); + ret = sfold_pools_init(); if (ret) { pr_err("Could not init memory pools; error %d\n", ret); goto err_pools; } /* Init the workqueues */ - ret = sflc_queues_init(); + ret = sfold_queues_init(); if (ret) { pr_err("Could not init workqueues; error %d\n", ret); goto err_queues; } - /* Register the DM callbacks */ - ret = dm_register_target(&sflc_target); - if (ret < 0) { - pr_err("dm_register failed: %d", ret); - goto err_dm; - } - - pr_info("Shufflecake loaded"); return 0; -err_dm: - sflc_queues_exit(); err_queues: - sflc_pools_exit(); + sfold_pools_exit(); err_pools: - sflc_sysfs_exit(); -err_sysfs: -err_rand_selftest: -err_sk: - sflc_rand_exit(); + sfold_rand_exit(); err_rand_init: - sflc_dev_exit(); -err_dev_init: return ret; } /* Module exit point, called just once, at module-unload time */ -static void sflc_exit(void) +void sfold_exit(void) { - dm_unregister_target(&sflc_target); - sflc_queues_exit(); - sflc_pools_exit(); - sflc_sysfs_exit(); - sflc_rand_exit(); - sflc_dev_exit(); + sfold_queues_exit(); + sfold_pools_exit(); + sfold_rand_exit(); - pr_info("Shufflecake unloaded"); return; } - -/* Declare them as such to the kernel */ -module_init(sflc_init); -module_exit(sflc_exit); - -/***************************************************** - * MODULE INFO * - *****************************************************/ - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Toninov"); diff --git a/dm-sflc/target/target.h b/dm-sflc/old/sflc_old.h similarity index 69% rename from dm-sflc/target/target.h rename to dm-sflc/old/sflc_old.h index 43eb64e..7f86748 100644 --- a/dm-sflc/target/target.h +++ b/dm-sflc/old/sflc_old.h @@ -20,25 +20,27 @@ * GNU General Public License along with this program. * If not, see . */ - -/* - * Methods of our DM target - */ -#ifndef _SFLC_TARGET_TARGET_H_ -#define _SFLC_TARGET_TARGET_H_ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -/***************************************************** - * PUBLIC VARIABLES DECLARATIONS * - *****************************************************/ - -extern struct target_type sflc_target; +#ifndef _SFOLD_SFOLD_H +#define _SFOLD_SFOLD_H -#endif /* _SFLC_TARGET_TARGET_H_ */ +// For the definition of sfold_Device and its functions +#include "old/device/device.h" +// For the definition of sfold_Volume and its functions +#include "old/volume/volume.h" + + +extern struct target_type sfold_target_type; + + +int sfold_init(void); +void sfold_exit(void); + +int sfold_sysfs_add_device(sfold_Device *dev); +void sfold_sysfs_remove_device(sfold_Device *dev); +int sfold_sysfs_add_volume(sfold_Volume *vol); +void sfold_sysfs_remove_volume(sfold_Volume *vol); + + +#endif /* _SFOLD_SFOLD_H */ diff --git a/dm-sflc/old/sysfs.c b/dm-sflc/old/sysfs.c new file mode 100644 index 0000000..5445d8f --- /dev/null +++ b/dm-sflc/old/sysfs.c @@ -0,0 +1,142 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include + +#include "old/sflc_old.h" +#include "old/log/log.h" + +// Only to import the definitions of structs sflc_volume and sflc_device +#include "sflc.h" + + +/* + *---------------------------- + * Devices + *---------------------------- + */ + +/* Show the total number of slices in a device */ +static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *top_dev; + sfold_Device * dev; + ssize_t ret; + + top_dev = container_of(kobj, struct sflc_device, kobj); + dev = top_dev->sfold_dev; + + /* Write the tot_slices */ + ret = sysfs_emit(buf, "%u\n", dev->tot_slices); + + return ret; +} + +/* Show the number of free slices in a device */ +static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *top_dev; + sfold_Device * dev; + ssize_t ret; + + top_dev = container_of(kobj, struct sflc_device, kobj); + dev = top_dev->sfold_dev; + + /* Write the free_slices */ + if (mutex_lock_interruptible(&dev->slices_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", dev->free_slices); + mutex_unlock(&dev->slices_lock); + + return ret; +} + +static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); +static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); +static struct attribute *sfold_device_attrs[] = { + &tot_slices_kattr.attr, + &free_slices_kattr.attr, + NULL +}; +static const struct attribute_group sfold_device_attr_group = { + .attrs = sfold_device_attrs, +}; + +int sfold_sysfs_add_device(sfold_Device *dev) +{ + return sysfs_create_group(dev->kobj_parent, &sfold_device_attr_group); +} + +void sfold_sysfs_remove_device(sfold_Device *dev) +{ + sysfs_remove_group(dev->kobj_parent, &sfold_device_attr_group); +} + + +/* + *---------------------------- + * Volumes + *---------------------------- + */ + +/* Show the number of mapped slices in a volume */ +static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_volume *top_vol; + sfold_Volume * vol; + ssize_t ret; + + top_vol = container_of(kobj, struct sflc_volume, kobj); + vol = top_vol->sfold_vol; + + /* Write the free_slices */ + if (mutex_lock_interruptible(&vol->fmap_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", vol->mapped_slices); + mutex_unlock(&vol->fmap_lock); + + return ret; +} + +static struct kobj_attribute mapped_slices_kattr = __ATTR_RO(mapped_slices); +static struct attribute *sfold_volume_attrs[] = { + &mapped_slices_kattr.attr, + NULL +}; +static const struct attribute_group sfold_volume_attr_group = { + .attrs = sfold_volume_attrs, +}; + +int sfold_sysfs_add_volume(sfold_Volume *vol) +{ + return sysfs_create_group(vol->kobj_parent, &sfold_volume_attr_group); +} + +void sfold_sysfs_remove_volume(sfold_Volume *vol) +{ + sysfs_remove_group(vol->kobj_parent, &sfold_volume_attr_group); +} diff --git a/dm-sflc/old/target.c b/dm-sflc/old/target.c new file mode 100644 index 0000000..4ceb71d --- /dev/null +++ b/dm-sflc/old/target.c @@ -0,0 +1,150 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* + * Methods of our DM target + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include "old/device/device.h" +#include "old/volume/volume.h" +#include "old/utils/bio.h" +#include "old/utils/string.h" +#include "old/log/log.h" + +// Only to import the definition of struct sflc_volume +#include "sflc.h" + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +static int sfold_tgt_map(struct dm_target *ti, struct bio *bio); +static void sfold_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits); +static int sfold_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, + void *data); + +/***************************************************** + * PUBLIC VARIABLES DEFINITIONS * + *****************************************************/ + +struct target_type sfold_target_type = { + .map = sfold_tgt_map, + .io_hints = sfold_tgt_ioHints, + .iterate_devices = sfold_tgt_iterateDevices, +}; + +/***************************************************** + * PRIVATE FUNCTIONS DEFINITIONS * + *****************************************************/ + + +/* Callback for every bio submitted to our virtual block device */ +static int sfold_tgt_map(struct dm_target *ti, struct bio *bio) +{ + int err; + struct sflc_volume *top_vol = ti->private; + sfold_Volume *vol = top_vol->sfold_vol; + + /* If no data, just quickly remap the sector and the block device (no crypto) */ + /* TODO: this is dangerous for deniability, will need more filtering */ + if (unlikely(!bio_has_data(bio))) { + pr_debug("No-data bio: bio_op = %d", bio_op(bio)); + err = sfold_vol_remapBioFast(vol, bio); + if (err) { + pr_err("Could not remap bio; error %d\n", err); + return DM_MAPIO_KILL; + } + return DM_MAPIO_REMAPPED; + } + + /* At this point, the bio has data. Do a few sanity checks */ + /* TODO: I think we can get rid of all of them */ + + /* Check that it is properly aligned and it doesn't cross vector boundaries */ + if (unlikely(!sfold_bio_isAligned(bio))) { + pr_err("Unaligned bio!\n"); + return DM_MAPIO_KILL; + } + /* If it contains more than one SFLC sector, complain with the DM layer and continue */ + if (unlikely(bio->bi_iter.bi_size > SFOLD_DEV_SECTOR_SIZE)) { + pr_notice("Large bio of size %u\n", bio->bi_iter.bi_size); + dm_accept_partial_bio(bio, SFOLD_DEV_SECTOR_SCALE); + } + /* Check that it contains exactly one SFLC sector */ + if (unlikely(bio->bi_iter.bi_size != SFOLD_DEV_SECTOR_SIZE)) { + pr_err("Wrong length (%u) of bio\n", bio->bi_iter.bi_size); + return DM_MAPIO_KILL; + } + + /* Now it is safe, process it */ + err = sfold_vol_processBio(vol, bio); + if (err) { + pr_err("Could not enqueue bio\n"); + return DM_MAPIO_KILL; + } + + return DM_MAPIO_SUBMITTED; +} + +/* Callback executed to inform the DM about our 4096-byte sector size */ +static void sfold_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits) +{ + struct sflc_volume *top_vol = ti->private; + sfold_Volume *vol = top_vol->sfold_vol; + + pr_info("Called io_hints on volume \"%s\"\n", vol->vol_name); + + limits->logical_block_size = SFOLD_DEV_SECTOR_SIZE; + limits->physical_block_size = SFOLD_DEV_SECTOR_SIZE; + + limits->io_min = SFOLD_DEV_SECTOR_SIZE; + limits->io_opt = SFOLD_DEV_SECTOR_SIZE; + + return; +} + +/* Callback needed for God knows what, otherwise io_hints never gets called */ +static int sfold_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, + void *data) +{ + struct sflc_volume *top_vol = ti->private; + sfold_Volume *vol = top_vol->sfold_vol; + sfold_Device * dev = vol->dev; + + pr_debug("Called iterate_devices on volume \"%s\"\n", vol->vol_name); + + if (!fn) { + return -EINVAL; + } + return fn(ti, vol->dev->bdev, 0, + (dev->dev_header_size + dev->tot_slices * SFOLD_DEV_PHYS_SLICE_SIZE) * SFOLD_DEV_SECTOR_SCALE, + data); +} diff --git a/dm-sflc/utils/bio.c b/dm-sflc/old/utils/bio.c similarity index 88% rename from dm-sflc/utils/bio.c rename to dm-sflc/old/utils/bio.c index 7591c52..6607f89 100644 --- a/dm-sflc/utils/bio.c +++ b/dm-sflc/old/utils/bio.c @@ -29,8 +29,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "bio.h" -#include "log/log.h" +#include "old/utils/bio.h" +#include "old/log/log.h" /***************************************************** @@ -41,7 +41,7 @@ * Checks whether each of the bio's segments contains a whole * number of 4096-byte sectors. */ -bool sflc_bio_isAligned(struct bio * bio) +bool sfold_bio_isAligned(struct bio * bio) { bool ret = true; @@ -51,12 +51,12 @@ bool sflc_bio_isAligned(struct bio * bio) return false; } /* Unlikely because we tell the DM layer about our sector size */ - if (unlikely(bio->bi_iter.bi_size % SFLC_DEV_SECTOR_SIZE != 0)) { + if (unlikely(bio->bi_iter.bi_size % SFOLD_DEV_SECTOR_SIZE != 0)) { pr_err("Abnormal bi_size = %u\n", bio->bi_iter.bi_size); return false; } /* Unlikely because we tell the DM layer about our sector size */ - if (unlikely(bio->bi_iter.bi_sector % SFLC_DEV_SECTOR_SCALE != 0)) { + if (unlikely(bio->bi_iter.bi_sector % SFOLD_DEV_SECTOR_SCALE != 0)) { pr_err("Abnormal bi_sector = %llu\n", bio->bi_iter.bi_sector); return false; } @@ -64,7 +64,7 @@ bool sflc_bio_isAligned(struct bio * bio) struct bio_vec bvl; struct bvec_iter iter; bio_for_each_segment(bvl, bio, iter) { - if ((bvl.bv_len == 0) || (bvl.bv_len % SFLC_DEV_SECTOR_SIZE != 0)) { + if ((bvl.bv_len == 0) || (bvl.bv_len % SFOLD_DEV_SECTOR_SIZE != 0)) { pr_err("Abnormal vector: bv_len = %u\n", bvl.bv_len); ret = false; } diff --git a/dm-sflc/utils/bio.h b/dm-sflc/old/utils/bio.h similarity index 91% rename from dm-sflc/utils/bio.h rename to dm-sflc/old/utils/bio.h index d7bf7b2..51917e6 100644 --- a/dm-sflc/utils/bio.h +++ b/dm-sflc/old/utils/bio.h @@ -25,14 +25,14 @@ * A collection of utility bio functions */ -#ifndef _SFLC_UTILS_BIO_H_ -#define _SFLC_UTILS_BIO_H_ +#ifndef _SFOLD_UTILS_BIO_H_ +#define _SFOLD_UTILS_BIO_H_ /***************************************************** * INCLUDE SECTION * *****************************************************/ -#include "device/device.h" +#include "old/device/device.h" /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * @@ -42,7 +42,7 @@ * Checks whether each of the bio's segments contains a whole * number of 4096-byte sectors. */ -bool sflc_bio_isAligned(struct bio * bio); +bool sfold_bio_isAligned(struct bio * bio); -#endif /* _SFLC_UTILS_BIO_H_ */ +#endif /* _SFOLD_UTILS_BIO_H_ */ diff --git a/dm-sflc/utils/pools.c b/dm-sflc/old/utils/pools.c similarity index 56% rename from dm-sflc/utils/pools.c rename to dm-sflc/old/utils/pools.c index 9e9af0b..5e67faf 100644 --- a/dm-sflc/utils/pools.c +++ b/dm-sflc/old/utils/pools.c @@ -29,100 +29,100 @@ * INCLUDE SECTION * *****************************************************/ -#include "pools.h" -#include "log/log.h" +#include "old/utils/pools.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ /* Pool sizes */ -#define SFLC_POOLS_BIOSET_POOL_SIZE 1024 -#define SFLC_POOLS_PAGE_POOL_SIZE 1024 -#define SFLC_POOLS_WRITE_WORK_POOL_SIZE 1024 -#define SFLC_POOLS_DECRYPT_WORK_POOL_SIZE 1024 +#define SFOLD_POOLS_BIOSET_POOL_SIZE 1024 +#define SFOLD_POOLS_PAGE_POOL_SIZE 1024 +#define SFOLD_POOLS_WRITE_WORK_POOL_SIZE 1024 +#define SFOLD_POOLS_DECRYPT_WORK_POOL_SIZE 1024 /* Slab cache names */ -#define SFLC_POOLS_WRITE_WORK_SLAB_NAME "sflc_write_work_slab" -#define SFLC_POOLS_DECRYPT_WORK_SLAB_NAME "sflc_decrypt_work_slab" -#define SFLC_POOLS_IV_SLAB_NAME "sflc_iv_slab" +#define SFOLD_POOLS_WRITE_WORK_SLAB_NAME "sfold_write_work_slab" +#define SFOLD_POOLS_DECRYPT_WORK_SLAB_NAME "sfold_decrypt_work_slab" +#define SFOLD_POOLS_IV_SLAB_NAME "sfold_iv_slab" /***************************************************** * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -struct bio_set sflc_pools_bioset; -mempool_t * sflc_pools_pagePool; -mempool_t * sflc_pools_writeWorkPool; -mempool_t * sflc_pools_decryptWorkPool; -struct kmem_cache * sflc_pools_ivSlab; +struct bio_set sfold_pools_bioset; +mempool_t * sfold_pools_pagePool; +mempool_t * sfold_pools_writeWorkPool; +mempool_t * sfold_pools_decryptWorkPool; +struct kmem_cache * sfold_pools_ivSlab; /***************************************************** * PRIVATE VARIABLES * *****************************************************/ -static struct kmem_cache * sflc_pools_writeWorkSlab; -static struct kmem_cache * sflc_pools_decryptWorkSlab; +static struct kmem_cache * sfold_pools_writeWorkSlab; +static struct kmem_cache * sfold_pools_decryptWorkSlab; /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sflc_pools_init(void) +int sfold_pools_init(void) { int err; /* Memory pools: bioset */ - err = bioset_init(&sflc_pools_bioset, SFLC_POOLS_BIOSET_POOL_SIZE, 0, BIOSET_NEED_BVECS); + err = bioset_init(&sfold_pools_bioset, SFOLD_POOLS_BIOSET_POOL_SIZE, 0, BIOSET_NEED_BVECS); if (err) { pr_err("Could not init bioset: error %d\n", err); goto err_bioset; } /* Memory pools: page_pool */ - sflc_pools_pagePool = mempool_create_page_pool(SFLC_POOLS_PAGE_POOL_SIZE, 0); - if (!sflc_pools_pagePool) { + sfold_pools_pagePool = mempool_create_page_pool(SFOLD_POOLS_PAGE_POOL_SIZE, 0); + if (!sfold_pools_pagePool) { pr_err("Could not create page pool\n"); err = -ENOMEM; goto err_pagepool; } /* Memory pools: writeWork slab cache */ - sflc_pools_writeWorkSlab = kmem_cache_create(SFLC_POOLS_WRITE_WORK_SLAB_NAME, sizeof(sflc_vol_WriteWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sflc_pools_writeWorkSlab)) { - err = PTR_ERR(sflc_pools_writeWorkSlab); + sfold_pools_writeWorkSlab = kmem_cache_create(SFOLD_POOLS_WRITE_WORK_SLAB_NAME, sizeof(sfold_vol_WriteWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sfold_pools_writeWorkSlab)) { + err = PTR_ERR(sfold_pools_writeWorkSlab); pr_err("Could not create writeWork slab cache; error %d\n", err); goto err_create_write_work_slab; } /* Memory pools: writeWork pool */ - sflc_pools_writeWorkPool = mempool_create_slab_pool(SFLC_POOLS_WRITE_WORK_POOL_SIZE, sflc_pools_writeWorkSlab); - if (!sflc_pools_writeWorkPool) { + sfold_pools_writeWorkPool = mempool_create_slab_pool(SFOLD_POOLS_WRITE_WORK_POOL_SIZE, sfold_pools_writeWorkSlab); + if (!sfold_pools_writeWorkPool) { pr_err("Could not create writeWork pool\n"); err = -ENOMEM; goto err_write_work_pool; } /* Memory pools: decryptWork slab cache */ - sflc_pools_decryptWorkSlab = kmem_cache_create(SFLC_POOLS_DECRYPT_WORK_SLAB_NAME, sizeof(sflc_vol_DecryptWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sflc_pools_decryptWorkSlab)) { - err = PTR_ERR(sflc_pools_decryptWorkSlab); + sfold_pools_decryptWorkSlab = kmem_cache_create(SFOLD_POOLS_DECRYPT_WORK_SLAB_NAME, sizeof(sfold_vol_DecryptWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sfold_pools_decryptWorkSlab)) { + err = PTR_ERR(sfold_pools_decryptWorkSlab); pr_err("Could not create decryptWork slab cache; error %d\n", err); goto err_create_decrypt_work_slab; } /* Memory pools: decryptWork pool */ - sflc_pools_decryptWorkPool = mempool_create_slab_pool(SFLC_POOLS_DECRYPT_WORK_POOL_SIZE, sflc_pools_decryptWorkSlab); - if (!sflc_pools_decryptWorkPool) { + sfold_pools_decryptWorkPool = mempool_create_slab_pool(SFOLD_POOLS_DECRYPT_WORK_POOL_SIZE, sfold_pools_decryptWorkSlab); + if (!sfold_pools_decryptWorkPool) { pr_err("Could not create decryptWork pool\n"); err = -ENOMEM; goto err_decrypt_work_pool; } /* Memory pools: IV slab cache */ - sflc_pools_ivSlab = kmem_cache_create(SFLC_POOLS_IV_SLAB_NAME, sizeof(sflc_dev_IvCacheEntry), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sflc_pools_ivSlab)) { - err = PTR_ERR(sflc_pools_ivSlab); + sfold_pools_ivSlab = kmem_cache_create(SFOLD_POOLS_IV_SLAB_NAME, sizeof(sfold_dev_IvCacheEntry), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sfold_pools_ivSlab)) { + err = PTR_ERR(sfold_pools_ivSlab); pr_err("Could not create IV slab cache; error %d\n", err); goto err_create_iv_slab; } @@ -131,28 +131,28 @@ int sflc_pools_init(void) err_create_iv_slab: - mempool_destroy(sflc_pools_decryptWorkPool); + mempool_destroy(sfold_pools_decryptWorkPool); err_decrypt_work_pool: - kmem_cache_destroy(sflc_pools_decryptWorkSlab); + kmem_cache_destroy(sfold_pools_decryptWorkSlab); err_create_decrypt_work_slab: - mempool_destroy(sflc_pools_writeWorkPool); + mempool_destroy(sfold_pools_writeWorkPool); err_write_work_pool: - kmem_cache_destroy(sflc_pools_writeWorkSlab); + kmem_cache_destroy(sfold_pools_writeWorkSlab); err_create_write_work_slab: - mempool_destroy(sflc_pools_pagePool); + mempool_destroy(sfold_pools_pagePool); err_pagepool: - bioset_exit(&sflc_pools_bioset); + bioset_exit(&sfold_pools_bioset); err_bioset: return err; } -void sflc_pools_exit(void) +void sfold_pools_exit(void) { - kmem_cache_destroy(sflc_pools_ivSlab); - mempool_destroy(sflc_pools_decryptWorkPool); - kmem_cache_destroy(sflc_pools_decryptWorkSlab); - mempool_destroy(sflc_pools_writeWorkPool); - kmem_cache_destroy(sflc_pools_writeWorkSlab); - mempool_destroy(sflc_pools_pagePool); - bioset_exit(&sflc_pools_bioset); + kmem_cache_destroy(sfold_pools_ivSlab); + mempool_destroy(sfold_pools_decryptWorkPool); + kmem_cache_destroy(sfold_pools_decryptWorkSlab); + mempool_destroy(sfold_pools_writeWorkPool); + kmem_cache_destroy(sfold_pools_writeWorkSlab); + mempool_destroy(sfold_pools_pagePool); + bioset_exit(&sfold_pools_bioset); } diff --git a/dm-sflc/utils/pools.h b/dm-sflc/old/utils/pools.h similarity index 81% rename from dm-sflc/utils/pools.h rename to dm-sflc/old/utils/pools.h index e0a1261..b87d6c6 100644 --- a/dm-sflc/utils/pools.h +++ b/dm-sflc/old/utils/pools.h @@ -25,31 +25,31 @@ * A set of memory pools */ -#ifndef _SFLC_UTILS_POOLS_H_ -#define _SFLC_UTILS_POOLS_H_ +#ifndef _SFOLD_UTILS_POOLS_H_ +#define _SFOLD_UTILS_POOLS_H_ /***************************************************** * INCLUDE SECTION * *****************************************************/ -#include "device/device.h" +#include "old/device/device.h" /***************************************************** * PUBLIC VARIABLES DECLARATIONS * *****************************************************/ -extern struct bio_set sflc_pools_bioset; -extern mempool_t * sflc_pools_pagePool; -extern mempool_t * sflc_pools_writeWorkPool; -extern mempool_t * sflc_pools_decryptWorkPool; -extern struct kmem_cache * sflc_pools_ivSlab; +extern struct bio_set sfold_pools_bioset; +extern mempool_t * sfold_pools_pagePool; +extern mempool_t * sfold_pools_writeWorkPool; +extern mempool_t * sfold_pools_decryptWorkPool; +extern struct kmem_cache * sfold_pools_ivSlab; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sflc_pools_init(void); -void sflc_pools_exit(void); +int sfold_pools_init(void); +void sfold_pools_exit(void); -#endif /* _SFLC_UTILS_POOLS_H_ */ +#endif /* _SFOLD_UTILS_POOLS_H_ */ diff --git a/dm-sflc/utils/string.c b/dm-sflc/old/utils/string.c similarity index 92% rename from dm-sflc/utils/string.c rename to dm-sflc/old/utils/string.c index 0199dae..8d533c6 100644 --- a/dm-sflc/utils/string.c +++ b/dm-sflc/old/utils/string.c @@ -31,15 +31,15 @@ #include -#include "string.h" -#include "log/log.h" +#include "old/utils/string.h" +#include "old/log/log.h" /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sflc_str_hexDecode(char * hex, u8 * bin) +int sfold_str_hexDecode(char * hex, u8 * bin) { char buf[3]; unsigned len; @@ -64,7 +64,7 @@ int sflc_str_hexDecode(char * hex, u8 * bin) } -void sflc_str_replaceAll(char * str, char old, char new) +void sfold_str_replaceAll(char * str, char old, char new) { int i; diff --git a/dm-sflc/utils/string.h b/dm-sflc/old/utils/string.h similarity index 88% rename from dm-sflc/utils/string.h rename to dm-sflc/old/utils/string.h index 8ac29df..98e0565 100644 --- a/dm-sflc/utils/string.h +++ b/dm-sflc/old/utils/string.h @@ -25,8 +25,8 @@ * A collection of utility string functions */ -#ifndef _SFLC_UTILS_STRING_H_ -#define _SFLC_UTILS_STRING_H_ +#ifndef _SFOLD_UTILS_STRING_H_ +#define _SFOLD_UTILS_STRING_H_ /***************************************************** * INCLUDE SECTION * @@ -38,8 +38,8 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sflc_str_hexDecode(char * hex, u8 * bin); -void sflc_str_replaceAll(char * str, char old, char new); +int sfold_str_hexDecode(char * hex, u8 * bin); +void sfold_str_replaceAll(char * str, char old, char new); -#endif /* _SFLC_UTILS_STRING_H_ */ +#endif /* _SFOLD_UTILS_STRING_H_ */ diff --git a/dm-sflc/utils/vector.c b/dm-sflc/old/utils/vector.c similarity index 92% rename from dm-sflc/utils/vector.c rename to dm-sflc/old/utils/vector.c index c18691b..4c73814 100644 --- a/dm-sflc/utils/vector.c +++ b/dm-sflc/old/utils/vector.c @@ -29,9 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "vector.h" -#include "crypto/rand/rand.h" -#include "log/log.h" +#include "old/utils/vector.h" +#include "old/crypto/rand/rand.h" +#include "old/log/log.h" /***************************************************** @@ -39,13 +39,13 @@ *****************************************************/ /* Shuffle a vector of u32's with the Fisher-Yates algorithm */ -int sflc_vec_u32shuffle(u32 *v, u32 len) +int sfold_vec_u32shuffle(u32 *v, u32 len) { u32 i; for (i = len-1; i >= 1; i--) { /* Sample a random index from 0 to i (inclusive) */ - s32 j = sflc_rand_uniform(i+1); + s32 j = sfold_rand_uniform(i+1); if (j < 0) { pr_err("Could not sample j; error %d", j); return j; diff --git a/dm-sflc/utils/vector.h b/dm-sflc/old/utils/vector.h similarity index 92% rename from dm-sflc/utils/vector.h rename to dm-sflc/old/utils/vector.h index 4edc7ff..ae75ca0 100644 --- a/dm-sflc/utils/vector.h +++ b/dm-sflc/old/utils/vector.h @@ -25,8 +25,8 @@ * A collection of utility vector functions */ -#ifndef _SFLC_UTILS_VECTOR_H_ -#define _SFLC_UTILS_VECTOR_H_ +#ifndef _SFOLD_UTILS_VECTOR_H_ +#define _SFOLD_UTILS_VECTOR_H_ /***************************************************** * INCLUDE SECTION * @@ -39,7 +39,7 @@ *****************************************************/ /* Shuffle a vector of u32's */ -int sflc_vec_u32shuffle(u32 *v, u32 len); +int sfold_vec_u32shuffle(u32 *v, u32 len); -#endif /* _SFLC_UTILS_VECTOR_H_ */ +#endif /* _SFOLD_UTILS_VECTOR_H_ */ diff --git a/dm-sflc/utils/workqueues.c b/dm-sflc/old/utils/workqueues.c similarity index 76% rename from dm-sflc/utils/workqueues.c rename to dm-sflc/old/utils/workqueues.c index 15697b1..942b765 100644 --- a/dm-sflc/utils/workqueues.c +++ b/dm-sflc/old/utils/workqueues.c @@ -28,42 +28,42 @@ * INCLUDE SECTION * *****************************************************/ -#include "workqueues.h" -#include "log/log.h" +#include "old/utils/workqueues.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFLC_QUEUES_WRITE_WQ_NAME "sflc_write_workqueue" -#define SFLC_QUEUES_DECRYPT_WQ_NAME "sflc_decrypt_workqueue" +#define SFOLD_QUEUES_WRITE_WQ_NAME "sfold_write_workqueue" +#define SFOLD_QUEUES_DECRYPT_WQ_NAME "sfold_decrypt_workqueue" /***************************************************** * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -struct workqueue_struct * sflc_queues_writeQueue; -struct workqueue_struct * sflc_queues_decryptQueue; +struct workqueue_struct * sfold_queues_writeQueue; +struct workqueue_struct * sfold_queues_decryptQueue; /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sflc_queues_init(void) +int sfold_queues_init(void) { int err; /* Write workqueue */ - sflc_queues_writeQueue = create_workqueue(SFLC_QUEUES_WRITE_WQ_NAME); - if (!sflc_queues_writeQueue) { + sfold_queues_writeQueue = create_workqueue(SFOLD_QUEUES_WRITE_WQ_NAME); + if (!sfold_queues_writeQueue) { pr_err("Could not create write workqueue\n"); err = -ENOMEM; goto err_write_queue; } /* Decrypt workqueue */ - sflc_queues_decryptQueue = create_workqueue(SFLC_QUEUES_DECRYPT_WQ_NAME); - if (!sflc_queues_decryptQueue) { + sfold_queues_decryptQueue = create_workqueue(SFOLD_QUEUES_DECRYPT_WQ_NAME); + if (!sfold_queues_decryptQueue) { pr_err("Could not create decrypt workqueue\n"); err = -ENOMEM; goto err_decrypt_queue; @@ -73,13 +73,13 @@ int sflc_queues_init(void) err_decrypt_queue: - destroy_workqueue(sflc_queues_writeQueue); + destroy_workqueue(sfold_queues_writeQueue); err_write_queue: return err; } -void sflc_queues_exit(void) +void sfold_queues_exit(void) { - destroy_workqueue(sflc_queues_decryptQueue); - destroy_workqueue(sflc_queues_writeQueue); + destroy_workqueue(sfold_queues_decryptQueue); + destroy_workqueue(sfold_queues_writeQueue); } diff --git a/dm-sflc/utils/workqueues.h b/dm-sflc/old/utils/workqueues.h similarity index 86% rename from dm-sflc/utils/workqueues.h rename to dm-sflc/old/utils/workqueues.h index 38fb031..3026a6d 100644 --- a/dm-sflc/utils/workqueues.h +++ b/dm-sflc/old/utils/workqueues.h @@ -25,8 +25,8 @@ * A set of workqueues */ -#ifndef _SFLC_UTILS_QUEUES_H_ -#define _SFLC_UTILS_QUEUES_H_ +#ifndef _SFOLD_UTILS_QUEUES_H_ +#define _SFOLD_UTILS_QUEUES_H_ /***************************************************** * INCLUDE SECTION * @@ -38,15 +38,15 @@ * PUBLIC VARIABLES DECLARATIONS * *****************************************************/ -extern struct workqueue_struct * sflc_queues_writeQueue; -extern struct workqueue_struct * sflc_queues_decryptQueue; +extern struct workqueue_struct * sfold_queues_writeQueue; +extern struct workqueue_struct * sfold_queues_decryptQueue; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sflc_queues_init(void); -void sflc_queues_exit(void); +int sfold_queues_init(void); +void sfold_queues_exit(void); -#endif /* _SFLC_UTILS_QUEUES_H_ */ +#endif /* _SFOLD_UTILS_QUEUES_H_ */ diff --git a/dm-sflc/volume/fmap.c b/dm-sflc/old/volume/fmap.c similarity index 81% rename from dm-sflc/volume/fmap.c rename to dm-sflc/old/volume/fmap.c index 980173a..63b837b 100644 --- a/dm-sflc/volume/fmap.c +++ b/dm-sflc/old/volume/fmap.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "volume.h" -#include "crypto/rand/rand.h" -#include "utils/pools.h" -#include "log/log.h" +#include "old/volume/volume.h" +#include "old/crypto/rand/rand.h" +#include "old/utils/pools.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * @@ -42,7 +42,7 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op); +static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * @@ -50,28 +50,28 @@ static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op); /* Maps a logical 512-byte sector to a physical 512-byte sector. Returns < 0 if error. * Specifically, if op == READ, and the logical slice is unmapped, -ENXIO is returned. */ -s64 sflc_vol_remapSector(sflc_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out) +s64 sfold_vol_remapSector(sfold_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out) { u32 lsi; u32 off_in_slice; s32 psi; sector_t phys_sector; - sflc_Device *dev = vol->dev; + sfold_Device *dev = vol->dev; /* Start by scaling down to a Shufflecake sector */ - log_sector /= SFLC_DEV_SECTOR_SCALE; + log_sector /= SFOLD_DEV_SECTOR_SCALE; /* Get the logical slice index it belongs to */ - lsi = log_sector / SFLC_VOL_LOG_SLICE_SIZE; + lsi = log_sector / SFOLD_VOL_LOG_SLICE_SIZE; /* Get which block it is within the slice */ - off_in_slice = log_sector % SFLC_VOL_LOG_SLICE_SIZE; + off_in_slice = log_sector % SFOLD_VOL_LOG_SLICE_SIZE; /* Output the off_in_slice */ if (off_in_slice_out) { *off_in_slice_out = off_in_slice; } /* Map it to a physical slice */ - psi = sflc_vol_mapSlice(vol, lsi, op); + psi = sfold_vol_mapSlice(vol, lsi, op); /* -ENXIO is a special case */ if (psi == -ENXIO) { pr_debug("mapSlice returned -ENXIO: stupid READ\n"); @@ -88,20 +88,20 @@ s64 sflc_vol_remapSector(sflc_Volume * vol, sector_t log_sector, int op, u32 * p } /* Get the physical sector (the first of every slice contains the IVs) */ - phys_sector = ((sector_t)psi * SFLC_DEV_PHYS_SLICE_SIZE) + 1 + off_in_slice; + phys_sector = ((sector_t)psi * SFOLD_DEV_PHYS_SLICE_SIZE) + 1 + off_in_slice; /* Add the device header */ phys_sector += dev->dev_header_size; /* Scale it back up to a kernel sector */ - phys_sector *= SFLC_DEV_SECTOR_SCALE; + phys_sector *= SFOLD_DEV_SECTOR_SCALE; return phys_sector; } /* Loads (and decrypts) the position map from the volume's header */ -int sflc_vol_loadFmap(sflc_Volume * vol) +int sfold_vol_loadFmap(sfold_Volume * vol) { - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; sector_t sector; struct page * iv_page; u8 * iv_ptr; @@ -111,12 +111,12 @@ int sflc_vol_loadFmap(sflc_Volume * vol) int err; /* Allocate pages */ - iv_page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!iv_page) { pr_err("Could not allocate IV page\n"); return -ENOMEM; } - data_page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + data_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!data_page) { pr_err("Could not allocate data page\n"); return -ENOMEM; @@ -145,7 +145,7 @@ int sflc_vol_loadFmap(sflc_Volume * vol) int i; for (i = 0; i < dev->vol_header_nr_iv_blocks && lsi < dev->tot_slices; i++) { /* Load the IV block */ - err = sflc_dev_rwSector(dev, iv_page, sector, READ); + err = sfold_dev_rwSector(dev, iv_page, sector, READ); if (err) { pr_err("Could not read IV block i=%d at sector %llu; error %d\n", i, sector, err); goto out; @@ -154,9 +154,9 @@ int sflc_vol_loadFmap(sflc_Volume * vol) /* Loop over the 256 data blocks */ int j; - for (j = 0; j < SFLC_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { + for (j = 0; j < SFOLD_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { /* Load the data block */ - err = sflc_dev_rwSector(dev, data_page, sector, READ); + err = sfold_dev_rwSector(dev, data_page, sector, READ); if (err) { pr_err("Could not read data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -164,7 +164,7 @@ int sflc_vol_loadFmap(sflc_Volume * vol) sector += 1; /* Decrypt it in place */ - err = sflc_sk_decrypt(vol->skctx, data_ptr, data_ptr, SFLC_DEV_SECTOR_SIZE, (iv_ptr + j*SFLC_SK_IV_LEN)); + err = sfold_sk_decrypt(vol->skctx, data_ptr, data_ptr, SFOLD_DEV_SECTOR_SIZE, (iv_ptr + j*SFOLD_SK_IV_LEN)); if (err) { pr_err("Could not decrypt data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -172,7 +172,7 @@ int sflc_vol_loadFmap(sflc_Volume * vol) /* Loop over the 1024 fmap entries in this data block */ int k; - for (k = 0; k < SFLC_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { + for (k = 0; k < SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { /* An entry is just a single big-endian PSI, the LSI is implicitly the index of this entry */ __be32 * be_psi = (void *) (data_ptr + (k * sizeof(__be32))); @@ -181,8 +181,8 @@ int sflc_vol_loadFmap(sflc_Volume * vol) /* Add mapping to the volume's fmap */ vol->fmap[lsi] = psi; /* Also add it to the device's rmap and to the count, if LSI is actually mapped */ - if (psi != SFLC_VOL_FMAP_INVALID_PSI) { - sflc_dev_markPsiTaken(dev, psi, vol->vol_idx); + if (psi != SFOLD_VOL_FMAP_INVALID_PSI) { + sfold_dev_markPsiTaken(dev, psi, vol->vol_idx); vol->mapped_slices += 1; } @@ -202,16 +202,16 @@ out: kunmap(iv_page); kunmap(data_page); /* Free them */ - mempool_free(iv_page, sflc_pools_pagePool); - mempool_free(data_page, sflc_pools_pagePool); + mempool_free(iv_page, sfold_pools_pagePool); + mempool_free(data_page, sfold_pools_pagePool); return err; } /* Stores (and encrypts) the position map to the volume's header */ -int sflc_vol_storeFmap(sflc_Volume * vol) +int sfold_vol_storeFmap(sfold_Volume * vol) { - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; sector_t sector; struct page * iv_page; u8 * iv_ptr; @@ -221,12 +221,12 @@ int sflc_vol_storeFmap(sflc_Volume * vol) int err; /* Allocate pages */ - iv_page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!iv_page) { pr_err("Could not allocate IV page\n"); return -ENOMEM; } - data_page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + data_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!data_page) { pr_err("Could not allocate data page\n"); return -ENOMEM; @@ -255,13 +255,13 @@ int sflc_vol_storeFmap(sflc_Volume * vol) int i; for (i = 0; i < dev->vol_header_nr_iv_blocks && lsi < dev->tot_slices; i++) { /* Fill the IV block with random bytes */ - err = sflc_rand_getBytes(iv_ptr, SFLC_DEV_SECTOR_SIZE); + err = sfold_rand_getBytes(iv_ptr, SFOLD_DEV_SECTOR_SIZE); if (err) { pr_err("Could not sample random IV for block i=%d at sector %llu; error %d\n", i, sector, err); goto out; } /* Store it on the disk (before it gets changed by the encryption) */ - err = sflc_dev_rwSector(dev, iv_page, sector, WRITE); + err = sfold_dev_rwSector(dev, iv_page, sector, WRITE); if (err) { pr_err("Could not read IV block i=%d at sector %llu; error %d\n", i, sector, err); goto out; @@ -270,10 +270,10 @@ int sflc_vol_storeFmap(sflc_Volume * vol) /* Loop over the 256 data blocks */ int j; - for (j = 0; j < SFLC_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { + for (j = 0; j < SFOLD_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { /* Loop over the 1024 fmap entries that fit in this data block */ int k; - for (k = 0; k < SFLC_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { + for (k = 0; k < SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { /* Get the PSI for the current LSI */ u32 psi = vol->fmap[lsi]; /* Write it into the block as big-endian */ @@ -285,14 +285,14 @@ int sflc_vol_storeFmap(sflc_Volume * vol) } /* Encrypt it in place */ - err = sflc_sk_encrypt(vol->skctx, data_ptr, data_ptr, SFLC_DEV_SECTOR_SIZE, (iv_ptr + j*SFLC_SK_IV_LEN)); + err = sfold_sk_encrypt(vol->skctx, data_ptr, data_ptr, SFOLD_DEV_SECTOR_SIZE, (iv_ptr + j*SFOLD_SK_IV_LEN)); if (err) { pr_err("Could not encrypt data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; } /* Store the data block */ - err = sflc_dev_rwSector(dev, data_page, sector, WRITE); + err = sfold_dev_rwSector(dev, data_page, sector, WRITE); if (err) { pr_err("Could not write data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -311,8 +311,8 @@ out: kunmap(iv_page); kunmap(data_page); /* Free them */ - mempool_free(iv_page, sflc_pools_pagePool); - mempool_free(data_page, sflc_pools_pagePool); + mempool_free(iv_page, sfold_pools_pagePool); + mempool_free(data_page, sfold_pools_pagePool); return err; } @@ -321,10 +321,10 @@ out: * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op) +static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op) { s32 psi; - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; /* Lock the volume's forward map */ if (mutex_lock_interruptible(&vol->fmap_lock)) { @@ -333,7 +333,7 @@ static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op) } /* If slice is already mapped, just return the mapping */ - if (vol->fmap[lsi] != SFLC_VOL_FMAP_INVALID_PSI) { + if (vol->fmap[lsi] != SFOLD_VOL_FMAP_INVALID_PSI) { mutex_unlock(&vol->fmap_lock); return vol->fmap[lsi]; } @@ -354,7 +354,7 @@ static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op) } /* Get a free physical slice */ - psi = sflc_dev_getNextRandomFreePsi(dev); + psi = sfold_dev_getNextRandomFreePsi(dev); if (psi < 0) { pr_err("Could not get a random free physical slice; error %d\n", psi); mutex_unlock(&dev->slices_lock); @@ -366,7 +366,7 @@ static s32 sflc_vol_mapSlice(sflc_Volume * vol, u32 lsi, int op) vol->fmap[lsi] = psi; vol->mapped_slices += 1; /* And in the device's rmap */ - sflc_dev_markPsiTaken(dev, psi, vol->vol_idx); + sfold_dev_markPsiTaken(dev, psi, vol->vol_idx); /* Unlock both maps */ mutex_unlock(&dev->slices_lock); diff --git a/dm-sflc/volume/io.c b/dm-sflc/old/volume/io.c similarity index 83% rename from dm-sflc/volume/io.c rename to dm-sflc/old/volume/io.c index 71ecc9c..6296834 100644 --- a/dm-sflc/volume/io.c +++ b/dm-sflc/old/volume/io.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "volume.h" -#include "utils/pools.h" -#include "utils/workqueues.h" -#include "log/log.h" +#include "old/volume/volume.h" +#include "old/utils/pools.h" +#include "old/utils/workqueues.h" +#include "old/log/log.h" /***************************************************** * CONSTANTS * @@ -47,7 +47,7 @@ *****************************************************/ /* Remaps the underlying block device and the sector number */ -int sflc_vol_remapBioFast(sflc_Volume * vol, struct bio * bio) +int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio) { s64 phys_sector; int err; @@ -56,7 +56,7 @@ int sflc_vol_remapBioFast(sflc_Volume * vol, struct bio * bio) bio_set_dev(bio, vol->dev->bdev->bdev); /* Remap the starting sector (we don't care about PSI and off_in_slice). Also no slice allocation */ - phys_sector = sflc_vol_remapSector(vol, bio->bi_iter.bi_sector, READ, NULL, NULL); + phys_sector = sfold_vol_remapSector(vol, bio->bi_iter.bi_sector, READ, NULL, NULL); if (phys_sector < 0) { err = (int) phys_sector; pr_err("Could not remap sector; error %d\n", err); @@ -68,18 +68,18 @@ int sflc_vol_remapBioFast(sflc_Volume * vol, struct bio * bio) } /* Submits the bio to the device's workqueue */ -int sflc_vol_processBio(sflc_Volume * vol, struct bio * bio) +int sfold_vol_processBio(sfold_Volume * vol, struct bio * bio) { - sflc_vol_WriteWork * write_work; + sfold_vol_WriteWork * write_work; /* If it is a READ, no need to pass it through a workqueue */ if (bio_data_dir(bio) == READ) { - sflc_vol_doRead(vol, bio); + sfold_vol_doRead(vol, bio); return 0; } /* Allocate writeWork structure */ - write_work = mempool_alloc(sflc_pools_writeWorkPool, GFP_NOIO); + write_work = mempool_alloc(sfold_pools_writeWorkPool, GFP_NOIO); if (!write_work) { pr_err("Failed allocation of work structure\n"); return -ENOMEM; @@ -88,10 +88,10 @@ int sflc_vol_processBio(sflc_Volume * vol, struct bio * bio) /* Set fields */ write_work->vol = vol; write_work->orig_bio = bio; - INIT_WORK(&write_work->work, sflc_vol_doWrite); + INIT_WORK(&write_work->work, sfold_vol_doWrite); /* Enqueue */ - queue_work(sflc_queues_writeQueue, &write_work->work); + queue_work(sfold_queues_writeQueue, &write_work->work); return 0; } diff --git a/dm-sflc/volume/read.c b/dm-sflc/old/volume/read.c similarity index 72% rename from dm-sflc/volume/read.c rename to dm-sflc/old/volume/read.c index 2d64784..30e2927 100644 --- a/dm-sflc/volume/read.c +++ b/dm-sflc/old/volume/read.c @@ -32,10 +32,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "volume.h" -#include "utils/pools.h" -#include "utils/workqueues.h" -#include "log/log.h" +#include "old/volume/volume.h" +#include "old/utils/pools.h" +#include "old/utils/workqueues.h" +#include "old/log/log.h" #include @@ -47,28 +47,28 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static void sflc_vol_fillBioWithZeros(struct bio * orig_bio); -static void sflc_vol_readEndIo(struct bio * phys_bio); -static void sflc_vol_readEndIoBottomHalf(struct work_struct * work); -static int sflc_vol_decryptBio(sflc_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice); -static int sflc_vol_fetchIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); +static void sfold_vol_fillBioWithZeros(struct bio * orig_bio); +static void sfold_vol_readEndIo(struct bio * phys_bio); +static void sfold_vol_readEndIoBottomHalf(struct work_struct * work); +static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice); +static int sfold_vol_fetchIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -/* Executed in context from sflc_tgt_map() */ -void sflc_vol_doRead(sflc_Volume * vol, struct bio * bio) +/* Executed in context from sfold_tgt_map() */ +void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio) { - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; struct bio * orig_bio = bio; struct bio * phys_bio; s64 phys_sector; - sflc_vol_DecryptWork * dec_work; + sfold_vol_DecryptWork * dec_work; blk_status_t status; /* Allocate decryptWork structure */ - dec_work = mempool_alloc(sflc_pools_decryptWorkPool, GFP_NOIO); + dec_work = mempool_alloc(sfold_pools_decryptWorkPool, GFP_NOIO); if (!dec_work) { pr_err("Could not allocate decryptWork structure\n"); status = BLK_STS_IOERR; @@ -83,7 +83,7 @@ void sflc_vol_doRead(sflc_Volume * vol, struct bio * bio) we can decrypt in place. */ /* Shallow copy */ - phys_bio = bio_alloc_clone(dev->bdev->bdev, orig_bio, GFP_NOIO, &sflc_pools_bioset); + phys_bio = bio_alloc_clone(dev->bdev->bdev, orig_bio, GFP_NOIO, &sfold_pools_bioset); if (!phys_bio) { pr_err("Could not clone original bio\n"); status = BLK_STS_IOERR; @@ -91,11 +91,11 @@ void sflc_vol_doRead(sflc_Volume * vol, struct bio * bio) } /* Remap sector */ - phys_sector = sflc_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, READ, &dec_work->psi, &dec_work->off_in_slice); + phys_sector = sfold_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, READ, &dec_work->psi, &dec_work->off_in_slice); /* If -ENXIO, special case: stupid READ */ if (phys_sector == -ENXIO) { pr_debug("Stupid READ. Returning all zeros\n"); - sflc_vol_fillBioWithZeros(orig_bio); + sfold_vol_fillBioWithZeros(orig_bio); status = BLK_STS_OK; goto err_stupid_read; } @@ -113,7 +113,7 @@ void sflc_vol_doRead(sflc_Volume * vol, struct bio * bio) dec_work->orig_bio = orig_bio; dec_work->phys_bio = phys_bio; /* Set fields for the endio */ - phys_bio->bi_end_io = sflc_vol_readEndIo; + phys_bio->bi_end_io = sfold_vol_readEndIo; phys_bio->bi_private = dec_work; /* Only submit the physical bio */ @@ -127,7 +127,7 @@ err_stupid_read: bio_put(phys_bio); err_clone_orig_bio: bio_put(orig_bio); - mempool_free(dec_work, sflc_pools_decryptWorkPool); + mempool_free(dec_work, sfold_pools_decryptWorkPool); err_alloc_dec_work: orig_bio->bi_status = status; @@ -139,42 +139,42 @@ err_alloc_dec_work: * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static void sflc_vol_fillBioWithZeros(struct bio * orig_bio) +static void sfold_vol_fillBioWithZeros(struct bio * orig_bio) { struct bio_vec bvl = bio_iovec(orig_bio); void * sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - memset(sector_ptr, 0, SFLC_DEV_SECTOR_SIZE); - bio_advance(orig_bio, SFLC_DEV_SECTOR_SIZE); + memset(sector_ptr, 0, SFOLD_DEV_SECTOR_SIZE); + bio_advance(orig_bio, SFOLD_DEV_SECTOR_SIZE); kunmap(bvl.bv_page); return; } /* Pushes all the decryption work to a workqueue bottom half */ -static void sflc_vol_readEndIo(struct bio * phys_bio) +static void sfold_vol_readEndIo(struct bio * phys_bio) { - sflc_vol_DecryptWork * dec_work = phys_bio->bi_private; + sfold_vol_DecryptWork * dec_work = phys_bio->bi_private; /* Init work structure */ - INIT_WORK(&dec_work->work, sflc_vol_readEndIoBottomHalf); + INIT_WORK(&dec_work->work, sfold_vol_readEndIoBottomHalf); /* Enqueue it */ - queue_work(sflc_queues_decryptQueue, &dec_work->work); + queue_work(sfold_queues_decryptQueue, &dec_work->work); return; } -static void sflc_vol_readEndIoBottomHalf(struct work_struct * work) +static void sfold_vol_readEndIoBottomHalf(struct work_struct * work) { - sflc_vol_DecryptWork * dec_work = container_of(work, sflc_vol_DecryptWork, work); - sflc_Volume * vol = dec_work->vol; + sfold_vol_DecryptWork * dec_work = container_of(work, sfold_vol_DecryptWork, work); + sfold_Volume * vol = dec_work->vol; struct bio * orig_bio = dec_work->orig_bio; struct bio * phys_bio = dec_work->phys_bio; blk_status_t status = phys_bio->bi_status; int err; /* Decrypt the physical bio and advance the original bio */ - err = sflc_vol_decryptBio(vol, orig_bio, dec_work->psi, dec_work->off_in_slice); + err = sfold_vol_decryptBio(vol, orig_bio, dec_work->psi, dec_work->off_in_slice); if (err) { pr_err("Could not decrypt bio; error %d\n", err); status = BLK_STS_IOERR; @@ -189,19 +189,19 @@ static void sflc_vol_readEndIoBottomHalf(struct work_struct * work) /* Free the physical bio */ bio_put(phys_bio); /* Free the work item */ - mempool_free(dec_work, sflc_pools_decryptWorkPool); + mempool_free(dec_work, sfold_pools_decryptWorkPool); return; } /* Decrypts the content of the physical bio, and at the same time it advances the original bio */ -static int sflc_vol_decryptBio(sflc_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice) +static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice) { - u8 iv[SFLC_SK_IV_LEN]; + u8 iv[SFOLD_SK_IV_LEN]; int err; /* Fetch IV */ - err = sflc_vol_fetchIv(vol, iv, psi, off_in_slice); + err = sfold_vol_fetchIv(vol, iv, psi, off_in_slice); if (err) { pr_err("Could not fetch IV; error %d\n", err); return err; @@ -210,7 +210,7 @@ static int sflc_vol_decryptBio(sflc_Volume * vol, struct bio * orig_bio, u32 psi /* Decrypt sector in place */ struct bio_vec bvl = bio_iovec(orig_bio); void * sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - err = sflc_sk_decrypt(vol->skctx, sector_ptr, sector_ptr, SFLC_DEV_SECTOR_SIZE, iv); + err = sfold_sk_decrypt(vol->skctx, sector_ptr, sector_ptr, SFOLD_DEV_SECTOR_SIZE, iv); kunmap(bvl.bv_page); if (err) { pr_err("Error while decrypting sector: %d\n", err); @@ -218,19 +218,19 @@ static int sflc_vol_decryptBio(sflc_Volume * vol, struct bio * orig_bio, u32 psi } /* Advance original bio by one sector */ - bio_advance(orig_bio, SFLC_DEV_SECTOR_SIZE); + bio_advance(orig_bio, SFOLD_DEV_SECTOR_SIZE); return 0; } -static int sflc_vol_fetchIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) +static int sfold_vol_fetchIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) { - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; u8 * iv_block; int err; /* Acquire a reference to the whole relevant IV block */ - iv_block = sflc_dev_getIvBlockRef(dev, psi, READ); + iv_block = sfold_dev_getIvBlockRef(dev, psi, READ); if (IS_ERR(iv_block)) { err = PTR_ERR(iv_block); pr_err("Could not acquire reference to IV block; error %ld\n", PTR_ERR(iv_block)); @@ -238,10 +238,10 @@ static int sflc_vol_fetchIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slic } /* Copy the relevant portion */ - memcpy(iv, iv_block + (off_in_slice * SFLC_SK_IV_LEN), SFLC_SK_IV_LEN); + memcpy(iv, iv_block + (off_in_slice * SFOLD_SK_IV_LEN), SFOLD_SK_IV_LEN); /* Release reference to the IV block */ - err = sflc_dev_putIvBlockRef(dev, psi); + err = sfold_dev_putIvBlockRef(dev, psi); if (err) { pr_err("Could not release reference to IV block; error %d\n", err); goto err_put_iv_block_ref; diff --git a/dm-sflc/volume/volume.c b/dm-sflc/old/volume/volume.c similarity index 57% rename from dm-sflc/volume/volume.c rename to dm-sflc/old/volume/volume.c index ec4dedc..e8b97b6 100644 --- a/dm-sflc/volume/volume.c +++ b/dm-sflc/old/volume/volume.c @@ -29,8 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "volume.h" -#include "log/log.h" +#include "old/volume/volume.h" +#include "old/utils/string.h" +#include "old/log/log.h" #include @@ -39,35 +40,64 @@ * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -/* Creates volume and adds it to the device. Returns an ERR_PTR() if unsuccessful */ -sflc_Volume * sflc_vol_create(struct dm_target *ti, sflc_Device *dev, int vol_idx, u8 * enckey) +/** + * Creates volume and adds it to the device. Returns an ERR_PTR() if unsuccessful + * Arguments: + * argv[0]: Shufflecake mode: legacy/lite + * argv[1]: Shufflecake-unique device ID + * argv[2]: path to underlying physical device + * argv[3]: volume index within the device + * argv[4]: number of 1 MB slices in the underlying device + * argv[5]: 32-byte encryption key (hex-encoded) + */ +sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, + int argc, char **argv, struct kobject *kobj) { - sflc_Volume * vol; + sfold_Volume * vol; + int vol_idx; + u8 enckey[SFOLD_SK_KEY_LEN]; int err; - pr_debug("Called to create sflc_Volume #%d on Device %s\n", vol_idx, dev->bdev_path); - /* Allocate volume */ - vol = kzalloc(sizeof(sflc_Volume), GFP_KERNEL); + vol = kzalloc(sizeof(sfold_Volume), GFP_KERNEL); if (!vol) { - pr_err("Could not allocate %lu bytes for sflc_Volume\n", sizeof(sflc_Volume)); + pr_err("Could not allocate %lu bytes for sfold_Volume\n", sizeof(sfold_Volume)); err = -ENOMEM; goto err_alloc_vol; } + /* Parse args */ + if (sscanf(argv[3], "%u", &vol_idx) != 1) { + pr_err("Could not decode tot_slices\n"); + err = -EINVAL; + goto err_parse; + } + /* Decode the encryption key */ + if (strlen(argv[5]) != 2 * SFOLD_SK_KEY_LEN) { + pr_err("Hexadecimal key (length %lu): %s\n", strlen(argv[5]), argv[5]); + err = -EINVAL; + goto err_parse; + } + err = sfold_str_hexDecode(argv[5], enckey); + if (err) { + pr_err("Could not decode hexadecimal encryption key"); + err = -EINVAL; + goto err_parse; + } + /* Set volume name */ - sprintf(vol->vol_name, "sflc_%lu_%d", dev->dev_id, vol_idx); + sprintf(vol->vol_name, "sflc_%u_%d", dev->dev_id, vol_idx); /* Sysfs stuff */ - vol->kvol = sflc_sysfs_volCreateAndAdd(vol); - if (IS_ERR(vol->kvol)) { - err = PTR_ERR(vol->kvol); - pr_err("Could not create sysfs entry; error %d\n", err); + vol->kobj_parent = kobj; + err = sfold_sysfs_add_volume(vol); + if (err) { + pr_err("Could not add volume to sysfs; error %d\n", err); goto err_sysfs; } /* Backing device */ - if (!sflc_dev_addVolume(dev, vol, vol_idx)) { + if (!sfold_dev_addVolume(dev, vol, vol_idx)) { pr_err("Could not add volume to device\n"); err = -EINVAL; goto err_add_to_dev; @@ -76,7 +106,7 @@ sflc_Volume * sflc_vol_create(struct dm_target *ti, sflc_Device *dev, int vol_id vol->vol_idx = vol_idx; /* Crypto */ - vol->skctx = sflc_sk_createContext(enckey); + vol->skctx = sfold_sk_createContext(enckey); if (IS_ERR(vol->skctx)) { err = PTR_ERR(vol->skctx); pr_err("Could not create crypto context\n"); @@ -97,38 +127,52 @@ sflc_Volume * sflc_vol_create(struct dm_target *ti, sflc_Device *dev, int vol_id /* Initialise fmap */ pr_notice("Volume opening for volume %s: loading fmap from header\n", vol->vol_name); - err = sflc_vol_loadFmap(vol); + err = sfold_vol_loadFmap(vol); if (err) { pr_err("Could not load position map; error %d\n", err); goto err_load_fmap; } pr_debug("Successfully loaded position map for volume %s\n", vol->vol_name); + + /* Tell DM we want one SFLC sector at a time */ + ti->max_io_len = SFOLD_DEV_SECTOR_SCALE; + /* Enable REQ_OP_FLUSH bios */ + ti->num_flush_bios = 1; + /* Disable REQ_OP_WRITE_ZEROES and REQ_OP_SECURE_ERASE (can't be passed through as + they would break deniability, and they would be too complicated to handle individually) */ + ti->num_secure_erase_bios = 0; + ti->num_write_zeroes_bios = 0; + /* Momentarily disable REQ_OP_DISCARD_BIOS + TODO: will need to support them to release slice mappings */ + ti->num_discard_bios = 0; + return vol; err_load_fmap: vfree(vol->fmap); err_alloc_fmap: - sflc_sk_destroyContext(vol->skctx); + sfold_sk_destroyContext(vol->skctx); err_create_skctx: - sflc_dev_removeVolume(vol->dev, vol->vol_idx); + sfold_dev_removeVolume(vol->dev, vol->vol_idx); err_add_to_dev: - sflc_sysfs_putVol(vol->kvol); + sfold_sysfs_remove_volume(vol); err_sysfs: +err_parse: kfree(vol); err_alloc_vol: return ERR_PTR(err); } /* Removes the volume from the device and frees it. */ -void sflc_vol_destroy(struct dm_target * ti, sflc_Volume * vol) +void sfold_vol_destroy(sfold_Volume * vol) { int err; /* Store fmap */ pr_notice("Going to store position map of volume %s\n", vol->vol_name); - err = sflc_vol_storeFmap(vol); + err = sfold_vol_storeFmap(vol); if (err) { pr_err("Could not store position map; error %d\n", err); } @@ -137,13 +181,13 @@ void sflc_vol_destroy(struct dm_target * ti, sflc_Volume * vol) vfree(vol->fmap); /* Skctx */ - sflc_sk_destroyContext(vol->skctx); + sfold_sk_destroyContext(vol->skctx); /* Remove from device */ - sflc_dev_removeVolume(vol->dev, vol->vol_idx); + sfold_dev_removeVolume(vol->dev, vol->vol_idx); /* Destroy sysfs entries */ - sflc_sysfs_putVol(vol->kvol); + sfold_sysfs_remove_volume(vol); /* Free volume structure */ kfree(vol); diff --git a/dm-sflc/volume/volume.h b/dm-sflc/old/volume/volume.h similarity index 70% rename from dm-sflc/volume/volume.h rename to dm-sflc/old/volume/volume.h index 401cd97..38faf40 100644 --- a/dm-sflc/volume/volume.h +++ b/dm-sflc/old/volume/volume.h @@ -26,8 +26,8 @@ * a "real" device represented by a device. */ -#ifndef _SFLC_VOLUME_VOLUME_H_ -#define _SFLC_VOLUME_VOLUME_H_ +#ifndef _SFOLD_VOLUME_VOLUME_H_ +#define _SFOLD_VOLUME_VOLUME_H_ /***************************************************** @@ -36,9 +36,9 @@ /* Necessary since device.h, volume.h, and sysfs.h all include each other */ -typedef struct sflc_vol_write_work_s sflc_vol_WriteWork; -typedef struct sflc_vol_decrypt_work_s sflc_vol_DecryptWork; -typedef struct sflc_volume_s sflc_Volume; +typedef struct sfold_vol_write_work_s sfold_vol_WriteWork; +typedef struct sfold_vol_decrypt_work_s sfold_vol_DecryptWork; +typedef struct sfold_volume_s sfold_Volume; /***************************************************** @@ -47,9 +47,9 @@ typedef struct sflc_volume_s sflc_Volume; #include -#include "device/device.h" -#include "crypto/symkey/symkey.h" -#include "sysfs/sysfs.h" +#include "old/sflc_old.h" +#include "old/device/device.h" +#include "old/crypto/symkey/symkey.h" /***************************************************** @@ -57,26 +57,26 @@ typedef struct sflc_volume_s sflc_Volume; *****************************************************/ /* A single header data block contains 1024 fmap mappings */ -#define SFLC_VOL_HEADER_MAPPINGS_PER_BLOCK (SFLC_DEV_SECTOR_SIZE / sizeof(u32)) +#define SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK (SFOLD_DEV_SECTOR_SIZE / sizeof(u32)) /* We split the volume's logical addressing space into 1 MB slices */ -#define SFLC_VOL_LOG_SLICE_SIZE 256 // In 4096-byte sectors +#define SFOLD_VOL_LOG_SLICE_SIZE 256 // In 4096-byte sectors /* Value marking an LSI as unassigned */ -#define SFLC_VOL_FMAP_INVALID_PSI 0xFFFFFFFFU +#define SFOLD_VOL_FMAP_INVALID_PSI 0xFFFFFFFFU /* The volume name is "sflc--" */ -#define SFLC_VOL_NAME_MAX_LEN 12 +#define SFOLD_VOL_NAME_MAX_LEN 12 /***************************************************** * TYPES * *****************************************************/ -struct sflc_vol_write_work_s +struct sfold_vol_write_work_s { /* Essential information */ - sflc_Volume * vol; + sfold_Volume * vol; struct bio * orig_bio; /* Write requests need to allocate own page */ @@ -86,10 +86,10 @@ struct sflc_vol_write_work_s struct work_struct work; }; -struct sflc_vol_decrypt_work_s +struct sfold_vol_decrypt_work_s { /* Essential information */ - sflc_Volume * vol; + sfold_Volume * vol; struct bio * orig_bio; struct bio * phys_bio; @@ -101,15 +101,15 @@ struct sflc_vol_decrypt_work_s struct work_struct work; }; -struct sflc_volume_s +struct sfold_volume_s { - /* Name of the volume, sflc--*/ - char vol_name[SFLC_VOL_NAME_MAX_LEN + 1]; + /* Name of the volume, sflc__*/ + char vol_name[SFOLD_VOL_NAME_MAX_LEN + 1]; /* Backing device */ - sflc_Device * dev; + sfold_Device * dev; /* Index of this volume within the device's volume array */ - int vol_idx; + u32 vol_idx; /* Forward position map */ struct mutex fmap_lock; @@ -117,11 +117,11 @@ struct sflc_volume_s /* Stats on the fmap */ u32 mapped_slices; - /* Sysfs stuff */ - sflc_sysfs_Volume * kvol; + /* Parent sysfs directory */ + struct kobject *kobj_parent; /* Crypto */ - sflc_sk_Context * skctx; + sfold_sk_Context * skctx; }; @@ -130,27 +130,28 @@ struct sflc_volume_s *****************************************************/ /* Creates volume and adds it to the device. Returns an ERR_PTR() if unsuccessful */ -sflc_Volume * sflc_vol_create(struct dm_target * ti, sflc_Device* dev, int vol_idx, u8 * enckey); +sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, + int argc, char **argv, struct kobject *kobj); /* Removes the volume from the device and frees it. */ -void sflc_vol_destroy(struct dm_target * ti, sflc_Volume * vol); +void sfold_vol_destroy(sfold_Volume * vol); /* Remaps the underlying block device and the sector number */ -int sflc_vol_remapBioFast(sflc_Volume * vol, struct bio * bio); +int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio); /* Processes the bio in the normal indirection+crypto way */ -int sflc_vol_processBio(sflc_Volume * vol, struct bio * bio); +int sfold_vol_processBio(sfold_Volume * vol, struct bio * bio); /* Executed in top half */ -void sflc_vol_doRead(sflc_Volume * vol, struct bio * bio); +void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio); /* Executed in bottom half */ -void sflc_vol_doWrite(struct work_struct * work); +void sfold_vol_doWrite(struct work_struct * work); /* Maps a logical 512-byte sector to a physical 512-byte sector. Returns < 0 if error. * Specifically, if op == READ, and the logical slice is unmapped, -ENXIO is returned. */ -s64 sflc_vol_remapSector(sflc_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out); +s64 sfold_vol_remapSector(sfold_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out); /* Loads (and decrypts) the position map from the volume's header */ -int sflc_vol_loadFmap(sflc_Volume * vol); +int sfold_vol_loadFmap(sfold_Volume * vol); /* Stores (and encrypts) the position map to the volume's header */ -int sflc_vol_storeFmap(sflc_Volume * vol); +int sfold_vol_storeFmap(sfold_Volume * vol); -#endif /* _SFLC_VOLUME_VOLUME_H_ */ +#endif /* _SFOLD_VOLUME_VOLUME_H_ */ diff --git a/dm-sflc/volume/write.c b/dm-sflc/old/volume/write.c similarity index 77% rename from dm-sflc/volume/write.c rename to dm-sflc/old/volume/write.c index 007783a..bbf113c 100644 --- a/dm-sflc/volume/write.c +++ b/dm-sflc/old/volume/write.c @@ -32,10 +32,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "volume.h" -#include "crypto/rand/rand.h" -#include "utils/pools.h" -#include "log/log.h" +#include "old/volume/volume.h" +#include "old/crypto/rand/rand.h" +#include "old/utils/pools.h" +#include "old/log/log.h" #include @@ -47,9 +47,9 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static int sflc_vol_encryptOrigBio(sflc_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice); -static int sflc_vol_sampleAndWriteIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); -static void sflc_vol_writeEndIo(struct bio * phys_bio); +static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice); +static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); +static void sfold_vol_writeEndIo(struct bio * phys_bio); /***************************************************** * PRIVATE VARIABLES * @@ -60,11 +60,11 @@ static void sflc_vol_writeEndIo(struct bio * phys_bio); *****************************************************/ /* Executed in workqueue bottom half */ -void sflc_vol_doWrite(struct work_struct * work) +void sfold_vol_doWrite(struct work_struct * work) { - sflc_vol_WriteWork * write_work = container_of(work, sflc_vol_WriteWork, work); - sflc_Volume * vol = write_work->vol; - sflc_Device * dev = vol->dev; + sfold_vol_WriteWork * write_work = container_of(work, sfold_vol_WriteWork, work); + sfold_Volume * vol = write_work->vol; + sfold_Device * dev = vol->dev; struct bio * orig_bio = write_work->orig_bio; struct bio * phys_bio; s64 phys_sector; @@ -80,14 +80,14 @@ void sflc_vol_doWrite(struct work_struct * work) non-owned bio's. So we need our own. */ /* Allocate an empty bio */ - phys_bio = bio_alloc_bioset(dev->bdev->bdev, bio_segments(orig_bio), orig_bio->bi_opf, GFP_NOIO, &sflc_pools_bioset); + phys_bio = bio_alloc_bioset(dev->bdev->bdev, bio_segments(orig_bio), orig_bio->bi_opf, GFP_NOIO, &sfold_pools_bioset); if (!phys_bio) { pr_err("Could not allocate bio\n"); goto err_alloc_phys_bio; } /* Remap sector */ - phys_sector = sflc_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, WRITE, &psi, &off_in_slice); + phys_sector = sfold_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, WRITE, &psi, &off_in_slice); if (phys_sector < 0) { pr_err("Could not remap sector for physical bio; error %d\n", (int) phys_sector); goto err_remap_sector; @@ -95,11 +95,11 @@ void sflc_vol_doWrite(struct work_struct * work) phys_bio->bi_iter.bi_sector = phys_sector; /* Set fields for the endio */ - phys_bio->bi_end_io = sflc_vol_writeEndIo; + phys_bio->bi_end_io = sfold_vol_writeEndIo; phys_bio->bi_private = write_work; /* Encrypt the original bio into the physical bio (newly-allocated pages) */ - int err = sflc_vol_encryptOrigBio(vol, orig_bio, phys_bio, psi, off_in_slice); + int err = sfold_vol_encryptOrigBio(vol, orig_bio, phys_bio, psi, off_in_slice); if (err) { pr_err("Could not encrypt original bio; error %d\n", err); goto err_encrypt_orig_bio; @@ -127,14 +127,14 @@ err_alloc_phys_bio: *****************************************************/ /* Encrypts the contents of the original bio into newly-allocated pages for the physical bio */ -static int sflc_vol_encryptOrigBio(sflc_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice) +static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice) { - sflc_vol_WriteWork * write_work = phys_bio->bi_private; - u8 iv[SFLC_SK_IV_LEN]; + sfold_vol_WriteWork * write_work = phys_bio->bi_private; + u8 iv[SFOLD_SK_IV_LEN]; int err; /* Allocate new page for the physical bio */ - write_work->page = mempool_alloc(sflc_pools_pagePool, GFP_NOIO); + write_work->page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); if (!write_work->page) { pr_err("Could not allocate page\n"); err = -ENOMEM; @@ -142,14 +142,14 @@ static int sflc_vol_encryptOrigBio(sflc_Volume * vol, struct bio * orig_bio, str } /* Add it to physical bio */ - if (!bio_add_page(phys_bio, write_work->page, SFLC_DEV_SECTOR_SIZE, 0)) { + if (!bio_add_page(phys_bio, write_work->page, SFOLD_DEV_SECTOR_SIZE, 0)) { pr_err("Catastrophe. Could not add page to copy bio. WTF?\n"); err = -EIO; goto err_bio_add_page; } /* Sample a random IV and write it in the IV block */ - err = sflc_vol_sampleAndWriteIv(vol, iv, psi, off_in_slice); + err = sfold_vol_sampleAndWriteIv(vol, iv, psi, off_in_slice); if (err) { pr_err("Could not sample and write IV; error %d\n", err); err = -EIO; @@ -160,7 +160,7 @@ static int sflc_vol_encryptOrigBio(sflc_Volume * vol, struct bio * orig_bio, str struct bio_vec bvl = bio_iovec(orig_bio); void * enc_sector_ptr = kmap(write_work->page); void * plain_sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - err = sflc_sk_encrypt(vol->skctx, plain_sector_ptr, enc_sector_ptr, SFLC_DEV_SECTOR_SIZE, iv); + err = sfold_sk_encrypt(vol->skctx, plain_sector_ptr, enc_sector_ptr, SFOLD_DEV_SECTOR_SIZE, iv); kunmap(bvl.bv_page); kunmap(write_work->page); if (err) { @@ -174,21 +174,21 @@ static int sflc_vol_encryptOrigBio(sflc_Volume * vol, struct bio * orig_bio, str err_encrypt: err_sample_and_write_iv: err_bio_add_page: - mempool_free(write_work->page, sflc_pools_pagePool); + mempool_free(write_work->page, sfold_pools_pagePool); err_alloc_page: return err; } /* Allocates the io_work's IV (will need to be freed afterwards), fills it with random bytes, and writes it into the IV block pointed by the io_work's PSI and off_in_slice. */ -static int sflc_vol_sampleAndWriteIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) +static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) { - sflc_Device * dev = vol->dev; + sfold_Device * dev = vol->dev; u8 * iv_block; int err; /* Sample IV */ - err = sflc_rand_getBytes(iv, SFLC_SK_IV_LEN); + err = sfold_rand_getBytes(iv, SFOLD_SK_IV_LEN); if (err) { pr_err("Could not sample IV; error %d\n", err); err = -EIO; @@ -196,7 +196,7 @@ static int sflc_vol_sampleAndWriteIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 of } /* Acquire a reference to the whole relevant IV block */ - iv_block = sflc_dev_getIvBlockRef(dev, psi, WRITE); + iv_block = sfold_dev_getIvBlockRef(dev, psi, WRITE); if (IS_ERR(iv_block)) { err = PTR_ERR(iv_block); pr_err("Could not acquire reference to IV block; error %d\n", err); @@ -204,10 +204,10 @@ static int sflc_vol_sampleAndWriteIv(sflc_Volume * vol, u8 * iv, u32 psi, u32 of } /* Copy it into the relevant portion of the block */ - memcpy(iv_block + (off_in_slice * SFLC_SK_IV_LEN), iv, SFLC_SK_IV_LEN); + memcpy(iv_block + (off_in_slice * SFOLD_SK_IV_LEN), iv, SFOLD_SK_IV_LEN); /* Release reference to the IV block */ - err = sflc_dev_putIvBlockRef(dev, psi); + err = sfold_dev_putIvBlockRef(dev, psi); if (err) { pr_err("Could not release reference to IV block; error %d\n", err); goto err_put_iv_block_ref; @@ -222,9 +222,9 @@ err_sample_iv: return err; } -static void sflc_vol_writeEndIo(struct bio * phys_bio) +static void sfold_vol_writeEndIo(struct bio * phys_bio) { - sflc_vol_WriteWork * write_work = phys_bio->bi_private; + sfold_vol_WriteWork * write_work = phys_bio->bi_private; struct bio * orig_bio = write_work->orig_bio; unsigned completed_bytes; @@ -245,8 +245,8 @@ static void sflc_vol_writeEndIo(struct bio * phys_bio) if (unlikely(page_ref_count(write_work->page) != 1)) { pr_err("WTF: page_ref_count = %d\n", page_ref_count(write_work->page)); } - mempool_free(write_work->page, sflc_pools_pagePool); - mempool_free(write_work, sflc_pools_writeWorkPool); + mempool_free(write_work->page, sfold_pools_pagePool); + mempool_free(write_work, sfold_pools_writeWorkPool); return; } diff --git a/dm-sflc/sflc.c b/dm-sflc/sflc.c new file mode 100644 index 0000000..0938fb0 --- /dev/null +++ b/dm-sflc/sflc.c @@ -0,0 +1,270 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include + +#include "sflc.h" +#include "old/sflc_old.h" + + +// Global variables +DEFINE_MUTEX(sflc_alldevs_lock); +struct sflc_device **sflc_alldevs = NULL; +u32 sflc_free_devid = 0; /* The lowest free devID */ + + +/* Add a device to the global array, and advance next_dev_id */ +static int sflc_add_device_global(u32 dev_id, struct sflc_device *sdev) +{ + int i; + + if (mutex_lock_interruptible(&sflc_alldevs_lock)) + return -ERESTARTSYS; + + /* Check for dev_id conflict, possible because a read() to next_dev_id + * in sysfs can return the same value to two processes */ + if (sflc_alldevs[dev_id]) { + mutex_unlock(&sflc_alldevs_lock); + DMERR("A device with this ID already exists. Retry"); + return -EINVAL; + } + // Add to the global array, and advance free_devid + sflc_alldevs[dev_id] = sdev; + for (i = sflc_free_devid; i < SFLC_MAX_DEVS && sflc_alldevs[i]; i++); + sflc_free_devid = i; + + mutex_unlock(&sflc_alldevs_lock); + + return 0; +} + +/* Remove a device from the global array, and update next_dev_id */ +static void sflc_remove_device_global(u32 dev_id) +{ + if (mutex_lock_interruptible(&sflc_alldevs_lock)) + return; + sflc_alldevs[dev_id] = NULL; + if (dev_id < sflc_free_devid) + sflc_free_devid = dev_id; + mutex_unlock(&sflc_alldevs_lock); +} + + +/* + * Create volume and, if not existent, the underlying device. + * Arguments: + * argv[0]: Shufflecake mode: legacy/lite + * argv[1]: Shufflecake-unique device ID + * argv[2]: path to underlying physical device + * argv[3]: volume index within the device + * argv[4:]: mode-specific parameters + */ +static int sflc_ctr(struct dm_target *ti, unsigned int argc, char **argv) +{ + u32 dev_id; + u32 vol_idx; + struct sflc_device *sdev; + struct sflc_volume *svol; + int err; + + /* Parse arguments */ + if (argc < 4) { + ti->error = "Invalid argument count"; + return -EINVAL; + } + if (sscanf(argv[1], "%u", &dev_id) != 1) { + ti->error = "Could not decode device ID"; + return -EINVAL; + } + if (sscanf(argv[3], "%u", &vol_idx) != 1) { + ti->error = "Could not decode volume index"; + return -EINVAL; + } + /* Sanity checks */ + if (dev_id >= SFLC_MAX_DEVS) { + ti->error = "Device ID out of bounds"; + return -EINVAL; + } + if (vol_idx >= SFLC_DEV_MAX_VOLUMES) { + ti->error = "Volume index out of bounds"; + return -EINVAL; + } + + /* Create device, if this is the first volume, otherwise retrieve it */ + if (vol_idx == 0) { + sdev = sflc_dev_create(ti, argc, argv); + if (IS_ERR(sdev)) { + ti->error = "Could not instantiate device"; + return PTR_ERR(sdev); + } + /* Insert in global array */ + err = sflc_add_device_global(dev_id, sdev); + if (err) { + ti->error = "Could not add device to global array"; + goto bad_dev_global; + } + } else { + if (mutex_lock_interruptible(&sflc_alldevs_lock)) + return -ERESTARTSYS; + sdev = sflc_alldevs[dev_id]; + mutex_unlock(&sflc_alldevs_lock); + + if (!sdev) { + ti->error = "Could not find device with specified ID"; + return -EINVAL; + } + } + + /* Create volume */ + svol = sflc_vol_create(sdev, ti, argc, argv); + if (IS_ERR(svol)) { + ti->error = "Could not instantiate volume"; + err = PTR_ERR(svol); + goto bad_vol_create; + } + /* We expect ->ctr() calls to be strictly sequential, so we don't need locking */ + sdev->nr_volumes++; + + ti->private = svol; + + return 0; + + +bad_vol_create: + if (vol_idx == 0) { + sflc_remove_device_global(dev_id); +bad_dev_global: + sflc_dev_destroy(sdev); + } + return err; +} + + +/* Destroy volume and, if needed, the underlying device */ +static void sflc_dtr(struct dm_target *ti) +{ + struct sflc_volume *svol = ti->private; + struct sflc_device *sdev = svol->sdev; + + sflc_vol_destroy(svol); + /* We expect ->dtr() calls to be strictly sequential, so we don't need locking */ + sdev->nr_volumes--; + + if (sdev->nr_volumes == 0) { + sflc_remove_device_global(sdev->dev_id); + sflc_dev_destroy(sdev); + } + + return; +} + + +static int sflc_map(struct dm_target *ti, struct bio *bio) +{ + struct sflc_volume *svol = ti->private; + return svol->tt->map(ti, bio); +} + +static void sflc_io_hints(struct dm_target *ti, struct queue_limits *limits) +{ + struct sflc_volume *svol = ti->private; + svol->tt->io_hints(ti, limits); + return; +} + +static int sflc_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) +{ + struct sflc_volume *svol = ti->private; + return svol->tt->iterate_devices(ti, fn, data); +} + + +/* + *---------------------------- + * Kernel module + *---------------------------- + */ + +static struct target_type sflc_target_type = { + .name = SFLC_TARGET_NAME, + .version = {SFLC_VER_MAJOR, SFLC_VER_MINOR, SFLC_VER_REVISION}, + .module = THIS_MODULE, + .ctr = sflc_ctr, + .dtr = sflc_dtr, + .map = sflc_map, + .io_hints = sflc_io_hints, + .iterate_devices = sflc_iterate_devices, +}; + + +/* Module entry point */ +static int sflc_init(void) +{ + int ret; + + /* Create the first sysfs entries */ + ret = sflc_sysfs_init(); + if (ret) + goto err_sysfs; + + /* Init the legacy module */ + ret = sfold_init(); + if (ret) + goto err_sfold; + + /* Register the DM callbacks */ + ret = dm_register_target(&sflc_target_type); + if (ret < 0) + goto err_dm; + + DMINFO("loaded"); + return 0; + + +err_dm: + sfold_exit(); +err_sfold: + sflc_sysfs_exit(); +err_sysfs: + return ret; +} + + +/* Module exit point */ +static void sflc_exit(void) +{ + dm_unregister_target(&sflc_target_type); + sfold_exit(); + sflc_sysfs_exit(); + + DMINFO("unloaded"); + return; +} + + +module_init(sflc_init); +module_exit(sflc_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Toninov"); diff --git a/dm-sflc/sflc.h b/dm-sflc/sflc.h new file mode 100644 index 0000000..6248f74 --- /dev/null +++ b/dm-sflc/sflc.h @@ -0,0 +1,117 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _SFLC_H +#define _SFLC_H + + +#include + +#include "sflc_constants.h" +#include "old/sflc_old.h" + + +/* + *---------------------------- + * Structs + *---------------------------- + */ + +struct sflc_device +{ + /* Shufflecake-unique device ID */ + u32 dev_id; + /* : */ + char name[16]; + /* Number of volumes */ + u32 nr_volumes; + + /* Mode-specific device struct */ + int mode; + union { + sfold_Device *sfold_dev; + }; + + /* Sysfs */ + struct kobject kobj; + struct completion kobj_released; +}; + +struct sflc_volume +{ + /* Backing device */ + struct sflc_device *sdev; + /* Volume name: sflc__ */ + char name[32]; + + /* Mode-specific volume struct */ + int mode; + union { + sfold_Volume *sfold_vol; + }; + /* Pointers to concrete, mode-specific callbacks */ + struct target_type *tt; + + /* Sysfs */ + struct kobject kobj; + struct completion kobj_released; +}; + + +/* + *---------------------------- + * Global variables + *---------------------------- + */ + +extern struct mutex sflc_alldevs_lock; +extern struct sflc_device **sflc_alldevs; +/* Next free device id */ +extern u32 sflc_free_devid; /* The lowest free devID */ + + +/* + *---------------------------- + * Functions + *---------------------------- + */ + +/* Device */ +struct sflc_device *sflc_dev_create(struct dm_target *ti, int argc, char **argv); +void sflc_dev_destroy(struct sflc_device *sdev); + +/* Volume */ +struct sflc_volume *sflc_vol_create(struct sflc_device *sdev, struct dm_target *ti, + int argc, char **argv); +void sflc_vol_destroy(struct sflc_volume *svol); + +/* Sysfs */ +int sflc_sysfs_init(void); +void sflc_sysfs_exit(void); +int sflc_sysfs_register_device(struct sflc_device *sdev); +void sflc_sysfs_unregister_device(struct sflc_device *sdev); +int sflc_sysfs_register_volume(struct sflc_volume *svol); +void sflc_sysfs_unregister_volume(struct sflc_volume *svol); + + +#endif /* _SFLC_H */ diff --git a/dm-sflc/sflc_constants.h b/dm-sflc/sflc_constants.h index 5b4668a..c978796 100644 --- a/dm-sflc/sflc_constants.h +++ b/dm-sflc/sflc_constants.h @@ -21,15 +21,18 @@ * If not, see . */ -/***************************************************** - * PLACEHOLDER * - *****************************************************/ +/* Here we gather Shufflecake-wide constants, independent of the mode (legacy/lite) */ -/* This is just a placeholder for defining constants and parameters that must be the same across shufflecake components (kernel module, userland tool, etc) such as block size, slice size etc */ +#ifndef _SFLC_CONSTANTS_H_ +#define _SFLC_CONSTANTS_H_ + + +#define SFLC_TARGET_NAME "shufflecake" +#define DM_MSG_PREFIX "sflc" #define SFLC_VER_MAJOR 0 -#define SFLC_VER_MINOR 4 +#define SFLC_VER_MINOR 5 #define SFLC_VER_REVISION 0 #define SFLC_VER_SPECIAL "rc1" @@ -39,3 +42,20 @@ #define SFLC_VERSION STRINGIFY(SFLC_VER_MAJOR)"."STRINGIFY(SFLC_VER_MINOR)"."STRINGIFY(SFLC_VER_REVISION)""SFLC_VER_SPECIAL +/* Each device can be formatted and used with a mode of choice, irrespective of the other devices */ +#define SFLC_MODE_LEGACY 0 +#define SFLC_MODE_LITE 1 + + +/* Max number of volumes per device */ +#define SFLC_DEV_MAX_VOLUMES 15 +/* Max number of open devices at any given time */ +#define SFLC_MAX_DEVS 1024 + + +/* Sysfs entries under /sys/module/dm_sflc/ */ +#define SFLC_SYSFS_BDEVS "bdevs" +#define SFLC_SYSFS_DEVID "next_dev_id" + + +#endif /* _SFLC_CONSTANTS_H_ */ diff --git a/dm-sflc/sysfs.c b/dm-sflc/sysfs.c new file mode 100644 index 0000000..acea092 --- /dev/null +++ b/dm-sflc/sysfs.c @@ -0,0 +1,206 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "sflc.h" + +/* + *---------------------------- + * Top-level entries + *---------------------------- + */ + +static ssize_t next_dev_id_show(struct module_attribute *mattr, struct module_kobject *mkobj, char *buf) +{ + ssize_t ret; + + if (mutex_lock_interruptible(&sflc_alldevs_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", sflc_free_devid); + mutex_unlock(&sflc_alldevs_lock); + + return ret; +} + +static struct kset *bdevs_kset; +static struct module_attribute devid_mattr = __ATTR_RO(next_dev_id); + +int sflc_sysfs_init() +{ + int err; + + bdevs_kset = kset_create_and_add(SFLC_SYSFS_BDEVS, NULL, &THIS_MODULE->mkobj.kobj); + if (!bdevs_kset) { + err = -ENOMEM; + DMERR("Could not create %s kset", SFLC_SYSFS_BDEVS); + goto bad_bdevs; + } + + err = sysfs_create_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); + if (err) { + DMERR("Could not create %s file", SFLC_SYSFS_DEVID); + goto bad_devid; + } + + return 0; + + +bad_devid: + kset_unregister(bdevs_kset); +bad_bdevs: + return err; +} + +void sflc_sysfs_exit() +{ + sysfs_remove_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); + kset_unregister(bdevs_kset); +} + + +/* + *---------------------------- + * Device entries + *---------------------------- + */ + +static ssize_t dev_id_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *sdev = container_of(kobj, struct sflc_device, kobj); + + return sysfs_emit(buf, "%u\n", sdev->dev_id); +} + +static ssize_t volumes_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *sdev = container_of(kobj, struct sflc_device, kobj); + + return sysfs_emit(buf, "%u\n", sdev->nr_volumes); +} + + +static struct kobj_attribute dev_id_kattr = __ATTR_RO(dev_id); +static struct kobj_attribute volumes_kattr = __ATTR_RO(volumes); +static struct attribute *sflc_device_default_attrs[] = { + &dev_id_kattr.attr, + &volumes_kattr.attr, + NULL +}; +ATTRIBUTE_GROUPS(sflc_device_default); + +static void sflc_device_kobj_release(struct kobject *kobj) +{ + struct sflc_device *sdev = container_of(kobj, struct sflc_device, kobj); + complete(&sdev->kobj_released); +} + +static struct kobj_type sflc_device_ktype = { + .release = sflc_device_kobj_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = sflc_device_default_groups +}; + +int sflc_sysfs_register_device(struct sflc_device *sdev) +{ + int err; + + /* Completion */ + init_completion(&sdev->kobj_released); + + /* Register directory :/ under bdevs/ */ + sdev->kobj.kset = bdevs_kset; + err = kobject_init_and_add(&sdev->kobj, &sflc_device_ktype, NULL, + "%s", sdev->name); + if (err) + goto bad; + /* Emit uevent */ + kobject_uevent(&sdev->kobj, KOBJ_ADD); + + return 0; + + +bad: + kobject_put(&sdev->kobj); + wait_for_completion(&sdev->kobj_released); + return err; +} + +void sflc_sysfs_unregister_device(struct sflc_device *sdev) +{ + kobject_put(&sdev->kobj); + wait_for_completion(&sdev->kobj_released); +} + + +/* + *---------------------------- + * Volume entries + *---------------------------- + */ + +static struct attribute *sflc_volume_default_attrs[] = { + NULL +}; +ATTRIBUTE_GROUPS(sflc_volume_default); + +static void sflc_volume_kobj_release(struct kobject *kobj) +{ + struct sflc_volume *svol = container_of(kobj, struct sflc_volume, kobj); + + complete(&svol->kobj_released); +} + +static struct kobj_type sflc_volume_ktype = { + .release = sflc_volume_kobj_release, + .sysfs_ops = &kobj_sysfs_ops, + .default_groups = sflc_volume_default_groups +}; + +int sflc_sysfs_register_volume(struct sflc_volume *svol) +{ + int err; + + /* Completion */ + init_completion(&svol->kobj_released); + + /* Register directory name>/ under device directory */ + err = kobject_init_and_add(&svol->kobj, &sflc_volume_ktype, &svol->sdev->kobj, + "%s", svol->name); + if (err) + goto bad; + /* Emit uevent */ + kobject_uevent(&svol->kobj, KOBJ_ADD); + + return 0; + + +bad: + kobject_put(&svol->kobj); + wait_for_completion(&svol->kobj_released); + return err; +} + +void sflc_sysfs_unregister_volume(struct sflc_volume *svol) +{ + kobject_put(&svol->kobj); + wait_for_completion(&svol->kobj_released); +} diff --git a/dm-sflc/sysfs/devices.c b/dm-sflc/sysfs/devices.c deleted file mode 100644 index 7729145..0000000 --- a/dm-sflc/sysfs/devices.c +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include "sysfs.h" -#include "utils/string.h" -#include "log/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define SFLC_SYSFS_DEV_VOLUMES_ATTR_NAME "volumes" -#define SFLC_SYSFS_DEV_TOT_SLICES_ATTR_NAME "tot_slices" -#define SFLC_SYSFS_DEV_FREE_SLICES_ATTR_NAME "free_slices" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Functions that will go in the sysfs_ops */ -static ssize_t sflc_sysfs_devShow(struct kobject * kobj, struct attribute * attr, char * buf); -static ssize_t sflc_sysfs_devStore(struct kobject * kobj, struct attribute * attr, const char * buf, size_t len); - -/* Concrete file showers */ -static ssize_t sflc_sysfs_showDeviceVolumes(struct kobject * kobj, struct attribute * attr, char * buf); -static ssize_t sflc_sysfs_showDeviceTotSlices(struct kobject * kobj, struct attribute * attr, char * buf); -static ssize_t sflc_sysfs_showDeviceFreeSlices(struct kobject * kobj, struct attribute * attr, char * buf); - -/* Release function for the sysfs_Device */ -static void sflc_sysfs_releaseDev(struct kobject * kobj); - - -/***************************************************** - * PRIVATE VARIABLES DEFINITIONS * - *****************************************************/ - -/* The attribute representing the volumes file */ -static const struct attribute sflc_sysfs_devVolumesAttr = { - .name = SFLC_SYSFS_DEV_VOLUMES_ATTR_NAME, - .mode = 0444 -}; - -/* The attribute representing the tot_slices file */ -static const struct attribute sflc_sysfs_devTotSlicesAttr = { - .name = SFLC_SYSFS_DEV_TOT_SLICES_ATTR_NAME, - .mode = 0444 -}; - -/* The attribute representing the free_slices file */ -static const struct attribute sflc_sysfs_devFreeSlicesAttr = { - .name = SFLC_SYSFS_DEV_FREE_SLICES_ATTR_NAME, - .mode = 0444 -}; - -/* The sysfs_ops struct encapsulating the access methods */ -static const struct sysfs_ops sflc_sysfs_devSysfsOps = { - .show = sflc_sysfs_devShow, - .store = sflc_sysfs_devStore -}; - -/* The type for our sysfs_Device */ -static struct kobj_type sflc_sysfs_devType = { - .release = sflc_sysfs_releaseDev, - .sysfs_ops = &sflc_sysfs_devSysfsOps, -}; - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Creates and registers a sysfs_Device instance. Return ERR_PTR() on error. */ -sflc_sysfs_Device * sflc_sysfs_devCreateAndAdd(sflc_Device * pdev) -{ - sflc_sysfs_Device * dev; - int err; - - /* Allocate outer structure */ - dev = kzalloc(sizeof(sflc_sysfs_Device), GFP_KERNEL); - if (!dev) { - err = -ENOMEM; - pr_err("Could not allocate sysfs_Device\n"); - goto err_dev_alloc; - } - - /* Set device */ - dev->pdev = pdev; - - /* Allocate inner string */ - dev->dirname = kzalloc(strlen(pdev->bdev_path) + 1, GFP_KERNEL); - if (!dev->dirname) { - err = -ENOMEM; - pr_err("Could not allocate dirname\n"); - goto err_dirname_alloc; - } - - /* Copy it and substitute slashes with underscores */ - strcpy(dev->dirname, pdev->bdev_path); - sflc_str_replaceAll(dev->dirname, '/', '_'); - - /* Init and add kobject */ - err = kobject_init_and_add(&dev->kobj, &sflc_sysfs_devType, - sflc_sysfs_bdevs, dev->dirname); - if (err) { - pr_err("Could not init and add internal kobject; error %d\n", err); - goto err_kobj_init_add; - } - - /* Create the volumes file */ - err = sysfs_create_file(&dev->kobj, &sflc_sysfs_devVolumesAttr); - if (err) { - pr_err("Could not add volumes file; error %d\n", err); - goto err_vol_file; - } - /* Create the tot_slices file */ - err = sysfs_create_file(&dev->kobj, &sflc_sysfs_devTotSlicesAttr); - if (err) { - pr_err("Could not add tot_slices file; error %d\n", err); - goto err_tot_slices_file; - } - /* Create the volumes file */ - err = sysfs_create_file(&dev->kobj, &sflc_sysfs_devFreeSlicesAttr); - if (err) { - pr_err("Could not add free_slices file; error %d\n", err); - goto err_free_slices_file; - } - - return dev; - - -err_free_slices_file: -err_tot_slices_file: -err_vol_file: - kobject_put(&dev->kobj); -err_kobj_init_add: - kfree(dev->dirname); -err_dirname_alloc: - kfree(dev); -err_dev_alloc: - return ERR_PTR(err); -} - -/* Releases a reference to a sysfs_Device instance */ -void sflc_sysfs_putDev(sflc_sysfs_Device * dev) -{ - kobject_put(&dev->kobj); -} - -/* Creates a symlink inside the device's directory (under /sys/modules/dm_sflc/bdevs/) - * pointing to the volume's directory (under /sys/devices/sflc/) */ -int sflc_sysfs_addVolumeToDevice(sflc_sysfs_Device * dev, sflc_sysfs_Volume * vol) -{ - return sysfs_create_link(&dev->kobj, &vol->kdev.kobj, vol->kdev.kobj.name); -} - -/* Removes the symlink created before */ -void sflc_sysfs_removeVolumeFromDevice(sflc_sysfs_Device * dev, sflc_sysfs_Volume * vol) -{ - sysfs_remove_link(&dev->kobj, vol->kdev.kobj.name); -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Dispatch to the right shower */ -static ssize_t sflc_sysfs_devShow(struct kobject * kobj, struct attribute * attr, char * buf) -{ - /* Dispatch based on name */ - if (strcmp(attr->name, SFLC_SYSFS_DEV_VOLUMES_ATTR_NAME) == 0) { - return sflc_sysfs_showDeviceVolumes(kobj, attr, buf); - } - if (strcmp(attr->name, SFLC_SYSFS_DEV_TOT_SLICES_ATTR_NAME) == 0) { - return sflc_sysfs_showDeviceTotSlices(kobj, attr, buf); - } - if (strcmp(attr->name, SFLC_SYSFS_DEV_FREE_SLICES_ATTR_NAME) == 0) { - return sflc_sysfs_showDeviceFreeSlices(kobj, attr, buf); - } - - /* Else, error */ - pr_err("Error, unknown attribute %s\n", attr->name); - return -EIO; -} - -/* Do nothing */ -static ssize_t sflc_sysfs_devStore(struct kobject * kobj, struct attribute * attr, const char * buf, size_t len) -{ - return -EIO; -} - -/* Show the list of mounted volumes */ -static ssize_t sflc_sysfs_showDeviceVolumes(struct kobject * kobj, struct attribute * attr, char * buf) -{ - sflc_sysfs_Device * dev; - sflc_Device * pdev; - ssize_t ret; - - /* Cast to a sysfs_Device */ - dev = container_of(kobj, sflc_sysfs_Device, kobj); - /* Get the device */ - pdev = dev->pdev; - - /* File contents */ - ret = 0; - ssize_t written; - /* Write the volume count */ - written = sprintf(buf, "%d", pdev->vol_cnt); - ret += written; - buf += written; - /* And all the volume names, space-separated */ - int i; - for (i = 0; i < pdev->vol_cnt; i++) { - written = sprintf(buf, " %s", pdev->vol[i]->vol_name); - ret += written; - buf += written; - } - /* Add newline */ - written = sprintf(buf, "\n"); - ret += written; - buf += written; - - return ret; -} - -/* Show the number of slices */ -static ssize_t sflc_sysfs_showDeviceTotSlices(struct kobject * kobj, struct attribute * attr, char * buf) -{ - sflc_sysfs_Device * dev; - sflc_Device * pdev; - ssize_t ret; - - /* Cast to a DeviceKobject */ - dev = container_of(kobj, sflc_sysfs_Device, kobj); - /* Get the device */ - pdev = dev->pdev; - - /* Write the tot_slices */ - ret = sprintf(buf, "%u\n", pdev->tot_slices); - - return ret; -} - -/* Show the number of free slices */ -static ssize_t sflc_sysfs_showDeviceFreeSlices(struct kobject * kobj, struct attribute * attr, char * buf) -{ - sflc_sysfs_Device * dev; - sflc_Device * pdev; - ssize_t ret; - - /* Cast to a DeviceKobject */ - dev = container_of(kobj, sflc_sysfs_Device, kobj); - /* Get the device */ - pdev = dev->pdev; - - /* Write the free_slices */ - ret = sprintf(buf, "%u\n", pdev->free_slices); - - return ret; -} - -/* Release function for the DeviceKobject */ -static void sflc_sysfs_releaseDev(struct kobject * kobj) -{ - sflc_sysfs_Device * dev; - - /* Cast to a DeviceKobject */ - dev = container_of(kobj, sflc_sysfs_Device, kobj); - - /* Free everything */ - kfree(dev->dirname); - kfree(dev); - - return; -} diff --git a/dm-sflc/sysfs/sysfs.c b/dm-sflc/sysfs/sysfs.c deleted file mode 100644 index a5854c4..0000000 --- a/dm-sflc/sysfs/sysfs.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -#include "sysfs.h" -#include "log/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define SFLC_SYSFS_BDEVS_ENTRY_NAME "bdevs" -#define SFLC_SYSFS_ROOT_DEVICE_NAME "sflc" -#define SFLC_SYSFS_NEXT_DEV_ID_ATTR_NAME next_dev_id - - -/***************************************************** - * PUBLIC VARIABLES DEFINITIONS * - *****************************************************/ - -/* Kobject associated to the /sys/module/dm_sflc/bdevs entry */ -struct kobject * sflc_sysfs_bdevs; - -/* Root device that will be every volume's parent */ -struct device * sflc_sysfs_root; - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static ssize_t sflc_sysfs_showNextDevId(struct device *kdev, struct device_attribute *attr, char *buf); - - -/***************************************************** - * PRIVATE VARIABLES DEFINITIONS * - *****************************************************/ - -/* Attribute showing the next device ID */ -static const struct device_attribute sflc_sysfs_nextDevIdAttr = __ATTR( - SFLC_SYSFS_NEXT_DEV_ID_ATTR_NAME, - 0444, - sflc_sysfs_showNextDevId, - NULL -); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Called on module load */ -int sflc_sysfs_init(void) -{ - int err; - - /* Create the bdevs entry under /sys/module/dm_sflc */ - sflc_sysfs_bdevs = kobject_create_and_add(SFLC_SYSFS_BDEVS_ENTRY_NAME, &THIS_MODULE->mkobj.kobj); - if (!sflc_sysfs_bdevs) { - err = -ENOMEM; - pr_err("Could not create bdevs kobject\n"); - goto err_realdevs; - } - - /* Create the root sflc device */ - sflc_sysfs_root = root_device_register(SFLC_SYSFS_ROOT_DEVICE_NAME); - if (IS_ERR(sflc_sysfs_root)) { - err = PTR_ERR(sflc_sysfs_root); - pr_err("Could not register sflc root device; error %d\n", err); - goto err_rootdev; - } - - /* Add next_dev_id attribute to it */ - err = device_create_file(sflc_sysfs_root, &sflc_sysfs_nextDevIdAttr); - if (err) { - pr_err("Could not create mapped_slices device file; error %d\n", err); - goto err_dev_create_file; - } - - return 0; - -err_dev_create_file: - root_device_unregister(sflc_sysfs_root); -err_rootdev: - kobject_put(sflc_sysfs_bdevs); -err_realdevs: - return err; -} - -/* Called on module unload */ -void sflc_sysfs_exit(void) -{ - root_device_unregister(sflc_sysfs_root); - kobject_put(sflc_sysfs_bdevs); -} - - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -static ssize_t sflc_sysfs_showNextDevId(struct device *kdev, struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "%lu\n", sflc_dev_nextId); -} - diff --git a/dm-sflc/sysfs/sysfs.h b/dm-sflc/sysfs/sysfs.h deleted file mode 100644 index 62fc014..0000000 --- a/dm-sflc/sysfs/sysfs.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Sysfs functions - */ - -#ifndef _SFLC_SYSFS_SYSFS_H_ -#define _SFLC_SYSFS_SYSFS_H_ - - -/***************************************************** - * TYPES FORWARD DECLARATIONS * - *****************************************************/ - -/* Necessary since device.h, volume.h, and sysfs.h all include each other */ - -typedef struct sflc_sysfs_device_s sflc_sysfs_Device; -typedef struct sflc_sysfs_volume_s sflc_sysfs_Volume; - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "device/device.h" -#include "volume/volume.h" - - -/***************************************************** - * TYPES * - *****************************************************/ - -/* This embeds a struct kobject */ -struct sflc_sysfs_device_s -{ - /* The Device it represents */ - sflc_Device * pdev; - /* The name of this device's subdirectoy under realdevs */ - char * dirname; - - /* The embedded kobject */ - struct kobject kobj; -}; - -/* This embeds a struct device */ -struct sflc_sysfs_volume_s -{ - /* The Volume it represents */ - sflc_Volume * pvol; - - /* The embedded device */ - struct device kdev; -}; - - -/***************************************************** - * PUBLIC VARIABLES DECLARATIONS * - *****************************************************/ - -/* Kobject associated to the /sys/module/dm_sflc/bdevs entry */ -extern struct kobject * sflc_sysfs_bdevs; - -/* Root device (/sys/devices/sflc) that will be every volume's parent. - A bit overkill to have a sflc root device, but it does the trick. */ -extern struct device * sflc_sysfs_root; - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Called on module load/unload */ -int sflc_sysfs_init(void); -void sflc_sysfs_exit(void); - - -/* Device-related functions */ - -/* Creates and registers a sysfs_Device instance. Returns ERR_PTR() on error. */ -sflc_sysfs_Device * sflc_sysfs_devCreateAndAdd(sflc_Device * pdev); - -/* Releases a reference to a sysfs_Device instance */ -void sflc_sysfs_putDev(sflc_sysfs_Device * dev); - -/* Creates a symlink inside the device's subdirectory pointing to the volume's subdirectory */ -int sflc_sysfs_addVolumeToDevice(sflc_sysfs_Device * dev, sflc_sysfs_Volume * vol); - -/* Removes the symlink created before */ -void sflc_sysfs_removeVolumeFromDevice(sflc_sysfs_Device * dev, sflc_sysfs_Volume * vol); - - -/* Volume-related functions */ - -/* Creates and registers a sysfs_Volume instance. Returns ERR_PTR() on error. */ -sflc_sysfs_Volume * sflc_sysfs_volCreateAndAdd(sflc_Volume * pvol); - -/* Releases a reference to a sysfs_Volume instance */ -void sflc_sysfs_putVol(sflc_sysfs_Volume * vol); - - -#endif /* _SFLC_SYSFS_SYSFS_H_ */ diff --git a/dm-sflc/sysfs/volumes.c b/dm-sflc/sysfs/volumes.c deleted file mode 100644 index 2ca2e7f..0000000 --- a/dm-sflc/sysfs/volumes.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include "sysfs.h" -#include "utils/string.h" -#include "log/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define SFLC_SYSFS_VOL_NR_SLICES_ATTR_NAME mapped_slices - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static void sflc_sysfs_volRelease(struct device * kdev); -static ssize_t sflc_sysfs_showVolNrSlices(struct device * dev, struct device_attribute * attr, char * buf); - - -/***************************************************** - * PRIVATE VARIABLES DEFINITIONS * - *****************************************************/ - -/* Attribute showing the number of slices utilised by the volume */ -static const struct device_attribute sflc_sysfs_volNrSlicesAttr = __ATTR( - SFLC_SYSFS_VOL_NR_SLICES_ATTR_NAME, - 0444, - sflc_sysfs_showVolNrSlices, - NULL -); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Creates and registers a sysfs_Volume instance. Returns ERR_PTR() on error. */ -sflc_sysfs_Volume * sflc_sysfs_volCreateAndAdd(sflc_Volume * pvol) -{ - sflc_sysfs_Volume * vol; - int err; - - /* Allocate device */ - vol = kzalloc(sizeof(sflc_sysfs_Volume), GFP_KERNEL); - if (!vol) { - err = -ENOMEM; - pr_err("Could not allocate sysfs_Volume\n"); - goto err_vol_alloc; - } - - /* Set volume */ - vol->pvol = pvol; - - /* Set name */ - err = dev_set_name(&vol->kdev, "%s", pvol->vol_name); - if (err) { - pr_err("Could not set device name %s; error %d\n", pvol->vol_name, err); - goto err_dev_set_name; - } - - /* Set destructor */ - vol->kdev.release = sflc_sysfs_volRelease; - /* Set parent */ - vol->kdev.parent = sflc_sysfs_root; - - /* Register */ - err = device_register(&vol->kdev); - if (err) { - pr_err("Could not register volume %s; error %d\n", pvol->vol_name, err); - goto err_dev_register; - } - - /* Add mapped_slices attribute */ - err = device_create_file(&vol->kdev, &sflc_sysfs_volNrSlicesAttr); - if (err) { - pr_err("Could not create mapped_slices device file; error %d\n", err); - goto err_dev_create_file; - } - - return vol; - - -err_dev_create_file: - device_unregister(&vol->kdev); -err_dev_register: - put_device(&vol->kdev); -err_dev_set_name: - kfree(vol); -err_vol_alloc: - return ERR_PTR(err); -} - -/* Releases a reference to a sysfs_Device instance */ -void sflc_sysfs_putVol(sflc_sysfs_Volume * vol) -{ - device_unregister(&vol->kdev); -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static ssize_t sflc_sysfs_showVolNrSlices(struct device * kdev, struct device_attribute * attr, char * buf) -{ - sflc_sysfs_Volume * vol = container_of(kdev, sflc_sysfs_Volume, kdev); - sflc_Volume * pvol = vol->pvol; - - return sprintf(buf, "%u\n", pvol->mapped_slices); -} - -static void sflc_sysfs_volRelease(struct device * kdev) -{ - sflc_sysfs_Volume * vol; - - /* Cast */ - vol = container_of(kdev, sflc_sysfs_Volume, kdev); - - /* Just free */ - kfree(vol); - - return; -} diff --git a/dm-sflc/target/target.c b/dm-sflc/target/target.c deleted file mode 100644 index afb143a..0000000 --- a/dm-sflc/target/target.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Methods of our DM target - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include "target.h" -#include "device/device.h" -#include "volume/volume.h" -#include "utils/bio.h" -#include "utils/string.h" -#include "log/log.h" - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static int sflc_tgt_ctr(struct dm_target *ti, unsigned int argc, char **argv); -static void sflc_tgt_dtr(struct dm_target *ti); -static int sflc_tgt_map(struct dm_target *ti, struct bio *bio); -static void sflc_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits); -static int sflc_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, - void *data); - -/***************************************************** - * PUBLIC VARIABLES DEFINITIONS * - *****************************************************/ - -struct target_type sflc_target = { - .name = "shufflecake", - .version = {1, 0, 0}, - .module = THIS_MODULE, - .ctr = sflc_tgt_ctr, - .dtr = sflc_tgt_dtr, - .map = sflc_tgt_map, - .status = NULL, - .io_hints = sflc_tgt_ioHints, - .iterate_devices = sflc_tgt_iterateDevices, -}; - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Called every time we create a volume with the userland tool */ -static int sflc_tgt_ctr(struct dm_target *ti, unsigned int argc, char **argv) -{ - char * bdev_path; - int vol_idx; - char * enckey_hex; - u8 enckey[SFLC_SK_KEY_LEN]; - u32 tot_slices; - sflc_Device * dev; - sflc_Volume * vol; - int err; - - /* - * Parse arguments. - * - * argv[0]: underlying block device path - * argv[1]: volume index within the device - * argv[2]: number of 1 MB slices in the underlying device - * argv[3]: 32-byte encryption key (hex-encoded) - */ - if (argc != 4) { - ti->error = "Invalid argument count"; - return -EINVAL; - } - bdev_path = argv[0]; - sscanf(argv[1], "%d", &vol_idx); - sscanf(argv[2], "%u", &tot_slices); - enckey_hex = argv[3]; - - /* Decode the encryption key */ - if (strlen(enckey_hex) != 2 * SFLC_SK_KEY_LEN) { - pr_err("Hexadecimal key (length %lu): %s\n", strlen(enckey_hex), enckey_hex); - ti->error = "Invalid key length"; - return -EINVAL; - } - err = sflc_str_hexDecode(enckey_hex, enckey); - if (err) { - ti->error = "Could not decode hexadecimal encryption key"; - return err; - } - - /* Acquire the big device lock */ - if (down_interruptible(&sflc_dev_mutex)) { - ti->error = "Interrupted while waiting for access to Device"; - return -EINTR; - } - - /* Check if we already have a Device for this bdev */ - dev = sflc_dev_lookupByPath(bdev_path); - if (IS_ERR(dev)) { - ti->error = "Could not look up device by path (interrupted)"; - up(&sflc_dev_mutex); - return PTR_ERR(dev); - } - - /* Otherwise create it (also adds it to the device list) */ - if (!dev) { - pr_notice("Device for %s didn't exist before, going to create it\n", bdev_path); - dev = sflc_dev_create(ti, bdev_path, tot_slices); - } else { - pr_notice("Device on %s already existed\n", bdev_path); - } - - /* Check for device creation errors */ - if (IS_ERR(dev)) { - ti->error = "Could not create device"; - up(&sflc_dev_mutex); - return PTR_ERR(dev); - } - - /* Create the volume (also adds it to the device) */ - vol = sflc_vol_create(ti, dev, vol_idx, enckey); - if (IS_ERR(vol)) { - ti->error = "Error creating volume"; - up(&sflc_dev_mutex); - return PTR_ERR(vol); - } - pr_debug("Now %d volumes are linked to device %s\n", dev->vol_cnt, bdev_path); - - /* Release the big device lock */ - up(&sflc_dev_mutex); - - /* Tell DM we want one SFLC sector at a time */ - ti->max_io_len = SFLC_DEV_SECTOR_SCALE; - /* Enable REQ_OP_FLUSH bios */ - ti->num_flush_bios = 1; - /* Disable REQ_OP_WRITE_ZEROES and REQ_OP_SECURE_ERASE (can't be passed through as - they would break deniability, and they would be too complicated to handle individually) */ - ti->num_secure_erase_bios = 0; - ti->num_write_zeroes_bios = 0; - /* Momentarily disable REQ_OP_DISCARD_BIOS - TODO: will need to support them to release slice mappings */ - ti->num_discard_bios = 0; - /* When we receive a ->map call, we won't need to take the device lock anymore */ - ti->private = vol; - - return 0; -} - -/* Called every time we destroy a volume with the userland tool */ -static void sflc_tgt_dtr(struct dm_target *ti) -{ - sflc_Volume * vol = ti->private; - sflc_Device * dev = vol->dev; - - pr_debug("Destroying volume \"%s\"\n", vol->vol_name); - - /* We do need to take the device lock here, as we'll be modifying the device */ - if (down_interruptible(&sflc_dev_mutex)) { - pr_err("Interrupted while waiting to destroy volume\n"); - return; - } - - /* Destroy volume (also decreases refcount in device) */ - sflc_vol_destroy(ti, vol); - - /* Destroy the device */ - if (dev->vol_cnt == 0) { - pr_notice("Removed the last volume from device\n"); - sflc_dev_destroy(ti, dev); - } - - /* End of critical section */ - up(&sflc_dev_mutex); - - return; -} - -/* Callback for every bio submitted to our virtual block device */ -static int sflc_tgt_map(struct dm_target *ti, struct bio *bio) -{ - int err; - sflc_Volume * vol = ti->private; - - /* If no data, just quickly remap the sector and the block device (no crypto) */ - /* TODO: this is dangerous for deniability, will need more filtering */ - if (unlikely(!bio_has_data(bio))) { - pr_debug("No-data bio: bio_op = %d", bio_op(bio)); - err = sflc_vol_remapBioFast(vol, bio); - if (err) { - pr_err("Could not remap bio; error %d\n", err); - return DM_MAPIO_KILL; - } - return DM_MAPIO_REMAPPED; - } - - /* At this point, the bio has data. Do a few sanity checks */ - /* TODO: I think we can get rid of all of them */ - - /* Check that it is properly aligned and it doesn't cross vector boundaries */ - if (unlikely(!sflc_bio_isAligned(bio))) { - pr_err("Unaligned bio!\n"); - return DM_MAPIO_KILL; - } - /* If it contains more than one SFLC sector, complain with the DM layer and continue */ - if (unlikely(bio->bi_iter.bi_size > SFLC_DEV_SECTOR_SIZE)) { - pr_notice("Large bio of size %u\n", bio->bi_iter.bi_size); - dm_accept_partial_bio(bio, SFLC_DEV_SECTOR_SCALE); - } - /* Check that it contains exactly one SFLC sector */ - if (unlikely(bio->bi_iter.bi_size != SFLC_DEV_SECTOR_SIZE)) { - pr_err("Wrong length (%u) of bio\n", bio->bi_iter.bi_size); - return DM_MAPIO_KILL; - } - - /* Now it is safe, process it */ - err = sflc_vol_processBio(vol, bio); - if (err) { - pr_err("Could not enqueue bio\n"); - return DM_MAPIO_KILL; - } - - return DM_MAPIO_SUBMITTED; -} - -/* Callback executed to inform the DM about our 4096-byte sector size */ -static void sflc_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits) -{ - sflc_Volume * vol = ti->private; - - pr_info("Called io_hints on volume \"%s\"\n", vol->vol_name); - - limits->logical_block_size = SFLC_DEV_SECTOR_SIZE; - limits->physical_block_size = SFLC_DEV_SECTOR_SIZE; - - limits->io_min = SFLC_DEV_SECTOR_SIZE; - limits->io_opt = SFLC_DEV_SECTOR_SIZE; - - return; -} - -/* Callback needed for God knows what, otherwise io_hints never gets called */ -static int sflc_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, - void *data) -{ - sflc_Volume * vol = ti->private; - sflc_Device * dev = vol->dev; - - pr_debug("Called iterate_devices on volume \"%s\"\n", vol->vol_name); - - if (!fn) { - return -EINVAL; - } - return fn(ti, vol->dev->bdev, 0, - (dev->dev_header_size + dev->tot_slices * SFLC_DEV_PHYS_SLICE_SIZE) * SFLC_DEV_SECTOR_SCALE, - data); -} From 64076b2652bd0161249aeda54b14f9d355b62c06 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 3 Aug 2024 12:01:23 +0200 Subject: [PATCH 59/98] Finished unification --- dm-sflc/Kbuild | 4 + dm-sflc/dev_vol.c | 21 ++ dm-sflc/lite/crypto.c | 125 +++++++++++ dm-sflc/lite/device.c | 204 +++++++++++++++++ dm-sflc/lite/posmap.c | 376 ++++++++++++++++++++++++++++++++ dm-sflc/lite/read.c | 168 ++++++++++++++ dm-sflc/lite/sflc_lite.c | 161 ++++++++++++++ dm-sflc/lite/sflc_lite.h | 182 ++++++++++++++++ dm-sflc/lite/sflite_constants.h | 53 +++++ dm-sflc/lite/sysfs.c | 116 ++++++++++ dm-sflc/lite/volume.c | 162 ++++++++++++++ dm-sflc/lite/write.c | 148 +++++++++++++ dm-sflc/old/device/device.c | 7 +- dm-sflc/old/volume/volume.c | 7 +- dm-sflc/sflc.c | 11 +- dm-sflc/sflc.h | 3 + 16 files changed, 1745 insertions(+), 3 deletions(-) create mode 100644 dm-sflc/lite/crypto.c create mode 100644 dm-sflc/lite/device.c create mode 100644 dm-sflc/lite/posmap.c create mode 100644 dm-sflc/lite/read.c create mode 100644 dm-sflc/lite/sflc_lite.c create mode 100644 dm-sflc/lite/sflc_lite.h create mode 100644 dm-sflc/lite/sflite_constants.h create mode 100644 dm-sflc/lite/sysfs.c create mode 100644 dm-sflc/lite/volume.c create mode 100644 dm-sflc/lite/write.c diff --git a/dm-sflc/Kbuild b/dm-sflc/Kbuild index 5c5c705..8b39934 100644 --- a/dm-sflc/Kbuild +++ b/dm-sflc/Kbuild @@ -34,6 +34,10 @@ OBJ_LIST += old/utils/string.o old/utils/bio.o old/utils/pools.o old/utils/workq OBJ_LIST += old/crypto/rand/rand.o OBJ_LIST += old/crypto/symkey/symkey.o old/crypto/symkey/skreq_pool.o +OBJ_LIST += lite/sflc_lite.o lite/sysfs.o +OBJ_LIST += lite/device.o lite/volume.o +OBJ_LIST += lite/posmap.o lite/read.o lite/write.o lite/crypto.o + $(MODULE_NAME)-y += $(OBJ_LIST) diff --git a/dm-sflc/dev_vol.c b/dm-sflc/dev_vol.c index 422831f..7d6f258 100644 --- a/dm-sflc/dev_vol.c +++ b/dm-sflc/dev_vol.c @@ -73,6 +73,13 @@ struct sflc_device *sflc_dev_create(struct dm_target *ti, int argc, char **argv) goto bad_inner; } break; + case SFLC_MODE_LITE: + sdev->sflite_dev = sflite_dev_create(ti, argc, argv, &sdev->kobj); + if (IS_ERR(sdev->sflite_dev)) { + err = PTR_ERR(sdev->sflite_dev); + goto bad_inner; + } + break; default: DMERR("Invalid Shufflecake mode %d", mode); err = -EINVAL; @@ -99,6 +106,9 @@ void sflc_dev_destroy(struct sflc_device *sdev) case SFLC_MODE_LEGACY: sfold_dev_destroy(sdev->sfold_dev); break; + case SFLC_MODE_LITE: + sflite_dev_destroy(sdev->sflite_dev); + break; default: DMCRIT("Destroying device with invalid Shufflecake mode %d", sdev->mode); return; @@ -150,6 +160,14 @@ struct sflc_volume *sflc_vol_create(struct sflc_device *sdev, struct dm_target * } svol->tt = &sfold_target_type; break; + case SFLC_MODE_LITE: + svol->sflite_vol = sflite_vol_create(ti, sdev->sflite_dev, argc, argv, &svol->kobj); + if (IS_ERR(svol->sflite_vol)) { + err = PTR_ERR(svol->sflite_vol); + goto bad_inner; + } + svol->tt = &sflite_target_type; + break; default: DMERR("Invalid Shufflecake mode %d", mode); err = -EINVAL; @@ -175,6 +193,9 @@ void sflc_vol_destroy(struct sflc_volume *svol) case SFLC_MODE_LEGACY: sfold_vol_destroy(svol->sfold_vol); break; + case SFLC_MODE_LITE: + sflite_vol_destroy(svol->sflite_vol); + break; default: DMCRIT("Destroying volume with invalid Shufflecake mode %d", svol->mode); return; diff --git a/dm-sflc/lite/crypto.c b/dm-sflc/lite/crypto.c new file mode 100644 index 0000000..8e27203 --- /dev/null +++ b/dm-sflc/lite/crypto.c @@ -0,0 +1,125 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include +#include "sflite_constants.h" + +#include "sflc_lite.h" + + +/** + * Encrypt/decrypt exactly one block, already encoded in the scatterlist. + * All other crypto functions reduce to this one. + * The IV is constructed as the right-0-padded LE representation of the + * physical block number, which is exactly what dm-crypt does when using the + * IV mode "plain64". + */ +static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, + struct scatterlist *dst, u64 pblk_num, int rw) +{ + u8 iv[SFLITE_XTS_IVLEN]; + struct skcipher_request *req = NULL; + DECLARE_CRYPTO_WAIT(wait); + int err; + + // TODO not too sure about the gfp_mask here + // TODO move @req into struct sflite_io? + req = skcipher_request_alloc(tfm, GFP_NOIO); + if (!req) + return -ENOMEM; + + skcipher_request_set_callback(req, + CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &wait); + + /* Construct IV */ + memset(iv, 0, SFLITE_XTS_IVLEN); + *(__le64 *)iv = cpu_to_le64(pblk_num); + + skcipher_request_set_crypt(req, src, dst, SFLITE_BLOCK_SIZE, iv); + if (rw == READ) + err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); + else + err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait); + skcipher_request_free(req); + + return err; +} + +/* Encrypt-decrypt a single block (memory buffer is a page) */ +int sflite_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw) +{ + struct scatterlist dst, src, *p_dst; + bool is_inplace; + + /* Use same scatterlist if in-place */ + is_inplace = (src_page == dst_page); + p_dst = is_inplace ? &src : &dst; + + /* We assume PAGE_SIZE == SFLITE_BLOCK_SIZE */ + /* And orig_bio to start at offset 0 within the page */ + sg_init_table(&src, 1); + sg_set_page(&src, src_page, SFLITE_BLOCK_SIZE, 0); + if (!is_inplace) { + sg_init_table(&dst, 1); + sg_set_page(&dst, dst_page, SFLITE_BLOCK_SIZE, 0); + } + + return crypt_sg(tfm, &src, p_dst, pblk_num, rw); +} + + +/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ +int sflite_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw) +{ + struct scatterlist dst, src, *p_dst; + u64 pblk_num; + bool is_inplace; + int err; + + /* Use same scatterlist if in-place */ + is_inplace = (src_buf == dst_buf); + p_dst = is_inplace ? &src : &dst; + + for (pblk_num = first_pblk_num; + pblk_num < first_pblk_num + num_blocks; + pblk_num++) { + sg_init_one(&src, src_buf, SFLITE_BLOCK_SIZE); + if (!is_inplace) + sg_init_one(&dst, dst_buf, SFLITE_BLOCK_SIZE); + + err = crypt_sg(tfm, &src, p_dst, pblk_num, rw); + if (err) + return err; + + src_buf += SFLITE_BLOCK_SIZE; + dst_buf += SFLITE_BLOCK_SIZE; + } + + return 0; +} + diff --git a/dm-sflc/lite/device.c b/dm-sflc/lite/device.c new file mode 100644 index 0000000..e62aa54 --- /dev/null +++ b/dm-sflc/lite/device.c @@ -0,0 +1,204 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include +#include +#include "sflc_lite.h" + + + +/* Depth of the mempool backing the bio_set */ +#define SFLITE_BIOSET_BIOS 64 + + +/* Fisher-Yates shuffle */ +static void fisheryates_u32(u32 *v, u32 len) +{ + u32 i, j; + + for (i = len-1; i >= 1; i--) { + j = get_random_u32_below(i+1); + swap(v[i], v[j]); + } + + return; +} + +/** + * Arguments: + * argv[0]: Shufflecake mode: legacy/lite + * argv[1]: Shufflecake-unique device ID + * argv[2]: path to underlying physical device + * argv[3]: volume index within the device + * argv[4]: number of 1 MB slices in the underlying device + * argv[5]: 64-byte encryption key (hex-encoded) + */ +struct sflite_device *sflite_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj) +{ + struct sflite_device *sdev; + dev_t devt; + u32 dev_id; + u32 tot_slices; + int i; + int err; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) { + DMERR("Could not allocate device"); + return ERR_PTR(-ENOMEM); + } + + /* Parse args */ + if (argc != 6) { + pr_err("Wrong argument count"); + err = -EINVAL; + goto bad_parse; + } + sscanf(argv[1], "%u", &dev_id); + if (sscanf(argv[4], "%u", &tot_slices) != 1) { + pr_err("Could not decode tot_slices\n"); + err = -EINVAL; + goto bad_parse; + } + + sdev->dev_id = dev_id; + + /* Look up block device and set name */ + err = lookup_bdev(argv[2], &devt); + if (err) { + DMERR("Could not look up block device"); + goto bad_lookup; + } + format_dev_t(sdev->name, devt); + + /* Compute sizes */ + sdev->tot_slices = tot_slices; + sdev->nr_free_slices = tot_slices; + /* Enough posmap blocks to fit all the entries */ + sdev->posmap_size_sectors = SFLITE_BLOCK_SCALE * + DIV_ROUND_UP(tot_slices, SFLITE_PSIS_PER_BLOCK); + /* DMB + VMBs + PosMaps */ + sdev->dev_header_size_sectors = SFLITE_BLOCK_SCALE + + (SFLITE_DEV_MAX_VOLUMES * SFLITE_BLOCK_SCALE) + + (SFLITE_DEV_MAX_VOLUMES * sdev->posmap_size_sectors); + + /* Shuffled PSIs */ + mutex_init(&sdev->slices_lock); + sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); + if (!sdev->slices_ofld) { + DMERR("Could not allocate PSI occupation bitfield"); + err = -ENOMEM; + goto bad_ofld; + } + + sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); + if (!sdev->prmslices) { + DMERR("Could not allocate shuffled PSI array"); + err = -ENOMEM; + goto bad_prmslices; + } + /* Generate a permutation */ + for (i = 0; i < tot_slices; i++) + sdev->prmslices[i] = i; + fisheryates_u32(sdev->prmslices, tot_slices); + sdev->prmslices_octr = 0; + + /* Bioset */ + err = bioset_init(&sdev->bioset, SFLITE_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); + if (err) { + DMERR("Could not init bioset; error %d", err); + goto bad_bioset; + } + + /* Client for dm-io */ + sdev->io_client = dm_io_client_create(); + if (IS_ERR(sdev->io_client)) { + err = PTR_ERR(sdev->io_client); + DMERR("Could not create dm-io client; error %d", err); + goto bad_dmio_client; + } + + /* I/O workqueue */ + sdev->io_queue = alloc_workqueue("sflite_%s_io", + WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, + 0, sdev->name); + if (!sdev->io_queue) { + err = -ENOMEM; + DMERR("Could not allocate I/O workqueue"); + goto bad_io_wq; + } + /* Decryption workqueue */ + sdev->crypt_queue = alloc_workqueue("sflite_%s_crypt", + WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, + 0, sdev->name); + if (!sdev->crypt_queue) { + err = -ENOMEM; + DMERR("Could not allocate decryption workqueue"); + goto bad_crypt_wq; + } + + /* Add to sysfs, once initialised */ + sdev->kobj_parent = kobj; + err = sflite_sysfs_add_device(sdev); + if (err) + goto bad_sysfs; + + return sdev; + + +bad_sysfs: + destroy_workqueue(sdev->crypt_queue); +bad_crypt_wq: + destroy_workqueue(sdev->io_queue); +bad_io_wq: + dm_io_client_destroy(sdev->io_client); +bad_dmio_client: + bioset_exit(&sdev->bioset); +bad_bioset: + vfree(sdev->prmslices); +bad_prmslices: + vfree(sdev->slices_ofld); +bad_ofld: +bad_lookup: +bad_parse: + kfree(sdev); + return ERR_PTR(err); +} + + +void sflite_dev_destroy(struct sflite_device *sdev) +{ + sflite_sysfs_remove_device(sdev); + destroy_workqueue(sdev->crypt_queue); + destroy_workqueue(sdev->io_queue); + dm_io_client_destroy(sdev->io_client); + bioset_exit(&sdev->bioset); + vfree(sdev->prmslices); + vfree(sdev->slices_ofld); + kfree(sdev); + + return; +} + diff --git a/dm-sflc/lite/posmap.c b/dm-sflc/lite/posmap.c new file mode 100644 index 0000000..eb57072 --- /dev/null +++ b/dm-sflc/lite/posmap.c @@ -0,0 +1,376 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include "sflc_lite.h" + + +/* Helpers */ + +#define IS_PSI_TAKEN(sdev, psi) ( (sdev)->slices_ofld[(psi)] ) +#define NEXT_RANDOM_PSI(sdev) ( (sdev)->prmslices[(sdev)->prmslices_octr] ) +#define IS_LAST_LSI_IN_BLOCK(lsi, sdev) ( (((lsi)+1) % SFLITE_PSIS_PER_BLOCK == 0) || \ + (((lsi)+1) == (sdev)->tot_slices) ) + + +/* + *---------------------------- + * Create slice mapping + *---------------------------- + */ + +/** + * Return the next free PSI in the device's shuffled array, without modifying + * the device state. + * + * MUTEX: @sdev->slices_lock must be held. + */ +static int peek_next_free_psi(struct sflite_device *sdev, u32 *psi) +{ + if (unlikely(!sdev->nr_free_slices)) + return -ENOSPC; + if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) { + DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); + print_hex_dump(KERN_CRIT, "prmslices(REV) ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); + msleep(10000); + print_hex_dump(KERN_CRIT, "ofld(REV) ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); + msleep(10000); + return -ENOTRECOVERABLE; // Grave inconsistency + } + + /* Invariant: @prmslices_octr points to a free slice */ + *psi = NEXT_RANDOM_PSI(sdev); + if (unlikely(IS_PSI_TAKEN(sdev, *psi))){ + DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); + DMCRIT("PSI %u is occupied", *psi); + print_hex_dump(KERN_CRIT, "prmslices ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); + msleep(10000); + print_hex_dump(KERN_CRIT, "ofld ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); + msleep(10000); + return -ENOTRECOVERABLE; // Grave inconsistency + } + + return 0; +} + +/** + * Map LSI => PSI, only in memory. + * Sanity checks to be performed by the caller. + * + * MUTEX: @sdev->slices_lock must be held. + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static void _create_local_slice_mapping(struct sflite_volume *svol, u32 lsi, u32 psi) +{ + struct sflite_device *sdev = svol->sdev; + + /* Grab it from the device */ + sdev->slices_ofld[psi] = true; + sdev->nr_free_slices--; + // Preserve the invariant: @prmslices_octr must point to a free slice + while(sdev->prmslices_octr < sdev->tot_slices && + IS_PSI_TAKEN(sdev, NEXT_RANDOM_PSI(sdev))) { + sdev->prmslices_octr++; + } + + /* Insert in the volume */ + svol->posmap[lsi] = psi; + svol->nr_mapped_slices++; + + return; +} + +/** + * Delete mapping for the given LSI, only in memory. + * Sanity checks to be performed by the caller. + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static void _delete_local_slice_mapping(struct sflite_volume *svol, u32 lsi) +{ + /* Delete mapping in the volume */ + svol->posmap[lsi] = SFLITE_PSI_INVALID; + svol->nr_mapped_slices--; + + /* Don't do anything in the device though, leave it there: we don't yet + * have an obvious way to release PSIs. + * This means a PSI will be incorrectly marked as occupied, but that's + * not too bad: the PSI shuffling and its occupation counter are + * ephemeral, so they reset if you close and reopen all the volumes. */ + return; +} + +/** + * Synchronously store (and flush) the given posmap block + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + */ +static int store_posmap_block(struct sflite_volume *svol, u32 posmap_block_num) +{ + struct sflite_device *sdev = svol->sdev; + struct page *page; + struct bio *bio; + int err; + + /* Sync + flush TODO GFP mask ok? */ + bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, + REQ_OP_WRITE | REQ_SYNC | REQ_FUA, GFP_NOIO, + &sdev->bioset); + if (!bio) { + DMERR("Could not allocate posmap block bio"); + return -ENOMEM; + } + bio->bi_iter.bi_sector = SFLITE_POSMAP_START_SECTOR(svol) + + (posmap_block_num << SFLITE_BLOCK_SHIFT); + + /* Alloc and add page TODO GFP mask */ + page = alloc_page(GFP_NOIO); + if (!page) { + DMERR("Could not allocate posmap block page"); + err = -ENOMEM; + goto bad_alloc_page; + } + // TODO remove this error check + if (unlikely(!bio_add_page(bio, page, SFLITE_BLOCK_SIZE, 0))) { + DMCRIT("Could not add posmap block page to bio!"); + err = -ENOTRECOVERABLE; + goto bad_add_page; + } + + /* Serialise posmap block onto the page */ + void *page_ptr = kmap_local_page(page); + u32 first_lsi = posmap_block_num * SFLITE_PSIS_PER_BLOCK; + u32 last_lsi = min(first_lsi + SFLITE_PSIS_PER_BLOCK, sdev->tot_slices); + u32 lsi; + for (lsi = first_lsi; lsi < last_lsi; lsi++) { + u32 psi = svol->posmap[lsi]; + __be32 *be_psi = (__be32*) (page_ptr + ((lsi-first_lsi) * sizeof(__be32))); + *be_psi = cpu_to_be32(psi); + } + +// print_hex_dump(KERN_WARNING, "page_ptr(REV) ", DUMP_PREFIX_OFFSET, 32, 4, page_ptr, SFLITE_BLOCK_SIZE, false); +// msleep(100); + + kunmap_local(page_ptr); + + /* Encrypt the block in place */ + err = sflite_crypt_block_page(svol->tfm, page, page, + bio->bi_iter.bi_sector >> SFLITE_BLOCK_SHIFT, WRITE); + if (err) { + DMERR("Could not encrypt posmap block; error %d", err); + goto bad_encrypt; + } + + /* Submit */ + err = submit_bio_wait(bio); + if (err) + DMERR("Could not complete posmap block bio; error %d", err); + +bad_encrypt: +bad_add_page: + __free_page(page); +bad_alloc_page: + bio_put(bio); + return err; +} + + +/** + * Create a new mapping for the given LSI, and synchronise back to disk. + * + * MUTEX: @svol->posmap_lock must be held, except under volume ctor. + * MUTEX: takes @sdev->slices_lock. + * + * Syncing to disk means the posmap lock will be held (by the caller) for a long + * time thus blocking out all the other incoming bio's, even unrelated ones + * (falling in different slices). Several strategies are possible to avoid this + * problem, but for now we keep this simple implementation. + */ +int sflite_create_persistent_slice_mapping(struct sflite_volume *svol, u32 lsi, u32 *psi) +{ + struct sflite_device *sdev = svol->sdev; + int err; + + /* Bounds check TODO redundant? */ + if(unlikely(lsi >= svol->sdev->tot_slices)) + return -EINVAL; + /* Check mapping not existent TODO redundant? */ + if (unlikely(svol->posmap[lsi] != SFLITE_PSI_INVALID)) + return -EINVAL; + + /* Create mapping */ + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + err = peek_next_free_psi(sdev, psi); + if (err) { + mutex_unlock(&sdev->slices_lock); + return err; + } + _create_local_slice_mapping(svol, lsi, *psi); + mutex_unlock(&sdev->slices_lock); + + /* Write posmap block to disk */ + err = store_posmap_block(svol, lsi/SFLITE_PSIS_PER_BLOCK); + if (err) { + DMERR("Could not store posmap block; error %d", err); + _delete_local_slice_mapping(svol, lsi); + return err; + } + + return 0; +} + + +/* + *---------------------------- + * Load position map + *---------------------------- + */ + +/** + * Synchronously read the entire on-disk encrypted position map + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + */ +static int read_encrypted_posmap(struct sflite_volume *svol) +{ + struct dm_io_request io_req = { + .bi_opf = REQ_OP_READ | REQ_SYNC, + .mem.type = DM_IO_VMA, + .mem.ptr.vma = svol->posmap, + .notify.fn = NULL, + .client = svol->sdev->io_client + }; + struct dm_io_region io_region = { + .bdev = svol->dm_dev->bdev, + .sector = SFLITE_POSMAP_START_SECTOR(svol), + .count = svol->sdev->posmap_size_sectors + }; + + return dm_io(&io_req, 1, &io_region, NULL); +} + +/** + * De-serialise the position map entries. On the fly, if a conflict is detected, + * resolve it by sampling a new PSI, and sync to disk (block by block). + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + * MUTEX: @sdev->slices_lock must be held. + */ +static int _deserialise_and_sanitise_posmap(struct sflite_volume *svol) +{ + struct sflite_device *sdev = svol->sdev; + void *posmap_ptr = svol->posmap; + u32 lsi; + bool posmap_block_dirty; + int err; + + for (lsi = 0; lsi < sdev->tot_slices; lsi++) { + /* Reset dirty bit at the start of every posmap block */ + if (lsi % SFLITE_PSIS_PER_BLOCK == 0) + posmap_block_dirty = false; + + /* De-serialise posmap entry */ + __be32 *be_psi = (__be32*) (posmap_ptr + (lsi * sizeof(__be32))); + u32 psi = be32_to_cpu(*be_psi); + + /* If LSI unmapped, skip mapping creation */ + if (psi == SFLITE_PSI_INVALID) { + svol->posmap[lsi] = psi; + goto skip_create_mapping; + } + + /* If PSI out of bounds, something's seriously wrong */ + if (psi >= sdev->tot_slices) { + DMERR("Decrypted PSI out of bounds: %u >= %u", psi, sdev->tot_slices); + return -EDOM; + } + + /* If PSI already taken, sample a new one */ + if (sdev->slices_ofld[psi]) { + DMWARN("Corruption of volume %u: LSI %u was evicted from PSI %u", + svol->vol_idx, lsi, psi); + err = peek_next_free_psi(sdev, &psi); + if (err) + return err; + posmap_block_dirty = true; + } + /* Whether sanitised or not, create the mapping locally */ + _create_local_slice_mapping(svol, lsi, psi); + +skip_create_mapping: + + /* Only check dirty bit at the end of the posmap block */ + if (posmap_block_dirty && + IS_LAST_LSI_IN_BLOCK(lsi, sdev)) { + err = store_posmap_block(svol, lsi/SFLITE_PSIS_PER_BLOCK); + if (err) + return err; + } + } + + return 0; +} + + +/** + * Load the volume's position map from the disk. If some conflicts are present + * (i.e. an LSI is mapped to a PSI that's already taken), then resolve them + * (i.e. re-sample a free PSI for the "unlucky" LSI) and sync back to disk. + * + * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). + * MUTEX: takes @sdev->slices_lock. + */ +int sflite_load_and_sanitise_posmap(struct sflite_volume *svol) +{ + int err; + struct sflite_device *sdev = svol->sdev; + + /* Read raw posmap from disk */ + err = read_encrypted_posmap(svol); + if (err) + return err; + + /* Decrypt in place */ + err = sflite_crypt_blocks_vm(svol->tfm, svol->posmap, svol->posmap, + svol->sdev->posmap_size_sectors >> SFLITE_BLOCK_SHIFT, + SFLITE_POSMAP_START_SECTOR(svol) >> SFLITE_BLOCK_SHIFT, + READ); + if (err) + return err; + + /* Deserialise and sanitise as you go */ + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + err = _deserialise_and_sanitise_posmap(svol); + mutex_unlock(&sdev->slices_lock); + if (err) + return err; + +// print_hex_dump(KERN_CRIT, "posmap(REV) ", DUMP_PREFIX_OFFSET, 32, 4, svol->posmap, 4*sdev->tot_slices, false); +// msleep(2000); + + return 0; +} + diff --git a/dm-sflc/lite/read.c b/dm-sflc/lite/read.c new file mode 100644 index 0000000..c7a2016 --- /dev/null +++ b/dm-sflc/lite/read.c @@ -0,0 +1,168 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "sflc_lite.h" +#include + + +static void sflite_read_endio(struct bio *phys_bio); +static void sflite_decrypt_work_fn(struct work_struct *work); + + +/* Landing here from ->map() through the io_queue */ +void sflite_read_work_fn(struct work_struct *work) +{ + struct sflite_io *sio = container_of(work, struct sflite_io, work); + struct sflite_volume *svol = sio->svol; + struct sflite_device *sdev = svol->sdev; + struct bio *orig_bio = sio->orig_bio; + struct bio *phys_bio; + u32 lsi = sio->lsi; + u32 block_offset = sio->block_offset; + u32 psi; + + /* Read position map */ + if (mutex_lock_interruptible(&svol->posmap_lock)) { + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + psi = svol->posmap[lsi]; + mutex_unlock(&svol->posmap_lock); + + /* If LSI is unmapped, short-circuit and return all zeros */ + if (psi == SFLITE_PSI_INVALID) { + + zero_fill_bio(orig_bio); + orig_bio->bi_status = BLK_STS_OK; + goto endio; + } + sio->psi = psi; + +// DMWARN("READ: LSI=%u, PSI=%u, offset=%u", lsi, psi, sio->block_offset); +// msleep(100); + + /* Shallow-copy the bio and submit it (different bi_endio). + We can shallow-copy because we don't need to own the pages, + we can decrypt in place. */ + + + //DMWARN("READ: shallow copying"); + //msleep(500); + + /* Shallow copy */ + phys_bio = bio_alloc_clone(svol->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); + if (!phys_bio) { + DMERR("Could not clone original bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + /* Insert in the I/O struct */ + sio->phys_bio = phys_bio; + +// DMWARN("READ: submitting bio"); +// msleep(500); + + /* Remap sector */ + phys_bio->bi_iter.bi_sector = SFLITE_PHYS_BIO_SECTOR(sdev, psi, block_offset); + /* Set fields for the endio */ + phys_bio->bi_private = sio; + phys_bio->bi_end_io = sflite_read_endio; + /* Submit */ + dm_submit_bio_remap(orig_bio, phys_bio); + + return; + + +endio: + bio_endio(orig_bio); + return; +} + + +/* ISR for the phys_bio */ +static void sflite_read_endio(struct bio *phys_bio) +{ + struct sflite_io *sio = phys_bio->bi_private; + +// DMWARN("READ ENDIO: queueing decryption"); +// //msleep(500); + + /* Can't decrypt here in ISR: submit to decryption workqueue. + * Can reuse the same work item, though, since it was popped out of the + * io_queue already */ + INIT_WORK(&sio->work, sflite_decrypt_work_fn); + queue_work(sio->svol->sdev->crypt_queue, &sio->work); +} + + +/* Decrypt and endio */ +static void sflite_decrypt_work_fn(struct work_struct *work) +{ + struct sflite_io *sio = container_of(work, struct sflite_io, work); + struct sflite_volume *svol = sio->svol; + struct bio *orig_bio = sio->orig_bio; + struct bio *phys_bio = sio->phys_bio; + struct bio_vec bvl = bio_iovec(orig_bio); + int err; + + /* If physical bio failed, then fail-fast */ + if (phys_bio->bi_status != BLK_STS_OK) { + orig_bio->bi_status = phys_bio->bi_status; + goto endio; + } + +// DMWARN("DECRYPT FN: decrypting page in place"); +// msleep(2000); + + /* Decrypt page in-place */ + err = sflite_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_page, + SFLITE_PHYS_BIO_SECTOR(svol->sdev, sio->psi, sio->block_offset) >> SFLITE_BLOCK_SHIFT, + READ); + if (err) { + DMERR("Could not decrypt bio; error %d", err); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + +// print_hex_dump(KERN_WARNING, "readpage ", DUMP_PREFIX_OFFSET, 32, 1, bvl.bv_page, SFLITE_BLOCK_SIZE, true); +// msleep(2000); + +// DMWARN("DECRYPT FN: bio_advance"); +// msleep(300); + + /* Advance original bio by one block */ + bio_advance(orig_bio, SFLITE_BLOCK_SIZE); + orig_bio->bi_status = BLK_STS_OK; + +endio: + /* Free the physical bio */ +// DMWARN("DECRYPT FN: bio_put"); +// msleep(300); + bio_put(phys_bio); + /* End original bio */ +// DMWARN("DECRYPT FN: bio_endio\n\n\n\n"); +// msleep(300); + bio_endio(orig_bio); + + return; +} diff --git a/dm-sflc/lite/sflc_lite.c b/dm-sflc/lite/sflc_lite.c new file mode 100644 index 0000000..edc245f --- /dev/null +++ b/dm-sflc/lite/sflc_lite.c @@ -0,0 +1,161 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include +#include +#include "sflite_constants.h" +#include "sflc_lite.h" + +// Only to import the definition of struct sflc_volume +#include "sflc.h" + +#include + + +/* + *---------------------------- + * Device mapper target + *---------------------------- + */ + +static int sflite_map(struct dm_target *ti, struct bio *bio) +{ + struct sflite_io *sio = dm_per_bio_data(bio, sizeof(struct sflite_io)); + struct sflc_volume *top_vol = ti->private; + struct sflite_volume *svol = top_vol->sflite_vol; + sector_t lblk_num = bio->bi_iter.bi_sector >> SFLITE_BLOCK_SHIFT; + + if (unlikely(!bio_has_data(bio))) { +// DMWARN("No-data bio: bio_op() = %d, bi_opf = %u, bi_io_vec = %p, bi_idx = %u", bio_op(bio), bio->bi_opf, bio->bi_io_vec, bio->bi_iter.bi_idx); +// msleep(100); + } + + /* Flush requests are just passed down, since our position map is + * currently write-through, so we have no volatile cache */ + if (unlikely(bio->bi_opf & REQ_PREFLUSH)) { + /* Has to be empty though */ + if (bio_sectors(bio)) { + DMWARN("Non-empty flush request!"); + msleep(3000); + return DM_MAPIO_KILL; + } +// DMWARN("REQ_PREFLUSH empty (phew), sector: %llu", bio->bi_iter.bi_sector); +// msleep(100); + bio_set_dev(bio, svol->dm_dev->bdev); + return DM_MAPIO_REMAPPED; + } + + /* Accept one block at a time TODO improve */ + if (unlikely(bio->bi_iter.bi_size > SFLITE_BLOCK_SIZE)) { + DMWARN("Big bio: %u", bio->bi_iter.bi_size); + msleep(300); + dm_accept_partial_bio(bio, SFLITE_BLOCK_SCALE); + } + /* Only one segment, single page, starting at 0 TODO improve */ + if (unlikely(bio_segments(bio) > 1 || + bio_offset(bio) != 0)) { + DMWARN("Unaligned bio!"); + msleep(3000); + return DM_MAPIO_KILL; + } + if (unlikely(bio->bi_iter.bi_size != SFLITE_BLOCK_SIZE)) { + DMWARN("Wrong bio size: %u", bio->bi_iter.bi_size); + msleep(3000); + return DM_MAPIO_KILL; + } + + /* Init I/O struct */ + sio->svol = svol; + sio->orig_bio = bio; + sio->lsi = lblk_num >> SFLITE_SLICE_SHIFT; + sio->block_offset = lblk_num & ((1U << SFLITE_SLICE_SHIFT) - 1); + + /* Enqueue */ + if (bio_data_dir(bio) == READ) + INIT_WORK(&sio->work, sflite_read_work_fn); + else + INIT_WORK(&sio->work, sflite_write_work_fn); + queue_work(svol->sdev->io_queue, &sio->work); + + return DM_MAPIO_SUBMITTED; +} + + +static void sflite_io_hints(struct dm_target *ti, struct queue_limits *limits) +{ + // Currently, we only handle one block at a time TODO improve + limits->logical_block_size = SFLITE_BLOCK_SIZE; + limits->physical_block_size = SFLITE_BLOCK_SIZE; + limits->io_min = SFLITE_BLOCK_SIZE; + limits->io_opt = SFLITE_BLOCK_SIZE; + + return; +} + + +static int sflite_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) +{ + struct sflc_volume *top_vol = ti->private; + struct sflite_volume *svol = top_vol->sflite_vol; + struct sflite_device *sdev = svol->sdev; + + if (!fn) { + dump_stack(); + msleep(2000); + return -EINVAL; + } + return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + ti->len, data); +} + + +struct target_type sflite_target_type = { + .map = sflite_map, + .io_hints = sflite_io_hints, + .iterate_devices = sflite_iterate_devices, +}; + + +/* + *---------------------------- + * Init and exit + *---------------------------- + */ + +int sflite_init(void) +{ + /* For the moment, we assume PAGE_SIZE == SFLITE_BLOCK_SIZE TODO improve */ + if (SFLITE_BLOCK_SIZE != PAGE_SIZE) { + DMERR("Error, PAGE_SIZE != %d bytes not yet supported", SFLITE_BLOCK_SIZE); + return -ENOTRECOVERABLE; + } + + return 0; +} + + +void sflite_exit(void) +{ + return; +} + diff --git a/dm-sflc/lite/sflc_lite.h b/dm-sflc/lite/sflc_lite.h new file mode 100644 index 0000000..1c2e5a7 --- /dev/null +++ b/dm-sflc/lite/sflc_lite.h @@ -0,0 +1,182 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _SFLITE_SFLITE_H +#define _SFLITE_SFLITE_H + +#include +#include +#include +#include +#include "sflite_constants.h" +#include "sflc_constants.h" + + +/* + *---------------------------- + * Structs + *---------------------------- + */ + +struct sflite_device +{ + /* Shufflecake-unique device ID */ + u32 dev_id; + /* : */ + char name[16]; + + /* Logical size of each volume */ + u32 tot_slices; + /* Header sizes in 512-byte sectors */ + sector_t posmap_size_sectors; + sector_t dev_header_size_sectors; + + /* Shuffled array of PSIs */ + struct mutex slices_lock; + u32 *prmslices; + u32 prmslices_octr; + bool *slices_ofld; + u32 nr_free_slices; + + /* Parent sysfs directory */ + struct kobject *kobj_parent; + + /* Resource sharing */ + struct bio_set bioset; + struct dm_io_client *io_client; + struct workqueue_struct *io_queue; + struct workqueue_struct *crypt_queue; +}; + +struct sflite_volume +{ + /* Backing device */ + struct sflite_device *sdev; + + /* Underlying block device. This can't go in the sflite_device struct, + * because each ti grabs its own reference. */ + struct dm_dev *dm_dev; + struct dm_target *ti; + + /* Volume index within the device */ + u32 vol_idx; + /* Volume name: sflite__ */ + char name[32]; + + /* Position map */ + struct mutex posmap_lock; + u32 *posmap; + u32 nr_mapped_slices; + + /* Parent sysfs directory */ + struct kobject *kobj_parent; + + /* Crypto */ + u8 enckey[SFLITE_XTS_KEYLEN]; + struct crypto_skcipher *tfm; +}; + +struct sflite_io +{ + struct sflite_volume *svol; + + struct bio *orig_bio; + struct bio *phys_bio; + u32 lsi; + u32 block_offset; + u32 psi; + + struct work_struct work; +}; + + +/* + *---------------------------- + * Macros + *---------------------------- + */ + +/* Starting sector of position map */ +#define SFLITE_POSMAP_START_SECTOR(svol) \ + (SFLITE_BLOCK_SCALE * (1 + SFLITE_DEV_MAX_VOLUMES) + \ + (svol)->vol_idx * (svol)->sdev->posmap_size_sectors) + + +/* Physical sector of a remapped bio */ +#define SFLITE_PHYS_BIO_SECTOR(sdev, psi, off) ( \ + (sdev)->dev_header_size_sectors + ( \ + ((psi << SFLITE_SLICE_SHIFT) + off) << SFLITE_BLOCK_SHIFT \ + ) \ +) + + +/* + *---------------------------- + * Public variables + *---------------------------- + */ + +extern struct target_type sflite_target_type; + + +/* + *---------------------------- + * Functions + *---------------------------- + */ + +/* Init and exit */ +int sflite_init(void); +void sflite_exit(void); + +/* Device */ +struct sflite_device *sflite_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj); +void sflite_dev_destroy(struct sflite_device *sdev); + +/* Volume */ +struct sflite_volume *sflite_vol_create(struct dm_target *ti, struct sflite_device *sdev, + int argc, char **argv, struct kobject *kobj); +void sflite_vol_destroy(struct sflite_volume *svol); + +/* Sysfs */ +int sflite_sysfs_add_device(struct sflite_device *sdev); +void sflite_sysfs_remove_device(struct sflite_device *sdev); +int sflite_sysfs_add_volume(struct sflite_volume *svol); +void sflite_sysfs_remove_volume(struct sflite_volume *svol); + +/* Bio mapping */ +void sflite_read_work_fn(struct work_struct *work); +void sflite_write_work_fn(struct work_struct *work); + +/* Position map */ +int sflite_load_and_sanitise_posmap(struct sflite_volume *svol); +int sflite_create_persistent_slice_mapping(struct sflite_volume *svol, u32 lsi, u32 *psi); + +/* Crypto */ +int sflite_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw); +int sflite_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, + struct page *dst_page, u64 pblk_num, int rw); + + +#endif /* _SFLITE_SFLITE_H */ diff --git a/dm-sflc/lite/sflite_constants.h b/dm-sflc/lite/sflite_constants.h new file mode 100644 index 0000000..801ff59 --- /dev/null +++ b/dm-sflc/lite/sflite_constants.h @@ -0,0 +1,53 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/* Constants specific to Shufflecake Lite */ + +#ifndef _SFLITE_SFLITE_CONSTANTS_H_ +#define _SFLITE_SFLITE_CONSTANTS_H_ + + +#define SFLITE_BLOCK_SIZE 4096 /* bytes */ +#define SFLITE_BLOCK_SHIFT 3 +#define SFLITE_BLOCK_SCALE (1 << SFLITE_BLOCK_SHIFT) /* 8 sectors in a block */ +#define SFLITE_SLICE_SHIFT 8 +#define SFLITE_SLICE_SCALE (1 << SFLITE_SLICE_SHIFT) /* 256 blocks in a slice */ + + +/* XTS requires doubling the key size */ +#define SFLITE_XTS_KEYLEN 64 /* bytes */ +/* The IV is the right-0-padded LE physical block number */ +#define SFLITE_XTS_IVLEN 16 /* bytes */ + + +#define SFLITE_DEV_MAX_VOLUMES 15 +#define SFLITE_MAX_DEVS 1024 + + +#define SFLITE_PSI_INVALID 0xFFFFFFFF +/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ +#define SFLITE_PSIS_PER_BLOCK 1024 + + + +#endif /* _SFLITE_SFLITE_CONSTANTS_H_ */ diff --git a/dm-sflc/lite/sysfs.c b/dm-sflc/lite/sysfs.c new file mode 100644 index 0000000..88e2d06 --- /dev/null +++ b/dm-sflc/lite/sysfs.c @@ -0,0 +1,116 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "sflc_lite.h" + +// Only to import the definitions of structs sflc_volume and sflc_device +#include "sflc.h" + +/* + *---------------------------- + * Device entries + *---------------------------- + */ + +static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *top_dev = container_of(kobj, struct sflc_device, kobj); + struct sflite_device *sdev = top_dev->sflite_dev; + + return sysfs_emit(buf, "%u\n", sdev->tot_slices); +} + +static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_device *top_dev = container_of(kobj, struct sflc_device, kobj); + struct sflite_device *sdev = top_dev->sflite_dev; + int ret; + + if (mutex_lock_interruptible(&sdev->slices_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", sdev->nr_free_slices); + mutex_unlock(&sdev->slices_lock); + + return ret; +} + +static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); +static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); +static struct attribute *sflite_device_attrs[] = { + &tot_slices_kattr.attr, + &free_slices_kattr.attr, + NULL +}; +static const struct attribute_group sflite_device_attr_group = { + .attrs = sflite_device_attrs, +}; + +int sflite_sysfs_add_device(struct sflite_device *sdev) +{ + return sysfs_create_group(sdev->kobj_parent, &sflite_device_attr_group); +} + +void sflite_sysfs_remove_device(struct sflite_device *sdev) +{ + sysfs_remove_group(sdev->kobj_parent, &sflite_device_attr_group); +} + + +/* + *---------------------------- + * Volume entries + *---------------------------- + */ + +static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) +{ + struct sflc_volume *top_vol = container_of(kobj, struct sflc_volume, kobj); + struct sflite_volume *svol = top_vol->sflite_vol; + int ret; + + if (mutex_lock_interruptible(&svol->posmap_lock)) + return -ERESTARTSYS; + ret = sysfs_emit(buf, "%u\n", svol->nr_mapped_slices); + mutex_unlock(&svol->posmap_lock); + + return ret; +} + +static struct kobj_attribute mapped_slices_kattr = __ATTR_RO(mapped_slices); +static struct attribute *sflite_volume_attrs[] = { + &mapped_slices_kattr.attr, + NULL +}; +static const struct attribute_group sflite_volume_attr_group = { + .attrs = sflite_volume_attrs, +}; + +int sflite_sysfs_add_volume(struct sflite_volume *svol) +{ + return sysfs_create_group(svol->kobj_parent, &sflite_volume_attr_group); +} + +void sflite_sysfs_remove_volume(struct sflite_volume *svol) +{ + sysfs_remove_group(svol->kobj_parent, &sflite_volume_attr_group); +} diff --git a/dm-sflc/lite/volume.c b/dm-sflc/lite/volume.c new file mode 100644 index 0000000..78dc76b --- /dev/null +++ b/dm-sflc/lite/volume.c @@ -0,0 +1,162 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include +#include "sflc_lite.h" + + +/** + * Arguments: + * argv[0]: Shufflecake mode: legacy/lite + * argv[1]: Shufflecake-unique device ID + * argv[2]: path to underlying physical device + * argv[3]: volume index within the device + * argv[4]: number of 1 MB slices in the underlying device + * argv[5]: 64-byte encryption key (hex-encoded) + */ +struct sflite_volume *sflite_vol_create(struct dm_target *ti, struct sflite_device* sdev, + int argc, char **argv, struct kobject *kobj) +{ + struct sflite_volume *svol; + u32 vol_idx; + int err; + + svol = kzalloc(sizeof(*svol), GFP_KERNEL); + if (!svol) { + DMERR("Could not allocate volume"); + return ERR_PTR(-ENOMEM); + } + + /* Parse arguments */ + if (argc != 6) { + DMERR("Wrong argument count"); + err = -EINVAL; + goto bad_parse; + } + if (sscanf(argv[3], "%u", &vol_idx) != 1) { + DMERR("Could not decode tot_slices\n"); + err = -EINVAL; + goto bad_parse; + } + /* Decode the encryption key */ + if (strlen(argv[5]) != 2 * SFLITE_XTS_KEYLEN) { + DMERR("Invalid key length"); + err = -EINVAL; + goto bad_parse; + } + err = hex2bin(svol->enckey, argv[5], SFLITE_XTS_KEYLEN); + if (err) { + DMERR("Could not decode hexadecimal encryption key"); + err = -EINVAL; + goto bad_parse; + } + + svol->sdev = sdev; + svol->vol_idx = vol_idx; + sprintf(svol->name, "sflc_%u_%u", sdev->dev_id, vol_idx); + + svol->ti = ti; + err = dm_get_device(ti, sdev->name, + dm_table_get_mode(ti->table), &svol->dm_dev); + if (err) { + ti->error = "Device lookup failed"; + goto bad_dm_dev; + } + + /* Crypto */ + svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); + if (IS_ERR(svol->tfm)) { + err = PTR_ERR(svol->tfm); + DMERR("Could not allocate AES-XTS cipher handle; error %d", err); + goto bad_tfm_alloc; + } + err = crypto_skcipher_setkey(svol->tfm, svol->enckey, SFLITE_XTS_KEYLEN); + if (err) { + DMERR("Could not set key in crypto transform; error %d", err); + goto bad_tfm_setkey; + } + + /* Position map */ + mutex_init(&svol->posmap_lock); + /* Slight over-allocation, to fit a whole number of blocks */ + svol->posmap = vmalloc(sdev->posmap_size_sectors * SECTOR_SIZE); + if (!svol->posmap) { + DMERR("Could not allocate position map"); + err = -ENOMEM; + goto bad_posmap_alloc; + } + svol->nr_mapped_slices = 0; + /* Load from disk */ + err = sflite_load_and_sanitise_posmap(svol); + if (err) { + DMERR("Could not load position map from disk; error %d", err); + goto bad_posmap_load; + } + + /* Add to sysfs, once initialised */ + err = sflite_sysfs_add_volume(svol); + if (err) { + DMERR("Could not register volume with sysfs; error %d", err); + goto bad_sysfs; + } + + /* Only accept one block per request for simplicity TODO: improve to one slice*/ + ti->max_io_len = SFLITE_BLOCK_SCALE; + ti->flush_supported = true; + ti->num_flush_bios = 1; + ti->discards_supported = false; + ti->num_discard_bios = 0; + ti->num_secure_erase_bios = 0; + ti->num_write_zeroes_bios = 0; + ti->accounts_remapped_io = true; + ti->per_io_data_size = sizeof(struct sflite_io); + ti->private = svol; + + return svol; + + +bad_sysfs: +bad_posmap_load: + vfree(svol->posmap); +bad_posmap_alloc: +bad_tfm_setkey: + crypto_free_skcipher(svol->tfm); +bad_tfm_alloc: + dm_put_device(ti, svol->dm_dev); +bad_dm_dev: +bad_parse: + kfree(svol); + return ERR_PTR(err); +} + + +void sflite_vol_destroy(struct sflite_volume *svol) +{ + sflite_sysfs_remove_volume(svol); + vfree(svol->posmap); + crypto_free_skcipher(svol->tfm); + dm_put_device(svol->ti, svol->dm_dev); + kfree(svol); + + return; +} diff --git a/dm-sflc/lite/write.c b/dm-sflc/lite/write.c new file mode 100644 index 0000000..5b2fb66 --- /dev/null +++ b/dm-sflc/lite/write.c @@ -0,0 +1,148 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#include "sflc_lite.h" +#include + + +static void sflite_write_endio(struct bio *phys_bio); + + +void sflite_write_work_fn(struct work_struct *work) +{ + struct sflite_io *sio = container_of(work, struct sflite_io, work); + struct sflite_volume *svol = sio->svol; + struct sflite_device *sdev = svol->sdev; + struct bio *orig_bio = sio->orig_bio; + struct bio_vec bvl = bio_iovec(orig_bio); + struct bio *phys_bio; + struct page *page; + u32 lsi = sio->lsi; + u32 block_offset = sio->block_offset; + u32 psi; + int err; + +// DMWARN("WRITE: dequeued. Sector = %llu", orig_bio->bi_iter.bi_sector); +// msleep(100); + + + /* Read existing mapping, or create new one */ + if (mutex_lock_interruptible(&svol->posmap_lock)) { + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + psi = svol->posmap[lsi]; + /* If LSI unmapped, create new mapping, while holding the lock */ + if (psi == SFLITE_PSI_INVALID) { +// DMWARN("WRITE: unmapped LSI %u, sampling PSI", lsi); +// msleep(100); + + err = sflite_create_persistent_slice_mapping(svol, lsi, &psi); + if (err){ + DMERR("Could not create slice mapping; error %d", err); + mutex_unlock(&svol->posmap_lock); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } +// DMWARN("WRITE: sampled PSI %u for LSI %u", psi, lsi); +// msleep(100); + } + mutex_unlock(&svol->posmap_lock); + sio->psi = psi; + + /* Allocate physical bio */ + phys_bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, orig_bio->bi_opf, + GFP_NOIO, &sdev->bioset); + if (!phys_bio) { + DMERR("Could not allocate physical bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto endio; + } + /* Insert in the I/O struct */ + sio->phys_bio = phys_bio; + + /* Physical bio needs its own page */ + page = alloc_pages(GFP_NOIO, 0); + if (!page) { + DMERR("Could not allocate page for physical bio"); + orig_bio->bi_status = BLK_STS_IOERR; + goto bad_alloc_page; + } + + /* Remap sector */ + phys_bio->bi_iter.bi_sector = SFLITE_PHYS_BIO_SECTOR(sdev, psi, block_offset); + /* Encrypt */ + err = sflite_crypt_block_page(svol->tfm, bvl.bv_page, page, + phys_bio->bi_iter.bi_sector >> SFLITE_BLOCK_SHIFT, WRITE); + if (err) { + DMERR("Could not encrypt bio; error %d", err); + orig_bio->bi_status = BLK_STS_IOERR; + goto bad_encrypt; + } + + /* Add page to bio */ + __bio_add_page(phys_bio, page, SFLITE_BLOCK_SIZE, 0); + /* Set fields for the endio */ + phys_bio->bi_private = sio; + phys_bio->bi_end_io = sflite_write_endio; + /* Submit */ + dm_submit_bio_remap(orig_bio, phys_bio); + + return; + + +bad_encrypt: + __free_page(page); +bad_alloc_page: + bio_put(phys_bio); +endio: + bio_endio(orig_bio); + return; +} + +static void sflite_write_endio(struct bio *phys_bio) +{ + struct sflite_io *sio = phys_bio->bi_private; + struct bio *orig_bio = sio->orig_bio; + + /* If physical bio failed, then fail-fast */ + if (phys_bio->bi_status != BLK_STS_OK) { + orig_bio->bi_status = phys_bio->bi_status; + DMWARN("WRITE ENDIO: phys_bio failed"); + goto endio; + } + + /* Advance original bio by one block */ + bio_advance(orig_bio, SFLITE_BLOCK_SIZE); + orig_bio->bi_status = BLK_STS_OK; + +endio: + /* Free the physical bio and its page */ + bio_free_pages(phys_bio); + bio_put(phys_bio); + /* End original bio */ + bio_endio(orig_bio); + + return; +} + diff --git a/dm-sflc/old/device/device.c b/dm-sflc/old/device/device.c index 1795575..b5045c1 100644 --- a/dm-sflc/old/device/device.c +++ b/dm-sflc/old/device/device.c @@ -54,7 +54,7 @@ static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); *****************************************************/ /** - * Creates Device and adds it to the list. Returns an ERR_PTR() if unsuccessful. + * Creates Device. Returns an ERR_PTR() if unsuccessful. * Arguments: * argv[0]: Shufflecake mode: legacy/lite * argv[1]: Shufflecake-unique device ID @@ -80,6 +80,11 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru } /* Parse args */ + if (argc != 6) { + pr_err("Wrong argument count"); + err = -EINVAL; + goto err_parse; + } sscanf(argv[1], "%u", &dev_id); if (sscanf(argv[4], "%u", &tot_slices) != 1) { pr_err("Could not decode tot_slices\n"); diff --git a/dm-sflc/old/volume/volume.c b/dm-sflc/old/volume/volume.c index e8b97b6..862d8f5 100644 --- a/dm-sflc/old/volume/volume.c +++ b/dm-sflc/old/volume/volume.c @@ -41,7 +41,7 @@ *****************************************************/ /** - * Creates volume and adds it to the device. Returns an ERR_PTR() if unsuccessful + * Creates volume. Returns an ERR_PTR() if unsuccessful * Arguments: * argv[0]: Shufflecake mode: legacy/lite * argv[1]: Shufflecake-unique device ID @@ -67,6 +67,11 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, } /* Parse args */ + if (argc != 6) { + pr_err("Wrong argument count"); + err = -EINVAL; + goto err_parse; + } if (sscanf(argv[3], "%u", &vol_idx) != 1) { pr_err("Could not decode tot_slices\n"); err = -EINVAL; diff --git a/dm-sflc/sflc.c b/dm-sflc/sflc.c index 0938fb0..5f154ab 100644 --- a/dm-sflc/sflc.c +++ b/dm-sflc/sflc.c @@ -26,6 +26,7 @@ #include "sflc.h" #include "old/sflc_old.h" +#include "lite/sflc_lite.h" // Global variables @@ -228,11 +229,16 @@ static int sflc_init(void) if (ret) goto err_sysfs; - /* Init the legacy module */ + /* Init the Legacy module */ ret = sfold_init(); if (ret) goto err_sfold; + /* Init the Lite module */ + ret = sflite_init(); + if (ret) + goto err_sflite; + /* Register the DM callbacks */ ret = dm_register_target(&sflc_target_type); if (ret < 0) @@ -243,6 +249,8 @@ static int sflc_init(void) err_dm: + sflite_exit(); +err_sflite: sfold_exit(); err_sfold: sflc_sysfs_exit(); @@ -256,6 +264,7 @@ static void sflc_exit(void) { dm_unregister_target(&sflc_target_type); sfold_exit(); + sflite_exit(); sflc_sysfs_exit(); DMINFO("unloaded"); diff --git a/dm-sflc/sflc.h b/dm-sflc/sflc.h index 6248f74..1462d16 100644 --- a/dm-sflc/sflc.h +++ b/dm-sflc/sflc.h @@ -29,6 +29,7 @@ #include "sflc_constants.h" #include "old/sflc_old.h" +#include "lite/sflc_lite.h" /* @@ -50,6 +51,7 @@ struct sflc_device int mode; union { sfold_Device *sfold_dev; + struct sflite_device *sflite_dev; }; /* Sysfs */ @@ -68,6 +70,7 @@ struct sflc_volume int mode; union { sfold_Volume *sfold_vol; + struct sflite_volume *sflite_vol; }; /* Pointers to concrete, mode-specific callbacks */ struct target_type *tt; From 322f4367a6357d3bcc44a0830922234cb629a6f4 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 3 Aug 2024 22:40:11 +0200 Subject: [PATCH 60/98] Bugfix --- .gitignore | 2 + dm-sflc/dev_vol.c | 3 + dm-sflc/lite/volume.c | 1 + dm-sflc/sflc.c | 14 ++ dm-sflc/sysfs.c | 9 +- .../.gitignore | 0 .../Makefile | 0 .../Makefile.sources | 0 .../include/actions/device.h | 0 .../include/actions/volume.h | 0 .../include/cli/cli.h | 0 .../include/commands/commands.h | 0 .../include/header/device_master_block.h | 0 .../include/header/position_map.h | 0 .../include/header/volume_master_block.h | 0 .../include/sflc_constants.h | 0 .../include/utils/crypto.h | 0 .../include/utils/disk.h | 0 .../include/utils/dm.h | 0 .../include/utils/file.h | 0 .../include/utils/input.h | 0 .../include/utils/log.h | 0 .../include/utils/math.h | 0 .../include/utils/sflc.h | 0 .../include/utils/string.h | 0 .../src/actions/close.c | 0 .../src/actions/close_vol.c | 0 .../src/actions/create.c | 0 .../src/actions/create_vol.c | 0 .../src/actions/dmb.c | 0 .../src/actions/open.c | 0 .../src/actions/open_vol.c | 0 .../src/cli/close.c | 0 .../src/cli/dispatch.c | 0 .../src/cli/init.c | 0 .../src/cli/open.c | 0 .../src/commands/close.c | 0 .../src/commands/init.c | 0 .../src/commands/open.c | 0 .../src/header/device_master_block.c | 0 .../src/header/position_map.c | 0 .../src/header/volume_master_block.c | 0 .../src/main.c | 0 .../src/utils/crypto.c | 0 .../src/utils/disk.c | 0 .../src/utils/dm.c | 0 .../src/utils/file.c | 0 .../src/utils/input.c | 0 .../src/utils/string.c | 0 .../test/actions/test_actions.h | 0 .../test/actions/test_all_actions.c | 0 .../test/actions/test_create.c | 0 .../test/commands/test_commands.h | 0 .../test/commands/test_init.c | 0 .../test/crypto/test_aes256ctr.c | 0 .../test/crypto/test_aes256ctr.h | 0 .../test/crypto/test_aes256gcm.c | 0 .../test/crypto/test_aes256gcm.h | 0 .../test/crypto/test_argon2id.c | 0 .../test/crypto/test_argon2id.h | 0 .../test/main.c | 0 .../test/minunit.h | 0 .../.gitignore | 0 .../Makefile | 4 +- .../Makefile.sources | 0 .../include/cli.h | 22 +-- .../include/commands.h | 24 +-- .../include/header.h | 44 +++--- .../include/operations.h | 16 +- .../include/utils/crypto.h | 42 ++--- .../include/utils/disk.h | 26 +-- .../include/utils/dm.h | 6 +- .../include/utils/file.h | 2 +- .../include/utils/input.h | 6 +- .../include/utils/log.h | 80 +++++----- .../include/utils/math.h | 0 .../include/utils/sflite.h | 49 +++--- .../include/utils/string.h | 4 +- .../src/cli/changepwd.c | 32 ++-- .../src/cli/close.c | 14 +- .../src/cli/dispatch.c | 98 +++++------- .../src/cli/init.c | 34 ++-- .../src/cli/open.c | 16 +- .../src/cli/testpwd.c | 22 +-- .../src/commands/change_pwd.c | 6 +- .../src/commands/close.c | 54 +++---- .../src/commands/init.c | 58 +++---- .../src/commands/open.c | 48 +++--- .../src/commands/test_pwd.c | 6 +- .../src/header/device_master_block.c | 90 +++++------ .../src/header/position_map.c | 26 +-- .../src/header/volume_master_block.c | 72 ++++----- .../src/main.c | 2 +- .../src/operations/devmapper.c | 28 ++-- .../src/operations/dmb.c | 52 +++--- .../src/operations/volume_header.c | 40 ++--- .../src/utils/crypto.c | 148 +++++++++--------- .../src/utils/disk.c | 56 +++---- .../src/utils/dm.c | 57 +++---- .../src/utils/file.c | 6 +- .../src/utils/input.c | 8 +- .../src/utils/string.c | 6 +- .../test/crypto/test_aes256ctr.c | 24 +-- .../test/crypto/test_aes256ctr.h | 0 .../test/crypto/test_aes256gcm.c | 22 +-- .../test/crypto/test_aes256gcm.h | 0 .../test/crypto/test_argon2id.c | 18 +-- .../test/crypto/test_argon2id.h | 0 .../test/main.c | 6 +- .../test/minunit.h | 0 vuvuzela-userland/include/vvz_constants.h | 1 - 111 files changed, 707 insertions(+), 697 deletions(-) rename {shufflecake-userland => shufflecake-userland-legacy}/.gitignore (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/Makefile (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/Makefile.sources (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/actions/device.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/actions/volume.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/cli/cli.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/commands/commands.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/header/device_master_block.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/header/position_map.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/header/volume_master_block.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/sflc_constants.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/crypto.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/disk.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/dm.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/file.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/input.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/log.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/math.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/sflc.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/include/utils/string.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/close.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/close_vol.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/create.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/create_vol.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/dmb.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/open.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/actions/open_vol.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/cli/close.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/cli/dispatch.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/cli/init.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/cli/open.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/commands/close.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/commands/init.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/commands/open.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/header/device_master_block.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/header/position_map.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/header/volume_master_block.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/main.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/crypto.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/disk.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/dm.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/file.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/input.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/src/utils/string.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/actions/test_actions.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/actions/test_all_actions.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/actions/test_create.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/commands/test_commands.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/commands/test_init.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_aes256ctr.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_aes256ctr.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_aes256gcm.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_aes256gcm.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_argon2id.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/crypto/test_argon2id.h (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/main.c (100%) rename {shufflecake-userland => shufflecake-userland-legacy}/test/minunit.h (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/.gitignore (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/Makefile (98%) rename {vuvuzela-userland => shufflecake-userland-lite}/Makefile.sources (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/cli.h (79%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/commands.h (87%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/header.h (75%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/operations.h (73%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/crypto.h (72%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/disk.h (75%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/dm.h (92%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/file.h (97%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/input.h (92%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/log.h (58%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/math.h (100%) rename vuvuzela-userland/include/utils/vvz.h => shufflecake-userland-lite/include/utils/sflite.h (68%) rename {vuvuzela-userland => shufflecake-userland-lite}/include/utils/string.h (93%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/changepwd.c (79%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/close.c (85%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/dispatch.c (70%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/init.c (78%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/open.c (87%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/cli/testpwd.c (83%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/commands/change_pwd.c (91%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/commands/close.c (72%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/commands/init.c (69%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/commands/open.c (74%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/commands/test_pwd.c (91%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/header/device_master_block.c (64%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/header/position_map.c (80%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/header/volume_master_block.c (67%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/main.c (97%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/operations/devmapper.c (76%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/operations/dmb.c (67%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/operations/volume_header.c (71%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/crypto.c (66%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/disk.c (74%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/dm.c (75%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/file.c (92%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/input.c (93%) rename {vuvuzela-userland => shufflecake-userland-lite}/src/utils/string.c (92%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_aes256ctr.c (88%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_aes256ctr.h (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_aes256gcm.c (87%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_aes256gcm.h (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_argon2id.c (84%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/crypto/test_argon2id.h (100%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/main.c (94%) rename {vuvuzela-userland => shufflecake-userland-lite}/test/minunit.h (100%) delete mode 120000 vuvuzela-userland/include/vvz_constants.h diff --git a/.gitignore b/.gitignore index d56891f..2d88f50 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,8 @@ dkms.conf # Shufflecake binaries shufflecake +shufflecake-lite +shufflecake-legacy # Build directory bin/ diff --git a/dm-sflc/dev_vol.c b/dm-sflc/dev_vol.c index 7d6f258..3c5e5b3 100644 --- a/dm-sflc/dev_vol.c +++ b/dm-sflc/dev_vol.c @@ -21,6 +21,8 @@ * If not, see . */ +#include + #include "sflc.h" #include "old/sflc_old.h" @@ -143,6 +145,7 @@ struct sflc_volume *sflc_vol_create(struct sflc_device *sdev, struct dm_target * /* Assign fields */ svol->mode = mode; sprintf(svol->name, "sflc_%u_%u", sdev->dev_id, vol_idx); + svol->sdev = sdev; /* Register with sysfs */ err = sflc_sysfs_register_volume(svol); diff --git a/dm-sflc/lite/volume.c b/dm-sflc/lite/volume.c index 78dc76b..e37d9c1 100644 --- a/dm-sflc/lite/volume.c +++ b/dm-sflc/lite/volume.c @@ -114,6 +114,7 @@ struct sflite_volume *sflite_vol_create(struct dm_target *ti, struct sflite_devi } /* Add to sysfs, once initialised */ + svol->kobj_parent = kobj; err = sflite_sysfs_add_volume(svol); if (err) { DMERR("Could not register volume with sysfs; error %d", err); diff --git a/dm-sflc/sflc.c b/dm-sflc/sflc.c index 5f154ab..67676b2 100644 --- a/dm-sflc/sflc.c +++ b/dm-sflc/sflc.c @@ -23,11 +23,14 @@ #include #include +#include #include "sflc.h" #include "old/sflc_old.h" #include "lite/sflc_lite.h" +#include + // Global variables DEFINE_MUTEX(sflc_alldevs_lock); @@ -224,6 +227,13 @@ static int sflc_init(void) { int ret; + sflc_alldevs = vzalloc(SFLC_MAX_DEVS * sizeof(*sflc_alldevs)); + if (!sflc_alldevs) { + DMERR("Could not allocate sflc_alldevs"); + ret = -ENOMEM; + goto bad_alldevs_alloc; + } + /* Create the first sysfs entries */ ret = sflc_sysfs_init(); if (ret) @@ -255,6 +265,9 @@ err_sflite: err_sfold: sflc_sysfs_exit(); err_sysfs: + vfree(sflc_alldevs); +bad_alldevs_alloc: + DMERR("not loaded"); return ret; } @@ -266,6 +279,7 @@ static void sflc_exit(void) sfold_exit(); sflite_exit(); sflc_sysfs_exit(); + vfree(sflc_alldevs); DMINFO("unloaded"); return; diff --git a/dm-sflc/sysfs.c b/dm-sflc/sysfs.c index acea092..2ef402d 100644 --- a/dm-sflc/sysfs.c +++ b/dm-sflc/sysfs.c @@ -23,6 +23,8 @@ #include "sflc.h" +#include + /* *---------------------------- * Top-level entries @@ -157,11 +159,6 @@ void sflc_sysfs_unregister_device(struct sflc_device *sdev) *---------------------------- */ -static struct attribute *sflc_volume_default_attrs[] = { - NULL -}; -ATTRIBUTE_GROUPS(sflc_volume_default); - static void sflc_volume_kobj_release(struct kobject *kobj) { struct sflc_volume *svol = container_of(kobj, struct sflc_volume, kobj); @@ -172,7 +169,6 @@ static void sflc_volume_kobj_release(struct kobject *kobj) static struct kobj_type sflc_volume_ktype = { .release = sflc_volume_kobj_release, .sysfs_ops = &kobj_sysfs_ops, - .default_groups = sflc_volume_default_groups }; int sflc_sysfs_register_volume(struct sflc_volume *svol) @@ -187,6 +183,7 @@ int sflc_sysfs_register_volume(struct sflc_volume *svol) "%s", svol->name); if (err) goto bad; + /* Emit uevent */ kobject_uevent(&svol->kobj, KOBJ_ADD); diff --git a/shufflecake-userland/.gitignore b/shufflecake-userland-legacy/.gitignore similarity index 100% rename from shufflecake-userland/.gitignore rename to shufflecake-userland-legacy/.gitignore diff --git a/shufflecake-userland/Makefile b/shufflecake-userland-legacy/Makefile similarity index 100% rename from shufflecake-userland/Makefile rename to shufflecake-userland-legacy/Makefile diff --git a/shufflecake-userland/Makefile.sources b/shufflecake-userland-legacy/Makefile.sources similarity index 100% rename from shufflecake-userland/Makefile.sources rename to shufflecake-userland-legacy/Makefile.sources diff --git a/shufflecake-userland/include/actions/device.h b/shufflecake-userland-legacy/include/actions/device.h similarity index 100% rename from shufflecake-userland/include/actions/device.h rename to shufflecake-userland-legacy/include/actions/device.h diff --git a/shufflecake-userland/include/actions/volume.h b/shufflecake-userland-legacy/include/actions/volume.h similarity index 100% rename from shufflecake-userland/include/actions/volume.h rename to shufflecake-userland-legacy/include/actions/volume.h diff --git a/shufflecake-userland/include/cli/cli.h b/shufflecake-userland-legacy/include/cli/cli.h similarity index 100% rename from shufflecake-userland/include/cli/cli.h rename to shufflecake-userland-legacy/include/cli/cli.h diff --git a/shufflecake-userland/include/commands/commands.h b/shufflecake-userland-legacy/include/commands/commands.h similarity index 100% rename from shufflecake-userland/include/commands/commands.h rename to shufflecake-userland-legacy/include/commands/commands.h diff --git a/shufflecake-userland/include/header/device_master_block.h b/shufflecake-userland-legacy/include/header/device_master_block.h similarity index 100% rename from shufflecake-userland/include/header/device_master_block.h rename to shufflecake-userland-legacy/include/header/device_master_block.h diff --git a/shufflecake-userland/include/header/position_map.h b/shufflecake-userland-legacy/include/header/position_map.h similarity index 100% rename from shufflecake-userland/include/header/position_map.h rename to shufflecake-userland-legacy/include/header/position_map.h diff --git a/shufflecake-userland/include/header/volume_master_block.h b/shufflecake-userland-legacy/include/header/volume_master_block.h similarity index 100% rename from shufflecake-userland/include/header/volume_master_block.h rename to shufflecake-userland-legacy/include/header/volume_master_block.h diff --git a/shufflecake-userland/include/sflc_constants.h b/shufflecake-userland-legacy/include/sflc_constants.h similarity index 100% rename from shufflecake-userland/include/sflc_constants.h rename to shufflecake-userland-legacy/include/sflc_constants.h diff --git a/shufflecake-userland/include/utils/crypto.h b/shufflecake-userland-legacy/include/utils/crypto.h similarity index 100% rename from shufflecake-userland/include/utils/crypto.h rename to shufflecake-userland-legacy/include/utils/crypto.h diff --git a/shufflecake-userland/include/utils/disk.h b/shufflecake-userland-legacy/include/utils/disk.h similarity index 100% rename from shufflecake-userland/include/utils/disk.h rename to shufflecake-userland-legacy/include/utils/disk.h diff --git a/shufflecake-userland/include/utils/dm.h b/shufflecake-userland-legacy/include/utils/dm.h similarity index 100% rename from shufflecake-userland/include/utils/dm.h rename to shufflecake-userland-legacy/include/utils/dm.h diff --git a/shufflecake-userland/include/utils/file.h b/shufflecake-userland-legacy/include/utils/file.h similarity index 100% rename from shufflecake-userland/include/utils/file.h rename to shufflecake-userland-legacy/include/utils/file.h diff --git a/shufflecake-userland/include/utils/input.h b/shufflecake-userland-legacy/include/utils/input.h similarity index 100% rename from shufflecake-userland/include/utils/input.h rename to shufflecake-userland-legacy/include/utils/input.h diff --git a/shufflecake-userland/include/utils/log.h b/shufflecake-userland-legacy/include/utils/log.h similarity index 100% rename from shufflecake-userland/include/utils/log.h rename to shufflecake-userland-legacy/include/utils/log.h diff --git a/shufflecake-userland/include/utils/math.h b/shufflecake-userland-legacy/include/utils/math.h similarity index 100% rename from shufflecake-userland/include/utils/math.h rename to shufflecake-userland-legacy/include/utils/math.h diff --git a/shufflecake-userland/include/utils/sflc.h b/shufflecake-userland-legacy/include/utils/sflc.h similarity index 100% rename from shufflecake-userland/include/utils/sflc.h rename to shufflecake-userland-legacy/include/utils/sflc.h diff --git a/shufflecake-userland/include/utils/string.h b/shufflecake-userland-legacy/include/utils/string.h similarity index 100% rename from shufflecake-userland/include/utils/string.h rename to shufflecake-userland-legacy/include/utils/string.h diff --git a/shufflecake-userland/src/actions/close.c b/shufflecake-userland-legacy/src/actions/close.c similarity index 100% rename from shufflecake-userland/src/actions/close.c rename to shufflecake-userland-legacy/src/actions/close.c diff --git a/shufflecake-userland/src/actions/close_vol.c b/shufflecake-userland-legacy/src/actions/close_vol.c similarity index 100% rename from shufflecake-userland/src/actions/close_vol.c rename to shufflecake-userland-legacy/src/actions/close_vol.c diff --git a/shufflecake-userland/src/actions/create.c b/shufflecake-userland-legacy/src/actions/create.c similarity index 100% rename from shufflecake-userland/src/actions/create.c rename to shufflecake-userland-legacy/src/actions/create.c diff --git a/shufflecake-userland/src/actions/create_vol.c b/shufflecake-userland-legacy/src/actions/create_vol.c similarity index 100% rename from shufflecake-userland/src/actions/create_vol.c rename to shufflecake-userland-legacy/src/actions/create_vol.c diff --git a/shufflecake-userland/src/actions/dmb.c b/shufflecake-userland-legacy/src/actions/dmb.c similarity index 100% rename from shufflecake-userland/src/actions/dmb.c rename to shufflecake-userland-legacy/src/actions/dmb.c diff --git a/shufflecake-userland/src/actions/open.c b/shufflecake-userland-legacy/src/actions/open.c similarity index 100% rename from shufflecake-userland/src/actions/open.c rename to shufflecake-userland-legacy/src/actions/open.c diff --git a/shufflecake-userland/src/actions/open_vol.c b/shufflecake-userland-legacy/src/actions/open_vol.c similarity index 100% rename from shufflecake-userland/src/actions/open_vol.c rename to shufflecake-userland-legacy/src/actions/open_vol.c diff --git a/shufflecake-userland/src/cli/close.c b/shufflecake-userland-legacy/src/cli/close.c similarity index 100% rename from shufflecake-userland/src/cli/close.c rename to shufflecake-userland-legacy/src/cli/close.c diff --git a/shufflecake-userland/src/cli/dispatch.c b/shufflecake-userland-legacy/src/cli/dispatch.c similarity index 100% rename from shufflecake-userland/src/cli/dispatch.c rename to shufflecake-userland-legacy/src/cli/dispatch.c diff --git a/shufflecake-userland/src/cli/init.c b/shufflecake-userland-legacy/src/cli/init.c similarity index 100% rename from shufflecake-userland/src/cli/init.c rename to shufflecake-userland-legacy/src/cli/init.c diff --git a/shufflecake-userland/src/cli/open.c b/shufflecake-userland-legacy/src/cli/open.c similarity index 100% rename from shufflecake-userland/src/cli/open.c rename to shufflecake-userland-legacy/src/cli/open.c diff --git a/shufflecake-userland/src/commands/close.c b/shufflecake-userland-legacy/src/commands/close.c similarity index 100% rename from shufflecake-userland/src/commands/close.c rename to shufflecake-userland-legacy/src/commands/close.c diff --git a/shufflecake-userland/src/commands/init.c b/shufflecake-userland-legacy/src/commands/init.c similarity index 100% rename from shufflecake-userland/src/commands/init.c rename to shufflecake-userland-legacy/src/commands/init.c diff --git a/shufflecake-userland/src/commands/open.c b/shufflecake-userland-legacy/src/commands/open.c similarity index 100% rename from shufflecake-userland/src/commands/open.c rename to shufflecake-userland-legacy/src/commands/open.c diff --git a/shufflecake-userland/src/header/device_master_block.c b/shufflecake-userland-legacy/src/header/device_master_block.c similarity index 100% rename from shufflecake-userland/src/header/device_master_block.c rename to shufflecake-userland-legacy/src/header/device_master_block.c diff --git a/shufflecake-userland/src/header/position_map.c b/shufflecake-userland-legacy/src/header/position_map.c similarity index 100% rename from shufflecake-userland/src/header/position_map.c rename to shufflecake-userland-legacy/src/header/position_map.c diff --git a/shufflecake-userland/src/header/volume_master_block.c b/shufflecake-userland-legacy/src/header/volume_master_block.c similarity index 100% rename from shufflecake-userland/src/header/volume_master_block.c rename to shufflecake-userland-legacy/src/header/volume_master_block.c diff --git a/shufflecake-userland/src/main.c b/shufflecake-userland-legacy/src/main.c similarity index 100% rename from shufflecake-userland/src/main.c rename to shufflecake-userland-legacy/src/main.c diff --git a/shufflecake-userland/src/utils/crypto.c b/shufflecake-userland-legacy/src/utils/crypto.c similarity index 100% rename from shufflecake-userland/src/utils/crypto.c rename to shufflecake-userland-legacy/src/utils/crypto.c diff --git a/shufflecake-userland/src/utils/disk.c b/shufflecake-userland-legacy/src/utils/disk.c similarity index 100% rename from shufflecake-userland/src/utils/disk.c rename to shufflecake-userland-legacy/src/utils/disk.c diff --git a/shufflecake-userland/src/utils/dm.c b/shufflecake-userland-legacy/src/utils/dm.c similarity index 100% rename from shufflecake-userland/src/utils/dm.c rename to shufflecake-userland-legacy/src/utils/dm.c diff --git a/shufflecake-userland/src/utils/file.c b/shufflecake-userland-legacy/src/utils/file.c similarity index 100% rename from shufflecake-userland/src/utils/file.c rename to shufflecake-userland-legacy/src/utils/file.c diff --git a/shufflecake-userland/src/utils/input.c b/shufflecake-userland-legacy/src/utils/input.c similarity index 100% rename from shufflecake-userland/src/utils/input.c rename to shufflecake-userland-legacy/src/utils/input.c diff --git a/shufflecake-userland/src/utils/string.c b/shufflecake-userland-legacy/src/utils/string.c similarity index 100% rename from shufflecake-userland/src/utils/string.c rename to shufflecake-userland-legacy/src/utils/string.c diff --git a/shufflecake-userland/test/actions/test_actions.h b/shufflecake-userland-legacy/test/actions/test_actions.h similarity index 100% rename from shufflecake-userland/test/actions/test_actions.h rename to shufflecake-userland-legacy/test/actions/test_actions.h diff --git a/shufflecake-userland/test/actions/test_all_actions.c b/shufflecake-userland-legacy/test/actions/test_all_actions.c similarity index 100% rename from shufflecake-userland/test/actions/test_all_actions.c rename to shufflecake-userland-legacy/test/actions/test_all_actions.c diff --git a/shufflecake-userland/test/actions/test_create.c b/shufflecake-userland-legacy/test/actions/test_create.c similarity index 100% rename from shufflecake-userland/test/actions/test_create.c rename to shufflecake-userland-legacy/test/actions/test_create.c diff --git a/shufflecake-userland/test/commands/test_commands.h b/shufflecake-userland-legacy/test/commands/test_commands.h similarity index 100% rename from shufflecake-userland/test/commands/test_commands.h rename to shufflecake-userland-legacy/test/commands/test_commands.h diff --git a/shufflecake-userland/test/commands/test_init.c b/shufflecake-userland-legacy/test/commands/test_init.c similarity index 100% rename from shufflecake-userland/test/commands/test_init.c rename to shufflecake-userland-legacy/test/commands/test_init.c diff --git a/shufflecake-userland/test/crypto/test_aes256ctr.c b/shufflecake-userland-legacy/test/crypto/test_aes256ctr.c similarity index 100% rename from shufflecake-userland/test/crypto/test_aes256ctr.c rename to shufflecake-userland-legacy/test/crypto/test_aes256ctr.c diff --git a/shufflecake-userland/test/crypto/test_aes256ctr.h b/shufflecake-userland-legacy/test/crypto/test_aes256ctr.h similarity index 100% rename from shufflecake-userland/test/crypto/test_aes256ctr.h rename to shufflecake-userland-legacy/test/crypto/test_aes256ctr.h diff --git a/shufflecake-userland/test/crypto/test_aes256gcm.c b/shufflecake-userland-legacy/test/crypto/test_aes256gcm.c similarity index 100% rename from shufflecake-userland/test/crypto/test_aes256gcm.c rename to shufflecake-userland-legacy/test/crypto/test_aes256gcm.c diff --git a/shufflecake-userland/test/crypto/test_aes256gcm.h b/shufflecake-userland-legacy/test/crypto/test_aes256gcm.h similarity index 100% rename from shufflecake-userland/test/crypto/test_aes256gcm.h rename to shufflecake-userland-legacy/test/crypto/test_aes256gcm.h diff --git a/shufflecake-userland/test/crypto/test_argon2id.c b/shufflecake-userland-legacy/test/crypto/test_argon2id.c similarity index 100% rename from shufflecake-userland/test/crypto/test_argon2id.c rename to shufflecake-userland-legacy/test/crypto/test_argon2id.c diff --git a/shufflecake-userland/test/crypto/test_argon2id.h b/shufflecake-userland-legacy/test/crypto/test_argon2id.h similarity index 100% rename from shufflecake-userland/test/crypto/test_argon2id.h rename to shufflecake-userland-legacy/test/crypto/test_argon2id.h diff --git a/shufflecake-userland/test/main.c b/shufflecake-userland-legacy/test/main.c similarity index 100% rename from shufflecake-userland/test/main.c rename to shufflecake-userland-legacy/test/main.c diff --git a/shufflecake-userland/test/minunit.h b/shufflecake-userland-legacy/test/minunit.h similarity index 100% rename from shufflecake-userland/test/minunit.h rename to shufflecake-userland-legacy/test/minunit.h diff --git a/vuvuzela-userland/.gitignore b/shufflecake-userland-lite/.gitignore similarity index 100% rename from vuvuzela-userland/.gitignore rename to shufflecake-userland-lite/.gitignore diff --git a/vuvuzela-userland/Makefile b/shufflecake-userland-lite/Makefile similarity index 98% rename from vuvuzela-userland/Makefile rename to shufflecake-userland-lite/Makefile index 19abeb7..c0d4151 100644 --- a/vuvuzela-userland/Makefile +++ b/shufflecake-userland-lite/Makefile @@ -61,10 +61,10 @@ DEPS := $(PROJ_DEPS) $(TEST_DEPS) DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) # The target binaries -MAIN_BIN := $(PROJ_OUT_DIR)/vuvuzela +MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake-lite TEST_BIN := $(TEST_OUT_DIR)/tests # Their symlink -MAIN_LINK := vuvuzela +MAIN_LINK := shufflecake-lite TEST_LINK := tests diff --git a/vuvuzela-userland/Makefile.sources b/shufflecake-userland-lite/Makefile.sources similarity index 100% rename from vuvuzela-userland/Makefile.sources rename to shufflecake-userland-lite/Makefile.sources diff --git a/vuvuzela-userland/include/cli.h b/shufflecake-userland-lite/include/cli.h similarity index 79% rename from vuvuzela-userland/include/cli.h rename to shufflecake-userland-lite/include/cli.h index b58b313..f175931 100644 --- a/vuvuzela-userland/include/cli.h +++ b/shufflecake-userland-lite/include/cli.h @@ -30,15 +30,15 @@ *****************************************************/ /* Action to create volumes */ -#define VVZ_CLI_INITACT "init" +#define SFLITE_CLI_INITACT "init" /* Action to open volumes */ -#define VVZ_CLI_OPENACT "open" +#define SFLITE_CLI_OPENACT "open" /* Action to close volumes */ -#define VVZ_CLI_CLOSEACT "close" +#define SFLITE_CLI_CLOSEACT "close" /* Action to test password */ -#define VVZ_CLI_TESTPWDACT "testpwd" +#define SFLITE_CLI_TESTPWDACT "testpwd" /* Action to change password */ -#define VVZ_CLI_CHANGEPWDACT "changepwd" +#define SFLITE_CLI_CHANGEPWDACT "changepwd" /***************************************************** @@ -46,18 +46,18 @@ *****************************************************/ /* Called by the main to parse the arguments and dispatch to the right command */ -int vvz_cli_dispatch(int argc, char **argv); +int sflite_cli_dispatch(int argc, char **argv); /* Initializes device and create empty volumes */ -int vvz_cli_init(char *block_device, int vvz_mode, int num_volumes, int skip_randfill); +int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill); /* Open volumes */ -int vvz_cli_open(char *block_device, int vvz_mode); +int sflite_cli_open(char *block_device); /* Close volumes */ -int vvz_cli_close(char *block_device); +int sflite_cli_close(char *block_device); /* Test password */ -int vvz_cli_testPwd(char *block_device, int vvz_mode); +int sflite_cli_testPwd(char *block_device); /* Change password */ -int vvz_cli_changePwd(char *block_device, int vvz_mode); +int sflite_cli_changePwd(char *block_device); #endif /* _CLI_H_ */ diff --git a/vuvuzela-userland/include/commands.h b/shufflecake-userland-lite/include/commands.h similarity index 87% rename from vuvuzela-userland/include/commands.h rename to shufflecake-userland-lite/include/commands.h index 1cdab7d..d21c767 100644 --- a/vuvuzela-userland/include/commands.h +++ b/shufflecake-userland-lite/include/commands.h @@ -51,7 +51,7 @@ typedef struct /* Underlying block device */ char *bdev_path; /* Shufflecake mode (legacy,lite,full) */ - int vvz_mode; + int sflite_mode; /* Number of volumes */ size_t nr_vols; /* Volumes' passwords */ @@ -60,7 +60,7 @@ typedef struct /* Option to skip random filling */ bool no_randfill; -} vvz_cmd_InitArgs; +} sflite_cmd_InitArgs; /* Parameters for the open command */ @@ -69,26 +69,26 @@ typedef struct /* Underlying block device */ char *bdev_path; /* Shufflecake mode (legacy,lite,full) */ - int vvz_mode; + int sflite_mode; /* The only password provided */ char *pwd; size_t pwd_len; -} vvz_cmd_OpenArgs; +} sflite_cmd_OpenArgs; typedef struct { /* Underlying block device */ char *bdev_path; /* Shufflecake mode (legacy,lite,full) */ - int vvz_mode; + int sflite_mode; /* Content of the DMB cell */ - vvz_DmbCell *dmb_cell; + sflite_DmbCell *dmb_cell; /* The new password */ char *new_pwd; size_t new_pwd_len; -} vvz_cmd_ChangePwdArgs; +} sflite_cmd_ChangePwdArgs; /***************************************************** @@ -96,19 +96,19 @@ typedef struct *****************************************************/ /* Create N volumes (only formats the device header, does not open the volumes) */ -int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args); +int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args); /* Open M volumes, from the first down to the one whose pwd is provided */ -int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args); +int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args); /* Close all volumes on the device (reads the list from sysfs) */ -int vvz_cmd_closeVolumes(char *bdev_path); +int sflite_cmd_closeVolumes(char *bdev_path); /* Tests which volume is unlocked by the given password */ -int vvz_cmd_testPwd(vvz_cmd_OpenArgs *args, vvz_DmbCell *dmb_cell); +int sflite_cmd_testPwd(sflite_cmd_OpenArgs *args, sflite_DmbCell *dmb_cell); /* Changes the specified volume's password */ -int vvz_cmd_changePwd(vvz_cmd_ChangePwdArgs *args); +int sflite_cmd_changePwd(sflite_cmd_ChangePwdArgs *args); #endif /* _COMMANDS_H_ */ diff --git a/vuvuzela-userland/include/header.h b/shufflecake-userland-lite/include/header.h similarity index 75% rename from vuvuzela-userland/include/header.h rename to shufflecake-userland-lite/include/header.h index 2423b40..469fe8b 100644 --- a/vuvuzela-userland/include/header.h +++ b/shufflecake-userland-lite/include/header.h @@ -41,18 +41,18 @@ *****************************************************/ /* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define VVZ_DMB_CELL_SIZE (VVZ_AESGCM_PADDED_IVLEN + VVZ_STANDARD_KEYLEN + VVZ_AESGCM_TAGLEN) +#define SFLITE_DMB_CELL_SIZE (SFLITE_AESGCM_PADDED_IVLEN + SFLITE_STANDARD_KEYLEN + SFLITE_AESGCM_TAGLEN) /* Let us enforce that the one DMB can fit cells for all volumes */ -#if VVZ_ARGON_SALTLEN + (VVZ_DEV_MAX_VOLUMES * VVZ_DMB_CELL_SIZE) > VVZ_BLOCK_SIZE -#error "Invalid combination of parameters: probably VVZ_DEV_MAX_VOLUMES is too big" +#if SFLITE_ARGON_SALTLEN + (SFLITE_DEV_MAX_VOLUMES * SFLITE_DMB_CELL_SIZE) > SFLITE_BLOCK_SIZE +#error "Invalid combination of parameters: probably SFLITE_DEV_MAX_VOLUMES is too big" #endif // The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define VVZ_CLEAR_VMB_LEN (VVZ_BLOCK_SIZE - \ - VVZ_AESGCM_PADDED_IVLEN - \ - VVZ_AESGCM_TAGLEN) +#define SFLITE_CLEAR_VMB_LEN (SFLITE_BLOCK_SIZE - \ + SFLITE_AESGCM_PADDED_IVLEN - \ + SFLITE_AESGCM_TAGLEN) @@ -67,12 +67,12 @@ */ typedef struct { // Each volume's VMB key - char vmb_keys[VVZ_DEV_MAX_VOLUMES][VVZ_STANDARD_KEYLEN]; + char vmb_keys[SFLITE_DEV_MAX_VOLUMES][SFLITE_STANDARD_KEYLEN]; // How many of these need actually be encrypted size_t nr_vols; -} vvz_Dmb; +} sflite_Dmb; /** @@ -81,12 +81,12 @@ typedef struct { */ typedef struct { // The unlocked VMB key - char vmb_key[VVZ_STANDARD_KEYLEN]; + char vmb_key[SFLITE_STANDARD_KEYLEN]; // The index of the volume opened by this VMB key size_t vol_idx; -} vvz_DmbCell; +} sflite_DmbCell; /** @@ -96,15 +96,15 @@ typedef struct { */ typedef struct { // The key that encrypts the volume's data section - char volume_key[VVZ_AESXTS_KEYLEN]; + char volume_key[SFLITE_AESXTS_KEYLEN]; // The key that encrypts the previous volume's master block - char prev_vmb_key[VVZ_STANDARD_KEYLEN]; + char prev_vmb_key[SFLITE_STANDARD_KEYLEN]; // The total number of logical slices virtually available to this volume size_t nr_slices; -} vvz_Vmb; +} sflite_Vmb; /***************************************************** @@ -113,11 +113,11 @@ typedef struct { // Starting block of a volume's position map -static inline uint64_t vvz_pmStartBlock(size_t vol_idx, size_t nr_slices) +static inline uint64_t sflite_pmStartBlock(size_t vol_idx, size_t nr_slices) { return 1 + - VVZ_DEV_MAX_VOLUMES + - vol_idx*ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); + SFLITE_DEV_MAX_VOLUMES + + vol_idx*ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); } @@ -126,21 +126,21 @@ static inline uint64_t vvz_pmStartBlock(size_t vol_idx, size_t nr_slices) *****************************************************/ /* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ -int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); +int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); /* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ -int vvz_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell); +int sflite_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell); /* Re-encrypt the content of a single DMB cell */ -int vvz_dmb_setCell(char *disk_block, vvz_DmbCell *dmb_cell, char *pwd, size_t pwd_len); +int sflite_dmb_setCell(char *disk_block, sflite_DmbCell *dmb_cell, char *pwd, size_t pwd_len); /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ -int vvz_vmb_seal(vvz_Vmb *vmb, char *vmb_key, char *disk_block); +int sflite_vmb_seal(sflite_Vmb *vmb, char *vmb_key, char *disk_block); /* "Decrypt" a VMB coming from the disk, directly using its key */ -int vvz_vmb_unseal(char *disk_block, char *vmb_key, vvz_Vmb *vmb); +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflite_Vmb *vmb); /* Create an encrypted empty position map for the given number of slices (allocates memory) */ -void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); +void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); diff --git a/vuvuzela-userland/include/operations.h b/shufflecake-userland-lite/include/operations.h similarity index 73% rename from vuvuzela-userland/include/operations.h rename to shufflecake-userland-lite/include/operations.h index dbcfd45..25af9fe 100644 --- a/vuvuzela-userland/include/operations.h +++ b/shufflecake-userland-lite/include/operations.h @@ -42,20 +42,20 @@ *****************************************************/ /* Encrypts and writes the DMB to disk */ -int vvz_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, vvz_Dmb *dmb); +int sflite_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflite_Dmb *dmb); /* Reads the DMB from disk and outputs the unlocked VMB key */ -int vvz_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell); +int sflite_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell); /* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ -int vvz_ops_rewriteDmbCell(char *bdev_path, vvz_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); +int sflite_ops_rewriteDmbCell(char *bdev_path, sflite_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); /* Encrypts and writes a volume header (VMB+PM) on-disk */ -int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size_t vol_idx); +int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflite_Vmb *vmb, size_t vol_idx); /* Reads a VMB from disk and unlocks it */ -int vvz_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, vvz_Vmb *vmb); +int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflite_Vmb *vmb); -/* Build parameter list for ctor in dm_vvz, and send DM ioctl to create virtual block device */ -int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb *vmb); +/* Build parameter list for ctor in dm_sflite, and send DM ioctl to create virtual block device */ +int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflite_Vmb *vmb); /* Close the volume via the appropriate ioctl to DM */ -int vvz_ops_closeVolume(char *label); +int sflite_ops_closeVolume(char *label); #endif /* _OPERATIONS_H_ */ diff --git a/vuvuzela-userland/include/utils/crypto.h b/shufflecake-userland-lite/include/utils/crypto.h similarity index 72% rename from vuvuzela-userland/include/utils/crypto.h rename to shufflecake-userland-lite/include/utils/crypto.h index c47ca9b..84be972 100644 --- a/vuvuzela-userland/include/utils/crypto.h +++ b/shufflecake-userland-lite/include/utils/crypto.h @@ -33,7 +33,7 @@ #include #include -#include "utils/vvz.h" +#include "utils/sflite.h" /***************************************************** @@ -41,41 +41,41 @@ *****************************************************/ // Key length, for input into AES-CTR and AES-GCM, and for output from Argon -#define VVZ_STANDARD_KEYLEN 32 /* bytes */ +#define SFLITE_STANDARD_KEYLEN 32 /* bytes */ // Key length for AES-XTS -#define VVZ_AESXTS_KEYLEN 64 /* bytes */ +#define SFLITE_AESXTS_KEYLEN 64 /* bytes */ // IV length for AES-CTR -#define VVZ_AESCTR_IVLEN 16 /* bytes */ +#define SFLITE_AESCTR_IVLEN 16 /* bytes */ // IV length for AES-XTS -#define VVZ_AESXTS_IVLEN 16 /* bytes */ +#define SFLITE_AESXTS_IVLEN 16 /* bytes */ // IV length for AES-GCM -#define VVZ_AESGCM_IVLEN 12 /* bytes */ +#define SFLITE_AESGCM_IVLEN 12 /* bytes */ // IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM -#define VVZ_AESGCM_PADDED_IVLEN 16 /* bytes */ +#define SFLITE_AESGCM_PADDED_IVLEN 16 /* bytes */ // MAC length for AES-GCM -#define VVZ_AESGCM_TAGLEN 16 /* bytes */ +#define SFLITE_AESGCM_TAGLEN 16 /* bytes */ // Content of output plaintext upon MAC verification failure -#define VVZ_AESGCM_POISON_PT 0xFF +#define SFLITE_AESGCM_POISON_PT 0xFF /* Argon parameters */ // Argon salt length -#define VVZ_ARGON_SALTLEN 16 /* bytes */ +#define SFLITE_ARGON_SALTLEN 16 /* bytes */ // Argon memory parameter // We assume machines with at least 128 MiB available RAM, so 2^17 kiB -#define VVZ_ARGON_M (1 << 17) /* kibibytes */ +#define SFLITE_ARGON_M (1 << 17) /* kibibytes */ // Argon iterations count // We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) -#define VVZ_ARGON_T 3 +#define SFLITE_ARGON_T 3 // Argon parallelism parameter (recommended to be 2 * CPU cores) // We assume use even on single core devices -#define VVZ_ARGON_P 2 +#define SFLITE_ARGON_P 2 /***************************************************** @@ -83,26 +83,26 @@ *****************************************************/ /* Get slow, strong random bytes (suited for keys) */ -int vvz_rand_getStrongBytes(char *buf, size_t buflen); +int sflite_rand_getStrongBytes(char *buf, size_t buflen); /* Get fast, weak(er) random bytes (suited for IVs and padding) */ -int vvz_rand_getWeakBytes(char *buf, size_t buflen); +int sflite_rand_getWeakBytes(char *buf, size_t buflen); /* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ -int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ -int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); +int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); /* AES256-XTS encryption. Set ct = NULL for in-place. * The IV is intepreted as a little-endian "sector number" */ -int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-GCM encryption, does not touch the IV */ -int vvz_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); +int sflite_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); /* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ -int vvz_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); +int sflite_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); /* Compute Argon KDF */ -int vvz_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); +int sflite_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); #endif /* _UTILS_CRYPTO_H_ */ diff --git a/vuvuzela-userland/include/utils/disk.h b/shufflecake-userland-lite/include/utils/disk.h similarity index 75% rename from vuvuzela-userland/include/utils/disk.h rename to shufflecake-userland-lite/include/utils/disk.h index a007f39..f2cc8b5 100644 --- a/vuvuzela-userland/include/utils/disk.h +++ b/shufflecake-userland-lite/include/utils/disk.h @@ -36,7 +36,7 @@ #include #include "utils/math.h" -#include "utils/vvz.h" +#include "utils/sflite.h" /***************************************************** @@ -46,25 +46,25 @@ /** * Max slices for given disk size (in 4096-byte blocks). */ -static inline uint32_t vvz_disk_maxSlices(uint64_t size) { +static inline uint32_t sflite_disk_maxSlices(uint64_t size) { uint64_t nr_slices; // Start from upper bound - nr_slices = size / VVZ_SLICE_SCALE; + nr_slices = size / SFLITE_SLICE_SCALE; while(true) { if (nr_slices == 0) break; // Stop when this nr_slices can fit in size, including the header - uint64_t posmap_blocks = ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); - uint64_t header_size = 1 + VVZ_DEV_MAX_VOLUMES * (1 + posmap_blocks); - if (header_size + nr_slices*VVZ_SLICE_SCALE <= size) + uint64_t posmap_blocks = ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); + uint64_t header_size = 1 + SFLITE_DEV_MAX_VOLUMES * (1 + posmap_blocks); + if (header_size + nr_slices*SFLITE_SLICE_SCALE <= size) break; nr_slices--; } - return nr_slices > VVZ_MAX_SLICES ? VVZ_MAX_SLICES : (uint32_t) nr_slices; + return nr_slices > SFLITE_MAX_SLICES ? SFLITE_MAX_SLICES : (uint32_t) nr_slices; } @@ -73,22 +73,22 @@ static inline uint32_t vvz_disk_maxSlices(uint64_t size) { *****************************************************/ /* Returns a malloc'ed string formatted as : */ -char *vvz_disk_getDeviceName(char *bdev_path); +char *sflite_disk_getDeviceName(char *bdev_path); /* Checks whether the given path points to a block device */ -bool vvz_disk_isBlockDevice(char *path); +bool sflite_disk_isBlockDevice(char *path); /* Returns the size in 4096-byte sectors (or < 0 if error) */ -int64_t vvz_disk_getSize(char *bdev_path); +int64_t sflite_disk_getSize(char *bdev_path); /* Reads a single 4096-byte block from the disk */ -int vvz_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); +int sflite_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); /* Writes many 4096-byte blocks to the disk */ -int vvz_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); +int sflite_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); /* Writes a single 4096-byte block to the disk */ -#define vvz_disk_writeBlock(bdev_path, bnum, buf) vvz_disk_writeManyBlocks(bdev_path, bnum, buf, 1) +#define sflite_disk_writeBlock(bdev_path, bnum, buf) sflite_disk_writeManyBlocks(bdev_path, bnum, buf, 1) #endif /* _UTILS_DISK_H_ */ diff --git a/vuvuzela-userland/include/utils/dm.h b/shufflecake-userland-lite/include/utils/dm.h similarity index 92% rename from vuvuzela-userland/include/utils/dm.h rename to shufflecake-userland-lite/include/utils/dm.h index 1670a73..ba27afb 100644 --- a/vuvuzela-userland/include/utils/dm.h +++ b/shufflecake-userland-lite/include/utils/dm.h @@ -35,7 +35,7 @@ #include -#include "utils/vvz.h" +#include "utils/sflite.h" /***************************************************** @@ -48,9 +48,9 @@ *****************************************************/ /* Create a new Shufflecake virtual device (volume) under /dev/mapper */ -int vvz_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); +int sflite_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); /* Destroy the virtual device under /dev/mapper */ -int vvz_dm_destroy(char * virt_dev_name); +int sflite_dm_destroy(char * virt_dev_name); #endif /* _UTILS_DM_H_ */ diff --git a/vuvuzela-userland/include/utils/file.h b/shufflecake-userland-lite/include/utils/file.h similarity index 97% rename from vuvuzela-userland/include/utils/file.h rename to shufflecake-userland-lite/include/utils/file.h index b4760c3..2346ee6 100644 --- a/vuvuzela-userland/include/utils/file.h +++ b/shufflecake-userland-lite/include/utils/file.h @@ -30,7 +30,7 @@ *****************************************************/ /* Malloc's the buffer for the file contents */ -char *vvz_readFile(char *path); +char *sflite_readFile(char *path); #endif /* _UTILS_FILE_H_ */ diff --git a/vuvuzela-userland/include/utils/input.h b/shufflecake-userland-lite/include/utils/input.h similarity index 92% rename from vuvuzela-userland/include/utils/input.h rename to shufflecake-userland-lite/include/utils/input.h index 068d376..5c54850 100644 --- a/vuvuzela-userland/include/utils/input.h +++ b/shufflecake-userland-lite/include/utils/input.h @@ -30,7 +30,7 @@ *****************************************************/ /* Clear a line from stdin, to use after a failed scanf (it didn't actually read input) */ -#define vvz_ignoreLine() scanf("%*[^\n]") +#define sflite_ignoreLine() scanf("%*[^\n]") /***************************************************** @@ -38,9 +38,9 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int vvz_safeReadLine(char *buf, size_t bufsize); +int sflite_safeReadLine(char *buf, size_t bufsize); /* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ -int vvz_safeReadPassphrase(char *buf, size_t bufsize); +int sflite_safeReadPassphrase(char *buf, size_t bufsize); #endif /* _UTILS_INPUT_H_ */ diff --git a/vuvuzela-userland/include/utils/log.h b/shufflecake-userland-lite/include/utils/log.h similarity index 58% rename from vuvuzela-userland/include/utils/log.h rename to shufflecake-userland-lite/include/utils/log.h index 60a251f..5b15ea5 100644 --- a/vuvuzela-userland/include/utils/log.h +++ b/shufflecake-userland-lite/include/utils/log.h @@ -37,29 +37,29 @@ *****************************************************/ // Printf colours (regular text) -#define VVZ_LOG_BLK "\033[0;30m" -#define VVZ_LOG_RED "\033[0;31m" -#define VVZ_LOG_GRN "\033[0;32m" -#define VVZ_LOG_YEL "\033[0;33m" -#define VVZ_LOG_BLU "\033[0;34m" -#define VVZ_LOG_MAG "\033[0;35m" -#define VVZ_LOG_CYN "\033[0;36m" -#define VVZ_LOG_WHT "\033[0;37m" +#define SFLITE_LOG_BLK "\033[0;30m" +#define SFLITE_LOG_RED "\033[0;31m" +#define SFLITE_LOG_GRN "\033[0;32m" +#define SFLITE_LOG_YEL "\033[0;33m" +#define SFLITE_LOG_BLU "\033[0;34m" +#define SFLITE_LOG_MAG "\033[0;35m" +#define SFLITE_LOG_CYN "\033[0;36m" +#define SFLITE_LOG_WHT "\033[0;37m" // Printf colours (bold text) -#define VVZ_LOG_BBLK "\033[1;30m" -#define VVZ_LOG_BRED "\033[1;31m" -#define VVZ_LOG_BGRN "\033[1;32m" -#define VVZ_LOG_BYEL "\033[1;33m" -#define VVZ_LOG_BBLU "\033[1;34m" -#define VVZ_LOG_BMAG "\033[1;35m" -#define VVZ_LOG_BCYN "\033[1;36m" -#define VVZ_LOG_BWHT "\033[1;37m" +#define SFLITE_LOG_BBLK "\033[1;30m" +#define SFLITE_LOG_BRED "\033[1;31m" +#define SFLITE_LOG_BGRN "\033[1;32m" +#define SFLITE_LOG_BYEL "\033[1;33m" +#define SFLITE_LOG_BBLU "\033[1;34m" +#define SFLITE_LOG_BMAG "\033[1;35m" +#define SFLITE_LOG_BCYN "\033[1;36m" +#define SFLITE_LOG_BWHT "\033[1;37m" // Reset colour -#define VVZ_LOG_RESET "\033[0m" +#define SFLITE_LOG_RESET "\033[0m" // Log level: debug implies detailed logs -#ifdef CONFIG_VVZ_LOG_DEBUG -#define CONFIG_VVZ_LOG_DETAILED +#ifdef CONFIG_SFLITE_LOG_DEBUG +#define CONFIG_SFLITE_LOG_DETAILED #endif @@ -68,42 +68,42 @@ *****************************************************/ // Gives the point in the code where it was called -#define vvz_log_detailed(col, ...) do{ \ - printf(VVZ_LOG_GRN "FUNC " VVZ_LOG_RESET "%s() " \ - VVZ_LOG_GRN "FILE " VVZ_LOG_RESET "%s " \ - VVZ_LOG_GRN "LINE " VVZ_LOG_RESET "%d | ", \ +#define sflite_log_detailed(col, ...) do{ \ + printf(SFLITE_LOG_GRN "FUNC " SFLITE_LOG_RESET "%s() " \ + SFLITE_LOG_GRN "FILE " SFLITE_LOG_RESET "%s " \ + SFLITE_LOG_GRN "LINE " SFLITE_LOG_RESET "%d | ", \ __func__, __FILE__, __LINE__); \ - vvz_log_concise(col, __VA_ARGS__); \ + sflite_log_concise(col, __VA_ARGS__); \ }while(0) // Only writes using the given colour -#define vvz_log_concise(col, ...) do{ \ +#define sflite_log_concise(col, ...) do{ \ printf(col); \ printf(__VA_ARGS__); \ - printf(VVZ_LOG_RESET "\n"); \ + printf(SFLITE_LOG_RESET "\n"); \ }while(0) // Maps to one or the other, based on a Makefile switch -#ifdef CONFIG_VVZ_LOG_DETAILED - #define vvz_log_colour(...) vvz_log_detailed(__VA_ARGS__) +#ifdef CONFIG_SFLITE_LOG_DETAILED + #define sflite_log_colour(...) sflite_log_detailed(__VA_ARGS__) #else - #define vvz_log_colour(...) vvz_log_concise(__VA_ARGS__) + #define sflite_log_colour(...) sflite_log_concise(__VA_ARGS__) #endif // Using specific colours -#define vvz_log_green(...) vvz_log_colour(VVZ_LOG_GRN, __VA_ARGS__) -#define vvz_log_red(...) vvz_log_colour(VVZ_LOG_RED, __VA_ARGS__) -#define vvz_log_yellow(...) vvz_log_colour(VVZ_LOG_YEL, __VA_ARGS__) -#define vvz_log_blue(...) vvz_log_colour(VVZ_LOG_BLU, __VA_ARGS__) -#define vvz_log_normal(...) vvz_log_colour(VVZ_LOG_RESET, __VA_ARGS__) +#define sflite_log_green(...) sflite_log_colour(SFLITE_LOG_GRN, __VA_ARGS__) +#define sflite_log_red(...) sflite_log_colour(SFLITE_LOG_RED, __VA_ARGS__) +#define sflite_log_yellow(...) sflite_log_colour(SFLITE_LOG_YEL, __VA_ARGS__) +#define sflite_log_blue(...) sflite_log_colour(SFLITE_LOG_BLU, __VA_ARGS__) +#define sflite_log_normal(...) sflite_log_colour(SFLITE_LOG_RESET, __VA_ARGS__) // With log levels -#define vvz_log_error(...) vvz_log_colour(VVZ_LOG_RED, "[ERROR] " __VA_ARGS__) -#define vvz_log_warn(...) vvz_log_colour(VVZ_LOG_MAG, "[WARN] " __VA_ARGS__) -#ifdef CONFIG_VVZ_LOG_DEBUG - #define vvz_log_debug(...) vvz_log_colour(VVZ_LOG_CYN, "[DEBUG] " __VA_ARGS__) +#define sflite_log_error(...) sflite_log_colour(SFLITE_LOG_RED, "[ERROR] " __VA_ARGS__) +#define sflite_log_warn(...) sflite_log_colour(SFLITE_LOG_MAG, "[WARN] " __VA_ARGS__) +#ifdef CONFIG_SFLITE_LOG_DEBUG + #define sflite_log_debug(...) sflite_log_colour(SFLITE_LOG_CYN, "[DEBUG] " __VA_ARGS__) #else - #define vvz_log_debug(...) + #define sflite_log_debug(...) #endif @@ -112,7 +112,7 @@ *****************************************************/ // Log a hex string -static inline void vvz_log_hex(char *str, size_t len) +static inline void sflite_log_hex(char *str, size_t len) { int i; unsigned char *s = (unsigned char *) str; diff --git a/vuvuzela-userland/include/utils/math.h b/shufflecake-userland-lite/include/utils/math.h similarity index 100% rename from vuvuzela-userland/include/utils/math.h rename to shufflecake-userland-lite/include/utils/math.h diff --git a/vuvuzela-userland/include/utils/vvz.h b/shufflecake-userland-lite/include/utils/sflite.h similarity index 68% rename from vuvuzela-userland/include/utils/vvz.h rename to shufflecake-userland-lite/include/utils/sflite.h index 71bc399..1dcd963 100644 --- a/vuvuzela-userland/include/utils/vvz.h +++ b/shufflecake-userland-lite/include/utils/sflite.h @@ -22,11 +22,11 @@ */ /* - * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO vvz_constans.h + * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflite_constans.h */ -#ifndef _UTILS_VVZ_H_ -#define _UTILS_VVZ_H_ +#ifndef _UTILS_SFLITE_H_ +#define _UTILS_SFLITE_H_ /***************************************************** @@ -39,46 +39,51 @@ *****************************************************/ /* Name of the DM target in the kernel */ -#define VVZ_DM_TARGET_NAME "vvz" +#define SFLC_DM_TARGET_NAME "shufflecake" + +/* Modes */ +#define SFLC_MODE_LEGACY 0 +#define SFLC_MODE_LITE 1 +#define SFLC_MODE_FULL 2 /* Sizes */ #define KERNEL_SECTOR_SIZE 512 /* bytes */ -#define VVZ_BLOCK_SIZE 4096 /* bytes */ -#define VVZ_BLOCK_SCALE (VVZ_BLOCK_SIZE / KERNEL_SECTOR_SIZE) -#define VVZ_SLICE_SCALE 256 /* blocks in a slice */ -#define VVZ_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ +#define SFLITE_BLOCK_SIZE 4096 /* bytes */ +#define SFLITE_BLOCK_SCALE (SFLITE_BLOCK_SIZE / KERNEL_SECTOR_SIZE) +#define SFLITE_SLICE_SCALE 256 /* blocks in a slice */ +#define SFLITE_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ /* Max number of volumes in a device */ -#define VVZ_DEV_MAX_VOLUMES 15 +#define SFLITE_DEV_MAX_VOLUMES 15 /* Max total number of open devices at any given time */ -#define VVZ_TOT_MAX_DEVICES 1024 -/* A volume name is vvz__ */ -#define VVZ_MAX_VOL_NAME_LEN 15 +#define SFLITE_TOT_MAX_DEVICES 1024 +/* A volume name is sflite__ */ +#define SFLITE_MAX_VOL_NAME_LEN 15 /* A slice index is represented over 32 bits */ -#define VVZ_SLICE_IDX_WIDTH 4 /* bytes */ +#define SFLITE_SLICE_IDX_WIDTH 4 /* bytes */ /* A position map block contains 1024 slice indices */ -#define VVZ_SLICE_IDX_PER_BLOCK (VVZ_BLOCK_SIZE / VVZ_SLICE_IDX_WIDTH) +#define SFLITE_SLICE_IDX_PER_BLOCK (SFLITE_BLOCK_SIZE / SFLITE_SLICE_IDX_WIDTH) /* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ -#define VVZ_EPM_FILLER 0xFF +#define SFLITE_EPM_FILLER 0xFF /* The sysfs file containing the next available device ID */ -#define VVZ_SYSFS_NEXTDEVID "/sys/module/dm_vvz/next_dev_id" +#define SFLITE_SYSFS_NEXTDEVID "/sys/module/dm_sflc/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ -#define VVZ_SYSFS_BDEVS_DIR "/sys/module/dm_vvz/bdevs" +#define SFLITE_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" /* Within each bdev's subdir, this file shows its number of open volumes */ -#define VVZ_SYSFS_OPENVOLUMES_FILENAME "volumes" +#define SFLITE_SYSFS_OPENVOLUMES_FILENAME "volumes" /* Within each bdev's subdir, this file shows its Shufflecake device ID */ -#define VVZ_SYSFS_DEVID_FILENAME "dev_id" +#define SFLITE_SYSFS_DEVID_FILENAME "dev_id" /* TODO: reasonable? */ -#define VVZ_BDEV_PATH_MAX_LEN 1024 +#define SFLITE_BDEV_PATH_MAX_LEN 1024 /* For when you can't be bothered to upper-bound a buffer size */ -#define VVZ_BIGBUFSIZE 4096 +#define SFLITE_BIGBUFSIZE 4096 -#endif /* _UTILS_VVZ_H_ */ +#endif /* _UTILS_SFLITE_H_ */ diff --git a/vuvuzela-userland/include/utils/string.h b/shufflecake-userland-lite/include/utils/string.h similarity index 93% rename from vuvuzela-userland/include/utils/string.h rename to shufflecake-userland-lite/include/utils/string.h index 99f057b..552ed82 100644 --- a/vuvuzela-userland/include/utils/string.h +++ b/shufflecake-userland-lite/include/utils/string.h @@ -30,10 +30,10 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *vvz_toHex(char *buf, size_t len); +char *sflite_toHex(char *buf, size_t len); /* Replaces all occurrences of character in-place */ -void vvz_str_replaceAll(char *str, char old, char new); +void sflite_str_replaceAll(char *str, char old, char new); #endif /* _UTILS_STRING_H_ */ diff --git a/vuvuzela-userland/src/cli/changepwd.c b/shufflecake-userland-lite/src/cli/changepwd.c similarity index 79% rename from vuvuzela-userland/src/cli/changepwd.c rename to shufflecake-userland-lite/src/cli/changepwd.c index e0f691f..a4f85e7 100644 --- a/vuvuzela-userland/src/cli/changepwd.c +++ b/shufflecake-userland-lite/src/cli/changepwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/input.h" #include "utils/log.h" @@ -45,14 +45,14 @@ * * @return Error code, 0 on success */ -int vvz_cli_changePwd(char *block_device) +int sflite_cli_changePwd(char *block_device) { // Requires: block_device is a correct block device path - vvz_cmd_OpenArgs open_args; - vvz_cmd_ChangePwdArgs change_pwd_args; - vvz_DmbCell dmb_cell; - char old_pwd[VVZ_BIGBUFSIZE]; + sflite_cmd_OpenArgs open_args; + sflite_cmd_ChangePwdArgs change_pwd_args; + sflite_DmbCell dmb_cell; + char old_pwd[SFLITE_BIGBUFSIZE]; size_t old_pwd_len; - char new_pwd[VVZ_BIGBUFSIZE]; + char new_pwd[SFLITE_BIGBUFSIZE]; size_t new_pwd_len; int err; @@ -60,9 +60,9 @@ int vvz_cli_changePwd(char *block_device) /* Gather password */ printf("Enter the password you want to change: "); - err = vvz_safeReadPassphrase(old_pwd, VVZ_BIGBUFSIZE); + err = sflite_safeReadPassphrase(old_pwd, SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Could not read password; error %d", err); + sflite_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -72,14 +72,14 @@ int vvz_cli_changePwd(char *block_device) open_args.pwd_len = old_pwd_len; /* Test the password */ - err = vvz_cmd_testPwd(&open_args, &dmb_cell); + err = sflite_cmd_testPwd(&open_args, &dmb_cell); if (err) { - vvz_log_error("Could not test password; error %d", err); + sflite_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); return 0; } @@ -87,9 +87,9 @@ int vvz_cli_changePwd(char *block_device) /* Gather new password (no secure shell) */ printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); - err = vvz_safeReadLine(new_pwd, VVZ_BIGBUFSIZE); + err = sflite_safeReadLine(new_pwd, SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Could not read new password; error %d", err); + sflite_log_error("Could not read new password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -102,9 +102,9 @@ int vvz_cli_changePwd(char *block_device) change_pwd_args.new_pwd_len = new_pwd_len; /* Change password */ - err = vvz_cmd_changePwd(&change_pwd_args); + err = sflite_cmd_changePwd(&change_pwd_args); if (err) { - vvz_log_error("Could not change password; error %d", err); + sflite_log_error("Could not change password; error %d", err); return err; } printf("Password changed successfully.\n"); diff --git a/vuvuzela-userland/src/cli/close.c b/shufflecake-userland-lite/src/cli/close.c similarity index 85% rename from vuvuzela-userland/src/cli/close.c rename to shufflecake-userland-lite/src/cli/close.c index a4c8bbd..ecedda2 100644 --- a/vuvuzela-userland/src/cli/close.c +++ b/shufflecake-userland-lite/src/cli/close.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/input.h" #include "utils/log.h" @@ -45,16 +45,16 @@ * * @return Error code, 0 on success */ -int vvz_cli_close(char *block_device) +int sflite_cli_close(char *block_device) { // Requires: block_device is a correct block device path -// char bdev_path[VVZ_BDEV_PATH_MAX_LEN + 2]; +// char bdev_path[SFLITE_BDEV_PATH_MAX_LEN + 2]; // int err; // /* Gather (absolute) path to underlying block device */ // printf("Enter the absolute path to the underlying block device containing the Shufflecake volumes to close: "); -// err = vvz_safeReadLine(bdev_path, VVZ_BDEV_PATH_MAX_LEN + 2); +// err = sflite_safeReadLine(bdev_path, SFLITE_BDEV_PATH_MAX_LEN + 2); // if (err) { -// vvz_log_error("Could not read path to underlying block device; error %d", err); +// sflite_log_error("Could not read path to underlying block device; error %d", err); // return err; // } // /* Check that it is absolute */ @@ -66,9 +66,9 @@ int vvz_cli_close(char *block_device) /* Actually perform the command */ -// return vvz_cmd_closeVolumes(bdev_path); +// return sflite_cmd_closeVolumes(bdev_path); - return vvz_cmd_closeVolumes(block_device); + return sflite_cmd_closeVolumes(block_device); } diff --git a/vuvuzela-userland/src/cli/dispatch.c b/shufflecake-userland-lite/src/cli/dispatch.c similarity index 70% rename from vuvuzela-userland/src/cli/dispatch.c rename to shufflecake-userland-lite/src/cli/dispatch.c index 3debf68..871b751 100644 --- a/vuvuzela-userland/src/cli/dispatch.c +++ b/shufflecake-userland-lite/src/cli/dispatch.c @@ -32,10 +32,9 @@ #include #include "cli.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/disk.h" #include "utils/log.h" -#include "vvz_constants.h" /***************************************************** @@ -43,29 +42,29 @@ *****************************************************/ /* Used by argp to provide the automatic "-V" option */ -const char *argp_program_version = VVZ_VERSION; +const char *argp_program_version = "0.5.0"; // Temporary hack /* Used by argp to provide the automatic "--help" option */ const char *argp_program_bug_address = ""; /* Signed integer values representing a handle for each option */ -#define VVZ_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option -#define VVZ_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this +#define SFLITE_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option +#define SFLITE_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this /***************************************************** * TYPES * *****************************************************/ -enum vvz_cli_action { - VVZ_ACT_INIT, - VVZ_ACT_OPEN, - VVZ_ACT_CLOSE, - VVZ_ACT_TESTPWD, - VVZ_ACT_CHANGEPWD +enum sflite_cli_action { + SFLITE_ACT_INIT, + SFLITE_ACT_OPEN, + SFLITE_ACT_CLOSE, + SFLITE_ACT_TESTPWD, + SFLITE_ACT_CHANGEPWD }; -struct vvz_cli_arguments { - enum vvz_cli_action act; +struct sflite_cli_arguments { + enum sflite_cli_action act; char *block_device; int sflc_mode; int num_volumes; @@ -112,12 +111,10 @@ static char doc[] = /* Description of each option */ static struct argp_option options[] = { - {"num-volumes", VVZ_OPT_NUMVOLS_KEY, "num", 0, + {"num-volumes", SFLITE_OPT_NUMVOLS_KEY, "num", 0, "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 - {"skip-randfill", VVZ_OPT_SKIPRAND_KEY, 0, 0, + {"skip-randfill", SFLITE_OPT_SKIPRAND_KEY, 0, 0, "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, - {"legacy", VVZ_OPT_LEGACY, 0, 0, - "Use the old (pre-v0.5.0) Shufflecake format. Only valid with `init`, `open', `testpwd', `changepwd'. Use of this option is not recommended. This mode is going to be deprecated in future versions."}, {0} }; @@ -139,12 +136,11 @@ static struct argp argp = {options, _parseArgpKey, args_doc, doc}; * @return Error code, 0 on success */ -int vvz_cli_dispatch(int argc, char **argv) { - struct vvz_cli_arguments arguments; +int sflite_cli_dispatch(int argc, char **argv) { + struct sflite_cli_arguments arguments; arguments.act = -1; arguments.block_device = NULL; - arguments.vvz_mode = VVZ_MODE_LITE; arguments.num_volumes = 0; arguments.skip_randfill = false; @@ -152,41 +148,36 @@ int vvz_cli_dispatch(int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, &arguments); /* Check options consistency */ - if (arguments.num_volumes && arguments.act != VVZ_ACT_INIT) { + if (arguments.num_volumes && arguments.act != SFLITE_ACT_INIT) { printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); return EINVAL; } - /* Legacy mode should not be specified with `close' action */ - if (arguments.vvz_mode && arguments.act == VVZ_ACT_CLOSE) { - printf("Error: --legacy should not be used with `close'.\n"); - return EINVAL; - } /* Check options consistency */ - if (arguments.skip_randfill && arguments.act != VVZ_ACT_INIT) { + if (arguments.skip_randfill && arguments.act != SFLITE_ACT_INIT) { printf("Error: --skip-randfill can only be combined with `init'.\n"); return EINVAL; } /* Check that input is actually a block device */ - if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !vvz_disk_isBlockDevice(arguments.block_device)) { + if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflite_disk_isBlockDevice(arguments.block_device)) { printf("Error: '%s' is not a valid block device.\n", arguments.block_device); return EINVAL; } /* Dispatch to specific command */ - if (arguments.act == VVZ_ACT_INIT) { - return vvz_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); + if (arguments.act == SFLITE_ACT_INIT) { + return sflite_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); } - if (arguments.act == VVZ_ACT_OPEN) { - return vvz_cli_open(arguments.block_device arguments.vvz_mode,); + if (arguments.act == SFLITE_ACT_OPEN) { + return sflite_cli_open(arguments.block_device); } - if (arguments.act == VVZ_ACT_CLOSE) { - return vvz_cli_close(arguments.block_device); + if (arguments.act == SFLITE_ACT_CLOSE) { + return sflite_cli_close(arguments.block_device); } - if (arguments.act == VVZ_ACT_TESTPWD) { - return vvz_cli_testPwd(arguments.block_device arguments.vvz_mode,); + if (arguments.act == SFLITE_ACT_TESTPWD) { + return sflite_cli_testPwd(arguments.block_device); } - if (arguments.act == VVZ_ACT_CHANGEPWD) { - return vvz_cli_changePwd(arguments.block_device arguments.vvz_mode,); + if (arguments.act == SFLITE_ACT_CHANGEPWD) { + return sflite_cli_changePwd(arguments.block_device); } printf("\n"); @@ -200,26 +191,26 @@ int vvz_cli_dispatch(int argc, char **argv) { *****************************************************/ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { - struct vvz_cli_arguments *arguments = state->input; + struct sflite_cli_arguments *arguments = state->input; switch (key) { /* We are parsing an argument (not an option) */ case ARGP_KEY_ARG: /* We are parsing the command */ if (state->arg_num == 0) { - if (strcmp(arg, VVZ_CLI_INITACT) == 0) { - arguments->act = VVZ_ACT_INIT; - } else if (strcmp(arg, VVZ_CLI_OPENACT) == 0) { - arguments->act = VVZ_ACT_OPEN; - } else if (strcmp(arg, VVZ_CLI_CLOSEACT) == 0) { - arguments->act = VVZ_ACT_CLOSE; - } else if (strcmp(arg, VVZ_CLI_TESTPWDACT) == 0) { - arguments->act = VVZ_ACT_TESTPWD; - } else if (strcmp(arg, VVZ_CLI_CHANGEPWDACT) == 0) { - arguments->act = VVZ_ACT_CHANGEPWD; + if (strcmp(arg, SFLITE_CLI_INITACT) == 0) { + arguments->act = SFLITE_ACT_INIT; + } else if (strcmp(arg, SFLITE_CLI_OPENACT) == 0) { + arguments->act = SFLITE_ACT_OPEN; + } else if (strcmp(arg, SFLITE_CLI_CLOSEACT) == 0) { + arguments->act = SFLITE_ACT_CLOSE; + } else if (strcmp(arg, SFLITE_CLI_TESTPWDACT) == 0) { + arguments->act = SFLITE_ACT_TESTPWD; + } else if (strcmp(arg, SFLITE_CLI_CHANGEPWDACT) == 0) { + arguments->act = SFLITE_ACT_CHANGEPWD; } else { argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", - VVZ_CLI_INITACT, VVZ_CLI_OPENACT, VVZ_CLI_CLOSEACT, VVZ_CLI_TESTPWDACT, VVZ_CLI_CHANGEPWDACT); + SFLITE_CLI_INITACT, SFLITE_CLI_OPENACT, SFLITE_CLI_CLOSEACT, SFLITE_CLI_TESTPWDACT, SFLITE_CLI_CHANGEPWDACT); } /* We are parsing the block device */ } else if (state->arg_num == 1) { @@ -231,13 +222,10 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { break; /* We are parsing an option */ - case VVZ_OPT_NUMVOLS_KEY: + case SFLITE_OPT_NUMVOLS_KEY: arguments->num_volumes = atoi(arg); break; - case VVZ_OPT_LEGACY: - arguments->vvz_mode = VVZ_MODE_LEGACY; - break; - case VVZ_OPT_SKIPRAND_KEY: + case SFLITE_OPT_SKIPRAND_KEY: arguments->skip_randfill = true; break; diff --git a/vuvuzela-userland/src/cli/init.c b/shufflecake-userland-lite/src/cli/init.c similarity index 78% rename from vuvuzela-userland/src/cli/init.c rename to shufflecake-userland-lite/src/cli/init.c index fc065d9..0639bb3 100644 --- a/vuvuzela-userland/src/cli/init.c +++ b/shufflecake-userland-lite/src/cli/init.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/input.h" #include "utils/log.h" @@ -45,12 +45,12 @@ * * @return Error code, 0 on success */ -int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) +int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) { // Requires: block_device is a correct block device path - vvz_cmd_InitArgs args; - char str_nrvols[VVZ_BIGBUFSIZE]; - char *pwds[VVZ_DEV_MAX_VOLUMES]; - size_t pwd_lens[VVZ_DEV_MAX_VOLUMES]; + sflite_cmd_InitArgs args; + char str_nrvols[SFLITE_BIGBUFSIZE]; + char *pwds[SFLITE_DEV_MAX_VOLUMES]; + size_t pwd_lens[SFLITE_DEV_MAX_VOLUMES]; int err; args.bdev_path = block_device; @@ -60,15 +60,15 @@ int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) args.nr_vols = num_volumes; } else { // If not, ask user for number of volumes - printf("\nHow many volumes do you want to create (maximum is %d)? ", VVZ_DEV_MAX_VOLUMES); - err = vvz_safeReadLine(str_nrvols, VVZ_BIGBUFSIZE); + printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLITE_DEV_MAX_VOLUMES); + err = sflite_safeReadLine(str_nrvols, SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Error: could not read number of volumes; error %d", err); + sflite_log_error("Error: could not read number of volumes; error %d", err); return err; } /* Parse string */ if (sscanf(str_nrvols, "%lu\n", &args.nr_vols) != 1) { - vvz_log_error("Error: could not parse number of volumes"); + sflite_log_error("Error: could not parse number of volumes"); return EINVAL; } } @@ -78,8 +78,8 @@ int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) printf("Error: number of volumes must be a positive integer"); return EINVAL; } - if (args.nr_vols > VVZ_DEV_MAX_VOLUMES) { - printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", VVZ_DEV_MAX_VOLUMES); + if (args.nr_vols > SFLITE_DEV_MAX_VOLUMES) { + printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLITE_DEV_MAX_VOLUMES); return EINVAL; } @@ -89,13 +89,13 @@ int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) size_t i; for (i = 0; i < args.nr_vols; i++) { // Allocate pwd - pwds[i] = malloc(VVZ_BIGBUFSIZE); + pwds[i] = malloc(SFLITE_BIGBUFSIZE); /* Read it */ printf("Choose password for volume %lu (must not be empty): ", i); - err = vvz_safeReadLine(pwds[i], VVZ_BIGBUFSIZE); + err = sflite_safeReadLine(pwds[i], SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Could not read password for volume %lu; error %d", i, err); + sflite_log_error("Could not read password for volume %lu; error %d", i, err); return err; } @@ -103,7 +103,7 @@ int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) pwd_lens[i] = strlen(pwds[i]); /* Check non-empty */ if (pwd_lens[i] == 0) { - vvz_log_error("Password cannot be empty!"); + sflite_log_error("Password cannot be empty!"); return EINVAL; } } @@ -114,5 +114,5 @@ int vvz_cli_init(char *block_device, int num_volumes, int skip_randfill) args.no_randfill = skip_randfill; /* Actually perform the command */ - return vvz_cmd_initVolumes(&args); + return sflite_cmd_initVolumes(&args); } diff --git a/vuvuzela-userland/src/cli/open.c b/shufflecake-userland-lite/src/cli/open.c similarity index 87% rename from vuvuzela-userland/src/cli/open.c rename to shufflecake-userland-lite/src/cli/open.c index 3cfe4dd..bc6d70b 100644 --- a/vuvuzela-userland/src/cli/open.c +++ b/shufflecake-userland-lite/src/cli/open.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/input.h" #include "utils/log.h" @@ -45,10 +45,10 @@ * * @return Error code, 0 on success */ -int vvz_cli_open(char *block_device) +int sflite_cli_open(char *block_device) { // Requires: block_device is a correct block device path - vvz_cmd_OpenArgs args; - char pwd[VVZ_BIGBUFSIZE]; + sflite_cmd_OpenArgs args; + char pwd[SFLITE_BIGBUFSIZE]; size_t pwd_len; int err; @@ -56,16 +56,16 @@ int vvz_cli_open(char *block_device) /* Gather password */ printf("Enter the password for the most secret volume you want to open: "); - err = vvz_safeReadPassphrase(pwd, VVZ_BIGBUFSIZE); + err = sflite_safeReadPassphrase(pwd, SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Could not read password; error %d", err); + sflite_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ pwd_len = strlen(pwd); /* Check non-empty */ if (pwd_len == 0) { - vvz_log_error("Password cannot be empty!"); + sflite_log_error("Password cannot be empty!"); return EINVAL; } /* Assign them */ @@ -73,5 +73,5 @@ int vvz_cli_open(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - return vvz_cmd_openVolumes(&args); + return sflite_cmd_openVolumes(&args); } diff --git a/vuvuzela-userland/src/cli/testpwd.c b/shufflecake-userland-lite/src/cli/testpwd.c similarity index 83% rename from vuvuzela-userland/src/cli/testpwd.c rename to shufflecake-userland-lite/src/cli/testpwd.c index 48cf710..4bc5695 100644 --- a/vuvuzela-userland/src/cli/testpwd.c +++ b/shufflecake-userland-lite/src/cli/testpwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/input.h" #include "utils/log.h" @@ -45,12 +45,12 @@ * * @return Error code, 0 on success */ -int vvz_cli_testPwd(char *block_device) +int sflite_cli_testPwd(char *block_device) { // Requires: block_device is a correct block device path - vvz_cmd_OpenArgs args; - vvz_DmbCell dmb_cell; -// char bdev_path[VVZ_BDEV_PATH_MAX_LEN + 2]; - char pwd[VVZ_BIGBUFSIZE]; + sflite_cmd_OpenArgs args; + sflite_DmbCell dmb_cell; +// char bdev_path[SFLITE_BDEV_PATH_MAX_LEN + 2]; + char pwd[SFLITE_BIGBUFSIZE]; size_t pwd_len; int err; @@ -58,9 +58,9 @@ int vvz_cli_testPwd(char *block_device) /* Gather password */ printf("Enter the password you want to test: "); - err = vvz_safeReadPassphrase(pwd, VVZ_BIGBUFSIZE); + err = sflite_safeReadPassphrase(pwd, SFLITE_BIGBUFSIZE); if (err) { - vvz_log_error("Could not read password; error %d", err); + sflite_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -70,14 +70,14 @@ int vvz_cli_testPwd(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - err = vvz_cmd_testPwd(&args, &dmb_cell); + err = sflite_cmd_testPwd(&args, &dmb_cell); if (err) { - vvz_log_error("Could not test password; error %d", err); + sflite_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); } else { printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); diff --git a/vuvuzela-userland/src/commands/change_pwd.c b/shufflecake-userland-lite/src/commands/change_pwd.c similarity index 91% rename from vuvuzela-userland/src/commands/change_pwd.c rename to shufflecake-userland-lite/src/commands/change_pwd.c index a56b3dc..dcac991 100644 --- a/vuvuzela-userland/src/commands/change_pwd.c +++ b/shufflecake-userland-lite/src/commands/change_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -51,8 +51,8 @@ * * @return Error code, 0 on success */ -int vvz_cmd_changePwd(vvz_cmd_ChangePwdArgs *args) +int sflite_cmd_changePwd(sflite_cmd_ChangePwdArgs *args) { /* Delegate entirely to the function reading the DMB */ - return vvz_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); + return sflite_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); } diff --git a/vuvuzela-userland/src/commands/close.c b/shufflecake-userland-lite/src/commands/close.c similarity index 72% rename from vuvuzela-userland/src/commands/close.c rename to shufflecake-userland-lite/src/commands/close.c index e14ee21..eff361d 100644 --- a/vuvuzela-userland/src/commands/close.c +++ b/shufflecake-userland-lite/src/commands/close.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/string.h" #include "utils/file.h" @@ -61,18 +61,18 @@ static int _closeVolumes(char **labels, size_t nr_vols); * * @return Error code, 0 on success */ -int vvz_cmd_closeVolumes(char *bdev_path) +int sflite_cmd_closeVolumes(char *bdev_path) { - char *labels[VVZ_DEV_MAX_VOLUMES]; + char *labels[SFLITE_DEV_MAX_VOLUMES]; size_t nr_vols; int err; /* Allocate labels */ size_t i; - for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { - labels[i] = malloc(VVZ_MAX_VOL_NAME_LEN + 1); + for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { + labels[i] = malloc(SFLITE_MAX_VOL_NAME_LEN + 1); if (!labels[1]) { - vvz_log_error("Could not allocate volume label %lu", i); + sflite_log_error("Could not allocate volume label %lu", i); return ENOMEM; // Do not free the ones already allocated } } @@ -80,14 +80,14 @@ int vvz_cmd_closeVolumes(char *bdev_path) /* Read them */ err = _buildVolumesList(bdev_path, labels, &nr_vols); if (err) { - vvz_log_error("Could not read volume list from sysfs; error %d", err); + sflite_log_error("Could not read volume list from sysfs; error %d", err); goto out; } /* Close the volumes (in reverse order of opening) */ err = _closeVolumes(labels, nr_vols); if (err) { - vvz_log_error("Could not close volumes; error %d", err); + sflite_log_error("Could not close volumes; error %d", err); goto out; } @@ -96,7 +96,7 @@ int vvz_cmd_closeVolumes(char *bdev_path) out: - for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { + for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { free(labels[i]); } return err; @@ -111,57 +111,57 @@ out: static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols) { char *bdev_name; - char devid_path[VVZ_BIGBUFSIZE]; + char devid_path[SFLITE_BIGBUFSIZE]; char *str_devid; size_t dev_id; - char nrvolumes_path[VVZ_BIGBUFSIZE]; + char nrvolumes_path[SFLITE_BIGBUFSIZE]; char *str_nrvolumes; /* Get device name as : */ - bdev_name = vvz_disk_getDeviceName(bdev_path); + bdev_name = sflite_disk_getDeviceName(bdev_path); if(!bdev_name) { - vvz_log_error("Could not allocate device name"); + sflite_log_error("Could not allocate device name"); return ENOMEM; } /* Build path to sysfs file containing device ID */ - sprintf(devid_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_name, VVZ_SYSFS_DEVID_FILENAME); + sprintf(devid_path, "%s/%s/%s", SFLITE_SYSFS_BDEVS_DIR, bdev_name, SFLITE_SYSFS_DEVID_FILENAME); /* Build path to sysfs file containing number of open volumes */ - sprintf(nrvolumes_path, "%s/%s/%s", VVZ_SYSFS_BDEVS_DIR, bdev_name, VVZ_SYSFS_OPENVOLUMES_FILENAME); + sprintf(nrvolumes_path, "%s/%s/%s", SFLITE_SYSFS_BDEVS_DIR, bdev_name, SFLITE_SYSFS_OPENVOLUMES_FILENAME); /* Read the device ID */ - str_devid = vvz_readFile(devid_path); + str_devid = sflite_readFile(devid_path); if (!str_devid) { - vvz_log_error("Could not read file %s", devid_path); + sflite_log_error("Could not read file %s", devid_path); return EBADF; } /* Parse the device ID */ if (sscanf(str_devid, "%lu", &dev_id) != 1) { - vvz_log_error("Could not parse device ID:\n%s", str_devid); + sflite_log_error("Could not parse device ID:\n%s", str_devid); return EBADF; } /* Read the number of volumes */ - str_nrvolumes = vvz_readFile(nrvolumes_path); + str_nrvolumes = sflite_readFile(nrvolumes_path); if (!str_nrvolumes) { - vvz_log_error("Could not read file %s", nrvolumes_path); + sflite_log_error("Could not read file %s", nrvolumes_path); return EBADF; } /* Parse the number of volumes */ if (sscanf(str_nrvolumes, "%lu", nr_vols) != 1) { - vvz_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); + sflite_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); return EBADF; } /* Just to be sure */ - if (*nr_vols > VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); + if (*nr_vols > SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); return EBADF; } /* Build labels */ size_t i; for (i = 0; i < *nr_vols; i++) { - sprintf(labels[i], "vvz_%lu_%lu", dev_id, i); + sprintf(labels[i], "sflc_%lu_%lu", dev_id, i); } return 0; @@ -176,12 +176,12 @@ static int _closeVolumes(char **labels, size_t nr_vols) /* Eazy peazy */ int i; for (i = nr_vols-1; i >= 0; i--) { - err = vvz_ops_closeVolume(labels[i]); + err = sflite_ops_closeVolume(labels[i]); if (err) { - vvz_log_error("Could not close volume %s; error %d", labels[i], err); + sflite_log_error("Could not close volume %s; error %d", labels[i], err); return err; } - vvz_log_debug("Closed volume %s", labels[i]); + sflite_log_debug("Closed volume %s", labels[i]); printf("Closed volume /dev/mapper/%s\n", labels[i]); } diff --git a/vuvuzela-userland/src/commands/init.c b/shufflecake-userland-lite/src/commands/init.c similarity index 69% rename from vuvuzela-userland/src/commands/init.c rename to shufflecake-userland-lite/src/commands/init.c index 592eb3c..41a6de8 100644 --- a/vuvuzela-userland/src/commands/init.c +++ b/shufflecake-userland-lite/src/commands/init.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -42,9 +42,9 @@ *****************************************************/ /* The device is randomised in chunks of 1024 blocks (arbitrary number) */ -#define VVZ_BLOCKS_IN_RAND_CHUNK 1024 +#define SFLITE_BLOCKS_IN_RAND_CHUNK 1024 /* That's 4 MiB */ -#define VVZ_RAND_CHUNK_SIZE (VVZ_BLOCKS_IN_RAND_CHUNK * VVZ_BLOCK_SIZE) +#define SFLITE_RAND_CHUNK_SIZE (SFLITE_BLOCKS_IN_RAND_CHUNK * SFLITE_BLOCK_SIZE) /***************************************************** @@ -72,36 +72,36 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); * * @return Error code, 0 on success */ -int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args) +int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args) { - vvz_Dmb dmb; - vvz_Vmb vmb; + sflite_Dmb dmb; + sflite_Vmb vmb; int64_t dev_size; size_t nr_slices; int err; /* Sanity check */ - if (args->nr_vols > VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Cannot create %lu volumes on a single device", args->nr_vols); + if (args->nr_vols > SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Cannot create %lu volumes on a single device", args->nr_vols); return EINVAL; } /* Get device size */ - dev_size = vvz_disk_getSize(args->bdev_path); + dev_size = sflite_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - vvz_log_error("Could not get device size for %s; error %d", args->bdev_path, err); + sflite_log_error("Could not get device size for %s; error %d", args->bdev_path, err); return err; } /* Convert to number of slices */ - nr_slices = vvz_disk_maxSlices(dev_size); - vvz_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); + nr_slices = sflite_disk_maxSlices(dev_size); + sflite_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); /* Fill disk with random bytes, if requested */ if (!args->no_randfill) { err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); if (err) { - vvz_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); + sflite_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); return err; } } @@ -111,16 +111,16 @@ int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args) /* Sample the VMB keys */ size_t i; for (i = 0; i < dmb.nr_vols; i++) { - err = vvz_rand_getStrongBytes(dmb.vmb_keys[i], VVZ_STANDARD_KEYLEN); + err = sflite_rand_getStrongBytes(dmb.vmb_keys[i], SFLITE_STANDARD_KEYLEN); if (err) { - vvz_log_error("Could not sample VMB key number %lu; error %d", i , err); + sflite_log_error("Could not sample VMB key number %lu; error %d", i , err); return err; } } /* And write (encrypted) to disk */ - err = vvz_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + err = sflite_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); if (err) { - vvz_log_error("Could not create DMB and write it to disk; error %d", err); + sflite_log_error("Could not create DMB and write it to disk; error %d", err); return err; } @@ -129,15 +129,15 @@ int vvz_cmd_initVolumes(vvz_cmd_InitArgs *args) for (i = 0; i < args->nr_vols; i++) { /* This volume's prev_vmb_key */ if (i > 0) { - memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], VVZ_STANDARD_KEYLEN); + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLITE_STANDARD_KEYLEN); } /* Sample this volume's VEK */ - vvz_rand_getStrongBytes(vmb.volume_key, VVZ_AESXTS_KEYLEN); + sflite_rand_getStrongBytes(vmb.volume_key, SFLITE_AESXTS_KEYLEN); /* Write complete volume header (VMB + PM) */ - err = vvz_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); + err = sflite_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); if (err) { - vvz_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); + sflite_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); return err; } } @@ -158,9 +158,9 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) int err; /* Allocate chunk */ - rand_chunk = malloc(VVZ_RAND_CHUNK_SIZE); + rand_chunk = malloc(SFLITE_RAND_CHUNK_SIZE); if (!rand_chunk) { - vvz_log_error("Could not allocate %d bytes for chunk of random data", VVZ_RAND_CHUNK_SIZE); + sflite_log_error("Could not allocate %d bytes for chunk of random data", SFLITE_RAND_CHUNK_SIZE); return ENOMEM; } @@ -169,20 +169,20 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) uint64_t sector = 0; while (blocks_remaining > 0) { uint64_t blocks_to_write = - (blocks_remaining > VVZ_BLOCKS_IN_RAND_CHUNK) ? VVZ_BLOCKS_IN_RAND_CHUNK : blocks_remaining; - uint64_t bytes_to_write = blocks_to_write * VVZ_BLOCK_SIZE; + (blocks_remaining > SFLITE_BLOCKS_IN_RAND_CHUNK) ? SFLITE_BLOCKS_IN_RAND_CHUNK : blocks_remaining; + uint64_t bytes_to_write = blocks_to_write * SFLITE_BLOCK_SIZE; /* Sample random bytes */ - err = vvz_rand_getWeakBytes(rand_chunk, bytes_to_write); + err = sflite_rand_getWeakBytes(rand_chunk, bytes_to_write); if (err) { - vvz_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); + sflite_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); goto out; } /* Write on disk */ - err = vvz_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); + err = sflite_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); if (err) { - vvz_log_error("Could not write random bytes on disk; error %d", err); + sflite_log_error("Could not write random bytes on disk; error %d", err); goto out; } diff --git a/vuvuzela-userland/src/commands/open.c b/shufflecake-userland-lite/src/commands/open.c similarity index 74% rename from vuvuzela-userland/src/commands/open.c rename to shufflecake-userland-lite/src/commands/open.c index 6b789bd..11ecddd 100644 --- a/vuvuzela-userland/src/commands/open.c +++ b/shufflecake-userland-lite/src/commands/open.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/disk.h" #include "utils/file.h" @@ -63,33 +63,33 @@ static int _getNextDevId(size_t *next_dev_id); * * @return Error code (also if no volume could be opened), 0 on success */ -int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args) +int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args) { int64_t dev_size; size_t nr_slices; - vvz_DmbCell dmb_cell; - vvz_Vmb vmbs[VVZ_DEV_MAX_VOLUMES]; + sflite_DmbCell dmb_cell; + sflite_Vmb vmbs[SFLITE_DEV_MAX_VOLUMES]; size_t dev_id; int err; /* Get number of slices */ - dev_size = vvz_disk_getSize(args->bdev_path); + dev_size = sflite_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - vvz_log_error("Could not read device size for %s; error %d", args->bdev_path, err); + sflite_log_error("Could not read device size for %s; error %d", args->bdev_path, err); return err; } - nr_slices = vvz_disk_maxSlices(dev_size); + nr_slices = sflite_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ - err = vvz_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); + err = sflite_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); if (err) { - vvz_log_error("Could not read DMB; error %d", err); + sflite_log_error("Could not read DMB; error %d", err); return err; } /* Was there one? */ - if (dmb_cell.vol_idx >= VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("The provided password opens no volume on the device"); + if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("The provided password opens no volume on the device"); return EINVAL; } printf("Password is correct! Opening volumes...\n"); @@ -108,9 +108,9 @@ int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args) } /* Read and unlock VMB */ - err = vvz_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + err = sflite_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); if (err) { - vvz_log_error("Could not read VMB %d on device %s; error %d", + sflite_log_error("Could not read VMB %d on device %s; error %d", i, args->bdev_path, err); return err; } @@ -119,23 +119,23 @@ int vvz_cmd_openVolumes(vvz_cmd_OpenArgs *args) /* Get the ID that will be assigned to the block device */ err = _getNextDevId(&dev_id); if (err) { - vvz_log_error("Could not get next device ID; error %d", err); + sflite_log_error("Could not get next device ID; error %d", err); return err; } - vvz_log_debug("Next device ID is %lu", dev_id); + sflite_log_debug("Next device ID is %lu", dev_id); /* Open volumes "in order" */ for (i = 0; i <= dmb_cell.vol_idx; i++) { - err = vvz_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + err = sflite_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); if (err) { - vvz_log_error("Could not open volume %d; error %d. " + sflite_log_error("Could not open volume %d; error %d. " "Previous volumes on the device might have already " "been opened, it's recommended you close them", i, err); return err; } - vvz_log_debug("Successfully opened volume %d with VMB key", i); - printf("Opened volume /dev/mapper/vvz_%lu_%d\n", dev_id, i); + sflite_log_debug("Successfully opened volume %d with VMB key", i); + printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); } return 0; @@ -153,21 +153,21 @@ static int _getNextDevId(size_t *next_dev_id) int err; /* Read sysfs entry */ - str_nextdevid = vvz_readFile(VVZ_SYSFS_NEXTDEVID); + str_nextdevid = sflite_readFile(SFLITE_SYSFS_NEXTDEVID); if (!str_nextdevid) { - vvz_log_error("Could not read sysfs entry %s", VVZ_SYSFS_NEXTDEVID); + sflite_log_error("Could not read sysfs entry %s", SFLITE_SYSFS_NEXTDEVID); return EINVAL; } /* Parse integer */ if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { - vvz_log_error("Error parsing content of file %s", VVZ_SYSFS_NEXTDEVID); + sflite_log_error("Error parsing content of file %s", SFLITE_SYSFS_NEXTDEVID); err = EINVAL; goto err_devid; } /* Sanity check */ - if (*next_dev_id >= VVZ_TOT_MAX_DEVICES) { - vvz_log_error("There are already %d open devices, this is the maximum allowed", VVZ_TOT_MAX_DEVICES); + if (*next_dev_id >= SFLITE_TOT_MAX_DEVICES) { + sflite_log_error("There are already %d open devices, this is the maximum allowed", SFLITE_TOT_MAX_DEVICES); err = E2BIG; goto err_devid; } diff --git a/vuvuzela-userland/src/commands/test_pwd.c b/shufflecake-userland-lite/src/commands/test_pwd.c similarity index 91% rename from vuvuzela-userland/src/commands/test_pwd.c rename to shufflecake-userland-lite/src/commands/test_pwd.c index 1d011c7..09cd5d3 100644 --- a/vuvuzela-userland/src/commands/test_pwd.c +++ b/shufflecake-userland-lite/src/commands/test_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -52,8 +52,8 @@ * * @return Error code, 0 on success */ -int vvz_cmd_testPwd(vvz_cmd_OpenArgs *args, vvz_DmbCell *dmb_cell) +int sflite_cmd_testPwd(sflite_cmd_OpenArgs *args, sflite_DmbCell *dmb_cell) { /* Delegate entirely to the function reading the DMB */ - return vvz_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); + return sflite_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); } diff --git a/vuvuzela-userland/src/header/device_master_block.c b/shufflecake-userland-lite/src/header/device_master_block.c similarity index 64% rename from vuvuzela-userland/src/header/device_master_block.c rename to shufflecake-userland-lite/src/header/device_master_block.c index a6522d6..0e6ce22 100644 --- a/vuvuzela-userland/src/header/device_master_block.c +++ b/shufflecake-userland-lite/src/header/device_master_block.c @@ -60,21 +60,21 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool * * @return The error code, 0 on success */ -int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) +int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) { char *salt; int err; /* Sanity check */ - if (dmb->nr_vols > VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Randomise whole block */ - err = vvz_rand_getWeakBytes(disk_block, VVZ_BLOCK_SIZE); + err = sflite_rand_getWeakBytes(disk_block, SFLITE_BLOCK_SIZE); if (err) { - vvz_log_error("Could not randomise DMB; error %d", err); + sflite_log_error("Could not randomise DMB; error %d", err); return err; } @@ -84,12 +84,12 @@ int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) /* Loop over all VMB keys to encrypt them */ size_t i; for (i = 0; i < dmb->nr_vols; i++) { - char *dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (i * VVZ_DMB_CELL_SIZE); + char *dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (i * SFLITE_DMB_CELL_SIZE); /* Encrypt it */ err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); if (err) { - vvz_log_error("Could not encrypt VMB key number %lu; error %d", i, err); + sflite_log_error("Could not encrypt VMB key number %lu; error %d", i, err); return err; } } @@ -106,50 +106,50 @@ int vvz_dmb_seal(vvz_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= VVZ_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= SFLITE_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int vvz_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, vvz_DmbCell *dmb_cell) +int sflite_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell) { // KDF salt char *salt; // The KDF-derived key - char kek[VVZ_STANDARD_KEYLEN]; + char kek[SFLITE_STANDARD_KEYLEN]; // The unlocked VMB key - char vmb_key[VVZ_STANDARD_KEYLEN]; + char vmb_key[SFLITE_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK once and for all */ salt = disk_block; - err = vvz_argon2id_derive(pwd, pwd_len, salt, kek); + err = sflite_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - vvz_log_error("Could not perform KDF: error %d", err); + sflite_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - vvz_log_debug("Successfully derived key-encryption-key with KDF"); + sflite_log_debug("Successfully derived key-encryption-key with KDF"); /* Init dmb->vol_idx to invalid */ - dmb_cell->vol_idx = VVZ_DEV_MAX_VOLUMES; + dmb_cell->vol_idx = SFLITE_DEV_MAX_VOLUMES; /* Try all DMB cells */ size_t i; - for (i = 0; i < VVZ_DEV_MAX_VOLUMES; i++) { - char *enc_dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (i * VVZ_DMB_CELL_SIZE); + for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { + char *enc_dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (i * SFLITE_DMB_CELL_SIZE); bool match; /* Try to decrypt this one */ err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); if (err) { - vvz_log_error("Error decrypting DMB cell number %lu; error %d", i, err); + sflite_log_error("Error decrypting DMB cell number %lu; error %d", i, err); goto bad_decrypt; } /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ if (match) { - vvz_log_debug("The provided password unlocks volume %lu", i); + sflite_log_debug("The provided password unlocks volume %lu", i); dmb_cell->vol_idx = i; - memcpy(dmb_cell->vmb_key, vmb_key, VVZ_STANDARD_KEYLEN); + memcpy(dmb_cell->vmb_key, vmb_key, SFLITE_STANDARD_KEYLEN); } } @@ -160,7 +160,7 @@ int vvz_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, vvz_DmbCell *dmb bad_decrypt: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, VVZ_STANDARD_KEYLEN); + memset(kek, 0, SFLITE_STANDARD_KEYLEN); return err; } @@ -174,28 +174,28 @@ bad_kdf: * * @return Error code, 0 on success */ -int vvz_dmb_setCell(char *disk_block, vvz_DmbCell *dmb_cell, char *pwd, size_t pwd_len) +int sflite_dmb_setCell(char *disk_block, sflite_DmbCell *dmb_cell, char *pwd, size_t pwd_len) { char *salt; char *enc_dmb_cell; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Pointers inside DMB */ salt = disk_block; - enc_dmb_cell = (salt + VVZ_ARGON_SALTLEN) + (dmb_cell->vol_idx * VVZ_DMB_CELL_SIZE); + enc_dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLITE_DMB_CELL_SIZE); /* Encrypt with KDF-derived key */ err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); if (err) { - vvz_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); + sflite_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); return err; } - vvz_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); + sflite_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); return 0; } @@ -210,36 +210,36 @@ static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vm { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + VVZ_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + VVZ_STANDARD_KEYLEN; + char *enc_vmb_key = iv + SFLITE_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLITE_STANDARD_KEYLEN; // Key-encryption-key derived from KDF - char kek[VVZ_STANDARD_KEYLEN]; + char kek[SFLITE_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK */ - err = vvz_argon2id_derive(pwd, pwd_len, salt, kek); + err = sflite_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - vvz_log_error("Could not perform KDF: error %d", err); + sflite_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - vvz_log_debug("Successfully derived key-encryption-key with KDF"); + sflite_log_debug("Successfully derived key-encryption-key with KDF"); /* Sample VMB_IV */ - err = vvz_rand_getWeakBytes(iv, VVZ_AESGCM_PADDED_IVLEN); + err = sflite_rand_getWeakBytes(iv, SFLITE_AESGCM_PADDED_IVLEN); if (err) { - vvz_log_error("Could not sample prologue IV: error %d", err); + sflite_log_error("Could not sample prologue IV: error %d", err); goto bad_sample_iv; } - vvz_log_debug("Successfully sampled prologue IV"); + sflite_log_debug("Successfully sampled prologue IV"); /* Encrypt the VMB key */ - err = vvz_aes256gcm_encrypt(kek, vmb_key, VVZ_STANDARD_KEYLEN, iv, enc_vmb_key, mac); + err = sflite_aes256gcm_encrypt(kek, vmb_key, SFLITE_STANDARD_KEYLEN, iv, enc_vmb_key, mac); if (err) { - vvz_log_error("Could not encrypt the VMB key: error %d", err); + sflite_log_error("Could not encrypt the VMB key: error %d", err); goto bad_encrypt; } - vvz_log_debug("Successfully encrypted VMB key with key-encryption-key"); + sflite_log_debug("Successfully encrypted VMB key with key-encryption-key"); // No prob err = 0; @@ -249,7 +249,7 @@ bad_encrypt: bad_sample_iv: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, VVZ_STANDARD_KEYLEN); + memset(kek, 0, SFLITE_STANDARD_KEYLEN); return err; } @@ -258,18 +258,18 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + VVZ_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + VVZ_STANDARD_KEYLEN; + char *enc_vmb_key = iv + SFLITE_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLITE_STANDARD_KEYLEN; // Error code int err; /* Decrypt the VMB key */ - err = vvz_aes256gcm_decrypt(kek, enc_vmb_key, VVZ_STANDARD_KEYLEN, mac, iv, vmb_key, match); + err = sflite_aes256gcm_decrypt(kek, enc_vmb_key, SFLITE_STANDARD_KEYLEN, mac, iv, vmb_key, match); if (err) { - vvz_log_error("Error while decrypting VMB key: error %d", err); + sflite_log_error("Error while decrypting VMB key: error %d", err); return err; } - vvz_log_debug("Decrypted VMB key: MAC match = %d", *match); + sflite_log_debug("Decrypted VMB key: MAC match = %d", *match); return 0; } diff --git a/vuvuzela-userland/src/header/position_map.c b/shufflecake-userland-lite/src/header/position_map.c similarity index 80% rename from vuvuzela-userland/src/header/position_map.c rename to shufflecake-userland-lite/src/header/position_map.c index 4abdab3..1059378 100644 --- a/vuvuzela-userland/src/header/position_map.c +++ b/shufflecake-userland-lite/src/header/position_map.c @@ -31,7 +31,7 @@ #include #include "header.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/math.h" #include "utils/log.h" @@ -49,42 +49,42 @@ * position map as well. * * @return The memory buffer containing the position map */ -void *vvz_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) +void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) { - char pmb[VVZ_BLOCK_SIZE]; - char iv[VVZ_AESXTS_IVLEN]; + char pmb[SFLITE_BLOCK_SIZE]; + char iv[SFLITE_AESXTS_IVLEN]; void *epm; size_t nr_pmbs; uint64_t pblk_num; int err; // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - nr_pmbs = ceil(nr_slices, VVZ_SLICE_IDX_PER_BLOCK); + nr_pmbs = ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); // Allocate EPM - epm = malloc(nr_pmbs * VVZ_BLOCK_SIZE); + epm = malloc(nr_pmbs * SFLITE_BLOCK_SIZE); if (!epm) { - vvz_log_error("Could not malloc EPM"); + sflite_log_error("Could not malloc EPM"); return NULL; } // Fill cleartext PMB with 0xFF - memset(pmb, VVZ_EPM_FILLER, VVZ_BLOCK_SIZE); + memset(pmb, SFLITE_EPM_FILLER, SFLITE_BLOCK_SIZE); // First physical block number - pblk_num = vvz_pmStartBlock(vol_idx, nr_slices); + pblk_num = sflite_pmStartBlock(vol_idx, nr_slices); // Loop to encrypt each PMB int i; for (i = 0; i < nr_pmbs; i++) { // Set IV - memset(iv, 0, VVZ_AESXTS_IVLEN); + memset(iv, 0, SFLITE_AESXTS_IVLEN); *((uint64_t*)iv) = htole64(pblk_num); // Encrypt - err = vvz_aes256xts_encrypt(volume_key, pmb, VVZ_BLOCK_SIZE, iv, - epm + i*VVZ_BLOCK_SIZE); + err = sflite_aes256xts_encrypt(volume_key, pmb, SFLITE_BLOCK_SIZE, iv, + epm + i*SFLITE_BLOCK_SIZE); if (err) { - vvz_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); + sflite_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); free(epm); return NULL; } diff --git a/vuvuzela-userland/src/header/volume_master_block.c b/shufflecake-userland-lite/src/header/volume_master_block.c similarity index 67% rename from vuvuzela-userland/src/header/volume_master_block.c rename to shufflecake-userland-lite/src/header/volume_master_block.c index 72c36b5..e44963b 100644 --- a/vuvuzela-userland/src/header/volume_master_block.c +++ b/shufflecake-userland-lite/src/header/volume_master_block.c @@ -41,10 +41,10 @@ *****************************************************/ /* Serialise the VMB before encrypting it */ -static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb); +static void _serialiseVmb(sflite_Vmb *vmb, char *clear_vmb); /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb); +static int _deserialiseVmb(char *clear_vmb, sflite_Vmb *vmb); /***************************************************** @@ -61,44 +61,44 @@ static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb); * * @return The error code, 0 on success */ -int vvz_vmb_seal(vvz_Vmb *vmb, char *vmb_key, char *disk_block) +int sflite_vmb_seal(sflite_Vmb *vmb, char *vmb_key, char *disk_block) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + VVZ_AESCTR_IVLEN; + char *enc_vmb = iv + SFLITE_AESCTR_IVLEN; // Serialised VMB (dynamically allocated), to be encrypted char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(VVZ_CLEAR_VMB_LEN); + clear_vmb = malloc(SFLITE_CLEAR_VMB_LEN); if (!clear_vmb) { - vvz_log_error("Could not allocate %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); + sflite_log_error("Could not allocate %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - vvz_log_debug("Successfully allocated %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); + sflite_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); /* Serialise the struct */ _serialiseVmb(vmb, clear_vmb); - vvz_log_debug("Serialised VMB struct"); + sflite_log_debug("Serialised VMB struct"); /* Sample VMB IV */ - err = vvz_rand_getWeakBytes(iv, VVZ_AESGCM_PADDED_IVLEN); + err = sflite_rand_getWeakBytes(iv, SFLITE_AESGCM_PADDED_IVLEN); if (err) { - vvz_log_error("Could not sample VMB IV: error %d", err); + sflite_log_error("Could not sample VMB IV: error %d", err); goto bad_sample_iv; } - vvz_log_debug("Successfully sampled VMB IV"); + sflite_log_debug("Successfully sampled VMB IV"); /* Encrypt the VMB */ - err = vvz_aes256ctr_encrypt(vmb_key, clear_vmb, VVZ_CLEAR_VMB_LEN, iv, enc_vmb); + err = sflite_aes256ctr_encrypt(vmb_key, clear_vmb, SFLITE_CLEAR_VMB_LEN, iv, enc_vmb); if (err) { - vvz_log_error("Could not encrypt VMB: error %d", err); + sflite_log_error("Could not encrypt VMB: error %d", err); goto bad_encrypt; } - vvz_log_debug("Successfully encrypted VMB"); + sflite_log_debug("Successfully encrypted VMB"); // No prob err = 0; @@ -107,7 +107,7 @@ int vvz_vmb_seal(vvz_Vmb *vmb, char *vmb_key, char *disk_block) bad_encrypt: bad_sample_iv: /* Always wipe and free the cleartext VMB, even on success */ - memset(clear_vmb, 0, VVZ_CLEAR_VMB_LEN); + memset(clear_vmb, 0, SFLITE_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -123,40 +123,40 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int vvz_vmb_unseal(char *disk_block, char *vmb_key, vvz_Vmb *vmb) +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflite_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + VVZ_AESCTR_IVLEN; + char *enc_vmb = iv + SFLITE_AESCTR_IVLEN; // Decrypted VMB (dynamically allocated), to be deserialised char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(VVZ_CLEAR_VMB_LEN); + clear_vmb = malloc(SFLITE_CLEAR_VMB_LEN); if (!clear_vmb) { - vvz_log_error("Could not allocate %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); + sflite_log_error("Could not allocate %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - vvz_log_debug("Successfully allocated %d bytes for VMB cleartext", VVZ_CLEAR_VMB_LEN); + sflite_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); /* Decrypt the VMB */ - err = vvz_aes256ctr_decrypt(vmb_key, enc_vmb, VVZ_CLEAR_VMB_LEN, iv, clear_vmb); + err = sflite_aes256ctr_decrypt(vmb_key, enc_vmb, SFLITE_CLEAR_VMB_LEN, iv, clear_vmb); if (err) { - vvz_log_error("Error while decrypting VMB: error %d", err); + sflite_log_error("Error while decrypting VMB: error %d", err); goto bad_decrypt; } - vvz_log_debug("Successfully decrypted VMB"); + sflite_log_debug("Successfully decrypted VMB"); /* Deserialise the struct */ err = _deserialiseVmb(clear_vmb, vmb); if (err) { - vvz_log_error("Error while deserialising VMB: error %d", err); + sflite_log_error("Error while deserialising VMB: error %d", err); goto bad_deserialise; } - vvz_log_debug("Deserialised VMB struct"); + sflite_log_debug("Deserialised VMB struct"); // No prob err = 0; @@ -165,7 +165,7 @@ int vvz_vmb_unseal(char *disk_block, char *vmb_key, vvz_Vmb *vmb) bad_deserialise: bad_decrypt: /* Always wipe and free the VMB cleartext, even on success */ - memset(clear_vmb, 0, VVZ_CLEAR_VMB_LEN); + memset(clear_vmb, 0, SFLITE_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -177,18 +177,18 @@ bad_clear_alloc: *****************************************************/ /* Serialise the payload before encrypting it */ -static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb) +static void _serialiseVmb(sflite_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + VVZ_AESXTS_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLITE_AESXTS_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLITE_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, VVZ_AESXTS_KEYLEN); + memcpy(p_vol_key, vmb->volume_key, SFLITE_AESXTS_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(p_prev_vmb_key, vmb->prev_vmb_key, VVZ_STANDARD_KEYLEN); + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLITE_STANDARD_KEYLEN); /* Write the number of slices (network byte order) */ *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); @@ -200,18 +200,18 @@ static void _serialiseVmb(vvz_Vmb *vmb, char *clear_vmb) /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, vvz_Vmb *vmb) +static int _deserialiseVmb(char *clear_vmb, sflite_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + VVZ_AESXTS_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + VVZ_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLITE_AESXTS_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLITE_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, VVZ_AESXTS_KEYLEN); + memcpy(vmb->volume_key, p_vol_key, SFLITE_AESXTS_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(vmb->prev_vmb_key, p_prev_vmb_key, VVZ_STANDARD_KEYLEN); + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLITE_STANDARD_KEYLEN); /* Read number of slices (network byte order) */ vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); diff --git a/vuvuzela-userland/src/main.c b/shufflecake-userland-lite/src/main.c similarity index 97% rename from vuvuzela-userland/src/main.c rename to shufflecake-userland-lite/src/main.c index 08a4b12..50d86e7 100644 --- a/vuvuzela-userland/src/main.c +++ b/shufflecake-userland-lite/src/main.c @@ -39,6 +39,6 @@ int main(int argc, char **argv) { - return vvz_cli_dispatch(argc, argv); + return sflite_cli_dispatch(argc, argv); } diff --git a/vuvuzela-userland/src/operations/devmapper.c b/shufflecake-userland-lite/src/operations/devmapper.c similarity index 76% rename from vuvuzela-userland/src/operations/devmapper.c rename to shufflecake-userland-lite/src/operations/devmapper.c index 4e54af2..1d0ebae 100644 --- a/vuvuzela-userland/src/operations/devmapper.c +++ b/shufflecake-userland-lite/src/operations/devmapper.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/string.h" @@ -46,7 +46,7 @@ *****************************************************/ /** - * Build parameter list for ctor in dm_vvz, and send DM ioctl to create + * Build parameter list for ctor in dm_sflite, and send DM ioctl to create * virtual block device. * * @param bdev_path The path to the underlying device @@ -56,35 +56,35 @@ * * @return Error code, 0 on success */ -int vvz_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, vvz_Vmb *vmb) +int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflite_Vmb *vmb) { - char label[VVZ_BIGBUFSIZE]; + char label[SFLITE_BIGBUFSIZE]; char *hex_key; - char params[VVZ_BIGBUFSIZE]; + char params[SFLITE_BIGBUFSIZE]; uint64_t num_sectors; int err; /* Build volume label */ - sprintf(label, "vvz_%lu_%lu", dev_id, vol_idx); + sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); /* Get the hex version of the volume's data section key */ - hex_key = vvz_toHex(vmb->volume_key, VVZ_AESXTS_KEYLEN); + hex_key = sflite_toHex(vmb->volume_key, SFLITE_AESXTS_KEYLEN); if (!hex_key) { - vvz_log_error("Could not encode volume key to hexadecimal"); + sflite_log_error("Could not encode volume key to hexadecimal"); err = ENOMEM; goto err_hexkey; } /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * VVZ_SLICE_SCALE * VVZ_BLOCK_SCALE; + num_sectors = ((uint64_t) vmb->nr_slices) * SFLITE_SLICE_SCALE * SFLITE_BLOCK_SCALE; /* Build param list */ - sprintf(params, "%lu %s %lu %lu %s", dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); + sprintf(params, "%d %lu %s %lu %lu %s", SFLC_MODE_LITE, dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); /* Issue ioctl */ - err = vvz_dm_create(label, num_sectors, params); + err = sflite_dm_create(label, num_sectors, params); if (err) { - vvz_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + sflite_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); goto err_dmcreate; } err = 0; @@ -105,8 +105,8 @@ err_hexkey: * * @return Error code, 0 on success */ -int vvz_ops_closeVolume(char *label) +int sflite_ops_closeVolume(char *label) { /* Issue ioctl */ - return vvz_dm_destroy(label); + return sflite_dm_destroy(label); } diff --git a/vuvuzela-userland/src/operations/dmb.c b/shufflecake-userland-lite/src/operations/dmb.c similarity index 67% rename from vuvuzela-userland/src/operations/dmb.c rename to shufflecake-userland-lite/src/operations/dmb.c index b15e583..31569e7 100644 --- a/vuvuzela-userland/src/operations/dmb.c +++ b/shufflecake-userland-lite/src/operations/dmb.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -54,30 +54,30 @@ * * @return Error code, 0 on success */ -int vvz_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, vvz_Dmb *dmb) +int sflite_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflite_Dmb *dmb) { /* On-disk DMB */ - char enc_dmb[VVZ_BLOCK_SIZE]; + char enc_dmb[SFLITE_BLOCK_SIZE]; /* Error code */ int err; /* Sanity check */ - if (dmb->nr_vols > VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Seal DMB */ - err = vvz_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); + err = sflite_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); if (err) { - vvz_log_error("Coul dnot seal DMB; error %d", err); + sflite_log_error("Coul dnot seal DMB; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = vvz_disk_writeBlock(bdev_path, 0, enc_dmb); + err = sflite_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - vvz_log_error("Could not write DMB to disk; error %d", err); + sflite_log_error("Could not write DMB to disk; error %d", err); return err; } @@ -93,26 +93,26 @@ int vvz_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, vvz_Dmb *dm * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= VVZ_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= SFLITE_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int vvz_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, vvz_DmbCell *dmb) +int sflite_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflite_DmbCell *dmb) { - char enc_dmb[VVZ_BLOCK_SIZE]; + char enc_dmb[SFLITE_BLOCK_SIZE]; int err; /* Read DMB from disk (at sector 0) */ - err = vvz_disk_readBlock(bdev_path, 0, enc_dmb); + err = sflite_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - vvz_log_error("Could not read DMB from disk; error %d", err); + sflite_log_error("Could not read DMB from disk; error %d", err); return err; } /* Unseal it */ - err = vvz_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); + err = sflite_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); if (err) { - vvz_log_error("Could not unseal DMB; error %d", err); + sflite_log_error("Could not unseal DMB; error %d", err); return err; } @@ -129,35 +129,35 @@ int vvz_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, vvz_DmbCell *dmb * * @return Error code, 0 on success */ -int vvz_ops_rewriteDmbCell(char *bdev_path, vvz_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) +int sflite_ops_rewriteDmbCell(char *bdev_path, sflite_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) { - char enc_dmb[VVZ_BLOCK_SIZE]; + char enc_dmb[SFLITE_BLOCK_SIZE]; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= VVZ_DEV_MAX_VOLUMES) { - vvz_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= SFLITE_DEV_MAX_VOLUMES) { + sflite_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Read DMB from disk (at sector 0) */ - err = vvz_disk_readBlock(bdev_path, 0, enc_dmb); + err = sflite_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - vvz_log_error("Could not read DMB from disk; error %d", err); + sflite_log_error("Could not read DMB from disk; error %d", err); return err; } /* Update the relevant cell */ - err = vvz_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); + err = sflite_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); if (err) { - vvz_log_error("Could not update DMB cell; error %d", err); + sflite_log_error("Could not update DMB cell; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = vvz_disk_writeBlock(bdev_path, 0, enc_dmb); + err = sflite_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - vvz_log_error("Could not write DMB to disk; error %d", err); + sflite_log_error("Could not write DMB to disk; error %d", err); return err; } diff --git a/vuvuzela-userland/src/operations/volume_header.c b/shufflecake-userland-lite/src/operations/volume_header.c similarity index 71% rename from vuvuzela-userland/src/operations/volume_header.c rename to shufflecake-userland-lite/src/operations/volume_header.c index 9c5e611..40e0fda 100644 --- a/vuvuzela-userland/src/operations/volume_header.c +++ b/shufflecake-userland-lite/src/operations/volume_header.c @@ -33,7 +33,7 @@ #include "header.h" #include "header.h" #include "operations.h" -#include "utils/vvz.h" +#include "utils/sflite.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -53,42 +53,42 @@ * * @return Error code, 0 on success */ -int vvz_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, vvz_Vmb *vmb, size_t vol_idx) +int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflite_Vmb *vmb, size_t vol_idx) { - char enc_vmb[VVZ_BLOCK_SIZE]; + char enc_vmb[SFLITE_BLOCK_SIZE]; void *epm; uint64_t sector; int err; // Encrypt VMB - err = vvz_vmb_seal(vmb, vmb_key, enc_vmb); + err = sflite_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { - vvz_log_error("Could not seal VMB; error %d", err); + sflite_log_error("Could not seal VMB; error %d", err); goto out; } // Write it to disk sector = 1 + vol_idx; - err = vvz_disk_writeBlock(bdev_path, sector, enc_vmb); + err = sflite_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { - vvz_log_error("Could not write VMB to disk; error %d", err); + sflite_log_error("Could not write VMB to disk; error %d", err); goto out; } // Create encrypted empty position map - epm = vvz_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key); + epm = sflite_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key); if (!epm) { - vvz_log_error("Could not create encrypted empty position map."); + sflite_log_error("Could not create encrypted empty position map."); err = ENOMEM; goto out; } // Write to disk - sector = vvz_pmStartBlock(vol_idx, vmb->nr_slices); - err = vvz_disk_writeManyBlocks(bdev_path, sector, epm, - ceil(vmb->nr_slices, VVZ_SLICE_IDX_PER_BLOCK)); + sector = sflite_pmStartBlock(vol_idx, vmb->nr_slices); + err = sflite_disk_writeManyBlocks(bdev_path, sector, epm, + ceil(vmb->nr_slices, SFLITE_SLICE_IDX_PER_BLOCK)); if (err) { - vvz_log_error("Could not write encrypted PosMap; error %d", err); + sflite_log_error("Could not write encrypted PosMap; error %d", err); } free(epm); @@ -110,30 +110,30 @@ out: * * @return Error code, 0 on success */ -int vvz_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, vvz_Vmb *vmb) +int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflite_Vmb *vmb) { - char enc_vmb[VVZ_BLOCK_SIZE]; + char enc_vmb[SFLITE_BLOCK_SIZE]; uint64_t sector; int err; /* Read encrypted VMB from disk */ sector = 1 + vol_idx; - err = vvz_disk_readBlock(bdev_path, sector, enc_vmb); + err = sflite_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { - vvz_log_error("Could not read VMB from disk; error %d", err); + sflite_log_error("Could not read VMB from disk; error %d", err); return err; } /* Unseal it */ - err = vvz_vmb_unseal(enc_vmb, vmb_key, vmb); + err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb); if (err) { - vvz_log_error("Could not unseal VMB; error %d", err); + sflite_log_error("Could not unseal VMB; error %d", err); return err; } /* Compare the number of slices */ if (nr_slices != vmb->nr_slices) { - vvz_log_error("Incompatible header size: the device size was different when the volumes" + sflite_log_error("Incompatible header size: the device size was different when the volumes" "were created. Did you resize the device %s since last time?", bdev_path); return EINVAL; } diff --git a/vuvuzela-userland/src/utils/crypto.c b/shufflecake-userland-lite/src/utils/crypto.c similarity index 66% rename from vuvuzela-userland/src/utils/crypto.c rename to shufflecake-userland-lite/src/utils/crypto.c index 6f93c4a..d7331e1 100644 --- a/vuvuzela-userland/src/utils/crypto.c +++ b/shufflecake-userland-lite/src/utils/crypto.c @@ -44,7 +44,7 @@ * *@return The error code (0 on success) */ -int vvz_rand_getStrongBytes(char *buf, size_t buflen) +int sflite_rand_getStrongBytes(char *buf, size_t buflen) { gcry_randomize(buf, buflen, GCRY_VERY_STRONG_RANDOM); return 0; @@ -61,7 +61,7 @@ int vvz_rand_getStrongBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int vvz_rand_getWeakBytes(char *buf, size_t buflen) +int sflite_rand_getWeakBytes(char *buf, size_t buflen) { gcry_create_nonce(buf, buflen); return 0; @@ -81,7 +81,7 @@ int vvz_rand_getWeakBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -89,26 +89,26 @@ int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - vvz_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + sflite_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully instantiated AES256-CTR cipher handle"); + sflite_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); if (err) { - vvz_log_error("Could not set AES key: error %d", err); + sflite_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - vvz_log_debug("Successfully set the AES key"); + sflite_log_debug("Successfully set the AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, VVZ_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, SFLITE_AESCTR_IVLEN); if (err) { - vvz_log_error("Could not set AES-CTR IV: error %d", err); + sflite_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - vvz_log_debug("Successfully set the IV"); + sflite_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -119,10 +119,10 @@ int vvz_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct } // Error check if (err) { - vvz_log_error("Could not encrypt: error %d", err); + sflite_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - vvz_log_debug("Successfully encrypted"); + sflite_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -149,7 +149,7 @@ bad_open: * *@return The error code (0 on success) */ -int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) +int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -157,26 +157,26 @@ int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - vvz_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + sflite_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully instantiated AES256-CTR cipher handle"); + sflite_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); if (err) { - vvz_log_error("Could not set AES key: error %d", err); + sflite_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - vvz_log_debug("Successfully set AES key"); + sflite_log_debug("Successfully set AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, VVZ_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, SFLITE_AESCTR_IVLEN); if (err) { - vvz_log_error("Could not set AES-CTR IV: error %d", err); + sflite_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - vvz_log_debug("Successfully set IV"); + sflite_log_debug("Successfully set IV"); // Decrypt if (pt == NULL) { // In-place @@ -187,10 +187,10 @@ int vvz_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt } // Error check if (err) { - vvz_log_error("Could not decrypt: error %d", err); + sflite_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - vvz_log_debug("Successfully decrypted"); + sflite_log_debug("Successfully decrypted"); // No prob err = 0; @@ -219,7 +219,7 @@ bad_open: * * @return The error code (0 on success) */ -int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -227,26 +227,26 @@ int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_XTS, GCRY_CIPHER_SECURE); if (err) { - vvz_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); + sflite_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully instantiated AES256-XTS cipher handle"); + sflite_log_debug("Successfully instantiated AES256-XTS cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, VVZ_AESXTS_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLITE_AESXTS_KEYLEN); if (err) { - vvz_log_error("Could not set AES-XTS key: error %d", err); + sflite_log_error("Could not set AES-XTS key: error %d", err); goto bad_setkey; } - vvz_log_debug("Successfully set the AES-XTS key"); + sflite_log_debug("Successfully set the AES-XTS key"); // Set the IV (not a counter, as per Gcrypt docs) - err = gcry_cipher_setiv(hd, iv, VVZ_AESXTS_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLITE_AESXTS_IVLEN); if (err) { - vvz_log_error("Could not set AES-XTS IV: error %d", err); + sflite_log_error("Could not set AES-XTS IV: error %d", err); goto bad_setiv; } - vvz_log_debug("Successfully set the IV"); + sflite_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -257,10 +257,10 @@ int vvz_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct } // Error check if (err) { - vvz_log_error("Could not encrypt: error %d", err); + sflite_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - vvz_log_debug("Successfully encrypted"); + sflite_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -289,7 +289,7 @@ bad_open: * *@return The error code (0 on success) */ -int vvz_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) +int sflite_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -297,42 +297,42 @@ int vvz_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - vvz_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + sflite_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully instantiated AES256-GCM cipher handle"); + sflite_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); if (err) { - vvz_log_error("Could not set AES key: error %d", err); + sflite_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - vvz_log_debug("Successfully set the AES key"); + sflite_log_debug("Successfully set the AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, VVZ_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLITE_AESGCM_IVLEN); if (err) { - vvz_log_error("Could not set AES-GCM IV: error %d", err); + sflite_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - vvz_log_debug("Successfully set the IV"); + sflite_log_debug("Successfully set the IV"); // Encrypt err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); if (err) { - vvz_log_error("Could not encrypt: error %d", err); + sflite_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - vvz_log_debug("Successfully encrypted"); + sflite_log_debug("Successfully encrypted"); // Get MAC - err = gcry_cipher_gettag(hd, tag, VVZ_AESGCM_TAGLEN); + err = gcry_cipher_gettag(hd, tag, SFLITE_AESGCM_TAGLEN); if (err) { - vvz_log_error("Could not get MAC: error %d", err); + sflite_log_error("Could not get MAC: error %d", err); goto bad_gettag; } - vvz_log_debug("Successfully gotten MAC"); + sflite_log_debug("Successfully gotten MAC"); // No prob? err = 0; @@ -364,7 +364,7 @@ bad_open: * *@return The error code (0 on success) */ -int vvz_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) +int sflite_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -372,52 +372,52 @@ int vvz_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *i // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - vvz_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + sflite_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully instantiated AES256-GCM cipher handle"); + sflite_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, VVZ_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); if (err) { - vvz_log_error("Could not set AES key: error %d", err); + sflite_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - vvz_log_debug("Successfully set AES key"); + sflite_log_debug("Successfully set AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, VVZ_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLITE_AESGCM_IVLEN); if (err) { - vvz_log_error("Could not set AES-GCM IV: error %d", err); + sflite_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - vvz_log_debug("Successfully set IV"); + sflite_log_debug("Successfully set IV"); // Decrypt err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); if (err) { - vvz_log_error("Could not decrypt: error %d", err); + sflite_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - vvz_log_debug("Successfully decrypted"); + sflite_log_debug("Successfully decrypted"); // Check MAC - err = gcry_cipher_checktag(hd, tag, VVZ_AESGCM_TAGLEN); + err = gcry_cipher_checktag(hd, tag, SFLITE_AESGCM_TAGLEN); if (gcry_err_code(err) == GPG_ERR_CHECKSUM) { // Undo decryption - memset(pt, VVZ_AESGCM_POISON_PT, ct_len); + memset(pt, SFLITE_AESGCM_POISON_PT, ct_len); // Flag it *match = false; } else if (err) { - vvz_log_error("Could not check MAC: error %d", err); + sflite_log_error("Could not check MAC: error %d", err); goto bad_checktag; } else { // Flag MAC verification success *match = true; } - vvz_log_debug("Successfully checked MAC: match = %d", *match); + sflite_log_debug("Successfully checked MAC: match = %d", *match); // No prob, whether MAC verified or not err = 0; @@ -444,11 +444,11 @@ bad_open: * * @return The error code (0 on success) */ -int vvz_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) +int sflite_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) { gcry_kdf_hd_t hd; const unsigned long argon_params[4] = - {VVZ_STANDARD_KEYLEN, VVZ_ARGON_T, VVZ_ARGON_M, VVZ_ARGON_P}; + {SFLITE_STANDARD_KEYLEN, SFLITE_ARGON_T, SFLITE_ARGON_M, SFLITE_ARGON_P}; gcry_error_t err; // Instantiate Argon2id handle @@ -456,31 +456,31 @@ int vvz_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) &hd, GCRY_KDF_ARGON2, GCRY_KDF_ARGON2ID, argon_params, 4, pwd, pwd_len, - salt, VVZ_ARGON_SALTLEN, + salt, SFLITE_ARGON_SALTLEN, NULL, 0, /* Optional secret value K */ NULL, 0 /* Optional associated data X */ ); if (err) { - vvz_log_error("Could not open Argon2id handle: error %d", err); + sflite_log_error("Could not open Argon2id handle: error %d", err); goto bad_open; } - vvz_log_debug("Successfully opened Argon2id handle"); + sflite_log_debug("Successfully opened Argon2id handle"); // Run the computation err = gcry_kdf_compute(hd, NULL); if (err) { - vvz_log_error("Could not run Argon2id computation: error %d", err); + sflite_log_error("Could not run Argon2id computation: error %d", err); goto bad_compute; } - vvz_log_debug("Successfully run Argon2id computation"); + sflite_log_debug("Successfully run Argon2id computation"); // Finalise hash - err = gcry_kdf_final(hd, VVZ_STANDARD_KEYLEN, hash); + err = gcry_kdf_final(hd, SFLITE_STANDARD_KEYLEN, hash); if (err) { - vvz_log_error("Could not finalise Argon2id hash: error %d", err); + sflite_log_error("Could not finalise Argon2id hash: error %d", err); goto bad_final; } - vvz_log_debug("Successfully finalised Argon2id hash"); + sflite_log_debug("Successfully finalised Argon2id hash"); // All in order err = 0; diff --git a/vuvuzela-userland/src/utils/disk.c b/shufflecake-userland-lite/src/utils/disk.c similarity index 74% rename from vuvuzela-userland/src/utils/disk.c rename to shufflecake-userland-lite/src/utils/disk.c index 8b35caa..79f2be2 100644 --- a/vuvuzela-userland/src/utils/disk.c +++ b/shufflecake-userland-lite/src/utils/disk.c @@ -56,14 +56,14 @@ * * @return A string version of the device ID */ -char *vvz_disk_getDeviceName(char *bdev_path) +char *sflite_disk_getDeviceName(char *bdev_path) { struct stat sb; char *dev_name; if (stat(bdev_path, &sb) != 0) return NULL; - dev_name = malloc(VVZ_BIGBUFSIZE); + dev_name = malloc(SFLITE_BIGBUFSIZE); if (!dev_name) return NULL; @@ -80,7 +80,7 @@ char *vvz_disk_getDeviceName(char *bdev_path) * * @return true iff it's a block device */ -bool vvz_disk_isBlockDevice(char *path) +bool sflite_disk_isBlockDevice(char *path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { @@ -96,7 +96,7 @@ bool vvz_disk_isBlockDevice(char *path) * * @return The size (in 4096-byte sectors) of the disk, or -errno if error */ -int64_t vvz_disk_getSize(char * bdev_path) +int64_t sflite_disk_getSize(char * bdev_path) { int fd; uint64_t size_bytes; @@ -105,24 +105,24 @@ int64_t vvz_disk_getSize(char * bdev_path) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - vvz_log_error("Could not open file %s", bdev_path); + sflite_log_error("Could not open file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_open; } - vvz_log_debug("Opened file %s", bdev_path); + sflite_log_debug("Opened file %s", bdev_path); /* Get size in bytes */ if (ioctl(fd, BLKGETSIZE64, &size_bytes) < 0) { - vvz_log_error("Could not ioctl file %s", bdev_path); + sflite_log_error("Could not ioctl file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_ioctl; } - vvz_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); + sflite_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); - /* Compute size in VVZ sectors */ - ret = (size_bytes / VVZ_BLOCK_SIZE); + /* Compute size in SFLITE sectors */ + ret = (size_bytes / SFLITE_BLOCK_SIZE); bad_ioctl: close(fd); @@ -140,7 +140,7 @@ bad_open: * * @return The error code (0 on success) */ -int vvz_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) +int sflite_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) { int fd; int err; @@ -148,29 +148,29 @@ int vvz_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - vvz_log_error("Could not open file %s", bdev_path); + sflite_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - vvz_log_debug("Opened file %s", bdev_path); + sflite_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * VVZ_BLOCK_SIZE, SEEK_SET) < 0) { - vvz_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * SFLITE_BLOCK_SIZE, SEEK_SET) < 0) { + sflite_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - vvz_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + sflite_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Read in a loop */ - size_t bytes_to_read = VVZ_BLOCK_SIZE; + size_t bytes_to_read = SFLITE_BLOCK_SIZE; while (bytes_to_read > 0) { /* Read syscall */ ssize_t bytes_read = read(fd, buf, bytes_to_read); if (bytes_read < 0) { - vvz_log_error("Could not read file %s at sector %lu", bdev_path, sector); + sflite_log_error("Could not read file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_read; @@ -178,7 +178,7 @@ int vvz_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Partial read? No problem just log */ if (bytes_read < bytes_to_read) { - vvz_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", + sflite_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_read, bytes_to_read); } @@ -209,7 +209,7 @@ bad_open: * * @return The error code (0 on success) */ -int vvz_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) +int sflite_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) { int fd; int err; @@ -217,29 +217,29 @@ int vvz_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size /* Open file */ fd = open(bdev_path, O_WRONLY); if (fd < 0) { - vvz_log_error("Could not open file %s", bdev_path); + sflite_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - vvz_log_debug("Opened file %s", bdev_path); + sflite_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * VVZ_BLOCK_SIZE, SEEK_SET) < 0) { - vvz_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * SFLITE_BLOCK_SIZE, SEEK_SET) < 0) { + sflite_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - vvz_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + sflite_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Write in a loop */ - size_t bytes_to_write = VVZ_BLOCK_SIZE * num_sectors; + size_t bytes_to_write = SFLITE_BLOCK_SIZE * num_sectors; while (bytes_to_write > 0) { /* Write syscall */ ssize_t bytes_written = write(fd, buf, bytes_to_write); if (bytes_written < 0) { - vvz_log_red("Could not write file %s at sector %lu", bdev_path, sector); + sflite_log_red("Could not write file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_write; @@ -247,7 +247,7 @@ int vvz_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size /* Partial write? No problem just log */ if (bytes_written < bytes_to_write) { - vvz_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", + sflite_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_written, bytes_to_write); } diff --git a/vuvuzela-userland/src/utils/dm.c b/shufflecake-userland-lite/src/utils/dm.c similarity index 75% rename from vuvuzela-userland/src/utils/dm.c rename to shufflecake-userland-lite/src/utils/dm.c index 31e3f14..da6f259 100644 --- a/vuvuzela-userland/src/utils/dm.c +++ b/shufflecake-userland-lite/src/utils/dm.c @@ -35,6 +35,7 @@ #include #include "utils/dm.h" +#include "utils/sflite.h" #include "utils/log.h" @@ -53,7 +54,7 @@ * * @return The error code (0 on success) */ -int vvz_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) +int sflite_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) { struct dm_task *dmt; uint32_t cookie = 0; @@ -64,60 +65,60 @@ int vvz_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) char * dup_virt_dev_name = strdup(virt_dev_name); char * dup_params = strdup(params); - vvz_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); + sflite_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the CREATE ioctl command) */ if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { - vvz_log_error("Cannot create dm_task"); + sflite_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - vvz_log_debug("Successfully created dm_task"); + sflite_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be created) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - vvz_log_error("Cannot set device name"); + sflite_log_error("Cannot set device name"); err = 2; goto out; } - vvz_log_debug("Successfully set device name"); + sflite_log_debug("Successfully set device name"); /* State that it is a Shufflecake device, pass the start and size, and the * constructor parameters */ - if (!dm_task_add_target(dmt, 0, num_sectors, VVZ_DM_TARGET_NAME, dup_params)) { - vvz_log_error("Cannot add DM target and parameters"); + if (!dm_task_add_target(dmt, 0, num_sectors, SFLC_DM_TARGET_NAME, dup_params)) { + sflite_log_error("Cannot add DM target and parameters"); err = 3; goto out; } - vvz_log_debug("Successfully added DM target and parameters"); + sflite_log_debug("Successfully added DM target and parameters"); /* Say that we want a new node under /dev/mapper */ if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { - vvz_log_error("Cannot add /dev/mapper node"); + sflite_log_error("Cannot add /dev/mapper node"); err = 4; goto out; } - vvz_log_debug("Successfully set the ADD_NODE flag"); + sflite_log_debug("Successfully set the ADD_NODE flag"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - vvz_log_error("Cannot get cookie"); + sflite_log_error("Cannot get cookie"); err = 5; goto out; } - vvz_log_debug("Successfully got a cookie"); + sflite_log_debug("Successfully got a cookie"); /* Run the task */ if (!dm_task_run(dmt)) { - vvz_log_error("Cannot issue ioctl"); + sflite_log_error("Cannot issue ioctl"); err = 6; goto out; } - vvz_log_debug("Successfully run DM task"); + sflite_log_debug("Successfully run DM task"); /* Wait for completion */ dm_udev_wait(cookie); - vvz_log_debug("Task completed"); + sflite_log_debug("Task completed"); // No prob err = 0; @@ -139,7 +140,7 @@ dup_free: * * @return error code (0 on success) */ -int vvz_dm_destroy(char * virt_dev_name) +int sflite_dm_destroy(char * virt_dev_name) { struct dm_task *dmt; uint32_t cookie = 0; @@ -149,47 +150,47 @@ int vvz_dm_destroy(char * virt_dev_name) /* Just to be sure, let's get it on the heap */ char * dup_virt_dev_name = strdup(virt_dev_name); - vvz_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); + sflite_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the REMOVE ioctl command) */ if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) { - vvz_log_error("Cannot create dm_task"); + sflite_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - vvz_log_debug("Successfully created dm_task"); + sflite_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be closed) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - vvz_log_error("Cannot set device name"); + sflite_log_error("Cannot set device name"); err = 2; goto out; } - vvz_log_debug("Successfully set device name"); + sflite_log_debug("Successfully set device name"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - vvz_log_error("Cannot set cookie"); + sflite_log_error("Cannot set cookie"); err = 3; goto out; } - vvz_log_debug("Successfully got a cookie"); + sflite_log_debug("Successfully got a cookie"); /* Needed for some reason */ dm_task_retry_remove(dmt); - vvz_log_debug("Successful retry_remove"); + sflite_log_debug("Successful retry_remove"); /* Run the task */ if (!dm_task_run(dmt)) { - vvz_log_error("Cannot issue ioctl"); + sflite_log_error("Cannot issue ioctl"); err = 4; goto out; } - vvz_log_debug("Successfully run task"); + sflite_log_debug("Successfully run task"); /* Wait for completion */ dm_udev_wait(cookie); - vvz_log_debug("Task completed"); + sflite_log_debug("Task completed"); // No prob err = 0; diff --git a/vuvuzela-userland/src/utils/file.c b/shufflecake-userland-lite/src/utils/file.c similarity index 92% rename from vuvuzela-userland/src/utils/file.c rename to shufflecake-userland-lite/src/utils/file.c index 84b13ff..82c7472 100644 --- a/vuvuzela-userland/src/utils/file.c +++ b/shufflecake-userland-lite/src/utils/file.c @@ -38,7 +38,7 @@ *****************************************************/ /* Reads the entire content of a file in a malloc-ed string */ -char *vvz_readFile(char *path) +char *sflite_readFile(char *path) { int filesize; FILE *fp; @@ -47,7 +47,7 @@ char *vvz_readFile(char *path) /* Open file */ fp = fopen(path, "r"); if (fp == NULL) { - vvz_log_error("Could not open file %s", path); + sflite_log_error("Could not open file %s", path); perror("Reason: "); goto bad_fopen; } @@ -60,7 +60,7 @@ char *vvz_readFile(char *path) /* Allocate */ content = malloc(filesize + 1); if (content == NULL) { - vvz_log_error("Could not malloc %d bytes for file content", filesize); + sflite_log_error("Could not malloc %d bytes for file content", filesize); goto bad_malloc; } diff --git a/vuvuzela-userland/src/utils/input.c b/shufflecake-userland-lite/src/utils/input.c similarity index 93% rename from vuvuzela-userland/src/utils/input.c rename to shufflecake-userland-lite/src/utils/input.c index 8a7b776..5a0e9ac 100644 --- a/vuvuzela-userland/src/utils/input.c +++ b/shufflecake-userland-lite/src/utils/input.c @@ -41,13 +41,13 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int vvz_safeReadLine(char *buf, size_t bufsize) +int sflite_safeReadLine(char *buf, size_t bufsize) { size_t len; /* Read from stdin */ if (fgets(buf, bufsize, stdin) == NULL) { - vvz_log_error("Could not read from stdin"); + sflite_log_error("Could not read from stdin"); return EBADFD; } @@ -62,7 +62,7 @@ int vvz_safeReadLine(char *buf, size_t bufsize) /* Reads a password/passphrase in a secure way (no echo) */ -int vvz_safeReadPassphrase(char *buf, size_t bufsize) +int sflite_safeReadPassphrase(char *buf, size_t bufsize) { size_t len; struct termios old, new; @@ -79,7 +79,7 @@ int vvz_safeReadPassphrase(char *buf, size_t bufsize) if (fgets(buf, bufsize, stdin) == NULL) { // If reading the password failed, ensure echoing is turned back on tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - vvz_log_error("Could not read from stdin"); + sflite_log_error("Could not read from stdin"); return EBADFD; } diff --git a/vuvuzela-userland/src/utils/string.c b/shufflecake-userland-lite/src/utils/string.c similarity index 92% rename from vuvuzela-userland/src/utils/string.c rename to shufflecake-userland-lite/src/utils/string.c index 555a127..86d735f 100644 --- a/vuvuzela-userland/src/utils/string.c +++ b/shufflecake-userland-lite/src/utils/string.c @@ -38,7 +38,7 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *vvz_toHex(char *buf, size_t len) +char *sflite_toHex(char *buf, size_t len) { unsigned char *u = (unsigned char *) buf; char *hex; @@ -46,7 +46,7 @@ char *vvz_toHex(char *buf, size_t len) /* Allocate buffer */ hex = malloc((len * 2) + 1); if (!hex) { - vvz_log_error("Could not allocate buffer for hex string"); + sflite_log_error("Could not allocate buffer for hex string"); return NULL; } @@ -61,7 +61,7 @@ char *vvz_toHex(char *buf, size_t len) } -void vvz_str_replaceAll(char * str, char old, char new) +void sflite_str_replaceAll(char * str, char old, char new) { int i; for (i = 0; str[i] != '\0'; i++) { diff --git a/vuvuzela-userland/test/crypto/test_aes256ctr.c b/shufflecake-userland-lite/test/crypto/test_aes256ctr.c similarity index 88% rename from vuvuzela-userland/test/crypto/test_aes256ctr.c rename to shufflecake-userland-lite/test/crypto/test_aes256ctr.c index 356cd15..ce2676b 100644 --- a/vuvuzela-userland/test/crypto/test_aes256ctr.c +++ b/shufflecake-userland-lite/test/crypto/test_aes256ctr.c @@ -56,10 +56,10 @@ char *test_aes256ctr_encrypt_inplace() char iv[] = AES256CTR_TEST_IV; int err; - vvz_log_blue("Testing AES256-CTR encryption in-place"); + sflite_log_blue("Testing AES256-CTR encryption in-place"); // Encrypt in-place - err = vvz_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); + err = sflite_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); // Check error mu_assert("Error while encrypting", !err); @@ -73,7 +73,7 @@ char *test_aes256ctr_encrypt_inplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } @@ -87,10 +87,10 @@ char *test_aes256ctr_encrypt_outofplace() char iv[] = AES256CTR_TEST_IV; int err; - vvz_log_blue("Testing AES256-CTR encryption out-of-place"); + sflite_log_blue("Testing AES256-CTR encryption out-of-place"); // Encrypt out-of-place - err = vvz_aes256ctr_encrypt(key, msg, msg_len, iv, ct); + err = sflite_aes256ctr_encrypt(key, msg, msg_len, iv, ct); // Check error mu_assert("Error while encrypting", !err); @@ -107,7 +107,7 @@ char *test_aes256ctr_encrypt_outofplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } @@ -120,10 +120,10 @@ char *test_aes256ctr_decrypt_inplace() char iv[] = AES256CTR_TEST_IV; int err; - vvz_log_blue("Testing AES256-CTR decryption in-place"); + sflite_log_blue("Testing AES256-CTR decryption in-place"); // Decrypt in-place - err = vvz_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); + err = sflite_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); // Check error mu_assert("Error while decrypting", !err); @@ -137,7 +137,7 @@ char *test_aes256ctr_decrypt_inplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } @@ -151,10 +151,10 @@ char *test_aes256ctr_decrypt_outofplace() char iv[] = AES256CTR_TEST_IV; int err; - vvz_log_blue("Testing AES256-CTR decryption out-of-place"); + sflite_log_blue("Testing AES256-CTR decryption out-of-place"); // Decrypt out-of-place - err = vvz_aes256ctr_decrypt(key, msg, msg_len, iv, pt); + err = sflite_aes256ctr_decrypt(key, msg, msg_len, iv, pt); // Check error mu_assert("Error while decrypting", !err); @@ -171,7 +171,7 @@ char *test_aes256ctr_decrypt_outofplace() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } diff --git a/vuvuzela-userland/test/crypto/test_aes256ctr.h b/shufflecake-userland-lite/test/crypto/test_aes256ctr.h similarity index 100% rename from vuvuzela-userland/test/crypto/test_aes256ctr.h rename to shufflecake-userland-lite/test/crypto/test_aes256ctr.h diff --git a/vuvuzela-userland/test/crypto/test_aes256gcm.c b/shufflecake-userland-lite/test/crypto/test_aes256gcm.c similarity index 87% rename from vuvuzela-userland/test/crypto/test_aes256gcm.c rename to shufflecake-userland-lite/test/crypto/test_aes256gcm.c index dfc8dfd..da5458a 100644 --- a/vuvuzela-userland/test/crypto/test_aes256gcm.c +++ b/shufflecake-userland-lite/test/crypto/test_aes256gcm.c @@ -56,20 +56,20 @@ char *test_aes256gcm_encrypt() char key[] = AES256GCM_TEST_KEY; char iv[] = AES256GCM_TEST_IV; char ct[sizeof(pt)]; - char tag[VVZ_AESGCM_TAGLEN]; + char tag[SFLITE_AESGCM_TAGLEN]; int err; - vvz_log_blue("Testing AES256-GCM encryption"); + sflite_log_blue("Testing AES256-GCM encryption"); // Encrypt - err = vvz_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); + err = sflite_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); // Check error mu_assert("Error while encrypting", !err); // Check outcome mu_assert("Ciphertext mismatch", memcmp(ct, CT, pt_len) == 0); - mu_assert("MAC mismatch", memcmp(tag, TAG, VVZ_AESGCM_TAGLEN) == 0); + mu_assert("MAC mismatch", memcmp(tag, TAG, SFLITE_AESGCM_TAGLEN) == 0); // Check key untouched mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); @@ -77,7 +77,7 @@ char *test_aes256gcm_encrypt() // Check IV untouched mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } @@ -93,10 +93,10 @@ char *test_aes256gcm_decrypt_good() char pt[sizeof(ct)]; int err; - vvz_log_blue("Testing AES256-GCM decryption with the proper MAC"); + sflite_log_blue("Testing AES256-GCM decryption with the proper MAC"); // Decrypt - err = vvz_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + err = sflite_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); // Check error mu_assert("Error while decrypting", !err); @@ -114,7 +114,7 @@ char *test_aes256gcm_decrypt_good() // Check MAC untouched mu_assert("MAC changed", memcmp(tag, TAG, sizeof(tag)) == 0); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } @@ -130,13 +130,13 @@ char *test_aes256gcm_decrypt_fail() char pt[sizeof(ct)]; int err; - vvz_log_blue("Testing AES256-GCM decryption without the proper MAC"); + sflite_log_blue("Testing AES256-GCM decryption without the proper MAC"); // Corrupt the MAC tag[0] += 1; // Decrypt - err = vvz_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); + err = sflite_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); // Check error mu_assert("Error while decrypting", !err); @@ -154,7 +154,7 @@ char *test_aes256gcm_decrypt_fail() mu_assert("Tail of MAC changed", memcmp(tag+1, TAG+1, sizeof(tag)-1) == 0); mu_assert("Head of MAC changed", tag[0] == TAG[0]+1); - vvz_log_green("OK"); + sflite_log_green("OK"); return NULL; } diff --git a/vuvuzela-userland/test/crypto/test_aes256gcm.h b/shufflecake-userland-lite/test/crypto/test_aes256gcm.h similarity index 100% rename from vuvuzela-userland/test/crypto/test_aes256gcm.h rename to shufflecake-userland-lite/test/crypto/test_aes256gcm.h diff --git a/vuvuzela-userland/test/crypto/test_argon2id.c b/shufflecake-userland-lite/test/crypto/test_argon2id.c similarity index 84% rename from vuvuzela-userland/test/crypto/test_argon2id.c rename to shufflecake-userland-lite/test/crypto/test_argon2id.c index 7a26ab9..b50bf3e 100644 --- a/vuvuzela-userland/test/crypto/test_argon2id.c +++ b/shufflecake-userland-lite/test/crypto/test_argon2id.c @@ -39,7 +39,7 @@ * CONSTANT VARIABLES * *****************************************************/ -char SALT[VVZ_ARGON_SALTLEN+1] = "Poor Petrol Pump"; +char SALT[SFLITE_ARGON_SALTLEN+1] = "Poor Petrol Pump"; /***************************************************** @@ -48,17 +48,17 @@ char SALT[VVZ_ARGON_SALTLEN+1] = "Poor Petrol Pump"; char *test_argon2id() { - char pwd[VVZ_BIGBUFSIZE]; - char key[VVZ_STANDARD_KEYLEN]; + char pwd[SFLITE_BIGBUFSIZE]; + char key[SFLITE_STANDARD_KEYLEN]; int err; - vvz_log_blue("Testing Argon2id interactively with a fixed salt"); + sflite_log_blue("Testing Argon2id interactively with a fixed salt"); // Loop until user breaks out while (true) { /* Collect password */ printf("Choose password to hash (empty to skip): "); - err = vvz_safeReadLine(pwd, VVZ_BIGBUFSIZE); + err = sflite_safeReadLine(pwd, SFLITE_BIGBUFSIZE); mu_assert("Could not read password", !err); /* Check if empty */ @@ -67,15 +67,15 @@ char *test_argon2id() } /* Hash it */ - err = vvz_argon2id_derive(pwd, strlen(pwd), SALT, key); + err = sflite_argon2id_derive(pwd, strlen(pwd), SALT, key); mu_assert("Could not hash password", !err); /* Print it */ printf("Salt used ASCII: \"%s\". Hex:\n", SALT); - vvz_log_hex(SALT, VVZ_ARGON_SALTLEN); + sflite_log_hex(SALT, SFLITE_ARGON_SALTLEN); printf("Argon2id hash (m = %d, t = %d, p = %d):\n", - VVZ_ARGON_M, VVZ_ARGON_T, VVZ_ARGON_P); - vvz_log_hex(key, VVZ_STANDARD_KEYLEN); + SFLITE_ARGON_M, SFLITE_ARGON_T, SFLITE_ARGON_P); + sflite_log_hex(key, SFLITE_STANDARD_KEYLEN); printf("Go check the result online\n\n"); } diff --git a/vuvuzela-userland/test/crypto/test_argon2id.h b/shufflecake-userland-lite/test/crypto/test_argon2id.h similarity index 100% rename from vuvuzela-userland/test/crypto/test_argon2id.h rename to shufflecake-userland-lite/test/crypto/test_argon2id.h diff --git a/vuvuzela-userland/test/main.c b/shufflecake-userland-lite/test/main.c similarity index 94% rename from vuvuzela-userland/test/main.c rename to shufflecake-userland-lite/test/main.c index ee4e805..1f3a506 100644 --- a/vuvuzela-userland/test/main.c +++ b/shufflecake-userland-lite/test/main.c @@ -49,10 +49,10 @@ int main() char *result = all_tests(); if (result != NULL) { - vvz_log_red("\nTEST FAILED: %s", result); + sflite_log_red("\nTEST FAILED: %s", result); } else { - vvz_log_green("\nALL TESTS PASSED"); + sflite_log_green("\nALL TESTS PASSED"); } return result != NULL; @@ -65,7 +65,7 @@ int main() static char *all_tests() { - vvz_log_yellow("Running crypto tests"); + sflite_log_yellow("Running crypto tests"); mu_run_test(test_aes256ctr_encrypt_inplace); mu_run_test(test_aes256ctr_encrypt_outofplace); mu_run_test(test_aes256ctr_decrypt_inplace); diff --git a/vuvuzela-userland/test/minunit.h b/shufflecake-userland-lite/test/minunit.h similarity index 100% rename from vuvuzela-userland/test/minunit.h rename to shufflecake-userland-lite/test/minunit.h diff --git a/vuvuzela-userland/include/vvz_constants.h b/vuvuzela-userland/include/vvz_constants.h deleted file mode 120000 index 5c10997..0000000 --- a/vuvuzela-userland/include/vvz_constants.h +++ /dev/null @@ -1 +0,0 @@ -../../dm-vvz/vvz_constants.h \ No newline at end of file From bec7c7765de1ad5bc4e5e7aec3d1ce419e8b327c Mon Sep 17 00:00:00 2001 From: = Date: Sat, 3 Aug 2024 22:42:38 +0200 Subject: [PATCH 61/98] Update legacy to current version --- shufflecake-userland-legacy/Makefile.sources | 6 +- .../include/{cli => }/cli.h | 26 +- .../include/{commands => }/commands.h | 28 +- .../device_master_block.h => header.h} | 96 +++++-- .../include/header/position_map.h | 76 ------ .../include/header/volume_master_block.h | 87 ------ .../{actions/volume.h => operations.h} | 70 ++--- .../include/utils/sflc.h | 4 +- .../src/actions/create_vol.c | 136 ---------- .../src/actions/open.c | 252 ------------------ .../src/actions/open_vol.c | 155 ----------- .../src/cli/changepwd.c | 112 ++++++++ shufflecake-userland-legacy/src/cli/close.c | 4 +- .../src/cli/dispatch.c | 77 ++++-- shufflecake-userland-legacy/src/cli/init.c | 13 +- shufflecake-userland-legacy/src/cli/open.c | 25 +- .../actions/device.h => src/cli/testpwd.c} | 72 +++-- .../close.c => commands/change_pwd.c} | 23 +- .../src/commands/close.c | 16 +- .../src/commands/init.c | 56 ++-- .../src/commands/open.c | 187 ++++++------- .../close_vol.c => commands/test_pwd.c} | 24 +- .../src/header/device_master_block.c | 58 +++- .../src/header/position_map.c | 2 +- .../src/header/volume_master_block.c | 14 +- shufflecake-userland-legacy/src/main.c | 2 +- .../src/operations/devmapper.c | 111 ++++++++ .../src/{actions => operations}/dmb.c | 76 ++++-- .../create.c => operations/volume_header.c} | 96 ++++--- shufflecake-userland-legacy/src/utils/input.c | 3 + .../test/actions/test_actions.h | 39 --- .../test/actions/test_all_actions.c | 128 --------- .../test/actions/test_create.c | 100 ------- .../test/commands/test_commands.h | 36 --- .../test/commands/test_init.c | 87 ------ 35 files changed, 814 insertions(+), 1483 deletions(-) rename shufflecake-userland-legacy/include/{cli => }/cli.h (78%) rename shufflecake-userland-legacy/include/{commands => }/commands.h (83%) rename shufflecake-userland-legacy/include/{header/device_master_block.h => header.h} (50%) delete mode 100644 shufflecake-userland-legacy/include/header/position_map.h delete mode 100644 shufflecake-userland-legacy/include/header/volume_master_block.h rename shufflecake-userland-legacy/include/{actions/volume.h => operations.h} (60%) delete mode 100644 shufflecake-userland-legacy/src/actions/create_vol.c delete mode 100644 shufflecake-userland-legacy/src/actions/open.c delete mode 100644 shufflecake-userland-legacy/src/actions/open_vol.c create mode 100644 shufflecake-userland-legacy/src/cli/changepwd.c rename shufflecake-userland-legacy/{include/actions/device.h => src/cli/testpwd.c} (52%) rename shufflecake-userland-legacy/src/{actions/close.c => commands/change_pwd.c} (78%) rename shufflecake-userland-legacy/src/{actions/close_vol.c => commands/test_pwd.c} (78%) create mode 100644 shufflecake-userland-legacy/src/operations/devmapper.c rename shufflecake-userland-legacy/src/{actions => operations}/dmb.c (64%) rename shufflecake-userland-legacy/src/{actions/create.c => operations/volume_header.c} (61%) delete mode 100644 shufflecake-userland-legacy/test/actions/test_actions.h delete mode 100644 shufflecake-userland-legacy/test/actions/test_all_actions.c delete mode 100644 shufflecake-userland-legacy/test/actions/test_create.c delete mode 100644 shufflecake-userland-legacy/test/commands/test_commands.h delete mode 100644 shufflecake-userland-legacy/test/commands/test_init.c diff --git a/shufflecake-userland-legacy/Makefile.sources b/shufflecake-userland-legacy/Makefile.sources index ce6de04..0e2a80b 100644 --- a/shufflecake-userland-legacy/Makefile.sources +++ b/shufflecake-userland-legacy/Makefile.sources @@ -29,9 +29,9 @@ PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c) PROJ_SRCS += $(addprefix header/,position_map.c volume_master_block.c device_master_block.c) -PROJ_SRCS += $(addprefix actions/,create_vol.c open_vol.c close_vol.c dmb.c) -PROJ_SRCS += $(addprefix commands/,init.c open.c close.c) -PROJ_SRCS += $(addprefix cli/,dispatch.c init.c open.c close.c) +PROJ_SRCS += $(addprefix operations/,volume_header.c devmapper.c dmb.c) +PROJ_SRCS += $(addprefix commands/,init.c open.c close.c test_pwd.c change_pwd.c) +PROJ_SRCS += $(addprefix cli/,dispatch.c init.c open.c close.c testpwd.c changepwd.c) PROJ_SRCS += main.c PROJ_ROOT := src diff --git a/shufflecake-userland-legacy/include/cli/cli.h b/shufflecake-userland-legacy/include/cli.h similarity index 78% rename from shufflecake-userland-legacy/include/cli/cli.h rename to shufflecake-userland-legacy/include/cli.h index fcca9c2..838f618 100644 --- a/shufflecake-userland-legacy/include/cli/cli.h +++ b/shufflecake-userland-legacy/include/cli.h @@ -21,20 +21,24 @@ * If not, see . */ -#ifndef _CLI_CLI_H_ -#define _CLI_CLI_H_ +#ifndef _CLI_H_ +#define _CLI_H_ /***************************************************** * CONSTANTS * *****************************************************/ -/* Subcommand to create volumes */ -#define SFLC_CLI_INITCMD "init" -/* Subcommand to open volumes */ -#define SFLC_CLI_OPENCMD "open" -/* Subcommand to close volumes */ -#define SFLC_CLI_CLOSECMD "close" +/* Action to create volumes */ +#define SFLC_CLI_INITACT "init" +/* Action to open volumes */ +#define SFLC_CLI_OPENACT "open" +/* Action to close volumes */ +#define SFLC_CLI_CLOSEACT "close" +/* Action to test password */ +#define SFLC_CLI_TESTPWDACT "testpwd" +/* Action to change password */ +#define SFLC_CLI_CHANGEPWDACT "changepwd" /***************************************************** @@ -50,8 +54,12 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill); int sflc_cli_open(char *block_device); /* Close volumes */ int sflc_cli_close(char *block_device); +/* Test password */ +int sflc_cli_testPwd(char *block_device); +/* Change password */ +int sflc_cli_changePwd(char *block_device); -#endif /* _CLI_CLI_H_ */ +#endif /* _CLI_H_ */ diff --git a/shufflecake-userland-legacy/include/commands/commands.h b/shufflecake-userland-legacy/include/commands.h similarity index 83% rename from shufflecake-userland-legacy/include/commands/commands.h rename to shufflecake-userland-legacy/include/commands.h index 959c1a4..68a14ac 100644 --- a/shufflecake-userland-legacy/include/commands/commands.h +++ b/shufflecake-userland-legacy/include/commands.h @@ -21,8 +21,8 @@ * If not, see . */ -#ifndef _COMMANDS_COMMANDS_H_ -#define _COMMANDS_COMMANDS_H_ +#ifndef _COMMANDS_H_ +#define _COMMANDS_H_ /***************************************************** @@ -33,6 +33,8 @@ #include #include +#include "header.h" + /***************************************************** * CONSTANTS * @@ -73,6 +75,20 @@ typedef struct } sflc_cmd_OpenArgs; +typedef struct +{ + /* Underlying block device */ + char *bdev_path; + + /* Content of the DMB cell */ + sflc_DmbCell *dmb_cell; + + /* The new password */ + char *new_pwd; + size_t new_pwd_len; + +} sflc_cmd_ChangePwdArgs; + /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * @@ -87,5 +103,11 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args); /* Close all volumes on the device (reads the list from sysfs) */ int sflc_cmd_closeVolumes(char *bdev_path); +/* Tests which volume is unlocked by the given password */ +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell); -#endif /* _COMMANDS_COMMANDS_H_ */ +/* Changes the specified volume's password */ +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args); + + +#endif /* _COMMANDS_H_ */ diff --git a/shufflecake-userland-legacy/include/header/device_master_block.h b/shufflecake-userland-legacy/include/header.h similarity index 50% rename from shufflecake-userland-legacy/include/header/device_master_block.h rename to shufflecake-userland-legacy/include/header.h index 235323f..869b868 100644 --- a/shufflecake-userland-legacy/include/header/device_master_block.h +++ b/shufflecake-userland-legacy/include/header.h @@ -21,12 +21,8 @@ * If not, see . */ -/* - * Defines the Device Master Block (DMB) and related functionalities - */ - -#ifndef _HEADER_DEVICE_MASTER_BLOCK_H_ -#define _HEADER_DEVICE_MASTER_BLOCK_H_ +#ifndef _HEADER_H_ +#define _HEADER_H_ /***************************************************** @@ -36,7 +32,6 @@ #include #include -#include "utils/disk.h" #include "utils/crypto.h" @@ -45,22 +40,29 @@ *****************************************************/ /* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define SFLC_DMB_CELL (SFLC_AESGCM_PADDED_IVLEN + SFLC_CRYPTO_KEYLEN + SFLC_AESGCM_TAGLEN) +#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_CRYPTO_KEYLEN + SFLC_AESGCM_TAGLEN) /* Let us enforce that the one DMB can fit cells for all volumes */ -#if SFLC_SCRYPT_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL) > SFLC_SECTOR_SIZE +#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL) > SFLC_SECTOR_SIZE #error "Invalid combination of parameters: probably SFLC_DEV_MAX_VOLUMES is too big" #endif +// The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) +#define SFLC_CLEAR_VMB_LEN (SFLC_SECTOR_SIZE - \ + SFLC_AESGCM_PADDED_IVLEN - \ + SFLC_AESGCM_TAGLEN) + + + /***************************************************** * STRUCTS * *****************************************************/ /** - * The on-disk master block of a volume contains lots of crypto stuff + * The on-disk master block of a device contains lots of crypto stuff * (a KDF salt, IVs, MACs...) used to properly hide the VMB keys. - * This struct only contains such useful info. + * This struct only contains such useful info, in the clear. */ typedef struct { // Each volume's VMB key @@ -69,7 +71,8 @@ typedef struct { // How many of these need actually be encrypted size_t nr_vols; -} sflc_CompleteDeviceMasterBlock; +} sflc_Dmb; + /** * When unsealing a DMB, only one VMB key can be unlocked with a password. @@ -82,7 +85,54 @@ typedef struct { // The index of the volume opened by this VMB key size_t vol_idx; -} sflc_PartialDeviceMasterBlock; +} sflc_DmbCell; + + +/** + * The on-disk master block of a volume contains crypto stuff + * (an IV) used to properly hide the useful info. This struct + * only contains the useful info, in the clear. + */ +typedef struct { + // The key that encrypts the volume's data section + char volume_key[SFLC_CRYPTO_KEYLEN]; + + // The key that encrypts the previous volume's master block + char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; + + // The total number of logical slices virtually available to this volume + size_t nr_slices; + +} sflc_Vmb; + + +/** + * This struct represents an encrypted empty position map. + * On-disk, the layout interleaves one IV block with 256 PosMap blocks (each + * encrypted by an IV in the IV block). Many such "runs" can be concatenated, + * until the position map is big enough to index the desired number of slices. + * The last "run" might be incomplete, in that it could have less than 256 + * PosMap blocks, if not all of them are needed. + * In the struct, there are as many IV blocks as there are PosMapBlock arrays + * (equal to the number of "runs"). The m-th IV of the n-th IV block encrypts + * the m-th block of the n-th array. The PosMapBlocks in an array are stored + * contiguously in RAM, so a PosMapBlock array is just a char array of length + * multiple of 4096. All the arrays are full (256 PosMapBlocks, 1 MiB) except + * for the last one, which may hold fewer blocks. + */ +typedef struct { + // The number of PosMapBlock arrays (and of IV blocks) + size_t nr_arrays; + + // The sequence of IV blocks + char **iv_blocks; + // The sequence of (encrypted) PosMapBlock arrays + char **pmb_arrays; + + // The number of PosMapBlocks in the last array + size_t nr_last_pmbs; + +} sflc_EncPosMap; /***************************************************** @@ -90,10 +140,22 @@ typedef struct { *****************************************************/ /* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ -int sflc_dmb_seal(sflc_CompleteDeviceMasterBlock *dmb, char **pwds, size_t *pwd_lens, char *disk_block); - +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); /* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_PartialDeviceMasterBlock *dmb); +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +/* Re-encrypt the content of a single DMB cell */ +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len); -#endif /* _HEADER_DEVICE_MASTER_BLOCK_H_ */ +/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ +int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +/* "Decrypt" a VMB coming from the disk, directly using its key */ +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); + + +/* Create an encrypted empty position map for the given number of slices (allocates memory) */ +int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm); + + + +#endif /* _HEADER_H_ */ diff --git a/shufflecake-userland-legacy/include/header/position_map.h b/shufflecake-userland-legacy/include/header/position_map.h deleted file mode 100644 index c927944..0000000 --- a/shufflecake-userland-legacy/include/header/position_map.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Helpers to encrypt an empty position map - */ - -#ifndef _HEADER_POSITION_MAP_H_ -#define _HEADER_POSITION_MAP_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - - -/***************************************************** - * STRUCTS * - *****************************************************/ - -/** - * This struct represents an encrypted empty position map. - * There are as many IV blocks as there are PosMapBlock arrays. - * The m-th IV of the n-th IV block encrypts the m-th block of the n-th array. - * The PosMapBlocks in an array are contiguous, so a PosMapBlock array is just - * a char array of length multiple of 4096. - * All the arrays are full (256 PosMapBlocks, 1 MiB) except for the last one, - * which may hold fewer blocks. - */ -typedef struct { - // The number of PosMapBlock arrays (and of IV blocks) - size_t nr_arrays; - - // The sequence of IV blocks - char **iv_blocks; - // The sequence of (encrypted) PosMapBlock arrays - char **pmb_arrays; - - // The number of PosMapBlocks in the last array - size_t nr_last_pmbs; - -} sflc_EncPosMap; - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Create an encrypted empty position map for the given number of slices (allocates memory) */ -int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm); - - -#endif /* _HEADER_POSITION_MAP_H_ */ diff --git a/shufflecake-userland-legacy/include/header/volume_master_block.h b/shufflecake-userland-legacy/include/header/volume_master_block.h deleted file mode 100644 index 12cbc84..0000000 --- a/shufflecake-userland-legacy/include/header/volume_master_block.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Defines the Volume Master Block (VMB) and related functionalities - */ - -#ifndef _HEADER_VOLUME_MASTER_BLOCK_H_ -#define _HEADER_VOLUME_MASTER_BLOCK_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/disk.h" -#include "utils/crypto.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -// The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define SFLC_CLEAR_VMB_LEN (SFLC_SECTOR_SIZE - \ - SFLC_AESGCM_PADDED_IVLEN - \ - SFLC_AESGCM_TAGLEN) - - - -/***************************************************** - * STRUCTS * - *****************************************************/ - -/** - * The on-disk master block of a volume contains crypto stuff - * (IVs, MACs...) used to properly hide the useful - * info. This struct only contains the useful info. - */ -typedef struct { - // The key that encrypts the volume's data section - char volume_key[SFLC_CRYPTO_KEYLEN]; - - // The key that encrypts the previous volume's master block - char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; - - // The total number of logical slices virtually available to this volume - uint32_t nr_slices; - -} sflc_VolumeMasterBlock; - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ -int sflc_vmb_seal(sflc_VolumeMasterBlock *vmb, char *vmb_key, char *disk_block); - -/* "Decrypt" a VMB coming from the disk, directly using its key */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_VolumeMasterBlock *vmb); - - -#endif /* _HEADER_VOLUME_MASTER_BLOCK_H_ */ diff --git a/shufflecake-userland-legacy/include/actions/volume.h b/shufflecake-userland-legacy/include/operations.h similarity index 60% rename from shufflecake-userland-legacy/include/actions/volume.h rename to shufflecake-userland-legacy/include/operations.h index 30aed5b..8de9ab6 100644 --- a/shufflecake-userland-legacy/include/actions/volume.h +++ b/shufflecake-userland-legacy/include/operations.h @@ -21,12 +21,8 @@ * If not, see . */ -/* - * Defines the Volume - */ - -#ifndef _ACTION_VOLUME_H_ -#define _ACTION_VOLUME_H_ +#ifndef _OPERATIONS_H_ +#define _OPERATIONS_H_ /***************************************************** @@ -36,47 +32,11 @@ #include #include -#include "utils/disk.h" +#include "header.h" #include "utils/crypto.h" #include "utils/math.h" -/***************************************************** - * CONSTANTS * - *****************************************************/ - - -/***************************************************** - * STRUCTS * - *****************************************************/ - -/** - * Struct representing a volume. - * The functions in this module take a partially-filled struct in input - * and fill some more fields in it as part of their output. - */ -typedef struct { - // Keys - char vmb_key[SFLC_CRYPTO_KEYLEN]; - char volume_key[SFLC_CRYPTO_KEYLEN]; - char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; - - // Number of slices - size_t nr_slices; - - // ID of the underlying block device - size_t dev_id; - // Index of this volume in the underlying block device - size_t vol_idx; - - // Underlying block device - char *bdev_path; - // Volume name under /dev/mapper/ - char label[SFLC_MAX_VOL_NAME_LEN + 1]; - -} sflc_Volume; - - /***************************************************** * INLINE FUNCTIONS * *****************************************************/ @@ -94,9 +54,9 @@ static inline size_t sflc_volHeaderSize(size_t nr_slices) } // Position of the VMB for the given volume -static inline uint64_t sflc_vmbPosition(sflc_Volume *vol) +static inline uint64_t sflc_vmbPosition(size_t vol_idx, size_t nr_slices) { - return 1 + ((uint64_t) vol->vol_idx) * ((uint64_t) sflc_volHeaderSize(vol->nr_slices)); + return 1 + ((uint64_t) vol_idx) * ((uint64_t) sflc_volHeaderSize(nr_slices)); } @@ -104,13 +64,21 @@ static inline uint64_t sflc_vmbPosition(sflc_Volume *vol) * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -/* Writes a volume header (VMB+PM) on-disk */ -int sflc_act_createVolume(sflc_Volume *vol); +/* Encrypts and writes the DMB to disk */ +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb); +/* Reads the DMB from disk and outputs the unlocked VMB key */ +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); +/* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); -/* Read a VMB from disk and issue a DM ioctl to create the appropriate virtual device */ -int sflc_act_openVolume(sflc_Volume *vol); +/* Encrypts and writes a volume header (VMB+PM) on-disk */ +int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +/* Reads a VMB from disk and unlocks it */ +int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); +/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device */ +int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); /* Close the volume via the appropriate ioctl to DM */ -int sflc_act_closeVolume(char *label); +int sflc_ops_closeVolume(char *label); -#endif /* _ACTION_VOLUME_H_ */ +#endif /* _OPERATIONS_H_ */ diff --git a/shufflecake-userland-legacy/include/utils/sflc.h b/shufflecake-userland-legacy/include/utils/sflc.h index 83ee0f6..e8135e4 100644 --- a/shufflecake-userland-legacy/include/utils/sflc.h +++ b/shufflecake-userland-legacy/include/utils/sflc.h @@ -22,7 +22,7 @@ */ /* - * Miscellaneous constants that must match with the definitions in the Kernel module + * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflc_constans.h */ #ifndef _UTILS_SFLC_H_ @@ -51,7 +51,7 @@ /* Max total number of open devices at any given time */ #define SFLC_TOT_MAX_DEVICES 1024 -/* A volume name is sflc-- */ +/* A volume name is sflc__ */ #define SFLC_MAX_VOL_NAME_LEN 15 /* A slice index is represented over 32 bits */ diff --git a/shufflecake-userland-legacy/src/actions/create_vol.c b/shufflecake-userland-legacy/src/actions/create_vol.c deleted file mode 100644 index 8cbc755..0000000 --- a/shufflecake-userland-legacy/src/actions/create_vol.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include - -#include "header/position_map.h" -#include "header/volume_master_block.h" -#include "actions/volume.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Writes a volume header (VMB+PM) on-disk. Also fills the struct's volume_key field. - * Some of the fields in the struct are input, some are output. - * - * @param vol->vmb_key The key to decrypt the VMB - * @param vol->prev_vmb_key The previous volume's VMB key - * @param vol->nr_slices The number of logical slices of the volume(s) - * @param vol->vol_idx The index of the volume within the device - * @param vol->bdev_path The underlying block device to write the volume header - * - * @output vol->volume_key This volume's data section key - * - * @unused vol->label - * @unused vol->dev_id - * - * @return Error code, 0 on success - */ -int sflc_act_createVolume(sflc_Volume *vol) -{ - sflc_VolumeMasterBlock vmb; - char enc_vmb[SFLC_SECTOR_SIZE]; - sflc_EncPosMap epm; - uint64_t sector; - int err; - - // Sample keys - sflc_rand_getWeakBytes(vol->volume_key, SFLC_CRYPTO_KEYLEN); - - // Fill VMB - memcpy(vmb.volume_key, vol->volume_key, SFLC_CRYPTO_KEYLEN); - memcpy(vmb.prev_vmb_key, vol->prev_vmb_key, SFLC_CRYPTO_KEYLEN); - vmb.nr_slices = vol->nr_slices; - - // Encrypt it - err = sflc_vmb_seal(&vmb, vol->vmb_key, enc_vmb); - if (err) { - sflc_log_error("Could not seal VMB; error %d", err); - goto out; - } - - // Write it to disk - sector = sflc_vmbPosition(vol); - err = sflc_disk_writeSector(vol->bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not write VMB to disk; error %d", err); - goto out; - } - sector += 1; - - // Create encrypted empty position map - err = sflc_epm_create(vol->nr_slices, vol->volume_key, &epm); - if (err) { - sflc_log_error("Could not create encrypted empty position map; error %d", err); - goto out; - } - - // Loop over PMB arrays to write it to disk - int i; - for (i = 0; i < epm.nr_arrays; i++) { - char *iv_block = epm.iv_blocks[i]; - char *pmb_array = epm.pmb_arrays[i]; - size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); - - // First write the IV block - err = sflc_disk_writeSector(vol->bdev_path, sector, iv_block); - if (err) { - sflc_log_error("Could not write IV block to disk; error %d", err); - goto out; - } - sector += 1; - - // Then the whole PMB array - err = sflc_disk_writeManySectors(vol->bdev_path, sector, pmb_array, nr_pmbs); - if (err) { - sflc_log_error("Could not write PMB array to disk; error %d", err); - goto out; - } - sector += nr_pmbs; - - // Free them both - free(iv_block); - free(pmb_array); - } - - // Free containers - free(epm.iv_blocks); - free(epm.pmb_arrays); - - -out: - return err; -} diff --git a/shufflecake-userland-legacy/src/actions/open.c b/shufflecake-userland-legacy/src/actions/open.c deleted file mode 100644 index 405a14a..0000000 --- a/shufflecake-userland-legacy/src/actions/open.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "header/volume_master_block.h" -#include "actions/volume.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Open the volume through the appropriate ioctl */ -static int _openVolume(sflc_Volume *vol); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Check if the password is correct for this volume. - * - * @param pwd The password - * @param pwd_len The password length - * @param vol_idx The index of the volume in the device - * @param bdev_path The path to the underlying device - * @param nr_slices The number of logical slices for this device / volumes - * - * @output match A boolean containing the answer - * - * @return Error code, 0 on success - */ -int sflc_act_checkPwd(sflc_Volume *vol, bool *match) -{ - char enc_vmb[SFLC_SECTOR_SIZE]; - uint64_t sector; - int err; - - /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol); - err = sflc_disk_readSector(vol->bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); - return err; - } - - /* Try unsealing it */ - return sflc_vmb_tryUnsealWithPwd(enc_vmb, vol->pwd, vol->pwd_len, match); -} - - -/** - * Read a VMB from disk and issue a DM ioctl to create the appropriate virtual device - * - * @param pwd The password - * @param pwd_len The password length - * @param dev_id The ID of the underlying block device - * @param vol_idx The index of the volume within the device - * @param bdev_path The path to the underlying device - * @param nr_slices The number of logical slices for this device / volumes - * - * @output vmb_key This volume's VMB key - * @output volume_key This volume's data section key - * @output prev_vmb_key The previous volume's VMB key - * @output label The volume's name under /dev/mapper/ - * - * @return Error code, 0 on success - */ -int sflc_act_openVolumeWithPwd(sflc_Volume *vol) -{ - sflc_VolumeMasterBlock vmb; - char enc_vmb[SFLC_SECTOR_SIZE]; - uint64_t sector; - int err; - - /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol); - err = sflc_disk_readSector(vol->bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); - return err; - } - - /* Unseal it */ - err = sflc_vmb_unsealWithPwd(enc_vmb, vol->pwd, vol->pwd_len, &vmb); - if (err) { - sflc_log_error("Could not unseal VMB; error %d", err); - return err; - } - - /* Compare the number of slices */ - if (vol->nr_slices != vmb.nr_slices) { - sflc_log_error("Incompatible header size: the device size was different when the volumes" - "were created. Did you resize the device %s since last time?", vol->bdev_path); - return EINVAL; - } - /* Copy the keys over to the Volume struct */ - memcpy(vol->vmb_key, vmb.vmb_key, SFLC_CRYPTO_KEYLEN); - memcpy(vol->volume_key, vmb.volume_key, SFLC_CRYPTO_KEYLEN); - memcpy(vol->prev_vmb_key, vmb.prev_vmb_key, SFLC_CRYPTO_KEYLEN); - /* Build volume label */ - sprintf(vol->label, "sflc-%lu-%lu", vol->dev_id, vol->vol_idx); - - /* Actually open the volume */ - err = _openVolume(vol); - if (err) { - sflc_log_error("Could not open volume; error %d", err); - return err; - } - - return 0; -} - - -/** - * Read a VMB from disk and issue a DM ioctl to create the appropriate virtual device - * - * @param vmb_key The key encrypting this volume's VMB payload - * @param dev_id The ID of the underlying block device - * @param vol_idx The index of the volume within the device - * @param bdev_path The path to the underlying device - * @param nr_slices The number of logical slices for this device / volumes - * - * @output volume_key This volume's data section key - * @output prev_vmb_key The previous volume's VMB key - * @output vol_id The volume's unique numeric ID - * @output label The volume's name under /dev/mapper/ - * - * @return Error code, 0 on success - */ -int sflc_act_openVolumeWithKey(sflc_Volume *vol) -{ - sflc_VolumeMasterBlock vmb; - char enc_vmb[SFLC_SECTOR_SIZE]; - uint64_t sector; - int err; - - /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol); - err = sflc_disk_readSector(vol->bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); - return err; - } - - /* Unseal it */ - err = sflc_vmb_unsealWithKey(enc_vmb, vol->vmb_key, &vmb); - if (err) { - sflc_log_error("Could not unseal VMB; error %d", err); - return err; - } - - /* Compare the number of slices */ - if (vol->nr_slices != vmb.nr_slices) { - sflc_log_error("Incompatible header size: the device size was different when the volumes" - "were created. Did you resize the device %s since last time?", vol->bdev_path); - return EINVAL; - } - /* Copy the keys over to the Volume struct */ - memcpy(vol->vmb_key, vmb.vmb_key, SFLC_CRYPTO_KEYLEN); - memcpy(vol->volume_key, vmb.volume_key, SFLC_CRYPTO_KEYLEN); - memcpy(vol->prev_vmb_key, vmb.prev_vmb_key, SFLC_CRYPTO_KEYLEN); - /* Build volume label */ - sprintf(vol->label, "sflc-%lu-%lu", vol->dev_id, vol->vol_idx); - - /* Actually open the volume */ - err = _openVolume(vol); - if (err) { - sflc_log_error("Could not open volume; error %d", err); - return err; - } - - return 0; -} - - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Open the volume through the appropriate ioctl */ -static int _openVolume(sflc_Volume *vol) -{ - char *hex_key; - char params[SFLC_BIGBUFSIZE]; - uint64_t num_sectors; - int err; - - /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vol->volume_key, SFLC_CRYPTO_KEYLEN); - if (!hex_key) { - sflc_log_error("Could not encode volume key to hexadecimal"); - err = ENOMEM; - goto err_hexkey; - } - - /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vol->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; - - /* Build param list */ - sprintf(params, "%s %lu %lu %s", vol->bdev_path, vol->vol_idx, vol->nr_slices, hex_key); - - /* Issue ioctl */ - err = sflc_dm_create(vol->label, num_sectors, params); - if (err) { - sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); - goto err_dmcreate; - } - err = 0; - - -err_dmcreate: - free(hex_key); -err_hexkey: - return err; -} diff --git a/shufflecake-userland-legacy/src/actions/open_vol.c b/shufflecake-userland-legacy/src/actions/open_vol.c deleted file mode 100644 index 6cffe87..0000000 --- a/shufflecake-userland-legacy/src/actions/open_vol.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "header/volume_master_block.h" -#include "actions/volume.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Open the volume through the appropriate ioctl */ -static int _openVolume(sflc_Volume *vol); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Read a VMB from disk and issue a DM ioctl to create the appropriate virtual device - * - * @param vol->vmb_key The key encrypting this volume's VMB - * @param vol->dev_id The ID of the underlying block device - * @param vol->vol_idx The index of the volume within the device - * @param vol->bdev_path The path to the underlying device - * @param vol->nr_slices The number of logical slices for this device / volumes - * - * @output vol->volume_key This volume's data section key - * @output vol->prev_vmb_key The previous volume's VMB key - * @output vol->label The volume's name under /dev/mapper/ - * - * @return Error code, 0 on success - */ -int sflc_act_openVolume(sflc_Volume *vol) -{ - sflc_VolumeMasterBlock vmb; - char enc_vmb[SFLC_SECTOR_SIZE]; - uint64_t sector; - int err; - - /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol); - err = sflc_disk_readSector(vol->bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); - return err; - } - - /* Unseal it */ - err = sflc_vmb_unseal(enc_vmb, vol->vmb_key, &vmb); - if (err) { - sflc_log_error("Could not unseal VMB; error %d", err); - return err; - } - - /* Compare the number of slices */ - if (vol->nr_slices != vmb.nr_slices) { - sflc_log_error("Incompatible header size: the device size was different when the volumes" - "were created. Did you resize the device %s since last time?", vol->bdev_path); - return EINVAL; - } - /* Copy the keys over to the Volume struct */ - memcpy(vol->volume_key, vmb.volume_key, SFLC_CRYPTO_KEYLEN); - memcpy(vol->prev_vmb_key, vmb.prev_vmb_key, SFLC_CRYPTO_KEYLEN); - /* Build volume label */ - sprintf(vol->label, "sflc_%lu_%lu", vol->dev_id, vol->vol_idx); - - /* Actually open the volume */ - err = _openVolume(vol); - if (err) { - sflc_log_error("Could not open volume; error %d", err); - return err; - } - - return 0; -} - - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Open the volume through the appropriate ioctl */ -static int _openVolume(sflc_Volume *vol) -{ - char *hex_key; - char params[SFLC_BIGBUFSIZE]; - uint64_t num_sectors; - int err; - - /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vol->volume_key, SFLC_CRYPTO_KEYLEN); - if (!hex_key) { - sflc_log_error("Could not encode volume key to hexadecimal"); - err = ENOMEM; - goto err_hexkey; - } - - /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vol->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; - - /* Build param list */ - sprintf(params, "%s %lu %lu %s", vol->bdev_path, vol->vol_idx, vol->nr_slices, hex_key); - - /* Issue ioctl */ - err = sflc_dm_create(vol->label, num_sectors, params); - if (err) { - sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); - goto err_dmcreate; - } - err = 0; - - -err_dmcreate: - free(hex_key); -err_hexkey: - return err; -} diff --git a/shufflecake-userland-legacy/src/cli/changepwd.c b/shufflecake-userland-legacy/src/cli/changepwd.c new file mode 100644 index 0000000..de37779 --- /dev/null +++ b/shufflecake-userland-legacy/src/cli/changepwd.c @@ -0,0 +1,112 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* + * Change volume password + * + * @return Error code, 0 on success + */ +int sflc_cli_changePwd(char *block_device) +{ // Requires: block_device is a correct block device path + sflc_cmd_OpenArgs open_args; + sflc_cmd_ChangePwdArgs change_pwd_args; + sflc_DmbCell dmb_cell; + char old_pwd[SFLC_BIGBUFSIZE]; + size_t old_pwd_len; + char new_pwd[SFLC_BIGBUFSIZE]; + size_t new_pwd_len; + int err; + + open_args.bdev_path = block_device; + + /* Gather password */ + printf("Enter the password you want to change: "); + err = sflc_safeReadPassphrase(old_pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + old_pwd_len = strlen(old_pwd); + /* Assign fields */ + open_args.pwd = old_pwd; + open_args.pwd_len = old_pwd_len; + + /* Test the password */ + err = sflc_cmd_testPwd(&open_args, &dmb_cell); + if (err) { + sflc_log_error("Could not test password; error %d", err); + return err; + } + + /* Does this password open any volumes? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + printf("This password does not unlock any volume.\n"); + return 0; + } + + /* Gather new password (no secure shell) */ + printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); + printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); + err = sflc_safeReadLine(new_pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read new password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + new_pwd_len = strlen(new_pwd); + + /* Assign fields */ + change_pwd_args.bdev_path = block_device; + change_pwd_args.dmb_cell = &dmb_cell; + change_pwd_args.new_pwd = new_pwd; + change_pwd_args.new_pwd_len = new_pwd_len; + + /* Change password */ + err = sflc_cmd_changePwd(&change_pwd_args); + if (err) { + sflc_log_error("Could not change password; error %d", err); + return err; + } + printf("Password changed successfully.\n"); + return err; +} diff --git a/shufflecake-userland-legacy/src/cli/close.c b/shufflecake-userland-legacy/src/cli/close.c index 3f37f35..fdbaa28 100644 --- a/shufflecake-userland-legacy/src/cli/close.c +++ b/shufflecake-userland-legacy/src/cli/close.c @@ -29,8 +29,8 @@ #include #include -#include "cli/cli.h" -#include "commands/commands.h" +#include "cli.h" +#include "commands.h" #include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" diff --git a/shufflecake-userland-legacy/src/cli/dispatch.c b/shufflecake-userland-legacy/src/cli/dispatch.c index 1273ba7..01da250 100644 --- a/shufflecake-userland-legacy/src/cli/dispatch.c +++ b/shufflecake-userland-legacy/src/cli/dispatch.c @@ -31,7 +31,7 @@ #include #include -#include "cli/cli.h" +#include "cli.h" #include "utils/sflc.h" #include "utils/disk.h" #include "utils/log.h" @@ -56,14 +56,16 @@ const char *argp_program_bug_address = ""; * TYPES * *****************************************************/ -enum sflc_cli_command { - SFLC_CMD_INIT, - SFLC_CMD_OPEN, - SFLC_CMD_CLOSE +enum sflc_cli_action { + SFLC_ACT_INIT, + SFLC_ACT_OPEN, + SFLC_ACT_CLOSE, + SFLC_ACT_TESTPWD, + SFLC_ACT_CHANGEPWD }; struct sflc_cli_arguments { - enum sflc_cli_command cmd; + enum sflc_cli_action act; char *block_device; int num_volumes; bool skip_randfill; @@ -82,8 +84,30 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state); *****************************************************/ /* Doc strings */ -static char args_doc[] = "COMMAND "; -static char doc[] = "Shufflecake is a plausible deniability (hidden storage) layer for Linux.\nSee official website at for more info.\nPossible values for COMMAND are:\n\n\tinit:\tInitialise a block device for Shufflecake use, formatting\n\t\theaders with provided passwords and overwriting with random\n\t\tdata. WARNING: THIS WILL ERASE THE CONTENT OF THE DEVICE.\n\n\topen:\tOpen a hierarchy of Shufflecake volumes within a device by\n\t\tasking a single user password. Virtual devices will appear\n\t\tin /dev/mapper. Notice: freshly created device must be\n\t\tuser-formatted and mounted in order to be used.\n\n\tclose:\tClose all open Shufflecake volumes supported by given\n\t\tdevice.\n\nPossible options are:"; +static char args_doc[] = "ACTION "; +static char doc[] = + "Shufflecake is a plausible deniability (hidden storage) layer for Linux.\n" + "See official website at for more info.\n" + "Possible values for mandatory ACTION are:\n\n" + + "\tinit:\t\tInitialise a block device for Shufflecake use, formatting\n" + "\t\t\theaders with provided passwords and overwriting with random\n" + "\t\t\tdata. WARNING: THIS WILL ERASE THE CONTENT OF THE DEVICE.\n\n" + + "\topen:\t\tOpen a hierarchy of Shufflecake volumes within a device by\n" + "\t\t\tasking a single user password. Virtual devices will appear\n" + "\t\t\tin /dev/mapper. Notice: freshly created device must be\n" + "\t\t\tuser-formatted and mounted in order to be used.\n\n" + + "\tclose:\t\tClose all open Shufflecake volumes supported by given\n" + "\t\t\tdevice.\n\n" + + "\ttestpwd:\tTest whether a given password unlocks any volume within\n" + "\t\t\tthe block device and, if so, show its index.\n\n" + + "\tchangepwd:\tChange the password unlocking a certain volume.\n\n" + + "Possible options are:"; /* Description of each option */ static struct argp_option options[] = { @@ -115,7 +139,7 @@ static struct argp argp = {options, _parseArgpKey, args_doc, doc}; int sflc_cli_dispatch(int argc, char **argv) { struct sflc_cli_arguments arguments; - arguments.cmd = -1; + arguments.act = -1; arguments.block_device = NULL; arguments.num_volumes = 0; arguments.skip_randfill = false; @@ -124,12 +148,12 @@ int sflc_cli_dispatch(int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, &arguments); /* Check options consistency */ - if (arguments.num_volumes && arguments.cmd != SFLC_CMD_INIT) { + if (arguments.num_volumes && arguments.act != SFLC_ACT_INIT) { printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); return EINVAL; } /* Check options consistency */ - if (arguments.skip_randfill && arguments.cmd != SFLC_CMD_INIT) { + if (arguments.skip_randfill && arguments.act != SFLC_ACT_INIT) { printf("Error: --skip-randfill can only be combined with `init'.\n"); return EINVAL; } @@ -140,15 +164,21 @@ int sflc_cli_dispatch(int argc, char **argv) { } /* Dispatch to specific command */ - if (arguments.cmd == SFLC_CMD_INIT) { + if (arguments.act == SFLC_ACT_INIT) { return sflc_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); } - if (arguments.cmd == SFLC_CMD_OPEN) { + if (arguments.act == SFLC_ACT_OPEN) { return sflc_cli_open(arguments.block_device); } - if (arguments.cmd == SFLC_CMD_CLOSE) { + if (arguments.act == SFLC_ACT_CLOSE) { return sflc_cli_close(arguments.block_device); } + if (arguments.act == SFLC_ACT_TESTPWD) { + return sflc_cli_testPwd(arguments.block_device); + } + if (arguments.act == SFLC_ACT_CHANGEPWD) { + return sflc_cli_changePwd(arguments.block_device); + } printf("\n"); @@ -168,14 +198,19 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { case ARGP_KEY_ARG: /* We are parsing the command */ if (state->arg_num == 0) { - if (strcmp(arg, "init") == 0) { - arguments->cmd = SFLC_CMD_INIT; - } else if (strcmp(arg, "open") == 0) { - arguments->cmd = SFLC_CMD_OPEN; - } else if (strcmp(arg, "close") == 0) { - arguments->cmd = SFLC_CMD_CLOSE; + if (strcmp(arg, SFLC_CLI_INITACT) == 0) { + arguments->act = SFLC_ACT_INIT; + } else if (strcmp(arg, SFLC_CLI_OPENACT) == 0) { + arguments->act = SFLC_ACT_OPEN; + } else if (strcmp(arg, SFLC_CLI_CLOSEACT) == 0) { + arguments->act = SFLC_ACT_CLOSE; + } else if (strcmp(arg, SFLC_CLI_TESTPWDACT) == 0) { + arguments->act = SFLC_ACT_TESTPWD; + } else if (strcmp(arg, SFLC_CLI_CHANGEPWDACT) == 0) { + arguments->act = SFLC_ACT_CHANGEPWD; } else { - argp_error(state, "Invalid action. Please enter one and only one of: `init', `open', or `close'."); + argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", + SFLC_CLI_INITACT, SFLC_CLI_OPENACT, SFLC_CLI_CLOSEACT, SFLC_CLI_TESTPWDACT, SFLC_CLI_CHANGEPWDACT); } /* We are parsing the block device */ } else if (state->arg_num == 1) { diff --git a/shufflecake-userland-legacy/src/cli/init.c b/shufflecake-userland-legacy/src/cli/init.c index 9a129c9..302a96d 100644 --- a/shufflecake-userland-legacy/src/cli/init.c +++ b/shufflecake-userland-legacy/src/cli/init.c @@ -29,8 +29,8 @@ #include #include -#include "cli/cli.h" -#include "commands/commands.h" +#include "cli.h" +#include "commands.h" #include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -88,11 +88,11 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) "volume 0 (the least secret) to volume %lu (the most secret).\n\n", args.nr_vols - 1); size_t i; for (i = 0; i < args.nr_vols; i++) { - // Allocate pwd TODO: USE SECURE INPUT METHOD FOR PASSWORDS + // Allocate pwd pwds[i] = malloc(SFLC_BIGBUFSIZE); /* Read it */ - printf("Choose password for volume %lu: ", i); + printf("Choose password for volume %lu (must not be empty): ", i); err = sflc_safeReadLine(pwds[i], SFLC_BIGBUFSIZE); if (err) { sflc_log_error("Could not read password for volume %lu; error %d", i, err); @@ -101,6 +101,11 @@ int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) /* You can trust the length of strings input this way */ pwd_lens[i] = strlen(pwds[i]); + /* Check non-empty */ + if (pwd_lens[i] == 0) { + sflc_log_error("Password cannot be empty!"); + return EINVAL; + } } /* Assign them */ args.pwds = pwds; diff --git a/shufflecake-userland-legacy/src/cli/open.c b/shufflecake-userland-legacy/src/cli/open.c index e0f10dc..45464c9 100644 --- a/shufflecake-userland-legacy/src/cli/open.c +++ b/shufflecake-userland-legacy/src/cli/open.c @@ -29,8 +29,8 @@ #include #include -#include "cli/cli.h" -#include "commands/commands.h" +#include "cli.h" +#include "commands.h" #include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -48,26 +48,10 @@ int sflc_cli_open(char *block_device) { // Requires: block_device is a correct block device path sflc_cmd_OpenArgs args; -// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; char pwd[SFLC_BIGBUFSIZE]; size_t pwd_len; int err; -// /* Gather (absolute) path to underlying block device */ -// printf("Enter the absolute path to the underlying block device containing the Shufflecake volumes to open: "); -// err = sflc_safeReadLine(bdev_path, SFLC_BDEV_PATH_MAX_LEN + 2); -// if (err) { -// sflc_log_error("Could not read path to underlying block device; error %d", err); -// return err; -// } -// /* Check that it is absolute */ -// if (bdev_path[0] != '/') { -// printf("The path to the block device must be absolute"); -// return EINVAL; -// } -// /* Assign it */ -// args.bdev_path = bdev_path; - args.bdev_path = block_device; /* Gather password */ @@ -79,6 +63,11 @@ int sflc_cli_open(char *block_device) } /* You can trust the length of strings input this way */ pwd_len = strlen(pwd); + /* Check non-empty */ + if (pwd_len == 0) { + sflc_log_error("Password cannot be empty!"); + return EINVAL; + } /* Assign them */ args.pwd = pwd; args.pwd_len = pwd_len; diff --git a/shufflecake-userland-legacy/include/actions/device.h b/shufflecake-userland-legacy/src/cli/testpwd.c similarity index 52% rename from shufflecake-userland-legacy/include/actions/device.h rename to shufflecake-userland-legacy/src/cli/testpwd.c index 8b7188a..550df25 100644 --- a/shufflecake-userland-legacy/include/actions/device.h +++ b/shufflecake-userland-legacy/src/cli/testpwd.c @@ -21,39 +21,67 @@ * If not, see . */ -/* - * Global actions on the device - */ - -#ifndef _ACTION_DEVICE_H_ -#define _ACTION_DEVICE_H_ - - /***************************************************** * INCLUDE SECTION * *****************************************************/ -#include -#include +#include +#include +#include -#include "header/device_master_block.h" -#include "utils/disk.h" -#include "utils/crypto.h" +#include "cli.h" +#include "commands.h" +#include "utils/sflc.h" +#include "utils/input.h" +#include "utils/log.h" /***************************************************** - * CONSTANTS * + * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ +/* + * Test volume password + * + * @return Error code, 0 on success + */ +int sflc_cli_testPwd(char *block_device) +{ // Requires: block_device is a correct block device path + sflc_cmd_OpenArgs args; + sflc_DmbCell dmb_cell; +// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; + char pwd[SFLC_BIGBUFSIZE]; + size_t pwd_len; + int err; -/* Samples VMB keys and writes the corresponding DMB to disk */ -int sflc_act_createAndWriteDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_CompleteDeviceMasterBlock *dmb); + args.bdev_path = block_device; + + /* Gather password */ + printf("Enter the password you want to test: "); + err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); + if (err) { + sflc_log_error("Could not read password; error %d", err); + return err; + } + /* You can trust the length of strings input this way */ + pwd_len = strlen(pwd); + /* Assign them */ + args.pwd = pwd; + args.pwd_len = pwd_len; -/* Reads the DMB from disk and outputs the unlocked VMB key */ -int sflc_act_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_PartialDeviceMasterBlock *dmb); + /* Actually perform the command */ + err = sflc_cmd_testPwd(&args, &dmb_cell); + if (err) { + sflc_log_error("Could not test password; error %d", err); + return err; + } + /* Does this password open any volumes? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + printf("This password does not unlock any volume.\n"); + } else { + printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); + } -#endif /* _ACTION_DEVICE_H_ */ + return 0; +} diff --git a/shufflecake-userland-legacy/src/actions/close.c b/shufflecake-userland-legacy/src/commands/change_pwd.c similarity index 78% rename from shufflecake-userland-legacy/src/actions/close.c rename to shufflecake-userland-legacy/src/commands/change_pwd.c index 008aaf6..2ef64e0 100644 --- a/shufflecake-userland-legacy/src/actions/close.c +++ b/shufflecake-userland-legacy/src/commands/change_pwd.c @@ -26,18 +26,14 @@ *****************************************************/ #include -#include -#include #include -#include +#include -#include "header/volume_master_block.h" -#include "actions/volume.h" +#include "commands.h" +#include "operations.h" #include "utils/sflc.h" #include "utils/crypto.h" #include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" #include "utils/log.h" @@ -46,14 +42,17 @@ *****************************************************/ /** - * Close the volume by issuing the appropriate ioctl to the DM. + * Changes the specified volume's password. * - * @param label The only needed parameter: the ID of the volume. + * @param args->bdev_path The underlying block device + * @param args->dmb_cell The DMB cell to re-encrypt + * @param args->new_pwd The new password + * @param args->new_pwd_len Its length * * @return Error code, 0 on success */ -int sflc_act_closeVolume(char *label) +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args) { - /* Issue ioctl */ - return sflc_dm_destroy(label); + /* Delegate entirely to the function reading the DMB */ + return sflc_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); } diff --git a/shufflecake-userland-legacy/src/commands/close.c b/shufflecake-userland-legacy/src/commands/close.c index 7b364cc..4c180f6 100644 --- a/shufflecake-userland-legacy/src/commands/close.c +++ b/shufflecake-userland-legacy/src/commands/close.c @@ -29,8 +29,8 @@ #include #include -#include "commands/commands.h" -#include "actions/volume.h" +#include "commands.h" +#include "operations.h" #include "utils/sflc.h" #include "utils/crypto.h" #include "utils/string.h" @@ -45,7 +45,7 @@ /* Reads the list of volumes from sysfs */ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols); -/* Close them all */ +/* Close them all (in reverse order of opening) */ static int _closeVolumes(char **labels, size_t nr_vols); @@ -83,7 +83,7 @@ int sflc_cmd_closeVolumes(char *bdev_path) goto out; } - /* Close the volumes */ + /* Close the volumes (in reverse order of opening) */ err = _closeVolumes(labels, nr_vols); if (err) { sflc_log_error("Could not close volumes; error %d", err); @@ -156,15 +156,15 @@ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) } -/* Close them all */ +/* Close them all (in reverse order of opening) */ static int _closeVolumes(char **labels, size_t nr_vols) { int err; /* Eazy peazy */ - size_t i; - for (i = 0; i < nr_vols; i++) { - err = sflc_act_closeVolume(labels[i]); + int i; + for (i = nr_vols-1; i >= 0; i--) { + err = sflc_ops_closeVolume(labels[i]); if (err) { sflc_log_error("Could not close volume %s; error %d", labels[i], err); return err; diff --git a/shufflecake-userland-legacy/src/commands/init.c b/shufflecake-userland-legacy/src/commands/init.c index 8f9212d..cd21b03 100644 --- a/shufflecake-userland-legacy/src/commands/init.c +++ b/shufflecake-userland-legacy/src/commands/init.c @@ -29,10 +29,10 @@ #include #include -#include "commands/commands.h" -#include "actions/device.h" -#include "actions/volume.h" +#include "commands.h" +#include "operations.h" #include "utils/sflc.h" +#include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -52,7 +52,7 @@ *****************************************************/ /* Fill the device with random data */ -static int _fillWithRandom(char *bdev_path, uint64_t dev_size); +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); /***************************************************** @@ -74,7 +74,8 @@ static int _fillWithRandom(char *bdev_path, uint64_t dev_size); */ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) { - sflc_CompleteDeviceMasterBlock dmb; + sflc_Dmb dmb; + sflc_Vmb vmb; int64_t dev_size; size_t nr_slices; int err; @@ -85,7 +86,7 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) return EINVAL; } - /* Get device info */ + /* Get device size */ dev_size = sflc_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; @@ -96,42 +97,47 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) nr_slices = sflc_disk_maxSlices(dev_size); sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); - /* Fill with random, if needed */ + /* Fill disk with random bytes, if requested */ if (!args->no_randfill) { - err = _fillWithRandom(args->bdev_path, (uint64_t) dev_size); + err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); if (err) { sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); return err; } } - /* Create the DMB and write it to disk */ + /* Fill the DMB */ dmb.nr_vols = args->nr_vols; - err = sflc_act_createAndWriteDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + /* Sample the VMB keys */ + size_t i; + for (i = 0; i < dmb.nr_vols; i++) { + err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_CRYPTO_KEYLEN); + if (err) { + sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); + return err; + } + } + /* And write (encrypted) to disk */ + err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); if (err) { sflc_log_error("Could not create DMB and write it to disk; error %d", err); return err; } - /* Common fields for all the volumes */ - sflc_Volume vol; - vol.bdev_path = args->bdev_path; - vol.nr_slices = nr_slices; - /* Create each of the volumes */ - size_t i; + /* Write the volume headers */ + vmb.nr_slices = nr_slices; for (i = 0; i < args->nr_vols; i++) { - /* This volume's vmb_key and prev_vmb_key */ - memcpy(vol.vmb_key, dmb.vmb_keys[i], SFLC_CRYPTO_KEYLEN); + /* This volume's prev_vmb_key */ if (i > 0) { - memcpy(vol.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_CRYPTO_KEYLEN); + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_CRYPTO_KEYLEN); } - /* This volume's index */ - vol.vol_idx = i; + /* Sample this volume's VEK */ + sflc_rand_getStrongBytes(vmb.volume_key, SFLC_CRYPTO_KEYLEN); - /* Create volume */ - err = sflc_act_createVolume(&vol); + /* Write complete volume header (VMB + PM) */ + err = sflc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); if (err) { - sflc_log_error("Error creating volume %lu on device %s; error %d", i, args->bdev_path, err); + sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); return err; } } @@ -146,7 +152,7 @@ int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) *****************************************************/ /* Fill the device with random data */ -static int _fillWithRandom(char *bdev_path, uint64_t dev_size) +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) { char *rand_chunk; int err; diff --git a/shufflecake-userland-legacy/src/commands/open.c b/shufflecake-userland-legacy/src/commands/open.c index 7e7b8e0..da0c4f0 100644 --- a/shufflecake-userland-legacy/src/commands/open.c +++ b/shufflecake-userland-legacy/src/commands/open.c @@ -25,29 +25,25 @@ * INCLUDE SECTION * *****************************************************/ +#include #include #include #include -#include "commands/commands.h" -#include "actions/volume.h" -#include "actions/device.h" +#include "commands.h" +#include "operations.h" #include "utils/sflc.h" #include "utils/crypto.h" +#include "utils/disk.h" #include "utils/file.h" #include "utils/log.h" +#include "utils/string.h" /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -/* Finds the volume opened by the given pwd */ -static int _findVolumeByPwd(sflc_cmd_OpenArgs *args, size_t nr_slices, size_t *vol_idx, char *vmb_key); - -/* Opens the indicated volume and the previous ones with the VMB keys */ -static int _openVolumes(sflc_cmd_OpenArgs *args, size_t nr_slices, size_t vol_idx, char *vmb_key); - /* Read the next device ID in sysfs */ static int _getNextDevId(size_t *next_dev_id); @@ -57,13 +53,15 @@ static int _getNextDevId(size_t *next_dev_id); *****************************************************/ /** - * Open M volumes, from the one whose pwd is provided back up to the first one. - * Scans the device to find the volume that can be opened with the provided - * pwd, opens it, then opens the previous ones with their VMB key. + * Open M volumes, from the first one down to the one whose pwd is provided. + * Scans the DMB cells to find which one is unlocked by the provided pwd; then, + * using the decrypted VMB key, unlocks the M-th VMB; then, iteratively using + * the prev_vmb_key field, unlocks all the previous VMBs; then, using the + * decrypted VMB keys, opens the volumes "in order" from 1 to M. * * @param args->bdev_path The underlying block device - * @param pwd The password - * @param pwd_len The password length + * @param args->pwd The password + * @param args->pwd_len The password length * * @return Error code (also if no volume could be opened), 0 on success */ @@ -71,10 +69,32 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) { int64_t dev_size; size_t nr_slices; - size_t vol_idx; - char vmb_key[SFLC_CRYPTO_KEYLEN]; + sflc_DmbCell dmb_cell; + sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; + size_t dev_id; int err; - + char bdev_path_noslash[SFLC_BDEV_PATH_MAX_LEN + 1]; + char opendev_path[SFLC_BIGBUFSIZE]; + DIR* opendev_dir; + + /* Check if device is already opened and abort if so. */ + /* Step 1: rebuild sysfs directory name of device to be checked. */ + /* TODO: this is duplicate code from close.c we might want to modularize it. */ + /* Remove the slashes from the bdev_path (replace with underscores). */ + strcpy(bdev_path_noslash, args->bdev_path); + sflc_str_replaceAll(bdev_path_noslash, '/', '_'); + /* Build sysfs path of opened device */ + sprintf(opendev_path, "%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_path_noslash); + /* Step 2: check if directory exists. */ + opendev_dir = opendir(opendev_path); + if (opendev_dir) { + /* Directory exists. */ + closedir(opendev_dir); + err = EEXIST; + sflc_log_error("Device %s seems to be already open; error %d", args->bdev_path, err); + return err; + } + /* Get number of slices */ dev_size = sflc_disk_getSize(args->bdev_path); if (dev_size < 0) { @@ -85,21 +105,63 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) nr_slices = sflc_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ - err = _findVolumeByPwd(args, nr_slices, &vol_idx, vmb_key); + err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); if (err) { - sflc_log_error("Could not find volume opened by given password; error %d", err); + sflc_log_error("Could not read DMB; error %d", err); return err; } - /* Was there one? */ - if (vol_idx >= SFLC_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { sflc_log_error("The provided password opens no volume on the device"); return EINVAL; } printf("Password is correct! Opening volumes...\n"); - /* Open volumes */ - return _openVolumes(args, nr_slices, vol_idx, vmb_key); + /* Unlock VMBs "backwards" */ + int i; // Needs sign, because loop ends on i>=0 + for (i = dmb_cell.vol_idx; i >= 0; i--) { + /* Which VMB key to use? */ + char *vmb_key; + if (i == dmb_cell.vol_idx) { + // The one unlocked by pwd + vmb_key = dmb_cell.vmb_key; + } else { + // Or the prev_vmb_key from last iteration + vmb_key = vmbs[i+1].prev_vmb_key; + } + + /* Read and unlock VMB */ + err = sflc_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not read VMB %d on device %s; error %d", + i, args->bdev_path, err); + return err; + } + } + + /* Get the ID that will be assigned to the block device */ + err = _getNextDevId(&dev_id); + if (err) { + sflc_log_error("Could not get next device ID; error %d", err); + return err; + } + sflc_log_debug("Next device ID is %lu", dev_id); + + /* Open volumes "in order" */ + for (i = 0; i <= dmb_cell.vol_idx; i++) { + err = sflc_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not open volume %d; error %d. " + "Previous volumes on the device might have already " + "been opened, it's recommended you close them", + i, err); + return err; + } + sflc_log_debug("Successfully opened volume %d with VMB key", i); + printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); + } + + return 0; } @@ -107,85 +169,6 @@ int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -/* Finds the volume opened by the given pwd */ -int _findVolumeByPwd(sflc_cmd_OpenArgs *args, size_t nr_slices, size_t *vol_idx, char *vmb_key) -{ - sflc_PartialDeviceMasterBlock dmb; - int err; - - /* Delegate entirely to the function reading the DMB */ - err = sflc_act_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb); - if (err) { - sflc_log_error("Could not read DMB; error %d", err); - return err; - } - - /* Check that a volume was found */ - if (dmb.vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Could not unlock any volumes: wrong password supplied"); - return EINVAL; - } - - /* Copy to output parameters */ - *vol_idx = dmb.vol_idx; - memcpy(vmb_key, dmb.vmb_key, SFLC_CRYPTO_KEYLEN); - - return 0; -} - - -/* Opens the indicated volume with the pwd and the previous ones with the VMB key */ -int _openVolumes(sflc_cmd_OpenArgs *args, size_t nr_slices, size_t vol_idx, char *vmb_key) -{ - sflc_Volume vol; - int err; - - /* Input fields to open a volume */ - vol.bdev_path = args->bdev_path; - vol.nr_slices = nr_slices; - /* Get the ID that will be assigned to the block device */ - err = _getNextDevId(&vol.dev_id); - if (err) { - sflc_log_error("Could not get next device ID; error %d", err); - return err; - } - sflc_log_debug("Next device ID is %lu", vol.dev_id); - - /* Open the last one with the provided vmb_key */ - vol.vol_idx = vol_idx; - memcpy(vol.vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); - err = sflc_act_openVolume(&vol); - if (err) { - sflc_log_error("Could not open volume %lu with pwd; error %d", vol_idx, err); - return err; - } - sflc_log_debug("Successfully opened most secret volume (%lu) with password", vol_idx); - printf("Opened volume /dev/mapper/sflc_%lu_%lu\n", vol.dev_id, vol_idx); - - /* Loop backwards to open the previous ones */ - int i; - for (i = vol_idx-1; i >= 0; i--) { - size_t idx = (size_t) i; // Looping directly with idx would overflow at the "last" iteration - - /* This volume's VMB key was an output of the last openVolume() */ - memcpy(vol.vmb_key, vol.prev_vmb_key, SFLC_CRYPTO_KEYLEN); - - /* Open this volume with VMB key */ - vol.vol_idx = idx; - err = sflc_act_openVolume(&vol); - if (err) { - sflc_log_error("Could not open volume %lu with VMB key; error %d. " - "Some volumes on the device have already been opened, it's recommended you close them", idx, err); - return err; - } - sflc_log_debug("Successfully opened volume %lu with VMB key", idx); - printf("Opened volume /dev/mapper/sflc-%lu-%lu\n", vol.dev_id, idx); - } - - return 0; -} - - /* Read the next device ID in sysfs */ static int _getNextDevId(size_t *next_dev_id) { diff --git a/shufflecake-userland-legacy/src/actions/close_vol.c b/shufflecake-userland-legacy/src/commands/test_pwd.c similarity index 78% rename from shufflecake-userland-legacy/src/actions/close_vol.c rename to shufflecake-userland-legacy/src/commands/test_pwd.c index 008aaf6..f794347 100644 --- a/shufflecake-userland-legacy/src/actions/close_vol.c +++ b/shufflecake-userland-legacy/src/commands/test_pwd.c @@ -26,18 +26,14 @@ *****************************************************/ #include -#include -#include #include -#include +#include -#include "header/volume_master_block.h" -#include "actions/volume.h" +#include "commands.h" +#include "operations.h" #include "utils/sflc.h" #include "utils/crypto.h" #include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" #include "utils/log.h" @@ -46,14 +42,18 @@ *****************************************************/ /** - * Close the volume by issuing the appropriate ioctl to the DM. + * Tests which volume is unlocked by the given password * - * @param label The only needed parameter: the ID of the volume. + * @param args->bdev_path The underlying block device + * @param pwd The password + * @param pwd_len The password length + * + * @output dmb_cell The unlocked DMB cell * * @return Error code, 0 on success */ -int sflc_act_closeVolume(char *label) +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell) { - /* Issue ioctl */ - return sflc_dm_destroy(label); + /* Delegate entirely to the function reading the DMB */ + return sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); } diff --git a/shufflecake-userland-legacy/src/header/device_master_block.c b/shufflecake-userland-legacy/src/header/device_master_block.c index cfcb7c6..cdd1f8c 100644 --- a/shufflecake-userland-legacy/src/header/device_master_block.c +++ b/shufflecake-userland-legacy/src/header/device_master_block.c @@ -29,7 +29,7 @@ #include #include -#include "header/device_master_block.h" +#include "header.h" #include "utils/crypto.h" #include "utils/log.h" @@ -60,7 +60,7 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool * * @return The error code, 0 on success */ -int sflc_dmb_seal(sflc_CompleteDeviceMasterBlock *dmb, char **pwds, size_t *pwd_lens, char *disk_block) +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) { char *salt; int err; @@ -84,7 +84,7 @@ int sflc_dmb_seal(sflc_CompleteDeviceMasterBlock *dmb, char **pwds, size_t *pwd_ /* Loop over all VMB keys to encrypt them */ size_t i; for (i = 0; i < dmb->nr_vols; i++) { - char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL); + char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); /* Encrypt it */ err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); @@ -105,12 +105,12 @@ int sflc_dmb_seal(sflc_CompleteDeviceMasterBlock *dmb, char **pwds, size_t *pwd_ * @param pwd The password locking the VMB key * @param pwd_len Its length * - * @output dmb->vmb_key The unlocked VMB key - * @output dmb->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vmb_key The unlocked VMB key + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_PartialDeviceMasterBlock *dmb) +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell) { // KDF salt char *salt; @@ -131,15 +131,15 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_PartialDev sflc_log_debug("Successfully derived key-encryption-key with KDF"); /* Init dmb->vol_idx to invalid */ - dmb->vol_idx = SFLC_DEV_MAX_VOLUMES; + dmb_cell->vol_idx = SFLC_DEV_MAX_VOLUMES; /* Try all DMB cells */ size_t i; for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL); + char *enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); bool match; /* Try to decrypt this one */ - err = _decryptVmbKeyWithPwd(dmb_cell, kek, vmb_key, &match); + err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); if (err) { sflc_log_error("Error decrypting DMB cell number %lu; error %d", i, err); goto bad_decrypt; @@ -148,8 +148,8 @@ int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_PartialDev /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ if (match) { sflc_log_debug("The provided password unlocks volume %lu", i); - dmb->vol_idx = i; - memcpy(dmb->vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); + dmb_cell->vol_idx = i; + memcpy(dmb_cell->vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); } } @@ -164,6 +164,42 @@ bad_kdf: return err; } +/** + * Re-encrypt the content of the specified DMB cell. + * + * @param disk_block The on-disk sealed DMB + * @param dmb_cell The DMB cell to re-encrypt + * @param pwd The new password + * @param pwd_len The password's length + * + * @return Error code, 0 on success + */ +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len) +{ + char *salt; + char *enc_dmb_cell; + int err; + + /* Sanity check */ + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + return EINVAL; + } + + /* Pointers inside DMB */ + salt = disk_block; + enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLC_DMB_CELL_SIZE); + /* Encrypt with KDF-derived key */ + err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); + if (err) { + sflc_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); + return err; + } + sflc_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); + + return 0; +} + /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * diff --git a/shufflecake-userland-legacy/src/header/position_map.c b/shufflecake-userland-legacy/src/header/position_map.c index 8f90c4f..f2b8144 100644 --- a/shufflecake-userland-legacy/src/header/position_map.c +++ b/shufflecake-userland-legacy/src/header/position_map.c @@ -29,7 +29,7 @@ #include #include -#include "header/position_map.h" +#include "header.h" #include "utils/sflc.h" #include "utils/crypto.h" #include "utils/math.h" diff --git a/shufflecake-userland-legacy/src/header/volume_master_block.c b/shufflecake-userland-legacy/src/header/volume_master_block.c index 35fd91f..9347582 100644 --- a/shufflecake-userland-legacy/src/header/volume_master_block.c +++ b/shufflecake-userland-legacy/src/header/volume_master_block.c @@ -30,7 +30,7 @@ #include #include // Network byte order -#include "header/volume_master_block.h" +#include "header.h" #include "utils/crypto.h" #include "utils/string.h" #include "utils/log.h" @@ -41,10 +41,10 @@ *****************************************************/ /* Serialise the VMB before encrypting it */ -static void _serialiseVmb(sflc_VolumeMasterBlock *vmb, char *clear_vmb); +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_VolumeMasterBlock *vmb); +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); /***************************************************** @@ -61,7 +61,7 @@ static int _deserialiseVmb(char *clear_vmb, sflc_VolumeMasterBlock *vmb); * * @return The error code, 0 on success */ -int sflc_vmb_seal(sflc_VolumeMasterBlock *vmb, char *vmb_key, char *disk_block) +int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) { // Pointers inside the block char *iv = disk_block; @@ -123,7 +123,7 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_VolumeMasterBlock *vmb) +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; @@ -177,7 +177,7 @@ bad_clear_alloc: *****************************************************/ /* Serialise the payload before encrypting it */ -static void _serialiseVmb(sflc_VolumeMasterBlock *vmb, char *clear_vmb) +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; @@ -200,7 +200,7 @@ static void _serialiseVmb(sflc_VolumeMasterBlock *vmb, char *clear_vmb) /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_VolumeMasterBlock *vmb) +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; diff --git a/shufflecake-userland-legacy/src/main.c b/shufflecake-userland-legacy/src/main.c index 8c541b5..737b57e 100644 --- a/shufflecake-userland-legacy/src/main.c +++ b/shufflecake-userland-legacy/src/main.c @@ -30,7 +30,7 @@ * INCLUDE SECTION * *****************************************************/ -#include "cli/cli.h" +#include "cli.h" /***************************************************** diff --git a/shufflecake-userland-legacy/src/operations/devmapper.c b/shufflecake-userland-legacy/src/operations/devmapper.c new file mode 100644 index 0000000..99a66a2 --- /dev/null +++ b/shufflecake-userland-legacy/src/operations/devmapper.c @@ -0,0 +1,111 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include + +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/file.h" +#include "utils/string.h" +#include "utils/dm.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Build parameter list for ctor in dm_sflc, and send DM ioctl to create + * virtual block device. + * + * @param bdev_path The path to the underlying device + * @param dev_id The ID of the underlying block device + * @param vol_idx The index of the volume within the device + * @param vmb Volume metadata + * + * @return Error code, 0 on success + */ +int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) +{ + char label[SFLC_BIGBUFSIZE]; + char *hex_key; + char params[SFLC_BIGBUFSIZE]; + uint64_t num_sectors; + int err; + + /* Build volume label */ + sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); + + /* Get the hex version of the volume's data section key */ + hex_key = sflc_toHex(vmb->volume_key, SFLC_CRYPTO_KEYLEN); + if (!hex_key) { + sflc_log_error("Could not encode volume key to hexadecimal"); + err = ENOMEM; + goto err_hexkey; + } + + /* Get the number of logical 512-byte sectors composing the volume */ + num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; + + /* Build param list */ + sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); + + /* Issue ioctl */ + err = sflc_dm_create(label, num_sectors, params); + if (err) { + sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + goto err_dmcreate; + } + err = 0; + + +err_dmcreate: + free(hex_key); +err_hexkey: + return err; +} + + +/** + * Close the volume by issuing the appropriate ioctl to the DM. + * + * @param label The only needed parameter: the ID of the volume. + * + * @return Error code, 0 on success + */ +int sflc_ops_closeVolume(char *label) +{ + /* Issue ioctl */ + return sflc_dm_destroy(label); +} diff --git a/shufflecake-userland-legacy/src/actions/dmb.c b/shufflecake-userland-legacy/src/operations/dmb.c similarity index 64% rename from shufflecake-userland-legacy/src/actions/dmb.c rename to shufflecake-userland-legacy/src/operations/dmb.c index f8da871..d529966 100644 --- a/shufflecake-userland-legacy/src/actions/dmb.c +++ b/shufflecake-userland-legacy/src/operations/dmb.c @@ -31,9 +31,10 @@ #include #include -#include "header/device_master_block.h" -#include "actions/device.h" +#include "header.h" +#include "operations.h" #include "utils/sflc.h" +#include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -43,18 +44,17 @@ *****************************************************/ /** - * Samples VMB keys and writes the corresponding DMB to disk. + * Encrypts and writes the DMB to disk. * * @param bdev_path The path to the underlying block device * @param pwds The array of passwords * @param pwd_lens Their lengths * @param dmb->nr_vols * - * @output dmb->vmb_keys * * @return Error code, 0 on success */ -int sflc_act_createAndWriteDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_CompleteDeviceMasterBlock *dmb) +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) { /* On-disk DMB */ char enc_dmb[SFLC_SECTOR_SIZE]; @@ -67,17 +67,7 @@ int sflc_act_createAndWriteDmb(char *bdev_path, char **pwds, size_t *pwd_lens, s return EINVAL; } - /* Sample the VMB keys */ - size_t i; - for (i = 0; i < dmb->nr_vols; i++) { - err = sflc_rand_getStrongBytes(dmb->vmb_keys[i], SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); - return err; - } - } - - /* Seal it */ + /* Seal DMB */ err = sflc_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); if (err) { sflc_log_error("Coul dnot seal DMB; error %d", err); @@ -102,16 +92,14 @@ int sflc_act_createAndWriteDmb(char *bdev_path, char **pwds, size_t *pwd_lens, s * @param pwd The user-provided password * @param pwd_len Its length * - * @output dmb->vmb_key The unlocked VMB key - * @output dmb->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vmb_key The unlocked VMB key + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflc_act_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_PartialDeviceMasterBlock *dmb) +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) { - /* On-disk DMB */ char enc_dmb[SFLC_SECTOR_SIZE]; - /* Error code */ int err; /* Read DMB from disk (at sector 0) */ @@ -131,3 +119,49 @@ int sflc_act_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_PartialDev return 0; } +/** + * Reads the DMB from disk, changes the relevant DMB cell, and writes it back. + * + * @param bdev_path The path to the underlying block device + * @param dmb_cell The index and vmb_key of the DMB cell to re-encrypt + * @param new_pwd The new password of the DMB cell to re-encrypt + * @param new_pwd_len Its length + * + * @return Error code, 0 on success + */ +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) +{ + char enc_dmb[SFLC_SECTOR_SIZE]; + int err; + + /* Sanity check */ + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + return EINVAL; + } + + /* Read DMB from disk (at sector 0) */ + err = sflc_disk_readSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not read DMB from disk; error %d", err); + return err; + } + + /* Update the relevant cell */ + err = sflc_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); + if (err) { + sflc_log_error("Could not update DMB cell; error %d", err); + return err; + } + + /* Write it to disk (at sector 0) */ + err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); + if (err) { + sflc_log_error("Could not write DMB to disk; error %d", err); + return err; + } + + return 0; +} + + diff --git a/shufflecake-userland-legacy/src/actions/create.c b/shufflecake-userland-legacy/src/operations/volume_header.c similarity index 61% rename from shufflecake-userland-legacy/src/actions/create.c rename to shufflecake-userland-legacy/src/operations/volume_header.c index f04486f..1088290 100644 --- a/shufflecake-userland-legacy/src/actions/create.c +++ b/shufflecake-userland-legacy/src/operations/volume_header.c @@ -30,10 +30,11 @@ #include #include -#include "header/position_map.h" -#include "header/volume_master_block.h" -#include "actions/volume.h" +#include "header.h" +#include "header.h" +#include "operations.h" #include "utils/sflc.h" +#include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -43,51 +44,32 @@ *****************************************************/ /** - * Writes a volume header (VMB+PM) on-disk. Also fills the vmb_key and volume_key. - * Some of the fields in the struct are input, some are output. + * Writes a volume header (VMB+PM) on-disk. * - * @param pwd The volume password - * @param pwd_len The password length - * @param prev_vmb_key The previous volume's VMB key - * @param nr_slices The number of logical slices of the volume(s) - * @param vol_idx The index of the volume within the device * @param bdev_path The underlying block device to write the volume header - * - * @output vmb_key This volume's VMB key - * @output volume_key This volume's data section key - * - * @unused label + * @param vmb_key The key to encrypt the VMB + * @param vmb The VMB to encrypt + * @param vol_idx The index of the volume within the device * * @return Error code, 0 on success */ -int sflc_act_createVolume(sflc_Volume *vol) +int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) { - sflc_VolumeMasterBlock vmb; char enc_vmb[SFLC_SECTOR_SIZE]; sflc_EncPosMap epm; uint64_t sector; int err; - // Sample keys - sflc_rand_getWeakBytes(vol->vmb_key, SFLC_CRYPTO_KEYLEN); - sflc_rand_getWeakBytes(vol->volume_key, SFLC_CRYPTO_KEYLEN); - - // Fill VMB - memcpy(vmb.vmb_key, vol->vmb_key, SFLC_CRYPTO_KEYLEN); - memcpy(vmb.volume_key, vol->volume_key, SFLC_CRYPTO_KEYLEN); - memcpy(vmb.prev_vmb_key, vol->prev_vmb_key, SFLC_CRYPTO_KEYLEN); - vmb.nr_slices = vol->nr_slices; - - // Encrypt it - err = sflc_vmb_seal(&vmb, vol->pwd, vol->pwd_len, enc_vmb); + // Encrypt VMB + err = sflc_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { sflc_log_error("Could not seal VMB; error %d", err); goto out; } // Write it to disk - sector = sflc_vmbPosition(vol); - err = sflc_disk_writeSector(vol->bdev_path, sector, enc_vmb); + sector = sflc_vmbPosition(vol_idx, vmb->nr_slices); + err = sflc_disk_writeSector(bdev_path, sector, enc_vmb); if (err) { sflc_log_error("Could not write VMB to disk; error %d", err); goto out; @@ -95,13 +77,13 @@ int sflc_act_createVolume(sflc_Volume *vol) sector += 1; // Create encrypted empty position map - err = sflc_epm_create(vol->nr_slices, vol->volume_key, &epm); + err = sflc_epm_create(vmb->nr_slices, vmb->volume_key, &epm); if (err) { sflc_log_error("Could not create encrypted empty position map; error %d", err); goto out; } - // Loop over PMB arrays to write it to disk + // Loop over PMB arrays to write them to disk int i; for (i = 0; i < epm.nr_arrays; i++) { char *iv_block = epm.iv_blocks[i]; @@ -109,7 +91,7 @@ int sflc_act_createVolume(sflc_Volume *vol) size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); // First write the IV block - err = sflc_disk_writeSector(vol->bdev_path, sector, iv_block); + err = sflc_disk_writeSector(bdev_path, sector, iv_block); if (err) { sflc_log_error("Could not write IV block to disk; error %d", err); goto out; @@ -117,7 +99,7 @@ int sflc_act_createVolume(sflc_Volume *vol) sector += 1; // Then the whole PMB array - err = sflc_disk_writeManySectors(vol->bdev_path, sector, pmb_array, nr_pmbs); + err = sflc_disk_writeManySectors(bdev_path, sector, pmb_array, nr_pmbs); if (err) { sflc_log_error("Could not write PMB array to disk; error %d", err); goto out; @@ -137,3 +119,47 @@ int sflc_act_createVolume(sflc_Volume *vol) out: return err; } + + +/** + * Reads a VMB from disk and unlocks it + * + * @param bdev_path The underlying block device + * @param vmb_key The key to decrypt the VMB + * @param nr_slices The number of slices in the device + * @param vol_idx The index of the volume within the device + * + * @output vmb The decrypted VMB + * + * @return Error code, 0 on success + */ +int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) +{ + char enc_vmb[SFLC_SECTOR_SIZE]; + uint64_t sector; + int err; + + /* Read encrypted VMB from disk */ + sector = sflc_vmbPosition(vol_idx, nr_slices); + err = sflc_disk_readSector(bdev_path, sector, enc_vmb); + if (err) { + sflc_log_error("Could not read VMB from disk; error %d", err); + return err; + } + + /* Unseal it */ + err = sflc_vmb_unseal(enc_vmb, vmb_key, vmb); + if (err) { + sflc_log_error("Could not unseal VMB; error %d", err); + return err; + } + + /* Compare the number of slices */ + if (nr_slices != vmb->nr_slices) { + sflc_log_error("Incompatible header size: the device size was different when the volumes" + "were created. Did you resize the device %s since last time?", bdev_path); + return EINVAL; + } + + return 0; +} diff --git a/shufflecake-userland-legacy/src/utils/input.c b/shufflecake-userland-legacy/src/utils/input.c index 7174ca5..ffc0b02 100644 --- a/shufflecake-userland-legacy/src/utils/input.c +++ b/shufflecake-userland-legacy/src/utils/input.c @@ -92,6 +92,9 @@ int sflc_safeReadPassphrase(char *buf, size_t bufsize) buf[len - 1] = '\0'; } + /* Newline on screen */ + printf("\n"); + return 0; } diff --git a/shufflecake-userland-legacy/test/actions/test_actions.h b/shufflecake-userland-legacy/test/actions/test_actions.h deleted file mode 100644 index aae2e00..0000000 --- a/shufflecake-userland-legacy/test/actions/test_actions.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - - -#ifndef _TEST_ACTIONS_H_ -#define _TEST_ACTIONS_H_ - - -/***************************************************** - * PUBLIC FUNCTIONS DECLARATIONS * - *****************************************************/ - -// Exported test cases - -char *test_vol_create(); - -char *test_vol_allActions(); - -#endif /* _TEST_ACTIONS_H_ */ diff --git a/shufflecake-userland-legacy/test/actions/test_all_actions.c b/shufflecake-userland-legacy/test/actions/test_all_actions.c deleted file mode 100644 index d1b7af7..0000000 --- a/shufflecake-userland-legacy/test/actions/test_all_actions.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/disk.h" -#include "utils/crypto.h" -#include "actions/volume.h" -#include "test_actions.h" -#include "minunit.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define DUMMY_PWD "lolololol" -#define WRONG_PWD "yeyeyeyeye" -#define MAX_BDEV_PATH_LEN 100 - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_vol_allActions() -{ - sflc_Volume vol; - char bdev_path[MAX_BDEV_PATH_LEN + 1]; - char label[SFLC_MAX_VOL_NAME_LEN + 1]; - int64_t dev_size; - size_t nr_slices; - bool match; - int err; - - sflc_log_blue("Testing all volume operations (needs sudo privileges and dm-sflc loaded)"); - - // Get bdev_path or terminate - printf("Type path of underlying block device (empty to skip test case): "); - fgets(bdev_path, MAX_BDEV_PATH_LEN, stdin); - bdev_path[strlen(bdev_path) - 1] = '\0'; // Discard newline - if (strlen(bdev_path) == 0) { - sflc_log_yellow("Skipping test case"); - return NULL; - } - // Get vol_idx - printf("Type index of volume within the device: "); - scanf("%lu", &vol.vol_idx); - - // Get device info - dev_size = sflc_disk_getSize(bdev_path); - mu_assert("Error reading device size", dev_size > 0); - nr_slices = sflc_disk_maxSlices(dev_size); - sflc_log_yellow("Device has %ld blocks, corresponding to %lu logical slices", dev_size, nr_slices); - - // Fill input fields in volume - vol.bdev_path = bdev_path; - vol.nr_slices = nr_slices; - vol.pwd = DUMMY_PWD; - vol.pwd_len = strlen(DUMMY_PWD); - memset(vol.prev_vmb_key, 0, SFLC_CRYPTO_KEYLEN); - - // Create volume - err = sflc_act_createVolume(&vol); - mu_assert("Error creating volume", !err); - - // Check with the wrong password - vol.pwd = WRONG_PWD; - vol.pwd_len = strlen(WRONG_PWD); - err = sflc_act_checkPwd(&vol, &match); - mu_assert("Error checking wrong volume password", !err); - mu_assert("Wrong password unexpectedly manages to open volume", !match); - - // Check with the right password - vol.pwd = DUMMY_PWD; - vol.pwd_len = strlen(DUMMY_PWD); - err = sflc_act_checkPwd(&vol, &match); - mu_assert("Error checking right volume password", !err); - mu_assert("Correct password doesn't open volume", match); - - // Actually open it (using the pwd) - err = sflc_act_openVolumeWithPwd(&vol); - mu_assert("Error opening volume with password", !err); - - // Close it (hack: label is known) - sprintf(label, "sflc-0-%lu", vol.vol_idx); - err = sflc_act_closeVolume(label); - mu_assert("Error closing volume (opened with pwd)", !err); - - // Open it again (using the key) - err = sflc_act_openVolumeWithKey(&vol); - mu_assert("Error opening volume with key", !err); - - // Close it again - err = sflc_act_closeVolume(label); - mu_assert("Error closing volume (opened with key)", !err); - - sflc_log_green("Test case finished, manually check the results"); - - return NULL; -} diff --git a/shufflecake-userland-legacy/test/actions/test_create.c b/shufflecake-userland-legacy/test/actions/test_create.c deleted file mode 100644 index 3cbc198..0000000 --- a/shufflecake-userland-legacy/test/actions/test_create.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/disk.h" -#include "utils/crypto.h" -#include "actions/volume.h" -#include "test_actions.h" -#include "minunit.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define DUMMY_PWD "lolololol" -#define MAX_BDEV_PATH_LEN 100 - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_vol_create() -{ - sflc_Volume vol; - char bdev_path[MAX_BDEV_PATH_LEN + 1]; - int64_t dev_size; - size_t nr_slices; - int err; - - sflc_log_blue("Testing volume creation"); - - // Get bdev_path or terminate - printf("Type path of underlying block device (empty to skip test case): "); - fgets(bdev_path, MAX_BDEV_PATH_LEN, stdin); - bdev_path[strlen(bdev_path) - 1] = '\0'; // Discard newline - if (strlen(bdev_path) == 0) { - sflc_log_yellow("Skipping test case"); - return NULL; - } - // Get vol_idx - printf("Type index of volume within the device: "); - scanf("%lu", &vol.vol_idx); - - // Get device info - dev_size = sflc_disk_getSize(bdev_path); - mu_assert("Error reading device size", dev_size > 0); - nr_slices = sflc_disk_maxSlices(dev_size); - sflc_log_yellow("Device has %ld blocks, corresponding to %lu logical slices", dev_size, nr_slices); - - // Fill input fields in volume - vol.bdev_path = bdev_path; - vol.nr_slices = nr_slices; - vol.pwd = DUMMY_PWD; - vol.pwd_len = strlen(DUMMY_PWD); - memset(vol.prev_vmb_key, 0, SFLC_CRYPTO_KEYLEN); - - // Create volume - err = sflc_act_createVolume(&vol); - mu_assert("Error creating volume", !err); - - // Log output keys - sflc_log_yellow("Output vmb_key:"); - sflc_log_hex(vol.vmb_key, SFLC_CRYPTO_KEYLEN); - sflc_log_yellow("Output volume_key:"); - sflc_log_hex(vol.volume_key, SFLC_CRYPTO_KEYLEN); - - sflc_log_green("Test case finished, manually check the results"); - - return NULL; -} diff --git a/shufflecake-userland-legacy/test/commands/test_commands.h b/shufflecake-userland-legacy/test/commands/test_commands.h deleted file mode 100644 index 450f22e..0000000 --- a/shufflecake-userland-legacy/test/commands/test_commands.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _TEST_COMMANDS_H_ -#define _TEST_COMMANDS_H_ - - -/***************************************************** - * PUBLIC FUNCTIONS DECLARATIONS * - *****************************************************/ - -// Exported test cases - -char *test_cmd_init(); - -#endif /* _TEST_COMMANDS_H_ */ diff --git a/shufflecake-userland-legacy/test/commands/test_init.c b/shufflecake-userland-legacy/test/commands/test_init.c deleted file mode 100644 index b60f5d4..0000000 --- a/shufflecake-userland-legacy/test/commands/test_init.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/disk.h" -#include "utils/crypto.h" -#include "commands/commands.h" -#include "test_commands.h" -#include "minunit.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define MAX_BDEV_PATH_LEN 100 -#define MAX_PWD_LEN 40 - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_cmd_init() -{ - sflc_cmd_InitArgs args; - char bdev_path[MAX_BDEV_PATH_LEN + 2]; - size_t nr_vols; - char pwds[SFLC_DEV_MAX_VOLUMES][MAX_PWD_LEN + 2]; - size_t pwd_lens[SFLC_DEV_MAX_VOLUMES]; - int err; - - sflc_log_blue("Testing volume creation"); - - /* Get bdev_path or terminate */ - printf("Type path of underlying block device (empty to skip test case): "); - err = sflc_safeReadLine(bdev_path, sizeof(bdev_path)); - mu_assert("Could not read path to underlying device", !err); - /* Terminate if empty input */ - if (strlen(bdev_path) == 0) { - sflc_log_yellow("Skipping test case"); - return NULL; - } - args.bdev_path = bdev_path; - - /* Get number of volumes */ - printf("How many volumes do you want to create?"); - mu_assert("Could not read number of volumes", scanf("%u", &nr_vols) == 1); - mu_assert("Number of volumes out of bounds", nr_vols <= SFLC_DEV_MAX_VOLUMES); - - /* Get passwords */ - size_t i; - for (i = 0; ) - - sflc_log_green("Test case finished, manually check the results"); - - return NULL; -} From e8c8cfb74d244aa8c7dc089928f2245314699f4d Mon Sep 17 00:00:00 2001 From: = Date: Sat, 3 Aug 2024 22:52:27 +0200 Subject: [PATCH 62/98] Upgrade legacy to new DM interface --- .../include/utils/sflc.h | 4 ++ .../src/commands/close.c | 72 +++++++++++-------- .../src/operations/devmapper.c | 2 +- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/shufflecake-userland-legacy/include/utils/sflc.h b/shufflecake-userland-legacy/include/utils/sflc.h index e8135e4..326aac8 100644 --- a/shufflecake-userland-legacy/include/utils/sflc.h +++ b/shufflecake-userland-legacy/include/utils/sflc.h @@ -33,6 +33,8 @@ * INCLUDE SECTION * *****************************************************/ +#include + /***************************************************** * CONSTANTS * @@ -78,6 +80,8 @@ #define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" /* Within each bdev's subdir, this file lists its open volumes */ #define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" +/* Within each bdev's subdir, this file shows its Shufflecake device ID */ +#define SFLC_SYSFS_DEVID_FILENAME "dev_id" /* TODO: reasonable? */ #define SFLC_BDEV_PATH_MAX_LEN 1024 diff --git a/shufflecake-userland-legacy/src/commands/close.c b/shufflecake-userland-legacy/src/commands/close.c index 4c180f6..73a7605 100644 --- a/shufflecake-userland-legacy/src/commands/close.c +++ b/shufflecake-userland-legacy/src/commands/close.c @@ -35,6 +35,7 @@ #include "utils/crypto.h" #include "utils/string.h" #include "utils/file.h" +#include "utils/disk.h" #include "utils/log.h" @@ -43,7 +44,7 @@ *****************************************************/ /* Reads the list of volumes from sysfs */ -static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols); +static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols); /* Close them all (in reverse order of opening) */ static int _closeVolumes(char **labels, size_t nr_vols); @@ -77,7 +78,7 @@ int sflc_cmd_closeVolumes(char *bdev_path) } /* Read them */ - err = _readVolumesList(bdev_path, labels, &nr_vols); + err = _buildVolumesList(bdev_path, labels, &nr_vols); if (err) { sflc_log_error("Could not read volume list from sysfs; error %d", err); goto out; @@ -106,31 +107,50 @@ out: * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -/* Reads the list of volumes from sysfs */ -static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) +/* Reads from sysfs to build the volumes list */ +static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols) { - char bdev_path_noslash[SFLC_BDEV_PATH_MAX_LEN + 1]; - char openvolumes_path[SFLC_BIGBUFSIZE]; - char *str_openvolumes; + char *bdev_name; + char devid_path[SFLC_BIGBUFSIZE]; + char *str_devid; + size_t dev_id; + char nrvolumes_path[SFLC_BIGBUFSIZE]; + char *str_nrvolumes; - /* Remove the slashes from the bdev_path (replace with underscores) */ - strcpy(bdev_path_noslash, bdev_path); - sflc_str_replaceAll(bdev_path_noslash, '/', '_'); - /* Build path to sysfsy file containing open volumes list */ - sprintf(openvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_path_noslash, SFLC_SYSFS_OPENVOLUMES_FILENAME); + /* Get device name as : */ + bdev_name = sflc_disk_getDeviceName(bdev_path); + if(!bdev_name) { + sflc_log_error("Could not allocate device name"); + return ENOMEM; + } + /* Build path to sysfs file containing device ID */ + sprintf(devid_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_DEVID_FILENAME); + /* Build path to sysfs file containing number of open volumes */ + sprintf(nrvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_OPENVOLUMES_FILENAME); - /* Read the sysfs file */ - str_openvolumes = sflc_readFile(openvolumes_path); - if (!str_openvolumes) { - sflc_log_error("Could not read file %s", openvolumes_path); + /* Read the device ID */ + str_devid = sflc_readFile(devid_path); + if (!str_devid) { + sflc_log_error("Could not read file %s", devid_path); + return EBADF; + } + /* Parse the device ID */ + if (sscanf(str_devid, "%lu", &dev_id) != 1) { + sflc_log_error("Could not parse device ID:\n%s", str_devid); return EBADF; } + /* Read the number of volumes */ + str_nrvolumes = sflc_readFile(nrvolumes_path); + if (!str_nrvolumes) { + sflc_log_error("Could not read file %s", nrvolumes_path); + return EBADF; + } /* Parse the number of volumes */ - char *endptr; - *nr_vols = strtoul(str_openvolumes, &endptr, 10); - /* Skip past the number of volumes (lands on a whitespace before the first label) */ - str_openvolumes = endptr; + if (sscanf(str_nrvolumes, "%lu", nr_vols) != 1) { + sflc_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); + return EBADF; + } /* Just to be sure */ if (*nr_vols > SFLC_DEV_MAX_VOLUMES) { @@ -138,18 +158,10 @@ static int _readVolumesList(char *bdev_path, char **labels, size_t *nr_vols) return EBADF; } - /* Read labels */ + /* Build labels */ size_t i; for (i = 0; i < *nr_vols; i++) { - /* Trust the content of the sysfs file */ - if (sscanf(str_openvolumes, " %s", labels[i]) != 1) { - sflc_log_error("Could not read volume label %lu. Sysfs content:\n%s", i, str_openvolumes); - return EBADF; - } - sflc_log_debug("Label %lu to close: %s", i, labels[i]); - - /* Skip past the whitespace and the label */ - str_openvolumes += 1 + strlen(labels[i]); + sprintf(labels[i], "sflc_%lu_%lu", dev_id, i); } return 0; diff --git a/shufflecake-userland-legacy/src/operations/devmapper.c b/shufflecake-userland-legacy/src/operations/devmapper.c index 99a66a2..a1bec5a 100644 --- a/shufflecake-userland-legacy/src/operations/devmapper.c +++ b/shufflecake-userland-legacy/src/operations/devmapper.c @@ -79,7 +79,7 @@ int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; /* Build param list */ - sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); + sprintf(params, "%d %lu %s %lu %lu %s", SFLC_MODE_LEGACY, dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); /* Issue ioctl */ err = sflc_dm_create(label, num_sectors, params); From 751f54f02804e647c73356d9795e028d0ab20154 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 3 Aug 2024 23:06:33 +0200 Subject: [PATCH 63/98] Everything in order --- shufflecake-userland-legacy/Makefile | 4 +-- .../include/utils/disk.h | 2 ++ .../include/utils/sflc.h | 2 +- shufflecake-userland-legacy/src/utils/disk.c | 26 +++++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/shufflecake-userland-legacy/Makefile b/shufflecake-userland-legacy/Makefile index ee59323..e8598dc 100644 --- a/shufflecake-userland-legacy/Makefile +++ b/shufflecake-userland-legacy/Makefile @@ -61,10 +61,10 @@ DEPS := $(PROJ_DEPS) $(TEST_DEPS) DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) # The target binaries -MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake +MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake-legacy TEST_BIN := $(TEST_OUT_DIR)/tests # Their symlink -MAIN_LINK := shufflecake +MAIN_LINK := shufflecake-legacy TEST_LINK := tests diff --git a/shufflecake-userland-legacy/include/utils/disk.h b/shufflecake-userland-legacy/include/utils/disk.h index 7666609..16885e3 100644 --- a/shufflecake-userland-legacy/include/utils/disk.h +++ b/shufflecake-userland-legacy/include/utils/disk.h @@ -88,6 +88,8 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ +char *sflc_disk_getDeviceName(char *bdev_path); + /* Checks whether the given path points to a block device */ bool sflc_disk_isBlockDevice(char *path); diff --git a/shufflecake-userland-legacy/include/utils/sflc.h b/shufflecake-userland-legacy/include/utils/sflc.h index 326aac8..4bab1ba 100644 --- a/shufflecake-userland-legacy/include/utils/sflc.h +++ b/shufflecake-userland-legacy/include/utils/sflc.h @@ -75,7 +75,7 @@ #define SFLC_EPM_FILLER 0xFF /* The sysfs file containing the next available device ID */ -#define SFLC_SYSFS_NEXTDEVID "/sys/devices/sflc/next_dev_id" +#define SFLC_SYSFS_NEXTDEVID "/sys/module/dm_sflc/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ #define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" /* Within each bdev's subdir, this file lists its open volumes */ diff --git a/shufflecake-userland-legacy/src/utils/disk.c b/shufflecake-userland-legacy/src/utils/disk.c index 2f78598..c37711a 100644 --- a/shufflecake-userland-legacy/src/utils/disk.c +++ b/shufflecake-userland-legacy/src/utils/disk.c @@ -30,11 +30,13 @@ *****************************************************/ #include +#include #include #include #include #include #include +#include #include #include @@ -42,10 +44,34 @@ #include "utils/log.h" + /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ +/** + * Returns a malloc'ed string formatted as : + * + * @param bdev_path The path to the block device + * + * @return A string version of the device ID + */ +char *sflc_disk_getDeviceName(char *bdev_path) +{ + struct stat sb; + char *dev_name; + + if (stat(bdev_path, &sb) != 0) + return NULL; + dev_name = malloc(SFLC_BIGBUFSIZE); + if (!dev_name) + return NULL; + + sprintf(dev_name, "%u:%u", major(sb.st_rdev), minor(sb.st_rdev)); + + return dev_name; +} + /** * Checks whether the given path points to a block device. From 748329c46a55d955a85e44541d96bbced22cbf6b Mon Sep 17 00:00:00 2001 From: = Date: Thu, 22 Aug 2024 23:22:08 +0200 Subject: [PATCH 64/98] Unify utils/ --- .../include/utils/crypto.h | 42 ++--- .../include/utils/disk.h | 25 ++- shufflecake-userland-lite/include/utils/dm.h | 6 +- .../include/utils/file.h | 2 +- .../include/utils/input.h | 6 +- shufflecake-userland-lite/include/utils/log.h | 80 +++++----- .../include/utils/{sflite.h => sflc.h} | 42 ++--- .../include/utils/string.h | 4 +- shufflecake-userland-lite/src/utils/crypto.c | 148 +++++++++--------- shufflecake-userland-lite/src/utils/disk.c | 56 +++---- shufflecake-userland-lite/src/utils/dm.c | 56 +++---- shufflecake-userland-lite/src/utils/file.c | 2 +- shufflecake-userland-lite/src/utils/input.c | 8 +- shufflecake-userland-lite/src/utils/string.c | 6 +- 14 files changed, 246 insertions(+), 237 deletions(-) rename shufflecake-userland-lite/include/utils/{sflite.h => sflc.h} (72%) diff --git a/shufflecake-userland-lite/include/utils/crypto.h b/shufflecake-userland-lite/include/utils/crypto.h index 84be972..8366134 100644 --- a/shufflecake-userland-lite/include/utils/crypto.h +++ b/shufflecake-userland-lite/include/utils/crypto.h @@ -33,7 +33,7 @@ #include #include -#include "utils/sflite.h" +#include "utils/sflc.h" /***************************************************** @@ -41,41 +41,41 @@ *****************************************************/ // Key length, for input into AES-CTR and AES-GCM, and for output from Argon -#define SFLITE_STANDARD_KEYLEN 32 /* bytes */ +#define SFLC_STANDARD_KEYLEN 32 /* bytes */ // Key length for AES-XTS -#define SFLITE_AESXTS_KEYLEN 64 /* bytes */ +#define SFLC_AESXTS_KEYLEN 64 /* bytes */ // IV length for AES-CTR -#define SFLITE_AESCTR_IVLEN 16 /* bytes */ +#define SFLC_AESCTR_IVLEN 16 /* bytes */ // IV length for AES-XTS -#define SFLITE_AESXTS_IVLEN 16 /* bytes */ +#define SFLC_AESXTS_IVLEN 16 /* bytes */ // IV length for AES-GCM -#define SFLITE_AESGCM_IVLEN 12 /* bytes */ +#define SFLC_AESGCM_IVLEN 12 /* bytes */ // IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM -#define SFLITE_AESGCM_PADDED_IVLEN 16 /* bytes */ +#define SFLC_AESGCM_PADDED_IVLEN 16 /* bytes */ // MAC length for AES-GCM -#define SFLITE_AESGCM_TAGLEN 16 /* bytes */ +#define SFLC_AESGCM_TAGLEN 16 /* bytes */ // Content of output plaintext upon MAC verification failure -#define SFLITE_AESGCM_POISON_PT 0xFF +#define SFLC_AESGCM_POISON_PT 0xFF /* Argon parameters */ // Argon salt length -#define SFLITE_ARGON_SALTLEN 16 /* bytes */ +#define SFLC_ARGON_SALTLEN 16 /* bytes */ // Argon memory parameter // We assume machines with at least 128 MiB available RAM, so 2^17 kiB -#define SFLITE_ARGON_M (1 << 17) /* kibibytes */ +#define SFLC_ARGON_M (1 << 17) /* kibibytes */ // Argon iterations count // We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) -#define SFLITE_ARGON_T 3 +#define SFLC_ARGON_T 3 // Argon parallelism parameter (recommended to be 2 * CPU cores) // We assume use even on single core devices -#define SFLITE_ARGON_P 2 +#define SFLC_ARGON_P 2 /***************************************************** @@ -83,26 +83,26 @@ *****************************************************/ /* Get slow, strong random bytes (suited for keys) */ -int sflite_rand_getStrongBytes(char *buf, size_t buflen); +int sflc_rand_getStrongBytes(char *buf, size_t buflen); /* Get fast, weak(er) random bytes (suited for IVs and padding) */ -int sflite_rand_getWeakBytes(char *buf, size_t buflen); +int sflc_rand_getWeakBytes(char *buf, size_t buflen); /* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ -int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ -int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); +int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); /* AES256-XTS encryption. Set ct = NULL for in-place. * The IV is intepreted as a little-endian "sector number" */ -int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); +int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); /* AES256-GCM encryption, does not touch the IV */ -int sflite_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); +int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); /* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ -int sflite_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); +int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); /* Compute Argon KDF */ -int sflite_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); +int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); #endif /* _UTILS_CRYPTO_H_ */ diff --git a/shufflecake-userland-lite/include/utils/disk.h b/shufflecake-userland-lite/include/utils/disk.h index f2cc8b5..56256c4 100644 --- a/shufflecake-userland-lite/include/utils/disk.h +++ b/shufflecake-userland-lite/include/utils/disk.h @@ -36,7 +36,7 @@ #include #include "utils/math.h" -#include "utils/sflite.h" +#include "utils/sflc.h" /***************************************************** @@ -44,7 +44,7 @@ *****************************************************/ /** - * Max slices for given disk size (in 4096-byte blocks). + * Max slices for given disk size (in 4096-byte blocks). LITE version */ static inline uint32_t sflite_disk_maxSlices(uint64_t size) { uint64_t nr_slices; @@ -67,28 +67,37 @@ static inline uint32_t sflite_disk_maxSlices(uint64_t size) { return nr_slices > SFLITE_MAX_SLICES ? SFLITE_MAX_SLICES : (uint32_t) nr_slices; } +/* LEGACY version */ +#define sfold_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLC_BLOCKS_PER_PHYS_SLICE) +/* We need this inequality to hold, in order for the previous bound to be true */ +#if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK +#error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" +#endif + + + /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ /* Returns a malloc'ed string formatted as : */ -char *sflite_disk_getDeviceName(char *bdev_path); +char *sflc_disk_getDeviceName(char *bdev_path); /* Checks whether the given path points to a block device */ -bool sflite_disk_isBlockDevice(char *path); +bool sflc_disk_isBlockDevice(char *path); /* Returns the size in 4096-byte sectors (or < 0 if error) */ -int64_t sflite_disk_getSize(char *bdev_path); +int64_t sflc_disk_getSize(char *bdev_path); /* Reads a single 4096-byte block from the disk */ -int sflite_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); +int sflc_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); /* Writes many 4096-byte blocks to the disk */ -int sflite_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); +int sflc_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); /* Writes a single 4096-byte block to the disk */ -#define sflite_disk_writeBlock(bdev_path, bnum, buf) sflite_disk_writeManyBlocks(bdev_path, bnum, buf, 1) +#define sflc_disk_writeBlock(bdev_path, bnum, buf) sflite_disk_writeManyBlocks(bdev_path, bnum, buf, 1) #endif /* _UTILS_DISK_H_ */ diff --git a/shufflecake-userland-lite/include/utils/dm.h b/shufflecake-userland-lite/include/utils/dm.h index ba27afb..3334852 100644 --- a/shufflecake-userland-lite/include/utils/dm.h +++ b/shufflecake-userland-lite/include/utils/dm.h @@ -35,7 +35,7 @@ #include -#include "utils/sflite.h" +#include "utils/sflc.h" /***************************************************** @@ -48,9 +48,9 @@ *****************************************************/ /* Create a new Shufflecake virtual device (volume) under /dev/mapper */ -int sflite_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); +int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); /* Destroy the virtual device under /dev/mapper */ -int sflite_dm_destroy(char * virt_dev_name); +int sflc_dm_destroy(char * virt_dev_name); #endif /* _UTILS_DM_H_ */ diff --git a/shufflecake-userland-lite/include/utils/file.h b/shufflecake-userland-lite/include/utils/file.h index 2346ee6..fb03078 100644 --- a/shufflecake-userland-lite/include/utils/file.h +++ b/shufflecake-userland-lite/include/utils/file.h @@ -30,7 +30,7 @@ *****************************************************/ /* Malloc's the buffer for the file contents */ -char *sflite_readFile(char *path); +char *sflc_readFile(char *path); #endif /* _UTILS_FILE_H_ */ diff --git a/shufflecake-userland-lite/include/utils/input.h b/shufflecake-userland-lite/include/utils/input.h index 5c54850..52e07fb 100644 --- a/shufflecake-userland-lite/include/utils/input.h +++ b/shufflecake-userland-lite/include/utils/input.h @@ -30,7 +30,7 @@ *****************************************************/ /* Clear a line from stdin, to use after a failed scanf (it didn't actually read input) */ -#define sflite_ignoreLine() scanf("%*[^\n]") +#define sflc_ignoreLine() scanf("%*[^\n]") /***************************************************** @@ -38,9 +38,9 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflite_safeReadLine(char *buf, size_t bufsize); +int sflc_safeReadLine(char *buf, size_t bufsize); /* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ -int sflite_safeReadPassphrase(char *buf, size_t bufsize); +int sflc_safeReadPassphrase(char *buf, size_t bufsize); #endif /* _UTILS_INPUT_H_ */ diff --git a/shufflecake-userland-lite/include/utils/log.h b/shufflecake-userland-lite/include/utils/log.h index 5b15ea5..0fd35d7 100644 --- a/shufflecake-userland-lite/include/utils/log.h +++ b/shufflecake-userland-lite/include/utils/log.h @@ -37,29 +37,29 @@ *****************************************************/ // Printf colours (regular text) -#define SFLITE_LOG_BLK "\033[0;30m" -#define SFLITE_LOG_RED "\033[0;31m" -#define SFLITE_LOG_GRN "\033[0;32m" -#define SFLITE_LOG_YEL "\033[0;33m" -#define SFLITE_LOG_BLU "\033[0;34m" -#define SFLITE_LOG_MAG "\033[0;35m" -#define SFLITE_LOG_CYN "\033[0;36m" -#define SFLITE_LOG_WHT "\033[0;37m" +#define SFLC_LOG_BLK "\033[0;30m" +#define SFLC_LOG_RED "\033[0;31m" +#define SFLC_LOG_GRN "\033[0;32m" +#define SFLC_LOG_YEL "\033[0;33m" +#define SFLC_LOG_BLU "\033[0;34m" +#define SFLC_LOG_MAG "\033[0;35m" +#define SFLC_LOG_CYN "\033[0;36m" +#define SFLC_LOG_WHT "\033[0;37m" // Printf colours (bold text) -#define SFLITE_LOG_BBLK "\033[1;30m" -#define SFLITE_LOG_BRED "\033[1;31m" -#define SFLITE_LOG_BGRN "\033[1;32m" -#define SFLITE_LOG_BYEL "\033[1;33m" -#define SFLITE_LOG_BBLU "\033[1;34m" -#define SFLITE_LOG_BMAG "\033[1;35m" -#define SFLITE_LOG_BCYN "\033[1;36m" -#define SFLITE_LOG_BWHT "\033[1;37m" +#define SFLC_LOG_BBLK "\033[1;30m" +#define SFLC_LOG_BRED "\033[1;31m" +#define SFLC_LOG_BGRN "\033[1;32m" +#define SFLC_LOG_BYEL "\033[1;33m" +#define SFLC_LOG_BBLU "\033[1;34m" +#define SFLC_LOG_BMAG "\033[1;35m" +#define SFLC_LOG_BCYN "\033[1;36m" +#define SFLC_LOG_BWHT "\033[1;37m" // Reset colour -#define SFLITE_LOG_RESET "\033[0m" +#define SFLC_LOG_RESET "\033[0m" // Log level: debug implies detailed logs -#ifdef CONFIG_SFLITE_LOG_DEBUG -#define CONFIG_SFLITE_LOG_DETAILED +#ifdef CONFIG_SFLC_LOG_DEBUG +#define CONFIG_SFLC_LOG_DETAILED #endif @@ -68,42 +68,42 @@ *****************************************************/ // Gives the point in the code where it was called -#define sflite_log_detailed(col, ...) do{ \ - printf(SFLITE_LOG_GRN "FUNC " SFLITE_LOG_RESET "%s() " \ - SFLITE_LOG_GRN "FILE " SFLITE_LOG_RESET "%s " \ - SFLITE_LOG_GRN "LINE " SFLITE_LOG_RESET "%d | ", \ +#define sflc_log_detailed(col, ...) do{ \ + printf(SFLC_LOG_GRN "FUNC " SFLC_LOG_RESET "%s() " \ + SFLC_LOG_GRN "FILE " SFLC_LOG_RESET "%s " \ + SFLC_LOG_GRN "LINE " SFLC_LOG_RESET "%d | ", \ __func__, __FILE__, __LINE__); \ - sflite_log_concise(col, __VA_ARGS__); \ + sflc_log_concise(col, __VA_ARGS__); \ }while(0) // Only writes using the given colour -#define sflite_log_concise(col, ...) do{ \ +#define sflc_log_concise(col, ...) do{ \ printf(col); \ printf(__VA_ARGS__); \ - printf(SFLITE_LOG_RESET "\n"); \ + printf(SFLC_LOG_RESET "\n"); \ }while(0) // Maps to one or the other, based on a Makefile switch -#ifdef CONFIG_SFLITE_LOG_DETAILED - #define sflite_log_colour(...) sflite_log_detailed(__VA_ARGS__) +#ifdef CONFIG_SFLC_LOG_DETAILED + #define sflc_log_colour(...) sflc_log_detailed(__VA_ARGS__) #else - #define sflite_log_colour(...) sflite_log_concise(__VA_ARGS__) + #define sflc_log_colour(...) sflc_log_concise(__VA_ARGS__) #endif // Using specific colours -#define sflite_log_green(...) sflite_log_colour(SFLITE_LOG_GRN, __VA_ARGS__) -#define sflite_log_red(...) sflite_log_colour(SFLITE_LOG_RED, __VA_ARGS__) -#define sflite_log_yellow(...) sflite_log_colour(SFLITE_LOG_YEL, __VA_ARGS__) -#define sflite_log_blue(...) sflite_log_colour(SFLITE_LOG_BLU, __VA_ARGS__) -#define sflite_log_normal(...) sflite_log_colour(SFLITE_LOG_RESET, __VA_ARGS__) +#define sflc_log_green(...) sflc_log_colour(SFLC_LOG_GRN, __VA_ARGS__) +#define sflc_log_red(...) sflc_log_colour(SFLC_LOG_RED, __VA_ARGS__) +#define sflc_log_yellow(...) sflc_log_colour(SFLC_LOG_YEL, __VA_ARGS__) +#define sflc_log_blue(...) sflc_log_colour(SFLC_LOG_BLU, __VA_ARGS__) +#define sflc_log_normal(...) sflc_log_colour(SFLC_LOG_RESET, __VA_ARGS__) // With log levels -#define sflite_log_error(...) sflite_log_colour(SFLITE_LOG_RED, "[ERROR] " __VA_ARGS__) -#define sflite_log_warn(...) sflite_log_colour(SFLITE_LOG_MAG, "[WARN] " __VA_ARGS__) -#ifdef CONFIG_SFLITE_LOG_DEBUG - #define sflite_log_debug(...) sflite_log_colour(SFLITE_LOG_CYN, "[DEBUG] " __VA_ARGS__) +#define sflc_log_error(...) sflc_log_colour(SFLC_LOG_RED, "[ERROR] " __VA_ARGS__) +#define sflc_log_warn(...) sflc_log_colour(SFLC_LOG_MAG, "[WARN] " __VA_ARGS__) +#ifdef CONFIG_SFLC_LOG_DEBUG + #define sflc_log_debug(...) sflc_log_colour(SFLC_LOG_CYN, "[DEBUG] " __VA_ARGS__) #else - #define sflite_log_debug(...) + #define sflc_log_debug(...) #endif @@ -112,7 +112,7 @@ *****************************************************/ // Log a hex string -static inline void sflite_log_hex(char *str, size_t len) +static inline void sflc_log_hex(char *str, size_t len) { int i; unsigned char *s = (unsigned char *) str; diff --git a/shufflecake-userland-lite/include/utils/sflite.h b/shufflecake-userland-lite/include/utils/sflc.h similarity index 72% rename from shufflecake-userland-lite/include/utils/sflite.h rename to shufflecake-userland-lite/include/utils/sflc.h index 1dcd963..a43efba 100644 --- a/shufflecake-userland-lite/include/utils/sflite.h +++ b/shufflecake-userland-lite/include/utils/sflc.h @@ -22,11 +22,11 @@ */ /* - * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflite_constans.h + * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflc_constans.h */ -#ifndef _UTILS_SFLITE_H_ -#define _UTILS_SFLITE_H_ +#ifndef _UTILS_SFLC_H_ +#define _UTILS_SFLC_H_ /***************************************************** @@ -48,42 +48,42 @@ /* Sizes */ #define KERNEL_SECTOR_SIZE 512 /* bytes */ -#define SFLITE_BLOCK_SIZE 4096 /* bytes */ -#define SFLITE_BLOCK_SCALE (SFLITE_BLOCK_SIZE / KERNEL_SECTOR_SIZE) -#define SFLITE_SLICE_SCALE 256 /* blocks in a slice */ -#define SFLITE_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ +#define SFLC_BLOCK_SIZE 4096 /* bytes */ +#define SFLC_BLOCK_SCALE (SFLC_BLOCK_SIZE / KERNEL_SECTOR_SIZE) +#define SFLC_SLICE_SCALE 256 /* blocks in a slice */ +#define SFLC_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ /* Max number of volumes in a device */ -#define SFLITE_DEV_MAX_VOLUMES 15 +#define SFLC_DEV_MAX_VOLUMES 15 /* Max total number of open devices at any given time */ -#define SFLITE_TOT_MAX_DEVICES 1024 -/* A volume name is sflite__ */ -#define SFLITE_MAX_VOL_NAME_LEN 15 +#define SFLC_TOT_MAX_DEVICES 1024 +/* A volume name is sflc__ */ +#define SFLC_MAX_VOL_NAME_LEN 15 /* A slice index is represented over 32 bits */ -#define SFLITE_SLICE_IDX_WIDTH 4 /* bytes */ +#define SFLC_SLICE_IDX_WIDTH 4 /* bytes */ /* A position map block contains 1024 slice indices */ -#define SFLITE_SLICE_IDX_PER_BLOCK (SFLITE_BLOCK_SIZE / SFLITE_SLICE_IDX_WIDTH) +#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_BLOCK_SIZE / SFLC_SLICE_IDX_WIDTH) /* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ -#define SFLITE_EPM_FILLER 0xFF +#define SFLC_EPM_FILLER 0xFF /* The sysfs file containing the next available device ID */ -#define SFLITE_SYSFS_NEXTDEVID "/sys/module/dm_sflc/next_dev_id" +#define SFLC_SYSFS_NEXTDEVID "/sys/module/dm_sflc/next_dev_id" /* The sysfs directory containing a subdir for each (underlying) block device */ -#define SFLITE_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" +#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" /* Within each bdev's subdir, this file shows its number of open volumes */ -#define SFLITE_SYSFS_OPENVOLUMES_FILENAME "volumes" +#define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" /* Within each bdev's subdir, this file shows its Shufflecake device ID */ -#define SFLITE_SYSFS_DEVID_FILENAME "dev_id" +#define SFLC_SYSFS_DEVID_FILENAME "dev_id" /* TODO: reasonable? */ -#define SFLITE_BDEV_PATH_MAX_LEN 1024 +#define SFLC_BDEV_PATH_MAX_LEN 1024 /* For when you can't be bothered to upper-bound a buffer size */ -#define SFLITE_BIGBUFSIZE 4096 +#define SFLC_BIGBUFSIZE 4096 -#endif /* _UTILS_SFLITE_H_ */ +#endif /* _UTILS_SFLC_H_ */ diff --git a/shufflecake-userland-lite/include/utils/string.h b/shufflecake-userland-lite/include/utils/string.h index 552ed82..ca42c0e 100644 --- a/shufflecake-userland-lite/include/utils/string.h +++ b/shufflecake-userland-lite/include/utils/string.h @@ -30,10 +30,10 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *sflite_toHex(char *buf, size_t len); +char *sflc_toHex(char *buf, size_t len); /* Replaces all occurrences of character in-place */ -void sflite_str_replaceAll(char *str, char old, char new); +void sflc_str_replaceAll(char *str, char old, char new); #endif /* _UTILS_STRING_H_ */ diff --git a/shufflecake-userland-lite/src/utils/crypto.c b/shufflecake-userland-lite/src/utils/crypto.c index d7331e1..4494dd1 100644 --- a/shufflecake-userland-lite/src/utils/crypto.c +++ b/shufflecake-userland-lite/src/utils/crypto.c @@ -44,7 +44,7 @@ * *@return The error code (0 on success) */ -int sflite_rand_getStrongBytes(char *buf, size_t buflen) +int sflc_rand_getStrongBytes(char *buf, size_t buflen) { gcry_randomize(buf, buflen, GCRY_VERY_STRONG_RANDOM); return 0; @@ -61,7 +61,7 @@ int sflite_rand_getStrongBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int sflite_rand_getWeakBytes(char *buf, size_t buflen) +int sflc_rand_getWeakBytes(char *buf, size_t buflen) { gcry_create_nonce(buf, buflen); return 0; @@ -81,7 +81,7 @@ int sflite_rand_getWeakBytes(char *buf, size_t buflen) * *@return The error code (0 on success) */ -int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -89,26 +89,26 @@ int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - sflite_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully instantiated AES256-CTR cipher handle"); + sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { - sflite_log_error("Could not set AES key: error %d", err); + sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflite_log_debug("Successfully set the AES key"); + sflc_log_debug("Successfully set the AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLITE_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); if (err) { - sflite_log_error("Could not set AES-CTR IV: error %d", err); + sflc_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - sflite_log_debug("Successfully set the IV"); + sflc_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -119,10 +119,10 @@ int sflite_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char } // Error check if (err) { - sflite_log_error("Could not encrypt: error %d", err); + sflc_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflite_log_debug("Successfully encrypted"); + sflc_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -149,7 +149,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) +int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -157,26 +157,26 @@ int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); if (err) { - sflite_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); + sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully instantiated AES256-CTR cipher handle"); + sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { - sflite_log_error("Could not set AES key: error %d", err); + sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflite_log_debug("Successfully set AES key"); + sflc_log_debug("Successfully set AES key"); // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLITE_AESCTR_IVLEN); + err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); if (err) { - sflite_log_error("Could not set AES-CTR IV: error %d", err); + sflc_log_error("Could not set AES-CTR IV: error %d", err); goto bad_setctr; } - sflite_log_debug("Successfully set IV"); + sflc_log_debug("Successfully set IV"); // Decrypt if (pt == NULL) { // In-place @@ -187,10 +187,10 @@ int sflite_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char } // Error check if (err) { - sflite_log_error("Could not decrypt: error %d", err); + sflc_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - sflite_log_debug("Successfully decrypted"); + sflc_log_debug("Successfully decrypted"); // No prob err = 0; @@ -219,7 +219,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) +int sflc_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -227,26 +227,26 @@ int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_XTS, GCRY_CIPHER_SECURE); if (err) { - sflite_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); + sflc_log_error("Could not instantiate AES256-XTS cipher handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully instantiated AES256-XTS cipher handle"); + sflc_log_debug("Successfully instantiated AES256-XTS cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLITE_AESXTS_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_AESXTS_KEYLEN); if (err) { - sflite_log_error("Could not set AES-XTS key: error %d", err); + sflc_log_error("Could not set AES-XTS key: error %d", err); goto bad_setkey; } - sflite_log_debug("Successfully set the AES-XTS key"); + sflc_log_debug("Successfully set the AES-XTS key"); // Set the IV (not a counter, as per Gcrypt docs) - err = gcry_cipher_setiv(hd, iv, SFLITE_AESXTS_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLC_AESXTS_IVLEN); if (err) { - sflite_log_error("Could not set AES-XTS IV: error %d", err); + sflc_log_error("Could not set AES-XTS IV: error %d", err); goto bad_setiv; } - sflite_log_debug("Successfully set the IV"); + sflc_log_debug("Successfully set the IV"); // Encrypt if (ct == NULL) { // In-place @@ -257,10 +257,10 @@ int sflite_aes256xts_encrypt(char *key, char *pt, size_t pt_len, char *iv, char } // Error check if (err) { - sflite_log_error("Could not encrypt: error %d", err); + sflc_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflite_log_debug("Successfully encrypted"); + sflc_log_debug("Successfully encrypted"); // No prob? err = 0; @@ -289,7 +289,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflite_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) +int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -297,42 +297,42 @@ int sflite_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - sflite_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully instantiated AES256-GCM cipher handle"); + sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { - sflite_log_error("Could not set AES key: error %d", err); + sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflite_log_debug("Successfully set the AES key"); + sflc_log_debug("Successfully set the AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLITE_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); if (err) { - sflite_log_error("Could not set AES-GCM IV: error %d", err); + sflc_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - sflite_log_debug("Successfully set the IV"); + sflc_log_debug("Successfully set the IV"); // Encrypt err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); if (err) { - sflite_log_error("Could not encrypt: error %d", err); + sflc_log_error("Could not encrypt: error %d", err); goto bad_encrypt; } - sflite_log_debug("Successfully encrypted"); + sflc_log_debug("Successfully encrypted"); // Get MAC - err = gcry_cipher_gettag(hd, tag, SFLITE_AESGCM_TAGLEN); + err = gcry_cipher_gettag(hd, tag, SFLC_AESGCM_TAGLEN); if (err) { - sflite_log_error("Could not get MAC: error %d", err); + sflc_log_error("Could not get MAC: error %d", err); goto bad_gettag; } - sflite_log_debug("Successfully gotten MAC"); + sflc_log_debug("Successfully gotten MAC"); // No prob? err = 0; @@ -364,7 +364,7 @@ bad_open: * *@return The error code (0 on success) */ -int sflite_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) +int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) { gcry_cipher_hd_t hd; gcry_error_t err; @@ -372,52 +372,52 @@ int sflite_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char // Instantiate the handle err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); if (err) { - sflite_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); + sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully instantiated AES256-GCM cipher handle"); + sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); // Set the key - err = gcry_cipher_setkey(hd, key, SFLITE_STANDARD_KEYLEN); + err = gcry_cipher_setkey(hd, key, SFLC_STANDARD_KEYLEN); if (err) { - sflite_log_error("Could not set AES key: error %d", err); + sflc_log_error("Could not set AES key: error %d", err); goto bad_setkey; } - sflite_log_debug("Successfully set AES key"); + sflc_log_debug("Successfully set AES key"); // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLITE_AESGCM_IVLEN); + err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); if (err) { - sflite_log_error("Could not set AES-GCM IV: error %d", err); + sflc_log_error("Could not set AES-GCM IV: error %d", err); goto bad_setiv; } - sflite_log_debug("Successfully set IV"); + sflc_log_debug("Successfully set IV"); // Decrypt err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); if (err) { - sflite_log_error("Could not decrypt: error %d", err); + sflc_log_error("Could not decrypt: error %d", err); goto bad_decrypt; } - sflite_log_debug("Successfully decrypted"); + sflc_log_debug("Successfully decrypted"); // Check MAC - err = gcry_cipher_checktag(hd, tag, SFLITE_AESGCM_TAGLEN); + err = gcry_cipher_checktag(hd, tag, SFLC_AESGCM_TAGLEN); if (gcry_err_code(err) == GPG_ERR_CHECKSUM) { // Undo decryption - memset(pt, SFLITE_AESGCM_POISON_PT, ct_len); + memset(pt, SFLC_AESGCM_POISON_PT, ct_len); // Flag it *match = false; } else if (err) { - sflite_log_error("Could not check MAC: error %d", err); + sflc_log_error("Could not check MAC: error %d", err); goto bad_checktag; } else { // Flag MAC verification success *match = true; } - sflite_log_debug("Successfully checked MAC: match = %d", *match); + sflc_log_debug("Successfully checked MAC: match = %d", *match); // No prob, whether MAC verified or not err = 0; @@ -444,11 +444,11 @@ bad_open: * * @return The error code (0 on success) */ -int sflite_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) +int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) { gcry_kdf_hd_t hd; const unsigned long argon_params[4] = - {SFLITE_STANDARD_KEYLEN, SFLITE_ARGON_T, SFLITE_ARGON_M, SFLITE_ARGON_P}; + {SFLC_STANDARD_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; gcry_error_t err; // Instantiate Argon2id handle @@ -456,31 +456,31 @@ int sflite_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) &hd, GCRY_KDF_ARGON2, GCRY_KDF_ARGON2ID, argon_params, 4, pwd, pwd_len, - salt, SFLITE_ARGON_SALTLEN, + salt, SFLC_ARGON_SALTLEN, NULL, 0, /* Optional secret value K */ NULL, 0 /* Optional associated data X */ ); if (err) { - sflite_log_error("Could not open Argon2id handle: error %d", err); + sflc_log_error("Could not open Argon2id handle: error %d", err); goto bad_open; } - sflite_log_debug("Successfully opened Argon2id handle"); + sflc_log_debug("Successfully opened Argon2id handle"); // Run the computation err = gcry_kdf_compute(hd, NULL); if (err) { - sflite_log_error("Could not run Argon2id computation: error %d", err); + sflc_log_error("Could not run Argon2id computation: error %d", err); goto bad_compute; } - sflite_log_debug("Successfully run Argon2id computation"); + sflc_log_debug("Successfully run Argon2id computation"); // Finalise hash - err = gcry_kdf_final(hd, SFLITE_STANDARD_KEYLEN, hash); + err = gcry_kdf_final(hd, SFLC_STANDARD_KEYLEN, hash); if (err) { - sflite_log_error("Could not finalise Argon2id hash: error %d", err); + sflc_log_error("Could not finalise Argon2id hash: error %d", err); goto bad_final; } - sflite_log_debug("Successfully finalised Argon2id hash"); + sflc_log_debug("Successfully finalised Argon2id hash"); // All in order err = 0; diff --git a/shufflecake-userland-lite/src/utils/disk.c b/shufflecake-userland-lite/src/utils/disk.c index 79f2be2..7da8c86 100644 --- a/shufflecake-userland-lite/src/utils/disk.c +++ b/shufflecake-userland-lite/src/utils/disk.c @@ -56,14 +56,14 @@ * * @return A string version of the device ID */ -char *sflite_disk_getDeviceName(char *bdev_path) +char *sflc_disk_getDeviceName(char *bdev_path) { struct stat sb; char *dev_name; if (stat(bdev_path, &sb) != 0) return NULL; - dev_name = malloc(SFLITE_BIGBUFSIZE); + dev_name = malloc(SFLC_BIGBUFSIZE); if (!dev_name) return NULL; @@ -80,7 +80,7 @@ char *sflite_disk_getDeviceName(char *bdev_path) * * @return true iff it's a block device */ -bool sflite_disk_isBlockDevice(char *path) +bool sflc_disk_isBlockDevice(char *path) { struct stat path_stat; if (stat(path, &path_stat) != 0) { @@ -96,7 +96,7 @@ bool sflite_disk_isBlockDevice(char *path) * * @return The size (in 4096-byte sectors) of the disk, or -errno if error */ -int64_t sflite_disk_getSize(char * bdev_path) +int64_t sflc_disk_getSize(char * bdev_path) { int fd; uint64_t size_bytes; @@ -105,24 +105,24 @@ int64_t sflite_disk_getSize(char * bdev_path) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - sflite_log_error("Could not open file %s", bdev_path); + sflc_log_error("Could not open file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_open; } - sflite_log_debug("Opened file %s", bdev_path); + sflc_log_debug("Opened file %s", bdev_path); /* Get size in bytes */ if (ioctl(fd, BLKGETSIZE64, &size_bytes) < 0) { - sflite_log_error("Could not ioctl file %s", bdev_path); + sflc_log_error("Could not ioctl file %s", bdev_path); perror("Cause: "); ret = -errno; goto bad_ioctl; } - sflite_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); + sflc_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); - /* Compute size in SFLITE sectors */ - ret = (size_bytes / SFLITE_BLOCK_SIZE); + /* Compute size in SFLC sectors */ + ret = (size_bytes / SFLC_BLOCK_SIZE); bad_ioctl: close(fd); @@ -140,7 +140,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflite_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) +int sflc_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) { int fd; int err; @@ -148,29 +148,29 @@ int sflite_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Open file */ fd = open(bdev_path, O_RDONLY); if (fd < 0) { - sflite_log_error("Could not open file %s", bdev_path); + sflc_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - sflite_log_debug("Opened file %s", bdev_path); + sflc_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLITE_BLOCK_SIZE, SEEK_SET) < 0) { - sflite_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { + sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - sflite_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Read in a loop */ - size_t bytes_to_read = SFLITE_BLOCK_SIZE; + size_t bytes_to_read = SFLC_BLOCK_SIZE; while (bytes_to_read > 0) { /* Read syscall */ ssize_t bytes_read = read(fd, buf, bytes_to_read); if (bytes_read < 0) { - sflite_log_error("Could not read file %s at sector %lu", bdev_path, sector); + sflc_log_error("Could not read file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_read; @@ -178,7 +178,7 @@ int sflite_disk_readBlock(char * bdev_path, uint64_t sector, char * buf) /* Partial read? No problem just log */ if (bytes_read < bytes_to_read) { - sflite_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", + sflc_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_read, bytes_to_read); } @@ -209,7 +209,7 @@ bad_open: * * @return The error code (0 on success) */ -int sflite_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) +int sflc_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) { int fd; int err; @@ -217,29 +217,29 @@ int sflite_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, s /* Open file */ fd = open(bdev_path, O_WRONLY); if (fd < 0) { - sflite_log_error("Could not open file %s", bdev_path); + sflc_log_error("Could not open file %s", bdev_path); perror("Cause: "); err = errno; goto bad_open; } - sflite_log_debug("Opened file %s", bdev_path); + sflc_log_debug("Opened file %s", bdev_path); /* Set offset in bytes */ - if (lseek(fd, sector * SFLITE_BLOCK_SIZE, SEEK_SET) < 0) { - sflite_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); + if (lseek(fd, sector * SFLC_BLOCK_SIZE, SEEK_SET) < 0) { + sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_lseek; } - sflite_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); + sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); /* Write in a loop */ - size_t bytes_to_write = SFLITE_BLOCK_SIZE * num_sectors; + size_t bytes_to_write = SFLC_BLOCK_SIZE * num_sectors; while (bytes_to_write > 0) { /* Write syscall */ ssize_t bytes_written = write(fd, buf, bytes_to_write); if (bytes_written < 0) { - sflite_log_red("Could not write file %s at sector %lu", bdev_path, sector); + sflc_log_red("Could not write file %s at sector %lu", bdev_path, sector); perror("Cause: "); err = errno; goto bad_write; @@ -247,7 +247,7 @@ int sflite_disk_writeManyBlocks(char * bdev_path, uint64_t sector, char * buf, s /* Partial write? No problem just log */ if (bytes_written < bytes_to_write) { - sflite_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", + sflc_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", bdev_path, sector, bytes_written, bytes_to_write); } diff --git a/shufflecake-userland-lite/src/utils/dm.c b/shufflecake-userland-lite/src/utils/dm.c index da6f259..275d61e 100644 --- a/shufflecake-userland-lite/src/utils/dm.c +++ b/shufflecake-userland-lite/src/utils/dm.c @@ -35,7 +35,7 @@ #include #include "utils/dm.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/log.h" @@ -54,7 +54,7 @@ * * @return The error code (0 on success) */ -int sflite_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) +int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) { struct dm_task *dmt; uint32_t cookie = 0; @@ -65,60 +65,60 @@ int sflite_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) char * dup_virt_dev_name = strdup(virt_dev_name); char * dup_params = strdup(params); - sflite_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); + sflc_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the CREATE ioctl command) */ if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { - sflite_log_error("Cannot create dm_task"); + sflc_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - sflite_log_debug("Successfully created dm_task"); + sflc_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be created) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflite_log_error("Cannot set device name"); + sflc_log_error("Cannot set device name"); err = 2; goto out; } - sflite_log_debug("Successfully set device name"); + sflc_log_debug("Successfully set device name"); /* State that it is a Shufflecake device, pass the start and size, and the * constructor parameters */ if (!dm_task_add_target(dmt, 0, num_sectors, SFLC_DM_TARGET_NAME, dup_params)) { - sflite_log_error("Cannot add DM target and parameters"); + sflc_log_error("Cannot add DM target and parameters"); err = 3; goto out; } - sflite_log_debug("Successfully added DM target and parameters"); + sflc_log_debug("Successfully added DM target and parameters"); /* Say that we want a new node under /dev/mapper */ if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { - sflite_log_error("Cannot add /dev/mapper node"); + sflc_log_error("Cannot add /dev/mapper node"); err = 4; goto out; } - sflite_log_debug("Successfully set the ADD_NODE flag"); + sflc_log_debug("Successfully set the ADD_NODE flag"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflite_log_error("Cannot get cookie"); + sflc_log_error("Cannot get cookie"); err = 5; goto out; } - sflite_log_debug("Successfully got a cookie"); + sflc_log_debug("Successfully got a cookie"); /* Run the task */ if (!dm_task_run(dmt)) { - sflite_log_error("Cannot issue ioctl"); + sflc_log_error("Cannot issue ioctl"); err = 6; goto out; } - sflite_log_debug("Successfully run DM task"); + sflc_log_debug("Successfully run DM task"); /* Wait for completion */ dm_udev_wait(cookie); - sflite_log_debug("Task completed"); + sflc_log_debug("Task completed"); // No prob err = 0; @@ -140,7 +140,7 @@ dup_free: * * @return error code (0 on success) */ -int sflite_dm_destroy(char * virt_dev_name) +int sflc_dm_destroy(char * virt_dev_name) { struct dm_task *dmt; uint32_t cookie = 0; @@ -150,47 +150,47 @@ int sflite_dm_destroy(char * virt_dev_name) /* Just to be sure, let's get it on the heap */ char * dup_virt_dev_name = strdup(virt_dev_name); - sflite_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); + sflc_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); /* Instantiate the DM task (with the REMOVE ioctl command) */ if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) { - sflite_log_error("Cannot create dm_task"); + sflc_log_error("Cannot create dm_task"); err = 1; goto dup_free; } - sflite_log_debug("Successfully created dm_task"); + sflc_log_debug("Successfully created dm_task"); /* Set the name of the target device (to be closed) */ if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflite_log_error("Cannot set device name"); + sflc_log_error("Cannot set device name"); err = 2; goto out; } - sflite_log_debug("Successfully set device name"); + sflc_log_debug("Successfully set device name"); /* Get a cookie (request ID, basically) to wait for task completion */ if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflite_log_error("Cannot set cookie"); + sflc_log_error("Cannot set cookie"); err = 3; goto out; } - sflite_log_debug("Successfully got a cookie"); + sflc_log_debug("Successfully got a cookie"); /* Needed for some reason */ dm_task_retry_remove(dmt); - sflite_log_debug("Successful retry_remove"); + sflc_log_debug("Successful retry_remove"); /* Run the task */ if (!dm_task_run(dmt)) { - sflite_log_error("Cannot issue ioctl"); + sflc_log_error("Cannot issue ioctl"); err = 4; goto out; } - sflite_log_debug("Successfully run task"); + sflc_log_debug("Successfully run task"); /* Wait for completion */ dm_udev_wait(cookie); - sflite_log_debug("Task completed"); + sflc_log_debug("Task completed"); // No prob err = 0; diff --git a/shufflecake-userland-lite/src/utils/file.c b/shufflecake-userland-lite/src/utils/file.c index 82c7472..11066b1 100644 --- a/shufflecake-userland-lite/src/utils/file.c +++ b/shufflecake-userland-lite/src/utils/file.c @@ -38,7 +38,7 @@ *****************************************************/ /* Reads the entire content of a file in a malloc-ed string */ -char *sflite_readFile(char *path) +char *sflc_readFile(char *path) { int filesize; FILE *fp; diff --git a/shufflecake-userland-lite/src/utils/input.c b/shufflecake-userland-lite/src/utils/input.c index 5a0e9ac..ffc0b02 100644 --- a/shufflecake-userland-lite/src/utils/input.c +++ b/shufflecake-userland-lite/src/utils/input.c @@ -41,13 +41,13 @@ *****************************************************/ /* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflite_safeReadLine(char *buf, size_t bufsize) +int sflc_safeReadLine(char *buf, size_t bufsize) { size_t len; /* Read from stdin */ if (fgets(buf, bufsize, stdin) == NULL) { - sflite_log_error("Could not read from stdin"); + sflc_log_error("Could not read from stdin"); return EBADFD; } @@ -62,7 +62,7 @@ int sflite_safeReadLine(char *buf, size_t bufsize) /* Reads a password/passphrase in a secure way (no echo) */ -int sflite_safeReadPassphrase(char *buf, size_t bufsize) +int sflc_safeReadPassphrase(char *buf, size_t bufsize) { size_t len; struct termios old, new; @@ -79,7 +79,7 @@ int sflite_safeReadPassphrase(char *buf, size_t bufsize) if (fgets(buf, bufsize, stdin) == NULL) { // If reading the password failed, ensure echoing is turned back on tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - sflite_log_error("Could not read from stdin"); + sflc_log_error("Could not read from stdin"); return EBADFD; } diff --git a/shufflecake-userland-lite/src/utils/string.c b/shufflecake-userland-lite/src/utils/string.c index 86d735f..c6cf998 100644 --- a/shufflecake-userland-lite/src/utils/string.c +++ b/shufflecake-userland-lite/src/utils/string.c @@ -38,7 +38,7 @@ *****************************************************/ /* Malloc's the buffer for the hex string */ -char *sflite_toHex(char *buf, size_t len) +char *sflc_toHex(char *buf, size_t len) { unsigned char *u = (unsigned char *) buf; char *hex; @@ -46,7 +46,7 @@ char *sflite_toHex(char *buf, size_t len) /* Allocate buffer */ hex = malloc((len * 2) + 1); if (!hex) { - sflite_log_error("Could not allocate buffer for hex string"); + sflc_log_error("Could not allocate buffer for hex string"); return NULL; } @@ -61,7 +61,7 @@ char *sflite_toHex(char *buf, size_t len) } -void sflite_str_replaceAll(char * str, char old, char new) +void sflc_str_replaceAll(char * str, char old, char new) { int i; for (i = 0; str[i] != '\0'; i++) { From cbf035905c2fdd761f539d3db287eb1b919c6874 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 22 Aug 2024 23:57:44 +0200 Subject: [PATCH 65/98] Merge header/ --- shufflecake-userland-lite/include/header.h | 87 +++++-- .../src/header/device_master_block.c | 90 +++---- .../src/header/position_map_legacy.c | 134 +++++++++++ .../{position_map.c => position_map_lite.c} | 18 +- ...r_block.c => volume_master_block_legacy.c} | 72 +++--- .../src/header/volume_master_block_lite.c | 223 ++++++++++++++++++ 6 files changed, 510 insertions(+), 114 deletions(-) create mode 100644 shufflecake-userland-lite/src/header/position_map_legacy.c rename shufflecake-userland-lite/src/header/{position_map.c => position_map_lite.c} (88%) rename shufflecake-userland-lite/src/header/{volume_master_block.c => volume_master_block_legacy.c} (67%) create mode 100644 shufflecake-userland-lite/src/header/volume_master_block_lite.c diff --git a/shufflecake-userland-lite/include/header.h b/shufflecake-userland-lite/include/header.h index 469fe8b..1062a21 100644 --- a/shufflecake-userland-lite/include/header.h +++ b/shufflecake-userland-lite/include/header.h @@ -41,18 +41,18 @@ *****************************************************/ /* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define SFLITE_DMB_CELL_SIZE (SFLITE_AESGCM_PADDED_IVLEN + SFLITE_STANDARD_KEYLEN + SFLITE_AESGCM_TAGLEN) +#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_STANDARD_KEYLEN + SFLC_AESGCM_TAGLEN) /* Let us enforce that the one DMB can fit cells for all volumes */ -#if SFLITE_ARGON_SALTLEN + (SFLITE_DEV_MAX_VOLUMES * SFLITE_DMB_CELL_SIZE) > SFLITE_BLOCK_SIZE +#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL_SIZE) > SFLC_BLOCK_SIZE #error "Invalid combination of parameters: probably SFLITE_DEV_MAX_VOLUMES is too big" #endif // The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define SFLITE_CLEAR_VMB_LEN (SFLITE_BLOCK_SIZE - \ - SFLITE_AESGCM_PADDED_IVLEN - \ - SFLITE_AESGCM_TAGLEN) +#define SFLC_CLEAR_VMB_LEN (SFLC_BLOCK_SIZE - \ + SFLC_AESGCM_PADDED_IVLEN - \ + SFLC_AESGCM_TAGLEN) @@ -67,13 +67,12 @@ */ typedef struct { // Each volume's VMB key - char vmb_keys[SFLITE_DEV_MAX_VOLUMES][SFLITE_STANDARD_KEYLEN]; + char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_STANDARD_KEYLEN]; // How many of these need actually be encrypted size_t nr_vols; -} sflite_Dmb; - +} sflc_Dmb; /** * When unsealing a DMB, only one VMB key can be unlocked with a password. @@ -81,12 +80,12 @@ typedef struct { */ typedef struct { // The unlocked VMB key - char vmb_key[SFLITE_STANDARD_KEYLEN]; + char vmb_key[SFLC_STANDARD_KEYLEN]; // The index of the volume opened by this VMB key size_t vol_idx; -} sflite_DmbCell; +} sflc_DmbCell; /** @@ -95,16 +94,49 @@ typedef struct { * only contains the useful info, in the clear. */ typedef struct { + int mode; + // The key that encrypts the volume's data section - char volume_key[SFLITE_AESXTS_KEYLEN]; + union { + char volume_key_lite[SFLC_AESXTS_KEYLEN]; + char volume_key_legacy[SFLC_STANDARD_KEYLEN]; + }; // The key that encrypts the previous volume's master block - char prev_vmb_key[SFLITE_STANDARD_KEYLEN]; + char prev_vmb_key[SFLC_STANDARD_KEYLEN]; // The total number of logical slices virtually available to this volume size_t nr_slices; -} sflite_Vmb; +} sflc_Vmb; + +/** + * This struct represents an encrypted empty position map. LEGACY version. + * On-disk, the layout interleaves one IV block with 256 PosMap blocks (each + * encrypted by an IV in the IV block). Many such "runs" can be concatenated, + * until the position map is big enough to index the desired number of slices. + * The last "run" might be incomplete, in that it could have less than 256 + * PosMap blocks, if not all of them are needed. + * In the struct, there are as many IV blocks as there are PosMapBlock arrays + * (equal to the number of "runs"). The m-th IV of the n-th IV block encrypts + * the m-th block of the n-th array. The PosMapBlocks in an array are stored + * contiguously in RAM, so a PosMapBlock array is just a char array of length + * multiple of 4096. All the arrays are full (256 PosMapBlocks, 1 MiB) except + * for the last one, which may hold fewer blocks. + */ +typedef struct { + // The number of PosMapBlock arrays (and of IV blocks) + size_t nr_arrays; + + // The sequence of IV blocks + char **iv_blocks; + // The sequence of (encrypted) PosMapBlock arrays + char **pmb_arrays; + + // The number of PosMapBlocks in the last array + size_t nr_last_pmbs; + +} sfold_EncPosMap; /***************************************************** @@ -112,12 +144,12 @@ typedef struct { *****************************************************/ -// Starting block of a volume's position map +// Starting block of a volume's position map. LITE version static inline uint64_t sflite_pmStartBlock(size_t vol_idx, size_t nr_slices) { return 1 + - SFLITE_DEV_MAX_VOLUMES + - vol_idx*ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); + SFLC_DEV_MAX_VOLUMES + + vol_idx*ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); } @@ -126,22 +158,29 @@ static inline uint64_t sflite_pmStartBlock(size_t vol_idx, size_t nr_slices) *****************************************************/ /* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ -int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); /* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ -int sflite_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell); +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); /* Re-encrypt the content of a single DMB cell */ -int sflite_dmb_setCell(char *disk_block, sflite_DmbCell *dmb_cell, char *pwd, size_t pwd_len); +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len); -/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ -int sflite_vmb_seal(sflite_Vmb *vmb, char *vmb_key, char *disk_block); -/* "Decrypt" a VMB coming from the disk, directly using its key */ -int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflite_Vmb *vmb); +/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LITE version */ +int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +/* "Decrypt" a VMB coming from the disk, directly using its key. LITE version */ +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); + +/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LEGACY version */ +int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +/* "Decrypt" a VMB coming from the disk, directly using its key. LEGACY version */ +int sfold_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); -/* Create an encrypted empty position map for the given number of slices (allocates memory) */ +/* Create an encrypted empty position map for the given number of slices (allocates memory). LITE version */ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); +/* Create an encrypted empty position map for the given number of slices (allocates memory). LEGACY version */ +int sfold_epm_create(size_t nr_slices, char *volume_key, sfold_EncPosMap *epm); #endif /* _HEADER_H_ */ diff --git a/shufflecake-userland-lite/src/header/device_master_block.c b/shufflecake-userland-lite/src/header/device_master_block.c index 0e6ce22..1cfcd9b 100644 --- a/shufflecake-userland-lite/src/header/device_master_block.c +++ b/shufflecake-userland-lite/src/header/device_master_block.c @@ -60,21 +60,21 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool * * @return The error code, 0 on success */ -int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) +int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) { char *salt; int err; /* Sanity check */ - if (dmb->nr_vols > SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Randomise whole block */ - err = sflite_rand_getWeakBytes(disk_block, SFLITE_BLOCK_SIZE); + err = sflc_rand_getWeakBytes(disk_block, SFLC_BLOCK_SIZE); if (err) { - sflite_log_error("Could not randomise DMB; error %d", err); + sflc_log_error("Could not randomise DMB; error %d", err); return err; } @@ -84,12 +84,12 @@ int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_b /* Loop over all VMB keys to encrypt them */ size_t i; for (i = 0; i < dmb->nr_vols; i++) { - char *dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (i * SFLITE_DMB_CELL_SIZE); + char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); /* Encrypt it */ err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); if (err) { - sflite_log_error("Could not encrypt VMB key number %lu; error %d", i, err); + sflc_log_error("Could not encrypt VMB key number %lu; error %d", i, err); return err; } } @@ -106,50 +106,50 @@ int sflite_dmb_seal(sflite_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_b * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLITE_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflite_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell) +int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell) { // KDF salt char *salt; // The KDF-derived key - char kek[SFLITE_STANDARD_KEYLEN]; + char kek[SFLC_STANDARD_KEYLEN]; // The unlocked VMB key - char vmb_key[SFLITE_STANDARD_KEYLEN]; + char vmb_key[SFLC_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK once and for all */ salt = disk_block; - err = sflite_argon2id_derive(pwd, pwd_len, salt, kek); + err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - sflite_log_error("Could not perform KDF: error %d", err); + sflc_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - sflite_log_debug("Successfully derived key-encryption-key with KDF"); + sflc_log_debug("Successfully derived key-encryption-key with KDF"); /* Init dmb->vol_idx to invalid */ - dmb_cell->vol_idx = SFLITE_DEV_MAX_VOLUMES; + dmb_cell->vol_idx = SFLC_DEV_MAX_VOLUMES; /* Try all DMB cells */ size_t i; - for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { - char *enc_dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (i * SFLITE_DMB_CELL_SIZE); + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + char *enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); bool match; /* Try to decrypt this one */ err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); if (err) { - sflite_log_error("Error decrypting DMB cell number %lu; error %d", i, err); + sflc_log_error("Error decrypting DMB cell number %lu; error %d", i, err); goto bad_decrypt; } /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ if (match) { - sflite_log_debug("The provided password unlocks volume %lu", i); + sflc_log_debug("The provided password unlocks volume %lu", i); dmb_cell->vol_idx = i; - memcpy(dmb_cell->vmb_key, vmb_key, SFLITE_STANDARD_KEYLEN); + memcpy(dmb_cell->vmb_key, vmb_key, SFLC_STANDARD_KEYLEN); } } @@ -160,7 +160,7 @@ int sflite_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflite_DmbCel bad_decrypt: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLITE_STANDARD_KEYLEN); + memset(kek, 0, SFLC_STANDARD_KEYLEN); return err; } @@ -174,28 +174,28 @@ bad_kdf: * * @return Error code, 0 on success */ -int sflite_dmb_setCell(char *disk_block, sflite_DmbCell *dmb_cell, char *pwd, size_t pwd_len) +int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len) { char *salt; char *enc_dmb_cell; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Pointers inside DMB */ salt = disk_block; - enc_dmb_cell = (salt + SFLITE_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLITE_DMB_CELL_SIZE); + enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLC_DMB_CELL_SIZE); /* Encrypt with KDF-derived key */ err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); if (err) { - sflite_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); + sflc_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); return err; } - sflite_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); + sflc_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); return 0; } @@ -210,36 +210,36 @@ static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vm { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLITE_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLITE_STANDARD_KEYLEN; + char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; // Key-encryption-key derived from KDF - char kek[SFLITE_STANDARD_KEYLEN]; + char kek[SFLC_STANDARD_KEYLEN]; // Error code int err; /* Derive KEK */ - err = sflite_argon2id_derive(pwd, pwd_len, salt, kek); + err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); if (err) { - sflite_log_error("Could not perform KDF: error %d", err); + sflc_log_error("Could not perform KDF: error %d", err); goto bad_kdf; } - sflite_log_debug("Successfully derived key-encryption-key with KDF"); + sflc_log_debug("Successfully derived key-encryption-key with KDF"); /* Sample VMB_IV */ - err = sflite_rand_getWeakBytes(iv, SFLITE_AESGCM_PADDED_IVLEN); + err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); if (err) { - sflite_log_error("Could not sample prologue IV: error %d", err); + sflc_log_error("Could not sample prologue IV: error %d", err); goto bad_sample_iv; } - sflite_log_debug("Successfully sampled prologue IV"); + sflc_log_debug("Successfully sampled prologue IV"); /* Encrypt the VMB key */ - err = sflite_aes256gcm_encrypt(kek, vmb_key, SFLITE_STANDARD_KEYLEN, iv, enc_vmb_key, mac); + err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_STANDARD_KEYLEN, iv, enc_vmb_key, mac); if (err) { - sflite_log_error("Could not encrypt the VMB key: error %d", err); + sflc_log_error("Could not encrypt the VMB key: error %d", err); goto bad_encrypt; } - sflite_log_debug("Successfully encrypted VMB key with key-encryption-key"); + sflc_log_debug("Successfully encrypted VMB key with key-encryption-key"); // No prob err = 0; @@ -249,7 +249,7 @@ bad_encrypt: bad_sample_iv: bad_kdf: /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLITE_STANDARD_KEYLEN); + memset(kek, 0, SFLC_STANDARD_KEYLEN); return err; } @@ -258,18 +258,18 @@ static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool { // Pointers inside the block char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLITE_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLITE_STANDARD_KEYLEN; + char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; + char *mac = enc_vmb_key + SFLC_STANDARD_KEYLEN; // Error code int err; /* Decrypt the VMB key */ - err = sflite_aes256gcm_decrypt(kek, enc_vmb_key, SFLITE_STANDARD_KEYLEN, mac, iv, vmb_key, match); + err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_STANDARD_KEYLEN, mac, iv, vmb_key, match); if (err) { - sflite_log_error("Error while decrypting VMB key: error %d", err); + sflc_log_error("Error while decrypting VMB key: error %d", err); return err; } - sflite_log_debug("Decrypted VMB key: MAC match = %d", *match); + sflc_log_debug("Decrypted VMB key: MAC match = %d", *match); return 0; } diff --git a/shufflecake-userland-lite/src/header/position_map_legacy.c b/shufflecake-userland-lite/src/header/position_map_legacy.c new file mode 100644 index 0000000..923a830 --- /dev/null +++ b/shufflecake-userland-lite/src/header/position_map_legacy.c @@ -0,0 +1,134 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "header.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/math.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/* Create an encrypted empty position map for the given number of slices. + * Allocates the internal pointers of the EPM structure. + * On failure, does not free the allocated memory. + * + * @param nr_slices The number of slices the device will be composed of. + * @param volume_key The volume's data section encryption key, used to encrypt the + * position map as well. + * @param epm The EncPosMap struct to be initialised. + * + * @return Error code, 0 on success */ +int sfold_epm_create(size_t nr_slices, char *volume_key, sfold_EncPosMap *epm) +{ + size_t nr_pmbs; + size_t nr_arrays; + int err; + + // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) + nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + // Each array holds up to 256 PosMapBlocks + nr_arrays = ceil(nr_pmbs, SFLC_SLICE_SCALE); + + // Fill the EPM numeric fields + epm->nr_arrays = nr_arrays; + // All arrays are full except the last one + epm->nr_last_pmbs = nr_pmbs - (SFLC_SLICE_SCALE * (nr_arrays - 1)); + + // Allocate array of IV blocks + epm->iv_blocks = malloc(nr_arrays * sizeof(char *)); + if (!epm->iv_blocks) { + sflc_log_error("Could not malloc array of IV blocks"); + err = ENOMEM; + goto out; + } + // Allocate array of PosMapBlock arrays + epm->pmb_arrays = malloc(nr_arrays * sizeof(char *)); + if (!epm->pmb_arrays) { + sflc_log_error("Could not malloc array of PosMapBlock arrays"); + err = ENOMEM; + goto out; + } + + // Loop to allocate and encrypt each array + int i; + for (i = 0; i < nr_arrays; i++) { + // The last PMB array might be smaller + size_t nr_pmbs_here = ((i == nr_arrays - 1) ? epm->nr_last_pmbs : SFLC_SLICE_SCALE); + size_t pmb_array_size = SFLC_BLOCK_SIZE * nr_pmbs_here; + char *iv_block; + char *pmb_array; + + // Allocate IV block + epm->iv_blocks[i] = malloc(SFLC_BLOCK_SIZE); + if (!epm->iv_blocks[i]) { + sflc_log_error("Could not allocate IV block number %d", i); + err = ENOMEM; + goto out; + } + // Allocate PosMapBlock array + epm->pmb_arrays[i] = malloc(pmb_array_size); + if (!epm->pmb_arrays[i]) { + sflc_log_error("Could not allocate PMB array number %d", i); + err = ENOMEM; + goto out; + } + // Shorthand + iv_block = epm->iv_blocks[i]; + pmb_array = epm->pmb_arrays[i]; + + // Fill the IV block with random data (can ignore return value) + sflc_rand_getWeakBytes(iv_block, SFLC_BLOCK_SIZE); + // Fill the PMB array with 0xFF + memset(pmb_array, SFLC_EPM_FILLER, pmb_array_size); + + // Loop to encrypt each PMB separately with its IV + int j; + for (j = 0; j < nr_pmbs_here; j++) { + char *iv = iv_block + (j * SFLC_AESCTR_IVLEN); + char *pmb = pmb_array + (j * SFLC_BLOCK_SIZE); + + // Encrypt in-place + err = sflc_aes256ctr_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, NULL); + if (err) { + sflc_log_error("Could not encrypt PMB %d of array %d", j, i); + goto out; + } + } + } + + +out: + return err; +} diff --git a/shufflecake-userland-lite/src/header/position_map.c b/shufflecake-userland-lite/src/header/position_map_lite.c similarity index 88% rename from shufflecake-userland-lite/src/header/position_map.c rename to shufflecake-userland-lite/src/header/position_map_lite.c index 1059378..7782947 100644 --- a/shufflecake-userland-lite/src/header/position_map.c +++ b/shufflecake-userland-lite/src/header/position_map_lite.c @@ -31,7 +31,7 @@ #include #include "header.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/math.h" #include "utils/log.h" @@ -51,25 +51,25 @@ * @return The memory buffer containing the position map */ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) { - char pmb[SFLITE_BLOCK_SIZE]; - char iv[SFLITE_AESXTS_IVLEN]; + char pmb[SFLC_BLOCK_SIZE]; + char iv[SFLC_AESXTS_IVLEN]; void *epm; size_t nr_pmbs; uint64_t pblk_num; int err; // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - nr_pmbs = ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); + nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); // Allocate EPM - epm = malloc(nr_pmbs * SFLITE_BLOCK_SIZE); + epm = malloc(nr_pmbs * SFLC_BLOCK_SIZE); if (!epm) { sflite_log_error("Could not malloc EPM"); return NULL; } // Fill cleartext PMB with 0xFF - memset(pmb, SFLITE_EPM_FILLER, SFLITE_BLOCK_SIZE); + memset(pmb, SFLC_EPM_FILLER, SFLC_BLOCK_SIZE); // First physical block number pblk_num = sflite_pmStartBlock(vol_idx, nr_slices); @@ -77,12 +77,12 @@ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) int i; for (i = 0; i < nr_pmbs; i++) { // Set IV - memset(iv, 0, SFLITE_AESXTS_IVLEN); + memset(iv, 0, SFLC_AESXTS_IVLEN); *((uint64_t*)iv) = htole64(pblk_num); // Encrypt - err = sflite_aes256xts_encrypt(volume_key, pmb, SFLITE_BLOCK_SIZE, iv, - epm + i*SFLITE_BLOCK_SIZE); + err = sflite_aes256xts_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, + epm + i*SFLC_BLOCK_SIZE); if (err) { sflite_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); free(epm); diff --git a/shufflecake-userland-lite/src/header/volume_master_block.c b/shufflecake-userland-lite/src/header/volume_master_block_legacy.c similarity index 67% rename from shufflecake-userland-lite/src/header/volume_master_block.c rename to shufflecake-userland-lite/src/header/volume_master_block_legacy.c index e44963b..30ab258 100644 --- a/shufflecake-userland-lite/src/header/volume_master_block.c +++ b/shufflecake-userland-lite/src/header/volume_master_block_legacy.c @@ -41,10 +41,10 @@ *****************************************************/ /* Serialise the VMB before encrypting it */ -static void _serialiseVmb(sflite_Vmb *vmb, char *clear_vmb); +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflite_Vmb *vmb); +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); /***************************************************** @@ -61,44 +61,44 @@ static int _deserialiseVmb(char *clear_vmb, sflite_Vmb *vmb); * * @return The error code, 0 on success */ -int sflite_vmb_seal(sflite_Vmb *vmb, char *vmb_key, char *disk_block) +int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + SFLITE_AESCTR_IVLEN; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; // Serialised VMB (dynamically allocated), to be encrypted char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLITE_CLEAR_VMB_LEN); + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); if (!clear_vmb) { - sflite_log_error("Could not allocate %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - sflite_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); /* Serialise the struct */ _serialiseVmb(vmb, clear_vmb); - sflite_log_debug("Serialised VMB struct"); + sflc_log_debug("Serialised VMB struct"); /* Sample VMB IV */ - err = sflite_rand_getWeakBytes(iv, SFLITE_AESGCM_PADDED_IVLEN); + err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); if (err) { - sflite_log_error("Could not sample VMB IV: error %d", err); + sflc_log_error("Could not sample VMB IV: error %d", err); goto bad_sample_iv; } - sflite_log_debug("Successfully sampled VMB IV"); + sflc_log_debug("Successfully sampled VMB IV"); /* Encrypt the VMB */ - err = sflite_aes256ctr_encrypt(vmb_key, clear_vmb, SFLITE_CLEAR_VMB_LEN, iv, enc_vmb); + err = sflc_aes256ctr_encrypt(vmb_key, clear_vmb, SFLC_CLEAR_VMB_LEN, iv, enc_vmb); if (err) { - sflite_log_error("Could not encrypt VMB: error %d", err); + sflc_log_error("Could not encrypt VMB: error %d", err); goto bad_encrypt; } - sflite_log_debug("Successfully encrypted VMB"); + sflc_log_debug("Successfully encrypted VMB"); // No prob err = 0; @@ -107,7 +107,7 @@ int sflite_vmb_seal(sflite_Vmb *vmb, char *vmb_key, char *disk_block) bad_encrypt: bad_sample_iv: /* Always wipe and free the cleartext VMB, even on success */ - memset(clear_vmb, 0, SFLITE_CLEAR_VMB_LEN); + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -123,40 +123,40 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflite_Vmb *vmb) +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; - char *enc_vmb = iv + SFLITE_AESCTR_IVLEN; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; // Decrypted VMB (dynamically allocated), to be deserialised char *clear_vmb; // Error code int err; /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLITE_CLEAR_VMB_LEN); + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); if (!clear_vmb) { - sflite_log_error("Could not allocate %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); err = ENOMEM; goto bad_clear_alloc; } - sflite_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLITE_CLEAR_VMB_LEN); + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); /* Decrypt the VMB */ - err = sflite_aes256ctr_decrypt(vmb_key, enc_vmb, SFLITE_CLEAR_VMB_LEN, iv, clear_vmb); + err = sflc_aes256ctr_decrypt(vmb_key, enc_vmb, SFLC_CLEAR_VMB_LEN, iv, clear_vmb); if (err) { - sflite_log_error("Error while decrypting VMB: error %d", err); + sflc_log_error("Error while decrypting VMB: error %d", err); goto bad_decrypt; } - sflite_log_debug("Successfully decrypted VMB"); + sflc_log_debug("Successfully decrypted VMB"); /* Deserialise the struct */ err = _deserialiseVmb(clear_vmb, vmb); if (err) { - sflite_log_error("Error while deserialising VMB: error %d", err); + sflc_log_error("Error while deserialising VMB: error %d", err); goto bad_deserialise; } - sflite_log_debug("Deserialised VMB struct"); + sflc_log_debug("Deserialised VMB struct"); // No prob err = 0; @@ -165,7 +165,7 @@ int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflite_Vmb *vmb) bad_deserialise: bad_decrypt: /* Always wipe and free the VMB cleartext, even on success */ - memset(clear_vmb, 0, SFLITE_CLEAR_VMB_LEN); + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); free(clear_vmb); bad_clear_alloc: return err; @@ -177,18 +177,18 @@ bad_clear_alloc: *****************************************************/ /* Serialise the payload before encrypting it */ -static void _serialiseVmb(sflite_Vmb *vmb, char *clear_vmb) +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLITE_AESXTS_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLITE_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, SFLITE_AESXTS_KEYLEN); + memcpy(p_vol_key, vmb->volume_key_legacy, SFLC_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLITE_STANDARD_KEYLEN); + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_STANDARD_KEYLEN); /* Write the number of slices (network byte order) */ *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); @@ -200,18 +200,18 @@ static void _serialiseVmb(sflite_Vmb *vmb, char *clear_vmb) /* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflite_Vmb *vmb) +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) { // Pointers inside the VMB char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLITE_AESXTS_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLITE_STANDARD_KEYLEN; + char *p_prev_vmb_key = p_vol_key + SFLC_STANDARD_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, SFLITE_AESXTS_KEYLEN); + memcpy(vmb->volume_key_legacy, p_vol_key, SFLC_STANDARD_KEYLEN); /* Copy the previous volume's VMB key */ - memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLITE_STANDARD_KEYLEN); + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_STANDARD_KEYLEN); /* Read number of slices (network byte order) */ vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); diff --git a/shufflecake-userland-lite/src/header/volume_master_block_lite.c b/shufflecake-userland-lite/src/header/volume_master_block_lite.c new file mode 100644 index 0000000..0561836 --- /dev/null +++ b/shufflecake-userland-lite/src/header/volume_master_block_lite.c @@ -0,0 +1,223 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include // Network byte order + +#include "header.h" +#include "utils/crypto.h" +#include "utils/string.h" +#include "utils/log.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Serialise the VMB before encrypting it */ +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); + +/* Deserialise the VMB after decrypting it */ +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Builds the on-disk master block (indistinguishable from random). + * + * @param vmb The useful information stored in this volume master block + * @param vmb_key The key encrypting the VMB + * @param disk_block The 4096-byte buffer that will contain the random-looking + * bytes to be written on-disk + * + * @return The error code, 0 on success + */ +int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) +{ + // Pointers inside the block + char *iv = disk_block; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + // Serialised VMB (dynamically allocated), to be encrypted + char *clear_vmb; + // Error code + int err; + + /* Allocate large buffer on the heap */ + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + if (!clear_vmb) { + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + err = ENOMEM; + goto bad_clear_alloc; + } + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + + /* Serialise the struct */ + _serialiseVmb(vmb, clear_vmb); + sflc_log_debug("Serialised VMB struct"); + + /* Sample VMB IV */ + err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); + if (err) { + sflc_log_error("Could not sample VMB IV: error %d", err); + goto bad_sample_iv; + } + sflc_log_debug("Successfully sampled VMB IV"); + + /* Encrypt the VMB */ + err = sflc_aes256ctr_encrypt(vmb_key, clear_vmb, SFLC_CLEAR_VMB_LEN, iv, enc_vmb); + if (err) { + sflc_log_error("Could not encrypt VMB: error %d", err); + goto bad_encrypt; + } + sflc_log_debug("Successfully encrypted VMB"); + + // No prob + err = 0; + + +bad_encrypt: +bad_sample_iv: + /* Always wipe and free the cleartext VMB, even on success */ + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + free(clear_vmb); +bad_clear_alloc: + return err; +} + + +/** + * Decrypt the VMB payload using the VMB key. + * + * @param disk_block The content of the on-disk encrypted VMB + * @param vmb_key The proposed VMB key to unseal its payload + * @param vmb A pointer to the output struct that will contain all the VMB fields + * + * @return An error code, 0 on success + */ +int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +{ + // Pointers inside the block + char *iv = disk_block; + char *enc_vmb = iv + SFLC_AESCTR_IVLEN; + // Decrypted VMB (dynamically allocated), to be deserialised + char *clear_vmb; + // Error code + int err; + + /* Allocate large buffer on the heap */ + clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); + if (!clear_vmb) { + sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + err = ENOMEM; + goto bad_clear_alloc; + } + sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); + + /* Decrypt the VMB */ + err = sflc_aes256ctr_decrypt(vmb_key, enc_vmb, SFLC_CLEAR_VMB_LEN, iv, clear_vmb); + if (err) { + sflc_log_error("Error while decrypting VMB: error %d", err); + goto bad_decrypt; + } + sflc_log_debug("Successfully decrypted VMB"); + + /* Deserialise the struct */ + err = _deserialiseVmb(clear_vmb, vmb); + if (err) { + sflc_log_error("Error while deserialising VMB: error %d", err); + goto bad_deserialise; + } + sflc_log_debug("Deserialised VMB struct"); + + // No prob + err = 0; + + +bad_deserialise: +bad_decrypt: + /* Always wipe and free the VMB cleartext, even on success */ + memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); + free(clear_vmb); +bad_clear_alloc: + return err; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Serialise the payload before encrypting it */ +static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) +{ + // Pointers inside the VMB + char *p_vol_key = clear_vmb; + char *p_prev_vmb_key = p_vol_key + SFLC_AESXTS_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; + + /* Copy the volume key */ + memcpy(p_vol_key, vmb->volume_key_lite, SFLC_AESXTS_KEYLEN); + + /* Copy the previous volume's VMB key */ + memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_STANDARD_KEYLEN); + + /* Write the number of slices (network byte order) */ + *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); + + // Leave the rest uninitialised + + return; +} + + +/* Deserialise the VMB after decrypting it */ +static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) +{ + // Pointers inside the VMB + char *p_vol_key = clear_vmb; + char *p_prev_vmb_key = p_vol_key + SFLC_AESXTS_KEYLEN; + char *p_nr_slices = p_prev_vmb_key + SFLC_STANDARD_KEYLEN; + + /* Copy the volume key */ + memcpy(vmb->volume_key_lite, p_vol_key, SFLC_AESXTS_KEYLEN); + + /* Copy the previous volume's VMB key */ + memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_STANDARD_KEYLEN); + + /* Read number of slices (network byte order) */ + vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); + + // Ignore the rest + + return 0; +} + From 79df245acf4061213c892120dfeaed32dd46e440 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 23 Aug 2024 17:13:21 +0200 Subject: [PATCH 66/98] Merge operations/ and commands/ --- shufflecake-userland-lite/include/commands.h | 36 +-- .../include/operations.h | 55 ++++- .../include/utils/disk.h | 12 +- .../include/utils/sflc.h | 3 + .../src/commands/change_pwd.c | 8 +- .../src/commands/close.c | 52 ++--- .../src/commands/init_legacy.c | 202 +++++++++++++++++ .../src/commands/{init.c => init_lite.c} | 54 ++--- .../src/commands/open_legacy.c | 207 ++++++++++++++++++ .../src/commands/{open.c => open_lite.c} | 62 ++++-- .../src/commands/test_pwd.c | 6 +- .../src/header/volume_master_block_legacy.c | 2 +- .../src/header/volume_master_block_lite.c | 2 +- .../src/operations/devmapper_legacy.c | 111 ++++++++++ .../{devmapper.c => devmapper_lite.c} | 22 +- .../src/operations/dmb.c | 52 ++--- .../src/operations/volume_header_legacy.c | 165 ++++++++++++++ .../{volume_header.c => volume_header_lite.c} | 34 +-- 18 files changed, 917 insertions(+), 168 deletions(-) create mode 100644 shufflecake-userland-lite/src/commands/init_legacy.c rename shufflecake-userland-lite/src/commands/{init.c => init_lite.c} (71%) create mode 100644 shufflecake-userland-lite/src/commands/open_legacy.c rename shufflecake-userland-lite/src/commands/{open.c => open_lite.c} (70%) create mode 100644 shufflecake-userland-lite/src/operations/devmapper_legacy.c rename shufflecake-userland-lite/src/operations/{devmapper.c => devmapper_lite.c} (83%) create mode 100644 shufflecake-userland-lite/src/operations/volume_header_legacy.c rename shufflecake-userland-lite/src/operations/{volume_header.c => volume_header_lite.c} (79%) diff --git a/shufflecake-userland-lite/include/commands.h b/shufflecake-userland-lite/include/commands.h index d21c767..6531bf0 100644 --- a/shufflecake-userland-lite/include/commands.h +++ b/shufflecake-userland-lite/include/commands.h @@ -50,8 +50,8 @@ typedef struct { /* Underlying block device */ char *bdev_path; - /* Shufflecake mode (legacy,lite,full) */ - int sflite_mode; + /* Shufflecake mode */ + int sflc_mode; /* Number of volumes */ size_t nr_vols; /* Volumes' passwords */ @@ -60,7 +60,7 @@ typedef struct /* Option to skip random filling */ bool no_randfill; -} sflite_cmd_InitArgs; +} sflc_cmd_InitArgs; /* Parameters for the open command */ @@ -68,47 +68,51 @@ typedef struct { /* Underlying block device */ char *bdev_path; - /* Shufflecake mode (legacy,lite,full) */ - int sflite_mode; + /* Shufflecake mode */ + int sflc_mode; /* The only password provided */ char *pwd; size_t pwd_len; -} sflite_cmd_OpenArgs; +} sflc_cmd_OpenArgs; typedef struct { /* Underlying block device */ char *bdev_path; /* Shufflecake mode (legacy,lite,full) */ - int sflite_mode; + int sflc_mode; /* Content of the DMB cell */ - sflite_DmbCell *dmb_cell; + sflc_DmbCell *dmb_cell; /* The new password */ char *new_pwd; size_t new_pwd_len; -} sflite_cmd_ChangePwdArgs; +} sflc_cmd_ChangePwdArgs; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -/* Create N volumes (only formats the device header, does not open the volumes) */ -int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args); +/* Create N volumes (only formats the device header, does not open the volumes). LITE version */ +int sflite_cmd_initVolumes(sflc_cmd_InitArgs *args); +/* Create N volumes (only formats the device header, does not open the volumes). LEGACY version */ +int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args); -/* Open M volumes, from the first down to the one whose pwd is provided */ -int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args); +/* Open M volumes, from the first down to the one whose pwd is provided. LITE version */ +int sflite_cmd_openVolumes(sflc_cmd_OpenArgs *args); +/* Open M volumes, from the first down to the one whose pwd is provided. LEGACY version */ +int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args); /* Close all volumes on the device (reads the list from sysfs) */ -int sflite_cmd_closeVolumes(char *bdev_path); +int sflc_cmd_closeVolumes(char *bdev_path); /* Tests which volume is unlocked by the given password */ -int sflite_cmd_testPwd(sflite_cmd_OpenArgs *args, sflite_DmbCell *dmb_cell); +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell); /* Changes the specified volume's password */ -int sflite_cmd_changePwd(sflite_cmd_ChangePwdArgs *args); +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args); #endif /* _COMMANDS_H_ */ diff --git a/shufflecake-userland-lite/include/operations.h b/shufflecake-userland-lite/include/operations.h index 25af9fe..c4aaa81 100644 --- a/shufflecake-userland-lite/include/operations.h +++ b/shufflecake-userland-lite/include/operations.h @@ -37,25 +37,60 @@ #include "utils/math.h" +/***************************************************** + * INLINE FUNCTIONS * + *****************************************************/ + +// Size, in 4096-byte blocks, of a whole volume header (VMB+PM). LEGACY version +static inline size_t sfold_volHeaderSize(size_t nr_slices) +{ + // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) + size_t nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + // Each array holds up to 256 PosMapBlocks + size_t nr_arrays = ceil(nr_pmbs, SFLC_SLICE_SCALE); + + // 1 VMB, the PMBs, and the IV blocks + return 1 + nr_pmbs + nr_arrays; +} + +// Position of the VMB for the given volume. LEGACY version +static inline uint64_t sfold_vmbPosition(size_t vol_idx, size_t nr_slices) +{ + return 1 + ((uint64_t) vol_idx) * ((uint64_t) sfold_volHeaderSize(nr_slices)); +} + + /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ /* Encrypts and writes the DMB to disk */ -int sflite_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflite_Dmb *dmb); +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb); /* Reads the DMB from disk and outputs the unlocked VMB key */ -int sflite_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflite_DmbCell *dmb_cell); +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); /* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ -int sflite_ops_rewriteDmbCell(char *bdev_path, sflite_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); -/* Encrypts and writes a volume header (VMB+PM) on-disk */ -int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflite_Vmb *vmb, size_t vol_idx); -/* Reads a VMB from disk and unlocks it */ -int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflite_Vmb *vmb); -/* Build parameter list for ctor in dm_sflite, and send DM ioctl to create virtual block device */ -int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflite_Vmb *vmb); -/* Close the volume via the appropriate ioctl to DM */ +/* Encrypts and writes a volume header (VMB+PM) on-disk. LITE version */ +int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +/* Reads a VMB from disk and unlocks it. LITE version */ +int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); + +/* Encrypts and writes a volume header (VMB+PM) on-disk. LEGACY version */ +int sfold_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +/* Reads a VMB from disk and unlocks it. LEGACY version */ +int sfold_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); + + +/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device. LITE version */ +int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); +/* Close the volume via the appropriate ioctl to DM. LITE version */ int sflite_ops_closeVolume(char *label); +/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device. LEGACY version */ +int sfold_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); +/* Close the volume via the appropriate ioctl to DM. LEGACY version */ +int sfold_ops_closeVolume(char *label); + #endif /* _OPERATIONS_H_ */ diff --git a/shufflecake-userland-lite/include/utils/disk.h b/shufflecake-userland-lite/include/utils/disk.h index 56256c4..954ebce 100644 --- a/shufflecake-userland-lite/include/utils/disk.h +++ b/shufflecake-userland-lite/include/utils/disk.h @@ -50,25 +50,25 @@ static inline uint32_t sflite_disk_maxSlices(uint64_t size) { uint64_t nr_slices; // Start from upper bound - nr_slices = size / SFLITE_SLICE_SCALE; + nr_slices = size / SFLC_SLICE_SCALE; while(true) { if (nr_slices == 0) break; // Stop when this nr_slices can fit in size, including the header - uint64_t posmap_blocks = ceil(nr_slices, SFLITE_SLICE_IDX_PER_BLOCK); - uint64_t header_size = 1 + SFLITE_DEV_MAX_VOLUMES * (1 + posmap_blocks); - if (header_size + nr_slices*SFLITE_SLICE_SCALE <= size) + uint64_t posmap_blocks = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); + uint64_t header_size = 1 + SFLC_DEV_MAX_VOLUMES * (1 + posmap_blocks); + if (header_size + nr_slices*SFLC_SLICE_SCALE <= size) break; nr_slices--; } - return nr_slices > SFLITE_MAX_SLICES ? SFLITE_MAX_SLICES : (uint32_t) nr_slices; + return nr_slices > SFLC_MAX_SLICES ? SFLC_MAX_SLICES : (uint32_t) nr_slices; } /* LEGACY version */ -#define sfold_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLC_BLOCKS_PER_PHYS_SLICE) +#define sfold_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFOLD_BLOCKS_PER_PHYS_SLICE) /* We need this inequality to hold, in order for the previous bound to be true */ #if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK #error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" diff --git a/shufflecake-userland-lite/include/utils/sflc.h b/shufflecake-userland-lite/include/utils/sflc.h index a43efba..499f5ae 100644 --- a/shufflecake-userland-lite/include/utils/sflc.h +++ b/shufflecake-userland-lite/include/utils/sflc.h @@ -53,6 +53,9 @@ #define SFLC_SLICE_SCALE 256 /* blocks in a slice */ #define SFLC_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ +/* For legacy */ +#define SFOLD_BLOCKS_PER_PHYS_SLICE (SFLC_SLICE_SCALE + 1) + /* Max number of volumes in a device */ #define SFLC_DEV_MAX_VOLUMES 15 diff --git a/shufflecake-userland-lite/src/commands/change_pwd.c b/shufflecake-userland-lite/src/commands/change_pwd.c index dcac991..b823662 100644 --- a/shufflecake-userland-lite/src/commands/change_pwd.c +++ b/shufflecake-userland-lite/src/commands/change_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -51,8 +51,8 @@ * * @return Error code, 0 on success */ -int sflite_cmd_changePwd(sflite_cmd_ChangePwdArgs *args) +int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args) { - /* Delegate entirely to the function reading the DMB */ - return sflite_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); + /* Delegate entirely to the function rewriting the DMB */ + return sflc_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); } diff --git a/shufflecake-userland-lite/src/commands/close.c b/shufflecake-userland-lite/src/commands/close.c index eff361d..73a7605 100644 --- a/shufflecake-userland-lite/src/commands/close.c +++ b/shufflecake-userland-lite/src/commands/close.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/string.h" #include "utils/file.h" @@ -61,18 +61,18 @@ static int _closeVolumes(char **labels, size_t nr_vols); * * @return Error code, 0 on success */ -int sflite_cmd_closeVolumes(char *bdev_path) +int sflc_cmd_closeVolumes(char *bdev_path) { - char *labels[SFLITE_DEV_MAX_VOLUMES]; + char *labels[SFLC_DEV_MAX_VOLUMES]; size_t nr_vols; int err; /* Allocate labels */ size_t i; - for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { - labels[i] = malloc(SFLITE_MAX_VOL_NAME_LEN + 1); + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { + labels[i] = malloc(SFLC_MAX_VOL_NAME_LEN + 1); if (!labels[1]) { - sflite_log_error("Could not allocate volume label %lu", i); + sflc_log_error("Could not allocate volume label %lu", i); return ENOMEM; // Do not free the ones already allocated } } @@ -80,14 +80,14 @@ int sflite_cmd_closeVolumes(char *bdev_path) /* Read them */ err = _buildVolumesList(bdev_path, labels, &nr_vols); if (err) { - sflite_log_error("Could not read volume list from sysfs; error %d", err); + sflc_log_error("Could not read volume list from sysfs; error %d", err); goto out; } /* Close the volumes (in reverse order of opening) */ err = _closeVolumes(labels, nr_vols); if (err) { - sflite_log_error("Could not close volumes; error %d", err); + sflc_log_error("Could not close volumes; error %d", err); goto out; } @@ -96,7 +96,7 @@ int sflite_cmd_closeVolumes(char *bdev_path) out: - for (i = 0; i < SFLITE_DEV_MAX_VOLUMES; i++) { + for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { free(labels[i]); } return err; @@ -111,50 +111,50 @@ out: static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols) { char *bdev_name; - char devid_path[SFLITE_BIGBUFSIZE]; + char devid_path[SFLC_BIGBUFSIZE]; char *str_devid; size_t dev_id; - char nrvolumes_path[SFLITE_BIGBUFSIZE]; + char nrvolumes_path[SFLC_BIGBUFSIZE]; char *str_nrvolumes; /* Get device name as : */ - bdev_name = sflite_disk_getDeviceName(bdev_path); + bdev_name = sflc_disk_getDeviceName(bdev_path); if(!bdev_name) { - sflite_log_error("Could not allocate device name"); + sflc_log_error("Could not allocate device name"); return ENOMEM; } /* Build path to sysfs file containing device ID */ - sprintf(devid_path, "%s/%s/%s", SFLITE_SYSFS_BDEVS_DIR, bdev_name, SFLITE_SYSFS_DEVID_FILENAME); + sprintf(devid_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_DEVID_FILENAME); /* Build path to sysfs file containing number of open volumes */ - sprintf(nrvolumes_path, "%s/%s/%s", SFLITE_SYSFS_BDEVS_DIR, bdev_name, SFLITE_SYSFS_OPENVOLUMES_FILENAME); + sprintf(nrvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_OPENVOLUMES_FILENAME); /* Read the device ID */ - str_devid = sflite_readFile(devid_path); + str_devid = sflc_readFile(devid_path); if (!str_devid) { - sflite_log_error("Could not read file %s", devid_path); + sflc_log_error("Could not read file %s", devid_path); return EBADF; } /* Parse the device ID */ if (sscanf(str_devid, "%lu", &dev_id) != 1) { - sflite_log_error("Could not parse device ID:\n%s", str_devid); + sflc_log_error("Could not parse device ID:\n%s", str_devid); return EBADF; } /* Read the number of volumes */ - str_nrvolumes = sflite_readFile(nrvolumes_path); + str_nrvolumes = sflc_readFile(nrvolumes_path); if (!str_nrvolumes) { - sflite_log_error("Could not read file %s", nrvolumes_path); + sflc_log_error("Could not read file %s", nrvolumes_path); return EBADF; } /* Parse the number of volumes */ if (sscanf(str_nrvolumes, "%lu", nr_vols) != 1) { - sflite_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); + sflc_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); return EBADF; } /* Just to be sure */ - if (*nr_vols > SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); + if (*nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); return EBADF; } @@ -176,12 +176,12 @@ static int _closeVolumes(char **labels, size_t nr_vols) /* Eazy peazy */ int i; for (i = nr_vols-1; i >= 0; i--) { - err = sflite_ops_closeVolume(labels[i]); + err = sflc_ops_closeVolume(labels[i]); if (err) { - sflite_log_error("Could not close volume %s; error %d", labels[i], err); + sflc_log_error("Could not close volume %s; error %d", labels[i], err); return err; } - sflite_log_debug("Closed volume %s", labels[i]); + sflc_log_debug("Closed volume %s", labels[i]); printf("Closed volume /dev/mapper/%s\n", labels[i]); } diff --git a/shufflecake-userland-lite/src/commands/init_legacy.c b/shufflecake-userland-lite/src/commands/init_legacy.c new file mode 100644 index 0000000..3ee1c81 --- /dev/null +++ b/shufflecake-userland-lite/src/commands/init_legacy.c @@ -0,0 +1,202 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * CONSTANTS * + *****************************************************/ + +/* The device is randomised in chunks of 1024 blocks (arbitrary number) */ +#define SFLC_BLOCKS_IN_RAND_CHUNK 1024 +/* That's 4 MiB */ +#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_BLOCK_SIZE) + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Fill the device with random data */ +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Create N volumes (only formats the device header, does not open the volumes). + * Creates them in order from 0 to N-1, so as to induce a back-linked list on the device. + * + * @param args->bdev_path The path to the underlying block device + * @param args->nr_vols The number of volumes to create + * @param args->pwds The array of passwords for the various volumes + * @param args->pwd_lens The length of each password + * @param args->no_randfill A boolean switch indicating that the volume should not + * be filled entirely with random data prior to formatting. + * + * @return Error code, 0 on success + */ +int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args) +{ + sflc_Dmb dmb; + sflc_Vmb vmb; + int64_t dev_size; + size_t nr_slices; + int err; + + /* Sanity check */ + if (args->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create %lu volumes on a single device", args->nr_vols); + return EINVAL; + } + + /* Get device size */ + dev_size = sflc_disk_getSize(args->bdev_path); + if (dev_size < 0) { + err = -dev_size; + sflc_log_error("Could not get device size for %s; error %d", args->bdev_path, err); + return err; + } + /* Convert to number of slices */ + nr_slices = sfold_disk_maxSlices(dev_size); + sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); + + /* Fill disk with random bytes, if requested */ + if (!args->no_randfill) { + err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); + if (err) { + sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); + return err; + } + } + + /* Fill the DMB */ + dmb.nr_vols = args->nr_vols; + /* Sample the VMB keys */ + size_t i; + for (i = 0; i < dmb.nr_vols; i++) { + err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_STANDARD_KEYLEN); + if (err) { + sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); + return err; + } + } + /* And write (encrypted) to disk */ + err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + if (err) { + sflc_log_error("Could not create DMB and write it to disk; error %d", err); + return err; + } + + /* Write the volume headers */ + vmb.nr_slices = nr_slices; + for (i = 0; i < args->nr_vols; i++) { + /* This volume's prev_vmb_key */ + if (i > 0) { + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_STANDARD_KEYLEN); + } + /* Sample this volume's VEK */ + sflc_rand_getStrongBytes(vmb.volume_key_legacy, SFLC_STANDARD_KEYLEN); + + /* Write complete volume header (VMB + PM) */ + err = sfold_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); + if (err) { + sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); + return err; + } + } + printf("Created %lu volumes on device %s\n", args->nr_vols, args->bdev_path); + + return 0; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Fill the device with random data */ +static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) +{ + char *rand_chunk; + int err; + + /* Allocate chunk */ + rand_chunk = malloc(SFLC_RAND_CHUNK_SIZE); + if (!rand_chunk) { + sflc_log_error("Could not allocate %d bytes for chunk of random data", SFLC_RAND_CHUNK_SIZE); + return ENOMEM; + } + + /* Loop to write random data in chunks */ + uint64_t blocks_remaining = dev_size; + uint64_t sector = 0; + while (blocks_remaining > 0) { + uint64_t blocks_to_write = + (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; + uint64_t bytes_to_write = blocks_to_write * SFLC_BLOCK_SIZE; + + /* Sample random bytes */ + err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); + if (err) { + sflc_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); + goto out; + } + + /* Write on disk */ + err = sflc_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); + if (err) { + sflc_log_error("Could not write random bytes on disk; error %d", err); + goto out; + } + + /* Advance loop */ + sector += blocks_to_write; + blocks_remaining -= blocks_to_write; + } + + /* No prob */ + err = 0; + + +out: + free(rand_chunk); + return err; +} + diff --git a/shufflecake-userland-lite/src/commands/init.c b/shufflecake-userland-lite/src/commands/init_lite.c similarity index 71% rename from shufflecake-userland-lite/src/commands/init.c rename to shufflecake-userland-lite/src/commands/init_lite.c index 41a6de8..f65d1b6 100644 --- a/shufflecake-userland-lite/src/commands/init.c +++ b/shufflecake-userland-lite/src/commands/init_lite.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -42,9 +42,9 @@ *****************************************************/ /* The device is randomised in chunks of 1024 blocks (arbitrary number) */ -#define SFLITE_BLOCKS_IN_RAND_CHUNK 1024 +#define SFLC_BLOCKS_IN_RAND_CHUNK 1024 /* That's 4 MiB */ -#define SFLITE_RAND_CHUNK_SIZE (SFLITE_BLOCKS_IN_RAND_CHUNK * SFLITE_BLOCK_SIZE) +#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_BLOCK_SIZE) /***************************************************** @@ -72,36 +72,36 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); * * @return Error code, 0 on success */ -int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args) +int sflite_cmd_initVolumes(sflc_cmd_InitArgs *args) { - sflite_Dmb dmb; - sflite_Vmb vmb; + sflc_Dmb dmb; + sflc_Vmb vmb; int64_t dev_size; size_t nr_slices; int err; /* Sanity check */ - if (args->nr_vols > SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Cannot create %lu volumes on a single device", args->nr_vols); + if (args->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create %lu volumes on a single device", args->nr_vols); return EINVAL; } /* Get device size */ - dev_size = sflite_disk_getSize(args->bdev_path); + dev_size = sflc_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - sflite_log_error("Could not get device size for %s; error %d", args->bdev_path, err); + sflc_log_error("Could not get device size for %s; error %d", args->bdev_path, err); return err; } /* Convert to number of slices */ nr_slices = sflite_disk_maxSlices(dev_size); - sflite_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); + sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); /* Fill disk with random bytes, if requested */ if (!args->no_randfill) { err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); if (err) { - sflite_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); + sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); return err; } } @@ -111,16 +111,16 @@ int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args) /* Sample the VMB keys */ size_t i; for (i = 0; i < dmb.nr_vols; i++) { - err = sflite_rand_getStrongBytes(dmb.vmb_keys[i], SFLITE_STANDARD_KEYLEN); + err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_STANDARD_KEYLEN); if (err) { - sflite_log_error("Could not sample VMB key number %lu; error %d", i , err); + sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); return err; } } /* And write (encrypted) to disk */ - err = sflite_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); + err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); if (err) { - sflite_log_error("Could not create DMB and write it to disk; error %d", err); + sflc_log_error("Could not create DMB and write it to disk; error %d", err); return err; } @@ -129,15 +129,15 @@ int sflite_cmd_initVolumes(sflite_cmd_InitArgs *args) for (i = 0; i < args->nr_vols; i++) { /* This volume's prev_vmb_key */ if (i > 0) { - memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLITE_STANDARD_KEYLEN); + memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_STANDARD_KEYLEN); } /* Sample this volume's VEK */ - sflite_rand_getStrongBytes(vmb.volume_key, SFLITE_AESXTS_KEYLEN); + sflc_rand_getStrongBytes(vmb.volume_key_lite, SFLC_AESXTS_KEYLEN); /* Write complete volume header (VMB + PM) */ err = sflite_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); if (err) { - sflite_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); + sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); return err; } } @@ -158,9 +158,9 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) int err; /* Allocate chunk */ - rand_chunk = malloc(SFLITE_RAND_CHUNK_SIZE); + rand_chunk = malloc(SFLC_RAND_CHUNK_SIZE); if (!rand_chunk) { - sflite_log_error("Could not allocate %d bytes for chunk of random data", SFLITE_RAND_CHUNK_SIZE); + sflc_log_error("Could not allocate %d bytes for chunk of random data", SFLC_RAND_CHUNK_SIZE); return ENOMEM; } @@ -169,20 +169,20 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) uint64_t sector = 0; while (blocks_remaining > 0) { uint64_t blocks_to_write = - (blocks_remaining > SFLITE_BLOCKS_IN_RAND_CHUNK) ? SFLITE_BLOCKS_IN_RAND_CHUNK : blocks_remaining; - uint64_t bytes_to_write = blocks_to_write * SFLITE_BLOCK_SIZE; + (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; + uint64_t bytes_to_write = blocks_to_write * SFLC_BLOCK_SIZE; /* Sample random bytes */ - err = sflite_rand_getWeakBytes(rand_chunk, bytes_to_write); + err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); if (err) { - sflite_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); + sflc_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); goto out; } /* Write on disk */ - err = sflite_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); + err = sflc_disk_writeManyBlocks(bdev_path, sector, rand_chunk, blocks_to_write); if (err) { - sflite_log_error("Could not write random bytes on disk; error %d", err); + sflc_log_error("Could not write random bytes on disk; error %d", err); goto out; } diff --git a/shufflecake-userland-lite/src/commands/open_legacy.c b/shufflecake-userland-lite/src/commands/open_legacy.c new file mode 100644 index 0000000..077edb9 --- /dev/null +++ b/shufflecake-userland-lite/src/commands/open_legacy.c @@ -0,0 +1,207 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include + +#include "commands.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/disk.h" +#include "utils/file.h" +#include "utils/log.h" +#include "utils/string.h" + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Read the next device ID in sysfs */ +static int _getNextDevId(size_t *next_dev_id); + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Open M volumes, from the first one down to the one whose pwd is provided. + * Scans the DMB cells to find which one is unlocked by the provided pwd; then, + * using the decrypted VMB key, unlocks the M-th VMB; then, iteratively using + * the prev_vmb_key field, unlocks all the previous VMBs; then, using the + * decrypted VMB keys, opens the volumes "in order" from 1 to M. + * + * @param args->bdev_path The underlying block device + * @param args->pwd The password + * @param args->pwd_len The password length + * + * @return Error code (also if no volume could be opened), 0 on success + */ +int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) +{ + int64_t dev_size; + size_t nr_slices; + sflc_DmbCell dmb_cell; + sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; + size_t dev_id; + int err; + char *bdev_name; + char opendev_path[SFLC_BIGBUFSIZE]; + DIR* opendev_dir; + + /* Check if device is already opened and abort if so. */ + /* Get device name as : */ + bdev_name = sflc_disk_getDeviceName(args->bdev_path); + if(!bdev_name) { + sflc_log_error("Could not allocate device name"); + return ENOMEM; + } + /* Build sysfs path of opened device */ + sprintf(opendev_path, "%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name); + /* Step 2: check if directory exists. */ + opendev_dir = opendir(opendev_path); + if (opendev_dir) { + /* Directory exists. */ + closedir(opendev_dir); + err = EEXIST; + sflc_log_error("Device %s seems to be already open; error %d", args->bdev_path, err); + return err; + } + + /* Get number of slices */ + dev_size = sflc_disk_getSize(args->bdev_path); + if (dev_size < 0) { + err = -dev_size; + sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); + return err; + } + nr_slices = sflc_disk_maxSlices(dev_size); + + /* Find volume opened by the pwd */ + err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); + if (err) { + sflc_log_error("Could not read DMB; error %d", err); + return err; + } + /* Was there one? */ + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("The provided password opens no volume on the device"); + return EINVAL; + } + printf("Password is correct! Opening volumes...\n"); + + /* Unlock VMBs "backwards" */ + int i; // Needs sign, because loop ends on i>=0 + for (i = dmb_cell.vol_idx; i >= 0; i--) { + /* Which VMB key to use? */ + char *vmb_key; + if (i == dmb_cell.vol_idx) { + // The one unlocked by pwd + vmb_key = dmb_cell.vmb_key; + } else { + // Or the prev_vmb_key from last iteration + vmb_key = vmbs[i+1].prev_vmb_key; + } + + /* Read and unlock VMB */ + err = sfold_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not read VMB %d on device %s; error %d", + i, args->bdev_path, err); + return err; + } + } + + /* Get the ID that will be assigned to the block device */ + err = _getNextDevId(&dev_id); + if (err) { + sflc_log_error("Could not get next device ID; error %d", err); + return err; + } + sflc_log_debug("Next device ID is %lu", dev_id); + + /* Open volumes "in order" */ + for (i = 0; i <= dmb_cell.vol_idx; i++) { + err = sfold_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + if (err) { + sflc_log_error("Could not open volume %d; error %d. " + "Previous volumes on the device might have already " + "been opened, it's recommended you close them", + i, err); + return err; + } + sflc_log_debug("Successfully opened volume %d with VMB key", i); + printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); + } + + return 0; +} + + +/***************************************************** + * PRIVATE FUNCTIONS PROTOTYPES * + *****************************************************/ + +/* Read the next device ID in sysfs */ +static int _getNextDevId(size_t *next_dev_id) +{ + char *str_nextdevid; + int err; + + /* Read sysfs entry */ + str_nextdevid = sflc_readFile(SFLC_SYSFS_NEXTDEVID); + if (!str_nextdevid) { + sflc_log_error("Could not read sysfs entry %s", SFLC_SYSFS_NEXTDEVID); + return EINVAL; + } + + /* Parse integer */ + if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { + sflc_log_error("Error parsing content of file %s", SFLC_SYSFS_NEXTDEVID); + err = EINVAL; + goto err_devid; + } + /* Sanity check */ + if (*next_dev_id >= SFLC_TOT_MAX_DEVICES) { + sflc_log_error("There are already %d open devices, this is the maximum allowed", SFLC_TOT_MAX_DEVICES); + err = E2BIG; + goto err_devid; + } + + /* All good */ + err = 0; + + +err_devid: + free(str_nextdevid); + return err; +} + diff --git a/shufflecake-userland-lite/src/commands/open.c b/shufflecake-userland-lite/src/commands/open_lite.c similarity index 70% rename from shufflecake-userland-lite/src/commands/open.c rename to shufflecake-userland-lite/src/commands/open_lite.c index 11ecddd..5f4fec4 100644 --- a/shufflecake-userland-lite/src/commands/open.c +++ b/shufflecake-userland-lite/src/commands/open_lite.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/disk.h" #include "utils/file.h" @@ -63,33 +63,55 @@ static int _getNextDevId(size_t *next_dev_id); * * @return Error code (also if no volume could be opened), 0 on success */ -int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args) +int sflite_cmd_openVolumes(sflc_cmd_OpenArgs *args) { int64_t dev_size; size_t nr_slices; - sflite_DmbCell dmb_cell; - sflite_Vmb vmbs[SFLITE_DEV_MAX_VOLUMES]; + sflc_DmbCell dmb_cell; + sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; size_t dev_id; + char *bdev_name; + char opendev_path[SFLC_BIGBUFSIZE]; + DIR* opendev_dir; int err; + /* Check if device is already opened and abort if so. */ + /* Get device name as : */ + bdev_name = sflc_disk_getDeviceName(args->bdev_path); + if(!bdev_name) { + sflc_log_error("Could not allocate device name"); + return ENOMEM; + } + /* Build sysfs path of opened device */ + sprintf(opendev_path, "%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name); + /* Step 2: check if directory exists. */ + opendev_dir = opendir(opendev_path); + if (opendev_dir) { + /* Directory exists. */ + closedir(opendev_dir); + err = EEXIST; + sflc_log_error("Device %s seems to be already open; error %d", args->bdev_path, err); + return err; + } + /* Get number of slices */ - dev_size = sflite_disk_getSize(args->bdev_path); + dev_size = sflc_disk_getSize(args->bdev_path); if (dev_size < 0) { err = -dev_size; - sflite_log_error("Could not read device size for %s; error %d", args->bdev_path, err); + sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); return err; } nr_slices = sflite_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ - err = sflite_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); + err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); if (err) { - sflite_log_error("Could not read DMB; error %d", err); + sflc_log_error("Could not read DMB; error %d", err); return err; } /* Was there one? */ - if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("The provided password opens no volume on the device"); + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("The provided password opens no volume on the device"); return EINVAL; } printf("Password is correct! Opening volumes...\n"); @@ -110,7 +132,7 @@ int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args) /* Read and unlock VMB */ err = sflite_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); if (err) { - sflite_log_error("Could not read VMB %d on device %s; error %d", + sflc_log_error("Could not read VMB %d on device %s; error %d", i, args->bdev_path, err); return err; } @@ -119,22 +141,22 @@ int sflite_cmd_openVolumes(sflite_cmd_OpenArgs *args) /* Get the ID that will be assigned to the block device */ err = _getNextDevId(&dev_id); if (err) { - sflite_log_error("Could not get next device ID; error %d", err); + sflc_log_error("Could not get next device ID; error %d", err); return err; } - sflite_log_debug("Next device ID is %lu", dev_id); + sflc_log_debug("Next device ID is %lu", dev_id); /* Open volumes "in order" */ for (i = 0; i <= dmb_cell.vol_idx; i++) { err = sflite_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); if (err) { - sflite_log_error("Could not open volume %d; error %d. " + sflc_log_error("Could not open volume %d; error %d. " "Previous volumes on the device might have already " "been opened, it's recommended you close them", i, err); return err; } - sflite_log_debug("Successfully opened volume %d with VMB key", i); + sflc_log_debug("Successfully opened volume %d with VMB key", i); printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); } @@ -153,21 +175,21 @@ static int _getNextDevId(size_t *next_dev_id) int err; /* Read sysfs entry */ - str_nextdevid = sflite_readFile(SFLITE_SYSFS_NEXTDEVID); + str_nextdevid = sflc_readFile(SFLC_SYSFS_NEXTDEVID); if (!str_nextdevid) { - sflite_log_error("Could not read sysfs entry %s", SFLITE_SYSFS_NEXTDEVID); + sflc_log_error("Could not read sysfs entry %s", SFLC_SYSFS_NEXTDEVID); return EINVAL; } /* Parse integer */ if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { - sflite_log_error("Error parsing content of file %s", SFLITE_SYSFS_NEXTDEVID); + sflc_log_error("Error parsing content of file %s", SFLC_SYSFS_NEXTDEVID); err = EINVAL; goto err_devid; } /* Sanity check */ - if (*next_dev_id >= SFLITE_TOT_MAX_DEVICES) { - sflite_log_error("There are already %d open devices, this is the maximum allowed", SFLITE_TOT_MAX_DEVICES); + if (*next_dev_id >= SFLC_TOT_MAX_DEVICES) { + sflc_log_error("There are already %d open devices, this is the maximum allowed", SFLC_TOT_MAX_DEVICES); err = E2BIG; goto err_devid; } diff --git a/shufflecake-userland-lite/src/commands/test_pwd.c b/shufflecake-userland-lite/src/commands/test_pwd.c index 09cd5d3..f794347 100644 --- a/shufflecake-userland-lite/src/commands/test_pwd.c +++ b/shufflecake-userland-lite/src/commands/test_pwd.c @@ -31,7 +31,7 @@ #include "commands.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/log.h" @@ -52,8 +52,8 @@ * * @return Error code, 0 on success */ -int sflite_cmd_testPwd(sflite_cmd_OpenArgs *args, sflite_DmbCell *dmb_cell) +int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell) { /* Delegate entirely to the function reading the DMB */ - return sflite_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); + return sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); } diff --git a/shufflecake-userland-lite/src/header/volume_master_block_legacy.c b/shufflecake-userland-lite/src/header/volume_master_block_legacy.c index 30ab258..d8b7529 100644 --- a/shufflecake-userland-lite/src/header/volume_master_block_legacy.c +++ b/shufflecake-userland-lite/src/header/volume_master_block_legacy.c @@ -123,7 +123,7 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +int sfold_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; diff --git a/shufflecake-userland-lite/src/header/volume_master_block_lite.c b/shufflecake-userland-lite/src/header/volume_master_block_lite.c index 0561836..d61099f 100644 --- a/shufflecake-userland-lite/src/header/volume_master_block_lite.c +++ b/shufflecake-userland-lite/src/header/volume_master_block_lite.c @@ -123,7 +123,7 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; diff --git a/shufflecake-userland-lite/src/operations/devmapper_legacy.c b/shufflecake-userland-lite/src/operations/devmapper_legacy.c new file mode 100644 index 0000000..a0417da --- /dev/null +++ b/shufflecake-userland-lite/src/operations/devmapper_legacy.c @@ -0,0 +1,111 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include +#include + +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/crypto.h" +#include "utils/file.h" +#include "utils/string.h" +#include "utils/dm.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Build parameter list for ctor in dm_sflc, and send DM ioctl to create + * virtual block device. + * + * @param bdev_path The path to the underlying device + * @param dev_id The ID of the underlying block device + * @param vol_idx The index of the volume within the device + * @param vmb Volume metadata + * + * @return Error code, 0 on success + */ +int sfold_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) +{ + char label[SFLC_BIGBUFSIZE]; + char *hex_key; + char params[SFLC_BIGBUFSIZE]; + uint64_t num_sectors; + int err; + + /* Build volume label */ + sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); + + /* Get the hex version of the volume's data section key */ + hex_key = sflc_toHex(vmb->volume_key_legacy, SFLC_STANDARD_KEYLEN); + if (!hex_key) { + sflc_log_error("Could not encode volume key to hexadecimal"); + err = ENOMEM; + goto err_hexkey; + } + + /* Get the number of logical 512-byte sectors composing the volume */ + num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_SLICE_SCALE * SFLC_BLOCK_SCALE; + + /* Build param list */ + sprintf(params, "%d %lu %s %lu %lu %s", SFLC_MODE_LEGACY, dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); + + /* Issue ioctl */ + err = sflc_dm_create(label, num_sectors, params); + if (err) { + sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + goto err_dmcreate; + } + err = 0; + + +err_dmcreate: + free(hex_key); +err_hexkey: + return err; +} + + +/** + * Close the volume by issuing the appropriate ioctl to the DM. + * + * @param label The only needed parameter: the ID of the volume. + * + * @return Error code, 0 on success + */ +int sfold_ops_closeVolume(char *label) +{ + /* Issue ioctl */ + return sflc_dm_destroy(label); +} diff --git a/shufflecake-userland-lite/src/operations/devmapper.c b/shufflecake-userland-lite/src/operations/devmapper_lite.c similarity index 83% rename from shufflecake-userland-lite/src/operations/devmapper.c rename to shufflecake-userland-lite/src/operations/devmapper_lite.c index 1d0ebae..5dc610b 100644 --- a/shufflecake-userland-lite/src/operations/devmapper.c +++ b/shufflecake-userland-lite/src/operations/devmapper_lite.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/crypto.h" #include "utils/file.h" #include "utils/string.h" @@ -46,7 +46,7 @@ *****************************************************/ /** - * Build parameter list for ctor in dm_sflite, and send DM ioctl to create + * Build parameter list for ctor in dm_sflc, and send DM ioctl to create * virtual block device. * * @param bdev_path The path to the underlying device @@ -56,11 +56,11 @@ * * @return Error code, 0 on success */ -int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflite_Vmb *vmb) +int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) { - char label[SFLITE_BIGBUFSIZE]; + char label[SFLC_BIGBUFSIZE]; char *hex_key; - char params[SFLITE_BIGBUFSIZE]; + char params[SFLC_BIGBUFSIZE]; uint64_t num_sectors; int err; @@ -68,23 +68,23 @@ int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflite sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); /* Get the hex version of the volume's data section key */ - hex_key = sflite_toHex(vmb->volume_key, SFLITE_AESXTS_KEYLEN); + hex_key = sflc_toHex(vmb->volume_key_lite, SFLC_AESXTS_KEYLEN); if (!hex_key) { - sflite_log_error("Could not encode volume key to hexadecimal"); + sflc_log_error("Could not encode volume key to hexadecimal"); err = ENOMEM; goto err_hexkey; } /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * SFLITE_SLICE_SCALE * SFLITE_BLOCK_SCALE; + num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_SLICE_SCALE * SFLC_BLOCK_SCALE; /* Build param list */ sprintf(params, "%d %lu %s %lu %lu %s", SFLC_MODE_LITE, dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); /* Issue ioctl */ - err = sflite_dm_create(label, num_sectors, params); + err = sflc_dm_create(label, num_sectors, params); if (err) { - sflite_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); + sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); goto err_dmcreate; } err = 0; @@ -108,5 +108,5 @@ err_hexkey: int sflite_ops_closeVolume(char *label) { /* Issue ioctl */ - return sflite_dm_destroy(label); + return sflc_dm_destroy(label); } diff --git a/shufflecake-userland-lite/src/operations/dmb.c b/shufflecake-userland-lite/src/operations/dmb.c index 31569e7..662742e 100644 --- a/shufflecake-userland-lite/src/operations/dmb.c +++ b/shufflecake-userland-lite/src/operations/dmb.c @@ -33,7 +33,7 @@ #include "header.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -54,30 +54,30 @@ * * @return Error code, 0 on success */ -int sflite_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflite_Dmb *dmb) +int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) { /* On-disk DMB */ - char enc_dmb[SFLITE_BLOCK_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; /* Error code */ int err; /* Sanity check */ - if (dmb->nr_vols > SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); + if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); return EINVAL; } /* Seal DMB */ - err = sflite_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); + err = sflc_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); if (err) { - sflite_log_error("Coul dnot seal DMB; error %d", err); + sflc_log_error("Coul dnot seal DMB; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = sflite_disk_writeBlock(bdev_path, 0, enc_dmb); + err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - sflite_log_error("Could not write DMB to disk; error %d", err); + sflc_log_error("Could not write DMB to disk; error %d", err); return err; } @@ -93,26 +93,26 @@ int sflite_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflite_D * @param pwd_len Its length * * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLITE_DEV_MAX_VOLUMES if none could be unlocked) + * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) * * @return Error code, 0 on success */ -int sflite_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflite_DmbCell *dmb) +int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) { - char enc_dmb[SFLITE_BLOCK_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; int err; /* Read DMB from disk (at sector 0) */ - err = sflite_disk_readBlock(bdev_path, 0, enc_dmb); + err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - sflite_log_error("Could not read DMB from disk; error %d", err); + sflc_log_error("Could not read DMB from disk; error %d", err); return err; } /* Unseal it */ - err = sflite_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); + err = sflc_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); if (err) { - sflite_log_error("Could not unseal DMB; error %d", err); + sflc_log_error("Could not unseal DMB; error %d", err); return err; } @@ -129,35 +129,35 @@ int sflite_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflite_DmbCel * * @return Error code, 0 on success */ -int sflite_ops_rewriteDmbCell(char *bdev_path, sflite_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) +int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) { - char enc_dmb[SFLITE_BLOCK_SIZE]; + char enc_dmb[SFLC_BLOCK_SIZE]; int err; /* Sanity check */ - if (dmb_cell->vol_idx >= SFLITE_DEV_MAX_VOLUMES) { - sflite_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); + if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { + sflc_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); return EINVAL; } /* Read DMB from disk (at sector 0) */ - err = sflite_disk_readBlock(bdev_path, 0, enc_dmb); + err = sflc_disk_readBlock(bdev_path, 0, enc_dmb); if (err) { - sflite_log_error("Could not read DMB from disk; error %d", err); + sflc_log_error("Could not read DMB from disk; error %d", err); return err; } /* Update the relevant cell */ - err = sflite_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); + err = sflc_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); if (err) { - sflite_log_error("Could not update DMB cell; error %d", err); + sflc_log_error("Could not update DMB cell; error %d", err); return err; } /* Write it to disk (at sector 0) */ - err = sflite_disk_writeBlock(bdev_path, 0, enc_dmb); + err = sflc_disk_writeBlock(bdev_path, 0, enc_dmb); if (err) { - sflite_log_error("Could not write DMB to disk; error %d", err); + sflc_log_error("Could not write DMB to disk; error %d", err); return err; } diff --git a/shufflecake-userland-lite/src/operations/volume_header_legacy.c b/shufflecake-userland-lite/src/operations/volume_header_legacy.c new file mode 100644 index 0000000..44ec50c --- /dev/null +++ b/shufflecake-userland-lite/src/operations/volume_header_legacy.c @@ -0,0 +1,165 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include +#include + +#include "header.h" +#include "header.h" +#include "operations.h" +#include "utils/sflc.h" +#include "utils/disk.h" +#include "utils/crypto.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +/** + * Writes a volume header (VMB+PM) on-disk. + * + * @param bdev_path The underlying block device to write the volume header + * @param vmb_key The key to encrypt the VMB + * @param vmb The VMB to encrypt + * @param vol_idx The index of the volume within the device + * + * @return Error code, 0 on success + */ +int sfold_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) +{ + char enc_vmb[SFLC_BLOCK_SIZE]; + sfold_EncPosMap epm; + uint64_t sector; + int err; + + // Encrypt VMB + err = sfold_vmb_seal(vmb, vmb_key, enc_vmb); + if (err) { + sflc_log_error("Could not seal VMB; error %d", err); + goto out; + } + + // Write it to disk + sector = sfold_vmbPosition(vol_idx, vmb->nr_slices); + err = sflc_disk_writeBlock(bdev_path, sector, enc_vmb); + if (err) { + sflc_log_error("Could not write VMB to disk; error %d", err); + goto out; + } + sector += 1; + + // Create encrypted empty position map + err = sfold_epm_create(vmb->nr_slices, vmb->volume_key_legacy, &epm); + if (err) { + sflc_log_error("Could not create encrypted empty position map; error %d", err); + goto out; + } + + // Loop over PMB arrays to write them to disk + int i; + for (i = 0; i < epm.nr_arrays; i++) { + char *iv_block = epm.iv_blocks[i]; + char *pmb_array = epm.pmb_arrays[i]; + size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_SLICE_SCALE); + + // First write the IV block + err = sflc_disk_writeBlock(bdev_path, sector, iv_block); + if (err) { + sflc_log_error("Could not write IV block to disk; error %d", err); + goto out; + } + sector += 1; + + // Then the whole PMB array + err = sflc_disk_writeManyBlocks(bdev_path, sector, pmb_array, nr_pmbs); + if (err) { + sflc_log_error("Could not write PMB array to disk; error %d", err); + goto out; + } + sector += nr_pmbs; + + // Free them both + free(iv_block); + free(pmb_array); + } + + // Free containers + free(epm.iv_blocks); + free(epm.pmb_arrays); + + +out: + return err; +} + + +/** + * Reads a VMB from disk and unlocks it + * + * @param bdev_path The underlying block device + * @param vmb_key The key to decrypt the VMB + * @param nr_slices The number of slices in the device + * @param vol_idx The index of the volume within the device + * + * @output vmb The decrypted VMB + * + * @return Error code, 0 on success + */ +int sfold_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) +{ + char enc_vmb[SFLC_BLOCK_SIZE]; + uint64_t sector; + int err; + + /* Read encrypted VMB from disk */ + sector = sfold_vmbPosition(vol_idx, nr_slices); + err = sflc_disk_readBlock(bdev_path, sector, enc_vmb); + if (err) { + sflc_log_error("Could not read VMB from disk; error %d", err); + return err; + } + + /* Unseal it */ + err = sfold_vmb_unseal(enc_vmb, vmb_key, vmb); + if (err) { + sflc_log_error("Could not unseal VMB; error %d", err); + return err; + } + + /* Compare the number of slices */ + if (nr_slices != vmb->nr_slices) { + sflc_log_error("Incompatible header size: the device size was different when the volumes" + "were created. Did you resize the device %s since last time?", bdev_path); + return EINVAL; + } + + return 0; +} diff --git a/shufflecake-userland-lite/src/operations/volume_header.c b/shufflecake-userland-lite/src/operations/volume_header_lite.c similarity index 79% rename from shufflecake-userland-lite/src/operations/volume_header.c rename to shufflecake-userland-lite/src/operations/volume_header_lite.c index 40e0fda..ce649ab 100644 --- a/shufflecake-userland-lite/src/operations/volume_header.c +++ b/shufflecake-userland-lite/src/operations/volume_header_lite.c @@ -33,7 +33,7 @@ #include "header.h" #include "header.h" #include "operations.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/disk.h" #include "utils/crypto.h" #include "utils/log.h" @@ -53,9 +53,9 @@ * * @return Error code, 0 on success */ -int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflite_Vmb *vmb, size_t vol_idx) +int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) { - char enc_vmb[SFLITE_BLOCK_SIZE]; + char enc_vmb[SFLC_BLOCK_SIZE]; void *epm; uint64_t sector; int err; @@ -63,32 +63,32 @@ int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflite_Vmb *vmb // Encrypt VMB err = sflite_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { - sflite_log_error("Could not seal VMB; error %d", err); + sflc_log_error("Could not seal VMB; error %d", err); goto out; } // Write it to disk sector = 1 + vol_idx; - err = sflite_disk_writeBlock(bdev_path, sector, enc_vmb); + err = sflc_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { - sflite_log_error("Could not write VMB to disk; error %d", err); + sflc_log_error("Could not write VMB to disk; error %d", err); goto out; } // Create encrypted empty position map - epm = sflite_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key); + epm = sflite_epm_create(vmb->nr_slices, vol_idx, vmb->volume_key_lite); if (!epm) { - sflite_log_error("Could not create encrypted empty position map."); + sflc_log_error("Could not create encrypted empty position map."); err = ENOMEM; goto out; } // Write to disk sector = sflite_pmStartBlock(vol_idx, vmb->nr_slices); - err = sflite_disk_writeManyBlocks(bdev_path, sector, epm, - ceil(vmb->nr_slices, SFLITE_SLICE_IDX_PER_BLOCK)); + err = sflc_disk_writeManyBlocks(bdev_path, sector, epm, + ceil(vmb->nr_slices, SFLC_SLICE_IDX_PER_BLOCK)); if (err) { - sflite_log_error("Could not write encrypted PosMap; error %d", err); + sflc_log_error("Could not write encrypted PosMap; error %d", err); } free(epm); @@ -110,30 +110,30 @@ out: * * @return Error code, 0 on success */ -int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflite_Vmb *vmb) +int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) { - char enc_vmb[SFLITE_BLOCK_SIZE]; + char enc_vmb[SFLC_BLOCK_SIZE]; uint64_t sector; int err; /* Read encrypted VMB from disk */ sector = 1 + vol_idx; - err = sflite_disk_readBlock(bdev_path, sector, enc_vmb); + err = sflc_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { - sflite_log_error("Could not read VMB from disk; error %d", err); + sflc_log_error("Could not read VMB from disk; error %d", err); return err; } /* Unseal it */ err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb); if (err) { - sflite_log_error("Could not unseal VMB; error %d", err); + sflc_log_error("Could not unseal VMB; error %d", err); return err; } /* Compare the number of slices */ if (nr_slices != vmb->nr_slices) { - sflite_log_error("Incompatible header size: the device size was different when the volumes" + sflc_log_error("Incompatible header size: the device size was different when the volumes" "were created. Did you resize the device %s since last time?", bdev_path); return EINVAL; } From 5f25d8f90b27595c032afcdf56af09201ee2c1aa Mon Sep 17 00:00:00 2001 From: = Date: Mon, 26 Aug 2024 23:30:00 +0200 Subject: [PATCH 67/98] Merge everything else --- shufflecake-userland-lite/Makefile | 4 +- shufflecake-userland-lite/Makefile.sources | 6 +- shufflecake-userland-lite/include/cli.h | 22 ++--- .../include/utils/disk.h | 2 +- shufflecake-userland-lite/src/cli/changepwd.c | 32 +++--- shufflecake-userland-lite/src/cli/close.c | 6 +- shufflecake-userland-lite/src/cli/dispatch.c | 97 +++++++++++-------- shufflecake-userland-lite/src/cli/init.c | 45 +++++---- shufflecake-userland-lite/src/cli/open.c | 26 +++-- shufflecake-userland-lite/src/cli/testpwd.c | 22 ++--- .../src/commands/close.c | 2 +- .../src/commands/open_legacy.c | 2 +- .../src/commands/open_lite.c | 1 + .../src/header/position_map_lite.c | 6 +- shufflecake-userland-lite/src/main.c | 2 +- shufflecake-userland-lite/src/utils/file.c | 4 +- 16 files changed, 157 insertions(+), 122 deletions(-) diff --git a/shufflecake-userland-lite/Makefile b/shufflecake-userland-lite/Makefile index c0d4151..ee59323 100644 --- a/shufflecake-userland-lite/Makefile +++ b/shufflecake-userland-lite/Makefile @@ -61,10 +61,10 @@ DEPS := $(PROJ_DEPS) $(TEST_DEPS) DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) # The target binaries -MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake-lite +MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake TEST_BIN := $(TEST_OUT_DIR)/tests # Their symlink -MAIN_LINK := shufflecake-lite +MAIN_LINK := shufflecake TEST_LINK := tests diff --git a/shufflecake-userland-lite/Makefile.sources b/shufflecake-userland-lite/Makefile.sources index 0e2a80b..d2313c8 100644 --- a/shufflecake-userland-lite/Makefile.sources +++ b/shufflecake-userland-lite/Makefile.sources @@ -28,9 +28,9 @@ #### PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c) -PROJ_SRCS += $(addprefix header/,position_map.c volume_master_block.c device_master_block.c) -PROJ_SRCS += $(addprefix operations/,volume_header.c devmapper.c dmb.c) -PROJ_SRCS += $(addprefix commands/,init.c open.c close.c test_pwd.c change_pwd.c) +PROJ_SRCS += $(addprefix header/,position_map_legacy.c position_map_lite.c volume_master_block_legacy.c volume_master_block_lite.c device_master_block.c) +PROJ_SRCS += $(addprefix operations/,volume_header_legacy.c volume_header_lite.c devmapper_legacy.c devmapper_lite.c dmb.c) +PROJ_SRCS += $(addprefix commands/,init_legacy.c init_lite.c open_legacy.c open_lite.c close.c test_pwd.c change_pwd.c) PROJ_SRCS += $(addprefix cli/,dispatch.c init.c open.c close.c testpwd.c changepwd.c) PROJ_SRCS += main.c diff --git a/shufflecake-userland-lite/include/cli.h b/shufflecake-userland-lite/include/cli.h index f175931..1c53043 100644 --- a/shufflecake-userland-lite/include/cli.h +++ b/shufflecake-userland-lite/include/cli.h @@ -30,15 +30,15 @@ *****************************************************/ /* Action to create volumes */ -#define SFLITE_CLI_INITACT "init" +#define SFLC_CLI_INITACT "init" /* Action to open volumes */ -#define SFLITE_CLI_OPENACT "open" +#define SFLC_CLI_OPENACT "open" /* Action to close volumes */ -#define SFLITE_CLI_CLOSEACT "close" +#define SFLC_CLI_CLOSEACT "close" /* Action to test password */ -#define SFLITE_CLI_TESTPWDACT "testpwd" +#define SFLC_CLI_TESTPWDACT "testpwd" /* Action to change password */ -#define SFLITE_CLI_CHANGEPWDACT "changepwd" +#define SFLC_CLI_CHANGEPWDACT "changepwd" /***************************************************** @@ -46,18 +46,18 @@ *****************************************************/ /* Called by the main to parse the arguments and dispatch to the right command */ -int sflite_cli_dispatch(int argc, char **argv); +int sflc_cli_dispatch(int argc, char **argv); /* Initializes device and create empty volumes */ -int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill); +int sflc_cli_init(char *block_device, int sflc_mode, int num_volumes, int skip_randfill); /* Open volumes */ -int sflite_cli_open(char *block_device); +int sflc_cli_open(char *block_device, int sflc_mode); /* Close volumes */ -int sflite_cli_close(char *block_device); +int sflc_cli_close(char *block_device); /* Test password */ -int sflite_cli_testPwd(char *block_device); +int sflc_cli_testPwd(char *block_device); /* Change password */ -int sflite_cli_changePwd(char *block_device); +int sflc_cli_changePwd(char *block_device); #endif /* _CLI_H_ */ diff --git a/shufflecake-userland-lite/include/utils/disk.h b/shufflecake-userland-lite/include/utils/disk.h index 954ebce..426921f 100644 --- a/shufflecake-userland-lite/include/utils/disk.h +++ b/shufflecake-userland-lite/include/utils/disk.h @@ -97,7 +97,7 @@ int sflc_disk_readBlock(char *bdev_path, uint64_t bnum, char *buf); int sflc_disk_writeManyBlocks(char *bdev_path, uint64_t bnum, char *buf, size_t num_blocks); /* Writes a single 4096-byte block to the disk */ -#define sflc_disk_writeBlock(bdev_path, bnum, buf) sflite_disk_writeManyBlocks(bdev_path, bnum, buf, 1) +#define sflc_disk_writeBlock(bdev_path, bnum, buf) sflc_disk_writeManyBlocks(bdev_path, bnum, buf, 1) #endif /* _UTILS_DISK_H_ */ diff --git a/shufflecake-userland-lite/src/cli/changepwd.c b/shufflecake-userland-lite/src/cli/changepwd.c index a4f85e7..de37779 100644 --- a/shufflecake-userland-lite/src/cli/changepwd.c +++ b/shufflecake-userland-lite/src/cli/changepwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -45,14 +45,14 @@ * * @return Error code, 0 on success */ -int sflite_cli_changePwd(char *block_device) +int sflc_cli_changePwd(char *block_device) { // Requires: block_device is a correct block device path - sflite_cmd_OpenArgs open_args; - sflite_cmd_ChangePwdArgs change_pwd_args; - sflite_DmbCell dmb_cell; - char old_pwd[SFLITE_BIGBUFSIZE]; + sflc_cmd_OpenArgs open_args; + sflc_cmd_ChangePwdArgs change_pwd_args; + sflc_DmbCell dmb_cell; + char old_pwd[SFLC_BIGBUFSIZE]; size_t old_pwd_len; - char new_pwd[SFLITE_BIGBUFSIZE]; + char new_pwd[SFLC_BIGBUFSIZE]; size_t new_pwd_len; int err; @@ -60,9 +60,9 @@ int sflite_cli_changePwd(char *block_device) /* Gather password */ printf("Enter the password you want to change: "); - err = sflite_safeReadPassphrase(old_pwd, SFLITE_BIGBUFSIZE); + err = sflc_safeReadPassphrase(old_pwd, SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Could not read password; error %d", err); + sflc_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -72,14 +72,14 @@ int sflite_cli_changePwd(char *block_device) open_args.pwd_len = old_pwd_len; /* Test the password */ - err = sflite_cmd_testPwd(&open_args, &dmb_cell); + err = sflc_cmd_testPwd(&open_args, &dmb_cell); if (err) { - sflite_log_error("Could not test password; error %d", err); + sflc_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); return 0; } @@ -87,9 +87,9 @@ int sflite_cli_changePwd(char *block_device) /* Gather new password (no secure shell) */ printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); - err = sflite_safeReadLine(new_pwd, SFLITE_BIGBUFSIZE); + err = sflc_safeReadLine(new_pwd, SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Could not read new password; error %d", err); + sflc_log_error("Could not read new password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -102,9 +102,9 @@ int sflite_cli_changePwd(char *block_device) change_pwd_args.new_pwd_len = new_pwd_len; /* Change password */ - err = sflite_cmd_changePwd(&change_pwd_args); + err = sflc_cmd_changePwd(&change_pwd_args); if (err) { - sflite_log_error("Could not change password; error %d", err); + sflc_log_error("Could not change password; error %d", err); return err; } printf("Password changed successfully.\n"); diff --git a/shufflecake-userland-lite/src/cli/close.c b/shufflecake-userland-lite/src/cli/close.c index ecedda2..da4c02f 100644 --- a/shufflecake-userland-lite/src/cli/close.c +++ b/shufflecake-userland-lite/src/cli/close.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -45,7 +45,7 @@ * * @return Error code, 0 on success */ -int sflite_cli_close(char *block_device) +int sflc_cli_close(char *block_device) { // Requires: block_device is a correct block device path // char bdev_path[SFLITE_BDEV_PATH_MAX_LEN + 2]; // int err; @@ -69,6 +69,6 @@ int sflite_cli_close(char *block_device) // return sflite_cmd_closeVolumes(bdev_path); - return sflite_cmd_closeVolumes(block_device); + return sflc_cmd_closeVolumes(block_device); } diff --git a/shufflecake-userland-lite/src/cli/dispatch.c b/shufflecake-userland-lite/src/cli/dispatch.c index 871b751..0bed6ff 100644 --- a/shufflecake-userland-lite/src/cli/dispatch.c +++ b/shufflecake-userland-lite/src/cli/dispatch.c @@ -32,7 +32,7 @@ #include #include "cli.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/disk.h" #include "utils/log.h" @@ -47,24 +47,25 @@ const char *argp_program_version = "0.5.0"; // Temporary hack const char *argp_program_bug_address = ""; /* Signed integer values representing a handle for each option */ -#define SFLITE_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option -#define SFLITE_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this +#define SFLC_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option +#define SFLC_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this +#define SFLC_OPT_LEGACY_KEY (-'l') /***************************************************** * TYPES * *****************************************************/ -enum sflite_cli_action { - SFLITE_ACT_INIT, - SFLITE_ACT_OPEN, - SFLITE_ACT_CLOSE, - SFLITE_ACT_TESTPWD, - SFLITE_ACT_CHANGEPWD +enum sflc_cli_action { + SFLC_ACT_INIT, + SFLC_ACT_OPEN, + SFLC_ACT_CLOSE, + SFLC_ACT_TESTPWD, + SFLC_ACT_CHANGEPWD }; -struct sflite_cli_arguments { - enum sflite_cli_action act; +struct sflc_cli_arguments { + enum sflc_cli_action act; char *block_device; int sflc_mode; int num_volumes; @@ -111,10 +112,11 @@ static char doc[] = /* Description of each option */ static struct argp_option options[] = { - {"num-volumes", SFLITE_OPT_NUMVOLS_KEY, "num", 0, + {"num-volumes", SFLC_OPT_NUMVOLS_KEY, "num", 0, "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 - {"skip-randfill", SFLITE_OPT_SKIPRAND_KEY, 0, 0, + {"skip-randfill", SFLC_OPT_SKIPRAND_KEY, 0, 0, "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, + {"legacy", SFLC_OPT_LEGACY_KEY, 0, 0, "Use the old (pre-v0.5.0) Shufflecake format. Only valid with `init` and `open'. Use of this option is not recommended. This mode is going to be deprecated in future versions."}, {0} }; @@ -136,11 +138,12 @@ static struct argp argp = {options, _parseArgpKey, args_doc, doc}; * @return Error code, 0 on success */ -int sflite_cli_dispatch(int argc, char **argv) { - struct sflite_cli_arguments arguments; +int sflc_cli_dispatch(int argc, char **argv) { + struct sflc_cli_arguments arguments; arguments.act = -1; arguments.block_device = NULL; + arguments.sflc_mode = SFLC_MODE_LITE; arguments.num_volumes = 0; arguments.skip_randfill = false; @@ -148,36 +151,43 @@ int sflite_cli_dispatch(int argc, char **argv) { argp_parse(&argp, argc, argv, 0, 0, &arguments); /* Check options consistency */ - if (arguments.num_volumes && arguments.act != SFLITE_ACT_INIT) { + if (arguments.num_volumes && arguments.act != SFLC_ACT_INIT) { printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); return EINVAL; } /* Check options consistency */ - if (arguments.skip_randfill && arguments.act != SFLITE_ACT_INIT) { + if (arguments.sflc_mode == SFLC_MODE_LEGACY && + arguments.act != SFLC_ACT_INIT && + arguments.act != SFLC_ACT_OPEN) { + printf("Error: --legacy (-l) can only be combined with `init' or 'open'.\n"); + return EINVAL; + } + /* Check options consistency */ + if (arguments.skip_randfill && arguments.act != SFLC_ACT_INIT) { printf("Error: --skip-randfill can only be combined with `init'.\n"); return EINVAL; } /* Check that input is actually a block device */ - if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflite_disk_isBlockDevice(arguments.block_device)) { + if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflc_disk_isBlockDevice(arguments.block_device)) { printf("Error: '%s' is not a valid block device.\n", arguments.block_device); return EINVAL; } /* Dispatch to specific command */ - if (arguments.act == SFLITE_ACT_INIT) { - return sflite_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); + if (arguments.act == SFLC_ACT_INIT) { + return sflc_cli_init(arguments.block_device, arguments.sflc_mode, arguments.num_volumes, arguments.skip_randfill); } - if (arguments.act == SFLITE_ACT_OPEN) { - return sflite_cli_open(arguments.block_device); + if (arguments.act == SFLC_ACT_OPEN) { + return sflc_cli_open(arguments.block_device, arguments.sflc_mode); } - if (arguments.act == SFLITE_ACT_CLOSE) { - return sflite_cli_close(arguments.block_device); + if (arguments.act == SFLC_ACT_CLOSE) { + return sflc_cli_close(arguments.block_device); } - if (arguments.act == SFLITE_ACT_TESTPWD) { - return sflite_cli_testPwd(arguments.block_device); + if (arguments.act == SFLC_ACT_TESTPWD) { + return sflc_cli_testPwd(arguments.block_device); } - if (arguments.act == SFLITE_ACT_CHANGEPWD) { - return sflite_cli_changePwd(arguments.block_device); + if (arguments.act == SFLC_ACT_CHANGEPWD) { + return sflc_cli_changePwd(arguments.block_device); } printf("\n"); @@ -191,26 +201,26 @@ int sflite_cli_dispatch(int argc, char **argv) { *****************************************************/ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { - struct sflite_cli_arguments *arguments = state->input; + struct sflc_cli_arguments *arguments = state->input; switch (key) { /* We are parsing an argument (not an option) */ case ARGP_KEY_ARG: /* We are parsing the command */ if (state->arg_num == 0) { - if (strcmp(arg, SFLITE_CLI_INITACT) == 0) { - arguments->act = SFLITE_ACT_INIT; - } else if (strcmp(arg, SFLITE_CLI_OPENACT) == 0) { - arguments->act = SFLITE_ACT_OPEN; - } else if (strcmp(arg, SFLITE_CLI_CLOSEACT) == 0) { - arguments->act = SFLITE_ACT_CLOSE; - } else if (strcmp(arg, SFLITE_CLI_TESTPWDACT) == 0) { - arguments->act = SFLITE_ACT_TESTPWD; - } else if (strcmp(arg, SFLITE_CLI_CHANGEPWDACT) == 0) { - arguments->act = SFLITE_ACT_CHANGEPWD; + if (strcmp(arg, SFLC_CLI_INITACT) == 0) { + arguments->act = SFLC_ACT_INIT; + } else if (strcmp(arg, SFLC_CLI_OPENACT) == 0) { + arguments->act = SFLC_ACT_OPEN; + } else if (strcmp(arg, SFLC_CLI_CLOSEACT) == 0) { + arguments->act = SFLC_ACT_CLOSE; + } else if (strcmp(arg, SFLC_CLI_TESTPWDACT) == 0) { + arguments->act = SFLC_ACT_TESTPWD; + } else if (strcmp(arg, SFLC_CLI_CHANGEPWDACT) == 0) { + arguments->act = SFLC_ACT_CHANGEPWD; } else { argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", - SFLITE_CLI_INITACT, SFLITE_CLI_OPENACT, SFLITE_CLI_CLOSEACT, SFLITE_CLI_TESTPWDACT, SFLITE_CLI_CHANGEPWDACT); + SFLC_CLI_INITACT, SFLC_CLI_OPENACT, SFLC_CLI_CLOSEACT, SFLC_CLI_TESTPWDACT, SFLC_CLI_CHANGEPWDACT); } /* We are parsing the block device */ } else if (state->arg_num == 1) { @@ -222,10 +232,13 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { break; /* We are parsing an option */ - case SFLITE_OPT_NUMVOLS_KEY: + case SFLC_OPT_NUMVOLS_KEY: arguments->num_volumes = atoi(arg); break; - case SFLITE_OPT_SKIPRAND_KEY: + case SFLC_OPT_LEGACY_KEY: + arguments->sflc_mode = SFLC_MODE_LEGACY; + break; + case SFLC_OPT_SKIPRAND_KEY: arguments->skip_randfill = true; break; diff --git a/shufflecake-userland-lite/src/cli/init.c b/shufflecake-userland-lite/src/cli/init.c index 0639bb3..85f5074 100644 --- a/shufflecake-userland-lite/src/cli/init.c +++ b/shufflecake-userland-lite/src/cli/init.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -45,30 +45,31 @@ * * @return Error code, 0 on success */ -int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) +int sflc_cli_init(char *block_device, int sflc_mode, int num_volumes, int skip_randfill) { // Requires: block_device is a correct block device path - sflite_cmd_InitArgs args; - char str_nrvols[SFLITE_BIGBUFSIZE]; - char *pwds[SFLITE_DEV_MAX_VOLUMES]; - size_t pwd_lens[SFLITE_DEV_MAX_VOLUMES]; + sflc_cmd_InitArgs args; + char str_nrvols[SFLC_BIGBUFSIZE]; + char *pwds[SFLC_DEV_MAX_VOLUMES]; + size_t pwd_lens[SFLC_DEV_MAX_VOLUMES]; int err; args.bdev_path = block_device; + args.sflc_mode = sflc_mode; // Check if number of volumes was nonzero passed by command line already if (num_volumes) { args.nr_vols = num_volumes; } else { // If not, ask user for number of volumes - printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLITE_DEV_MAX_VOLUMES); - err = sflite_safeReadLine(str_nrvols, SFLITE_BIGBUFSIZE); + printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLC_DEV_MAX_VOLUMES); + err = sflc_safeReadLine(str_nrvols, SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Error: could not read number of volumes; error %d", err); + sflc_log_error("Error: could not read number of volumes; error %d", err); return err; } /* Parse string */ if (sscanf(str_nrvols, "%lu\n", &args.nr_vols) != 1) { - sflite_log_error("Error: could not parse number of volumes"); + sflc_log_error("Error: could not parse number of volumes"); return EINVAL; } } @@ -78,8 +79,8 @@ int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) printf("Error: number of volumes must be a positive integer"); return EINVAL; } - if (args.nr_vols > SFLITE_DEV_MAX_VOLUMES) { - printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLITE_DEV_MAX_VOLUMES); + if (args.nr_vols > SFLC_DEV_MAX_VOLUMES) { + printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLC_DEV_MAX_VOLUMES); return EINVAL; } @@ -89,13 +90,13 @@ int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) size_t i; for (i = 0; i < args.nr_vols; i++) { // Allocate pwd - pwds[i] = malloc(SFLITE_BIGBUFSIZE); + pwds[i] = malloc(SFLC_BIGBUFSIZE); /* Read it */ printf("Choose password for volume %lu (must not be empty): ", i); - err = sflite_safeReadLine(pwds[i], SFLITE_BIGBUFSIZE); + err = sflc_safeReadLine(pwds[i], SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Could not read password for volume %lu; error %d", i, err); + sflc_log_error("Could not read password for volume %lu; error %d", i, err); return err; } @@ -103,7 +104,7 @@ int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) pwd_lens[i] = strlen(pwds[i]); /* Check non-empty */ if (pwd_lens[i] == 0) { - sflite_log_error("Password cannot be empty!"); + sflc_log_error("Password cannot be empty!"); return EINVAL; } } @@ -114,5 +115,15 @@ int sflite_cli_init(char *block_device, int num_volumes, int skip_randfill) args.no_randfill = skip_randfill; /* Actually perform the command */ - return sflite_cmd_initVolumes(&args); + switch(sflc_mode) + { + case SFLC_MODE_LEGACY: + return sfold_cmd_initVolumes(&args); + case SFLC_MODE_LITE: + return sflite_cmd_initVolumes(&args); + default: + sflc_log_error("WTF, sflc_mode out of bounds!"); + return ENOTRECOVERABLE; + } + } diff --git a/shufflecake-userland-lite/src/cli/open.c b/shufflecake-userland-lite/src/cli/open.c index bc6d70b..ae1cb8d 100644 --- a/shufflecake-userland-lite/src/cli/open.c +++ b/shufflecake-userland-lite/src/cli/open.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -45,10 +45,10 @@ * * @return Error code, 0 on success */ -int sflite_cli_open(char *block_device) +int sflc_cli_open(char *block_device, int sflc_mode) { // Requires: block_device is a correct block device path - sflite_cmd_OpenArgs args; - char pwd[SFLITE_BIGBUFSIZE]; + sflc_cmd_OpenArgs args; + char pwd[SFLC_BIGBUFSIZE]; size_t pwd_len; int err; @@ -56,16 +56,16 @@ int sflite_cli_open(char *block_device) /* Gather password */ printf("Enter the password for the most secret volume you want to open: "); - err = sflite_safeReadPassphrase(pwd, SFLITE_BIGBUFSIZE); + err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Could not read password; error %d", err); + sflc_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ pwd_len = strlen(pwd); /* Check non-empty */ if (pwd_len == 0) { - sflite_log_error("Password cannot be empty!"); + sflc_log_error("Password cannot be empty!"); return EINVAL; } /* Assign them */ @@ -73,5 +73,15 @@ int sflite_cli_open(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - return sflite_cmd_openVolumes(&args); + /* Actually perform the command */ + switch(sflc_mode) + { + case SFLC_MODE_LEGACY: + return sfold_cmd_openVolumes(&args); + case SFLC_MODE_LITE: + return sflite_cmd_openVolumes(&args); + default: + sflc_log_error("WTF, sflc_mode out of bounds!"); + return ENOTRECOVERABLE; + } } diff --git a/shufflecake-userland-lite/src/cli/testpwd.c b/shufflecake-userland-lite/src/cli/testpwd.c index 4bc5695..550df25 100644 --- a/shufflecake-userland-lite/src/cli/testpwd.c +++ b/shufflecake-userland-lite/src/cli/testpwd.c @@ -31,7 +31,7 @@ #include "cli.h" #include "commands.h" -#include "utils/sflite.h" +#include "utils/sflc.h" #include "utils/input.h" #include "utils/log.h" @@ -45,12 +45,12 @@ * * @return Error code, 0 on success */ -int sflite_cli_testPwd(char *block_device) +int sflc_cli_testPwd(char *block_device) { // Requires: block_device is a correct block device path - sflite_cmd_OpenArgs args; - sflite_DmbCell dmb_cell; -// char bdev_path[SFLITE_BDEV_PATH_MAX_LEN + 2]; - char pwd[SFLITE_BIGBUFSIZE]; + sflc_cmd_OpenArgs args; + sflc_DmbCell dmb_cell; +// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; + char pwd[SFLC_BIGBUFSIZE]; size_t pwd_len; int err; @@ -58,9 +58,9 @@ int sflite_cli_testPwd(char *block_device) /* Gather password */ printf("Enter the password you want to test: "); - err = sflite_safeReadPassphrase(pwd, SFLITE_BIGBUFSIZE); + err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); if (err) { - sflite_log_error("Could not read password; error %d", err); + sflc_log_error("Could not read password; error %d", err); return err; } /* You can trust the length of strings input this way */ @@ -70,14 +70,14 @@ int sflite_cli_testPwd(char *block_device) args.pwd_len = pwd_len; /* Actually perform the command */ - err = sflite_cmd_testPwd(&args, &dmb_cell); + err = sflc_cmd_testPwd(&args, &dmb_cell); if (err) { - sflite_log_error("Could not test password; error %d", err); + sflc_log_error("Could not test password; error %d", err); return err; } /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLITE_DEV_MAX_VOLUMES) { + if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { printf("This password does not unlock any volume.\n"); } else { printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); diff --git a/shufflecake-userland-lite/src/commands/close.c b/shufflecake-userland-lite/src/commands/close.c index 73a7605..28e63d4 100644 --- a/shufflecake-userland-lite/src/commands/close.c +++ b/shufflecake-userland-lite/src/commands/close.c @@ -176,7 +176,7 @@ static int _closeVolumes(char **labels, size_t nr_vols) /* Eazy peazy */ int i; for (i = nr_vols-1; i >= 0; i--) { - err = sflc_ops_closeVolume(labels[i]); + err = sflite_ops_closeVolume(labels[i]); if (err) { sflc_log_error("Could not close volume %s; error %d", labels[i], err); return err; diff --git a/shufflecake-userland-lite/src/commands/open_legacy.c b/shufflecake-userland-lite/src/commands/open_legacy.c index 077edb9..66660d4 100644 --- a/shufflecake-userland-lite/src/commands/open_legacy.c +++ b/shufflecake-userland-lite/src/commands/open_legacy.c @@ -103,7 +103,7 @@ int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); return err; } - nr_slices = sflc_disk_maxSlices(dev_size); + nr_slices = sfold_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); diff --git a/shufflecake-userland-lite/src/commands/open_lite.c b/shufflecake-userland-lite/src/commands/open_lite.c index 5f4fec4..4604719 100644 --- a/shufflecake-userland-lite/src/commands/open_lite.c +++ b/shufflecake-userland-lite/src/commands/open_lite.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "commands.h" #include "operations.h" diff --git a/shufflecake-userland-lite/src/header/position_map_lite.c b/shufflecake-userland-lite/src/header/position_map_lite.c index 7782947..ae2b48a 100644 --- a/shufflecake-userland-lite/src/header/position_map_lite.c +++ b/shufflecake-userland-lite/src/header/position_map_lite.c @@ -64,7 +64,7 @@ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) // Allocate EPM epm = malloc(nr_pmbs * SFLC_BLOCK_SIZE); if (!epm) { - sflite_log_error("Could not malloc EPM"); + sflc_log_error("Could not malloc EPM"); return NULL; } @@ -81,10 +81,10 @@ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key) *((uint64_t*)iv) = htole64(pblk_num); // Encrypt - err = sflite_aes256xts_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, + err = sflc_aes256xts_encrypt(volume_key, pmb, SFLC_BLOCK_SIZE, iv, epm + i*SFLC_BLOCK_SIZE); if (err) { - sflite_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); + sflc_log_error("Could not encrypt %d-th block of PosMap; error %d", i , err); free(epm); return NULL; } diff --git a/shufflecake-userland-lite/src/main.c b/shufflecake-userland-lite/src/main.c index 50d86e7..737b57e 100644 --- a/shufflecake-userland-lite/src/main.c +++ b/shufflecake-userland-lite/src/main.c @@ -39,6 +39,6 @@ int main(int argc, char **argv) { - return sflite_cli_dispatch(argc, argv); + return sflc_cli_dispatch(argc, argv); } diff --git a/shufflecake-userland-lite/src/utils/file.c b/shufflecake-userland-lite/src/utils/file.c index 11066b1..a8e70e2 100644 --- a/shufflecake-userland-lite/src/utils/file.c +++ b/shufflecake-userland-lite/src/utils/file.c @@ -47,7 +47,7 @@ char *sflc_readFile(char *path) /* Open file */ fp = fopen(path, "r"); if (fp == NULL) { - sflite_log_error("Could not open file %s", path); + sflc_log_error("Could not open file %s", path); perror("Reason: "); goto bad_fopen; } @@ -60,7 +60,7 @@ char *sflc_readFile(char *path) /* Allocate */ content = malloc(filesize + 1); if (content == NULL) { - sflite_log_error("Could not malloc %d bytes for file content", filesize); + sflc_log_error("Could not malloc %d bytes for file content", filesize); goto bad_malloc; } From 08b298c39c0662711b84e638a4308b04412f17b4 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 27 Aug 2024 20:07:40 +0200 Subject: [PATCH 68/98] Remove duplicate dirs --- dm-vvz/Kbuild | 36 -- dm-vvz/Makefile | 41 -- dm-vvz/SCHOLIA.md | 18 - dm-vvz/crypto.c | 125 ------ dm-vvz/device.c | 177 -------- dm-vvz/posmap.c | 376 ---------------- dm-vvz/read.c | 168 ------- dm-vvz/sysfs.c | 244 ---------- dm-vvz/volume.c | 116 ----- dm-vvz/vvz.c | 382 ---------------- dm-vvz/vvz.h | 194 -------- dm-vvz/vvz_constants.h | 78 ---- dm-vvz/write.c | 148 ------ shufflecake-userland-legacy/Makefile | 131 ------ shufflecake-userland-legacy/Makefile.sources | 49 -- shufflecake-userland-legacy/include/cli.h | 65 --- .../include/commands.h | 113 ----- shufflecake-userland-legacy/include/header.h | 161 ------- .../include/operations.h | 84 ---- .../include/sflc_constants.h | 1 - .../include/utils/crypto.h | 103 ----- .../include/utils/disk.h | 109 ----- .../include/utils/input.h | 46 -- .../include/utils/log.h | 136 ------ .../include/utils/sflc.h | 93 ---- shufflecake-userland-legacy/src/cli/close.c | 74 --- .../src/cli/dispatch.c | 245 ---------- shufflecake-userland-legacy/src/cli/init.c | 118 ----- shufflecake-userland-legacy/src/cli/open.c | 77 ---- .../src/commands/change_pwd.c | 58 --- .../src/commands/close.c | 190 -------- .../src/commands/init.c | 202 --------- .../src/commands/open.c | 206 --------- .../src/header/device_master_block.c | 276 ------------ .../src/header/position_map.c | 134 ------ .../src/header/volume_master_block.c | 223 --------- .../src/operations/devmapper.c | 111 ----- .../src/operations/dmb.c | 167 ------- .../src/operations/volume_header.c | 165 ------- .../src/utils/crypto.c | 424 ------------------ shufflecake-userland-legacy/src/utils/disk.c | 283 ------------ shufflecake-userland-legacy/src/utils/dm.c | 203 --------- .../test/crypto/test_aes256ctr.c | 178 -------- .../test/crypto/test_aes256gcm.c | 160 ------- .../test/crypto/test_argon2id.c | 83 ---- shufflecake-userland-legacy/test/main.c | 80 ---- shufflecake-userland-lite/.gitignore | 11 - shufflecake-userland-lite/include/utils/dm.h | 56 --- .../include/utils/file.h | 36 -- .../include/utils/math.h | 36 -- .../include/utils/string.h | 39 -- shufflecake-userland-lite/src/cli/changepwd.c | 112 ----- shufflecake-userland-lite/src/cli/testpwd.c | 87 ---- .../src/commands/test_pwd.c | 59 --- shufflecake-userland-lite/src/main.c | 44 -- shufflecake-userland-lite/src/utils/file.c | 81 ---- shufflecake-userland-lite/src/utils/input.c | 101 ----- shufflecake-userland-lite/src/utils/string.c | 74 --- .../test/crypto/test_aes256ctr.h | 85 ---- .../test/crypto/test_aes256gcm.h | 91 ---- .../test/crypto/test_argon2id.h | 43 -- shufflecake-userland-lite/test/minunit.h | 10 - .../.gitignore | 0 .../Makefile | 0 .../Makefile.sources | 0 .../include/cli.h | 0 .../include/commands.h | 0 .../include/header.h | 0 .../include/operations.h | 0 .../include/utils/crypto.h | 0 .../include/utils/disk.h | 0 .../include/utils/dm.h | 0 .../include/utils/file.h | 0 .../include/utils/input.h | 0 .../include/utils/log.h | 0 .../include/utils/math.h | 0 .../include/utils/sflc.h | 0 .../include/utils/string.h | 0 .../src/cli/changepwd.c | 0 .../src/cli/close.c | 0 .../src/cli/dispatch.c | 0 .../src/cli/init.c | 0 .../src/cli/open.c | 0 .../src/cli/testpwd.c | 0 .../src/commands/change_pwd.c | 0 .../src/commands/close.c | 0 .../src/commands/init_legacy.c | 0 .../src/commands/init_lite.c | 0 .../src/commands/open_legacy.c | 0 .../src/commands/open_lite.c | 0 .../src/commands/test_pwd.c | 0 .../src/header/device_master_block.c | 0 .../src/header/position_map_legacy.c | 0 .../src/header/position_map_lite.c | 0 .../src/header/volume_master_block_legacy.c | 0 .../src/header/volume_master_block_lite.c | 0 .../src/main.c | 0 .../src/operations/devmapper_legacy.c | 0 .../src/operations/devmapper_lite.c | 0 .../src/operations/dmb.c | 0 .../src/operations/volume_header_legacy.c | 0 .../src/operations/volume_header_lite.c | 0 .../src/utils/crypto.c | 0 .../src/utils/disk.c | 0 .../src/utils/dm.c | 0 .../src/utils/file.c | 0 .../src/utils/input.c | 0 .../src/utils/string.c | 0 .../test/crypto/test_aes256ctr.c | 0 .../test/crypto/test_aes256ctr.h | 0 .../test/crypto/test_aes256gcm.c | 0 .../test/crypto/test_aes256gcm.h | 0 .../test/crypto/test_argon2id.c | 0 .../test/crypto/test_argon2id.h | 0 .../test/main.c | 0 .../test/minunit.h | 0 116 files changed, 7816 deletions(-) delete mode 100644 dm-vvz/Kbuild delete mode 100644 dm-vvz/Makefile delete mode 100644 dm-vvz/SCHOLIA.md delete mode 100644 dm-vvz/crypto.c delete mode 100644 dm-vvz/device.c delete mode 100644 dm-vvz/posmap.c delete mode 100644 dm-vvz/read.c delete mode 100644 dm-vvz/sysfs.c delete mode 100644 dm-vvz/volume.c delete mode 100644 dm-vvz/vvz.c delete mode 100644 dm-vvz/vvz.h delete mode 100644 dm-vvz/vvz_constants.h delete mode 100644 dm-vvz/write.c delete mode 100644 shufflecake-userland-legacy/Makefile delete mode 100644 shufflecake-userland-legacy/Makefile.sources delete mode 100644 shufflecake-userland-legacy/include/cli.h delete mode 100644 shufflecake-userland-legacy/include/commands.h delete mode 100644 shufflecake-userland-legacy/include/header.h delete mode 100644 shufflecake-userland-legacy/include/operations.h delete mode 120000 shufflecake-userland-legacy/include/sflc_constants.h delete mode 100644 shufflecake-userland-legacy/include/utils/crypto.h delete mode 100644 shufflecake-userland-legacy/include/utils/disk.h delete mode 100644 shufflecake-userland-legacy/include/utils/input.h delete mode 100644 shufflecake-userland-legacy/include/utils/log.h delete mode 100644 shufflecake-userland-legacy/include/utils/sflc.h delete mode 100644 shufflecake-userland-legacy/src/cli/close.c delete mode 100644 shufflecake-userland-legacy/src/cli/dispatch.c delete mode 100644 shufflecake-userland-legacy/src/cli/init.c delete mode 100644 shufflecake-userland-legacy/src/cli/open.c delete mode 100644 shufflecake-userland-legacy/src/commands/change_pwd.c delete mode 100644 shufflecake-userland-legacy/src/commands/close.c delete mode 100644 shufflecake-userland-legacy/src/commands/init.c delete mode 100644 shufflecake-userland-legacy/src/commands/open.c delete mode 100644 shufflecake-userland-legacy/src/header/device_master_block.c delete mode 100644 shufflecake-userland-legacy/src/header/position_map.c delete mode 100644 shufflecake-userland-legacy/src/header/volume_master_block.c delete mode 100644 shufflecake-userland-legacy/src/operations/devmapper.c delete mode 100644 shufflecake-userland-legacy/src/operations/dmb.c delete mode 100644 shufflecake-userland-legacy/src/operations/volume_header.c delete mode 100644 shufflecake-userland-legacy/src/utils/crypto.c delete mode 100644 shufflecake-userland-legacy/src/utils/disk.c delete mode 100644 shufflecake-userland-legacy/src/utils/dm.c delete mode 100644 shufflecake-userland-legacy/test/crypto/test_aes256ctr.c delete mode 100644 shufflecake-userland-legacy/test/crypto/test_aes256gcm.c delete mode 100644 shufflecake-userland-legacy/test/crypto/test_argon2id.c delete mode 100644 shufflecake-userland-legacy/test/main.c delete mode 100644 shufflecake-userland-lite/.gitignore delete mode 100644 shufflecake-userland-lite/include/utils/dm.h delete mode 100644 shufflecake-userland-lite/include/utils/file.h delete mode 100644 shufflecake-userland-lite/include/utils/math.h delete mode 100644 shufflecake-userland-lite/include/utils/string.h delete mode 100644 shufflecake-userland-lite/src/cli/changepwd.c delete mode 100644 shufflecake-userland-lite/src/cli/testpwd.c delete mode 100644 shufflecake-userland-lite/src/commands/test_pwd.c delete mode 100644 shufflecake-userland-lite/src/main.c delete mode 100644 shufflecake-userland-lite/src/utils/file.c delete mode 100644 shufflecake-userland-lite/src/utils/input.c delete mode 100644 shufflecake-userland-lite/src/utils/string.c delete mode 100644 shufflecake-userland-lite/test/crypto/test_aes256ctr.h delete mode 100644 shufflecake-userland-lite/test/crypto/test_aes256gcm.h delete mode 100644 shufflecake-userland-lite/test/crypto/test_argon2id.h delete mode 100644 shufflecake-userland-lite/test/minunit.h rename {shufflecake-userland-legacy => shufflecake-userland}/.gitignore (100%) rename {shufflecake-userland-lite => shufflecake-userland}/Makefile (100%) rename {shufflecake-userland-lite => shufflecake-userland}/Makefile.sources (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/cli.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/commands.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/header.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/operations.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/utils/crypto.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/utils/disk.h (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/include/utils/dm.h (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/include/utils/file.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/utils/input.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/utils/log.h (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/include/utils/math.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/include/utils/sflc.h (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/include/utils/string.h (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/cli/changepwd.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/cli/close.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/cli/dispatch.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/cli/init.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/cli/open.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/cli/testpwd.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/change_pwd.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/close.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/init_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/init_lite.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/open_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/commands/open_lite.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/commands/test_pwd.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/header/device_master_block.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/header/position_map_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/header/position_map_lite.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/header/volume_master_block_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/header/volume_master_block_lite.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/main.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/operations/devmapper_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/operations/devmapper_lite.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/operations/dmb.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/operations/volume_header_legacy.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/operations/volume_header_lite.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/utils/crypto.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/utils/disk.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/src/utils/dm.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/utils/file.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/utils/input.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/src/utils/string.c (100%) rename {shufflecake-userland-lite => shufflecake-userland}/test/crypto/test_aes256ctr.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/test/crypto/test_aes256ctr.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/test/crypto/test_aes256gcm.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/test/crypto/test_aes256gcm.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/test/crypto/test_argon2id.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/test/crypto/test_argon2id.h (100%) rename {shufflecake-userland-lite => shufflecake-userland}/test/main.c (100%) rename {shufflecake-userland-legacy => shufflecake-userland}/test/minunit.h (100%) diff --git a/dm-vvz/Kbuild b/dm-vvz/Kbuild deleted file mode 100644 index 1ed4b30..0000000 --- a/dm-vvz/Kbuild +++ /dev/null @@ -1,36 +0,0 @@ - # - # Copyright The Shufflecake Project Authors (2022) - # Copyright The Shufflecake Project Contributors (2022) - # Copyright Contributors to the The Shufflecake Project. - # - # See the AUTHORS file at the top-level directory of this distribution and at - # - # - # This file is part of the program shufflecake-c, which is part of the - # Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - # layer for Linux. See . - # - # This program is free software: you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by the Free - # Software Foundation, either version 2 of the License, or (at your option) - # any later version. This program 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 General - # Public License for more details. You should have received a copy of the - # GNU General Public License along with this program. - # If not, see . - # - -MODULE_NAME := dm_vvz -obj-m := $(MODULE_NAME).o - - -OBJ_LIST := vvz.o device.o volume.o sysfs.o crypto.o posmap.o read.o write.o - -$(MODULE_NAME)-y += $(OBJ_LIST) - - -# Normal CC flags -ccflags-y := -O2 -ccflags-y += -I$(src) -ccflags-y += -Wall -Wno-declaration-after-statement diff --git a/dm-vvz/Makefile b/dm-vvz/Makefile deleted file mode 100644 index 982f8ab..0000000 --- a/dm-vvz/Makefile +++ /dev/null @@ -1,41 +0,0 @@ - # - # Copyright The Shufflecake Project Authors (2022) - # Copyright The Shufflecake Project Contributors (2022) - # Copyright Contributors to the The Shufflecake Project. - # - # See the AUTHORS file at the top-level directory of this distribution and at - # - # - # This file is part of the program shufflecake-c, which is part of the - # Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - # layer for Linux. See . - # - # This program is free software: you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by the Free - # Software Foundation, either version 2 of the License, or (at your option) - # any later version. This program 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 General - # Public License for more details. You should have received a copy of the - # GNU General Public License along with this program. - # If not, see . - # - -KERNEL_DIR = /lib/modules/$(shell uname -r)/build -SRC_DIR = $(shell pwd) -BUILD_DIR = $(shell pwd)/bin -BUILD_DIR_MAKEFILE = $(BUILD_DIR)/Makefile - -default: $(BUILD_DIR_MAKEFILE) - make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules - -$(BUILD_DIR_MAKEFILE): | $(BUILD_DIR) - echo "# This Makefile does nothing, but Kbuild needs one in this directory" > $@ - -$(BUILD_DIR): - mkdir $@ - - -clean: - rm -rf $(BUILD_DIR) - diff --git a/dm-vvz/SCHOLIA.md b/dm-vvz/SCHOLIA.md deleted file mode 100644 index a8b2439..0000000 --- a/dm-vvz/SCHOLIA.md +++ /dev/null @@ -1,18 +0,0 @@ -#### Workqueues -Apparently, using bound workqueues is recommended, unless the work items are expected to hog the CPU for "huge" amounts of cycles (see the [cmwq docs](https://www.kernel.org/doc/html/v6.6/core-api/workqueue.html)). In fact, `dm-crypt` uses a bound `io_queue` for bio mapping; it also has a `crypt_queue` for encryption/decryption, that is `WQ_CPU_INTENSIVE`: that wq is either bound or unbound, based on the flag `DM_CRYPT_SAME_CPU`. Both these workqueues are `WQ_MEM_RECLAIM`, and we probably need that for Shufflecake too. -As for granularity, almost no DM target uses the kernel-global wq (with `schedule_work()`); many allocate a per-target wq, while some use a module-wide wq. At this first stage, we use a per-device wq, since we imagine the device as the unit of resource sharing; we might hit the concurrency limit (set by `max_active`) in the future, so that's one thing to look out for. -All in all, we can use a per-device bound wq, that is `WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE`, and whose work items process bio's start-to-finish. - -#### Position map: write-back or write-through? -A first idea was to have slice allocation happen exclusively in memory, and only synchronise the position map to disk upon FLUSH requests (write-back posmap), but this gives the scheme a weird and inconsistent *crash behaviour*. -Suppose, for example, that the upper-layer FS keeps an `index` block with a list of all allocated data blocks; suppose that a new data block is written at a position falling in a yet-unmapped slice (triggering slice allocation); the `index` block is consequently updated so that the list also contains a pointer to the new data block; suppose that, by Satan's inscrutable will, the update of the `index` block gets persisted to disk but the system crashes right afterwards, before the on-disk position map is updated; when the system boots back up, the FS will legitimately believe that the data block is allocated (because that's what the `index` block says) and contains all zeros (because READ requests to blocks in unmapped slices return all-zeros, for good reasons). This is bad. -On the other hand, one might possibly argue that the upper layer employed an unsafe sequence of operations, and should have only updated the `index` after a FLUSH request. After all, the all-zeros was just the previous (logical) content of the data block, before it was written by the FS, so it's just as if our driver reordered the two WRITE requests and failed in the middle, which is perfectly legit. Right now, I can't think of a more compelling argument against a write-back position map, but the fact that a WRITE request can complete, only for the affected block to then become "unreachable" (i.e. contain all-zeros) again, is fishy. -Anyway, to avoid headaches, we'll probably go for a write-through position map at first, at least until we discuss this more accurately with someone. Also, this simplifies handling of FLUSH requests: we have nothing to flush, so we just pass them down. This is because there is no volatile cache, since the in-memory view of the position map is write-through. - -#### FLUSH requests and where to find them -How do we even know if a bio is a FLUSH? Main references are the comments in [this source file](https://elixir.bootlin.com/linux/v6.6.9/source/block/blk-flush.c), as well as [these docs](https://www.kernel.org/doc/Documentation/block/writeback_cache_control.txt). The point seems to be that we are unlikely to see `REQ_OP_FLUSH` bio's incoming: rather, we'll receive an empty bio with `REQ_OP_WRITE | REQ_PREFLUSH`. Indeed, that's what `dm-crypt` checks for; there's also a comment there that reads `for REQ_PREFLUSH device-mapper core ensures that no IO is in-flight` although I couldn't confirm that myself. `REQ_FUA` might be set on some bio's, and we should pass it down; I've read somewhere that the device-mapper core will strip it and just send an emtpy FLUSH afterwards, but I haven't found many confirmations of that. - -#### Locking of write-through posmap -About the posmap view being write-through, there is something to add. This first implementation is very crude: when we create a new slice mapping, we hold onto the `posmap_lock` for dear life until the new mapping is persisted on-disk. This approach unnecessarily blocks all new bio's, even unrelated ones (i.e. falling in a different slice); also, the new bio's falling in that same slice could at least start processing, and only wait for the slice mapping to be persisted once they are done, before returning. -There is a fix to both these problems, but it results in the locking scheme for the position map to be a bit more convoluted, so it's better to resist the urge of premature optimisation. We would also reserve the MSB of the posmap entry (which is a `u32`) to signal that the entry "is being written to disk": this limits the range of usable PSIs to `0x00000000` to `0x7FFFFFFE` (with `0x7FFFFFFF` and `0xFFFFFFFF` being the "unmapped LSI" symbol), so we can have at most 2^31-1 PSIs. -The optimised posmap would work as follows. The function `create_slice_mapping()` takes the `posmap_lock`, then samples a new PSIs, then writes it in the posmap **with the MSB set**, then releases the `posmap_lock` (so other bio's can use the posmap), then writes the updated posmap block on the disk. If a new incoming bio falls in the same LSI, it can still proceed even if the MSB of the entry is set ("speculating" that the update of the posmap block will succeed) and use the slice mapping normally to launch the mapped bio; then, just before returning, it waits for the MSB to clear using a waitqueue (as explained in [LDD](https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s02.html)). The waitqueue is declared in the volume struct alongside the posmap, and is woken up by `create_slice_mapping()` once the update of the posmap block finishes successfully, after clearing the MSB. diff --git a/dm-vvz/crypto.c b/dm-vvz/crypto.c deleted file mode 100644 index e4a2c0d..0000000 --- a/dm-vvz/crypto.c +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include -#include -#include "vvz_constants.h" - -#include "vvz.h" - - -/** - * Encrypt/decrypt exactly one block, already encoded in the scatterlist. - * All other crypto functions reduce to this one. - * The IV is constructed as the right-0-padded LE representation of the - * physical block number, which is exactly what dm-crypt does when using the - * IV mode "plain64". - */ -static int crypt_sg(struct crypto_skcipher *tfm, struct scatterlist *src, - struct scatterlist *dst, u64 pblk_num, int rw) -{ - u8 iv[VVZ_XTS_IVLEN]; - struct skcipher_request *req = NULL; - DECLARE_CRYPTO_WAIT(wait); - int err; - - // TODO not too sure about the gfp_mask here - // TODO move @req into struct vvz_io? - req = skcipher_request_alloc(tfm, GFP_NOIO); - if (!req) - return -ENOMEM; - - skcipher_request_set_callback(req, - CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP, - crypto_req_done, &wait); - - /* Construct IV */ - memset(iv, 0, VVZ_XTS_IVLEN); - *(__le64 *)iv = cpu_to_le64(pblk_num); - - skcipher_request_set_crypt(req, src, dst, VVZ_BLOCK_SIZE, iv); - if (rw == READ) - err = crypto_wait_req(crypto_skcipher_decrypt(req), &wait); - else - err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait); - skcipher_request_free(req); - - return err; -} - -/* Encrypt-decrypt a single block (memory buffer is a page) */ -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw) -{ - struct scatterlist dst, src, *p_dst; - bool is_inplace; - - /* Use same scatterlist if in-place */ - is_inplace = (src_page == dst_page); - p_dst = is_inplace ? &src : &dst; - - /* We assume PAGE_SIZE == VVZ_BLOCK_SIZE */ - /* And orig_bio to start at offset 0 within the page */ - sg_init_table(&src, 1); - sg_set_page(&src, src_page, VVZ_BLOCK_SIZE, 0); - if (!is_inplace) { - sg_init_table(&dst, 1); - sg_set_page(&dst, dst_page, VVZ_BLOCK_SIZE, 0); - } - - return crypt_sg(tfm, &src, p_dst, pblk_num, rw); -} - - -/* Encrypt-decrypt consecutive blocks (memory buffer is vmalloc'ed) */ -int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, - u64 num_blocks, u64 first_pblk_num, int rw) -{ - struct scatterlist dst, src, *p_dst; - u64 pblk_num; - bool is_inplace; - int err; - - /* Use same scatterlist if in-place */ - is_inplace = (src_buf == dst_buf); - p_dst = is_inplace ? &src : &dst; - - for (pblk_num = first_pblk_num; - pblk_num < first_pblk_num + num_blocks; - pblk_num++) { - sg_init_one(&src, src_buf, VVZ_BLOCK_SIZE); - if (!is_inplace) - sg_init_one(&dst, dst_buf, VVZ_BLOCK_SIZE); - - err = crypt_sg(tfm, &src, p_dst, pblk_num, rw); - if (err) - return err; - - src_buf += VVZ_BLOCK_SIZE; - dst_buf += VVZ_BLOCK_SIZE; - } - - return 0; -} - diff --git a/dm-vvz/device.c b/dm-vvz/device.c deleted file mode 100644 index fd7cab3..0000000 --- a/dm-vvz/device.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include -#include -#include -#include "vvz.h" - - -/* Depth of the mempool backing the bio_set */ -#define VVZ_BIOSET_BIOS 64 - - -/* Fisher-Yates shuffle */ -static void fisheryates_u32(u32 *v, u32 len) -{ - u32 i, j; - - for (i = len-1; i >= 1; i--) { - j = get_random_u32_below(i+1); - swap(v[i], v[j]); - } - - return; -} - -struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices) -{ - struct vvz_device *sdev; - dev_t devt; - int i; - int err; - - sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); - if (!sdev) { - DMERR("Could not allocate device"); - return ERR_PTR(-ENOMEM); - } - sdev->dev_id = dev_id; - sdev->nr_volumes = 0; - - /* Look up block device and set name */ - err = lookup_bdev(bdev_path, &devt); - if (err) { - DMERR("Could not look up block device"); - goto bad_lookup; - } - format_dev_t(sdev->name, devt); - - /* Compute sizes */ - sdev->tot_slices = tot_slices; - sdev->nr_free_slices = tot_slices; - /* Enough posmap blocks to fit all the entries */ - sdev->posmap_size_sectors = VVZ_BLOCK_SCALE * - DIV_ROUND_UP(tot_slices, VVZ_PSIS_PER_BLOCK); - /* DMB + VMBs + PosMaps */ - sdev->dev_header_size_sectors = VVZ_BLOCK_SCALE + - (VVZ_DEV_MAX_VOLUMES * VVZ_BLOCK_SCALE) + - (VVZ_DEV_MAX_VOLUMES * sdev->posmap_size_sectors); - - /* Shuffled PSIs */ - mutex_init(&sdev->slices_lock); - sdev->slices_ofld = vzalloc(tot_slices * sizeof(bool)); - if (!sdev->slices_ofld) { - DMERR("Could not allocate PSI occupation bitfield"); - err = -ENOMEM; - goto bad_ofld; - } - - sdev->prmslices = vmalloc(tot_slices * sizeof(u32)); - if (!sdev->prmslices) { - DMERR("Could not allocate shuffled PSI array"); - err = -ENOMEM; - goto bad_prmslices; - } - /* Generate a permutation */ - for (i = 0; i < tot_slices; i++) - sdev->prmslices[i] = i; - fisheryates_u32(sdev->prmslices, tot_slices); - sdev->prmslices_octr = 0; - - /* Bioset */ - err = bioset_init(&sdev->bioset, VVZ_BIOSET_BIOS, 0, BIOSET_NEED_BVECS); - if (err) { - DMERR("Could not init bioset; error %d", err); - goto bad_bioset; - } - - /* Client for dm-io */ - sdev->io_client = dm_io_client_create(); - if (IS_ERR(sdev->io_client)) { - err = PTR_ERR(sdev->io_client); - DMERR("Could not create dm-io client; error %d", err); - goto bad_dmio_client; - } - - /* I/O workqueue */ - sdev->io_queue = alloc_workqueue("vvz_%s_io", - WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, - 0, sdev->name); - if (!sdev->io_queue) { - err = -ENOMEM; - DMERR("Could not allocate I/O workqueue"); - goto bad_io_wq; - } - /* Decryption workqueue */ - sdev->crypt_queue = alloc_workqueue("vvz_%s_crypt", - WQ_MEM_RECLAIM | WQ_CPU_INTENSIVE, - 0, sdev->name); - if (!sdev->crypt_queue) { - err = -ENOMEM; - DMERR("Could not allocate decryption workqueue"); - goto bad_crypt_wq; - } - - /* Register to sysfs, once initialised */ - err = vvz_sysfs_register_device(sdev); - if (err) - goto bad_sysfs; - - return sdev; - - -bad_sysfs: - destroy_workqueue(sdev->crypt_queue); -bad_crypt_wq: - destroy_workqueue(sdev->io_queue); -bad_io_wq: - dm_io_client_destroy(sdev->io_client); -bad_dmio_client: - bioset_exit(&sdev->bioset); -bad_bioset: - vfree(sdev->prmslices); -bad_prmslices: - vfree(sdev->slices_ofld); -bad_ofld: -bad_lookup: - kfree(sdev); - return ERR_PTR(err); -} - - -void vvz_dev_destroy(struct vvz_device *sdev) -{ - vvz_sysfs_unregister_device(sdev); - destroy_workqueue(sdev->crypt_queue); - destroy_workqueue(sdev->io_queue); - dm_io_client_destroy(sdev->io_client); - bioset_exit(&sdev->bioset); - vfree(sdev->prmslices); - vfree(sdev->slices_ofld); - kfree(sdev); - - return; -} - diff --git a/dm-vvz/posmap.c b/dm-vvz/posmap.c deleted file mode 100644 index 6d8711c..0000000 --- a/dm-vvz/posmap.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include -#include "vvz.h" - - -/* Helpers */ - -#define IS_PSI_TAKEN(sdev, psi) ( (sdev)->slices_ofld[(psi)] ) -#define NEXT_RANDOM_PSI(sdev) ( (sdev)->prmslices[(sdev)->prmslices_octr] ) -#define IS_LAST_LSI_IN_BLOCK(lsi, sdev) ( (((lsi)+1) % VVZ_PSIS_PER_BLOCK == 0) || \ - (((lsi)+1) == (sdev)->tot_slices) ) - - -/* - *---------------------------- - * Create slice mapping - *---------------------------- - */ - -/** - * Return the next free PSI in the device's shuffled array, without modifying - * the device state. - * - * MUTEX: @sdev->slices_lock must be held. - */ -static int peek_next_free_psi(struct vvz_device *sdev, u32 *psi) -{ - if (unlikely(!sdev->nr_free_slices)) - return -ENOSPC; - if (unlikely(sdev->prmslices_octr >= sdev->tot_slices)) { - DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); - print_hex_dump(KERN_CRIT, "prmslices(REV) ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); - msleep(10000); - print_hex_dump(KERN_CRIT, "ofld(REV) ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); - msleep(10000); - return -ENOTRECOVERABLE; // Grave inconsistency - } - - /* Invariant: @prmslices_octr points to a free slice */ - *psi = NEXT_RANDOM_PSI(sdev); - if (unlikely(IS_PSI_TAKEN(sdev, *psi))){ - DMCRIT("octr = %u, tot_slices = %u, free_slices = %u", sdev->prmslices_octr, sdev->tot_slices, sdev->nr_free_slices); - DMCRIT("PSI %u is occupied", *psi); - print_hex_dump(KERN_CRIT, "prmslices ", DUMP_PREFIX_OFFSET, 32, 4, sdev->prmslices, 4*sdev->tot_slices, false); - msleep(10000); - print_hex_dump(KERN_CRIT, "ofld ", DUMP_PREFIX_OFFSET, 32, 1, sdev->slices_ofld, sdev->tot_slices, false); - msleep(10000); - return -ENOTRECOVERABLE; // Grave inconsistency - } - - return 0; -} - -/** - * Map LSI => PSI, only in memory. - * Sanity checks to be performed by the caller. - * - * MUTEX: @sdev->slices_lock must be held. - * MUTEX: @svol->posmap_lock must be held, except under volume ctor. - */ -static void _create_local_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 psi) -{ - struct vvz_device *sdev = svol->sdev; - - /* Grab it from the device */ - sdev->slices_ofld[psi] = true; - sdev->nr_free_slices--; - // Preserve the invariant: @prmslices_octr must point to a free slice - while(sdev->prmslices_octr < sdev->tot_slices && - IS_PSI_TAKEN(sdev, NEXT_RANDOM_PSI(sdev))) { - sdev->prmslices_octr++; - } - - /* Insert in the volume */ - svol->posmap[lsi] = psi; - svol->nr_mapped_slices++; - - return; -} - -/** - * Delete mapping for the given LSI, only in memory. - * Sanity checks to be performed by the caller. - * - * MUTEX: @svol->posmap_lock must be held, except under volume ctor. - */ -static void _delete_local_slice_mapping(struct vvz_volume *svol, u32 lsi) -{ - /* Delete mapping in the volume */ - svol->posmap[lsi] = VVZ_PSI_INVALID; - svol->nr_mapped_slices--; - - /* Don't do anything in the device though, leave it there: we don't yet - * have an obvious way to release PSIs. - * This means a PSI will be incorrectly marked as occupied, but that's - * not too bad: the PSI shuffling and its occupation counter are - * ephemeral, so they reset if you close and reopen all the volumes. */ - return; -} - -/** - * Synchronously store (and flush) the given posmap block - * - * MUTEX: @svol->posmap_lock must be held, except under volume ctor. - */ -static int store_posmap_block(struct vvz_volume *svol, u32 posmap_block_num) -{ - struct vvz_device *sdev = svol->sdev; - struct page *page; - struct bio *bio; - int err; - - /* Sync + flush TODO GFP mask ok? */ - bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, - REQ_OP_WRITE | REQ_SYNC | REQ_FUA, GFP_NOIO, - &sdev->bioset); - if (!bio) { - DMERR("Could not allocate posmap block bio"); - return -ENOMEM; - } - bio->bi_iter.bi_sector = VVZ_POSMAP_START_SECTOR(svol) + - (posmap_block_num << VVZ_BLOCK_SHIFT); - - /* Alloc and add page TODO GFP mask */ - page = alloc_page(GFP_NOIO); - if (!page) { - DMERR("Could not allocate posmap block page"); - err = -ENOMEM; - goto bad_alloc_page; - } - // TODO remove this error check - if (unlikely(!bio_add_page(bio, page, VVZ_BLOCK_SIZE, 0))) { - DMCRIT("Could not add posmap block page to bio!"); - err = -ENOTRECOVERABLE; - goto bad_add_page; - } - - /* Serialise posmap block onto the page */ - void *page_ptr = kmap_local_page(page); - u32 first_lsi = posmap_block_num * VVZ_PSIS_PER_BLOCK; - u32 last_lsi = min(first_lsi + VVZ_PSIS_PER_BLOCK, sdev->tot_slices); - u32 lsi; - for (lsi = first_lsi; lsi < last_lsi; lsi++) { - u32 psi = svol->posmap[lsi]; - __be32 *be_psi = (__be32*) (page_ptr + ((lsi-first_lsi) * sizeof(__be32))); - *be_psi = cpu_to_be32(psi); - } - -// print_hex_dump(KERN_WARNING, "page_ptr(REV) ", DUMP_PREFIX_OFFSET, 32, 4, page_ptr, VVZ_BLOCK_SIZE, false); -// msleep(100); - - kunmap_local(page_ptr); - - /* Encrypt the block in place */ - err = vvz_crypt_block_page(svol->tfm, page, page, - bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); - if (err) { - DMERR("Could not encrypt posmap block; error %d", err); - goto bad_encrypt; - } - - /* Submit */ - err = submit_bio_wait(bio); - if (err) - DMERR("Could not complete posmap block bio; error %d", err); - -bad_encrypt: -bad_add_page: - __free_page(page); -bad_alloc_page: - bio_put(bio); - return err; -} - - -/** - * Create a new mapping for the given LSI, and synchronise back to disk. - * - * MUTEX: @svol->posmap_lock must be held, except under volume ctor. - * MUTEX: takes @sdev->slices_lock. - * - * Syncing to disk means the posmap lock will be held (by the caller) for a long - * time thus blocking out all the other incoming bio's, even unrelated ones - * (falling in different slices). Several strategies are possible to avoid this - * problem, but for now we keep this simple implementation. - */ -int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *psi) -{ - struct vvz_device *sdev = svol->sdev; - int err; - - /* Bounds check TODO redundant? */ - if(unlikely(lsi >= svol->sdev->tot_slices)) - return -EINVAL; - /* Check mapping not existent TODO redundant? */ - if (unlikely(svol->posmap[lsi] != VVZ_PSI_INVALID)) - return -EINVAL; - - /* Create mapping */ - if (mutex_lock_interruptible(&sdev->slices_lock)) - return -ERESTARTSYS; - err = peek_next_free_psi(sdev, psi); - if (err) { - mutex_unlock(&sdev->slices_lock); - return err; - } - _create_local_slice_mapping(svol, lsi, *psi); - mutex_unlock(&sdev->slices_lock); - - /* Write posmap block to disk */ - err = store_posmap_block(svol, lsi/VVZ_PSIS_PER_BLOCK); - if (err) { - DMERR("Could not store posmap block; error %d", err); - _delete_local_slice_mapping(svol, lsi); - return err; - } - - return 0; -} - - -/* - *---------------------------- - * Load position map - *---------------------------- - */ - -/** - * Synchronously read the entire on-disk encrypted position map - * - * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). - */ -static int read_encrypted_posmap(struct vvz_volume *svol) -{ - struct dm_io_request io_req = { - .bi_opf = REQ_OP_READ | REQ_SYNC, - .mem.type = DM_IO_VMA, - .mem.ptr.vma = svol->posmap, - .notify.fn = NULL, - .client = svol->sdev->io_client - }; - struct dm_io_region io_region = { - .bdev = svol->dm_dev->bdev, - .sector = VVZ_POSMAP_START_SECTOR(svol), - .count = svol->sdev->posmap_size_sectors - }; - - return dm_io(&io_req, 1, &io_region, NULL); -} - -/** - * De-serialise the position map entries. On the fly, if a conflict is detected, - * resolve it by sampling a new PSI, and sync to disk (block by block). - * - * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). - * MUTEX: @sdev->slices_lock must be held. - */ -static int _deserialise_and_sanitise_posmap(struct vvz_volume *svol) -{ - struct vvz_device *sdev = svol->sdev; - void *posmap_ptr = svol->posmap; - u32 lsi; - bool posmap_block_dirty; - int err; - - for (lsi = 0; lsi < sdev->tot_slices; lsi++) { - /* Reset dirty bit at the start of every posmap block */ - if (lsi % VVZ_PSIS_PER_BLOCK == 0) - posmap_block_dirty = false; - - /* De-serialise posmap entry */ - __be32 *be_psi = (__be32*) (posmap_ptr + (lsi * sizeof(__be32))); - u32 psi = be32_to_cpu(*be_psi); - - /* If LSI unmapped, skip mapping creation */ - if (psi == VVZ_PSI_INVALID) { - svol->posmap[lsi] = psi; - goto skip_create_mapping; - } - - /* If PSI out of bounds, something's seriously wrong */ - if (psi >= sdev->tot_slices) { - DMERR("Decrypted PSI out of bounds: %u >= %u", psi, sdev->tot_slices); - return -EDOM; - } - - /* If PSI already taken, sample a new one */ - if (sdev->slices_ofld[psi]) { - DMWARN("Corruption of volume %lu: LSI %u was evicted from PSI %u", - svol->vol_idx, lsi, psi); - err = peek_next_free_psi(sdev, &psi); - if (err) - return err; - posmap_block_dirty = true; - } - /* Whether sanitised or not, create the mapping locally */ - _create_local_slice_mapping(svol, lsi, psi); - -skip_create_mapping: - - /* Only check dirty bit at the end of the posmap block */ - if (posmap_block_dirty && - IS_LAST_LSI_IN_BLOCK(lsi, sdev)) { - err = store_posmap_block(svol, lsi/VVZ_PSIS_PER_BLOCK); - if (err) - return err; - } - } - - return 0; -} - - -/** - * Load the volume's position map from the disk. If some conflicts are present - * (i.e. an LSI is mapped to a PSI that's already taken), then resolve them - * (i.e. re-sample a free PSI for the "unlucky" LSI) and sync back to disk. - * - * MUTEX: no need for the caller to hold @svol->posmap_lock (we are in ctor). - * MUTEX: takes @sdev->slices_lock. - */ -int vvz_load_and_sanitise_posmap(struct vvz_volume *svol) -{ - int err; - struct vvz_device *sdev = svol->sdev; - - /* Read raw posmap from disk */ - err = read_encrypted_posmap(svol); - if (err) - return err; - - /* Decrypt in place */ - err = vvz_crypt_blocks_vm(svol->tfm, svol->posmap, svol->posmap, - svol->sdev->posmap_size_sectors >> VVZ_BLOCK_SHIFT, - VVZ_POSMAP_START_SECTOR(svol) >> VVZ_BLOCK_SHIFT, - READ); - if (err) - return err; - - /* Deserialise and sanitise as you go */ - if (mutex_lock_interruptible(&sdev->slices_lock)) - return -ERESTARTSYS; - err = _deserialise_and_sanitise_posmap(svol); - mutex_unlock(&sdev->slices_lock); - if (err) - return err; - -// print_hex_dump(KERN_CRIT, "posmap(REV) ", DUMP_PREFIX_OFFSET, 32, 4, svol->posmap, 4*sdev->tot_slices, false); -// msleep(2000); - - return 0; -} - diff --git a/dm-vvz/read.c b/dm-vvz/read.c deleted file mode 100644 index cd61934..0000000 --- a/dm-vvz/read.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include "vvz.h" -#include - - -static void vvz_read_endio(struct bio *phys_bio); -static void vvz_decrypt_work_fn(struct work_struct *work); - - -/* Landing here from ->map() through the io_queue */ -void vvz_read_work_fn(struct work_struct *work) -{ - struct vvz_io *sio = container_of(work, struct vvz_io, work); - struct vvz_volume *svol = sio->svol; - struct vvz_device *sdev = svol->sdev; - struct bio *orig_bio = sio->orig_bio; - struct bio *phys_bio; - u32 lsi = sio->lsi; - u32 block_offset = sio->block_offset; - u32 psi; - - /* Read position map */ - if (mutex_lock_interruptible(&svol->posmap_lock)) { - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } - psi = svol->posmap[lsi]; - mutex_unlock(&svol->posmap_lock); - - /* If LSI is unmapped, short-circuit and return all zeros */ - if (psi == VVZ_PSI_INVALID) { - - zero_fill_bio(orig_bio); - orig_bio->bi_status = BLK_STS_OK; - goto endio; - } - sio->psi = psi; - -// DMWARN("READ: LSI=%u, PSI=%u, offset=%u", lsi, psi, sio->block_offset); -// msleep(100); - - /* Shallow-copy the bio and submit it (different bi_endio). - We can shallow-copy because we don't need to own the pages, - we can decrypt in place. */ - - - //DMWARN("READ: shallow copying"); - //msleep(500); - - /* Shallow copy */ - phys_bio = bio_alloc_clone(svol->dm_dev->bdev, orig_bio, GFP_NOIO, &sdev->bioset); - if (!phys_bio) { - DMERR("Could not clone original bio"); - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } - /* Insert in the I/O struct */ - sio->phys_bio = phys_bio; - -// DMWARN("READ: submitting bio"); -// msleep(500); - - /* Remap sector */ - phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); - /* Set fields for the endio */ - phys_bio->bi_private = sio; - phys_bio->bi_end_io = vvz_read_endio; - /* Submit */ - dm_submit_bio_remap(orig_bio, phys_bio); - - return; - - -endio: - bio_endio(orig_bio); - return; -} - - -/* ISR for the phys_bio */ -static void vvz_read_endio(struct bio *phys_bio) -{ - struct vvz_io *sio = phys_bio->bi_private; - -// DMWARN("READ ENDIO: queueing decryption"); -// //msleep(500); - - /* Can't decrypt here in ISR: submit to decryption workqueue. - * Can reuse the same work item, though, since it was popped out of the - * io_queue already */ - INIT_WORK(&sio->work, vvz_decrypt_work_fn); - queue_work(sio->svol->sdev->crypt_queue, &sio->work); -} - - -/* Decrypt and endio */ -static void vvz_decrypt_work_fn(struct work_struct *work) -{ - struct vvz_io *sio = container_of(work, struct vvz_io, work); - struct vvz_volume *svol = sio->svol; - struct bio *orig_bio = sio->orig_bio; - struct bio *phys_bio = sio->phys_bio; - struct bio_vec bvl = bio_iovec(orig_bio); - int err; - - /* If physical bio failed, then fail-fast */ - if (phys_bio->bi_status != BLK_STS_OK) { - orig_bio->bi_status = phys_bio->bi_status; - goto endio; - } - -// DMWARN("DECRYPT FN: decrypting page in place"); -// msleep(2000); - - /* Decrypt page in-place */ - err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, bvl.bv_page, - VVZ_PHYS_BIO_SECTOR(svol->sdev, sio->psi, sio->block_offset) >> VVZ_BLOCK_SHIFT, - READ); - if (err) { - DMERR("Could not decrypt bio; error %d", err); - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } - -// print_hex_dump(KERN_WARNING, "readpage ", DUMP_PREFIX_OFFSET, 32, 1, bvl.bv_page, VVZ_BLOCK_SIZE, true); -// msleep(2000); - -// DMWARN("DECRYPT FN: bio_advance"); -// msleep(300); - - /* Advance original bio by one block */ - bio_advance(orig_bio, VVZ_BLOCK_SIZE); - orig_bio->bi_status = BLK_STS_OK; - -endio: - /* Free the physical bio */ -// DMWARN("DECRYPT FN: bio_put"); -// msleep(300); - bio_put(phys_bio); - /* End original bio */ -// DMWARN("DECRYPT FN: bio_endio\n\n\n\n"); -// msleep(300); - bio_endio(orig_bio); - - return; -} diff --git a/dm-vvz/sysfs.c b/dm-vvz/sysfs.c deleted file mode 100644 index efd3df2..0000000 --- a/dm-vvz/sysfs.c +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include "vvz.h" - -/* - *---------------------------- - * Top-level entries - *---------------------------- - */ - -static ssize_t next_dev_id_show(struct module_attribute *mattr, struct module_kobject *mkobj, char *buf) -{ - ssize_t ret; - - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return -ERESTARTSYS; - ret = sysfs_emit(buf, "%u\n", vvz_free_devid); - mutex_unlock(&vvz_alldevs_lock); - - return ret; -} - -static struct kset *bdevs_kset; -static struct module_attribute devid_mattr = __ATTR_RO(next_dev_id); - -int vvz_sysfs_init() -{ - int err; - - bdevs_kset = kset_create_and_add(VVZ_SYSFS_BDEVS, NULL, &THIS_MODULE->mkobj.kobj); - if (!bdevs_kset) { - err = -ENOMEM; - DMERR("Could not create %s kset", VVZ_SYSFS_BDEVS); - goto bad_bdevs; - } - - err = sysfs_create_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); - if (err) { - DMERR("Could not create %s file", VVZ_SYSFS_DEVID); - goto bad_devid; - } - - return 0; - - -bad_devid: - kset_unregister(bdevs_kset); -bad_bdevs: - return err; -} - -void vvz_sysfs_exit() -{ - sysfs_remove_file(&THIS_MODULE->mkobj.kobj, &devid_mattr.attr); - kset_unregister(bdevs_kset); -} - - -/* - *---------------------------- - * Device entries - *---------------------------- - */ - -static ssize_t dev_id_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) -{ - struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - - return sysfs_emit(buf, "%u\n", sdev->dev_id); -} - -static ssize_t volumes_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) -{ - struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - - return sysfs_emit(buf, "%lu\n", sdev->nr_volumes); -} - -static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) -{ - struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - - return sysfs_emit(buf, "%u\n", sdev->tot_slices); -} - -static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) -{ - struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - int ret; - - if (mutex_lock_interruptible(&sdev->slices_lock)) - return -ERESTARTSYS; - ret = sysfs_emit(buf, "%u\n", sdev->nr_free_slices); - mutex_unlock(&sdev->slices_lock); - - return ret; -} - -static struct kobj_attribute dev_id_kattr = __ATTR_RO(dev_id); -static struct kobj_attribute volumes_kattr = __ATTR_RO(volumes); -static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); -static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); -static struct attribute *vvz_device_default_attrs[] = { - &dev_id_kattr.attr, - &volumes_kattr.attr, - &tot_slices_kattr.attr, - &free_slices_kattr.attr, - NULL -}; -ATTRIBUTE_GROUPS(vvz_device_default); - -static void vvz_device_kobj_release(struct kobject *kobj) -{ - struct vvz_device *sdev = container_of(kobj, struct vvz_device, kobj); - complete(&sdev->kobj_released); -} - -static struct kobj_type vvz_device_ktype = { - .release = vvz_device_kobj_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_groups = vvz_device_default_groups -}; - -int vvz_sysfs_register_device(struct vvz_device *sdev) -{ - int err; - - /* Completion */ - init_completion(&sdev->kobj_released); - - /* Register directory :/ under bdevs/ */ - sdev->kobj.kset = bdevs_kset; - err = kobject_init_and_add(&sdev->kobj, &vvz_device_ktype, NULL, - "%s", sdev->name); - if (err) - goto bad; - /* Emit uevent */ - kobject_uevent(&sdev->kobj, KOBJ_ADD); - - return 0; - - -bad: - kobject_put(&sdev->kobj); - wait_for_completion(&sdev->kobj_released); - return err; -} - -void vvz_sysfs_unregister_device(struct vvz_device *sdev) -{ - kobject_put(&sdev->kobj); - wait_for_completion(&sdev->kobj_released); -} - - -/* - *---------------------------- - * Volume entries - *---------------------------- - */ - -static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) -{ - struct vvz_volume *svol = container_of(kobj, struct vvz_volume, kobj); - int ret; - - if (mutex_lock_interruptible(&svol->posmap_lock)) - return -ERESTARTSYS; - ret = sysfs_emit(buf, "%u\n", svol->nr_mapped_slices); - mutex_unlock(&svol->posmap_lock); - - return ret; -} - -static struct kobj_attribute mapped_slices_kattr = __ATTR_RO(mapped_slices); -static struct attribute *vvz_volume_default_attrs[] = { - &mapped_slices_kattr.attr, - NULL -}; -ATTRIBUTE_GROUPS(vvz_volume_default); - -static void vvz_volume_kobj_release(struct kobject *kobj) -{ - struct vvz_volume *svol = container_of(kobj, struct vvz_volume, kobj); - - complete(&svol->kobj_released); -} - -static struct kobj_type vvz_volume_ktype = { - .release = vvz_volume_kobj_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_groups = vvz_volume_default_groups -}; - -int vvz_sysfs_register_volume(struct vvz_volume *svol) -{ - int err; - - /* Completion */ - init_completion(&svol->kobj_released); - - /* Register directory name>/ under device directory */ - err = kobject_init_and_add(&svol->kobj, &vvz_volume_ktype, &svol->sdev->kobj, - "%s", svol->name); - if (err) - goto bad; - /* Emit uevent */ - kobject_uevent(&svol->kobj, KOBJ_ADD); - - return 0; - - -bad: - kobject_put(&svol->kobj); - wait_for_completion(&svol->kobj_released); - return err; -} - -void vvz_sysfs_unregister_volume(struct vvz_volume *svol) -{ - kobject_put(&svol->kobj); - wait_for_completion(&svol->kobj_released); -} diff --git a/dm-vvz/volume.c b/dm-vvz/volume.c deleted file mode 100644 index 7de135b..0000000 --- a/dm-vvz/volume.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include "vvz.h" - - -struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, - u8 *enckey, struct dm_target *ti) -{ - struct vvz_volume *svol; - int err; - - svol = kzalloc(sizeof(*svol), GFP_KERNEL); - if (!svol) { - DMERR("Could not allocate volume"); - return ERR_PTR(-ENOMEM); - } - - svol->sdev = sdev; - svol->vol_idx = vol_idx; - sprintf(svol->name, "vvz_%u_%lu", sdev->dev_id, vol_idx); - - svol->ti = ti; - err = dm_get_device(ti, sdev->name, - dm_table_get_mode(ti->table), &svol->dm_dev); - if (err) { - ti->error = "Device lookup failed"; - goto bad_dm_dev; - } - - /* Crypto */ - svol->tfm = crypto_alloc_skcipher("xts(aes)", 0, 0); - if (IS_ERR(svol->tfm)) { - err = PTR_ERR(svol->tfm); - DMERR("Could not allocate AES-XTS cipher handle; error %d", err); - goto bad_tfm_alloc; - } - memcpy(svol->enckey, enckey, VVZ_XTS_KEYLEN); - err = crypto_skcipher_setkey(svol->tfm, svol->enckey, VVZ_XTS_KEYLEN); - if (err) { - DMERR("Could not set key in crypto transform; error %d", err); - goto bad_tfm_setkey; - } - - /* Position map */ - mutex_init(&svol->posmap_lock); - /* Slight over-allocation, to fit a whole number of blocks */ - svol->posmap = vmalloc(sdev->posmap_size_sectors * SECTOR_SIZE); - if (!svol->posmap) { - DMERR("Could not allocate position map"); - err = -ENOMEM; - goto bad_posmap_alloc; - } - svol->nr_mapped_slices = 0; - /* Load from disk */ - err = vvz_load_and_sanitise_posmap(svol); - if (err) { - DMERR("Could not load position map from disk; error %d", err); - goto bad_posmap_load; - } - - /* Register to sysfs, once initialised */ - err = vvz_sysfs_register_volume(svol); - if (err) { - DMERR("Could not register volume with sysfs; error %d", err); - goto bad_sysfs; - } - - return svol; - - -bad_sysfs: -bad_posmap_load: - vfree(svol->posmap); -bad_posmap_alloc: -bad_tfm_setkey: - crypto_free_skcipher(svol->tfm); -bad_tfm_alloc: - dm_put_device(ti, svol->dm_dev); -bad_dm_dev: - kfree(svol); - return ERR_PTR(err); -} - - -void vvz_vol_destroy(struct vvz_volume *svol) -{ - vvz_sysfs_unregister_volume(svol); - vfree(svol->posmap); - crypto_free_skcipher(svol->tfm); - dm_put_device(svol->ti, svol->dm_dev); - kfree(svol); - - return; -} diff --git a/dm-vvz/vvz.c b/dm-vvz/vvz.c deleted file mode 100644 index a61b688..0000000 --- a/dm-vvz/vvz.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include -#include -#include -#include "vvz_constants.h" -#include "vvz.h" - -#include - -/* - *---------------------------- - * Device mapper target - *---------------------------- - */ - -DEFINE_MUTEX(vvz_alldevs_lock); -struct vvz_device **vvz_alldevs = NULL; -u32 vvz_free_devid = 0; /* The lowest free devID */ - - -/* Add a device to the global array, and advance next_dev_id */ -static int vvz_add_device_global(u32 dev_id, struct vvz_device *sdev) -{ - int i; - - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return -ERESTARTSYS; - - /* Check for dev_id conflict, possible because a read() to next_dev_id - * in sysfs can return the same value to two processes */ - if (vvz_alldevs[dev_id]) { - mutex_unlock(&vvz_alldevs_lock); - DMERR("A device with this ID already exists. Retry"); - return -EINVAL; - } - // Add to the global array, and advance free_devid - vvz_alldevs[dev_id] = sdev; - for (i = vvz_free_devid; i < VVZ_MAX_DEVS && vvz_alldevs[i]; i++); - vvz_free_devid = i; - - mutex_unlock(&vvz_alldevs_lock); - - return 0; -} - -/* Remove a device from the global array, and update next_dev_id */ -static void vvz_remove_device_global(u32 dev_id) -{ - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return; - vvz_alldevs[dev_id] = NULL; - if (dev_id < vvz_free_devid) - vvz_free_devid = dev_id; - mutex_unlock(&vvz_alldevs_lock); -} - - -/* - * Create volume and, if not existent, the underlying device. - */ -static int vvz_ctr(struct dm_target *ti, unsigned int argc, char **argv) -{ - u32 dev_id; - char *bdev_path; - size_t vol_idx; - char *enckey_hex; - u8 enckey[VVZ_XTS_KEYLEN]; - u32 tot_slices; - struct vvz_device *sdev; - struct vvz_volume *svol; - int err; - - /** - * Parse arguments. - * - * argv[0]: Shufflecake-unique device ID - * argv[1]: path to underlying physical device - * argv[2]: volume index within the device - * argv[3]: number of 1-MiB slices in the underlying device - * argv[4]: 64-byte encryption key (hex-encoded, so 128 chars) - */ - if (argc != 5) { - ti->error = "Invalid argument count"; - return -EINVAL; - } - sscanf(argv[0], "%u", &dev_id); - bdev_path = argv[1]; - sscanf(argv[2], "%lu", &vol_idx); - sscanf(argv[3], "%u", &tot_slices); - enckey_hex = argv[4]; - /* Sanity checks */ - if (dev_id >= VVZ_MAX_DEVS) { - ti->error = "Device ID out of bounds"; - return -EINVAL; - } - if (vol_idx >= VVZ_DEV_MAX_VOLUMES) { - ti->error = "Volume index out of bounds"; - return -EINVAL; - } - if (strlen(enckey_hex) != 2 * VVZ_XTS_KEYLEN) { - ti->error = "Invalid key length"; - return -EINVAL; - } - /* Decode the encryption key */ - err = hex2bin(enckey, enckey_hex, VVZ_XTS_KEYLEN); - if (err) { - ti->error = "Could not decode hexadecimal encryption key"; - return err; - } - - /* Create device, if this is the first volume, otherwise retrieve it */ - if (vol_idx == 0) { - sdev = vvz_dev_create(dev_id, bdev_path, tot_slices); - if (IS_ERR(sdev)) { - ti->error = "Could not instantiate device"; - return PTR_ERR(sdev); - } - /* Insert in global array */ - err = vvz_add_device_global(dev_id, sdev); - if (err) { - ti->error = "Could not add device to global array"; - goto bad_dev_global; - } - - } else { - if (mutex_lock_interruptible(&vvz_alldevs_lock)) - return -ERESTARTSYS; - sdev = vvz_alldevs[dev_id]; - mutex_unlock(&vvz_alldevs_lock); - - if (!sdev) { - ti->error = "Could not find device"; - return -EINVAL; - } - } - - /* Create volume */ - svol = vvz_vol_create(sdev, vol_idx, enckey, ti); - if (IS_ERR(svol)) { - ti->error = "Could not instantiate volume"; - err = PTR_ERR(svol); - goto bad_vol_create; - } - /* We expect ->ctr() calls to be strictly sequential */ - sdev->nr_volumes++; - - /* Only accept one block per request for simplicity TODO: improve to one slice*/ - ti->max_io_len = VVZ_BLOCK_SCALE; - ti->flush_supported = true; - ti->num_flush_bios = 1; - ti->discards_supported = false; - ti->num_discard_bios = 0; - ti->num_secure_erase_bios = 0; - ti->num_write_zeroes_bios = 0; - ti->accounts_remapped_io = true; - ti->per_io_data_size = sizeof(struct vvz_io); - ti->private = svol; - - return 0; - - -bad_vol_create: - if (vol_idx == 0) { - vvz_remove_device_global(dev_id); -bad_dev_global: - vvz_dev_destroy(sdev); - } - return err; -} - - -/* Destroy volume and, if needed, the underlying device */ -static void vvz_dtr(struct dm_target *ti) -{ - struct vvz_volume *svol = ti->private; - struct vvz_device *sdev = svol->sdev; - - vvz_vol_destroy(svol); - /* We expect ->dtr() calls to be strictly sequential */ - sdev->nr_volumes--; - - if (sdev->nr_volumes == 0) { - vvz_remove_device_global(sdev->dev_id); - vvz_dev_destroy(sdev); - } - - return; -} - - -static int vvz_map(struct dm_target *ti, struct bio *bio) -{ - struct vvz_io *sio = dm_per_bio_data(bio, sizeof(struct vvz_io)); - struct vvz_volume *svol = ti->private; - sector_t lblk_num = bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT; - - if (unlikely(!bio_has_data(bio))) { -// DMWARN("No-data bio: bio_op() = %d, bi_opf = %u, bi_io_vec = %p, bi_idx = %u", bio_op(bio), bio->bi_opf, bio->bi_io_vec, bio->bi_iter.bi_idx); -// msleep(100); - } - - /* Flush requests are just passed down, since our position map is - * currently write-through, so we have no volatile cache */ - if (unlikely(bio->bi_opf & REQ_PREFLUSH)) { - /* Has to be empty though */ - if (bio_sectors(bio)) { - DMWARN("Non-empty flush request!"); - msleep(3000); - return DM_MAPIO_KILL; - } -// DMWARN("REQ_PREFLUSH empty (phew), sector: %llu", bio->bi_iter.bi_sector); -// msleep(100); - bio_set_dev(bio, svol->dm_dev->bdev); - return DM_MAPIO_REMAPPED; - } - - /* Accept one block at a time TODO improve */ - if (unlikely(bio->bi_iter.bi_size > VVZ_BLOCK_SIZE)) { - DMWARN("Big bio: %u", bio->bi_iter.bi_size); - msleep(300); - dm_accept_partial_bio(bio, VVZ_BLOCK_SCALE); - } - /* Only one segment, single page, starting at 0 TODO improve */ - if (unlikely(bio_segments(bio) > 1 || - bio_offset(bio) != 0)) { - DMWARN("Unaligned bio!"); - msleep(3000); - return DM_MAPIO_KILL; - } - if (unlikely(bio->bi_iter.bi_size != VVZ_BLOCK_SIZE)) { - DMWARN("Wrong bio size: %u", bio->bi_iter.bi_size); - msleep(3000); - return DM_MAPIO_KILL; - } - - /* Init I/O struct */ - sio->svol = svol; - sio->orig_bio = bio; - sio->lsi = lblk_num >> VVZ_SLICE_SHIFT; - sio->block_offset = lblk_num & ((1U << VVZ_SLICE_SHIFT) - 1); - - /* Enqueue */ - if (bio_data_dir(bio) == READ) - INIT_WORK(&sio->work, vvz_read_work_fn); - else - INIT_WORK(&sio->work, vvz_write_work_fn); - queue_work(svol->sdev->io_queue, &sio->work); - - return DM_MAPIO_SUBMITTED; -} - - -static void vvz_io_hints(struct dm_target *ti, struct queue_limits *limits) -{ - // Currently, we only handle one block at a time TODO improve - limits->logical_block_size = VVZ_BLOCK_SIZE; - limits->physical_block_size = VVZ_BLOCK_SIZE; - limits->io_min = VVZ_BLOCK_SIZE; - limits->io_opt = VVZ_BLOCK_SIZE; - - return; -} - - -static int vvz_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) -{ - struct vvz_volume *svol = ti->private; - struct vvz_device *sdev = svol->sdev; - - if (!fn) { - dump_stack(); - msleep(2000); - return -EINVAL; - } - return fn(ti, svol->dm_dev, 0, sdev->dev_header_size_sectors + ti->len, data); -} - - -/* - *---------------------------- - * Kernel module - *---------------------------- - */ - -static struct target_type vvz_target = { - .name = VVZ_TARGET_NAME, - .version = {VVZ_VER_MAJOR, VVZ_VER_MINOR, VVZ_VER_REVISION}, - .module = THIS_MODULE, - .ctr = vvz_ctr, - .dtr = vvz_dtr, - .map = vvz_map, - .io_hints = vvz_io_hints, - .iterate_devices = vvz_iterate_devices, -}; - - -/* Module entry point: init variables and register DM target */ -static int __init vvz_init(void) -{ - int err; - - /* For the moment, we assume PAGE_SIZE == VVZ_BLOCK_SIZE TODO improve */ - if (VVZ_BLOCK_SIZE != PAGE_SIZE) { - DMERR("Error, PAGE_SIZE != %d bytes not yet supported", VVZ_BLOCK_SIZE); - err = -ENOTRECOVERABLE; - goto bad_page_size; - } - - vvz_alldevs = vzalloc(VVZ_MAX_DEVS * sizeof(*vvz_alldevs)); - if (!vvz_alldevs) { - DMERR("Could not allocate vvz_alldevs"); - err = -ENOMEM; - goto bad_alldevs_alloc; - } - - err = vvz_sysfs_init(); - if (err) { - DMERR("Could not init sysfs; error %d", err); - goto bad_sysfs_init; - } - - err = dm_register_target(&vvz_target); - if (err < 0) { - DMERR("Could not register DM target"); - goto bad_register_target; - } - - DMINFO("loaded"); - return 0; - - -bad_register_target: - vvz_sysfs_exit(); -bad_sysfs_init: - vfree(vvz_alldevs); -bad_alldevs_alloc: -bad_page_size: - DMERR("not loaded"); - return err; -} - - -/* Module exit point: de-init variables and unregister DM target */ -static void __exit vvz_exit(void) -{ - dm_unregister_target(&vvz_target); - vvz_sysfs_exit(); - vfree(vvz_alldevs); - - DMINFO("unloaded"); - return; -} - - -module_init(vvz_init); -module_exit(vvz_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Shufflecake authors"); -MODULE_DESCRIPTION(DM_NAME " target for Shufflecake"); diff --git a/dm-vvz/vvz.h b/dm-vvz/vvz.h deleted file mode 100644 index de4ec73..0000000 --- a/dm-vvz/vvz.h +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _VVZ_H -#define _VVZ_H - -#include -#include -#include -#include -#include "vvz_constants.h" - - -/* - *---------------------------- - * Constants - *---------------------------- - */ - -#define DM_MSG_PREFIX "vvz" - - -/* - *---------------------------- - * Structs - *---------------------------- - */ - -struct vvz_device -{ - /* Shufflecake-unique device ID */ - u32 dev_id; - /* : */ - char name[16]; - - /* Logical size of each volume */ - u32 tot_slices; - /* Header sizes in 512-byte sectors */ - sector_t posmap_size_sectors; - sector_t dev_header_size_sectors; - - /* Number of volumes. No need for an atomic_t */ - size_t nr_volumes; - - /* Shuffled array of PSIs */ - struct mutex slices_lock; - u32 *prmslices; - u32 prmslices_octr; - bool *slices_ofld; - u32 nr_free_slices; - - /* Sysfs */ - struct kobject kobj; - struct completion kobj_released; - - /* Resource sharing */ - struct bio_set bioset; - struct dm_io_client *io_client; - struct workqueue_struct *io_queue; - struct workqueue_struct *crypt_queue; -}; - -struct vvz_volume -{ - /* Backing device */ - struct vvz_device *sdev; - - /* Underlying block device. This can't go in the vvz_device struct, - * because each ti grabs its own reference. */ - struct dm_dev *dm_dev; - struct dm_target *ti; - - /* Volume index within the device */ - size_t vol_idx; - /* Volume name: vvz__ */ - char name[32]; - - /* Position map */ - struct mutex posmap_lock; - u32 *posmap; - u32 nr_mapped_slices; - - /* Sysfs */ - struct kobject kobj; - struct completion kobj_released; - - /* Crypto */ - u8 enckey[VVZ_XTS_KEYLEN]; - struct crypto_skcipher *tfm; -}; - -struct vvz_io -{ - struct vvz_volume *svol; - - struct bio *orig_bio; - struct bio *phys_bio; - u32 lsi; - u32 block_offset; - u32 psi; - - struct work_struct work; -}; - - -/* - *---------------------------- - * Macros - *---------------------------- - */ - -/* Starting sector of position map */ -#define VVZ_POSMAP_START_SECTOR(svol) \ - (VVZ_BLOCK_SCALE * (1 + VVZ_DEV_MAX_VOLUMES) + \ - (svol)->vol_idx * (svol)->sdev->posmap_size_sectors) - - -/* Physical sector of a remapped bio */ -#define VVZ_PHYS_BIO_SECTOR(sdev, psi, off) ( \ - (sdev)->dev_header_size_sectors + ( \ - ((psi << VVZ_SLICE_SHIFT) + off) << VVZ_BLOCK_SHIFT \ - ) \ -) - -/* - *---------------------------- - * Global variables - *---------------------------- - */ - -/* Array of devices, and next free id */ -extern struct mutex vvz_alldevs_lock; -extern struct vvz_device **vvz_alldevs; -extern u32 vvz_free_devid; /* The lowest free devID */ - - -/* - *---------------------------- - * Functions - *---------------------------- - */ - -/* Device */ -struct vvz_device *vvz_dev_create(u32 dev_id, char *bdev_path, u32 tot_slices); -void vvz_dev_destroy(struct vvz_device *sdev); - -/* Volume */ -struct vvz_volume *vvz_vol_create(struct vvz_device *sdev, size_t vol_idx, - u8 *enckey, struct dm_target *ti); -void vvz_vol_destroy(struct vvz_volume *svol); - -/* Sysfs */ -int vvz_sysfs_init(void); -void vvz_sysfs_exit(void); -int vvz_sysfs_register_device(struct vvz_device *sdev); -void vvz_sysfs_unregister_device(struct vvz_device *sdev); -int vvz_sysfs_register_volume(struct vvz_volume *svol); -void vvz_sysfs_unregister_volume(struct vvz_volume *svol); - -/* Bio mapping */ -void vvz_read_work_fn(struct work_struct *work); -void vvz_write_work_fn(struct work_struct *work); - -/* Position map */ -int vvz_load_and_sanitise_posmap(struct vvz_volume *svol); -int vvz_create_persistent_slice_mapping(struct vvz_volume *svol, u32 lsi, u32 *psi); - -/* Crypto */ -int vvz_crypt_blocks_vm(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, - u64 num_blocks, u64 first_pblk_num, int rw); -int vvz_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, - struct page *dst_page, u64 pblk_num, int rw); - -#endif /* _VVZ_H */ diff --git a/dm-vvz/vvz_constants.h b/dm-vvz/vvz_constants.h deleted file mode 100644 index 7ed62d1..0000000 --- a/dm-vvz/vvz_constants.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* This is just a placeholder for defining constants and parameters that must - * be the same across Shufflecake components (kernel module, userland tool) - * such as block size, slice size etc */ - -#ifndef _VVZ_CONSTANTS_H_ -#define _VVZ_CONSTANTS_H_ - - -#define VVZ_TARGET_NAME "vvz" - - -#define VVZ_VER_MAJOR 0 -#define VVZ_VER_MINOR 4 -#define VVZ_VER_REVISION 0 -#define VVZ_VER_SPECIAL "rc1" - -#define STRINGIFY0(s) # s -#define STRINGIFY(s) STRINGIFY0(s) - -#define VVZ_VERSION STRINGIFY(VVZ_VER_MAJOR)"."STRINGIFY(VVZ_VER_MINOR)"."STRINGIFY(VVZ_VER_REVISION)""VVZ_VER_SPECIAL - - -#define VVZ_MODE_LEGACY 0 -#define VVZ_MODE_LITE 1 -#define VVZ_MODE_FULL 2 - - -#define VVZ_BLOCK_SIZE 4096 /* bytes */ -#define VVZ_BLOCK_SHIFT 3 -#define VVZ_BLOCK_SCALE (1 << VVZ_BLOCK_SHIFT) /* 8 sectors in a block */ -#define VVZ_SLICE_SHIFT 8 -#define VVZ_SLICE_SCALE (1 << VVZ_SLICE_SHIFT) /* 256 blocks in a slice */ - - -/* XTS requires doubling the key size */ -#define VVZ_XTS_KEYLEN 64 /* bytes */ -/* The IV is the right-0-padded LE physical block number */ -#define VVZ_XTS_IVLEN 16 /* bytes */ - - -#define VVZ_DEV_MAX_VOLUMES 15 -#define VVZ_MAX_DEVS 1024 - - -#define VVZ_PSI_INVALID 0xFFFFFFFF -/* PosMap entries are 4 bytes, therefore there are 1024 of them in a block */ -#define VVZ_PSIS_PER_BLOCK 1024 - - -/* Sysfs entries under /sys/module/dm_vvz/ */ -#define VVZ_SYSFS_BDEVS "bdevs" -#define VVZ_SYSFS_DEVID "next_dev_id" - - -#endif /* _VVZ_CONSTANTS_H_ */ diff --git a/dm-vvz/write.c b/dm-vvz/write.c deleted file mode 100644 index 36ee151..0000000 --- a/dm-vvz/write.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#include "vvz.h" -#include - - -static void vvz_write_endio(struct bio *phys_bio); - - -void vvz_write_work_fn(struct work_struct *work) -{ - struct vvz_io *sio = container_of(work, struct vvz_io, work); - struct vvz_volume *svol = sio->svol; - struct vvz_device *sdev = svol->sdev; - struct bio *orig_bio = sio->orig_bio; - struct bio_vec bvl = bio_iovec(orig_bio); - struct bio *phys_bio; - struct page *page; - u32 lsi = sio->lsi; - u32 block_offset = sio->block_offset; - u32 psi; - int err; - -// DMWARN("WRITE: dequeued. Sector = %llu", orig_bio->bi_iter.bi_sector); -// msleep(100); - - - /* Read existing mapping, or create new one */ - if (mutex_lock_interruptible(&svol->posmap_lock)) { - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } - psi = svol->posmap[lsi]; - /* If LSI unmapped, create new mapping, while holding the lock */ - if (psi == VVZ_PSI_INVALID) { -// DMWARN("WRITE: unmapped LSI %u, sampling PSI", lsi); -// msleep(100); - - err = vvz_create_persistent_slice_mapping(svol, lsi, &psi); - if (err){ - DMERR("Could not create slice mapping; error %d", err); - mutex_unlock(&svol->posmap_lock); - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } -// DMWARN("WRITE: sampled PSI %u for LSI %u", psi, lsi); -// msleep(100); - } - mutex_unlock(&svol->posmap_lock); - sio->psi = psi; - - /* Allocate physical bio */ - phys_bio = bio_alloc_bioset(svol->dm_dev->bdev, 1, orig_bio->bi_opf, - GFP_NOIO, &sdev->bioset); - if (!phys_bio) { - DMERR("Could not allocate physical bio"); - orig_bio->bi_status = BLK_STS_IOERR; - goto endio; - } - /* Insert in the I/O struct */ - sio->phys_bio = phys_bio; - - /* Physical bio needs its own page */ - page = alloc_pages(GFP_NOIO, 0); - if (!page) { - DMERR("Could not allocate page for physical bio"); - orig_bio->bi_status = BLK_STS_IOERR; - goto bad_alloc_page; - } - - /* Remap sector */ - phys_bio->bi_iter.bi_sector = VVZ_PHYS_BIO_SECTOR(sdev, psi, block_offset); - /* Encrypt */ - err = vvz_crypt_block_page(svol->tfm, bvl.bv_page, page, - phys_bio->bi_iter.bi_sector >> VVZ_BLOCK_SHIFT, WRITE); - if (err) { - DMERR("Could not encrypt bio; error %d", err); - orig_bio->bi_status = BLK_STS_IOERR; - goto bad_encrypt; - } - - /* Add page to bio */ - __bio_add_page(phys_bio, page, VVZ_BLOCK_SIZE, 0); - /* Set fields for the endio */ - phys_bio->bi_private = sio; - phys_bio->bi_end_io = vvz_write_endio; - /* Submit */ - dm_submit_bio_remap(orig_bio, phys_bio); - - return; - - -bad_encrypt: - __free_page(page); -bad_alloc_page: - bio_put(phys_bio); -endio: - bio_endio(orig_bio); - return; -} - -static void vvz_write_endio(struct bio *phys_bio) -{ - struct vvz_io *sio = phys_bio->bi_private; - struct bio *orig_bio = sio->orig_bio; - - /* If physical bio failed, then fail-fast */ - if (phys_bio->bi_status != BLK_STS_OK) { - orig_bio->bi_status = phys_bio->bi_status; - DMWARN("WRITE ENDIO: phys_bio failed"); - goto endio; - } - - /* Advance original bio by one block */ - bio_advance(orig_bio, VVZ_BLOCK_SIZE); - orig_bio->bi_status = BLK_STS_OK; - -endio: - /* Free the physical bio and its page */ - bio_free_pages(phys_bio); - bio_put(phys_bio); - /* End original bio */ - bio_endio(orig_bio); - - return; -} - diff --git a/shufflecake-userland-legacy/Makefile b/shufflecake-userland-legacy/Makefile deleted file mode 100644 index e8598dc..0000000 --- a/shufflecake-userland-legacy/Makefile +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright The Shufflecake Project Authors (2022) -# Copyright The Shufflecake Project Contributors (2022) -# Copyright Contributors to the The Shufflecake Project. - -# See the AUTHORS file at the top-level directory of this distribution and at -# - -# This file is part of the program shufflecake-c, which is part of the Shufflecake -# Project. Shufflecake is a plausible deniability (hidden storage) layer for -# Linux. See . - -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 2 of the License, or (at your option) -# any later version. This program 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 General -# Public License for more details. You should have received a copy of the -# GNU General Public License along with this program. -# If not, see . - -############################################################################# -# Makefile with dependency auto-generation, taken and adapted from -# https://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ -############################################################################# - -# Output dirs for binaries -BIN_DIR := bin -PROJ_OUT_DIR := $(BIN_DIR)/proj_build -TEST_OUT_DIR := $(BIN_DIR)/test_build -# Output dirs for dependency files -PROJ_DEP_DIR := $(BIN_DIR)/.proj_deps -TEST_DEP_DIR := $(BIN_DIR)/.test_deps - -# Include directories for compilation -INCLUDE := include test - -# Use gcc -CC := gcc -# All warnings, add includes (other options may be supplied on command line) -override CFLAGS += -Wall $(addprefix -I,$(INCLUDE)) -# Flags for dependency file auto-generation (deferred evaluation) -PROJ_DEPFLAGS = -MT $@ -MMD -MP -MF $(PROJ_DEP_DIR)/$*.d -TEST_DEPFLAGS = -MT $@ -MMD -MP -MF $(TEST_DEP_DIR)/$*.d -# Linker flags -LDFLAGS := -lgcrypt -ldevmapper - -# The variables PROJ_SRCS (and PROJ_ROOT) and TEST_SRCS (and TEST_ROOT) are defined in this Makefile -include Makefile.sources -# Create the three lists of object files -PROJ_OBJS := $(PROJ_SRCS:$(PROJ_ROOT)/%.c=$(PROJ_OUT_DIR)/%.o) -TEST_OBJS := $(TEST_SRCS:$(TEST_ROOT)/%.c=$(TEST_OUT_DIR)/%.o) -PROJ_OBJS_NO_MAIN := $(filter-out $(PROJ_OUT_DIR)/main.o,$(PROJ_OBJS)) -# Create the two lists of dependency files -PROJ_DEPS := $(PROJ_SRCS:$(PROJ_ROOT)/%.c=$(PROJ_DEP_DIR)/%.d) -TEST_DEPS := $(TEST_SRCS:$(TEST_ROOT)/%.c=$(TEST_DEP_DIR)/%.d) -# Put them together -DEPS := $(PROJ_DEPS) $(TEST_DEPS) - -# All directories to be created if non-existing (sort to remove duplicates) -DIRS := $(sort $(dir $(BIN_DIR) $(PROJ_OBJS) $(TEST_OBJS) $(PROJ_DEPS) $(TEST_DEPS))) - -# The target binaries -MAIN_BIN := $(PROJ_OUT_DIR)/shufflecake-legacy -TEST_BIN := $(TEST_OUT_DIR)/tests -# Their symlink -MAIN_LINK := shufflecake-legacy -TEST_LINK := tests - - - -#### -#### RULES -#### - - -.PHONY: main -main: $(MAIN_BIN) - -.PHONY: test -test: $(TEST_BIN) - -.PHONY: link_msg -link_msg: - @echo "Linking object files" - -.PHONY: compile_msg -compile_msg: - @echo "Compiling source files" - -# Link project object files -$(MAIN_BIN): $(PROJ_OBJS) | link_msg - @echo "\t---> $@" - @$(CC) $^ -o $@ $(LDFLAGS) - @rm -f $(MAIN_LINK) - @ln -s $@ $(MAIN_LINK) - -# Link test object files -$(TEST_BIN): $(PROJ_OBJS_NO_MAIN) $(TEST_OBJS) | link_msg - @echo "\t---> $@" - @$(CC) $^ -o $@ $(LDFLAGS) - @rm -f $(TEST_LINK) - @ln -s $@ $(TEST_LINK) - @echo "Done, launching tests" - @./$(TEST_LINK) - -# Cancel implicit rule -%.o : %.c - -# Build project object file -$(PROJ_OUT_DIR)/%.o : $(PROJ_ROOT)/%.c $(PROJ_DEP_DIR)/%.d | $(DIRS) compile_msg - @echo "\t---> $@" - @$(CC) $(PROJ_DEPFLAGS) $(CFLAGS) -c -o $@ $< - -# Build test object file -$(TEST_OUT_DIR)/%.o : $(TEST_ROOT)/%.c $(TEST_DEP_DIR)/%.d | $(DIRS) compile_msg - @echo "\t---> $@" - @$(CC) $(TEST_DEPFLAGS) $(CFLAGS) -c -o $@ $< - -# Create needed directories -$(DIRS): - @mkdir -p $@ - -.PHONY: clean -clean: - rm -rf $(PROJ_OUT_DIR) $(TEST_OUT_DIR) $(PROJ_DEP_DIR) $(TEST_DEP_DIR) - rm -rf $(BIN_DIR) - @rm -f $(MAIN_LINK) $(TEST_LINK) - -$(DEPS): -include $(wildcard $(DEPS)) diff --git a/shufflecake-userland-legacy/Makefile.sources b/shufflecake-userland-legacy/Makefile.sources deleted file mode 100644 index 0e2a80b..0000000 --- a/shufflecake-userland-legacy/Makefile.sources +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright The Shufflecake Project Authors (2022) -# Copyright The Shufflecake Project Contributors (2022) -# Copyright Contributors to the The Shufflecake Project. - -# See the AUTHORS file at the top-level directory of this distribution and at -# - -# This file is part of the program shufflecake-c, which is part of the Shufflecake -# Project. Shufflecake is a plausible deniability (hidden storage) layer for -# Linux. See . - -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 2 of the License, or (at your option) -# any later version. This program 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 General -# Public License for more details. You should have received a copy of the -# GNU General Public License along with this program. -# If not, see . - -######################################### -# Only define the sources to be compiled -######################################### - -#### -#### Main files -#### - -PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c) -PROJ_SRCS += $(addprefix header/,position_map.c volume_master_block.c device_master_block.c) -PROJ_SRCS += $(addprefix operations/,volume_header.c devmapper.c dmb.c) -PROJ_SRCS += $(addprefix commands/,init.c open.c close.c test_pwd.c change_pwd.c) -PROJ_SRCS += $(addprefix cli/,dispatch.c init.c open.c close.c testpwd.c changepwd.c) -PROJ_SRCS += main.c - -PROJ_ROOT := src -PROJ_SRCS := $(addprefix $(PROJ_ROOT)/,$(PROJ_SRCS)) - - -#### -#### Test files -#### - -TEST_SRCS := $(addprefix crypto/,test_aes256ctr.c test_aes256gcm.c test_argon2id.c) -TEST_SRCS += main.c - -TEST_ROOT := test -TEST_SRCS := $(addprefix $(TEST_ROOT)/,$(TEST_SRCS)) diff --git a/shufflecake-userland-legacy/include/cli.h b/shufflecake-userland-legacy/include/cli.h deleted file mode 100644 index 838f618..0000000 --- a/shufflecake-userland-legacy/include/cli.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _CLI_H_ -#define _CLI_H_ - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/* Action to create volumes */ -#define SFLC_CLI_INITACT "init" -/* Action to open volumes */ -#define SFLC_CLI_OPENACT "open" -/* Action to close volumes */ -#define SFLC_CLI_CLOSEACT "close" -/* Action to test password */ -#define SFLC_CLI_TESTPWDACT "testpwd" -/* Action to change password */ -#define SFLC_CLI_CHANGEPWDACT "changepwd" - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Called by the main to parse the arguments and dispatch to the right command */ -int sflc_cli_dispatch(int argc, char **argv); - -/* Initializes device and create empty volumes */ -int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill); -/* Open volumes */ -int sflc_cli_open(char *block_device); -/* Close volumes */ -int sflc_cli_close(char *block_device); -/* Test password */ -int sflc_cli_testPwd(char *block_device); -/* Change password */ -int sflc_cli_changePwd(char *block_device); - - -#endif /* _CLI_H_ */ - - diff --git a/shufflecake-userland-legacy/include/commands.h b/shufflecake-userland-legacy/include/commands.h deleted file mode 100644 index 68a14ac..0000000 --- a/shufflecake-userland-legacy/include/commands.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _COMMANDS_H_ -#define _COMMANDS_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "header.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - - -/***************************************************** - * STRUCTS * - *****************************************************/ - -/* Parameters for the init command */ -typedef struct -{ - /* Underlying block device */ - char *bdev_path; - - /* Number of volumes */ - size_t nr_vols; - /* Volumes' passwords */ - char **pwds; - size_t *pwd_lens; - - /* Option to skip random filling */ - bool no_randfill; - -} sflc_cmd_InitArgs; - - -/* Parameters for the open command */ -typedef struct -{ - /* Underlying block device */ - char *bdev_path; - - /* The only password provided */ - char *pwd; - size_t pwd_len; - -} sflc_cmd_OpenArgs; - -typedef struct -{ - /* Underlying block device */ - char *bdev_path; - - /* Content of the DMB cell */ - sflc_DmbCell *dmb_cell; - - /* The new password */ - char *new_pwd; - size_t new_pwd_len; - -} sflc_cmd_ChangePwdArgs; - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Create N volumes (only formats the device header, does not open the volumes) */ -int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args); - -/* Open M volumes, from the first down to the one whose pwd is provided */ -int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args); - -/* Close all volumes on the device (reads the list from sysfs) */ -int sflc_cmd_closeVolumes(char *bdev_path); - -/* Tests which volume is unlocked by the given password */ -int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell); - -/* Changes the specified volume's password */ -int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args); - - -#endif /* _COMMANDS_H_ */ diff --git a/shufflecake-userland-legacy/include/header.h b/shufflecake-userland-legacy/include/header.h deleted file mode 100644 index 869b868..0000000 --- a/shufflecake-userland-legacy/include/header.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _HEADER_H_ -#define _HEADER_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/crypto.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/* The DMB contains one IV + one VMB key + one MAC for each volume */ -#define SFLC_DMB_CELL_SIZE (SFLC_AESGCM_PADDED_IVLEN + SFLC_CRYPTO_KEYLEN + SFLC_AESGCM_TAGLEN) - -/* Let us enforce that the one DMB can fit cells for all volumes */ -#if SFLC_ARGON_SALTLEN + (SFLC_DEV_MAX_VOLUMES * SFLC_DMB_CELL) > SFLC_SECTOR_SIZE -#error "Invalid combination of parameters: probably SFLC_DEV_MAX_VOLUMES is too big" -#endif - - -// The VMB cleartext occupies the last 4064 bytes on-disk (4096 bytes minus IV and MAC) -#define SFLC_CLEAR_VMB_LEN (SFLC_SECTOR_SIZE - \ - SFLC_AESGCM_PADDED_IVLEN - \ - SFLC_AESGCM_TAGLEN) - - - -/***************************************************** - * STRUCTS * - *****************************************************/ - -/** - * The on-disk master block of a device contains lots of crypto stuff - * (a KDF salt, IVs, MACs...) used to properly hide the VMB keys. - * This struct only contains such useful info, in the clear. - */ -typedef struct { - // Each volume's VMB key - char vmb_keys[SFLC_DEV_MAX_VOLUMES][SFLC_CRYPTO_KEYLEN]; - - // How many of these need actually be encrypted - size_t nr_vols; - -} sflc_Dmb; - - -/** - * When unsealing a DMB, only one VMB key can be unlocked with a password. - * An invalid value for vol_idx means no VMB key could be unlocked (wrong pwd) - */ -typedef struct { - // The unlocked VMB key - char vmb_key[SFLC_CRYPTO_KEYLEN]; - - // The index of the volume opened by this VMB key - size_t vol_idx; - -} sflc_DmbCell; - - -/** - * The on-disk master block of a volume contains crypto stuff - * (an IV) used to properly hide the useful info. This struct - * only contains the useful info, in the clear. - */ -typedef struct { - // The key that encrypts the volume's data section - char volume_key[SFLC_CRYPTO_KEYLEN]; - - // The key that encrypts the previous volume's master block - char prev_vmb_key[SFLC_CRYPTO_KEYLEN]; - - // The total number of logical slices virtually available to this volume - size_t nr_slices; - -} sflc_Vmb; - - -/** - * This struct represents an encrypted empty position map. - * On-disk, the layout interleaves one IV block with 256 PosMap blocks (each - * encrypted by an IV in the IV block). Many such "runs" can be concatenated, - * until the position map is big enough to index the desired number of slices. - * The last "run" might be incomplete, in that it could have less than 256 - * PosMap blocks, if not all of them are needed. - * In the struct, there are as many IV blocks as there are PosMapBlock arrays - * (equal to the number of "runs"). The m-th IV of the n-th IV block encrypts - * the m-th block of the n-th array. The PosMapBlocks in an array are stored - * contiguously in RAM, so a PosMapBlock array is just a char array of length - * multiple of 4096. All the arrays are full (256 PosMapBlocks, 1 MiB) except - * for the last one, which may hold fewer blocks. - */ -typedef struct { - // The number of PosMapBlock arrays (and of IV blocks) - size_t nr_arrays; - - // The sequence of IV blocks - char **iv_blocks; - // The sequence of (encrypted) PosMapBlock arrays - char **pmb_arrays; - - // The number of PosMapBlocks in the last array - size_t nr_last_pmbs; - -} sflc_EncPosMap; - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* "Encrypt" each VMB key with its pwd, so the DMB is ready to be written on-disk */ -int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block); -/* "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF) */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); -/* Re-encrypt the content of a single DMB cell */ -int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len); - - -/* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk */ -int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); -/* "Decrypt" a VMB coming from the disk, directly using its key */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); - - -/* Create an encrypted empty position map for the given number of slices (allocates memory) */ -int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm); - - - -#endif /* _HEADER_H_ */ diff --git a/shufflecake-userland-legacy/include/operations.h b/shufflecake-userland-legacy/include/operations.h deleted file mode 100644 index 8de9ab6..0000000 --- a/shufflecake-userland-legacy/include/operations.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _OPERATIONS_H_ -#define _OPERATIONS_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "header.h" -#include "utils/crypto.h" -#include "utils/math.h" - - -/***************************************************** - * INLINE FUNCTIONS * - *****************************************************/ - -// Size, in 4096-byte blocks, of a whole volume header (VMB+PM) -static inline size_t sflc_volHeaderSize(size_t nr_slices) -{ - // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - size_t nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); - // Each array holds up to 256 PosMapBlocks - size_t nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); - - // 1 VMB, the PMBs, and the IV blocks - return 1 + nr_pmbs + nr_arrays; -} - -// Position of the VMB for the given volume -static inline uint64_t sflc_vmbPosition(size_t vol_idx, size_t nr_slices) -{ - return 1 + ((uint64_t) vol_idx) * ((uint64_t) sflc_volHeaderSize(nr_slices)); -} - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Encrypts and writes the DMB to disk */ -int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb); -/* Reads the DMB from disk and outputs the unlocked VMB key */ -int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell); -/* Reads the DMB from disk, changes the relevant DMB cell, and writes it back */ -int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len); - -/* Encrypts and writes a volume header (VMB+PM) on-disk */ -int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); -/* Reads a VMB from disk and unlocks it */ -int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); - -/* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device */ -int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); -/* Close the volume via the appropriate ioctl to DM */ -int sflc_ops_closeVolume(char *label); - -#endif /* _OPERATIONS_H_ */ diff --git a/shufflecake-userland-legacy/include/sflc_constants.h b/shufflecake-userland-legacy/include/sflc_constants.h deleted file mode 120000 index fab9e42..0000000 --- a/shufflecake-userland-legacy/include/sflc_constants.h +++ /dev/null @@ -1 +0,0 @@ -../../dm-sflc/sflc_constants.h \ No newline at end of file diff --git a/shufflecake-userland-legacy/include/utils/crypto.h b/shufflecake-userland-legacy/include/utils/crypto.h deleted file mode 100644 index 15ac111..0000000 --- a/shufflecake-userland-legacy/include/utils/crypto.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_CRYPTO_H_ -#define _UTILS_CRYPTO_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/sflc.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -// Key length, for input into AES-CTR and AES-GCM, and for output from Argon -#define SFLC_CRYPTO_KEYLEN 32 /* bytes */ - -// IV length for AES-CTR -#define SFLC_AESCTR_IVLEN 16 /* bytes */ - -// IV length for AES-GCM -#define SFLC_AESGCM_IVLEN 12 /* bytes */ - -// IVs occupy 16 bytes on-disk, but only the *FIRST* 12 are used for AES-GCM -#define SFLC_AESGCM_PADDED_IVLEN 16 /* bytes */ - -// MAC length for AES-GCM -#define SFLC_AESGCM_TAGLEN 16 /* bytes */ - -// Content of output plaintext upon MAC verification failure -#define SFLC_AESGCM_POISON_PT 0xFF - - -/* Argon parameters */ - -// Argon salt length -#define SFLC_ARGON_SALTLEN 16 /* bytes */ - -// Argon memory parameter -// We assume machines with at least 128 MiB available RAM, so 2^17 kiB -#define SFLC_ARGON_M (1 << 17) /* kibibytes */ - -// Argon iterations count -// We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) -#define SFLC_ARGON_T 3 - -// Argon parallelism parameter (recommended to be 2 * CPU cores) -// We assume use even on single core devices -#define SFLC_ARGON_P 2 - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Get slow, true random bytes (suited for keys) */ -int sflc_rand_getStrongBytes(char *buf, size_t buflen); -/* Get fast, pseudo random bytes (suited for IVs and padding) */ -int sflc_rand_getWeakBytes(char *buf, size_t buflen); - -/* AES256-CTR encryption, does not touch the IV. Set ct = NULL for in-place. */ -int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct); -/* AES256-CTR decryption, does not touch the IV. Set pt = NULL for in-place. */ -int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt); - -/* AES256-GCM encryption, does not touch the IV */ -int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag); -/* AES256-GCM decryption, does not touch the IV (only decrypts if MAC is valid) */ -int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match); - -/* Compute Argon KDF */ -int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash); - - -#endif /* _UTILS_CRYPTO_H_ */ diff --git a/shufflecake-userland-legacy/include/utils/disk.h b/shufflecake-userland-legacy/include/utils/disk.h deleted file mode 100644 index 16885e3..0000000 --- a/shufflecake-userland-legacy/include/utils/disk.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Disk helper functions - */ - -#ifndef _UTILS_DISK_H_ -#define _UTILS_DISK_H_ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/sflc.h" - - -/***************************************************** - * MACROS * - *****************************************************/ - -/** - * Max slices for given disk size (in 4096-byte blocks). - * - * The bigger a disk is, the more slices it can host. However, the more slices we format it with, - * the bigger the position map needed to index them: the header size grows with the number of slices, - * taking up part of the space that's supposed to host those slices. - * To settle the matter, let us derive an upper bound on the header size, yielding a "safe" value - * for the number of slices (given a disk size). - * - * To index s slices, we need pm := ceil(s/1024) <= s/1024 + 1 PosMap blocks, since each PosMap - * block (4096 bytes) can host 1024 slice indices (4 bytes each). - * - * To encrypt those PosMap blocks, we need iv := ceil(pm/256) <= pm IV blocks, since each IV block - * (4096 bytes) can encrypt 256 data blocks (IVs are 16 bytes). - * - * Therefore, a position map indexing s slices occupies pm+iv <= 2*pm <= 2*s/1024 + 2 blocks. - * - * A single volume's header contains the Volume Master Block and the position map, therefore it - * occupies 1+pm+iv <= 2*s/1024 + 3 blocks. - * - * The entire device's header simply contains 15 volume headers of the same size, therefore it - * occupies h := 15 * (1+pm+iv) <= 15*2*s/1024 + 3*15 <= s + 3*15 blocks. - * The last inequality follows from 15*2/1024 <= 1 (we need to enforce this on the symbolic values). - * - * To actually host the s slices, the data section needs 257*s blocks (256 data blocks + 1 IV block - * per slice). - * - * Therefore, in order to format a disk with s slices, we need at most (s + 3*15) + 257*s = - * = (1 + 257)*s + 3*15 blocks. - * - * If we are given d blocks on the disk, a safe value for s is one that satisfies - * (1 + 257)*s + 3*15 <= d <==> s <= (d - 3*15) / (1 + 257) - */ -#define sflc_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLC_BLOCKS_PER_PHYS_SLICE) - - -/* Let us enforce, on the symbolic values, the inequality used in the previous bound */ -#if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK -#error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" -#endif - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -char *sflc_disk_getDeviceName(char *bdev_path); - -/* Checks whether the given path points to a block device */ -bool sflc_disk_isBlockDevice(char *path); - -/* Returns the size in 4096-byte sectors (or < 0 if error) */ -int64_t sflc_disk_getSize(char * bdev_path); - -/* Reads a single 4096-byte sector from the disk */ -int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf); - -/* Writes a single 4096-byte sector to the disk */ -int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf); - -/* Writes many 4096-byte sectors to the disk */ -int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors); - - -#endif /* _UTILS_DISK_H_ */ diff --git a/shufflecake-userland-legacy/include/utils/input.h b/shufflecake-userland-legacy/include/utils/input.h deleted file mode 100644 index 9463999..0000000 --- a/shufflecake-userland-legacy/include/utils/input.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_INPUT_H_ -#define _UTILS_INPUT_H_ - - -/***************************************************** - * MACROS * - *****************************************************/ - -/* Clear a line from stdin, to use after a failed scanf (it didn't actually read input) */ -#define sflc_ignoreLine() scanf("%*[^\n]") - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflc_safeReadLine(char *buf, size_t bufsize); - -/* Reads a password or passphrase (discarding the newline) from stdin in a secure way (no echo) */ -int sflc_safeReadPassphrase(char *buf, size_t bufsize); - -#endif /* _UTILS_FILE_H_ */ diff --git a/shufflecake-userland-legacy/include/utils/log.h b/shufflecake-userland-legacy/include/utils/log.h deleted file mode 100644 index 2088fbb..0000000 --- a/shufflecake-userland-legacy/include/utils/log.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_LOG_H_ -#define _UTILS_LOG_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -// Printf colours (regular text) -#define SFLC_LOG_BLK "\033[0;30m" -#define SFLC_LOG_RED "\033[0;31m" -#define SFLC_LOG_GRN "\033[0;32m" -#define SFLC_LOG_YEL "\033[0;33m" -#define SFLC_LOG_BLU "\033[0;34m" -#define SFLC_LOG_MAG "\033[0;35m" -#define SFLC_LOG_CYN "\033[0;36m" -#define SFLC_LOG_WHT "\033[0;37m" -// Printf colours (bold text) -#define SFLC_LOG_BBLK "\033[1;30m" -#define SFLC_LOG_BRED "\033[1;31m" -#define SFLC_LOG_BGRN "\033[1;32m" -#define SFLC_LOG_BYEL "\033[1;33m" -#define SFLC_LOG_BBLU "\033[1;34m" -#define SFLC_LOG_BMAG "\033[1;35m" -#define SFLC_LOG_BCYN "\033[1;36m" -#define SFLC_LOG_BWHT "\033[1;37m" -// Reset colour -#define SFLC_LOG_RESET "\033[0m" - -// Log level: debug implies detailed logs -#ifdef CONFIG_SFLC_LOG_DEBUG -#define CONFIG_SFLC_LOG_DETAILED -#endif - - -/***************************************************** - * MACROS * - *****************************************************/ - -// Gives the point in the code where it was called -#define sflc_log_detailed(col, ...) do{ \ - printf(SFLC_LOG_GRN "FUNC " SFLC_LOG_RESET "%s() " \ - SFLC_LOG_GRN "FILE " SFLC_LOG_RESET "%s " \ - SFLC_LOG_GRN "LINE " SFLC_LOG_RESET "%d | ", \ - __func__, __FILE__, __LINE__); \ - sflc_log_concise(col, __VA_ARGS__); \ -}while(0) - -// Only writes using the given colour -#define sflc_log_concise(col, ...) do{ \ - printf(col); \ - printf(__VA_ARGS__); \ - printf(SFLC_LOG_RESET "\n"); \ -}while(0) - -// Maps to one or the other, based on a Makefile switch -#ifdef CONFIG_SFLC_LOG_DETAILED - #define sflc_log_colour(...) sflc_log_detailed(__VA_ARGS__) -#else - #define sflc_log_colour(...) sflc_log_concise(__VA_ARGS__) -#endif - -// Using specific colours -#define sflc_log_green(...) sflc_log_colour(SFLC_LOG_GRN, __VA_ARGS__) -#define sflc_log_red(...) sflc_log_colour(SFLC_LOG_RED, __VA_ARGS__) -#define sflc_log_yellow(...) sflc_log_colour(SFLC_LOG_YEL, __VA_ARGS__) -#define sflc_log_blue(...) sflc_log_colour(SFLC_LOG_BLU, __VA_ARGS__) -#define sflc_log_normal(...) sflc_log_colour(SFLC_LOG_RESET, __VA_ARGS__) - -// With log levels -#define sflc_log_error(...) sflc_log_colour(SFLC_LOG_RED, "[ERROR] " __VA_ARGS__) -#define sflc_log_warn(...) sflc_log_colour(SFLC_LOG_MAG, "[WARN] " __VA_ARGS__) -#ifdef CONFIG_SFLC_LOG_DEBUG - #define sflc_log_debug(...) sflc_log_colour(SFLC_LOG_CYN, "[DEBUG] " __VA_ARGS__) -#else - #define sflc_log_debug(...) -#endif - - -/***************************************************** - * INLINE FUNCTIONS * - *****************************************************/ - -// Log a hex string -static inline void sflc_log_hex(char *str, size_t len) -{ - int i; - unsigned char *s = (unsigned char *) str; - - for (i = 0; i < len; i++) { - printf("%02x ", s[i]); - // Nice aligned wrapping - if (i % 16 == 15) { - printf("\n"); - } - } - - // Always end with a newline - if (i % 16 != 0) { - printf("\n"); - } - - return; -} - -#endif /* _UTILS_LOG_H_ */ diff --git a/shufflecake-userland-legacy/include/utils/sflc.h b/shufflecake-userland-legacy/include/utils/sflc.h deleted file mode 100644 index 4bab1ba..0000000 --- a/shufflecake-userland-legacy/include/utils/sflc.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Miscellaneous constants that must match with the definitions in the Kernel module TODO: MOVE TO sflc_constans.h - */ - -#ifndef _UTILS_SFLC_H_ -#define _UTILS_SFLC_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/* Name of the DM target in the kernel */ -#define SFLC_DM_TARGET_NAME "shufflecake" - -/* Disk constants */ -#define SFLC_SECTOR_SIZE 4096 /* bytes */ -#define KERNEL_SECTOR_SIZE 512 /* bytes */ -#define SFLC_SECTOR_SCALE (SFLC_SECTOR_SIZE / KERNEL_SECTOR_SIZE) - -/* Max number of volumes in a device */ -#define SFLC_DEV_MAX_VOLUMES 15 - -/* Max total number of open devices at any given time */ -#define SFLC_TOT_MAX_DEVICES 1024 -/* A volume name is sflc__ */ -#define SFLC_MAX_VOL_NAME_LEN 15 - -/* A slice index is represented over 32 bits */ -#define SFLC_SLICE_IDX_WIDTH 4 /* bytes */ -/* A position map block contains 1024 slice indices */ -#define SFLC_SLICE_IDX_PER_BLOCK (SFLC_SECTOR_SIZE / SFLC_SLICE_IDX_WIDTH) - -// IV length for AES-CTR -#define SFLC_AESCTR_IVLEN 16 /* bytes */ - -/* An IV block can encrypt 256 data blocks */ -#define SFLC_DATA_BLOCKS_PER_IV_BLOCK (SFLC_SECTOR_SIZE / SFLC_AESCTR_IVLEN) -/* A logical slice spans 256 blocks of data (1 MiB) */ -#define SFLC_BLOCKS_PER_LOG_SLICE SFLC_DATA_BLOCKS_PER_IV_BLOCK -/* A physical slice also includes the IV block */ -#define SFLC_BLOCKS_PER_PHYS_SLICE (1 + SFLC_BLOCKS_PER_LOG_SLICE) - -/* A PSI of 0xFFFFFFFF indicates an unassigned LSI */ -#define SFLC_EPM_FILLER 0xFF - -/* The sysfs file containing the next available device ID */ -#define SFLC_SYSFS_NEXTDEVID "/sys/module/dm_sflc/next_dev_id" -/* The sysfs directory containing a subdir for each (underlying) block device */ -#define SFLC_SYSFS_BDEVS_DIR "/sys/module/dm_sflc/bdevs" -/* Within each bdev's subdir, this file lists its open volumes */ -#define SFLC_SYSFS_OPENVOLUMES_FILENAME "volumes" -/* Within each bdev's subdir, this file shows its Shufflecake device ID */ -#define SFLC_SYSFS_DEVID_FILENAME "dev_id" - -/* TODO: reasonable? */ -#define SFLC_BDEV_PATH_MAX_LEN 1024 - -/* For when you can't be bothered to upper-bound a buffer size */ -#define SFLC_BIGBUFSIZE 4096 - - -#endif /* _UTILS_SFLC_H_ */ diff --git a/shufflecake-userland-legacy/src/cli/close.c b/shufflecake-userland-legacy/src/cli/close.c deleted file mode 100644 index fdbaa28..0000000 --- a/shufflecake-userland-legacy/src/cli/close.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "cli.h" -#include "commands.h" -#include "utils/sflc.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * Close volumes - * - * @return Error code, 0 on success - */ -int sflc_cli_close(char *block_device) -{ // Requires: block_device is a correct block device path -// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; -// int err; - -// /* Gather (absolute) path to underlying block device */ -// printf("Enter the absolute path to the underlying block device containing the Shufflecake volumes to close: "); -// err = sflc_safeReadLine(bdev_path, SFLC_BDEV_PATH_MAX_LEN + 2); -// if (err) { -// sflc_log_error("Could not read path to underlying block device; error %d", err); -// return err; -// } -// /* Check that it is absolute */ -// if (bdev_path[0] != '/') { -// printf("The path to the block device must be absolute"); -// return EINVAL; -// } -// - - - /* Actually perform the command */ -// return sflc_cmd_closeVolumes(bdev_path); - - - return sflc_cmd_closeVolumes(block_device); - -} diff --git a/shufflecake-userland-legacy/src/cli/dispatch.c b/shufflecake-userland-legacy/src/cli/dispatch.c deleted file mode 100644 index 01da250..0000000 --- a/shufflecake-userland-legacy/src/cli/dispatch.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "cli.h" -#include "utils/sflc.h" -#include "utils/disk.h" -#include "utils/log.h" -#include "sflc_constants.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/* Used by argp to provide the automatic "-V" option */ -const char *argp_program_version = SFLC_VERSION; -/* Used by argp to provide the automatic "--help" option */ -const char *argp_program_bug_address = ""; - -/* Signed integer values representing a handle for each option */ -#define SFLC_OPT_NUMVOLS_KEY 'n' // Positive and printable: also serves as short command-line option -#define SFLC_OPT_SKIPRAND_KEY (-'r') // Negative, because we don't want a short option available for this - - -/***************************************************** - * TYPES * - *****************************************************/ - -enum sflc_cli_action { - SFLC_ACT_INIT, - SFLC_ACT_OPEN, - SFLC_ACT_CLOSE, - SFLC_ACT_TESTPWD, - SFLC_ACT_CHANGEPWD -}; - -struct sflc_cli_arguments { - enum sflc_cli_action act; - char *block_device; - int num_volumes; - bool skip_randfill; -}; - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static error_t _parseArgpKey(int key, char *arg, struct argp_state *state); - - -/***************************************************** - * PRIVATE VARIABLES * - *****************************************************/ - -/* Doc strings */ -static char args_doc[] = "ACTION "; -static char doc[] = - "Shufflecake is a plausible deniability (hidden storage) layer for Linux.\n" - "See official website at for more info.\n" - "Possible values for mandatory ACTION are:\n\n" - - "\tinit:\t\tInitialise a block device for Shufflecake use, formatting\n" - "\t\t\theaders with provided passwords and overwriting with random\n" - "\t\t\tdata. WARNING: THIS WILL ERASE THE CONTENT OF THE DEVICE.\n\n" - - "\topen:\t\tOpen a hierarchy of Shufflecake volumes within a device by\n" - "\t\t\tasking a single user password. Virtual devices will appear\n" - "\t\t\tin /dev/mapper. Notice: freshly created device must be\n" - "\t\t\tuser-formatted and mounted in order to be used.\n\n" - - "\tclose:\t\tClose all open Shufflecake volumes supported by given\n" - "\t\t\tdevice.\n\n" - - "\ttestpwd:\tTest whether a given password unlocks any volume within\n" - "\t\t\tthe block device and, if so, show its index.\n\n" - - "\tchangepwd:\tChange the password unlocking a certain volume.\n\n" - - "Possible options are:"; - -/* Description of each option */ -static struct argp_option options[] = { - {"num-volumes", SFLC_OPT_NUMVOLS_KEY, "num", 0, - "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 - {"skip-randfill", SFLC_OPT_SKIPRAND_KEY, 0, 0, - "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, - {0} -}; - -/* Wrapper containing everything */ -static struct argp argp = {options, _parseArgpKey, args_doc, doc}; - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * The following is the main dispatch function called by the main to parse - * the arguments and dispatch to the right command. - * - * @param argc The number of command-line arguments supplied to the main - * @param argv The arguments - * - * @return Error code, 0 on success - */ - -int sflc_cli_dispatch(int argc, char **argv) { - struct sflc_cli_arguments arguments; - - arguments.act = -1; - arguments.block_device = NULL; - arguments.num_volumes = 0; - arguments.skip_randfill = false; - - /* Parse */ - argp_parse(&argp, argc, argv, 0, 0, &arguments); - - /* Check options consistency */ - if (arguments.num_volumes && arguments.act != SFLC_ACT_INIT) { - printf("Error: --num-volumes (-n) can only be combined with `init'.\n"); - return EINVAL; - } - /* Check options consistency */ - if (arguments.skip_randfill && arguments.act != SFLC_ACT_INIT) { - printf("Error: --skip-randfill can only be combined with `init'.\n"); - return EINVAL; - } - /* Check that input is actually a block device */ - if (strncmp(arguments.block_device, "/dev/", 5) != 0 || !sflc_disk_isBlockDevice(arguments.block_device)) { - printf("Error: '%s' is not a valid block device.\n", arguments.block_device); - return EINVAL; - } - - /* Dispatch to specific command */ - if (arguments.act == SFLC_ACT_INIT) { - return sflc_cli_init(arguments.block_device, arguments.num_volumes, arguments.skip_randfill); - } - if (arguments.act == SFLC_ACT_OPEN) { - return sflc_cli_open(arguments.block_device); - } - if (arguments.act == SFLC_ACT_CLOSE) { - return sflc_cli_close(arguments.block_device); - } - if (arguments.act == SFLC_ACT_TESTPWD) { - return sflc_cli_testPwd(arguments.block_device); - } - if (arguments.act == SFLC_ACT_CHANGEPWD) { - return sflc_cli_changePwd(arguments.block_device); - } - - printf("\n"); - - return EINVAL; -} - - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -static error_t _parseArgpKey(int key, char *arg, struct argp_state *state) { - struct sflc_cli_arguments *arguments = state->input; - - switch (key) { - /* We are parsing an argument (not an option) */ - case ARGP_KEY_ARG: - /* We are parsing the command */ - if (state->arg_num == 0) { - if (strcmp(arg, SFLC_CLI_INITACT) == 0) { - arguments->act = SFLC_ACT_INIT; - } else if (strcmp(arg, SFLC_CLI_OPENACT) == 0) { - arguments->act = SFLC_ACT_OPEN; - } else if (strcmp(arg, SFLC_CLI_CLOSEACT) == 0) { - arguments->act = SFLC_ACT_CLOSE; - } else if (strcmp(arg, SFLC_CLI_TESTPWDACT) == 0) { - arguments->act = SFLC_ACT_TESTPWD; - } else if (strcmp(arg, SFLC_CLI_CHANGEPWDACT) == 0) { - arguments->act = SFLC_ACT_CHANGEPWD; - } else { - argp_error(state, "Invalid action. Please enter one and only one of: `%s', `%s', `%s', '%s', or '%s'.", - SFLC_CLI_INITACT, SFLC_CLI_OPENACT, SFLC_CLI_CLOSEACT, SFLC_CLI_TESTPWDACT, SFLC_CLI_CHANGEPWDACT); - } - /* We are parsing the block device */ - } else if (state->arg_num == 1) { - arguments->block_device = arg; - /* Too many arguments */ - } else { - argp_usage(state); - } - break; - - /* We are parsing an option */ - case SFLC_OPT_NUMVOLS_KEY: - arguments->num_volumes = atoi(arg); - break; - case SFLC_OPT_SKIPRAND_KEY: - arguments->skip_randfill = true; - break; - - /* End of arg list */ - case ARGP_KEY_END: - if (state->arg_num < 2) { - argp_usage(state); - } - break; - - /* Unrecognised key */ - default: - return ARGP_ERR_UNKNOWN; - } - - return 0; -} diff --git a/shufflecake-userland-legacy/src/cli/init.c b/shufflecake-userland-legacy/src/cli/init.c deleted file mode 100644 index 302a96d..0000000 --- a/shufflecake-userland-legacy/src/cli/init.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "cli.h" -#include "commands.h" -#include "utils/sflc.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * Create volumes - * - * @return Error code, 0 on success - */ -int sflc_cli_init(char *block_device, int num_volumes, int skip_randfill) -{ // Requires: block_device is a correct block device path - sflc_cmd_InitArgs args; - char str_nrvols[SFLC_BIGBUFSIZE]; - char *pwds[SFLC_DEV_MAX_VOLUMES]; - size_t pwd_lens[SFLC_DEV_MAX_VOLUMES]; - int err; - - args.bdev_path = block_device; - - // Check if number of volumes was nonzero passed by command line already - if (num_volumes) { - args.nr_vols = num_volumes; - } else { - // If not, ask user for number of volumes - printf("\nHow many volumes do you want to create (maximum is %d)? ", SFLC_DEV_MAX_VOLUMES); - err = sflc_safeReadLine(str_nrvols, SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Error: could not read number of volumes; error %d", err); - return err; - } - /* Parse string */ - if (sscanf(str_nrvols, "%lu\n", &args.nr_vols) != 1) { - sflc_log_error("Error: could not parse number of volumes"); - return EINVAL; - } - } - - /* Bounds check */ - if (args.nr_vols <= 0) { - printf("Error: number of volumes must be a positive integer"); - return EINVAL; - } - if (args.nr_vols > SFLC_DEV_MAX_VOLUMES) { - printf("Number of volumes too high, Shufflecake supports up to %d volumes on a single device", SFLC_DEV_MAX_VOLUMES); - return EINVAL; - } - - /* Collects the passwords */ - printf("\nNow you will be asked to insert the passwords for all the volumes you want to create, \nfrom " - "volume 0 (the least secret) to volume %lu (the most secret).\n\n", args.nr_vols - 1); - size_t i; - for (i = 0; i < args.nr_vols; i++) { - // Allocate pwd - pwds[i] = malloc(SFLC_BIGBUFSIZE); - - /* Read it */ - printf("Choose password for volume %lu (must not be empty): ", i); - err = sflc_safeReadLine(pwds[i], SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Could not read password for volume %lu; error %d", i, err); - return err; - } - - /* You can trust the length of strings input this way */ - pwd_lens[i] = strlen(pwds[i]); - /* Check non-empty */ - if (pwd_lens[i] == 0) { - sflc_log_error("Password cannot be empty!"); - return EINVAL; - } - } - /* Assign them */ - args.pwds = pwds; - args.pwd_lens = pwd_lens; - - args.no_randfill = skip_randfill; - - /* Actually perform the command */ - return sflc_cmd_initVolumes(&args); -} diff --git a/shufflecake-userland-legacy/src/cli/open.c b/shufflecake-userland-legacy/src/cli/open.c deleted file mode 100644 index 45464c9..0000000 --- a/shufflecake-userland-legacy/src/cli/open.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "cli.h" -#include "commands.h" -#include "utils/sflc.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * Open volumes - * - * @return Error code, 0 on success - */ -int sflc_cli_open(char *block_device) -{ // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs args; - char pwd[SFLC_BIGBUFSIZE]; - size_t pwd_len; - int err; - - args.bdev_path = block_device; - - /* Gather password */ - printf("Enter the password for the most secret volume you want to open: "); - err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Could not read password; error %d", err); - return err; - } - /* You can trust the length of strings input this way */ - pwd_len = strlen(pwd); - /* Check non-empty */ - if (pwd_len == 0) { - sflc_log_error("Password cannot be empty!"); - return EINVAL; - } - /* Assign them */ - args.pwd = pwd; - args.pwd_len = pwd_len; - - /* Actually perform the command */ - return sflc_cmd_openVolumes(&args); -} diff --git a/shufflecake-userland-legacy/src/commands/change_pwd.c b/shufflecake-userland-legacy/src/commands/change_pwd.c deleted file mode 100644 index 2ef64e0..0000000 --- a/shufflecake-userland-legacy/src/commands/change_pwd.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "commands.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Changes the specified volume's password. - * - * @param args->bdev_path The underlying block device - * @param args->dmb_cell The DMB cell to re-encrypt - * @param args->new_pwd The new password - * @param args->new_pwd_len Its length - * - * @return Error code, 0 on success - */ -int sflc_cmd_changePwd(sflc_cmd_ChangePwdArgs *args) -{ - /* Delegate entirely to the function reading the DMB */ - return sflc_ops_rewriteDmbCell(args->bdev_path, args->dmb_cell, args->new_pwd, args->new_pwd_len); -} diff --git a/shufflecake-userland-legacy/src/commands/close.c b/shufflecake-userland-legacy/src/commands/close.c deleted file mode 100644 index 73a7605..0000000 --- a/shufflecake-userland-legacy/src/commands/close.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "commands.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/string.h" -#include "utils/file.h" -#include "utils/disk.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Reads the list of volumes from sysfs */ -static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols); - -/* Close them all (in reverse order of opening) */ -static int _closeVolumes(char **labels, size_t nr_vols); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Close all volumes on the device (reads the list from sysfs) - * - * @param bdev_path The path to the underlying block device - * - * @return Error code, 0 on success - */ -int sflc_cmd_closeVolumes(char *bdev_path) -{ - char *labels[SFLC_DEV_MAX_VOLUMES]; - size_t nr_vols; - int err; - - /* Allocate labels */ - size_t i; - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - labels[i] = malloc(SFLC_MAX_VOL_NAME_LEN + 1); - if (!labels[1]) { - sflc_log_error("Could not allocate volume label %lu", i); - return ENOMEM; // Do not free the ones already allocated - } - } - - /* Read them */ - err = _buildVolumesList(bdev_path, labels, &nr_vols); - if (err) { - sflc_log_error("Could not read volume list from sysfs; error %d", err); - goto out; - } - - /* Close the volumes (in reverse order of opening) */ - err = _closeVolumes(labels, nr_vols); - if (err) { - sflc_log_error("Could not close volumes; error %d", err); - goto out; - } - - /* No prob */ - err = 0; - - -out: - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - free(labels[i]); - } - return err; -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Reads from sysfs to build the volumes list */ -static int _buildVolumesList(char *bdev_path, char **labels, size_t *nr_vols) -{ - char *bdev_name; - char devid_path[SFLC_BIGBUFSIZE]; - char *str_devid; - size_t dev_id; - char nrvolumes_path[SFLC_BIGBUFSIZE]; - char *str_nrvolumes; - - /* Get device name as : */ - bdev_name = sflc_disk_getDeviceName(bdev_path); - if(!bdev_name) { - sflc_log_error("Could not allocate device name"); - return ENOMEM; - } - /* Build path to sysfs file containing device ID */ - sprintf(devid_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_DEVID_FILENAME); - /* Build path to sysfs file containing number of open volumes */ - sprintf(nrvolumes_path, "%s/%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_name, SFLC_SYSFS_OPENVOLUMES_FILENAME); - - /* Read the device ID */ - str_devid = sflc_readFile(devid_path); - if (!str_devid) { - sflc_log_error("Could not read file %s", devid_path); - return EBADF; - } - /* Parse the device ID */ - if (sscanf(str_devid, "%lu", &dev_id) != 1) { - sflc_log_error("Could not parse device ID:\n%s", str_devid); - return EBADF; - } - - /* Read the number of volumes */ - str_nrvolumes = sflc_readFile(nrvolumes_path); - if (!str_nrvolumes) { - sflc_log_error("Could not read file %s", nrvolumes_path); - return EBADF; - } - /* Parse the number of volumes */ - if (sscanf(str_nrvolumes, "%lu", nr_vols) != 1) { - sflc_log_error("Could not parse number of volumes:\n%s", str_nrvolumes); - return EBADF; - } - - /* Just to be sure */ - if (*nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Something is seriously wrong, sysfs file says there are %lu open volumes, that's too many!", *nr_vols); - return EBADF; - } - - /* Build labels */ - size_t i; - for (i = 0; i < *nr_vols; i++) { - sprintf(labels[i], "sflc_%lu_%lu", dev_id, i); - } - - return 0; -} - - -/* Close them all (in reverse order of opening) */ -static int _closeVolumes(char **labels, size_t nr_vols) -{ - int err; - - /* Eazy peazy */ - int i; - for (i = nr_vols-1; i >= 0; i--) { - err = sflc_ops_closeVolume(labels[i]); - if (err) { - sflc_log_error("Could not close volume %s; error %d", labels[i], err); - return err; - } - sflc_log_debug("Closed volume %s", labels[i]); - printf("Closed volume /dev/mapper/%s\n", labels[i]); - } - - return 0; -} - diff --git a/shufflecake-userland-legacy/src/commands/init.c b/shufflecake-userland-legacy/src/commands/init.c deleted file mode 100644 index cd21b03..0000000 --- a/shufflecake-userland-legacy/src/commands/init.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "commands.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/disk.h" -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -/* The device is randomised in chunks of 1024 blocks (arbitrary number) */ -#define SFLC_BLOCKS_IN_RAND_CHUNK 1024 -/* That's 4 MiB */ -#define SFLC_RAND_CHUNK_SIZE (SFLC_BLOCKS_IN_RAND_CHUNK * SFLC_SECTOR_SIZE) - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Fill the device with random data */ -static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Create N volumes (only formats the device header, does not open the volumes). - * Creates them in order from 0 to N-1, so as to induce a back-linked list on the device. - * - * @param args->bdev_path The path to the underlying block device - * @param args->nr_vols The number of volumes to create - * @param args->pwds The array of passwords for the various volumes - * @param args->pwd_lens The length of each password - * @param args->no_randfill A boolean switch indicating that the volume should not - * be filled entirely with random data prior to formatting. - * - * @return Error code, 0 on success - */ -int sflc_cmd_initVolumes(sflc_cmd_InitArgs *args) -{ - sflc_Dmb dmb; - sflc_Vmb vmb; - int64_t dev_size; - size_t nr_slices; - int err; - - /* Sanity check */ - if (args->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create %lu volumes on a single device", args->nr_vols); - return EINVAL; - } - - /* Get device size */ - dev_size = sflc_disk_getSize(args->bdev_path); - if (dev_size < 0) { - err = -dev_size; - sflc_log_error("Could not get device size for %s; error %d", args->bdev_path, err); - return err; - } - /* Convert to number of slices */ - nr_slices = sflc_disk_maxSlices(dev_size); - sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); - - /* Fill disk with random bytes, if requested */ - if (!args->no_randfill) { - err = _fillDiskWithRandom(args->bdev_path, (uint64_t) dev_size); - if (err) { - sflc_log_error("Could not fill device %s with random bytes; error %d", args->bdev_path, err); - return err; - } - } - - /* Fill the DMB */ - dmb.nr_vols = args->nr_vols; - /* Sample the VMB keys */ - size_t i; - for (i = 0; i < dmb.nr_vols; i++) { - err = sflc_rand_getStrongBytes(dmb.vmb_keys[i], SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not sample VMB key number %lu; error %d", i , err); - return err; - } - } - /* And write (encrypted) to disk */ - err = sflc_ops_writeDmb(args->bdev_path, args->pwds, args->pwd_lens, &dmb); - if (err) { - sflc_log_error("Could not create DMB and write it to disk; error %d", err); - return err; - } - - /* Write the volume headers */ - vmb.nr_slices = nr_slices; - for (i = 0; i < args->nr_vols; i++) { - /* This volume's prev_vmb_key */ - if (i > 0) { - memcpy(vmb.prev_vmb_key, dmb.vmb_keys[i-1], SFLC_CRYPTO_KEYLEN); - } - /* Sample this volume's VEK */ - sflc_rand_getStrongBytes(vmb.volume_key, SFLC_CRYPTO_KEYLEN); - - /* Write complete volume header (VMB + PM) */ - err = sflc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); - if (err) { - sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); - return err; - } - } - printf("Created %lu volumes on device %s\n", args->nr_vols, args->bdev_path); - - return 0; -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Fill the device with random data */ -static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size) -{ - char *rand_chunk; - int err; - - /* Allocate chunk */ - rand_chunk = malloc(SFLC_RAND_CHUNK_SIZE); - if (!rand_chunk) { - sflc_log_error("Could not allocate %d bytes for chunk of random data", SFLC_RAND_CHUNK_SIZE); - return ENOMEM; - } - - /* Loop to write random data in chunks */ - uint64_t blocks_remaining = dev_size; - uint64_t sector = 0; - while (blocks_remaining > 0) { - uint64_t blocks_to_write = - (blocks_remaining > SFLC_BLOCKS_IN_RAND_CHUNK) ? SFLC_BLOCKS_IN_RAND_CHUNK : blocks_remaining; - uint64_t bytes_to_write = blocks_to_write * SFLC_SECTOR_SIZE; - - /* Sample random bytes */ - err = sflc_rand_getWeakBytes(rand_chunk, bytes_to_write); - if (err) { - sflc_log_error("Could not sample %lu random bytes to write on disk; error %d", bytes_to_write, err); - goto out; - } - - /* Write on disk */ - err = sflc_disk_writeManySectors(bdev_path, sector, rand_chunk, blocks_to_write); - if (err) { - sflc_log_error("Could not write random bytes on disk; error %d", err); - goto out; - } - - /* Advance loop */ - sector += blocks_to_write; - blocks_remaining -= blocks_to_write; - } - - /* No prob */ - err = 0; - - -out: - free(rand_chunk); - return err; -} - diff --git a/shufflecake-userland-legacy/src/commands/open.c b/shufflecake-userland-legacy/src/commands/open.c deleted file mode 100644 index da0c4f0..0000000 --- a/shufflecake-userland-legacy/src/commands/open.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include - -#include "commands.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/disk.h" -#include "utils/file.h" -#include "utils/log.h" -#include "utils/string.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Read the next device ID in sysfs */ -static int _getNextDevId(size_t *next_dev_id); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Open M volumes, from the first one down to the one whose pwd is provided. - * Scans the DMB cells to find which one is unlocked by the provided pwd; then, - * using the decrypted VMB key, unlocks the M-th VMB; then, iteratively using - * the prev_vmb_key field, unlocks all the previous VMBs; then, using the - * decrypted VMB keys, opens the volumes "in order" from 1 to M. - * - * @param args->bdev_path The underlying block device - * @param args->pwd The password - * @param args->pwd_len The password length - * - * @return Error code (also if no volume could be opened), 0 on success - */ -int sflc_cmd_openVolumes(sflc_cmd_OpenArgs *args) -{ - int64_t dev_size; - size_t nr_slices; - sflc_DmbCell dmb_cell; - sflc_Vmb vmbs[SFLC_DEV_MAX_VOLUMES]; - size_t dev_id; - int err; - char bdev_path_noslash[SFLC_BDEV_PATH_MAX_LEN + 1]; - char opendev_path[SFLC_BIGBUFSIZE]; - DIR* opendev_dir; - - /* Check if device is already opened and abort if so. */ - /* Step 1: rebuild sysfs directory name of device to be checked. */ - /* TODO: this is duplicate code from close.c we might want to modularize it. */ - /* Remove the slashes from the bdev_path (replace with underscores). */ - strcpy(bdev_path_noslash, args->bdev_path); - sflc_str_replaceAll(bdev_path_noslash, '/', '_'); - /* Build sysfs path of opened device */ - sprintf(opendev_path, "%s/%s", SFLC_SYSFS_BDEVS_DIR, bdev_path_noslash); - /* Step 2: check if directory exists. */ - opendev_dir = opendir(opendev_path); - if (opendev_dir) { - /* Directory exists. */ - closedir(opendev_dir); - err = EEXIST; - sflc_log_error("Device %s seems to be already open; error %d", args->bdev_path, err); - return err; - } - - /* Get number of slices */ - dev_size = sflc_disk_getSize(args->bdev_path); - if (dev_size < 0) { - err = -dev_size; - sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); - return err; - } - nr_slices = sflc_disk_maxSlices(dev_size); - - /* Find volume opened by the pwd */ - err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); - if (err) { - sflc_log_error("Could not read DMB; error %d", err); - return err; - } - /* Was there one? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("The provided password opens no volume on the device"); - return EINVAL; - } - printf("Password is correct! Opening volumes...\n"); - - /* Unlock VMBs "backwards" */ - int i; // Needs sign, because loop ends on i>=0 - for (i = dmb_cell.vol_idx; i >= 0; i--) { - /* Which VMB key to use? */ - char *vmb_key; - if (i == dmb_cell.vol_idx) { - // The one unlocked by pwd - vmb_key = dmb_cell.vmb_key; - } else { - // Or the prev_vmb_key from last iteration - vmb_key = vmbs[i+1].prev_vmb_key; - } - - /* Read and unlock VMB */ - err = sflc_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); - if (err) { - sflc_log_error("Could not read VMB %d on device %s; error %d", - i, args->bdev_path, err); - return err; - } - } - - /* Get the ID that will be assigned to the block device */ - err = _getNextDevId(&dev_id); - if (err) { - sflc_log_error("Could not get next device ID; error %d", err); - return err; - } - sflc_log_debug("Next device ID is %lu", dev_id); - - /* Open volumes "in order" */ - for (i = 0; i <= dmb_cell.vol_idx; i++) { - err = sflc_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); - if (err) { - sflc_log_error("Could not open volume %d; error %d. " - "Previous volumes on the device might have already " - "been opened, it's recommended you close them", - i, err); - return err; - } - sflc_log_debug("Successfully opened volume %d with VMB key", i); - printf("Opened volume /dev/mapper/sflc_%lu_%d\n", dev_id, i); - } - - return 0; -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Read the next device ID in sysfs */ -static int _getNextDevId(size_t *next_dev_id) -{ - char *str_nextdevid; - int err; - - /* Read sysfs entry */ - str_nextdevid = sflc_readFile(SFLC_SYSFS_NEXTDEVID); - if (!str_nextdevid) { - sflc_log_error("Could not read sysfs entry %s", SFLC_SYSFS_NEXTDEVID); - return EINVAL; - } - - /* Parse integer */ - if (sscanf(str_nextdevid, "%lu", next_dev_id) != 1) { - sflc_log_error("Error parsing content of file %s", SFLC_SYSFS_NEXTDEVID); - err = EINVAL; - goto err_devid; - } - /* Sanity check */ - if (*next_dev_id >= SFLC_TOT_MAX_DEVICES) { - sflc_log_error("There are already %d open devices, this is the maximum allowed", SFLC_TOT_MAX_DEVICES); - err = E2BIG; - goto err_devid; - } - - /* All good */ - err = 0; - - -err_devid: - free(str_nextdevid); - return err; -} - diff --git a/shufflecake-userland-legacy/src/header/device_master_block.c b/shufflecake-userland-legacy/src/header/device_master_block.c deleted file mode 100644 index cdd1f8c..0000000 --- a/shufflecake-userland-legacy/src/header/device_master_block.c +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "header.h" -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* AES-GCM-encrypt a VMB key with the KDF-generated key */ -static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vmb_key, char *dmb_cell); - -/* AES-GCM-decrypt a VMB key with the KDF-generated key */ -static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool *match); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Builds the on-disk device master block (indistinguishable from random). - * - * @param dmb The list of VMB keys - * @param pwds The passwords of the volumes - * @param pwd_lens Their lengths - * @param disk_block The 4096-byte buffer that will contain the random-looking - * bytes to be written on-disk - * - * @return The error code, 0 on success - */ -int sflc_dmb_seal(sflc_Dmb *dmb, char **pwds, size_t *pwd_lens, char *disk_block) -{ - char *salt; - int err; - - /* Sanity check */ - if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create DMB with %lu volumes, too many!", dmb->nr_vols); - return EINVAL; - } - - /* Randomise whole block */ - err = sflc_rand_getWeakBytes(disk_block, SFLC_SECTOR_SIZE); - if (err) { - sflc_log_error("Could not randomise DMB; error %d", err); - return err; - } - - /* Assign salt */ - salt = disk_block; - - /* Loop over all VMB keys to encrypt them */ - size_t i; - for (i = 0; i < dmb->nr_vols; i++) { - char *dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); - - /* Encrypt it */ - err = _encryptVmbKeyWithPwd(salt, pwds[i], pwd_lens[i], dmb->vmb_keys[i], dmb_cell); - if (err) { - sflc_log_error("Could not encrypt VMB key number %lu; error %d", i, err); - return err; - } - } - - return 0; -} - - -/** - * "Decrypt" a single VMB key from the on-disk DMB, using its password (perform the KDF). - * - * @param disk_block The on-disk sealed DMB - * @param pwd The password locking the VMB key - * @param pwd_len Its length - * - * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) - * - * @return Error code, 0 on success - */ -int sflc_dmb_unseal(char *disk_block, char *pwd, size_t pwd_len, sflc_DmbCell *dmb_cell) -{ - // KDF salt - char *salt; - // The KDF-derived key - char kek[SFLC_CRYPTO_KEYLEN]; - // The unlocked VMB key - char vmb_key[SFLC_CRYPTO_KEYLEN]; - // Error code - int err; - - /* Derive KEK once and for all */ - salt = disk_block; - err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); - if (err) { - sflc_log_error("Could not perform KDF: error %d", err); - goto bad_kdf; - } - sflc_log_debug("Successfully derived key-encryption-key with KDF"); - - /* Init dmb->vol_idx to invalid */ - dmb_cell->vol_idx = SFLC_DEV_MAX_VOLUMES; - /* Try all DMB cells */ - size_t i; - for (i = 0; i < SFLC_DEV_MAX_VOLUMES; i++) { - char *enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (i * SFLC_DMB_CELL_SIZE); - bool match; - - /* Try to decrypt this one */ - err = _decryptVmbKeyWithPwd(enc_dmb_cell, kek, vmb_key, &match); - if (err) { - sflc_log_error("Error decrypting DMB cell number %lu; error %d", i, err); - goto bad_decrypt; - } - - /* If MAC matched, mark it, but don't break from the loop (timing attacks) */ - if (match) { - sflc_log_debug("The provided password unlocks volume %lu", i); - dmb_cell->vol_idx = i; - memcpy(dmb_cell->vmb_key, vmb_key, SFLC_CRYPTO_KEYLEN); - } - } - - // No prob - err = 0; - - -bad_decrypt: -bad_kdf: - /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_CRYPTO_KEYLEN); - return err; -} - -/** - * Re-encrypt the content of the specified DMB cell. - * - * @param disk_block The on-disk sealed DMB - * @param dmb_cell The DMB cell to re-encrypt - * @param pwd The new password - * @param pwd_len The password's length - * - * @return Error code, 0 on success - */ -int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t pwd_len) -{ - char *salt; - char *enc_dmb_cell; - int err; - - /* Sanity check */ - if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot set DMB cell %lu: out of bounds!", dmb_cell->vol_idx); - return EINVAL; - } - - /* Pointers inside DMB */ - salt = disk_block; - enc_dmb_cell = (salt + SFLC_ARGON_SALTLEN) + (dmb_cell->vol_idx * SFLC_DMB_CELL_SIZE); - /* Encrypt with KDF-derived key */ - err = _encryptVmbKeyWithPwd(salt, pwd, pwd_len, dmb_cell->vmb_key, enc_dmb_cell); - if (err) { - sflc_log_error("Could not re-encrypt VMB key number %lu; error %d", dmb_cell->vol_idx, err); - return err; - } - sflc_log_debug("Successfully re-encrypted VMB key number %lu", dmb_cell->vol_idx); - - return 0; -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* AES-GCM-encrypt a VMB key with the KDF-generated key */ -static int _encryptVmbKeyWithPwd(char *salt, char *pwd, size_t pwd_len, char *vmb_key, char *dmb_cell) -{ - // Pointers inside the block - char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; - // Key-encryption-key derived from KDF - char kek[SFLC_CRYPTO_KEYLEN]; - // Error code - int err; - - /* Derive KEK */ - err = sflc_argon2id_derive(pwd, pwd_len, salt, kek); - if (err) { - sflc_log_error("Could not perform KDF: error %d", err); - goto bad_kdf; - } - sflc_log_debug("Successfully derived key-encryption-key with KDF"); - - /* Sample VMB_IV */ - err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); - if (err) { - sflc_log_error("Could not sample prologue IV: error %d", err); - goto bad_sample_iv; - } - sflc_log_debug("Successfully sampled prologue IV"); - - /* Encrypt the VMB key */ - err = sflc_aes256gcm_encrypt(kek, vmb_key, SFLC_CRYPTO_KEYLEN, iv, enc_vmb_key, mac); - if (err) { - sflc_log_error("Could not encrypt the VMB key: error %d", err); - goto bad_encrypt; - } - sflc_log_debug("Successfully encrypted VMB key with key-encryption-key"); - - // No prob - err = 0; - - -bad_encrypt: -bad_sample_iv: -bad_kdf: - /* Always wipe the key from memory, even on success */ - memset(kek, 0, SFLC_CRYPTO_KEYLEN); - return err; -} - -/* AES-GCM-decrypt a VMB key with the KDF-generated key */ -static int _decryptVmbKeyWithPwd(char *dmb_cell, char *kek, char *vmb_key, bool *match) -{ - // Pointers inside the block - char *iv = dmb_cell; - char *enc_vmb_key = iv + SFLC_AESGCM_PADDED_IVLEN; - char *mac = enc_vmb_key + SFLC_CRYPTO_KEYLEN; - // Error code - int err; - - /* Decrypt the VMB key */ - err = sflc_aes256gcm_decrypt(kek, enc_vmb_key, SFLC_CRYPTO_KEYLEN, mac, iv, vmb_key, match); - if (err) { - sflc_log_error("Error while decrypting VMB key: error %d", err); - return err; - } - sflc_log_debug("Decrypted VMB key: MAC match = %d", *match); - - return 0; -} - diff --git a/shufflecake-userland-legacy/src/header/position_map.c b/shufflecake-userland-legacy/src/header/position_map.c deleted file mode 100644 index f2b8144..0000000 --- a/shufflecake-userland-legacy/src/header/position_map.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "header.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/math.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Create an encrypted empty position map for the given number of slices. - * Allocates the internal pointers of the EPM structure. - * On failure, does not free the allocated memory. - * - * @param nr_slices The number of slices the device will be composed of. - * @param volume_key The volume's data section encryption key, used to encrypt the - * position map as well. - * @param epm The EncPosMap struct to be initialised. - * - * @return Error code, 0 on success */ -int sflc_epm_create(size_t nr_slices, char *volume_key, sflc_EncPosMap *epm) -{ - size_t nr_pmbs; - size_t nr_arrays; - int err; - - // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) - nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); - // Each array holds up to 256 PosMapBlocks - nr_arrays = ceil(nr_pmbs, SFLC_BLOCKS_PER_LOG_SLICE); - - // Fill the EPM numeric fields - epm->nr_arrays = nr_arrays; - // All arrays are full except the last one - epm->nr_last_pmbs = nr_pmbs - (SFLC_BLOCKS_PER_LOG_SLICE * (nr_arrays - 1)); - - // Allocate array of IV blocks - epm->iv_blocks = malloc(nr_arrays * sizeof(char *)); - if (!epm->iv_blocks) { - sflc_log_error("Could not malloc array of IV blocks"); - err = ENOMEM; - goto out; - } - // Allocate array of PosMapBlock arrays - epm->pmb_arrays = malloc(nr_arrays * sizeof(char *)); - if (!epm->pmb_arrays) { - sflc_log_error("Could not malloc array of PosMapBlock arrays"); - err = ENOMEM; - goto out; - } - - // Loop to allocate and encrypt each array - int i; - for (i = 0; i < nr_arrays; i++) { - // The last PMB array might be smaller - size_t nr_pmbs_here = ((i == nr_arrays - 1) ? epm->nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); - size_t pmb_array_size = SFLC_SECTOR_SIZE * nr_pmbs_here; - char *iv_block; - char *pmb_array; - - // Allocate IV block - epm->iv_blocks[i] = malloc(SFLC_SECTOR_SIZE); - if (!epm->iv_blocks[i]) { - sflc_log_error("Could not allocate IV block number %d", i); - err = ENOMEM; - goto out; - } - // Allocate PosMapBlock array - epm->pmb_arrays[i] = malloc(pmb_array_size); - if (!epm->pmb_arrays[i]) { - sflc_log_error("Could not allocate PMB array number %d", i); - err = ENOMEM; - goto out; - } - // Shorthand - iv_block = epm->iv_blocks[i]; - pmb_array = epm->pmb_arrays[i]; - - // Fill the IV block with random data (can ignore return value) - sflc_rand_getWeakBytes(iv_block, SFLC_SECTOR_SIZE); - // Fill the PMB array with 0xFF - memset(pmb_array, SFLC_EPM_FILLER, pmb_array_size); - - // Loop to encrypt each PMB separately with its IV - int j; - for (j = 0; j < nr_pmbs_here; j++) { - char *iv = iv_block + (j * SFLC_AESCTR_IVLEN); - char *pmb = pmb_array + (j * SFLC_SECTOR_SIZE); - - // Encrypt in-place - err = sflc_aes256ctr_encrypt(volume_key, pmb, SFLC_SECTOR_SIZE, iv, NULL); - if (err) { - sflc_log_error("Could not encrypt PMB %d of array %d", j, i); - goto out; - } - } - } - - -out: - return err; -} diff --git a/shufflecake-userland-legacy/src/header/volume_master_block.c b/shufflecake-userland-legacy/src/header/volume_master_block.c deleted file mode 100644 index 9347582..0000000 --- a/shufflecake-userland-legacy/src/header/volume_master_block.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include // Network byte order - -#include "header.h" -#include "utils/crypto.h" -#include "utils/string.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Serialise the VMB before encrypting it */ -static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb); - -/* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Builds the on-disk master block (indistinguishable from random). - * - * @param vmb The useful information stored in this volume master block - * @param vmb_key The key encrypting the VMB - * @param disk_block The 4096-byte buffer that will contain the random-looking - * bytes to be written on-disk - * - * @return The error code, 0 on success - */ -int sflc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) -{ - // Pointers inside the block - char *iv = disk_block; - char *enc_vmb = iv + SFLC_AESCTR_IVLEN; - // Serialised VMB (dynamically allocated), to be encrypted - char *clear_vmb; - // Error code - int err; - - /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); - if (!clear_vmb) { - sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); - err = ENOMEM; - goto bad_clear_alloc; - } - sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); - - /* Serialise the struct */ - _serialiseVmb(vmb, clear_vmb); - sflc_log_debug("Serialised VMB struct"); - - /* Sample VMB IV */ - err = sflc_rand_getWeakBytes(iv, SFLC_AESGCM_PADDED_IVLEN); - if (err) { - sflc_log_error("Could not sample VMB IV: error %d", err); - goto bad_sample_iv; - } - sflc_log_debug("Successfully sampled VMB IV"); - - /* Encrypt the VMB */ - err = sflc_aes256ctr_encrypt(vmb_key, clear_vmb, SFLC_CLEAR_VMB_LEN, iv, enc_vmb); - if (err) { - sflc_log_error("Could not encrypt VMB: error %d", err); - goto bad_encrypt; - } - sflc_log_debug("Successfully encrypted VMB"); - - // No prob - err = 0; - - -bad_encrypt: -bad_sample_iv: - /* Always wipe and free the cleartext VMB, even on success */ - memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); - free(clear_vmb); -bad_clear_alloc: - return err; -} - - -/** - * Decrypt the VMB payload using the VMB key. - * - * @param disk_block The content of the on-disk encrypted VMB - * @param vmb_key The proposed VMB key to unseal its payload - * @param vmb A pointer to the output struct that will contain all the VMB fields - * - * @return An error code, 0 on success - */ -int sflc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) -{ - // Pointers inside the block - char *iv = disk_block; - char *enc_vmb = iv + SFLC_AESCTR_IVLEN; - // Decrypted VMB (dynamically allocated), to be deserialised - char *clear_vmb; - // Error code - int err; - - /* Allocate large buffer on the heap */ - clear_vmb = malloc(SFLC_CLEAR_VMB_LEN); - if (!clear_vmb) { - sflc_log_error("Could not allocate %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); - err = ENOMEM; - goto bad_clear_alloc; - } - sflc_log_debug("Successfully allocated %d bytes for VMB cleartext", SFLC_CLEAR_VMB_LEN); - - /* Decrypt the VMB */ - err = sflc_aes256ctr_decrypt(vmb_key, enc_vmb, SFLC_CLEAR_VMB_LEN, iv, clear_vmb); - if (err) { - sflc_log_error("Error while decrypting VMB: error %d", err); - goto bad_decrypt; - } - sflc_log_debug("Successfully decrypted VMB"); - - /* Deserialise the struct */ - err = _deserialiseVmb(clear_vmb, vmb); - if (err) { - sflc_log_error("Error while deserialising VMB: error %d", err); - goto bad_deserialise; - } - sflc_log_debug("Deserialised VMB struct"); - - // No prob - err = 0; - - -bad_deserialise: -bad_decrypt: - /* Always wipe and free the VMB cleartext, even on success */ - memset(clear_vmb, 0, SFLC_CLEAR_VMB_LEN); - free(clear_vmb); -bad_clear_alloc: - return err; -} - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Serialise the payload before encrypting it */ -static void _serialiseVmb(sflc_Vmb *vmb, char *clear_vmb) -{ - // Pointers inside the VMB - char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; - - /* Copy the volume key */ - memcpy(p_vol_key, vmb->volume_key, SFLC_CRYPTO_KEYLEN); - - /* Copy the previous volume's VMB key */ - memcpy(p_prev_vmb_key, vmb->prev_vmb_key, SFLC_CRYPTO_KEYLEN); - - /* Write the number of slices (network byte order) */ - *((uint32_t *) p_nr_slices) = htonl(vmb->nr_slices); - - // Leave the rest uninitialised - - return; -} - - -/* Deserialise the VMB after decrypting it */ -static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb) -{ - // Pointers inside the VMB - char *p_vol_key = clear_vmb; - char *p_prev_vmb_key = p_vol_key + SFLC_CRYPTO_KEYLEN; - char *p_nr_slices = p_prev_vmb_key + SFLC_CRYPTO_KEYLEN; - - /* Copy the volume key */ - memcpy(vmb->volume_key, p_vol_key, SFLC_CRYPTO_KEYLEN); - - /* Copy the previous volume's VMB key */ - memcpy(vmb->prev_vmb_key, p_prev_vmb_key, SFLC_CRYPTO_KEYLEN); - - /* Read number of slices (network byte order) */ - vmb->nr_slices = ntohl( *((uint32_t *) p_nr_slices) ); - - // Ignore the rest - - return 0; -} - diff --git a/shufflecake-userland-legacy/src/operations/devmapper.c b/shufflecake-userland-legacy/src/operations/devmapper.c deleted file mode 100644 index a1bec5a..0000000 --- a/shufflecake-userland-legacy/src/operations/devmapper.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "header.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Build parameter list for ctor in dm_sflc, and send DM ioctl to create - * virtual block device. - * - * @param bdev_path The path to the underlying device - * @param dev_id The ID of the underlying block device - * @param vol_idx The index of the volume within the device - * @param vmb Volume metadata - * - * @return Error code, 0 on success - */ -int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) -{ - char label[SFLC_BIGBUFSIZE]; - char *hex_key; - char params[SFLC_BIGBUFSIZE]; - uint64_t num_sectors; - int err; - - /* Build volume label */ - sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); - - /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vmb->volume_key, SFLC_CRYPTO_KEYLEN); - if (!hex_key) { - sflc_log_error("Could not encode volume key to hexadecimal"); - err = ENOMEM; - goto err_hexkey; - } - - /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; - - /* Build param list */ - sprintf(params, "%d %lu %s %lu %lu %s", SFLC_MODE_LEGACY, dev_id, bdev_path, vol_idx, vmb->nr_slices, hex_key); - - /* Issue ioctl */ - err = sflc_dm_create(label, num_sectors, params); - if (err) { - sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); - goto err_dmcreate; - } - err = 0; - - -err_dmcreate: - free(hex_key); -err_hexkey: - return err; -} - - -/** - * Close the volume by issuing the appropriate ioctl to the DM. - * - * @param label The only needed parameter: the ID of the volume. - * - * @return Error code, 0 on success - */ -int sflc_ops_closeVolume(char *label) -{ - /* Issue ioctl */ - return sflc_dm_destroy(label); -} diff --git a/shufflecake-userland-legacy/src/operations/dmb.c b/shufflecake-userland-legacy/src/operations/dmb.c deleted file mode 100644 index d529966..0000000 --- a/shufflecake-userland-legacy/src/operations/dmb.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "header.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/disk.h" -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Encrypts and writes the DMB to disk. - * - * @param bdev_path The path to the underlying block device - * @param pwds The array of passwords - * @param pwd_lens Their lengths - * @param dmb->nr_vols - * - * - * @return Error code, 0 on success - */ -int sflc_ops_writeDmb(char *bdev_path, char **pwds, size_t *pwd_lens, sflc_Dmb *dmb) -{ - /* On-disk DMB */ - char enc_dmb[SFLC_SECTOR_SIZE]; - /* Error code */ - int err; - - /* Sanity check */ - if (dmb->nr_vols > SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot create %lu volumes, too many!", dmb->nr_vols); - return EINVAL; - } - - /* Seal DMB */ - err = sflc_dmb_seal(dmb, pwds, pwd_lens, enc_dmb); - if (err) { - sflc_log_error("Coul dnot seal DMB; error %d", err); - return err; - } - - /* Write it to disk (at sector 0) */ - err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); - if (err) { - sflc_log_error("Could not write DMB to disk; error %d", err); - return err; - } - - return 0; -} - - -/** - * Reads the DMB from disk and outputs the unlocked VMB key. - * - * @param bdev_path The path to the underlying block device - * @param pwd The user-provided password - * @param pwd_len Its length - * - * @output dmb_cell->vmb_key The unlocked VMB key - * @output dmb_cell->vol_idx Its index (>= SFLC_DEV_MAX_VOLUMES if none could be unlocked) - * - * @return Error code, 0 on success - */ -int sflc_ops_readDmb(char *bdev_path, char *pwd, size_t pwd_len, sflc_DmbCell *dmb) -{ - char enc_dmb[SFLC_SECTOR_SIZE]; - int err; - - /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readSector(bdev_path, 0, enc_dmb); - if (err) { - sflc_log_error("Could not read DMB from disk; error %d", err); - return err; - } - - /* Unseal it */ - err = sflc_dmb_unseal(enc_dmb, pwd, pwd_len, dmb); - if (err) { - sflc_log_error("Could not unseal DMB; error %d", err); - return err; - } - - return 0; -} - -/** - * Reads the DMB from disk, changes the relevant DMB cell, and writes it back. - * - * @param bdev_path The path to the underlying block device - * @param dmb_cell The index and vmb_key of the DMB cell to re-encrypt - * @param new_pwd The new password of the DMB cell to re-encrypt - * @param new_pwd_len Its length - * - * @return Error code, 0 on success - */ -int sflc_ops_rewriteDmbCell(char *bdev_path, sflc_DmbCell *dmb_cell, char *new_pwd, size_t new_pwd_len) -{ - char enc_dmb[SFLC_SECTOR_SIZE]; - int err; - - /* Sanity check */ - if (dmb_cell->vol_idx >= SFLC_DEV_MAX_VOLUMES) { - sflc_log_error("Cannot re-encrypt DMB cell %lu: out of bounds!", dmb_cell->vol_idx); - return EINVAL; - } - - /* Read DMB from disk (at sector 0) */ - err = sflc_disk_readSector(bdev_path, 0, enc_dmb); - if (err) { - sflc_log_error("Could not read DMB from disk; error %d", err); - return err; - } - - /* Update the relevant cell */ - err = sflc_dmb_setCell(enc_dmb, dmb_cell, new_pwd, new_pwd_len); - if (err) { - sflc_log_error("Could not update DMB cell; error %d", err); - return err; - } - - /* Write it to disk (at sector 0) */ - err = sflc_disk_writeSector(bdev_path, 0, enc_dmb); - if (err) { - sflc_log_error("Could not write DMB to disk; error %d", err); - return err; - } - - return 0; -} - - diff --git a/shufflecake-userland-legacy/src/operations/volume_header.c b/shufflecake-userland-legacy/src/operations/volume_header.c deleted file mode 100644 index 1088290..0000000 --- a/shufflecake-userland-legacy/src/operations/volume_header.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include - -#include "header.h" -#include "header.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/disk.h" -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Writes a volume header (VMB+PM) on-disk. - * - * @param bdev_path The underlying block device to write the volume header - * @param vmb_key The key to encrypt the VMB - * @param vmb The VMB to encrypt - * @param vol_idx The index of the volume within the device - * - * @return Error code, 0 on success - */ -int sflc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) -{ - char enc_vmb[SFLC_SECTOR_SIZE]; - sflc_EncPosMap epm; - uint64_t sector; - int err; - - // Encrypt VMB - err = sflc_vmb_seal(vmb, vmb_key, enc_vmb); - if (err) { - sflc_log_error("Could not seal VMB; error %d", err); - goto out; - } - - // Write it to disk - sector = sflc_vmbPosition(vol_idx, vmb->nr_slices); - err = sflc_disk_writeSector(bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not write VMB to disk; error %d", err); - goto out; - } - sector += 1; - - // Create encrypted empty position map - err = sflc_epm_create(vmb->nr_slices, vmb->volume_key, &epm); - if (err) { - sflc_log_error("Could not create encrypted empty position map; error %d", err); - goto out; - } - - // Loop over PMB arrays to write them to disk - int i; - for (i = 0; i < epm.nr_arrays; i++) { - char *iv_block = epm.iv_blocks[i]; - char *pmb_array = epm.pmb_arrays[i]; - size_t nr_pmbs = ((i == epm.nr_arrays-1) ? epm.nr_last_pmbs : SFLC_BLOCKS_PER_LOG_SLICE); - - // First write the IV block - err = sflc_disk_writeSector(bdev_path, sector, iv_block); - if (err) { - sflc_log_error("Could not write IV block to disk; error %d", err); - goto out; - } - sector += 1; - - // Then the whole PMB array - err = sflc_disk_writeManySectors(bdev_path, sector, pmb_array, nr_pmbs); - if (err) { - sflc_log_error("Could not write PMB array to disk; error %d", err); - goto out; - } - sector += nr_pmbs; - - // Free them both - free(iv_block); - free(pmb_array); - } - - // Free containers - free(epm.iv_blocks); - free(epm.pmb_arrays); - - -out: - return err; -} - - -/** - * Reads a VMB from disk and unlocks it - * - * @param bdev_path The underlying block device - * @param vmb_key The key to decrypt the VMB - * @param nr_slices The number of slices in the device - * @param vol_idx The index of the volume within the device - * - * @output vmb The decrypted VMB - * - * @return Error code, 0 on success - */ -int sflc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) -{ - char enc_vmb[SFLC_SECTOR_SIZE]; - uint64_t sector; - int err; - - /* Read encrypted VMB from disk */ - sector = sflc_vmbPosition(vol_idx, nr_slices); - err = sflc_disk_readSector(bdev_path, sector, enc_vmb); - if (err) { - sflc_log_error("Could not read VMB from disk; error %d", err); - return err; - } - - /* Unseal it */ - err = sflc_vmb_unseal(enc_vmb, vmb_key, vmb); - if (err) { - sflc_log_error("Could not unseal VMB; error %d", err); - return err; - } - - /* Compare the number of slices */ - if (nr_slices != vmb->nr_slices) { - sflc_log_error("Incompatible header size: the device size was different when the volumes" - "were created. Did you resize the device %s since last time?", bdev_path); - return EINVAL; - } - - return 0; -} diff --git a/shufflecake-userland-legacy/src/utils/crypto.c b/shufflecake-userland-legacy/src/utils/crypto.c deleted file mode 100644 index b42f50f..0000000 --- a/shufflecake-userland-legacy/src/utils/crypto.c +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -#include "utils/crypto.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - *Fills the given buffer with strong random bytes, suitable for long-term - *cryptographic keys (uses the entropy pool). Always succeeds. - * - *@param buf The buffer to fill - *@param buflen The number of desired random bytes - * - *@return The error code (0 on success) - */ -int sflc_rand_getStrongBytes(char *buf, size_t buflen) -{ - gcry_randomize(buf, buflen, GCRY_VERY_STRONG_RANDOM); - return 0; -} - - -/** - *Faster than the previous one, fills the given buffer with weak random bytes, - *suitable for IVs or block filling (does not use the entropy pool). - *Always succeeds. - * - *@param buf The buffer to fill - *@param buflen The number of desired random bytes - * - *@return The error code (0 on success) - */ -int sflc_rand_getWeakBytes(char *buf, size_t buflen) -{ - gcry_create_nonce(buf, buflen); - return 0; -} - - -/** - *AES-CTR encryption, does not touch the IV. Set ct = NULL for in-place. - * - *@param key The 32-byte AES key - *@param pt The plaintext - *@param pt_len The length of the plaintext, must be a multiple of the AES - * block size (16 bytes) - *@param iv The 16-byte AES-CTR IV - *@param ct A caller-allocated buffer that will contain the output ciphertext, - * cannot overlap with pt. If NULL, in-place encryption. - * - *@return The error code (0 on success) - */ -int sflc_aes256ctr_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct) -{ - gcry_cipher_hd_t hd; - gcry_error_t err; - - // Instantiate the handle - err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); - if (err) { - sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); - goto bad_open; - } - sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); - - // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not set AES key: error %d", err); - goto bad_setkey; - } - sflc_log_debug("Successfully set the AES key"); - - // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); - if (err) { - sflc_log_error("Could not set AES-CTR IV: error %d", err); - goto bad_setctr; - } - sflc_log_debug("Successfully set the IV"); - - // Encrypt - if (ct == NULL) { // In-place - err = gcry_cipher_encrypt(hd, pt, pt_len, NULL, 0); - } - else { // Out-of-place - err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); - } - // Error check - if (err) { - sflc_log_error("Could not encrypt: error %d", err); - goto bad_encrypt; - } - sflc_log_debug("Successfully encrypted"); - - // No prob? - err = 0; - - -bad_encrypt: -bad_setctr: -bad_setkey: - gcry_cipher_close(hd); -bad_open: - return err; -} - -/** - *AES-CTR decryption, does not touch the IV. Set pt = NULL for in-place. - * - *@param key The 32-byte AES key - *@param ct The ciphertext - *@param ct_len The length of the ciphertext, must be a multiple of the AES - * block size (16 bytes) - *@param iv The 16-byte AES-CTR IV - *@param pt A caller-allocated buffer that will contain the output plaintext, - * cannot overlap with ct. If NULL, in-place decryption. - * - *@return The error code (0 on success) - */ -int sflc_aes256ctr_decrypt(char *key, char *ct, size_t ct_len, char *iv, char *pt) -{ - gcry_cipher_hd_t hd; - gcry_error_t err; - - // Instantiate the handle - err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, GCRY_CIPHER_SECURE); - if (err) { - sflc_log_error("Could not instantiate AES256-CTR cipher handle: error %d", err); - goto bad_open; - } - sflc_log_debug("Successfully instantiated AES256-CTR cipher handle"); - - // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not set AES key: error %d", err); - goto bad_setkey; - } - sflc_log_debug("Successfully set AES key"); - - // Set the counter (not an IV, as per Gcrypt docs) - err = gcry_cipher_setctr(hd, iv, SFLC_AESCTR_IVLEN); - if (err) { - sflc_log_error("Could not set AES-CTR IV: error %d", err); - goto bad_setctr; - } - sflc_log_debug("Successfully set IV"); - - // Decrypt - if (pt == NULL) { // In-place - err = gcry_cipher_decrypt(hd, ct, ct_len, NULL, 0); - } - else { // Out-of-place - err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); - } - // Error check - if (err) { - sflc_log_error("Could not decrypt: error %d", err); - goto bad_decrypt; - } - sflc_log_debug("Successfully decrypted"); - - // No prob - err = 0; - - -bad_decrypt: -bad_setctr: -bad_setkey: - gcry_cipher_close(hd); -bad_open: - return err; -} - - -/** - *AES-GCM encryption, does not touch the IV. - * - *@param key The 32-byte AES key - *@param pt The plaintext - *@param pt_len The length of the plaintext, must be a multiple of the AES - * block size (16 bytes) - *@param iv The 12-byte AES-GCM IV - *@param ct A caller-allocated buffer that will contain the output ciphertext, - * cannot overlap with pt - *@param tag A caller-allocated buffer that will contain the 16-byte output MAC - * - *@return The error code (0 on success) - */ -int sflc_aes256gcm_encrypt(char *key, char *pt, size_t pt_len, char *iv, char *ct, char *tag) -{ - gcry_cipher_hd_t hd; - gcry_error_t err; - - // Instantiate the handle - err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); - if (err) { - sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); - goto bad_open; - } - sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); - - // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not set AES key: error %d", err); - goto bad_setkey; - } - sflc_log_debug("Successfully set the AES key"); - - // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); - if (err) { - sflc_log_error("Could not set AES-GCM IV: error %d", err); - goto bad_setiv; - } - sflc_log_debug("Successfully set the IV"); - - // Encrypt - err = gcry_cipher_encrypt(hd, ct, pt_len, pt, pt_len); - if (err) { - sflc_log_error("Could not encrypt: error %d", err); - goto bad_encrypt; - } - sflc_log_debug("Successfully encrypted"); - - // Get MAC - err = gcry_cipher_gettag(hd, tag, SFLC_AESGCM_TAGLEN); - if (err) { - sflc_log_error("Could not get MAC: error %d", err); - goto bad_gettag; - } - sflc_log_debug("Successfully gotten MAC"); - - // No prob? - err = 0; - - -bad_gettag: -bad_encrypt: -bad_setiv: -bad_setkey: - gcry_cipher_close(hd); -bad_open: - return err; -} - - -/** - *AES-GCM decryption, does not touch the IV. - * - *@param key The 32-byte AES key - *@param ct The ciphertext - *@param ct_len The length of the ciphertext, must be a multiple of the AES - * block size (16 bytes) - *@param tag The 16-byte MAC - *@param iv The 12-byte AES-GCM IV - *@param pt A caller-allocated buffer that will contain the output plaintext, - * cannot overlap with ct - *@param match A pointer to a single output boolean, indicating MAC verification - * success or failure. On failure, pt is filled with poison bytes. - * - *@return The error code (0 on success) - */ -int sflc_aes256gcm_decrypt(char *key, char *ct, size_t ct_len, char *tag, char *iv, char *pt, bool *match) -{ - gcry_cipher_hd_t hd; - gcry_error_t err; - - // Instantiate the handle - err = gcry_cipher_open(&hd, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE); - if (err) { - sflc_log_error("Could not instantiate AES256-GCM cipher handle: error %d", err); - goto bad_open; - } - sflc_log_debug("Successfully instantiated AES256-GCM cipher handle"); - - // Set the key - err = gcry_cipher_setkey(hd, key, SFLC_CRYPTO_KEYLEN); - if (err) { - sflc_log_error("Could not set AES key: error %d", err); - goto bad_setkey; - } - sflc_log_debug("Successfully set AES key"); - - // Set the IV - err = gcry_cipher_setiv(hd, iv, SFLC_AESGCM_IVLEN); - if (err) { - sflc_log_error("Could not set AES-GCM IV: error %d", err); - goto bad_setiv; - } - sflc_log_debug("Successfully set IV"); - - // Decrypt - err = gcry_cipher_decrypt(hd, pt, ct_len, ct, ct_len); - if (err) { - sflc_log_error("Could not decrypt: error %d", err); - goto bad_decrypt; - } - sflc_log_debug("Successfully decrypted"); - - // Check MAC - err = gcry_cipher_checktag(hd, tag, SFLC_AESGCM_TAGLEN); - if (gcry_err_code(err) == GPG_ERR_CHECKSUM) { - // Undo decryption - memset(pt, SFLC_AESGCM_POISON_PT, ct_len); - // Flag it - *match = false; - } - else if (err) { - sflc_log_error("Could not check MAC: error %d", err); - goto bad_checktag; - } - else { - // Flag MAC verification success - *match = true; - } - sflc_log_debug("Successfully checked MAC: match = %d", *match); - - // No prob, whether MAC verified or not - err = 0; - - -bad_checktag: -bad_decrypt: -bad_setiv: -bad_setkey: - gcry_cipher_close(hd); -bad_open: - return err; -} - - -/** - * Argon2id KDF. - * - * @param pwd The password - * @param pwd_len The length of the password - * @param salt The 16-byte KDF salt - * @param hash A caller-allocated 32-byte output buffer that will contain the - * password hash - * - * @return The error code (0 on success) - */ -int sflc_argon2id_derive(char *pwd, size_t pwd_len, char *salt, char *hash) -{ - gcry_kdf_hd_t hd; - const unsigned long argon_params[4] = - {SFLC_CRYPTO_KEYLEN, SFLC_ARGON_T, SFLC_ARGON_M, SFLC_ARGON_P}; - gcry_error_t err; - - // Instantiate Argon2id handle - err = gcry_kdf_open( - &hd, GCRY_KDF_ARGON2, GCRY_KDF_ARGON2ID, - argon_params, 4, - pwd, pwd_len, - salt, SFLC_ARGON_SALTLEN, - NULL, 0, /* Optional secret value K */ - NULL, 0 /* Optional associated data X */ - ); - if (err) { - sflc_log_error("Could not open Argon2id handle: error %d", err); - goto bad_open; - } - sflc_log_debug("Successfully opened Argon2id handle"); - - // Run the computation - err = gcry_kdf_compute(hd, NULL); - if (err) { - sflc_log_error("Could not run Argon2id computation: error %d", err); - goto bad_compute; - } - sflc_log_debug("Successfully run Argon2id computation"); - - // Finalise hash - err = gcry_kdf_final(hd, SFLC_CRYPTO_KEYLEN, hash); - if (err) { - sflc_log_error("Could not finalise Argon2id hash: error %d", err); - goto bad_final; - } - sflc_log_debug("Successfully finalised Argon2id hash"); - - // All in order - err = 0; - - -bad_final: -bad_compute: - gcry_kdf_close(hd); -bad_open: - return err; -} diff --git a/shufflecake-userland-legacy/src/utils/disk.c b/shufflecake-userland-legacy/src/utils/disk.c deleted file mode 100644 index c37711a..0000000 --- a/shufflecake-userland-legacy/src/utils/disk.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Disk helper functions - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils/disk.h" -#include "utils/log.h" - - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Returns a malloc'ed string formatted as : - * - * @param bdev_path The path to the block device - * - * @return A string version of the device ID - */ -char *sflc_disk_getDeviceName(char *bdev_path) -{ - struct stat sb; - char *dev_name; - - if (stat(bdev_path, &sb) != 0) - return NULL; - dev_name = malloc(SFLC_BIGBUFSIZE); - if (!dev_name) - return NULL; - - sprintf(dev_name, "%u:%u", major(sb.st_rdev), minor(sb.st_rdev)); - - return dev_name; -} - - -/** - * Checks whether the given path points to a block device. - * - * @param path The path to check - * - * @return true iff it's a block device - */ -bool sflc_disk_isBlockDevice(char *path) -{ - struct stat path_stat; - if (stat(path, &path_stat) != 0) { - return false; - } - return S_ISBLK(path_stat.st_mode); -} - -/** - * Returns the size in 4096-byte sectors (or < 0 if error). - * - * @param bdev_path The path of the block device - * - * @return The size (in 4096-byte sectors) of the disk, or -errno if error - */ -int64_t sflc_disk_getSize(char * bdev_path) -{ - int fd; - uint64_t size_bytes; - int64_t ret; - - /* Open file */ - fd = open(bdev_path, O_RDONLY); - if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); - perror("Cause: "); - ret = -errno; - goto bad_open; - } - sflc_log_debug("Opened file %s", bdev_path); - - /* Get size in bytes */ - if (ioctl(fd, BLKGETSIZE64, &size_bytes) < 0) { - sflc_log_error("Could not ioctl file %s", bdev_path); - perror("Cause: "); - ret = -errno; - goto bad_ioctl; - } - sflc_log_debug("Size of %s is %lu bytes", bdev_path, size_bytes); - - /* Compute size in SFLC sectors */ - ret = (size_bytes / SFLC_SECTOR_SIZE); - -bad_ioctl: - close(fd); -bad_open: - return ret; -} - -/** - * Reads a single 4096-byte sector from the disk. - * - * @param bdev_path The path of the block device - * @param sector The index of the desired sector - * @param The caller-allocated buffer (must hold 4096 bytes) where the data - * from the disk will go - * - * @return The error code (0 on success) - */ -int sflc_disk_readSector(char * bdev_path, uint64_t sector, char * buf) -{ - int fd; - int err; - - /* Open file */ - fd = open(bdev_path, O_RDONLY); - if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); - perror("Cause: "); - err = errno; - goto bad_open; - } - sflc_log_debug("Opened file %s", bdev_path); - - /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { - sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); - perror("Cause: "); - err = errno; - goto bad_lseek; - } - sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); - - /* Read in a loop */ - size_t bytes_to_read = SFLC_SECTOR_SIZE; - while (bytes_to_read > 0) { - /* Read syscall */ - ssize_t bytes_read = read(fd, buf, bytes_to_read); - if (bytes_read < 0) { - sflc_log_error("Could not read file %s at sector %lu", bdev_path, sector); - perror("Cause: "); - err = errno; - goto bad_read; - } - - /* Partial read? No problem just log */ - if (bytes_read < bytes_to_read) { - sflc_log_debug("Partial read from file %s at sector %lu: %ld bytes instead of %ld", - bdev_path, sector, bytes_read, bytes_to_read); - } - - /* Advance loop */ - bytes_to_read -= bytes_read; - buf += bytes_read; - } - - // No prob - err = 0; - -bad_read: -bad_lseek: - close(fd); -bad_open: - return err; -} - - -/** - * Writes a single 4096-byte sector to the disk. - * - * @param bdev_path The path of the block device - * @param sector The index of the desired sector - * @param The caller-allocated buffer (must hold 4096 bytes) where the data - * comes from - * - * @return The error code (0 on success) - */ -int sflc_disk_writeSector(char * bdev_path, uint64_t sector, char * buf) -{ - return sflc_disk_writeManySectors(bdev_path, sector, buf, 1); -} - - -/** - * Writes many 4096-byte sectors to the disk. - * - * @param bdev_path The path of the block device - * @param sector The index of the starting sector - * @param The caller-allocated buffer where the data - * comes from - * @param num_sectors The number of sectors to write - * - * @return The error code (0 on success) - */ -int sflc_disk_writeManySectors(char * bdev_path, uint64_t sector, char * buf, size_t num_sectors) -{ - int fd; - int err; - - /* Open file */ - fd = open(bdev_path, O_WRONLY); - if (fd < 0) { - sflc_log_error("Could not open file %s", bdev_path); - perror("Cause: "); - err = errno; - goto bad_open; - } - sflc_log_debug("Opened file %s", bdev_path); - - /* Set offset in bytes */ - if (lseek(fd, sector * SFLC_SECTOR_SIZE, SEEK_SET) < 0) { - sflc_log_error("Could not lseek file %s to sector %lu", bdev_path, sector); - perror("Cause: "); - err = errno; - goto bad_lseek; - } - sflc_log_debug("Successful lseek on file %s to sector %lu", bdev_path, sector); - - /* Write in a loop */ - size_t bytes_to_write = SFLC_SECTOR_SIZE * num_sectors; - while (bytes_to_write > 0) { - /* Write syscall */ - ssize_t bytes_written = write(fd, buf, bytes_to_write); - if (bytes_written < 0) { - sflc_log_red("Could not write file %s at sector %lu", bdev_path, sector); - perror("Cause: "); - err = errno; - goto bad_write; - } - - /* Partial write? No problem just log */ - if (bytes_written < bytes_to_write) { - sflc_log_debug("Partial write to file %s at sector %lu: %ld bytes instead of %ld", - bdev_path, sector, bytes_written, bytes_to_write); - } - - /* Advance loop */ - bytes_to_write -= bytes_written; - buf += bytes_written; - } - - // No prob - err = 0; - -bad_write: -bad_lseek: - close(fd); -bad_open: - return err; -} diff --git a/shufflecake-userland-legacy/src/utils/dm.c b/shufflecake-userland-legacy/src/utils/dm.c deleted file mode 100644 index 6c2d054..0000000 --- a/shufflecake-userland-legacy/src/utils/dm.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - - -/** - * Interface to the device mapper. The only example I could find is the - * cryptsetup source, at - * https://kernel.googlesource.com/pub/scm/utils/cryptsetup/cryptsetup/+/v1_6_3/lib/libdevmapper.c - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -#include "utils/dm.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Create a new Shufflecake virtual device (volume) under /dev/mapper. - * - * @param virt_dev_name The name of the new virtual device, as it will appear - * under /dev/mapper - * @param num_sectors The size of the virtual device, in 512-byte sectors - * @param params The string containing the space-separated paramters that will - * be passed to the Shufflecake constructor in the kernel module - * - * @return The error code (0 on success) - */ -int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params) -{ - struct dm_task *dmt; - uint32_t cookie = 0; - uint16_t udev_flags = 0; - int err; - - /* Just to be sure, let's get them on the heap */ - char * dup_virt_dev_name = strdup(virt_dev_name); - char * dup_params = strdup(params); - - sflc_log_debug("Creating /dev/mapper/%s", dup_virt_dev_name); - - /* Instantiate the DM task (with the CREATE ioctl command) */ - if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { - sflc_log_error("Cannot create dm_task"); - err = 1; - goto dup_free; - } - sflc_log_debug("Successfully created dm_task"); - - /* Set the name of the target device (to be created) */ - if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflc_log_error("Cannot set device name"); - err = 2; - goto out; - } - sflc_log_debug("Successfully set device name"); - - /* State that it is a Shufflecake device, pass the start and size, and the - * constructor parameters */ - if (!dm_task_add_target(dmt, 0, num_sectors, SFLC_DM_TARGET_NAME, dup_params)) { - sflc_log_error("Cannot add DM target and parameters"); - err = 3; - goto out; - } - sflc_log_debug("Successfully added DM target and parameters"); - - /* Say that we want a new node under /dev/mapper */ - if (!dm_task_set_add_node(dmt, DM_ADD_NODE_ON_CREATE)) { - sflc_log_error("Cannot add /dev/mapper node"); - err = 4; - goto out; - } - sflc_log_debug("Successfully set the ADD_NODE flag"); - - /* Get a cookie (request ID, basically) to wait for task completion */ - if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflc_log_error("Cannot get cookie"); - err = 5; - goto out; - } - sflc_log_debug("Successfully got a cookie"); - - /* Run the task */ - if (!dm_task_run(dmt)) { - sflc_log_error("Cannot issue ioctl"); - err = 6; - goto out; - } - sflc_log_debug("Successfully run DM task"); - - /* Wait for completion */ - dm_udev_wait(cookie); - sflc_log_debug("Task completed"); - - // No prob - err = 0; - -out: - dm_task_destroy(dmt); -dup_free: - free(dup_virt_dev_name); - free(dup_params); - - return err; -} - -/** - * Close a Shufflecake virtual device (volume) under /dev/mapper. - * - * @param virt_dev_name the name of the virtual device, as it appears under - * /dev/mapper - * - * @return error code (0 on success) - */ -int sflc_dm_destroy(char * virt_dev_name) -{ - struct dm_task *dmt; - uint32_t cookie = 0; - uint16_t udev_flags = 0; - int err = 0; - - /* Just to be sure, let's get it on the heap */ - char * dup_virt_dev_name = strdup(virt_dev_name); - - sflc_log_debug("Closing /dev/mapper/%s", dup_virt_dev_name); - - /* Instantiate the DM task (with the REMOVE ioctl command) */ - if (!(dmt = dm_task_create(DM_DEVICE_REMOVE))) { - sflc_log_error("Cannot create dm_task"); - err = 1; - goto dup_free; - } - sflc_log_debug("Successfully created dm_task"); - - /* Set the name of the target device (to be closed) */ - if (!dm_task_set_name(dmt, dup_virt_dev_name)) { - sflc_log_error("Cannot set device name"); - err = 2; - goto out; - } - sflc_log_debug("Successfully set device name"); - - /* Get a cookie (request ID, basically) to wait for task completion */ - if (!dm_task_set_cookie(dmt, &cookie, udev_flags)) { - sflc_log_error("Cannot set cookie"); - err = 3; - goto out; - } - sflc_log_debug("Successfully got a cookie"); - - /* Needed for some reason */ - dm_task_retry_remove(dmt); - sflc_log_debug("Successful retry_remove"); - - /* Run the task */ - if (!dm_task_run(dmt)) { - sflc_log_error("Cannot issue ioctl"); - err = 4; - goto out; - } - sflc_log_debug("Successfully run task"); - - /* Wait for completion */ - dm_udev_wait(cookie); - sflc_log_debug("Task completed"); - - // No prob - err = 0; - -out: - dm_task_destroy(dmt); -dup_free: - free(dup_virt_dev_name); - - return err; -} diff --git a/shufflecake-userland-legacy/test/crypto/test_aes256ctr.c b/shufflecake-userland-legacy/test/crypto/test_aes256ctr.c deleted file mode 100644 index 700a910..0000000 --- a/shufflecake-userland-legacy/test/crypto/test_aes256ctr.c +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/crypto.h" -#include "test_aes256ctr.h" -#include "minunit.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANT VARIABLES * - *****************************************************/ - -static const char KEY[] = AES256CTR_TEST_KEY; -static const char IV[] = AES256CTR_TEST_IV; -static const char PT[] = AES256CTR_TEST_PT; -static const char CT[] = AES256CTR_TEST_CT; - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_aes256ctr_encrypt_inplace() -{ - char msg[] = AES256CTR_TEST_PT; - size_t msg_len = sizeof(msg); - char key[] = AES256CTR_TEST_KEY; - char iv[] = AES256CTR_TEST_IV; - int err; - - sflc_log_blue("Testing AES256-CTR encryption in-place"); - - // Encrypt in-place - err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, NULL); - - // Check error - mu_assert("Error while encrypting", !err); - - // Check outcome - mu_assert("Ciphertext mismatch", memcmp(msg, CT, msg_len) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - -char *test_aes256ctr_encrypt_outofplace() -{ - char msg[] = AES256CTR_TEST_PT; - char ct[sizeof(msg)]; - size_t msg_len = sizeof(msg); - char key[] = AES256CTR_TEST_KEY; - char iv[] = AES256CTR_TEST_IV; - int err; - - sflc_log_blue("Testing AES256-CTR encryption out-of-place"); - - // Encrypt out-of-place - err = sflc_aes256ctr_encrypt(key, msg, msg_len, iv, ct); - - // Check error - mu_assert("Error while encrypting", !err); - - // Check outcome - mu_assert("Ciphertext mismatch", memcmp(ct, CT, msg_len) == 0); - - // Check msg untouched - mu_assert("Plaintext changed", memcmp(msg, PT, sizeof(key)) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - -char *test_aes256ctr_decrypt_inplace() -{ - char msg[] = AES256CTR_TEST_CT; - size_t msg_len = sizeof(msg); - char key[] = AES256CTR_TEST_KEY; - char iv[] = AES256CTR_TEST_IV; - int err; - - sflc_log_blue("Testing AES256-CTR decryption in-place"); - - // Decrypt in-place - err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, NULL); - - // Check error - mu_assert("Error while decrypting", !err); - - // Check outcome - mu_assert("Plaintext mismatch", memcmp(msg, PT, msg_len) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - -char *test_aes256ctr_decrypt_outofplace() -{ - char msg[] = AES256CTR_TEST_CT; - char pt[sizeof(msg)]; - size_t msg_len = sizeof(msg); - char key[] = AES256CTR_TEST_KEY; - char iv[] = AES256CTR_TEST_IV; - int err; - - sflc_log_blue("Testing AES256-CTR decryption out-of-place"); - - // Decrypt out-of-place - err = sflc_aes256ctr_decrypt(key, msg, msg_len, iv, pt); - - // Check error - mu_assert("Error while decrypting", !err); - - // Check outcome - mu_assert("Plaintext mismatch", memcmp(pt, PT, msg_len) == 0); - - // Check msg untouched - mu_assert("Ciphertext changed", memcmp(msg, CT, sizeof(key)) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - diff --git a/shufflecake-userland-legacy/test/crypto/test_aes256gcm.c b/shufflecake-userland-legacy/test/crypto/test_aes256gcm.c deleted file mode 100644 index b332c51..0000000 --- a/shufflecake-userland-legacy/test/crypto/test_aes256gcm.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/crypto.h" -#include "test_aes256gcm.h" -#include "minunit.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANT VARIABLES * - *****************************************************/ - -static const char KEY[] = AES256GCM_TEST_KEY; -static const char IV[] = AES256GCM_TEST_IV; -static const char PT[] = AES256GCM_TEST_PT; -static const char CT[] = AES256GCM_TEST_CT; -static const char TAG[] = AES256GCM_TEST_TAG; - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_aes256gcm_encrypt() -{ - char pt[] = AES256GCM_TEST_PT; - size_t pt_len = sizeof(pt); - char key[] = AES256GCM_TEST_KEY; - char iv[] = AES256GCM_TEST_IV; - char ct[sizeof(pt)]; - char tag[SFLC_AESGCM_TAGLEN]; - int err; - - sflc_log_blue("Testing AES256-GCM encryption"); - - // Encrypt - err = sflc_aes256gcm_encrypt(key, pt, pt_len, iv, ct, tag); - - // Check error - mu_assert("Error while encrypting", !err); - - // Check outcome - mu_assert("Ciphertext mismatch", memcmp(ct, CT, pt_len) == 0); - mu_assert("MAC mismatch", memcmp(tag, TAG, SFLC_AESGCM_TAGLEN) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - -char *test_aes256gcm_decrypt_good() -{ - char ct[] = AES256GCM_TEST_CT; - size_t ct_len = sizeof(ct); - char tag[] = AES256GCM_TEST_TAG; - char key[] = AES256GCM_TEST_KEY; - char iv[] = AES256GCM_TEST_IV; - bool match; - char pt[sizeof(ct)]; - int err; - - sflc_log_blue("Testing AES256-GCM decryption with the proper MAC"); - - // Decrypt - err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); - - // Check error - mu_assert("Error while decrypting", !err); - - // Check outcome - mu_assert("MAC verification failed", match); - mu_assert("Plaintext mismatch", memcmp(pt, PT, ct_len) == 0); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - // Check MAC untouched - mu_assert("MAC changed", memcmp(tag, TAG, sizeof(tag)) == 0); - - sflc_log_green("OK"); - - return NULL; -} - -char *test_aes256gcm_decrypt_fail() -{ - char ct[] = AES256GCM_TEST_CT; - size_t ct_len = sizeof(ct); - char tag[] = AES256GCM_TEST_TAG; - char key[] = AES256GCM_TEST_KEY; - char iv[] = AES256GCM_TEST_IV; - bool match; - char pt[sizeof(ct)]; - int err; - - sflc_log_blue("Testing AES256-GCM decryption without the proper MAC"); - - // Corrupt the MAC - tag[0] += 1; - - // Decrypt - err = sflc_aes256gcm_decrypt(key, ct, ct_len, tag, iv, pt, &match); - - // Check error - mu_assert("Error while decrypting", !err); - - // Check outcome - mu_assert("MAC verification succeeded", !match); - - // Check key untouched - mu_assert("Key changed", memcmp(key, KEY, sizeof(key)) == 0); - - // Check IV untouched - mu_assert("IV changed", memcmp(iv, IV, sizeof(iv)) == 0); - - // Check MAC untouched - mu_assert("Tail of MAC changed", memcmp(tag+1, TAG+1, sizeof(tag)-1) == 0); - mu_assert("Head of MAC changed", tag[0] == TAG[0]+1); - - sflc_log_green("OK"); - - return NULL; -} diff --git a/shufflecake-userland-legacy/test/crypto/test_argon2id.c b/shufflecake-userland-legacy/test/crypto/test_argon2id.c deleted file mode 100644 index 0ded58b..0000000 --- a/shufflecake-userland-legacy/test/crypto/test_argon2id.c +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include - -#include "utils/crypto.h" -#include "test_argon2id.h" -#include "minunit.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * CONSTANT VARIABLES * - *****************************************************/ - -char SALT[SFLC_ARGON_SALTLEN+1] = "Poor Petrol Pump"; - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -char *test_argon2id() -{ - char pwd[SFLC_BIGBUFSIZE]; - char key[SFLC_CRYPTO_KEYLEN]; - int err; - - sflc_log_blue("Testing Argon2id interactively with a fixed salt"); - - // Loop until user breaks out - while (true) { - /* Collect password */ - printf("Choose password to hash (empty to skip): "); - err = sflc_safeReadLine(pwd, SFLC_BIGBUFSIZE); - mu_assert("Could not read password", !err); - - /* Check if empty */ - if (strlen(pwd) == 0) { - break; - } - - /* Hash it */ - err = sflc_argon2id_derive(pwd, strlen(pwd), SALT, key); - mu_assert("Could not hash password", !err); - - /* Print it */ - printf("Salt used ASCII: \"%s\". Hex:\n", SALT); - sflc_log_hex(SALT, SFLC_ARGON_SALTLEN); - printf("Argon2id hash (m = %d, t = %d, p = %d):\n", - SFLC_ARGON_M, SFLC_ARGON_T, SFLC_ARGON_P); - sflc_log_hex(key, SFLC_CRYPTO_KEYLEN); - printf("Go check the result online\n\n"); - } - - return NULL; -} diff --git a/shufflecake-userland-legacy/test/main.c b/shufflecake-userland-legacy/test/main.c deleted file mode 100644 index 565aa2c..0000000 --- a/shufflecake-userland-legacy/test/main.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - - -#include "crypto/test_aes256ctr.h" -#include "crypto/test_aes256gcm.h" -#include "crypto/test_argon2id.h" -#include "minunit.h" -#include "utils/log.h" - - -/***************************************************** - * PRIVATE FUNCTIONS PROTOTYPES * - *****************************************************/ - -static char *all_tests(); - - -/***************************************************** - * MAIN * - *****************************************************/ - -int main() -{ - char *result = all_tests(); - - if (result != NULL) { - sflc_log_red("\nTEST FAILED: %s", result); - } - else { - sflc_log_green("\nALL TESTS PASSED"); - } - - return result != NULL; -} - - -/***************************************************** - * PRIVATE FUNCTIONS DEFINITIONS * - *****************************************************/ - -static char *all_tests() -{ - sflc_log_yellow("Running crypto tests"); - mu_run_test(test_aes256ctr_encrypt_inplace); - mu_run_test(test_aes256ctr_encrypt_outofplace); - mu_run_test(test_aes256ctr_decrypt_inplace); - mu_run_test(test_aes256ctr_decrypt_outofplace); - mu_run_test(test_aes256gcm_encrypt); - mu_run_test(test_aes256gcm_decrypt_good); - mu_run_test(test_aes256gcm_decrypt_fail); - mu_run_test(test_argon2id); - - return 0; -} - diff --git a/shufflecake-userland-lite/.gitignore b/shufflecake-userland-lite/.gitignore deleted file mode 100644 index ca8a718..0000000 --- a/shufflecake-userland-lite/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -proj_build/ -test_build/ -.proj_deps/ -.test_deps/ -build/ -.cproject -.project -.settings/ -tests -shufflecake -disks/ diff --git a/shufflecake-userland-lite/include/utils/dm.h b/shufflecake-userland-lite/include/utils/dm.h deleted file mode 100644 index 3334852..0000000 --- a/shufflecake-userland-lite/include/utils/dm.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/* - * Interface to the device mapper - */ - -#ifndef _UTILS_DM_H_ -#define _UTILS_DM_H_ - - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include - -#include "utils/sflc.h" - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Create a new Shufflecake virtual device (volume) under /dev/mapper */ -int sflc_dm_create(char * virt_dev_name, uint64_t num_sectors, char * params); -/* Destroy the virtual device under /dev/mapper */ -int sflc_dm_destroy(char * virt_dev_name); - - -#endif /* _UTILS_DM_H_ */ diff --git a/shufflecake-userland-lite/include/utils/file.h b/shufflecake-userland-lite/include/utils/file.h deleted file mode 100644 index fb03078..0000000 --- a/shufflecake-userland-lite/include/utils/file.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_FILE_H_ -#define _UTILS_FILE_H_ - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Malloc's the buffer for the file contents */ -char *sflc_readFile(char *path); - - -#endif /* _UTILS_FILE_H_ */ diff --git a/shufflecake-userland-lite/include/utils/math.h b/shufflecake-userland-lite/include/utils/math.h deleted file mode 100644 index db5e397..0000000 --- a/shufflecake-userland-lite/include/utils/math.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_MATH_H_ -#define _UTILS_MATH_H_ - - -/***************************************************** - * MACROS * - *****************************************************/ - -// Function ceil(a/b) = floor((a+b-1)/b) -#define ceil(a, b) ( ((a)+(b)-1) / (b) ) - - -#endif /* _UTILS_MATH_H_ */ diff --git a/shufflecake-userland-lite/include/utils/string.h b/shufflecake-userland-lite/include/utils/string.h deleted file mode 100644 index ca42c0e..0000000 --- a/shufflecake-userland-lite/include/utils/string.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -#ifndef _UTILS_STRING_H_ -#define _UTILS_STRING_H_ - - -/***************************************************** - * PUBLIC FUNCTIONS PROTOTYPES * - *****************************************************/ - -/* Malloc's the buffer for the hex string */ -char *sflc_toHex(char *buf, size_t len); - -/* Replaces all occurrences of character in-place */ -void sflc_str_replaceAll(char *str, char old, char new); - - -#endif /* _UTILS_STRING_H_ */ diff --git a/shufflecake-userland-lite/src/cli/changepwd.c b/shufflecake-userland-lite/src/cli/changepwd.c deleted file mode 100644 index de37779..0000000 --- a/shufflecake-userland-lite/src/cli/changepwd.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "cli.h" -#include "commands.h" -#include "utils/sflc.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * Change volume password - * - * @return Error code, 0 on success - */ -int sflc_cli_changePwd(char *block_device) -{ // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs open_args; - sflc_cmd_ChangePwdArgs change_pwd_args; - sflc_DmbCell dmb_cell; - char old_pwd[SFLC_BIGBUFSIZE]; - size_t old_pwd_len; - char new_pwd[SFLC_BIGBUFSIZE]; - size_t new_pwd_len; - int err; - - open_args.bdev_path = block_device; - - /* Gather password */ - printf("Enter the password you want to change: "); - err = sflc_safeReadPassphrase(old_pwd, SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Could not read password; error %d", err); - return err; - } - /* You can trust the length of strings input this way */ - old_pwd_len = strlen(old_pwd); - /* Assign fields */ - open_args.pwd = old_pwd; - open_args.pwd_len = old_pwd_len; - - /* Test the password */ - err = sflc_cmd_testPwd(&open_args, &dmb_cell); - if (err) { - sflc_log_error("Could not test password; error %d", err); - return err; - } - - /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { - printf("This password does not unlock any volume.\n"); - return 0; - } - - /* Gather new password (no secure shell) */ - printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); - printf("Choose new password for volume %lu: ", dmb_cell.vol_idx); - err = sflc_safeReadLine(new_pwd, SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Could not read new password; error %d", err); - return err; - } - /* You can trust the length of strings input this way */ - new_pwd_len = strlen(new_pwd); - - /* Assign fields */ - change_pwd_args.bdev_path = block_device; - change_pwd_args.dmb_cell = &dmb_cell; - change_pwd_args.new_pwd = new_pwd; - change_pwd_args.new_pwd_len = new_pwd_len; - - /* Change password */ - err = sflc_cmd_changePwd(&change_pwd_args); - if (err) { - sflc_log_error("Could not change password; error %d", err); - return err; - } - printf("Password changed successfully.\n"); - return err; -} diff --git a/shufflecake-userland-lite/src/cli/testpwd.c b/shufflecake-userland-lite/src/cli/testpwd.c deleted file mode 100644 index 550df25..0000000 --- a/shufflecake-userland-lite/src/cli/testpwd.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "cli.h" -#include "commands.h" -#include "utils/sflc.h" -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* - * Test volume password - * - * @return Error code, 0 on success - */ -int sflc_cli_testPwd(char *block_device) -{ // Requires: block_device is a correct block device path - sflc_cmd_OpenArgs args; - sflc_DmbCell dmb_cell; -// char bdev_path[SFLC_BDEV_PATH_MAX_LEN + 2]; - char pwd[SFLC_BIGBUFSIZE]; - size_t pwd_len; - int err; - - args.bdev_path = block_device; - - /* Gather password */ - printf("Enter the password you want to test: "); - err = sflc_safeReadPassphrase(pwd, SFLC_BIGBUFSIZE); - if (err) { - sflc_log_error("Could not read password; error %d", err); - return err; - } - /* You can trust the length of strings input this way */ - pwd_len = strlen(pwd); - /* Assign them */ - args.pwd = pwd; - args.pwd_len = pwd_len; - - /* Actually perform the command */ - err = sflc_cmd_testPwd(&args, &dmb_cell); - if (err) { - sflc_log_error("Could not test password; error %d", err); - return err; - } - - /* Does this password open any volumes? */ - if (dmb_cell.vol_idx >= SFLC_DEV_MAX_VOLUMES) { - printf("This password does not unlock any volume.\n"); - } else { - printf("This password opens volume number %lu .\n", dmb_cell.vol_idx); - } - - return 0; -} diff --git a/shufflecake-userland-lite/src/commands/test_pwd.c b/shufflecake-userland-lite/src/commands/test_pwd.c deleted file mode 100644 index f794347..0000000 --- a/shufflecake-userland-lite/src/commands/test_pwd.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "commands.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Tests which volume is unlocked by the given password - * - * @param args->bdev_path The underlying block device - * @param pwd The password - * @param pwd_len The password length - * - * @output dmb_cell The unlocked DMB cell - * - * @return Error code, 0 on success - */ -int sflc_cmd_testPwd(sflc_cmd_OpenArgs *args, sflc_DmbCell *dmb_cell) -{ - /* Delegate entirely to the function reading the DMB */ - return sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, dmb_cell); -} diff --git a/shufflecake-userland-lite/src/main.c b/shufflecake-userland-lite/src/main.c deleted file mode 100644 index 737b57e..0000000 --- a/shufflecake-userland-lite/src/main.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - - -/* - * Shufflecake userland tool - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include "cli.h" - - -/***************************************************** - * MAIN * - *****************************************************/ - -int main(int argc, char **argv) -{ - return sflc_cli_dispatch(argc, argv); -} - diff --git a/shufflecake-userland-lite/src/utils/file.c b/shufflecake-userland-lite/src/utils/file.c deleted file mode 100644 index a8e70e2..0000000 --- a/shufflecake-userland-lite/src/utils/file.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/file.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Reads the entire content of a file in a malloc-ed string */ -char *sflc_readFile(char *path) -{ - int filesize; - FILE *fp; - char *content; - - /* Open file */ - fp = fopen(path, "r"); - if (fp == NULL) { - sflc_log_error("Could not open file %s", path); - perror("Reason: "); - goto bad_fopen; - } - - /* Get size (overestimated) */ - fseek(fp, 0L, SEEK_END); - filesize = ftell(fp); - rewind(fp); - - /* Allocate */ - content = malloc(filesize + 1); - if (content == NULL) { - sflc_log_error("Could not malloc %d bytes for file content", filesize); - goto bad_malloc; - } - - /* Read (adjust filesize) */ - filesize = fread(content, 1, filesize, fp); - content[filesize] = '\0'; - - /* Close */ - fclose(fp); - - return content; - - -bad_malloc: - fclose(fp); -bad_fopen: - return NULL; -} diff --git a/shufflecake-userland-lite/src/utils/input.c b/shufflecake-userland-lite/src/utils/input.c deleted file mode 100644 index ffc0b02..0000000 --- a/shufflecake-userland-lite/src/utils/input.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "utils/input.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Reads a line (discarding the newline) from stdin. No buffer overflow */ -int sflc_safeReadLine(char *buf, size_t bufsize) -{ - size_t len; - - /* Read from stdin */ - if (fgets(buf, bufsize, stdin) == NULL) { - sflc_log_error("Could not read from stdin"); - return EBADFD; - } - - /* Discard newline */ - len = strlen(buf); - if (buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - } - - return 0; -} - - -/* Reads a password/passphrase in a secure way (no echo) */ -int sflc_safeReadPassphrase(char *buf, size_t bufsize) -{ - size_t len; - struct termios old, new; - - fflush(stdout); // Ensure stdout is flushed is printed immediately - - // Turn off echoing - tcgetattr(STDIN_FILENO, &old); - new = old; - new.c_lflag &= ~ECHO; - tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); - - /* Read from stdin */ - if (fgets(buf, bufsize, stdin) == NULL) { - // If reading the password failed, ensure echoing is turned back on - tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - sflc_log_error("Could not read from stdin"); - return EBADFD; - } - - // Turn echoing back on - tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); - - /* Discard newline */ - len = strlen(buf); - if (buf[len - 1] == '\n') { - buf[len - 1] = '\0'; - } - - /* Newline on screen */ - printf("\n"); - - return 0; -} - - diff --git a/shufflecake-userland-lite/src/utils/string.c b/shufflecake-userland-lite/src/utils/string.c deleted file mode 100644 index c6cf998..0000000 --- a/shufflecake-userland-lite/src/utils/string.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include - -#include "utils/string.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/* Malloc's the buffer for the hex string */ -char *sflc_toHex(char *buf, size_t len) -{ - unsigned char *u = (unsigned char *) buf; - char *hex; - - /* Allocate buffer */ - hex = malloc((len * 2) + 1); - if (!hex) { - sflc_log_error("Could not allocate buffer for hex string"); - return NULL; - } - - /* To hex */ - int i; - for(i = 0; i < len; i++) { - sprintf(hex + (i * 2), "%02x", u[i]); - } - hex[len*2] = '\0'; - - return hex; -} - - -void sflc_str_replaceAll(char * str, char old, char new) -{ - int i; - for (i = 0; str[i] != '\0'; i++) { - if (str[i] == old) { - str[i] = new; - } - } - - return; -} diff --git a/shufflecake-userland-lite/test/crypto/test_aes256ctr.h b/shufflecake-userland-lite/test/crypto/test_aes256ctr.h deleted file mode 100644 index c1070f8..0000000 --- a/shufflecake-userland-lite/test/crypto/test_aes256ctr.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -// Test vectors taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - -#ifndef _TEST_CRYPTO_AES256CTR_H_ -#define _TEST_CRYPTO_AES256CTR_H_ - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define AES256CTR_TEST_KEY \ -{ \ - 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, \ - 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, \ - 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, \ - 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4 \ -} - -#define AES256CTR_TEST_IV \ -{ \ - 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, \ - 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff \ -} - -#define AES256CTR_TEST_PT \ -{ \ - 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, \ - 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, \ - 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, \ - 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, \ - 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, \ - 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, \ - 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, \ - 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 \ -} - -#define AES256CTR_TEST_CT \ -{ \ - 0x60, 0x1e, 0xc3, 0x13, 0x77, 0x57, 0x89, 0xa5, \ - 0xb7, 0xa7, 0xf5, 0x04, 0xbb, 0xf3, 0xd2, 0x28, \ - 0xf4, 0x43, 0xe3, 0xca, 0x4d, 0x62, 0xb5, 0x9a, \ - 0xca, 0x84, 0xe9, 0x90, 0xca, 0xca, 0xf5, 0xc5, \ - 0x2b, 0x09, 0x30, 0xda, 0xa2, 0x3d, 0xe9, 0x4c, \ - 0xe8, 0x70, 0x17, 0xba, 0x2d, 0x84, 0x98, 0x8d, \ - 0xdf, 0xc9, 0xc5, 0x8d, 0xb6, 0x7a, 0xad, 0xa6, \ - 0x13, 0xc2, 0xdd, 0x08, 0x45, 0x79, 0x41, 0xa6 \ -} - - -/***************************************************** - * PUBLIC FUNCTIONS DECLARATIONS * - *****************************************************/ - -// Exported test cases - -char *test_aes256ctr_encrypt_inplace(); -char *test_aes256ctr_encrypt_outofplace(); -char *test_aes256ctr_decrypt_inplace(); -char *test_aes256ctr_decrypt_outofplace(); - - -#endif /* _TEST_CRYPTO_AES256CTR_H_ */ diff --git a/shufflecake-userland-lite/test/crypto/test_aes256gcm.h b/shufflecake-userland-lite/test/crypto/test_aes256gcm.h deleted file mode 100644 index 9ad445b..0000000 --- a/shufflecake-userland-lite/test/crypto/test_aes256gcm.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -// Test vectors taken from test case 15 at -// https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/gcm-spec.pdf - -#ifndef _TEST_CRYPTO_AES256GCM_H_ -#define _TEST_CRYPTO_AES256GCM_H_ - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - -#define AES256GCM_TEST_KEY \ -{ \ - 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, \ - 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08, \ - 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, \ - 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 \ -} - -#define AES256GCM_TEST_IV \ -{ \ - 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, \ - 0xde, 0xca, 0xf8, 0x88 \ -} - -#define AES256GCM_TEST_PT \ -{ \ - 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, \ - 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, \ - 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, \ - 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, \ - 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, \ - 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, \ - 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, \ - 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 \ -} - -#define AES256GCM_TEST_CT \ -{ \ - 0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07, \ - 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d, \ - 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9, \ - 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa, \ - 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d, \ - 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38, \ - 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a, \ - 0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad, \ -} - -#define AES256GCM_TEST_TAG \ -{ \ - 0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd, \ - 0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3, 0xcc, 0x6c, \ -} - - -/***************************************************** - * PUBLIC FUNCTIONS DECLARATIONS * - *****************************************************/ - -// Exported test cases - -char *test_aes256gcm_encrypt(); -char *test_aes256gcm_decrypt_good(); -char *test_aes256gcm_decrypt_fail(); - - -#endif /* _TEST_CRYPTO_AES256GCM_H_ */ diff --git a/shufflecake-userland-lite/test/crypto/test_argon2id.h b/shufflecake-userland-lite/test/crypto/test_argon2id.h deleted file mode 100644 index 12bd2c0..0000000 --- a/shufflecake-userland-lite/test/crypto/test_argon2id.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - - -#ifndef _TEST_CRYPTO_ARGON2ID_H_ -#define _TEST_CRYPTO_ARGON2ID_H_ - - -/***************************************************** - * CONSTANTS * - *****************************************************/ - - -/***************************************************** - * PUBLIC FUNCTIONS DECLARATIONS * - *****************************************************/ - -// Exported test cases - -char *test_argon2id(); - - -#endif /* _TEST_CRYPTO_ARGON2ID_H_ */ diff --git a/shufflecake-userland-lite/test/minunit.h b/shufflecake-userland-lite/test/minunit.h deleted file mode 100644 index 4181aa1..0000000 --- a/shufflecake-userland-lite/test/minunit.h +++ /dev/null @@ -1,10 +0,0 @@ -// MinUnit -- a minimal unit testing framework for C. Slightly modified. -// https://jera.com/techinfo/jtns/jtn002 - -#ifndef _MINUNIT_H_ -#define _MINUNIT_H_ - -#define mu_assert(message, test) do { if (!(test)) return message; } while (0) -#define mu_run_test(test) do { char *message = test(); if (message) return message; } while (0) - -#endif // _MINUNIT_H_ diff --git a/shufflecake-userland-legacy/.gitignore b/shufflecake-userland/.gitignore similarity index 100% rename from shufflecake-userland-legacy/.gitignore rename to shufflecake-userland/.gitignore diff --git a/shufflecake-userland-lite/Makefile b/shufflecake-userland/Makefile similarity index 100% rename from shufflecake-userland-lite/Makefile rename to shufflecake-userland/Makefile diff --git a/shufflecake-userland-lite/Makefile.sources b/shufflecake-userland/Makefile.sources similarity index 100% rename from shufflecake-userland-lite/Makefile.sources rename to shufflecake-userland/Makefile.sources diff --git a/shufflecake-userland-lite/include/cli.h b/shufflecake-userland/include/cli.h similarity index 100% rename from shufflecake-userland-lite/include/cli.h rename to shufflecake-userland/include/cli.h diff --git a/shufflecake-userland-lite/include/commands.h b/shufflecake-userland/include/commands.h similarity index 100% rename from shufflecake-userland-lite/include/commands.h rename to shufflecake-userland/include/commands.h diff --git a/shufflecake-userland-lite/include/header.h b/shufflecake-userland/include/header.h similarity index 100% rename from shufflecake-userland-lite/include/header.h rename to shufflecake-userland/include/header.h diff --git a/shufflecake-userland-lite/include/operations.h b/shufflecake-userland/include/operations.h similarity index 100% rename from shufflecake-userland-lite/include/operations.h rename to shufflecake-userland/include/operations.h diff --git a/shufflecake-userland-lite/include/utils/crypto.h b/shufflecake-userland/include/utils/crypto.h similarity index 100% rename from shufflecake-userland-lite/include/utils/crypto.h rename to shufflecake-userland/include/utils/crypto.h diff --git a/shufflecake-userland-lite/include/utils/disk.h b/shufflecake-userland/include/utils/disk.h similarity index 100% rename from shufflecake-userland-lite/include/utils/disk.h rename to shufflecake-userland/include/utils/disk.h diff --git a/shufflecake-userland-legacy/include/utils/dm.h b/shufflecake-userland/include/utils/dm.h similarity index 100% rename from shufflecake-userland-legacy/include/utils/dm.h rename to shufflecake-userland/include/utils/dm.h diff --git a/shufflecake-userland-legacy/include/utils/file.h b/shufflecake-userland/include/utils/file.h similarity index 100% rename from shufflecake-userland-legacy/include/utils/file.h rename to shufflecake-userland/include/utils/file.h diff --git a/shufflecake-userland-lite/include/utils/input.h b/shufflecake-userland/include/utils/input.h similarity index 100% rename from shufflecake-userland-lite/include/utils/input.h rename to shufflecake-userland/include/utils/input.h diff --git a/shufflecake-userland-lite/include/utils/log.h b/shufflecake-userland/include/utils/log.h similarity index 100% rename from shufflecake-userland-lite/include/utils/log.h rename to shufflecake-userland/include/utils/log.h diff --git a/shufflecake-userland-legacy/include/utils/math.h b/shufflecake-userland/include/utils/math.h similarity index 100% rename from shufflecake-userland-legacy/include/utils/math.h rename to shufflecake-userland/include/utils/math.h diff --git a/shufflecake-userland-lite/include/utils/sflc.h b/shufflecake-userland/include/utils/sflc.h similarity index 100% rename from shufflecake-userland-lite/include/utils/sflc.h rename to shufflecake-userland/include/utils/sflc.h diff --git a/shufflecake-userland-legacy/include/utils/string.h b/shufflecake-userland/include/utils/string.h similarity index 100% rename from shufflecake-userland-legacy/include/utils/string.h rename to shufflecake-userland/include/utils/string.h diff --git a/shufflecake-userland-legacy/src/cli/changepwd.c b/shufflecake-userland/src/cli/changepwd.c similarity index 100% rename from shufflecake-userland-legacy/src/cli/changepwd.c rename to shufflecake-userland/src/cli/changepwd.c diff --git a/shufflecake-userland-lite/src/cli/close.c b/shufflecake-userland/src/cli/close.c similarity index 100% rename from shufflecake-userland-lite/src/cli/close.c rename to shufflecake-userland/src/cli/close.c diff --git a/shufflecake-userland-lite/src/cli/dispatch.c b/shufflecake-userland/src/cli/dispatch.c similarity index 100% rename from shufflecake-userland-lite/src/cli/dispatch.c rename to shufflecake-userland/src/cli/dispatch.c diff --git a/shufflecake-userland-lite/src/cli/init.c b/shufflecake-userland/src/cli/init.c similarity index 100% rename from shufflecake-userland-lite/src/cli/init.c rename to shufflecake-userland/src/cli/init.c diff --git a/shufflecake-userland-lite/src/cli/open.c b/shufflecake-userland/src/cli/open.c similarity index 100% rename from shufflecake-userland-lite/src/cli/open.c rename to shufflecake-userland/src/cli/open.c diff --git a/shufflecake-userland-legacy/src/cli/testpwd.c b/shufflecake-userland/src/cli/testpwd.c similarity index 100% rename from shufflecake-userland-legacy/src/cli/testpwd.c rename to shufflecake-userland/src/cli/testpwd.c diff --git a/shufflecake-userland-lite/src/commands/change_pwd.c b/shufflecake-userland/src/commands/change_pwd.c similarity index 100% rename from shufflecake-userland-lite/src/commands/change_pwd.c rename to shufflecake-userland/src/commands/change_pwd.c diff --git a/shufflecake-userland-lite/src/commands/close.c b/shufflecake-userland/src/commands/close.c similarity index 100% rename from shufflecake-userland-lite/src/commands/close.c rename to shufflecake-userland/src/commands/close.c diff --git a/shufflecake-userland-lite/src/commands/init_legacy.c b/shufflecake-userland/src/commands/init_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/commands/init_legacy.c rename to shufflecake-userland/src/commands/init_legacy.c diff --git a/shufflecake-userland-lite/src/commands/init_lite.c b/shufflecake-userland/src/commands/init_lite.c similarity index 100% rename from shufflecake-userland-lite/src/commands/init_lite.c rename to shufflecake-userland/src/commands/init_lite.c diff --git a/shufflecake-userland-lite/src/commands/open_legacy.c b/shufflecake-userland/src/commands/open_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/commands/open_legacy.c rename to shufflecake-userland/src/commands/open_legacy.c diff --git a/shufflecake-userland-lite/src/commands/open_lite.c b/shufflecake-userland/src/commands/open_lite.c similarity index 100% rename from shufflecake-userland-lite/src/commands/open_lite.c rename to shufflecake-userland/src/commands/open_lite.c diff --git a/shufflecake-userland-legacy/src/commands/test_pwd.c b/shufflecake-userland/src/commands/test_pwd.c similarity index 100% rename from shufflecake-userland-legacy/src/commands/test_pwd.c rename to shufflecake-userland/src/commands/test_pwd.c diff --git a/shufflecake-userland-lite/src/header/device_master_block.c b/shufflecake-userland/src/header/device_master_block.c similarity index 100% rename from shufflecake-userland-lite/src/header/device_master_block.c rename to shufflecake-userland/src/header/device_master_block.c diff --git a/shufflecake-userland-lite/src/header/position_map_legacy.c b/shufflecake-userland/src/header/position_map_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/header/position_map_legacy.c rename to shufflecake-userland/src/header/position_map_legacy.c diff --git a/shufflecake-userland-lite/src/header/position_map_lite.c b/shufflecake-userland/src/header/position_map_lite.c similarity index 100% rename from shufflecake-userland-lite/src/header/position_map_lite.c rename to shufflecake-userland/src/header/position_map_lite.c diff --git a/shufflecake-userland-lite/src/header/volume_master_block_legacy.c b/shufflecake-userland/src/header/volume_master_block_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/header/volume_master_block_legacy.c rename to shufflecake-userland/src/header/volume_master_block_legacy.c diff --git a/shufflecake-userland-lite/src/header/volume_master_block_lite.c b/shufflecake-userland/src/header/volume_master_block_lite.c similarity index 100% rename from shufflecake-userland-lite/src/header/volume_master_block_lite.c rename to shufflecake-userland/src/header/volume_master_block_lite.c diff --git a/shufflecake-userland-legacy/src/main.c b/shufflecake-userland/src/main.c similarity index 100% rename from shufflecake-userland-legacy/src/main.c rename to shufflecake-userland/src/main.c diff --git a/shufflecake-userland-lite/src/operations/devmapper_legacy.c b/shufflecake-userland/src/operations/devmapper_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/operations/devmapper_legacy.c rename to shufflecake-userland/src/operations/devmapper_legacy.c diff --git a/shufflecake-userland-lite/src/operations/devmapper_lite.c b/shufflecake-userland/src/operations/devmapper_lite.c similarity index 100% rename from shufflecake-userland-lite/src/operations/devmapper_lite.c rename to shufflecake-userland/src/operations/devmapper_lite.c diff --git a/shufflecake-userland-lite/src/operations/dmb.c b/shufflecake-userland/src/operations/dmb.c similarity index 100% rename from shufflecake-userland-lite/src/operations/dmb.c rename to shufflecake-userland/src/operations/dmb.c diff --git a/shufflecake-userland-lite/src/operations/volume_header_legacy.c b/shufflecake-userland/src/operations/volume_header_legacy.c similarity index 100% rename from shufflecake-userland-lite/src/operations/volume_header_legacy.c rename to shufflecake-userland/src/operations/volume_header_legacy.c diff --git a/shufflecake-userland-lite/src/operations/volume_header_lite.c b/shufflecake-userland/src/operations/volume_header_lite.c similarity index 100% rename from shufflecake-userland-lite/src/operations/volume_header_lite.c rename to shufflecake-userland/src/operations/volume_header_lite.c diff --git a/shufflecake-userland-lite/src/utils/crypto.c b/shufflecake-userland/src/utils/crypto.c similarity index 100% rename from shufflecake-userland-lite/src/utils/crypto.c rename to shufflecake-userland/src/utils/crypto.c diff --git a/shufflecake-userland-lite/src/utils/disk.c b/shufflecake-userland/src/utils/disk.c similarity index 100% rename from shufflecake-userland-lite/src/utils/disk.c rename to shufflecake-userland/src/utils/disk.c diff --git a/shufflecake-userland-lite/src/utils/dm.c b/shufflecake-userland/src/utils/dm.c similarity index 100% rename from shufflecake-userland-lite/src/utils/dm.c rename to shufflecake-userland/src/utils/dm.c diff --git a/shufflecake-userland-legacy/src/utils/file.c b/shufflecake-userland/src/utils/file.c similarity index 100% rename from shufflecake-userland-legacy/src/utils/file.c rename to shufflecake-userland/src/utils/file.c diff --git a/shufflecake-userland-legacy/src/utils/input.c b/shufflecake-userland/src/utils/input.c similarity index 100% rename from shufflecake-userland-legacy/src/utils/input.c rename to shufflecake-userland/src/utils/input.c diff --git a/shufflecake-userland-legacy/src/utils/string.c b/shufflecake-userland/src/utils/string.c similarity index 100% rename from shufflecake-userland-legacy/src/utils/string.c rename to shufflecake-userland/src/utils/string.c diff --git a/shufflecake-userland-lite/test/crypto/test_aes256ctr.c b/shufflecake-userland/test/crypto/test_aes256ctr.c similarity index 100% rename from shufflecake-userland-lite/test/crypto/test_aes256ctr.c rename to shufflecake-userland/test/crypto/test_aes256ctr.c diff --git a/shufflecake-userland-legacy/test/crypto/test_aes256ctr.h b/shufflecake-userland/test/crypto/test_aes256ctr.h similarity index 100% rename from shufflecake-userland-legacy/test/crypto/test_aes256ctr.h rename to shufflecake-userland/test/crypto/test_aes256ctr.h diff --git a/shufflecake-userland-lite/test/crypto/test_aes256gcm.c b/shufflecake-userland/test/crypto/test_aes256gcm.c similarity index 100% rename from shufflecake-userland-lite/test/crypto/test_aes256gcm.c rename to shufflecake-userland/test/crypto/test_aes256gcm.c diff --git a/shufflecake-userland-legacy/test/crypto/test_aes256gcm.h b/shufflecake-userland/test/crypto/test_aes256gcm.h similarity index 100% rename from shufflecake-userland-legacy/test/crypto/test_aes256gcm.h rename to shufflecake-userland/test/crypto/test_aes256gcm.h diff --git a/shufflecake-userland-lite/test/crypto/test_argon2id.c b/shufflecake-userland/test/crypto/test_argon2id.c similarity index 100% rename from shufflecake-userland-lite/test/crypto/test_argon2id.c rename to shufflecake-userland/test/crypto/test_argon2id.c diff --git a/shufflecake-userland-legacy/test/crypto/test_argon2id.h b/shufflecake-userland/test/crypto/test_argon2id.h similarity index 100% rename from shufflecake-userland-legacy/test/crypto/test_argon2id.h rename to shufflecake-userland/test/crypto/test_argon2id.h diff --git a/shufflecake-userland-lite/test/main.c b/shufflecake-userland/test/main.c similarity index 100% rename from shufflecake-userland-lite/test/main.c rename to shufflecake-userland/test/main.c diff --git a/shufflecake-userland-legacy/test/minunit.h b/shufflecake-userland/test/minunit.h similarity index 100% rename from shufflecake-userland-legacy/test/minunit.h rename to shufflecake-userland/test/minunit.h From f0e48edb8da28047cb6dea6f9ec264290faab278 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 27 Aug 2024 20:14:32 +0200 Subject: [PATCH 69/98] fix Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6d3666a..4c88d2f 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ default: make -C dm-sflc - cp dm-sflc/bin/dm-sflc.ko ./dm-sflc.ko + cp dm-sflc/bin/dm_sflc.ko ./dm_sflc.ko make -C shufflecake-userland cp shufflecake-userland/bin/proj_build/shufflecake ./shufflecake @@ -48,7 +48,7 @@ clean: make -C dm-sflc clean make -C shufflecake-userland clean rm ./shufflecake - rm ./dm-sflc.ko + rm ./dm_sflc.ko From d84ebfaebc75e9dae05076632df1386cae8e418e Mon Sep 17 00:00:00 2001 From: = Date: Thu, 29 Aug 2024 20:02:24 +0200 Subject: [PATCH 70/98] Temporary build solution --- dm-sflc/.Kbuild | 53 +++++++++++++++++++ dm-sflc/.gitignore | 6 ++- dm-sflc/Makefile | 28 +++++----- dm-sflc/{ => bin}/Kbuild | 1 + dm-sflc/bin/dev_vol.c | 1 + dm-sflc/bin/lite/crypto.c | 1 + dm-sflc/bin/lite/device.c | 1 + dm-sflc/bin/lite/posmap.c | 1 + dm-sflc/bin/lite/read.c | 1 + dm-sflc/bin/lite/sflc_lite.c | 1 + dm-sflc/bin/lite/sflc_lite.h | 1 + dm-sflc/bin/lite/sflite_constants.h | 1 + dm-sflc/bin/lite/sysfs.c | 1 + dm-sflc/bin/lite/volume.c | 1 + dm-sflc/bin/lite/write.c | 1 + dm-sflc/bin/old/crypto/rand/rand.c | 1 + dm-sflc/bin/old/crypto/rand/rand.h | 1 + dm-sflc/bin/old/crypto/symkey/skreq_pool.c | 1 + dm-sflc/bin/old/crypto/symkey/skreq_pool.h | 1 + dm-sflc/bin/old/crypto/symkey/symkey.c | 1 + dm-sflc/bin/old/crypto/symkey/symkey.h | 1 + dm-sflc/bin/old/device/device.c | 1 + dm-sflc/bin/old/device/device.h | 1 + dm-sflc/bin/old/device/iv.c | 1 + dm-sflc/bin/old/device/rawio.c | 1 + dm-sflc/bin/old/device/rmap.c | 1 + dm-sflc/bin/old/device/volumes.c | 1 + dm-sflc/bin/old/log/log.h | 1 + dm-sflc/bin/old/sflc_old.c | 1 + dm-sflc/bin/old/sflc_old.h | 1 + dm-sflc/bin/old/sysfs.c | 1 + dm-sflc/bin/old/target.c | 1 + dm-sflc/bin/old/utils/bio.c | 1 + dm-sflc/bin/old/utils/bio.h | 1 + dm-sflc/bin/old/utils/pools.c | 1 + dm-sflc/bin/old/utils/pools.h | 1 + dm-sflc/bin/old/utils/string.c | 1 + dm-sflc/bin/old/utils/string.h | 1 + dm-sflc/bin/old/utils/vector.c | 1 + dm-sflc/bin/old/utils/vector.h | 1 + dm-sflc/bin/old/utils/workqueues.c | 1 + dm-sflc/bin/old/utils/workqueues.h | 1 + dm-sflc/bin/old/volume/fmap.c | 1 + dm-sflc/bin/old/volume/io.c | 1 + dm-sflc/bin/old/volume/read.c | 1 + dm-sflc/bin/old/volume/volume.c | 1 + dm-sflc/bin/old/volume/volume.h | 1 + dm-sflc/bin/old/volume/write.c | 1 + dm-sflc/bin/sflc.c | 1 + dm-sflc/bin/sflc.h | 1 + dm-sflc/bin/sflc_constants.h | 1 + dm-sflc/bin/sysfs.c | 1 + dm-sflc/{ => src}/dev_vol.c | 0 dm-sflc/{ => src}/lite/crypto.c | 0 dm-sflc/{ => src}/lite/device.c | 0 dm-sflc/{ => src}/lite/posmap.c | 0 dm-sflc/{ => src}/lite/read.c | 0 dm-sflc/{ => src}/lite/sflc_lite.c | 0 dm-sflc/{ => src}/lite/sflc_lite.h | 0 dm-sflc/{ => src}/lite/sflite_constants.h | 0 dm-sflc/{ => src}/lite/sysfs.c | 0 dm-sflc/{ => src}/lite/volume.c | 0 dm-sflc/{ => src}/lite/write.c | 0 dm-sflc/{ => src}/old/crypto/rand/rand.c | 0 dm-sflc/{ => src}/old/crypto/rand/rand.h | 0 .../{ => src}/old/crypto/symkey/skreq_pool.c | 0 .../{ => src}/old/crypto/symkey/skreq_pool.h | 0 dm-sflc/{ => src}/old/crypto/symkey/symkey.c | 0 dm-sflc/{ => src}/old/crypto/symkey/symkey.h | 0 dm-sflc/{ => src}/old/device/device.c | 0 dm-sflc/{ => src}/old/device/device.h | 0 dm-sflc/{ => src}/old/device/iv.c | 0 dm-sflc/{ => src}/old/device/rawio.c | 0 dm-sflc/{ => src}/old/device/rmap.c | 0 dm-sflc/{ => src}/old/device/volumes.c | 0 dm-sflc/{ => src}/old/log/log.h | 0 dm-sflc/{ => src}/old/sflc_old.c | 0 dm-sflc/{ => src}/old/sflc_old.h | 0 dm-sflc/{ => src}/old/sysfs.c | 0 dm-sflc/{ => src}/old/target.c | 0 dm-sflc/{ => src}/old/utils/bio.c | 0 dm-sflc/{ => src}/old/utils/bio.h | 0 dm-sflc/{ => src}/old/utils/pools.c | 0 dm-sflc/{ => src}/old/utils/pools.h | 0 dm-sflc/{ => src}/old/utils/string.c | 0 dm-sflc/{ => src}/old/utils/string.h | 0 dm-sflc/{ => src}/old/utils/vector.c | 0 dm-sflc/{ => src}/old/utils/vector.h | 0 dm-sflc/{ => src}/old/utils/workqueues.c | 0 dm-sflc/{ => src}/old/utils/workqueues.h | 0 dm-sflc/{ => src}/old/volume/fmap.c | 0 dm-sflc/{ => src}/old/volume/io.c | 0 dm-sflc/{ => src}/old/volume/read.c | 0 dm-sflc/{ => src}/old/volume/volume.c | 0 dm-sflc/{ => src}/old/volume/volume.h | 0 dm-sflc/{ => src}/old/volume/write.c | 0 dm-sflc/{ => src}/sflc.c | 0 dm-sflc/{ => src}/sflc.h | 0 dm-sflc/{ => src}/sflc_constants.h | 0 dm-sflc/{ => src}/sysfs.c | 0 100 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 dm-sflc/.Kbuild rename dm-sflc/{ => bin}/Kbuild (99%) create mode 120000 dm-sflc/bin/dev_vol.c create mode 120000 dm-sflc/bin/lite/crypto.c create mode 120000 dm-sflc/bin/lite/device.c create mode 120000 dm-sflc/bin/lite/posmap.c create mode 120000 dm-sflc/bin/lite/read.c create mode 120000 dm-sflc/bin/lite/sflc_lite.c create mode 120000 dm-sflc/bin/lite/sflc_lite.h create mode 120000 dm-sflc/bin/lite/sflite_constants.h create mode 120000 dm-sflc/bin/lite/sysfs.c create mode 120000 dm-sflc/bin/lite/volume.c create mode 120000 dm-sflc/bin/lite/write.c create mode 120000 dm-sflc/bin/old/crypto/rand/rand.c create mode 120000 dm-sflc/bin/old/crypto/rand/rand.h create mode 120000 dm-sflc/bin/old/crypto/symkey/skreq_pool.c create mode 120000 dm-sflc/bin/old/crypto/symkey/skreq_pool.h create mode 120000 dm-sflc/bin/old/crypto/symkey/symkey.c create mode 120000 dm-sflc/bin/old/crypto/symkey/symkey.h create mode 120000 dm-sflc/bin/old/device/device.c create mode 120000 dm-sflc/bin/old/device/device.h create mode 120000 dm-sflc/bin/old/device/iv.c create mode 120000 dm-sflc/bin/old/device/rawio.c create mode 120000 dm-sflc/bin/old/device/rmap.c create mode 120000 dm-sflc/bin/old/device/volumes.c create mode 120000 dm-sflc/bin/old/log/log.h create mode 120000 dm-sflc/bin/old/sflc_old.c create mode 120000 dm-sflc/bin/old/sflc_old.h create mode 120000 dm-sflc/bin/old/sysfs.c create mode 120000 dm-sflc/bin/old/target.c create mode 120000 dm-sflc/bin/old/utils/bio.c create mode 120000 dm-sflc/bin/old/utils/bio.h create mode 120000 dm-sflc/bin/old/utils/pools.c create mode 120000 dm-sflc/bin/old/utils/pools.h create mode 120000 dm-sflc/bin/old/utils/string.c create mode 120000 dm-sflc/bin/old/utils/string.h create mode 120000 dm-sflc/bin/old/utils/vector.c create mode 120000 dm-sflc/bin/old/utils/vector.h create mode 120000 dm-sflc/bin/old/utils/workqueues.c create mode 120000 dm-sflc/bin/old/utils/workqueues.h create mode 120000 dm-sflc/bin/old/volume/fmap.c create mode 120000 dm-sflc/bin/old/volume/io.c create mode 120000 dm-sflc/bin/old/volume/read.c create mode 120000 dm-sflc/bin/old/volume/volume.c create mode 120000 dm-sflc/bin/old/volume/volume.h create mode 120000 dm-sflc/bin/old/volume/write.c create mode 120000 dm-sflc/bin/sflc.c create mode 120000 dm-sflc/bin/sflc.h create mode 120000 dm-sflc/bin/sflc_constants.h create mode 120000 dm-sflc/bin/sysfs.c rename dm-sflc/{ => src}/dev_vol.c (100%) rename dm-sflc/{ => src}/lite/crypto.c (100%) rename dm-sflc/{ => src}/lite/device.c (100%) rename dm-sflc/{ => src}/lite/posmap.c (100%) rename dm-sflc/{ => src}/lite/read.c (100%) rename dm-sflc/{ => src}/lite/sflc_lite.c (100%) rename dm-sflc/{ => src}/lite/sflc_lite.h (100%) rename dm-sflc/{ => src}/lite/sflite_constants.h (100%) rename dm-sflc/{ => src}/lite/sysfs.c (100%) rename dm-sflc/{ => src}/lite/volume.c (100%) rename dm-sflc/{ => src}/lite/write.c (100%) rename dm-sflc/{ => src}/old/crypto/rand/rand.c (100%) rename dm-sflc/{ => src}/old/crypto/rand/rand.h (100%) rename dm-sflc/{ => src}/old/crypto/symkey/skreq_pool.c (100%) rename dm-sflc/{ => src}/old/crypto/symkey/skreq_pool.h (100%) rename dm-sflc/{ => src}/old/crypto/symkey/symkey.c (100%) rename dm-sflc/{ => src}/old/crypto/symkey/symkey.h (100%) rename dm-sflc/{ => src}/old/device/device.c (100%) rename dm-sflc/{ => src}/old/device/device.h (100%) rename dm-sflc/{ => src}/old/device/iv.c (100%) rename dm-sflc/{ => src}/old/device/rawio.c (100%) rename dm-sflc/{ => src}/old/device/rmap.c (100%) rename dm-sflc/{ => src}/old/device/volumes.c (100%) rename dm-sflc/{ => src}/old/log/log.h (100%) rename dm-sflc/{ => src}/old/sflc_old.c (100%) rename dm-sflc/{ => src}/old/sflc_old.h (100%) rename dm-sflc/{ => src}/old/sysfs.c (100%) rename dm-sflc/{ => src}/old/target.c (100%) rename dm-sflc/{ => src}/old/utils/bio.c (100%) rename dm-sflc/{ => src}/old/utils/bio.h (100%) rename dm-sflc/{ => src}/old/utils/pools.c (100%) rename dm-sflc/{ => src}/old/utils/pools.h (100%) rename dm-sflc/{ => src}/old/utils/string.c (100%) rename dm-sflc/{ => src}/old/utils/string.h (100%) rename dm-sflc/{ => src}/old/utils/vector.c (100%) rename dm-sflc/{ => src}/old/utils/vector.h (100%) rename dm-sflc/{ => src}/old/utils/workqueues.c (100%) rename dm-sflc/{ => src}/old/utils/workqueues.h (100%) rename dm-sflc/{ => src}/old/volume/fmap.c (100%) rename dm-sflc/{ => src}/old/volume/io.c (100%) rename dm-sflc/{ => src}/old/volume/read.c (100%) rename dm-sflc/{ => src}/old/volume/volume.c (100%) rename dm-sflc/{ => src}/old/volume/volume.h (100%) rename dm-sflc/{ => src}/old/volume/write.c (100%) rename dm-sflc/{ => src}/sflc.c (100%) rename dm-sflc/{ => src}/sflc.h (100%) rename dm-sflc/{ => src}/sflc_constants.h (100%) rename dm-sflc/{ => src}/sysfs.c (100%) diff --git a/dm-sflc/.Kbuild b/dm-sflc/.Kbuild new file mode 100644 index 0000000..89a38fe --- /dev/null +++ b/dm-sflc/.Kbuild @@ -0,0 +1,53 @@ + # + # Copyright The Shufflecake Project Authors (2022) + # Copyright The Shufflecake Project Contributors (2022) + # Copyright Contributors to the The Shufflecake Project. + # + # See the AUTHORS file at the top-level directory of this distribution and at + # + # + # This file is part of the program shufflecake-c, which is part of the + # Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + # layer for Linux. See . + # + # This program is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the Free + # Software Foundation, either version 2 of the License, or (at your option) + # any later version. This program 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 General + # Public License for more details. You should have received a copy of the + # GNU General Public License along with this program. + # If not, see . + # + +MODULE_NAME := dm_sflc +obj-m := $(MODULE_NAME).o + + +OBJ_LIST := sflc.o dev_vol.o sysfs.o + +OBJ_LIST += old/sflc_old.o old/target.o old/sysfs.o +OBJ_LIST += old/device/device.o old/device/volumes.o old/device/rawio.o old/device/rmap.o old/device/iv.o +OBJ_LIST += old/volume/volume.o old/volume/io.o old/volume/read.o old/volume/write.o old/volume/fmap.o +OBJ_LIST += old/utils/string.o old/utils/bio.o old/utils/pools.o old/utils/workqueues.o old/utils/vector.o +OBJ_LIST += old/crypto/rand/rand.o +OBJ_LIST += old/crypto/symkey/symkey.o old/crypto/symkey/skreq_pool.o + +OBJ_LIST += lite/sflc_lite.o lite/sysfs.o +OBJ_LIST += lite/device.o lite/volume.o +OBJ_LIST += lite/posmap.o lite/read.o lite/write.o lite/crypto.o + +$(MODULE_NAME)-y += $(OBJ_LIST) + + +# Normal CC flags +ccflags-y := -O2 +ccflags-y += -I$(src) +ccflags-y += -Wall -Wno-declaration-after-statement + +# Debug CC flags +ccflags-$(CONFIG_SFLC_DEBUG) += -DDEBUG +ccflags-$(CONFIG_SFLC_DEBUG) += -Og -g +ccflags-$(CONFIG_SFLC_DEBUG) += -fsanitize=kernel-address -fno-omit-frame-pointer + diff --git a/dm-sflc/.gitignore b/dm-sflc/.gitignore index ee31866..1ee0110 100644 --- a/dm-sflc/.gitignore +++ b/dm-sflc/.gitignore @@ -1,4 +1,8 @@ .project .cproject .settings/ -bin/ + +!bin/ +*.o +*.symvers +*.ko diff --git a/dm-sflc/Makefile b/dm-sflc/Makefile index b75f428..45a4f1e 100644 --- a/dm-sflc/Makefile +++ b/dm-sflc/Makefile @@ -22,25 +22,27 @@ # KERNEL_DIR = /lib/modules/$(shell uname -r)/build -SRC_DIR = $(shell pwd) -BUILD_DIR = $(shell pwd)/bin -BUILD_DIR_MAKEFILE = $(BUILD_DIR)/Makefile +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -default: $(BUILD_DIR_MAKEFILE) - make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) modules - -$(BUILD_DIR_MAKEFILE): $(BUILD_DIR) - echo "# This Makefile is here because of Kbuild" > $@ - -$(BUILD_DIR): - mkdir -p $@ +default: + make -C $(KERNEL_DIR) M=$(ROOT_DIR)/bin CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) modules debug: CONFIG_SFLC_DEBUG=y debug: default install: - make -C $(KERNEL_DIR) M=$(BUILD_DIR) src=$(SRC_DIR) modules_install + make -C $(KERNEL_DIR) M=$(BUILD_DIR) CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) modules_install clean: - rm -rf $(BUILD_DIR) + make -C $(KERNEL_DIR) M=$(BUILD_DIR) CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) clean + +# Reserved +ORIGINALS = $(shell find src/ -type f) +SYMLINKS = $(patsubst src/%, bin/%, $(ORIGINALS)) + +symlinks: $(SYMLINKS) + +bin/%: src/% + @mkdir -p "$(@D)" + ln -s $(shell realpath -m --relative-to=$(@D) $<) $@ diff --git a/dm-sflc/Kbuild b/dm-sflc/bin/Kbuild similarity index 99% rename from dm-sflc/Kbuild rename to dm-sflc/bin/Kbuild index 8b39934..89a38fe 100644 --- a/dm-sflc/Kbuild +++ b/dm-sflc/bin/Kbuild @@ -50,3 +50,4 @@ ccflags-y += -Wall -Wno-declaration-after-statement ccflags-$(CONFIG_SFLC_DEBUG) += -DDEBUG ccflags-$(CONFIG_SFLC_DEBUG) += -Og -g ccflags-$(CONFIG_SFLC_DEBUG) += -fsanitize=kernel-address -fno-omit-frame-pointer + diff --git a/dm-sflc/bin/dev_vol.c b/dm-sflc/bin/dev_vol.c new file mode 120000 index 0000000..a35e5d5 --- /dev/null +++ b/dm-sflc/bin/dev_vol.c @@ -0,0 +1 @@ +../src/dev_vol.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/crypto.c b/dm-sflc/bin/lite/crypto.c new file mode 120000 index 0000000..1f775dc --- /dev/null +++ b/dm-sflc/bin/lite/crypto.c @@ -0,0 +1 @@ +../../src/lite/crypto.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/device.c b/dm-sflc/bin/lite/device.c new file mode 120000 index 0000000..d2bcea5 --- /dev/null +++ b/dm-sflc/bin/lite/device.c @@ -0,0 +1 @@ +../../src/lite/device.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/posmap.c b/dm-sflc/bin/lite/posmap.c new file mode 120000 index 0000000..e60d53b --- /dev/null +++ b/dm-sflc/bin/lite/posmap.c @@ -0,0 +1 @@ +../../src/lite/posmap.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/read.c b/dm-sflc/bin/lite/read.c new file mode 120000 index 0000000..db9620e --- /dev/null +++ b/dm-sflc/bin/lite/read.c @@ -0,0 +1 @@ +../../src/lite/read.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/sflc_lite.c b/dm-sflc/bin/lite/sflc_lite.c new file mode 120000 index 0000000..1f2a01a --- /dev/null +++ b/dm-sflc/bin/lite/sflc_lite.c @@ -0,0 +1 @@ +../../src/lite/sflc_lite.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/sflc_lite.h b/dm-sflc/bin/lite/sflc_lite.h new file mode 120000 index 0000000..23798f5 --- /dev/null +++ b/dm-sflc/bin/lite/sflc_lite.h @@ -0,0 +1 @@ +../../src/lite/sflc_lite.h \ No newline at end of file diff --git a/dm-sflc/bin/lite/sflite_constants.h b/dm-sflc/bin/lite/sflite_constants.h new file mode 120000 index 0000000..3900993 --- /dev/null +++ b/dm-sflc/bin/lite/sflite_constants.h @@ -0,0 +1 @@ +../../src/lite/sflite_constants.h \ No newline at end of file diff --git a/dm-sflc/bin/lite/sysfs.c b/dm-sflc/bin/lite/sysfs.c new file mode 120000 index 0000000..4fd0f32 --- /dev/null +++ b/dm-sflc/bin/lite/sysfs.c @@ -0,0 +1 @@ +../../src/lite/sysfs.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/volume.c b/dm-sflc/bin/lite/volume.c new file mode 120000 index 0000000..2242793 --- /dev/null +++ b/dm-sflc/bin/lite/volume.c @@ -0,0 +1 @@ +../../src/lite/volume.c \ No newline at end of file diff --git a/dm-sflc/bin/lite/write.c b/dm-sflc/bin/lite/write.c new file mode 120000 index 0000000..665a955 --- /dev/null +++ b/dm-sflc/bin/lite/write.c @@ -0,0 +1 @@ +../../src/lite/write.c \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/rand/rand.c b/dm-sflc/bin/old/crypto/rand/rand.c new file mode 120000 index 0000000..cd1f529 --- /dev/null +++ b/dm-sflc/bin/old/crypto/rand/rand.c @@ -0,0 +1 @@ +../../../../src/old/crypto/rand/rand.c \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/rand/rand.h b/dm-sflc/bin/old/crypto/rand/rand.h new file mode 120000 index 0000000..f515491 --- /dev/null +++ b/dm-sflc/bin/old/crypto/rand/rand.h @@ -0,0 +1 @@ +../../../../src/old/crypto/rand/rand.h \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/symkey/skreq_pool.c b/dm-sflc/bin/old/crypto/symkey/skreq_pool.c new file mode 120000 index 0000000..86e2c3a --- /dev/null +++ b/dm-sflc/bin/old/crypto/symkey/skreq_pool.c @@ -0,0 +1 @@ +../../../../src/old/crypto/symkey/skreq_pool.c \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/symkey/skreq_pool.h b/dm-sflc/bin/old/crypto/symkey/skreq_pool.h new file mode 120000 index 0000000..26bf22c --- /dev/null +++ b/dm-sflc/bin/old/crypto/symkey/skreq_pool.h @@ -0,0 +1 @@ +../../../../src/old/crypto/symkey/skreq_pool.h \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/symkey/symkey.c b/dm-sflc/bin/old/crypto/symkey/symkey.c new file mode 120000 index 0000000..f9290b7 --- /dev/null +++ b/dm-sflc/bin/old/crypto/symkey/symkey.c @@ -0,0 +1 @@ +../../../../src/old/crypto/symkey/symkey.c \ No newline at end of file diff --git a/dm-sflc/bin/old/crypto/symkey/symkey.h b/dm-sflc/bin/old/crypto/symkey/symkey.h new file mode 120000 index 0000000..9db23d7 --- /dev/null +++ b/dm-sflc/bin/old/crypto/symkey/symkey.h @@ -0,0 +1 @@ +../../../../src/old/crypto/symkey/symkey.h \ No newline at end of file diff --git a/dm-sflc/bin/old/device/device.c b/dm-sflc/bin/old/device/device.c new file mode 120000 index 0000000..597ebc4 --- /dev/null +++ b/dm-sflc/bin/old/device/device.c @@ -0,0 +1 @@ +../../../src/old/device/device.c \ No newline at end of file diff --git a/dm-sflc/bin/old/device/device.h b/dm-sflc/bin/old/device/device.h new file mode 120000 index 0000000..17a90f4 --- /dev/null +++ b/dm-sflc/bin/old/device/device.h @@ -0,0 +1 @@ +../../../src/old/device/device.h \ No newline at end of file diff --git a/dm-sflc/bin/old/device/iv.c b/dm-sflc/bin/old/device/iv.c new file mode 120000 index 0000000..ba70509 --- /dev/null +++ b/dm-sflc/bin/old/device/iv.c @@ -0,0 +1 @@ +../../../src/old/device/iv.c \ No newline at end of file diff --git a/dm-sflc/bin/old/device/rawio.c b/dm-sflc/bin/old/device/rawio.c new file mode 120000 index 0000000..d1e7e2b --- /dev/null +++ b/dm-sflc/bin/old/device/rawio.c @@ -0,0 +1 @@ +../../../src/old/device/rawio.c \ No newline at end of file diff --git a/dm-sflc/bin/old/device/rmap.c b/dm-sflc/bin/old/device/rmap.c new file mode 120000 index 0000000..1b346c1 --- /dev/null +++ b/dm-sflc/bin/old/device/rmap.c @@ -0,0 +1 @@ +../../../src/old/device/rmap.c \ No newline at end of file diff --git a/dm-sflc/bin/old/device/volumes.c b/dm-sflc/bin/old/device/volumes.c new file mode 120000 index 0000000..531eab0 --- /dev/null +++ b/dm-sflc/bin/old/device/volumes.c @@ -0,0 +1 @@ +../../../src/old/device/volumes.c \ No newline at end of file diff --git a/dm-sflc/bin/old/log/log.h b/dm-sflc/bin/old/log/log.h new file mode 120000 index 0000000..be4c94e --- /dev/null +++ b/dm-sflc/bin/old/log/log.h @@ -0,0 +1 @@ +../../../src/old/log/log.h \ No newline at end of file diff --git a/dm-sflc/bin/old/sflc_old.c b/dm-sflc/bin/old/sflc_old.c new file mode 120000 index 0000000..f003fde --- /dev/null +++ b/dm-sflc/bin/old/sflc_old.c @@ -0,0 +1 @@ +../../src/old/sflc_old.c \ No newline at end of file diff --git a/dm-sflc/bin/old/sflc_old.h b/dm-sflc/bin/old/sflc_old.h new file mode 120000 index 0000000..17e112f --- /dev/null +++ b/dm-sflc/bin/old/sflc_old.h @@ -0,0 +1 @@ +../../src/old/sflc_old.h \ No newline at end of file diff --git a/dm-sflc/bin/old/sysfs.c b/dm-sflc/bin/old/sysfs.c new file mode 120000 index 0000000..a61025a --- /dev/null +++ b/dm-sflc/bin/old/sysfs.c @@ -0,0 +1 @@ +../../src/old/sysfs.c \ No newline at end of file diff --git a/dm-sflc/bin/old/target.c b/dm-sflc/bin/old/target.c new file mode 120000 index 0000000..d853b61 --- /dev/null +++ b/dm-sflc/bin/old/target.c @@ -0,0 +1 @@ +../../src/old/target.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/bio.c b/dm-sflc/bin/old/utils/bio.c new file mode 120000 index 0000000..3290da9 --- /dev/null +++ b/dm-sflc/bin/old/utils/bio.c @@ -0,0 +1 @@ +../../../src/old/utils/bio.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/bio.h b/dm-sflc/bin/old/utils/bio.h new file mode 120000 index 0000000..287e38d --- /dev/null +++ b/dm-sflc/bin/old/utils/bio.h @@ -0,0 +1 @@ +../../../src/old/utils/bio.h \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/pools.c b/dm-sflc/bin/old/utils/pools.c new file mode 120000 index 0000000..fe89f0f --- /dev/null +++ b/dm-sflc/bin/old/utils/pools.c @@ -0,0 +1 @@ +../../../src/old/utils/pools.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/pools.h b/dm-sflc/bin/old/utils/pools.h new file mode 120000 index 0000000..ccc41b3 --- /dev/null +++ b/dm-sflc/bin/old/utils/pools.h @@ -0,0 +1 @@ +../../../src/old/utils/pools.h \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/string.c b/dm-sflc/bin/old/utils/string.c new file mode 120000 index 0000000..7e3ad7b --- /dev/null +++ b/dm-sflc/bin/old/utils/string.c @@ -0,0 +1 @@ +../../../src/old/utils/string.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/string.h b/dm-sflc/bin/old/utils/string.h new file mode 120000 index 0000000..8391130 --- /dev/null +++ b/dm-sflc/bin/old/utils/string.h @@ -0,0 +1 @@ +../../../src/old/utils/string.h \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/vector.c b/dm-sflc/bin/old/utils/vector.c new file mode 120000 index 0000000..f340f39 --- /dev/null +++ b/dm-sflc/bin/old/utils/vector.c @@ -0,0 +1 @@ +../../../src/old/utils/vector.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/vector.h b/dm-sflc/bin/old/utils/vector.h new file mode 120000 index 0000000..ee1ff17 --- /dev/null +++ b/dm-sflc/bin/old/utils/vector.h @@ -0,0 +1 @@ +../../../src/old/utils/vector.h \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/workqueues.c b/dm-sflc/bin/old/utils/workqueues.c new file mode 120000 index 0000000..d60d1a3 --- /dev/null +++ b/dm-sflc/bin/old/utils/workqueues.c @@ -0,0 +1 @@ +../../../src/old/utils/workqueues.c \ No newline at end of file diff --git a/dm-sflc/bin/old/utils/workqueues.h b/dm-sflc/bin/old/utils/workqueues.h new file mode 120000 index 0000000..b8d2f7e --- /dev/null +++ b/dm-sflc/bin/old/utils/workqueues.h @@ -0,0 +1 @@ +../../../src/old/utils/workqueues.h \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/fmap.c b/dm-sflc/bin/old/volume/fmap.c new file mode 120000 index 0000000..5b46520 --- /dev/null +++ b/dm-sflc/bin/old/volume/fmap.c @@ -0,0 +1 @@ +../../../src/old/volume/fmap.c \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/io.c b/dm-sflc/bin/old/volume/io.c new file mode 120000 index 0000000..d7221c0 --- /dev/null +++ b/dm-sflc/bin/old/volume/io.c @@ -0,0 +1 @@ +../../../src/old/volume/io.c \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/read.c b/dm-sflc/bin/old/volume/read.c new file mode 120000 index 0000000..efdcf3b --- /dev/null +++ b/dm-sflc/bin/old/volume/read.c @@ -0,0 +1 @@ +../../../src/old/volume/read.c \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/volume.c b/dm-sflc/bin/old/volume/volume.c new file mode 120000 index 0000000..8e26280 --- /dev/null +++ b/dm-sflc/bin/old/volume/volume.c @@ -0,0 +1 @@ +../../../src/old/volume/volume.c \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/volume.h b/dm-sflc/bin/old/volume/volume.h new file mode 120000 index 0000000..8f28ef6 --- /dev/null +++ b/dm-sflc/bin/old/volume/volume.h @@ -0,0 +1 @@ +../../../src/old/volume/volume.h \ No newline at end of file diff --git a/dm-sflc/bin/old/volume/write.c b/dm-sflc/bin/old/volume/write.c new file mode 120000 index 0000000..24e67c7 --- /dev/null +++ b/dm-sflc/bin/old/volume/write.c @@ -0,0 +1 @@ +../../../src/old/volume/write.c \ No newline at end of file diff --git a/dm-sflc/bin/sflc.c b/dm-sflc/bin/sflc.c new file mode 120000 index 0000000..d090f9d --- /dev/null +++ b/dm-sflc/bin/sflc.c @@ -0,0 +1 @@ +../src/sflc.c \ No newline at end of file diff --git a/dm-sflc/bin/sflc.h b/dm-sflc/bin/sflc.h new file mode 120000 index 0000000..0b7ccf2 --- /dev/null +++ b/dm-sflc/bin/sflc.h @@ -0,0 +1 @@ +../src/sflc.h \ No newline at end of file diff --git a/dm-sflc/bin/sflc_constants.h b/dm-sflc/bin/sflc_constants.h new file mode 120000 index 0000000..69cd0ab --- /dev/null +++ b/dm-sflc/bin/sflc_constants.h @@ -0,0 +1 @@ +../src/sflc_constants.h \ No newline at end of file diff --git a/dm-sflc/bin/sysfs.c b/dm-sflc/bin/sysfs.c new file mode 120000 index 0000000..e250188 --- /dev/null +++ b/dm-sflc/bin/sysfs.c @@ -0,0 +1 @@ +../src/sysfs.c \ No newline at end of file diff --git a/dm-sflc/dev_vol.c b/dm-sflc/src/dev_vol.c similarity index 100% rename from dm-sflc/dev_vol.c rename to dm-sflc/src/dev_vol.c diff --git a/dm-sflc/lite/crypto.c b/dm-sflc/src/lite/crypto.c similarity index 100% rename from dm-sflc/lite/crypto.c rename to dm-sflc/src/lite/crypto.c diff --git a/dm-sflc/lite/device.c b/dm-sflc/src/lite/device.c similarity index 100% rename from dm-sflc/lite/device.c rename to dm-sflc/src/lite/device.c diff --git a/dm-sflc/lite/posmap.c b/dm-sflc/src/lite/posmap.c similarity index 100% rename from dm-sflc/lite/posmap.c rename to dm-sflc/src/lite/posmap.c diff --git a/dm-sflc/lite/read.c b/dm-sflc/src/lite/read.c similarity index 100% rename from dm-sflc/lite/read.c rename to dm-sflc/src/lite/read.c diff --git a/dm-sflc/lite/sflc_lite.c b/dm-sflc/src/lite/sflc_lite.c similarity index 100% rename from dm-sflc/lite/sflc_lite.c rename to dm-sflc/src/lite/sflc_lite.c diff --git a/dm-sflc/lite/sflc_lite.h b/dm-sflc/src/lite/sflc_lite.h similarity index 100% rename from dm-sflc/lite/sflc_lite.h rename to dm-sflc/src/lite/sflc_lite.h diff --git a/dm-sflc/lite/sflite_constants.h b/dm-sflc/src/lite/sflite_constants.h similarity index 100% rename from dm-sflc/lite/sflite_constants.h rename to dm-sflc/src/lite/sflite_constants.h diff --git a/dm-sflc/lite/sysfs.c b/dm-sflc/src/lite/sysfs.c similarity index 100% rename from dm-sflc/lite/sysfs.c rename to dm-sflc/src/lite/sysfs.c diff --git a/dm-sflc/lite/volume.c b/dm-sflc/src/lite/volume.c similarity index 100% rename from dm-sflc/lite/volume.c rename to dm-sflc/src/lite/volume.c diff --git a/dm-sflc/lite/write.c b/dm-sflc/src/lite/write.c similarity index 100% rename from dm-sflc/lite/write.c rename to dm-sflc/src/lite/write.c diff --git a/dm-sflc/old/crypto/rand/rand.c b/dm-sflc/src/old/crypto/rand/rand.c similarity index 100% rename from dm-sflc/old/crypto/rand/rand.c rename to dm-sflc/src/old/crypto/rand/rand.c diff --git a/dm-sflc/old/crypto/rand/rand.h b/dm-sflc/src/old/crypto/rand/rand.h similarity index 100% rename from dm-sflc/old/crypto/rand/rand.h rename to dm-sflc/src/old/crypto/rand/rand.h diff --git a/dm-sflc/old/crypto/symkey/skreq_pool.c b/dm-sflc/src/old/crypto/symkey/skreq_pool.c similarity index 100% rename from dm-sflc/old/crypto/symkey/skreq_pool.c rename to dm-sflc/src/old/crypto/symkey/skreq_pool.c diff --git a/dm-sflc/old/crypto/symkey/skreq_pool.h b/dm-sflc/src/old/crypto/symkey/skreq_pool.h similarity index 100% rename from dm-sflc/old/crypto/symkey/skreq_pool.h rename to dm-sflc/src/old/crypto/symkey/skreq_pool.h diff --git a/dm-sflc/old/crypto/symkey/symkey.c b/dm-sflc/src/old/crypto/symkey/symkey.c similarity index 100% rename from dm-sflc/old/crypto/symkey/symkey.c rename to dm-sflc/src/old/crypto/symkey/symkey.c diff --git a/dm-sflc/old/crypto/symkey/symkey.h b/dm-sflc/src/old/crypto/symkey/symkey.h similarity index 100% rename from dm-sflc/old/crypto/symkey/symkey.h rename to dm-sflc/src/old/crypto/symkey/symkey.h diff --git a/dm-sflc/old/device/device.c b/dm-sflc/src/old/device/device.c similarity index 100% rename from dm-sflc/old/device/device.c rename to dm-sflc/src/old/device/device.c diff --git a/dm-sflc/old/device/device.h b/dm-sflc/src/old/device/device.h similarity index 100% rename from dm-sflc/old/device/device.h rename to dm-sflc/src/old/device/device.h diff --git a/dm-sflc/old/device/iv.c b/dm-sflc/src/old/device/iv.c similarity index 100% rename from dm-sflc/old/device/iv.c rename to dm-sflc/src/old/device/iv.c diff --git a/dm-sflc/old/device/rawio.c b/dm-sflc/src/old/device/rawio.c similarity index 100% rename from dm-sflc/old/device/rawio.c rename to dm-sflc/src/old/device/rawio.c diff --git a/dm-sflc/old/device/rmap.c b/dm-sflc/src/old/device/rmap.c similarity index 100% rename from dm-sflc/old/device/rmap.c rename to dm-sflc/src/old/device/rmap.c diff --git a/dm-sflc/old/device/volumes.c b/dm-sflc/src/old/device/volumes.c similarity index 100% rename from dm-sflc/old/device/volumes.c rename to dm-sflc/src/old/device/volumes.c diff --git a/dm-sflc/old/log/log.h b/dm-sflc/src/old/log/log.h similarity index 100% rename from dm-sflc/old/log/log.h rename to dm-sflc/src/old/log/log.h diff --git a/dm-sflc/old/sflc_old.c b/dm-sflc/src/old/sflc_old.c similarity index 100% rename from dm-sflc/old/sflc_old.c rename to dm-sflc/src/old/sflc_old.c diff --git a/dm-sflc/old/sflc_old.h b/dm-sflc/src/old/sflc_old.h similarity index 100% rename from dm-sflc/old/sflc_old.h rename to dm-sflc/src/old/sflc_old.h diff --git a/dm-sflc/old/sysfs.c b/dm-sflc/src/old/sysfs.c similarity index 100% rename from dm-sflc/old/sysfs.c rename to dm-sflc/src/old/sysfs.c diff --git a/dm-sflc/old/target.c b/dm-sflc/src/old/target.c similarity index 100% rename from dm-sflc/old/target.c rename to dm-sflc/src/old/target.c diff --git a/dm-sflc/old/utils/bio.c b/dm-sflc/src/old/utils/bio.c similarity index 100% rename from dm-sflc/old/utils/bio.c rename to dm-sflc/src/old/utils/bio.c diff --git a/dm-sflc/old/utils/bio.h b/dm-sflc/src/old/utils/bio.h similarity index 100% rename from dm-sflc/old/utils/bio.h rename to dm-sflc/src/old/utils/bio.h diff --git a/dm-sflc/old/utils/pools.c b/dm-sflc/src/old/utils/pools.c similarity index 100% rename from dm-sflc/old/utils/pools.c rename to dm-sflc/src/old/utils/pools.c diff --git a/dm-sflc/old/utils/pools.h b/dm-sflc/src/old/utils/pools.h similarity index 100% rename from dm-sflc/old/utils/pools.h rename to dm-sflc/src/old/utils/pools.h diff --git a/dm-sflc/old/utils/string.c b/dm-sflc/src/old/utils/string.c similarity index 100% rename from dm-sflc/old/utils/string.c rename to dm-sflc/src/old/utils/string.c diff --git a/dm-sflc/old/utils/string.h b/dm-sflc/src/old/utils/string.h similarity index 100% rename from dm-sflc/old/utils/string.h rename to dm-sflc/src/old/utils/string.h diff --git a/dm-sflc/old/utils/vector.c b/dm-sflc/src/old/utils/vector.c similarity index 100% rename from dm-sflc/old/utils/vector.c rename to dm-sflc/src/old/utils/vector.c diff --git a/dm-sflc/old/utils/vector.h b/dm-sflc/src/old/utils/vector.h similarity index 100% rename from dm-sflc/old/utils/vector.h rename to dm-sflc/src/old/utils/vector.h diff --git a/dm-sflc/old/utils/workqueues.c b/dm-sflc/src/old/utils/workqueues.c similarity index 100% rename from dm-sflc/old/utils/workqueues.c rename to dm-sflc/src/old/utils/workqueues.c diff --git a/dm-sflc/old/utils/workqueues.h b/dm-sflc/src/old/utils/workqueues.h similarity index 100% rename from dm-sflc/old/utils/workqueues.h rename to dm-sflc/src/old/utils/workqueues.h diff --git a/dm-sflc/old/volume/fmap.c b/dm-sflc/src/old/volume/fmap.c similarity index 100% rename from dm-sflc/old/volume/fmap.c rename to dm-sflc/src/old/volume/fmap.c diff --git a/dm-sflc/old/volume/io.c b/dm-sflc/src/old/volume/io.c similarity index 100% rename from dm-sflc/old/volume/io.c rename to dm-sflc/src/old/volume/io.c diff --git a/dm-sflc/old/volume/read.c b/dm-sflc/src/old/volume/read.c similarity index 100% rename from dm-sflc/old/volume/read.c rename to dm-sflc/src/old/volume/read.c diff --git a/dm-sflc/old/volume/volume.c b/dm-sflc/src/old/volume/volume.c similarity index 100% rename from dm-sflc/old/volume/volume.c rename to dm-sflc/src/old/volume/volume.c diff --git a/dm-sflc/old/volume/volume.h b/dm-sflc/src/old/volume/volume.h similarity index 100% rename from dm-sflc/old/volume/volume.h rename to dm-sflc/src/old/volume/volume.h diff --git a/dm-sflc/old/volume/write.c b/dm-sflc/src/old/volume/write.c similarity index 100% rename from dm-sflc/old/volume/write.c rename to dm-sflc/src/old/volume/write.c diff --git a/dm-sflc/sflc.c b/dm-sflc/src/sflc.c similarity index 100% rename from dm-sflc/sflc.c rename to dm-sflc/src/sflc.c diff --git a/dm-sflc/sflc.h b/dm-sflc/src/sflc.h similarity index 100% rename from dm-sflc/sflc.h rename to dm-sflc/src/sflc.h diff --git a/dm-sflc/sflc_constants.h b/dm-sflc/src/sflc_constants.h similarity index 100% rename from dm-sflc/sflc_constants.h rename to dm-sflc/src/sflc_constants.h diff --git a/dm-sflc/sysfs.c b/dm-sflc/src/sysfs.c similarity index 100% rename from dm-sflc/sysfs.c rename to dm-sflc/src/sysfs.c From d66667ed2a7b66d761520c8432de653a771c7b9d Mon Sep 17 00:00:00 2001 From: = Date: Thu, 29 Aug 2024 23:09:55 +0200 Subject: [PATCH 71/98] fix Makefile --- dm-sflc/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dm-sflc/Makefile b/dm-sflc/Makefile index 45a4f1e..29c9b12 100644 --- a/dm-sflc/Makefile +++ b/dm-sflc/Makefile @@ -31,10 +31,10 @@ debug: CONFIG_SFLC_DEBUG=y debug: default install: - make -C $(KERNEL_DIR) M=$(BUILD_DIR) CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) modules_install + make -C $(KERNEL_DIR) M=$(ROOT_DIR)/bin CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) modules_install clean: - make -C $(KERNEL_DIR) M=$(BUILD_DIR) CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) clean + make -C $(KERNEL_DIR) M=$(ROOT_DIR)/bin CONFIG_SFLC_DEBUG=$(CONFIG_SFLC_DEBUG) clean # Reserved From fb5cad845113919df050ae5f0f170ae8e2619db8 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 29 Aug 2024 23:58:49 +0200 Subject: [PATCH 72/98] Fix dm_io --- dm-sflc/bin/lite/dm_io_helper.h | 1 + dm-sflc/src/lite/dm_io_helper.h | 56 +++++++++++++++++++++++++++++++++ dm-sflc/src/lite/posmap.c | 3 +- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 120000 dm-sflc/bin/lite/dm_io_helper.h create mode 100644 dm-sflc/src/lite/dm_io_helper.h diff --git a/dm-sflc/bin/lite/dm_io_helper.h b/dm-sflc/bin/lite/dm_io_helper.h new file mode 120000 index 0000000..ec2aa8e --- /dev/null +++ b/dm-sflc/bin/lite/dm_io_helper.h @@ -0,0 +1 @@ +../../src/lite/dm_io_helper.h \ No newline at end of file diff --git a/dm-sflc/src/lite/dm_io_helper.h b/dm-sflc/src/lite/dm_io_helper.h new file mode 100644 index 0000000..7e0d915 --- /dev/null +++ b/dm-sflc/src/lite/dm_io_helper.h @@ -0,0 +1,56 @@ +/* + * Copyright The Shufflecake Project Authors (2022) + * Copyright The Shufflecake Project Contributors (2022) + * Copyright Contributors to the The Shufflecake Project. + * + * See the AUTHORS file at the top-level directory of this distribution and at + * + * + * This file is part of the program shufflecake-c, which is part of the + * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) + * layer for Linux. See . + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or (at your option) + * any later version. This program 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 General + * Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. + * If not, see . + */ + +#ifndef _SFLITE_DMIOHELPER_H +#define _SFLITE_DMIOHELPER_H + +#include +#include + +/** + * The function dm_io() has changed signature in recent kernels. + * Here we provide a version-independent adapter, which uses a default value + * for the fifth parameter (the new one). + * The new signature is present for kernel 6.1.x with x>=83, 6.6.x with x>=23, + * 6.7.x with x>=11, 6.8.x with x>=2, 6.x with x>=9 + */ +#if LINUX_VERSION_MAJOR <= 5 // Old +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err) +#elif LINUX_VERSION_MAJOR >= 7 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +// Ok LINUX_VERSION_MAJOR is 6 +#elif LINUX_VERSION_PATCHLEVEL >= 9 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +#elif LINUX_VERSION_PATCHLEVEL == 8 && LINUX_VERSION_SUBLEVEL >= 2 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +#elif LINUX_VERSION_PATCHLEVEL == 7 && LINUX_VERSION_SUBLEVEL >= 11 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +#elif LINUX_VERSION_PATCHLEVEL == 6 && LINUX_VERSION_SUBLEVEL >= 23 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +#elif LINUX_VERSION_PATCHLEVEL == 1 && LINUX_VERSION_SUBLEVEL >= 83 // New +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err, IOPRIO_DEFAULT) +#else // Old +#define sflc_dm_io(ioreq, numreg, region, err) dm_io(ioreq, numreg, region, err) +#endif + +#endif /* _SFLITE_DMIOHELPER_H */ diff --git a/dm-sflc/src/lite/posmap.c b/dm-sflc/src/lite/posmap.c index eb57072..3ee2551 100644 --- a/dm-sflc/src/lite/posmap.c +++ b/dm-sflc/src/lite/posmap.c @@ -23,6 +23,7 @@ #include #include +#include "dm_io_helper.h" #include "sflc_lite.h" @@ -268,7 +269,7 @@ static int read_encrypted_posmap(struct sflite_volume *svol) .count = svol->sdev->posmap_size_sectors }; - return dm_io(&io_req, 1, &io_region, NULL); + return sflc_dm_io(&io_req, 1, &io_region, NULL); } /** From 92d787530100f56dd98a4aed8bda2d01449df196 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 30 Aug 2024 00:27:01 +0200 Subject: [PATCH 73/98] Mah --- Makefile | 4 ++-- dm-sflc/bin/Kbuild | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 4c88d2f..6d3666a 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ default: make -C dm-sflc - cp dm-sflc/bin/dm_sflc.ko ./dm_sflc.ko + cp dm-sflc/bin/dm-sflc.ko ./dm-sflc.ko make -C shufflecake-userland cp shufflecake-userland/bin/proj_build/shufflecake ./shufflecake @@ -48,7 +48,7 @@ clean: make -C dm-sflc clean make -C shufflecake-userland clean rm ./shufflecake - rm ./dm_sflc.ko + rm ./dm-sflc.ko diff --git a/dm-sflc/bin/Kbuild b/dm-sflc/bin/Kbuild index 89a38fe..5e5aa11 100644 --- a/dm-sflc/bin/Kbuild +++ b/dm-sflc/bin/Kbuild @@ -21,7 +21,7 @@ # If not, see . # -MODULE_NAME := dm_sflc +MODULE_NAME := dm-sflc obj-m := $(MODULE_NAME).o From d157647057c2d02e37fc2b3bef1c433c3adb63ca Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Fri, 30 Aug 2024 00:27:54 +0200 Subject: [PATCH 74/98] chore:Update benchmark script --- sflc-benchmark-suite/sflc-benchmark.sh | 63 +++++++++++++------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/sflc-benchmark-suite/sflc-benchmark.sh b/sflc-benchmark-suite/sflc-benchmark.sh index 8c980d3..d703e84 100755 --- a/sflc-benchmark-suite/sflc-benchmark.sh +++ b/sflc-benchmark-suite/sflc-benchmark.sh @@ -42,7 +42,7 @@ NC='\033[0m' # No color # Help print_help() { # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars - echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]...${NC}" + echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" echo " " echo "This script is used to benchmark Shufflecake on this machine." echo "This script is part of the Shufflecake benchmark suite." @@ -54,8 +54,7 @@ print_help() { echo "1) Creates a Shufflecake device with two volumes." echo "2) Opens the second (hidden) one, formats it with ext4 and mounts it." echo "3) Performs various fio r/w stress operations on it." - echo "4) Checks fragmentation status." - echo "5) Unmounts and closes the used volume." + echo "4) Unmounts and closes the used volume." echo " " # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo "You must have already compiled and installed Shufflecake in order to run this." @@ -71,7 +70,7 @@ print_help() { echo "formatted with the appropriate tools. The file will be removed at the end." echo " " # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars - echo "WARNING: ALL CONTENT OF THE PROVIDED BLOCK DEVICE WILL BE ERASED!" + echo -e "${BLUE}WARNING: ALL CONTENT OF THE PROVIDED BLOCK DEVICE WILL BE ERASED!${NC}" echo " " exit 0 } @@ -94,7 +93,7 @@ usage() { # Check that this is run as root check_sudo() { if [[ $EUID -ne 0 ]]; then - echo -e "${RED}Error: This script must be run as root${NC}" + echo -e "${RED}Error: This script must be run as root.${NC}" usage exit 1 fi @@ -129,7 +128,7 @@ find_sflc_path() { done # If the command was not found, print an error message - echo -e "${RED}ERROR: Command '$cmd' not found${NC}" >&2 + echo -e "${RED}ERROR: Command '$cmd' not found${NC}." >&2 exit 1 } @@ -162,14 +161,13 @@ load_dmsflc() { fi # If the module file was not found, print an error message - echo -e "${RED} ERROR: Module file '$mod_file' not found.${NC}" >&2 + echo -e "${RED} ERROR: Module file '$mod_file' not found.${NC}." >&2 exit 1 } # Unload dm-sflc if it was loaded locally unload_dmsflc() { local mod="dm-sflc" - # Only unload dm-sflc if it was loaded manually when the script was invoked if ! $DMSFLC_INSTALLED; then rmmod $mod || exit 1 @@ -193,15 +191,15 @@ check_block_device() { # Function to create loop device create_loop_device() { - echo "I will now try to create a file $LOOP_FILENAME" >&2 + echo "I will now try to create a file $LOOP_FILENAME ..." >&2 if [ -e "$LOOP_FILENAME" ]; then - echo -e "${RED}Error: Impossible to generate file, $LOOP_FILENAME already exists${NC}" + echo -e "${RED}Error: Impossible to generate file, $LOOP_FILENAME already exists.${NC}" exit 1 fi sudo dd if=/dev/zero of="$LOOP_FILENAME" bs=1M count=1024 > /dev/null echo "Writing of empty file complete. I will now try to attach it to a new loop device..." >&2 LOOP_DEVICE=$(sudo losetup -f --show "$LOOP_FILENAME") - echo "Successfully created loop device $LOOP_DEVICE" >&2 + echo "Successfully created loop device $LOOP_DEVICE ." >&2 echo "$LOOP_DEVICE" } @@ -222,7 +220,7 @@ find_sflcvolname() { # Function for user confirmation confirm() { while true; do - echo -e "${RED}Are you sure you want to proceed? All data on disk $BLOCK_DEVICE will be erased. (y/n)${NC}" + echo -e "${BLUE}Are you sure you want to proceed? All data on disk $BLOCK_DEVICE will be erased. (y/n)${NC}" read -r response case "$response" in [yY]|[yY][eE][sS]) # Responded Yes @@ -232,7 +230,7 @@ confirm() { return 1 # Return 1 for No (error, convention for bash scripting) ;; *) # Responded something else - echo "Please press only y or n." + echo "Please press only (y)es or (n)o." ;; esac done @@ -243,7 +241,11 @@ benchmark() { SFLCVOLUME="" MNTPOINT="" - echo -e "Starting benchmark for Shufflecake." + TESTNAME="sflc" + RUNTIME="20" # running time in seconds FOR EACH TEST + DATASIZE="500M" + TESTFILENAME="testfile" + echo "Starting benchmark for Shufflecake..." echo "Initializing block device $BLOCK_DEVICE with two Shufflecake volumes (--skip-randfill)..." etime=$( (time echo -e "passwd1\npasswd2" | $SFLCNAME --skip-randfill -n 2 init $BLOCK_DEVICE > /dev/null) 2>&1 ) echo -e "${GREEN}Action init took $etime seconds.${NC}" @@ -264,20 +266,20 @@ benchmark() { echo "Volume mounted at $MNTPOINT. Starting fio tests..." # TESTS HERE # test 01: random read - echo "Test 01: random read with a queue of 32 4kiB blocks on a file (20s)..." - OUTPUT=$(fio --name=sflc-r-rnd --ioengine=libaio --iodepth=32 --rw=randread --bs=4k --numjobs=1 --size=500M --runtime=20 --time_based --end_fsync=1 --filename=$MNTPOINT/testfile --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') + echo "Test 01: random read with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-r-rnd --ioengine=libaio --iodepth=32 --rw=randread --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') echo -e "${GREEN}${OUTPUT}${NC}" # test 02: random write - echo "Test 02: random write with a queue of 32 4kiB blocks on a file (20s)..." - OUTPUT=$(fio --name=sflc-w-rnd --ioengine=libaio --iodepth=32 --rw=randwrite --bs=4k --numjobs=1 --size=500M --runtime=20 --time_based --end_fsync=1 --filename=$MNTPOINT/testfile --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') + echo "Test 02: random write with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-w-rnd --ioengine=libaio --iodepth=32 --rw=randwrite --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') echo -e "${GREEN}${OUTPUT}${NC}" # test 03: sequential read - echo "Test 03: sequential read with a queue of 32 4kiB blocks on a file (20s)..." - OUTPUT=$(fio --name=sflc-r-seq --ioengine=libaio --iodepth=32 --rw=read --bs=4k --numjobs=1 --size=500M --runtime=20 --time_based --end_fsync=1 --filename=$MNTPOINT/testfile --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') + echo "Test 03: sequential read with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-r-seq --ioengine=libaio --iodepth=32 --rw=read --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') echo -e "${GREEN}${OUTPUT}${NC}" # test 04: sequential write - echo "Test 04: sequential write with a queue of 32 4kiB blocks on a file (20s)..." - OUTPUT=$(fio --name=sflc-w-seq --ioengine=libaio --iodepth=32 --rw=write --bs=4k --numjobs=1 --size=500M --runtime=20 --time_based --end_fsync=1 --filename=$MNTPOINT/testfile --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') + echo "Test 04: sequential write with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-w-seq --ioengine=libaio --iodepth=32 --rw=write --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') echo -e "${GREEN}${OUTPUT}${NC}" # END TESTS echo "Shufflecake fio tests ended. Unmounting volume." @@ -295,11 +297,10 @@ benchmark() { # Clean up cleanup() { echo "Exiting and cleaning..." - # TODO clean other stuff if necessary if [[ -n $LOOP_DEVICE ]]; then - echo "Detaching $LOOP_DEVICE" + echo "Detaching $LOOP_DEVICE ..." sudo losetup -d "$LOOP_DEVICE" - echo "Deleting $LOOP_FILENAME" + echo "Deleting $LOOP_FILENAME ..." sudo rm -f "$LOOP_FILENAME" echo "Loop device detached and backing file deleted." fi @@ -314,12 +315,12 @@ cleanup() { # BANNER # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars -echo -e "${GREEN}===============================================================================${NC}" -echo -e "${GREEN} Benchmark Suite Script for Shufflecake${NC}" -echo -e "${GREEN}===============================================================================${NC}" +echo -e "${BLUE}===============================================================================${NC}" +echo -e "${BLUE} Benchmark Suite Script for Shufflecake${NC}" +echo -e "${BLUE}===============================================================================${NC}" -# PRELIMINARY: PARSE HELP AND LOAD SHUFFLECAKE +# PRELIMINARY: PARSE HELP, SUDO, AND LOAD SHUFFLECAKE (IF IT EXISTS, OTHERWISE ERROR) case "$1" in # help @@ -331,7 +332,7 @@ esac check_sudo -echo -e "${GREEN}Initializing Shufflecake...${NC}" +echo -e "${BLUE}Initializing Shufflecake...${NC}" echo "Searching Shufflecake executable..." find_sflc_path echo "Shufflecake executable found at $SFLCNAME ." @@ -373,7 +374,7 @@ check_block_device "$BLOCK_DEVICE" # MAIN PROGRAM if confirm; then - benchmark # Call your disk formatting function here + benchmark else echo "Aborting..." fi From 2a53c4fdd7ab59a0b3ad02c7fe35c6bac59671f7 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 10:47:20 +0200 Subject: [PATCH 75/98] chore:Renamed benchmark-suite directory --- {sflc-benchmark-suite => benchmark-suite}/INSTRUCTIONS.md | 0 {sflc-benchmark-suite => benchmark-suite}/luks-benchmark.sh | 0 {sflc-benchmark-suite => benchmark-suite}/sflc-benchmark.sh | 0 {sflc-benchmark-suite => benchmark-suite}/veracrypt-benchmark.sh | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {sflc-benchmark-suite => benchmark-suite}/INSTRUCTIONS.md (100%) rename {sflc-benchmark-suite => benchmark-suite}/luks-benchmark.sh (100%) rename {sflc-benchmark-suite => benchmark-suite}/sflc-benchmark.sh (100%) rename {sflc-benchmark-suite => benchmark-suite}/veracrypt-benchmark.sh (100%) diff --git a/sflc-benchmark-suite/INSTRUCTIONS.md b/benchmark-suite/INSTRUCTIONS.md similarity index 100% rename from sflc-benchmark-suite/INSTRUCTIONS.md rename to benchmark-suite/INSTRUCTIONS.md diff --git a/sflc-benchmark-suite/luks-benchmark.sh b/benchmark-suite/luks-benchmark.sh similarity index 100% rename from sflc-benchmark-suite/luks-benchmark.sh rename to benchmark-suite/luks-benchmark.sh diff --git a/sflc-benchmark-suite/sflc-benchmark.sh b/benchmark-suite/sflc-benchmark.sh similarity index 100% rename from sflc-benchmark-suite/sflc-benchmark.sh rename to benchmark-suite/sflc-benchmark.sh diff --git a/sflc-benchmark-suite/veracrypt-benchmark.sh b/benchmark-suite/veracrypt-benchmark.sh similarity index 100% rename from sflc-benchmark-suite/veracrypt-benchmark.sh rename to benchmark-suite/veracrypt-benchmark.sh From 211c2913c072a267317e8e239950dceb3fa9819d Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 11:44:33 +0200 Subject: [PATCH 76/98] chore:Remove shufflecake-userland/src/operations/devmapper.c --- .../src/operations/devmapper.c | 111 ------------------ 1 file changed, 111 deletions(-) delete mode 100644 shufflecake-userland/src/operations/devmapper.c diff --git a/shufflecake-userland/src/operations/devmapper.c b/shufflecake-userland/src/operations/devmapper.c deleted file mode 100644 index 99a66a2..0000000 --- a/shufflecake-userland/src/operations/devmapper.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright The Shufflecake Project Authors (2022) - * Copyright The Shufflecake Project Contributors (2022) - * Copyright Contributors to the The Shufflecake Project. - * - * See the AUTHORS file at the top-level directory of this distribution and at - * - * - * This file is part of the program shufflecake-c, which is part of the - * Shufflecake Project. Shufflecake is a plausible deniability (hidden storage) - * layer for Linux. See . - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 2 of the License, or (at your option) - * any later version. This program 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 General - * Public License for more details. You should have received a copy of the - * GNU General Public License along with this program. - * If not, see . - */ - -/***************************************************** - * INCLUDE SECTION * - *****************************************************/ - -#include -#include -#include -#include -#include - -#include "header.h" -#include "operations.h" -#include "utils/sflc.h" -#include "utils/crypto.h" -#include "utils/file.h" -#include "utils/string.h" -#include "utils/dm.h" -#include "utils/log.h" - - -/***************************************************** - * PUBLIC FUNCTIONS DEFINITIONS * - *****************************************************/ - -/** - * Build parameter list for ctor in dm_sflc, and send DM ioctl to create - * virtual block device. - * - * @param bdev_path The path to the underlying device - * @param dev_id The ID of the underlying block device - * @param vol_idx The index of the volume within the device - * @param vmb Volume metadata - * - * @return Error code, 0 on success - */ -int sflc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) -{ - char label[SFLC_BIGBUFSIZE]; - char *hex_key; - char params[SFLC_BIGBUFSIZE]; - uint64_t num_sectors; - int err; - - /* Build volume label */ - sprintf(label, "sflc_%lu_%lu", dev_id, vol_idx); - - /* Get the hex version of the volume's data section key */ - hex_key = sflc_toHex(vmb->volume_key, SFLC_CRYPTO_KEYLEN); - if (!hex_key) { - sflc_log_error("Could not encode volume key to hexadecimal"); - err = ENOMEM; - goto err_hexkey; - } - - /* Get the number of logical 512-byte sectors composing the volume */ - num_sectors = ((uint64_t) vmb->nr_slices) * SFLC_BLOCKS_PER_LOG_SLICE * SFLC_SECTOR_SCALE; - - /* Build param list */ - sprintf(params, "%s %lu %lu %s", bdev_path, vol_idx, vmb->nr_slices, hex_key); - - /* Issue ioctl */ - err = sflc_dm_create(label, num_sectors, params); - if (err) { - sflc_log_error("Could not issue ioctl CREATE command to device mapper; error %d", err); - goto err_dmcreate; - } - err = 0; - - -err_dmcreate: - free(hex_key); -err_hexkey: - return err; -} - - -/** - * Close the volume by issuing the appropriate ioctl to the DM. - * - * @param label The only needed parameter: the ID of the volume. - * - * @return Error code, 0 on success - */ -int sflc_ops_closeVolume(char *label) -{ - /* Issue ioctl */ - return sflc_dm_destroy(label); -} From d42fe61cb0c013ce98419bb803a6b4b1f7e2d834 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 11:50:14 +0200 Subject: [PATCH 77/98] chore:Readd link to sflc_constants.h --- shufflecake-userland/include/sflc-constants.h | 1 + 1 file changed, 1 insertion(+) create mode 120000 shufflecake-userland/include/sflc-constants.h diff --git a/shufflecake-userland/include/sflc-constants.h b/shufflecake-userland/include/sflc-constants.h new file mode 120000 index 0000000..3a95dcc --- /dev/null +++ b/shufflecake-userland/include/sflc-constants.h @@ -0,0 +1 @@ +../../dm-sflc/src/sflc_constants.h \ No newline at end of file From 54e1b34bf9cef00d933ceac7645c29414277f06b Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 12:03:15 +0200 Subject: [PATCH 78/98] doc:Change header scheme to legacy vs lite --- README.md | 2 +- .../{headers.png => headers_legacy.png} | Bin .../{headers.svg => headers_legacy.svg} | 0 resources/images/headers_lite.png | Bin 0 -> 138276 bytes resources/images/headers_lite.svg | 1764 +++++++++++++++++ shufflecake-userland/src/cli/dispatch.c | 6 +- 6 files changed, 1768 insertions(+), 4 deletions(-) rename resources/images/{headers.png => headers_legacy.png} (100%) rename resources/images/{headers.svg => headers_legacy.svg} (100%) create mode 100644 resources/images/headers_lite.png create mode 100644 resources/images/headers_lite.svg diff --git a/README.md b/README.md index 560d944..70cbaf7 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Volumes are password-protected, and embedded in the underlying device as data _s Up to 15 _ordered_ Shufflecake volumes can be created on a single device, with the implicit assumption that "lower-order" volumes (e.g. layer 0) are _less_ _secret_ than "higher-order" ones (e.g. layer 3). The Shufflecake header is designed in such a way that it _chains_ volumes in a backwards-linked list: volume __i__ "points to" (contains the key of) volume __i-1__. This way, providing the key of volume __i__ allows this tool to traverse the list and also automatically open volumes __0__ through __i-1__ (besides volume __i__). -![Scheme of Shufflecake device layout](resources/images/headers.png) +![Scheme of Shufflecake device layout](resources/images/headers_legacy.png) Opened volumes appear as block devices of the form `sflc_x_y` in `/dev/mapper`. It is up to the user to format them with an appropriate filesystem and mounting them before use. It is recommended to always unlock the most sensitive volume for daily use ("home alone" scenario), even if that volume is not being used or even mounted. Only open a subset of volumes (with a "decoy password") if under coercion. This is due to the high possibility of corrupting hidden volumes otherwise. diff --git a/resources/images/headers.png b/resources/images/headers_legacy.png similarity index 100% rename from resources/images/headers.png rename to resources/images/headers_legacy.png diff --git a/resources/images/headers.svg b/resources/images/headers_legacy.svg similarity index 100% rename from resources/images/headers.svg rename to resources/images/headers_legacy.svg diff --git a/resources/images/headers_lite.png b/resources/images/headers_lite.png new file mode 100644 index 0000000000000000000000000000000000000000..562702cceefc5e1285fb573c8ced336b4ab79c3f GIT binary patch literal 138276 zcmd43hdY+-A3uIeN+BwujZ$QkkYpw+DtjlgGqRISh?Gz$rN{_bk&&%XvdKvL<9I&L^TF-D?(2M?@Aqq+*BzxxvNT)wY$cIMH0R}{R7j+aizL#9 zJ(Qd99hI)fjQH0UTRCk966wHu;vcf>O-^F?=59x6Ek{)wQ%C2k_9i4}XJ?*k*DW24 zuG*UL*w~vroe{_oi zUsvrY+7aB``M~|&5hk}WWusOmnnnCYF#~wL7Iu={{ z+dbN$vB25%hUty@D-F{F^uZY!*BxbKWZ3Ao|NUxqlhxGxld{SCF`wDC=;-KgH@pJ> zeI?WJTqzDrQB8`YCh=eIVS4`jxlNIx)W0t^%BZL)66slf4=wAzFV}O_Gyned_L3o{ z{zL!YPa1ivW&gc&_PJ4L$iKJwCHj&1;J+6&yWKx4_U|=ufA;yh|9g$pq|m0tf3FGr z=J5a88*%bkJi6Rp zb(gWat}Z+=>d6yHtQq&*znhV~5aAj+Is7?EtJGcSlvSrUejrlYp03O2yNTv~vU0qE zscEF85X;88`g(@ptgNhLr5Eq_{wYXUSzDeYUc;kT=r=p-$lHm^tv{~ljF2n(TW{-}^45x_+`4(? zA3r9Qla)q47x%ht*v%{)&&qalM?Z1EjPJLbJ4!(Tu-o0z#J?ZrB z-8=x3F3?UJjjYqCcAo_qK1*{7eQ z^l21Zrgom_;Yhh2`DEwOOYHMa9x14)sfDxFS5;96+I*{w z;xjvObE@xhZLVd#bk1|?5`*)885_j}}DRIkZwI1ut>MV5PL+)sGQ-sy2RFgd}&gv z_+}PseMbl5XnXp(FL~C@dDglM%gaQ`2H95?_^tH4^QEMu3>DMW(b=W@_1^`sv3X9( z=;%Bd+3}@Od|jk*l$wSn%YD(F-=bA6@&F@a!1ABTcw!%nd>sFhZ#UM-E8;p!dFITS z*ZKLfc$?oM9)EBL0`J~^n5dqr!QCMtB`LY#{rmUE*EVh1M6tE*8vm0GJrwjl<$__6 zk+)ToE`3MU-&~sIn4O(f%C%6>F>l`BG}6kU@(f+!?agUc9{tjLNs2M7+}t#~cI`56 zP2PU_@?`@91Es#gmd~H<7H3Q@7dmdiCcZiSgW|%43ra5}DXx9We(~aJ*iy>ir-w&x z&i)ExEx9q?eL_H>J~t%k(zEw-qaBI7X)1|Zb#lzf+}xH0CscKG_GDZsc_o*rnzRjn z#lB<$yXEHEN�rdJwM2Mo* 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

5|oSM=65BA~vxzbDm8qkrGT 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 G^Z!3ZVE`@w literal 0 HcmV?d00001 diff --git a/resources/images/headers_lite.svg b/resources/images/headers_lite.svg new file mode 100644 index 0000000..d637326 --- /dev/null +++ b/resources/images/headers_lite.svg @@ -0,0 +1,1764 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DMB + Header + Device / Cake + + Header + + Encrypted Slices + IVs + + VMB + + 0 + + EncryptedPosMap + 0 + . . . + + VMB + 1 + + EncryptedPosMap + 1 + + VMB + 14 + + EncryptedPosMap + 14 + DMB + + IV + 1 + . . . + + ctxt + 1 + + IV + 14 + + ctxt + 14 + Password + Argon2 + + KEK + AES-GCM + + VMK + + 1 + VMB + + IV + VMB + + ctxt + AES-CTR + + + + 1 + 1 + VEK + VMK + 0 + NumSlices + Metadata + | + | + | + | + | + | + AES-CTR + + + + Data I/O + 1 + 1 + 1 + 1 + 1 + VMB + + + IV + POS + + ctxt + 1 + 1 + POS + + AES-CTR + + + + + PSI + 0 + PSI + 1 + | + | + PSI + NumSlices-1 + . . . + | + | + | + | + EncryptedPosition Map + 1 + + IV + 1 + + ctxt + 1 + + salt + + IV + 1 + + ctxt + 1 + + salt + + IV + 0 + + ctxt + 0 + + Shufflecake v0.4.x + + Structure of disk and headers + Example where a password for volume 1unlocks volume 1 and recursively alsovolume 0 (dotted arrow). + + + + + diff --git a/shufflecake-userland/src/cli/dispatch.c b/shufflecake-userland/src/cli/dispatch.c index 0bed6ff..356e26d 100644 --- a/shufflecake-userland/src/cli/dispatch.c +++ b/shufflecake-userland/src/cli/dispatch.c @@ -87,8 +87,8 @@ static error_t _parseArgpKey(int key, char *arg, struct argp_state *state); /* Doc strings */ static char args_doc[] = "ACTION "; static char doc[] = - "Shufflecake is a plausible deniability (hidden storage) layer for Linux.\n" - "See official website at for more info.\n" + "\nShufflecake is a plausible deniability (hidden storage) layer for Linux.\n" + "See official website at for more info.\n\n" "Possible values for mandatory ACTION are:\n\n" "\tinit:\t\tInitialise a block device for Shufflecake use, formatting\n" @@ -116,7 +116,7 @@ static struct argp_option options[] = { "Specify number of volumes to be created with `init'. Must be an integer between 1 and 15.", 0 }, // TODO: define MAX_VOLS instead of hardcoding 15 {"skip-randfill", SFLC_OPT_SKIPRAND_KEY, 0, 0, "Skip pre-overwriting block device with random data, only valid with `init'. Faster but less secure. Use only for debugging or testing."}, - {"legacy", SFLC_OPT_LEGACY_KEY, 0, 0, "Use the old (pre-v0.5.0) Shufflecake format. Only valid with `init` and `open'. Use of this option is not recommended. This mode is going to be deprecated in future versions."}, + {"legacy", SFLC_OPT_LEGACY_KEY, 0, 0, "Use the old (pre-v0.5.0) Shufflecake format. Only valid with `init` and `open'. This mode is less secure and error-prone, use of this option is not recommended, it is provided for backward-compatibility only. This mode is going to be deprecated in the future."}, {0} }; From 5854bc60ea7156da20e673ce14274b582d58b432 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 14:39:34 +0200 Subject: [PATCH 79/98] doc:Update README, header schemes, references --- README.md | 42 +- .../badges/badge_community_jabber_chat.png | Bin 0 -> 3885 bytes .../images/badges/badge_version_0.4.5.png | Bin 2352 -> 0 bytes .../images/badges/badge_version_0.5.0.png | Bin 0 -> 2410 bytes resources/images/badges/badges.svg | 97 +- resources/images/headers_legacy.png | Bin 138276 -> 141795 bytes resources/images/headers_legacy.svg | 16 +- resources/images/headers_lite.png | Bin 138276 -> 118256 bytes resources/images/headers_lite.svg | 1933 ++++++++--------- 9 files changed, 1004 insertions(+), 1084 deletions(-) create mode 100644 resources/images/badges/badge_community_jabber_chat.png delete mode 100644 resources/images/badges/badge_version_0.4.5.png create mode 100644 resources/images/badges/badge_version_0.5.0.png diff --git a/README.md b/README.md index 70cbaf7..118da68 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,24 @@ [![Status](resources/images/badges/badge_status_active.png)](https://codeberg.org/shufflecake/shufflecake-c)  -[![Version](resources/images/badges/badge_version_0.4.5.png)](https://codeberg.org/shufflecake/shufflecake-c/releases/tag/v0.4.5)  +[![Version](resources/images/badges/badge_version_0.5.0.png)](https://codeberg.org/shufflecake/shufflecake-c/releases/tag/v0.5.0)  [![License](resources/images/badges/badge_license_gplv2plus.png)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)  [![Docs researchpaper](resources/images/badges/badge_docs_researchpaper.png)](https://eprint.iacr.org/2023/1529)  [![Website](resources/images/badges/badge_web_shufflecakedotnet.png)](https://shufflecake.net/)  [![Issue tracker](resources/images/badges/badge_community_issuetracker.png)](https://codeberg.org/shufflecake/shufflecake-c/issues)  [![Mastodon](resources/images/badges/badge_community_mastodon.png)](https://fosstodon.org/@shufflecake)  +[![Jabber](resources/images/badges/badge_community_jabber_chat.png)](xmpp:shufflecake@conference.draugr.de?join)  [![Please don't upload to GitHub](resources/images/badges/badge_no_github.png)](https://nogithub.codeberg.page) -# Shufflecake - Full C Implementation - v0.4.5 +# Shufflecake - Full C Implementation - v0.5.0 _Shufflecake_ is a plausible deniability (hidden storage) layer for Linux. You can consider Shufflecake a spiritual successor of tools like TrueCrypt and VeraCrypt, but vastly improved, both in terms of security and functionality. Official website: . This repository contains C source code and documentation to use and manage Shufflecake volumes. -__WARNING__: Shufflecake is still experimental software, please do not rely on its security or stability. +__WARNING__: Shufflecake is a strong encryption and plausible deniability solution. The mere presence of Shufflecake on your device might put you at risk of being targeted, accused of crime or foul play, arrested, or worse. In Shufflecake there is no easy way to convince an adversary that you have given up all your passwords. Please consider carefully your threat model before using Shufflecake. In any case, this is still experimental software, please do not rely on its security or stability. - [Overview](#overview) - [Installation](#installation) @@ -30,6 +31,9 @@ __WARNING__: Shufflecake is still experimental software, please do not rely on i - [Other Commands](#other) - [Changing A Password](#changepwd) - [Check If A Password Is Correct](#testpwd) + - [Modes of Operation](#modes) + - [Shufflecake Lite](#lite) + - [Shufflecake Legacy](#legacy) - [Advanced Usage](#advanced) - [Volume Corruption Mitigation](#mitigcorr) - [Providing Passwords Non-Interactively](#passwordnonint) @@ -47,15 +51,13 @@ __WARNING__: Shufflecake is still experimental software, please do not rely on i In the context of Shufflecake, a _device_, or _cake_, is the underlying raw block device (e.g., a disk) that is formatted to contain hidden data, while a _volume_, or _layer_, is the logical, encrypted and hidden "partition" within a device. The device can be a whole USB stick (or disk), a physical or logical partition, a file-backed loop device, etc. (you likely find it under `/dev`). The three operating principles of Shufflecake are: -- 1 device = multiple volumes; +- 1 device = multiple (concurrently usable) volumes; - 1 volume = 1 password = 1 "secrecy level"; - unlocking a volume also unlocks all those of lesser secrecy level. Volumes are password-protected, and embedded in the underlying device as data _slices_ which are indistinguishable from random noise without the proper password. Headers are also indistinguishable from random when not decrypted. A Shufflecake-initialized device does not have cleartext headers, and is indistinguishable from random noise when not decrypted. -Up to 15 _ordered_ Shufflecake volumes can be created on a single device, with the implicit assumption that "lower-order" volumes (e.g. layer 0) are _less_ _secret_ than "higher-order" ones (e.g. layer 3). The Shufflecake header is designed in such a way that it _chains_ volumes in a backwards-linked list: volume __i__ "points to" (contains the key of) volume __i-1__. This way, providing the key of volume __i__ allows this tool to traverse the list and also automatically open volumes __0__ through __i-1__ (besides volume __i__). - -![Scheme of Shufflecake device layout](resources/images/headers_legacy.png) +Up to 15 _ordered_ Shufflecake volumes can be created on a single device, with the implicit assumption that "lower-order" volumes (e.g. layer 0) are _less_ _secret_ than "higher-order" ones (e.g. layer 3). Shufflecake is designed in such a way that it _chains_ volumes in a backwards-linked list: volume __i__ "points to" (contains the key of) volume __i-1__. This way, providing the key of volume __i__ allows this tool to traverse the list and also automatically open volumes __0__ through __i-1__ (besides volume __i__). Opened volumes appear as block devices of the form `sflc_x_y` in `/dev/mapper`. It is up to the user to format them with an appropriate filesystem and mounting them before use. It is recommended to always unlock the most sensitive volume for daily use ("home alone" scenario), even if that volume is not being used or even mounted. Only open a subset of volumes (with a "decoy password") if under coercion. This is due to the high possibility of corrupting hidden volumes otherwise. @@ -67,7 +69,7 @@ For security and consistency reasons, you cannot init/open/close nested volumes ## Installation -This implementation of Shufflecake consists of two components: a module for the Linux kernel (`dm-sflc`), and a `shufflecake` userland tool. Both are necessary in order to use Shufflecake. They have been tested on Linux kernel versions 6.1 (LTS), up to 6.7. The following instructions are given for Debian/Ubuntu and similar derivatives. +This implementation of Shufflecake consists of two components: a module for the Linux kernel (`dm-sflc`), and a `shufflecake` userland tool. Both are necessary in order to use Shufflecake. They have been tested on Linux kernel versions 6.1 (LTS), up to 6.10. The following instructions are given for Debian/Ubuntu and similar derivatives. First of all, you need the kernel headers, device-mapper userspace library, and libgcrypt to compile the source. Use: @@ -193,6 +195,23 @@ sudo shufflecake testpwd A password will be required. If this is the correct password for volume __M__, then this information will be returned to the user, without actually opening the volume. This can be useful if a user wants to be sure that a password unlocks a certain volume without risk of the OS logging the access to that volume and without the risk of corrupting the content of other, unlocked, hidden volumes. +### Modes of Operation + +Shufflecake can operate in two different modes, with different features in terms of security, performance, and data resilience. + +#### Shufflecake Lite + +As of v0.5.0, Shufflecake's default mode of operation is the "Lite" scheme. This mode introduces the AES-XTS encryption mode rather than AES-CTR, with a related change of header format. This mode has only advantages compared to the old "Legacy" mode: it offers the _same_ level of security, but with better performance and data resilience, and it is therefore the strongly recommended way to use Shufflecake. It will be used by default whenever a Shufflecake command is invoked (see [Usage](#usage)). + +![Scheme of Shufflecake Lite device layout](resources/images/headers_lite.png) + +#### Shufflecake Legacy + +For backward compatibility reasons, this release of Shufflecake still offers support for the old (pre-v0.5.x) "Legacy" scheme, which can be selected with the `--legacy` option when invoking `init` or `open` (see [Usage](#usage)), all other actions (including `close`) do not require specifying a Shufflecake mode. This scheme uses AES-CTR with explicit IVs written on disks, and it offers therefore ciphertext re-randomization, which is in turn a necessary ingredient for achieving future modes with better security. However, since these modes have not yet been implemented, currently the use of Shufflecake Legacy has only drawbacks compared to other modes: it does not offer better security, while being slower, less space-efficient, and more prone to data corruption. The use of this mode is therefore discouraged, and will be deprecated at some point in the future. However, you must use this option when trying to open volumes created with an old (pre-v0.5.x) release of Shufflecake. You are strongly encouraged to migrate Legacy volumes to the new Lite ones if you haven't done it yet. + +![Scheme of Shufflecake Legacy device layout](resources/images/headers_legacy.png) + + ### Advanced Usage Certain features of Shufflecake are not yet implemented, but sometimes a workaround is possible. Consider all the instructions in this sections as experimental, use them at your own risk. @@ -246,6 +265,10 @@ Both methods works with the `init` action, and we do not have current plans to c Please see the file `CHANGELOG.md` for a detailed history of changes. +### [0.5.0] - 2024-09-01 + + - BREAKING CHANGE: major rewrite, introduced Shufflecake "Lite" as a default mode of operation. + ### [0.4.5] - 2024-06-03 - Fixed a compile error on some versions of LTS kernels. @@ -289,8 +312,7 @@ Bugs and other issues are tracked at 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 literal 0 HcmV?d00001 diff --git a/resources/images/badges/badge_version_0.4.5.png b/resources/images/badges/badge_version_0.4.5.png deleted file mode 100644 index 14a5825bdaf244bf503ccdcb78289b991305f4ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2352 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 literal 0 HcmV?d00001 diff --git a/resources/images/badges/badges.svg b/resources/images/badges/badges.svg index eadd2c3..254bb97 100644 --- a/resources/images/badges/badges.svg +++ b/resources/images/badges/badges.svg @@ -29,12 +29,12 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="2.7438272" - inkscape:cx="200.26771" - inkscape:cy="241.99775" + inkscape:cx="234.16198" + inkscape:cy="198.26321" inkscape:window-width="1920" - inkscape:window-height="979" + inkscape:window-height="975" inkscape:window-x="0" - inkscape:window-y="0" + inkscape:window-y="32" inkscape:window-maximized="1" inkscape:current-layer="layer1" /> @@ -223,38 +223,38 @@ xml:space="preserve" style="font-size:4.39831px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.183262" x="63.241447" - y="53.516087" + y="60.878864" id="text1131">web + y="60.878864">web + y="57.0406" /> shufflecake.net + y="60.878864">shufflecake.net @@ -262,38 +262,38 @@ xml:space="preserve" style="font-size:4.39831px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.183262" x="63.241447" - y="61.032242" + y="68.39502" id="text1143">docs + y="68.39502">docs + y="64.556755" /> Research Paper + y="68.39502">Research Paper @@ -301,69 +301,108 @@ xml:space="preserve" style="font-size:4.39831px;line-height:1.25;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke-width:0.183262" x="63.241447" - y="68.44519" + y="75.807968" id="text1155">license + y="75.807968">license + y="71.969704" /> GPLv2+ + y="75.807968">GPLv2+ version + y="83.4077">version + y="79.569435" /> 0.4.5 + y="83.4077">0.5.0 + + community + + Jabber Chat diff --git a/resources/images/headers_legacy.png b/resources/images/headers_legacy.png index 562702cceefc5e1285fb573c8ced336b4ab79c3f..7a1f4fc5cb22afe227dda844d948a022e626cf2f 100644 GIT binary patch literal 141795 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 O#6QY>D0;8w`+op>9&P#n literal 138276 zcmd43hdY+-A3uIeN+BwujZ$QkkYpw+DtjlgGqRISh?Gz$rN{_bk&&%XvdKvL<9I&L^TF-D?(2M?@Aqq+*BzxxvNT)wY$cIMH0R}{R7j+aizL#9 zJ(Qd99hI)fjQH0UTRCk966wHu;vcf>O-^F?=59x6Ek{)wQ%C2k_9i4}XJ?*k*DW24 zuG*UL*w~vroe{_oi zUsvrY+7aB``M~|&5hk}WWusOmnnnCYF#~wL7Iu={{ z+dbN$vB25%hUty@D-F{F^uZY!*BxbKWZ3Ao|NUxqlhxGxld{SCF`wDC=;-KgH@pJ> zeI?WJTqzDrQB8`YCh=eIVS4`jxlNIx)W0t^%BZL)66slf4=wAzFV}O_Gyned_L3o{ z{zL!YPa1ivW&gc&_PJ4L$iKJwCHj&1;J+6&yWKx4_U|=ufA;yh|9g$pq|m0tf3FGr z=J5a88*%bkJi6Rp zb(gWat}Z+=>d6yHtQq&*znhV~5aAj+Is7?EtJGcSlvSrUejrlYp03O2yNTv~vU0qE zscEF85X;88`g(@ptgNhLr5Eq_{wYXUSzDeYUc;kT=r=p-$lHm^tv{~ljF2n(TW{-}^45x_+`4(? zA3r9Qla)q47x%ht*v%{)&&qalM?Z1EjPJLbJ4!(Tu-o0z#J?ZrB z-8=x3F3?UJjjYqCcAo_qK1*{7eQ z^l21Zrgom_;Yhh2`DEwOOYHMa9x14)sfDxFS5;96+I*{w z;xjvObE@xhZLVd#bk1|?5`*)885_j}}DRIkZwI1ut>MV5PL+)sGQ-sy2RFgd}&gv z_+}PseMbl5XnXp(FL~C@dDglM%gaQ`2H95?_^tH4^QEMu3>DMW(b=W@_1^`sv3X9( z=;%Bd+3}@Od|jk*l$wSn%YD(F-=bA6@&F@a!1ABTcw!%nd>sFhZ#UM-E8;p!dFITS z*ZKLfc$?oM9)EBL0`J~^n5dqr!QCMtB`LY#{rmUE*EVh1M6tE*8vm0GJrwjl<$__6 zk+)ToE`3MU-&~sIn4O(f%C%6>F>l`BG}6kU@(f+!?agUc9{tjLNs2M7+}t#~cI`56 zP2PU_@?`@91Es#gmd~H<7H3Q@7dmdiCcZiSgW|%43ra5}DXx9We(~aJ*iy>ir-w&x z&i)ExEx9q?eL_H>J~t%k(zEw-qaBI7X)1|Zb#lzf+}xH0CscKG_GDZsc_o*rnzRjn z#lB<$yXEHEN�rdJwM2Mo* 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

5|oSM=65BA~vxzbDm8qkrGT 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 G^Z!3ZVE`@w diff --git a/resources/images/headers_legacy.svg b/resources/images/headers_legacy.svg index d637326..cb1a69d 100644 --- a/resources/images/headers_legacy.svg +++ b/resources/images/headers_legacy.svg @@ -8,7 +8,7 @@ version="1.1" id="svg2909" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" - sodipodi:docname="headers.svg" + sodipodi:docname="headers_legacy.svg" inkscape:export-filename="headers.png" inkscape:export-xdpi="200" inkscape:export-ydpi="200" @@ -27,13 +27,13 @@ inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" showgrid="false" - inkscape:zoom="1.3719136" - inkscape:cx="466.13723" - inkscape:cy="319.991" + inkscape:zoom="1.9401788" + inkscape:cx="424.96083" + inkscape:cy="242.76113" inkscape:window-width="1920" - inkscape:window-height="979" + inkscape:window-height="975" inkscape:window-x="0" - inkscape:window-y="0" + inkscape:window-y="32" inkscape:window-maximized="1" inkscape:current-layer="layer1" /> Shufflecake v0.4.x + + y="37.286942">Shufflecake "Legacy" v0.4.x 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

J78D= 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

>1bJp$PZ~srU%uyA)Qj3o0o9%sZ}^F8DD^ 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�rdJwM2Mo* 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

5|oSM=65BA~vxzbDm8qkrGT zBSNpv`A(owR+9OOGfOw{dd500yp!W;;9tr>wg=#q+ zc1!R!z5ZuWaErr;b)ZNgXM1zqvHGex_!s~%f<)#GT5K3ZkKuY@bwG7M*Tz3cLD>`| zSE4*zWs2v)N0DD@c3j;S`@HyzZ)4S9s1wQMiQjrQ0YT+UDD?)6M?O2ps1|%^WZC$h zCO=y56`S)!mUKdJDa9xJDV_;%N`=p)yrwolv@ks&Q|^ZdpQ#lk_6&yHH(zT>60r~?=W?Nt(i4TDhF55qxvpktAIJTi9?oIE<?hDmFz=dcmQ1qs_d-_m0LI3~Xj*XR-OdFJ!gO|H-9QLsYRa0)HO)sq;hY1L( zPSUCznsmZ}=38n0xysJ;@wnN;7J`p8{>AFWH`l~Za;d2T*Jej9a!JSQR7SPCJ3|o| zrSs~{pJTo#?G`D&PpTDdaHx4)!Q`dpbt6Dhkr&!;l)BCwh@|t=4Rxa2;uDndh~XJ<1dzoa=?ZItirvmYj_gks#gu8KLY}Fr4pd=? ze-^3VS8HzqKBR^yG+g0340Fg10WGFcaGUz-RpNJJp)H#t7w-o+)ljI4fa z_TK9NZ=VCq{4eBWt|$6SeB5~V9lx?=UXq09vCTo~Ts|&=v)|sp+gkztg&Fchj;c)7 z*887%FTv>q#$mccGMkIL2m=uo79iS8Uv7VVui`s96Gvq605wYcR}OJixf-tV$-QxS zn91SpNqwTK56@rTsrXp&Cz9o7d5(eKykj&hy=nsOHyMUI`z!qr0K)9~jX!{Nj@MqT71Io(E#twjC1id+8u@2NgT7Wwe=)@!>kR$T| z+Y!rEc{a@7a94#>aOWn!tG1&&lluGS@b8~LS?j7czn$;Bj4&?9+FScE26pWupac*r zBfRT?;1zQnOtMt+&j+CCr?n)hk442?nB8Kh_LO7iA!Yp`y+r6g-%UJ|*ko^WIsu@9 zArL)~T7~qHY$2H&j7EVT?TUBYU}lZNbCgyXb@Pwz8qfMYE=~q4DNw>Ab^$Us zxZ3iMyJ>x>BVh9akPLek*jxH~mC(1BZi3)(@s)b|R3a4IKa@?}`VeUakg9-SKTmm} z{^>A&0^-S9|IfSy#vH&mMewqLDNo43p)wR@Z)@vkW25lvE5_h-0^QhKjM`HkNZaSY zy`#*4y!0NTpit0DHY)fH%l!8_?*Z2!&<70eIyyRu|Ck=CuK~{k3L}UuNxy4bxlvkM zTM>U747>-d@{pUG1Y8F|fk6&ZG1L_{AxIJ_fY)K}9MnV>Fi4^xgNESf2E|eP|NXE9 zu!tZjDwv^06={h0k9sCo=6 zBLfBEjs24n^pB|p-5s=ZNNj;lk!{k`)%6gv2d^(2VPMYzbg`wr3bVw(zFiLo6qpa; zVS}2F_=$)EaLgE08UtFP@k1fdpx18^Cc682rv1++Il)l(SZ=#OC?gPJ$rC481W10l zb;K1F&t;Vc91|FX+wbTAs|#!vD>E@A>tm>yz}|YVf{B`1B-Ck>=wEl2Sp`(3EFZc_ z4T_NyHuuo}#tbcd?Eg|Ut4GihVZdAw+Q`t*JlpGP$O7Z2ddCfHPRiqUaFBytES$&p}wf$HAn6(B(ahu=#=# z5^3KF2pu|HS5yC(#tU7*gC#O01j0Jf)e?4C7(<--&nw`|G1L#+0pK%yd{Jp>e>kB2 zg(7so=#h~{oVWs6-3YBO5R1*ogkjVmKH@g5Mb4T*CGXq%Jy3V z5EF*^Rqz^Y^QnO!?Q-+-oWJ&H4ikucWHbbvRo}sp1cBUmV9l5zVgsi!ye9Gg?O0zS z&%l8+W(Jm{3HDs1+5lyX8b%{SiG9%{Wj!xqB1ql&R8x%0%o{13N1-t?HROQO= z$w^J(w|aF|Ootu~U&!6ZK_A1V zN)UkmM@vVA+sXs_++5A!6rS1cHRP`yMf67A{0VO}%$Wp^LR(QwOIASv7y9bJ)|q() zstgb6>(|lHZq%aaiM&l;9B@pzoSG;UA|Hh2@yve;4P5y`bZGXV z>YtrXUoL@`s9&N6@9_(1X{2_6tS0npfy6yzqV3QFMegIo8hieth2OqX&kRV_6Pnf_ zY7KbBe8xzTk3vUlM2Ao~K)qHd+$(cnJZnT#U1;bXSXML`9#AL4qYsIX-@v`X1A#eE zU3_LKspJlgzKMcK$m^6=fLYwQ!7<9X_{VHsn&iO z84>XiABFDmW8wVau&-ZfR8UN@QDr401T*j6@ep8+#dS^k_wtoC%IC4bOHsb~Ro#2+ z?!Vu&?oPO5sxs=)D`R#NHa0d0Ey=%nHKKQI27oAN`>8lHfIt^4k8wx?;4%8+9HSwB zjL^Fp=nQDrAb58Sxee|D=I!#C%AqKnMng}6Y+hzZ0K|hk3Z)?8#fSP>mzj_nl^Jd( z7+&bmIN?w_`-+P$iz~E%hy1?Iw?a5(VODlKv<0QRqlfwL$K1;pG=wz+4B}c*p4GLP zogGWWegU&CK$9_T(9q3!nXyofY9RsF1kw%ddR;|S6AfSyN9ZpHu%`Q zdpN+x37Z7|^LVZZ=I^~kE!RYWHy;^(@x8h_78b#PVTm#-Cu{=kmmGl70Fxol#iO5u zat8zXM$G=@C!a+%EH_HNKa@@{%CO1kf{P&Zy5+IsYtrs|RZpkEb}SMZW*< zV!J3ARz2QcRJ@;-kDhrl^5%oVsJ{j&S{lAq z-A2MDEp5VSROSP#$d{AOfBCyK*(IZkkOf)}|K!?tg`Z=jTz}(ypB>}HYIVzv0$mmY zFp@CAN=*!k9j&YW=TZH;3g9J^5^2_QPjp3>`*$IuHQp={6Vx!2kF<@%REvJXI#B%P zsU1ahMDRU-#ldX>ou}hmq0=L&+QyRiiA`F2?)yxN3Yx|hlSX7Tj;ju?^GSETT&0(J zHP$_`Rp34sw-(f=eOo|16T>K=V%)_=?^7_{+iiRvf3-6@k>rA0y`{v3om|SYc-=x&OL+cLZ(l?n~(wg^` z+O|pNxx`0S$C(MrNY_y=@PsNS(>qogUJiSw_W5^gtQ{8ai<}-VRWM;7d+b($6EQnCHi`mOy3q+#|xEX3J29sTy>lUl6FLCcAcv2M%2tJyoYNfX_OM> zk7o;rEGkHmMZc9T{NFZ+?u|eD^rE_OBbt1en~ZVihvD(j;^f(mW>RbA2fnB;d`0u9 z9V7a>7r)9ZZ2A=5S`u9zUr0J#WmSwxn(8!DoC^86>FmNj-$UM;a@#g0Op~PjXfsTm zu9MfQu}L=kS4^0My*(+)J2UdPmnJcld%Ys|&zyVA1g^{e_>|DdN41!0@Ml@fac`XP zl+NTK1Gx#&W)F?|`*QJdZ`=;*!bb;Fy-ibLQ$O4p^~YX!%!=D7)V()vFiF=ey;`E0 zkuH-UHeX=JBy@`gLyw|G{zsH|dxGOS$sK39m1k2r=S2mm>sEayQVJ&(UOO@p3B78+ zCkFF;S|yf~j*gA#r?{JnJCDhov%ca7m;CDAaBFi&Gmc;GeYt(1YiT-#;--=Nho!r4 z>_EBW@tfZ{|ix=(NL*qR+(3wXe?B8Ih5o-cRa+H>rE z@|l8e;&4x*bZr37+7yjwv+H`|?6M?F&v~sBMU-N9IK!^|waQIY^}aftrqBUKE1{)W z71o^WEq$qzaS^czmcA5|Wv<5t6Y|#tM%cFf9p1HAn6Q#D%kOpa(NFPD)P4Hv_4Ehd zeP*XparGr^g5MftM)HUKWDmqHuzV&(RAy;w4)zb^-mg|O7@yj*+5)J?N19Vw3 zOE3R{?IfP~eMEPn^-z5HVKtVpMLK9EPpWQb z>&o3d+5H_ViG}{2fa^q>q+>#iewJldOQs_w<5^mhV{j!^XhY=ELh+a-_?fT1!(kTf z@l+uQDKY0AOdz;zT%AHXddU>HP&-ga66Nvx+cE3#Ce{77ENM%^#jHyxGwTVSu3Kno zw{GYgbheE1<1$U7Ny~iHcTSc!5;n*g^X(e*mF2&k-v3!>(I+?eUce!GY#=*vwyWSg zzN|=t&7<%sLc>z`4pI50ieC$os~y8L)@rC*gQ>IY3I0N}lez2X^#-=`rzRM&3?% ztfb<+3mUSR7xj3-ZFkExY^$B-GaqYvHJXaisN&wWV7!^iv5(I`S5Sg;#|9%U?UVg+ z%UnSjRT$mV0b{Yu9ok288fwH=TemwCD_l4(DDe!kM!lOgMCFg3P=%Q$@BPd!CdS;_ zk-C51o~otA7%m>=7e2Aa1E$0-W%fs{TpF3jX=v%q?iwlgw{n=npR?J18cYc?b;zLJ zBKkqBKRR7AoMkHZiXenZM@_%X`SerIBQjX9VtBJMy+2-GCC`6m^DcX&ZeP}5RM&`- z^*vvUxAW2Uz?S!!t@1yvp#<2WRsJS=rS=%Ut>8NTXIXwS;jVktXLG&wqQ5ojeAtyR zYAdY=#&Fh3-@moQFW1OmtshW5^Hp3Ff3;>-pdORKbcz}?D4=sBJrIqv`q-t()C%)V zEkXZ^5RI-0-l3HH%v-8|Dk08B=+~$N@nsZ9>&UCCkEaIp>W8183=9t9l9D>L8?OZd zxUrhF8Adf9*0jS2*;M`9xUsOFUMAI{#Yg{MJN|`q5%>4aA)oZZ4@z7gtBf2-A2>^v z`}~UJ%U2oIGgN6hvsuSvzYRxEk)_LqvY`^+fr2E(`;0wc+>=C zvgj9PXRSBvFsg1>6gTSSul6_S`2_#&LQh!@3*1LeB1gIt<1e~aY$elQUvyaJdK4JV zZOdSb&_ssp%qeHpJt7XzP-B|r^aykq8_BtzW5V{f)5+(aFe6LUO|wf8+Z-OuT)A5* zADjiCEzFfYC9Z;fS=K4srnKo#jiza=n{0Plcz(_Dx~87}=Kh{=&fd&*NlmpaR?MeR zToOJ*@=exa=fl2IE3tY0jh*4X>}eNNuu8V-xTbM9M^d~sCqth=3wjRO@AtL zZTN|Ow=8&^ac89p_qaOjY@3`2MZie#nGCnlHW}H-JJS4D+N=+%3&ZU^*H+UG<@D(s z94HQ!u##H2+Y)?9!h((N9tB{$Y1$N1+}S)oC;mZurSZe8bvf_Vm{nVxh$7EK-(CG@ z5p#IKlERB}Z_TQs3YVQeM{4uPIzGnGBY)F+_)H@1a9iG#&NePoS zJAYm~uBnT*k=!o*ng^N7t%6)rv)(mrlIPfqf0%*wu)@I9>D>8zr8_qo#Q zz|r^Xd!zUI>uKnx+9JP>ulL5S-^;_heC}Svf&IpauzE|gy!2ABph(`<-VL_YE4t zirJS_5U$80ylNHG0|@&NT^HE`OROp2X1~y z+PY?gr%Hud5rN6oBRoF)BN!r->s5E9>1N~YW3r> zoX=t$XXhxMrTG3(&ysW6)n33*E{1Tau$H4V|FQ0%_zj*hmy;c4GZS;nX@js6TH>1% z$_z|Y+fU0lDOfmKgcrgx7tnhA&?YLzR$qR#9i4hOf=uoqfy2VT17uDpcV+>Dz;cLM$^_*Rb%tcTS-LKL&ZEQWGgmNI#m%W71qt{n3=E;+?{wOJ{@xs=#HofEO=3h*dKTE+9@@;@SgI=xM-NY`0 z`GE3G{|5P1;ZQDP_<>)s%sRWNOzpIsWPe>=i9;^+GG)gmzW@FzCday`SQha$4=?A zu+^k#ns%YjR{6E?#Tr3@U(}ziv~>31Z8Ydi+u1sO6u~EwenxRmiO5~|+33Og-6y$= zvHLOgTtl;xgF8ZdkyV%7LQ7(H4|d2d$HJDr8YKN%V9}5*YoK5jWnvhQ$qD7qk^6=j zpt7R71!>WoJQ#{1Xn42KTd~)P# z5NXoNZ_k5zlKD;j^JtANV|~an9uA8r@%+z&s;5Jnl_p+h)N$##Yd{Oq9jaw$h3^Rc`z4 z?3if9L}wOWX;NO6@K)jHE2Oj{`#XU?fAx0PuEpci;E!ayR_HT%l&UMmHghmxB$tRy zb|qNXXdh$zy;O?&^+liQcgtD3BNeMWSk^Z)f2hYm0dCHfb6+)a2(9^Q3}*2W)sHfa1L#E@NaIyAmCJZn|oX zTDExZZCaL(PqBQ6;La;Y{7o~#mvsDjoBsdA|#_K98qk#jqga+jxt@!!-Q-g61a$AHES=q0k zlW8N;#uA1fKpPPaXg*XkRt&==YA-Jw4ULRI4S5ei5b#)bq`*K;5)cdcP@cyHf^j1_ zF0KplXPvBOLyL_D3?~u~rTUOFF~i4dzr|0I<=Z8QDB9z-c`&ILjPu{Nh+2ryUYn!% zo?4M~+l-&BkZvRR=LDIGbRBcvu=9#XiAd9lREN6*Q{Lsgh{17oS<#K<#BOFaOA>=N zlO_&coXa-K4B?vZ^i|FngKWmxlnZ_OZzZ`#T5V%RcX;HR%$6@^CgVlyK;~t`K)ATkE@cp3 zfC@>WdW^5^d5w7eHRrvD$^;CwR2H}lw@=NvOlB;TXASyo7U_<`TKA1J zw{bEZg=Ta2zN!Bm*rEF$FThy~foUeZ>Cv3H)lPc6OWzL899#SP zzR4J$P;B4sy#IcHVrwTLg>yN9+<8o`!ys!?5`IG0;Tm=jdkr?;KYRV?ezkXFNepz~ReIndp9qvR8!LXd}}^0SNM zf|wJM2p1IGC_ADF^Mm#WWQe~UhOt9rvT4>FN%2wc+_nPdM)Bd~Z`@kVq2`MRQD(@#rX3)9Gwy!=3CGku<8=5kY}gzT73lkvu&c&(1ej#(zd-(MI8?Dw8J9Nd77>cFhxs@q9> z0=!SWY~r<4{JTu>C(KYJvCeKt89lsqdLef5+KgqRx+jxKTUdC|?;{@<$WYK>F~O2y;Ka7 zi^D9O=lTtgW(^79!RtcFj1gmi5SfW~f*()l#@$om@{Z1Z)y1}8Qs;Bji=8O%V5L9& z08k=ack`RP@X~yS104^N$6ReD~(I%Cx999N;~_BAC0k8v%V7CIf;4oldR~mpVK-M zj>ZPxg|2FA3c&kO*WHO``?N~!`>ZCJ8)3f)IJsk^u1$Vf3LwcIDVw3CyKB$P z%>H(kgtbL4u(0@RzkCU*QOqf^S>ANO=&Khhy&r&Id3ScK-9Mz(;+|_q8Tr^CV@zP# za!*CHqt`25)MlGNF(z~6{sUV6FOqnj?zyf%SM*bo46gz!M9FE1b;@x`(T0~m!#K}J zkICQss_sS@CBfy4pFh{K9svHzW!}>^f<*7Dw+BL7`95>Lc(6Z06nk7kn^qmu)WoqV zo@uc~oSx=UdYysNGfaR!h0uqCFa2$u$Hsrk1a%-oL2uxo;d&YeXPj(aU8;&o%y zz+~m3a^o=z5t`RAypx5#!IOB&h97bLq$b}~x|p)!Z<04W9wL~wE-QPtuiDJXz~A3S4EKUqsx8!n-Dpzhg!bqO8yQy!ONqp-*Ncf($@oGvUqno$%0~K%B;t= zw0jj$DE}^t_ANXA&ENi`s#N3=9g0s@`q3+CKQKAZm<>x^{K($woc!Rzx}>%An0u4f zb@?3STl5ZHkL$Qz&7ekCJO%IL><)Z2s1kzP!dVA5g6ZEL+(;jkSm$^s!YB)zMZhK4 zzU>D0E4JKqjczlhV^*li{(Q>|OYQkbLoA*JK+ z496s^YK@Is3o0}htb9J7eqa4g>a^}LD*N+5YT83Ii)EiHjYdU@OQaPVVbThG~-y^8aP0v`_YP6PH{XAaG-M|x(`$z`3~)-lMXcAO6& zACDk&3M=|#W;>R?y!pgua_#Aj_P3J25Bz#7sgaj=D3>e83RNl;Vx-ID%&|}P-ryOH z^h|}VR@gm}%)Rq&bH)1xKBh^yk~CMd@jNY!1$$VOS8Odo{heFn|LHWI&2MJG%K5iu zUI9Gi%^+~0B8?!B)~PT%Dmt1N{);$BE|(|6b>++Pj=rBi&%x;sfmRDEJSI}h0mH4R zhy|i7<`o+7p1dZ$gf_|HXtkJ(zb$<&N-d)$|ftoZtRE zH5MDx>@-(FGrqkerPC0LuU|ds0VL+H+JwBc3Kuyhb=%a{exWucMSiA}j9<%qtu4I! z->}&C%X4emRu6_q=MRRA$6kKAM6=`LZv0AoMthLkUL|g$8Fi}*-K>G2DFti)&4AH| z+2X24u77n}ud}>xHKGw*7TdBqYCY#$su!S94dvWWfRl?vuaOUhwDi6IZO7Crq@M(L znm(Wh)p;lI`>oaN8hEBOi%lM;dTNpdO@F&ztiT5pO5&cC4?ek^#&u?6+n(boi3_BN zt!P#=N!vG{M%&cO{@V^HwB>qA0>!6I{X>l+lRge|QQhKRzRq8jp?#{@=6`0TgSMg6 z4ddu+wzIy^HchI#iBDT5WT`Xq`p8F`+I9J=qIyi`T`$X-T}|s%6Ka_w67@QwX;Gaa zFWC~zc%b&otn-}i#M#DeGtvHf9Rdm;vJED)`W}oEJ(B=+YJ4`l8E{( z3(@nqJgKUV#ReT_YlBPJnZNhgP1+FL^g0dDgsd3Hq%~d--{}+fm;j`o{@b=Y5E-b4 zAOR|Lhq*p;aDAds&xd*>s_T8`d3Jv76@g6Uo$1R90)Bow(-?KLnh5x1e^{CJBfINm z(~|M_b*Gb?Tl+RX=7uF|7h6931@FFab96z_u+rCwB?`&yfpQBak`!~XHCAsfXWzN7 z>cN3zUX2kC-Mo-O)}nv(%)MjF6UrSMRn5cP^wQmFo8jPK^3I%&I}&6Rl2h^ZUAfk2 z@-@}ybx#E!ovWaA-?CfoypmI5!kWYFGT3Gd#+$_J2?K7jq8ukKjFs7Wn9NIIRv6mf z+&2FRQ02)bpXGj{W{h*A7m*rDP6#ntczb4cPeM-m?Pdh+p>7qoBv!R#o4YFSSmkaf z7w#O(hVVR@#MD8R#faJq?!R@^{*IlE9qIW4`Pso)5#F5vX(zjHs^3hyR@GoI%-q|Z zpwfPHoBvu>qm$-FGKn+(F?<8Zg=W(3?z-zyLSj4pVHx(c6kZ9RJ$+hnt+l8x@I0#a zi=L~L$Fj4i_H64LlLa@6Z8NbvmXWw0M7*{3io%W{R1YTttFdBG*D z(S4$oJjHd(@{EhKBPN{a!=ZWR(aFKMbRS#$-~&C=mApzufj&IE7xvaX(dcGxHrwuB zUXY>1QAjkEV0dZ~`6vz#J|nw-(ygFBUzwK1`Fh}MUU=4SsZ#j&D!pIZRB z*KKR0)7~yDxXu(r$!Ew;7GlwK5gx_bG57yS{}Ux}?V(eb;}adfU!!)PaR+VwaV-E} zEe#f|NLSd?CJ(?h6~Q+R%@84)B5?w}Z&?f`-LIOuBYuPIyvm!d$kb2-gQ^3tDQuEzE7&X9-rk{)H<%o&}THNx% zqpXYM^5IycrMY`g7EQNl%wKY%hK<)$Ung%j80oWcId8d6soK%2eMd%0_JK;r%?$fP zoHU-Rlmz0Gs+W~6iholWle(0JnrE>y_;&E2m`3xC^HN+KK5c28DdNgJ<v)^rIBH zIMBZkj#T$t2{@>Bs$5BezW8)?80yZ$@+}uSjI5GW@laX7(cU2CH(jn{eETb(qepz; zuebD0pIYE#J~O<==T;e!$|=1eFMmFvPbXcyz1+^2l-D{}WRnNYr!)Vw%=9pzXg#gf zpFeZBMLFc@7*cF~#Y^gRQ-(Ws>o)0CmTT-%ht0M?ZCy)kPiSvQ+D;^UghiS&)A&ql z#wgue(q(QuiGAK*_+J8I#`7t0K;JUNHOWs0hpVjgqNHgUYxWfBnh1VEdlBe=`2!lY ztQ`baKUvqUoViC4B$_`=F&y8}5YHYh%ZZmjtYAL<+M9+AI)bjF8d1cM&xAl1k{4L~oUFJU# zSr~n${-)xr_&a@_ghLgt_I`ZExT3;QWzDtc{2~cBEa>t5xV8GZEGaj8sSXu?znpl= zQ9^Fo@*x~6LhG?#fV1d4nV`T6M^P1m_j*rTTJp5`TN`P0sQFM+^oseO`cj4<(g4*<;_%G6F;oa$F+ElgfM6bpT9dlyGNNm-AEJScQUof(2;no;i zILzsnD4l9z%wpo9*JcR&Z+^K!XRU09Rr^S?9q;)}#WnO=9nZZ{#g%xD_UX3tvpA={ z$X4cy7|crJu(P$hx73X?F0#Ib^w{J!O_1G;V5-Mmmg*QPB?gMY@vgj(xo5o)M{3pG>giuTq8F!@O{?4aX>Iz%@rsaaPdMG^ z+V{YzW!fsyw$)bN8dw;#PnFtVB-r6ZIc?o6+H3NTe6N$U^X`CGeGAiWdN|gW>$LX8 zhqDG_lpGqi@u~P1y=jETCXx;PhEcXgG?WsPUy3IS6Kee5v*3yxPQ7M+^q=R0R{Ddk zF*FMo5w+1a$>TLX09Oct-6zwyNGnAaX@{blK4jZ`oih>fNC6>kJ)4A1h`;3=AEkAP{8I$Qgpnns3Rf2r| zV5faj;p4>iWiaLH85p>dVNaJ-Z$f zEUVTIUqC8Z0y1@?e3vs4AipgC5iUXmGSShVgU6(@6ud#6g+agjzBM9($v$~KP(_f4 z9;_fFIbqrn-qIL)AXr7Z#~CX6XW9^ zO|k#XA`k#&J4=Oh(Wd=1E!pshxHo8RvMVoYx$aH+GdBQ|@A$)XT5V>IX-)KOy52CaWtKT*-!Hk; z`XN`{S!s{n(KWgIAf$1UnHaw!$7H+#|4K9yOXu<{h$bh++U#UOP1`~J%4K1dMJnm3 zzm^-CW!Vp=aLr%g=Q!HFFZ%i>%{)$+3_2ise+)4q&{lxoULz#I4#Eiw-tv;|4Lvw@RAqZ9YLeC1xb0J~j)LIldo^8*&$A-_aBy)2p(zpA|46wkJ)zT({lfO?UVk3{lU9GC2hMrf*}DblX=%U{v!meP;q|B7 zrz6z*h0n4yIp9oKGP$%6RppXgow3|>j>5&AUB2h7K;Ch#yXOC*rTY&bT@9$mUXHFU z`h>6k%tXn8^a6@dX1^sa>;{>*a5Zt!1`>Ohx zbgu>VA{_awMr7=dA0~o1h=K(pCY3y*g~7WGpEz03v~iq2s`TrF@TD4WNxUZCkh+x2 z_ayZpRvGz6A(cCU4Nuo29PlNXGG+3tDNg@urO$(yshg%);y$|eg4*xjqRa!|ErRa zts}1)%eh5@AdMB7whLEB<7cl%)@@^D%W!%xT;(`Pu`+uKMSlbimz7yKrr4^`*`~G-H7Te+Fn_3 zgFs`d4HXpLBwNwL73Jq)Rg1R7+3a^BEJ=X;5zG4x^0ScrnUDtKjfiU$x`yq-xr2D{ zL&MzomQ@*L0$0X>*@BoYOwxeHsj*aI(^DJBTc+&I{2gJCX$m^Wlwli(2N(IZ#hCsk zA5UHYNX$S?iytT1`N~w0T;d#_3s6`Q*|7N}nu+H5gwsnt?XBZdT-RH?$hkz3SH)@i zaLJKy{#a>byW2i(vJw*|QcBD!m5DL^Rvk0m&4^QCR+8#RllT&IT=7q9;&!ZF~7v?S7>@O>?!JEW#u&S}dUq;NNNy0rv(({>Lv#Iq`y^IaAIQOkt#c;vPoO@viWK2ei^X%z)0v3Fidkyn12%I`)Qk3K6jIq_z>F{ zC@QZtW4m%e8JXKKZsrY`GZ2$%yX35mP34?*c?O-4Zr9GI3BKiLE*B@}`Qr=5hdk5g znK|Ql`-8bTm#fTmb$>92oImRSoR3=p$&&Sht^KdP!4Axy*O z^X?9rA1NGu`kLGA0gMxS=>wu__7;NlLBEoNCQMc^`~YD7*};JYUJ~<#YyP~4c%WOL zQigrApFRAwNZb;;i=mc42riVCxI?0;3qKUjxR?F`|DZ|%ey_C3xV{lkIjBG&=xh-Z z^FJf&&8!n&$Cou8n`}zE#EtA|`!hR zcio4h)JpC6pnw?e10$niop$cpf@u=Qn6RNUn!xJd#-VNPeuoURK}9*V)IMx*f1G)= zQiin^z9uHJ>Soz{8maqKHRSpJ!(_#;i9zw=?F+7bW8rcy21LJ0J1Y>UWZK?9J)wIP zu4R9k{$kK}(UNVx=kjI*4UEdHAF<*SP1+Nr;EfzGxD?97y3aYsBi3^<^POzkyw$+g zUb$zZXW|XVtEY>$_KjF#8F_%-=UeD4@0n4bo+Kn;Z?X(HFaXe!aCy&Saag7O{v>V1 zx16z1?sP!E5%h{i+WXhiYw&?I!ei115LyWsdmJu7kdM@k$i8-y9}E$`3jG)BxUzq1 zuczO^VQJF~>=oA*LoQ-SQ5xj$P+GnK=_D#pkV9#B{2EQ)0Aj+4vR1N7vms5PAq;*> zb$stmE5EGH$S8^k^^=BH{)_T)EQ0n&8;9&H6<=mN#XRtcB`Ol#rmOdOQMjimum!e< z^y4MZ9=MWO@Jpp!c50_wNFNNRJR0Tl=-DX0 zhd53)Ojdr%vMoAqFcA_qg#F_vrtO4=U=G%(RL6?EQ$@?&uYelzbTP-lq;!(D&A^%X}LI_E%c+%R1W=5NS z^9twjv1K=_ZMPO3Orm$#^6#v)<+Y4BtuFS_u3VF47jf~Io;59_;N64@WM7bM>cK%; zpwaSHW7J)~Ugs;0K5auLz1Py41<%JU_jcd-zFKf8KLCZG^g1zzm&+$tFl%(GC?uJtY`&j#5TH>TNq zuiizxZnw=QP1o7Wn$O{80NXmp3+LyKjuEvEZtaJY9jx=g8PP>4To(bGOE+&q6DoIu@yyBd6y`z6rul7H;0A58y5ykpJN& zTt!a9yk^|e^V(f4yLId(cSh^Mkh+<~;Y+6cc}xtm>TR)v)7pbEBj=5A0!!CJ%#W@k zH{EJ{9A7MRCGGB4IJcB(E|I!1=+%buoUOHB$xZE)?PjVC)dc@x9zR{gcr?mv;>B8L zFz*yqdwO&AWkiVMUnb`uK2pz&rX0} zj&oj@%7vTJ^yf!i9n+sW80MU94mCE$_vyn-Cpu1+yJ;+zaK*GO+&ls-ava~^+kPhb z&E-_~AYWQ{bLUp+h25QH{@j8klIl&?hrBIi1V*#4aP%sXOqLutM;2UhtMl!CPG!K{!QVF zE7}lD1^Q&so5VEuWXj=hE;RoOgAs%34sKG=LDBW{lmaNd9Ox+nvox2Xgs%t45Aut} z`Tnq@=ZP$|Hh~!7*4mGa36(fZs|lDk0Rhx)2yFI`j6A|0h3O$nQ;wU9@KOuVgh{ma zx@h$FQc-T1*RPnznI9R+QR&#tbFFogANDii+>|8?d|A~hY+dH8W^8@vJE2sblUsPY zJ09t5BVMm!t507Z?TL=~R4=5w#p_|UO=;PMFA!;*8-pRS}ktPQUe zlb=shb@{E|UFH#&Z{qqc>r8~=QxnlpBZu{e(_-I=?>i%wYMHYJ`-igiZg%mYWC^p= z=PFQ4)z`!( z*iFWIb)9w4G_v}MO0)o+UA~_iHxbH3qx?;rcOfA4PV@jUl^ z4eMIhT1(^WOn1ArUsg*SdbP0v7o6obeM%3w`7(^m@@go74lOuspsKR%UH zza&(4$Ui*RL!tmEC{`cB*C!1a>yZilY6tDME0j=R=;@d|F!4@$+iRZK7e!C2ppAlcj|UTux=JE5B2# z8UyPxnKak=NNfsKQZQ;-%QtZQUQU$CwWxQO)XJ@o@^k!X{;ccu_M(8omZutei@P`9 zf520F?utX4M$x8g-9>la0t%6fok&*KtW- zZC0%GRj2Z)#xj;|e=dnQa+>9ujPzIb^#oXD*N+=f74AAvVfj;E=GuOdiPJqc;Y&^m zXeA_)i{>XYcKN!70GvQC_gsq_bk8kM4(Q%_d+M+)*Ce^E(?o_{pufA!=Z+rkYiJ;^zZeS;Glg?sIO^L<^AXA#TZb6@p( zzDMR2x>W&7HJd`vSqz@;@cP)c-ddQ^kv~(*faPaHT#8d~v&-J1=R2<0VZ)1l11%Ku zDw5tbhXQTJF5J9`8`e6Z7@hAZ;UgFG{Y~1ZU->ckgTGgsTWkyBq+9GikvJhJ|EJKm zWOQ(!x^16%pAFso&(kP%Tb6qJb!wZ2zunxn`F?ZnmPf_kkNK;gpFf88E!4WLg^v!5 z3uSZLRbRjIJ{)#u1p|-DC31)5@2CbRc`K_5SDq$z4t7w|!8erc%3P5zY1qjT@%G%d zSPy!Wodevf>wX)Y`E7cH>)nArOE>wg2iKj*Pq>z`r57ATslwAX z<9H)F=}cc0n~JFJNXU8@W|Iqte^@s=mR~t&WxP@YYQ&B7EEb#AnJ-N;ySi@G^c&Xs z2OC+66)*EU(g)oTe_?%A!+Lf##wZ@a*M}RNt7X0y#68-dZ`D?dT?e5(C9Bw8yUBq| zk0;nT;>Kd27uJ_?*0yh(-Ms1cD%Svvb#JuPKl#bmYH2YX$Z?`Gu9w}6@@LL1O@r2n zPirU`Otv<({Vcn8MBmR+=FrUO&4~DQOpE*z`Ws?9T?Qm<(2YR2JnZQ$8Ns~(f;p6K*zDS25}Ud~71 zZ{B;#QvUWspi=C;txDTFtiRo7JurL5Fs*S~w8HL4R62`IRr{Y6i8k=XVhz zig$e~%KUVJtaW2ec8=xVGO;?k;T9l93>p4RQS@6}B-xdF11O9qW9Ah7Z_J1KGk?80L-~9C^&(64cl{arTr*?r&hFZ5 z^!jCWK9>TWQh}*}co%zJp~-=ZpQN`T)W4nToajqAy+iQphx8bIch{nAoWM2dMB?9GVXZ+-Nhyoyv&=)IbYy_L(s2cIdrj4kfpd22^8OG66p zyN!t+_H_=7OzV6_tom!dZ4rA#8z-&O=t*1cAU|om=(?tib=+V0v(~8U$P2?_jiEIIt!b3hF4vE!5m<>Ve=T2U(iHRKiPfLY6lRmZ^Vxlmn==08|_*%)( zBIWj~>zmRzLM$Ba`L0OJ83!-smiV#enC?p0dAH4%D<(SKBXwQL;8D9&_eTRP6h<}O z%tB(J9}PDeOz1=GLQ$PrY&?B2x6J)+CRkQ z6t>_RS!-)z7Y65wXTU7q!7U4duW*dvhDK)m!NYI(IL$PvJlu?gLPd!*IzK;_i?OU) zHCJ+`vR``YPglQV1n#BCB`&x{q@lA0Rx_}X6{_6);urr6w+D%a3E<`C~En(#5y_C}x1 z!`=6_${)#pwO**|JAj7Tzn%I-uleQbo@4BE%ghP6{3jND8G1|tPt^n%z4qSzMSa(x z(*j~$-%Uk4zNRJwtix>>=BiH|Du9)?=lo-tE#Kp3#V!b+`$s21&*T* z?;}ugkiFbfrwrk84?zisgvI^z;Tu`}0yU_Ewf>j&s0f zpzDaJGHiw}Lg_LvI5-UhV8}+*QfQ0IkA*_p+soU@Nh`mh5o z?dhu!UVH9>VB_8JfPddi$Y1e8VOLu4*($(YwICc9*2R0R7}H$ zj4H~a=Y|=&t(g|vU=C&k5mLCXT8(5hPr}IfdGkzU!GBqNyy$E zf<`FTx1AxL4;{F!`g;}Y*OaVbG8=gNNV@M`Xa>tw7PBWRtea(Q4{sIn$bNCS-&giu z-^Wh+y`w%VPMD*>ra(hY?F1|m?%%(Uy?JZ$z@=MM=@}S=8V6|C+=dw@34)R0v}a zE9gfX_e3yY=nhTHD^O~Bb1vVYU^5ko+)N3Vc)HIb6j3kblpn^kMT%Ov(gQG1d2Tr_UhFhI6k!&x*r!46C({K z%#E~ogZd(lI1K9~=S?c&RjDU7Nn5--VWlBv)lC6G3u6de!g4VUa_kUqSJBY;toH2d zUf8uMK{<;80(6822n9>7%wNCDzFS%0m;Q`B&-DU%I|e?eK%tG2karKkI1E<9G7!(F zKX3m_EwPCR-<%f64}{9>1k7V6q3kjV@GSscY3uiKp4JCe zdoT!R%VaBGbvdo(p`mytqxrL6&!1(hCjQu1CePBg!y~&ac7@73zN_&fe&^H52eFg_u&<%6%PQd{2zlE$d^iyYizNJ4 zA__;_yfjm|614N%YM%Z3xrKxn^7ZRLmpcuC`NoG@{HDTeMB4yNx(LTEE=X&_G9jM* zvA3_I8oIJPNN|wMQ(Fu9 z1S$am0Y+KU2O`!PnV7^m7;|aa4}4@r5QK9IF_D5|(Oc+RK(pAq?5O$q%xlCJktw+b z3fj=fz!rr{td@xCxpS-ndE=CX0|*<^+u;s#do3A>(=OSqfb>Zo!iZUgh930rh`KgZ z>#sm*h`3fk+6B@B%$)k(^%QzcBDCW!HI4tC#vW899#zhz-`?34K~fKqs=R z{0ce$ehQ%w9|H>lkgbQdvz$63qwK+OhyCu)*IPRR!G_Ke+?Fcv5{WkxKJOl3;dB){ zqoAN5)~^hU@E9XDkQmgKHZ%l){aIaI-6z&3K+J3igBj!qs3J8!)Z_*fPW)(8A|>v_ zNlaE&mdL4Ju4aUsdbC5H89(YN*lR>L4?)IAV#{8Ym+*cvtAL`t{7xukq5yb;bAfCd zbFa$)*#ebDx*hTQi@$LscM6sWryvy~BEl4*A`bx;uTcr$_R#Wz6PTL#J9-f*ga<7C zKxve?J|KXtqocbFts`O6&oU!U{*6f`QuvB&ey@rxMC?Nbjf{-& zzsWz`{sK2UBs7!^*+@2ln`7U?sA>nxXKO4cp>Vhr%|l`dTx~-ksvXY`jW)Ag`~E(p z7F6|i9y`XKuYdjLr_-vc6%eKR$M_fL;z`Dor_%|n=Yt5FYEsAHFxsI2*9YR8gli?O z&Y01ApY1A9&4%$3cA?{2K}3$@7pGG=}d6 z((Aj6C2L{2q>T81Q<41TiH{e3&&g8(c)HTn`}p{Fp|r&{_&V^=J!jbIg-E&{Ma7~i9*M|H zqd^p`CW4n+B7y@0W$+i*hKWcgHud0&!B>TVV!BD%Dyj_8kBKn=jwiI<_Z>KJ1v;&S zpa0i%MY)lZ*dRcW)VR!tNfb_%>~ZB9j9$27TG^IRc7>nkBa~DOEG+RF@4^yr++^hB zU_+vU)3s%fk|ES@N%FwGGBV?JFNW4(XXT-n(0RJ^FQrQTl)Wg#;D)B5p~1t;i}X+{ zNMnS3Gq`C~3rldl{s2kTR+Pa^gH{bSq(mU=b zgaUu`Lx?eJd1+b{!eG5UcM9@rM3C}jiL^Jb!=K@E(8!a9)+RLHcZ-UK9O-hp1G(;f z8@&~?;QvlU+no*5(Z)B*V1LIPIWF;XRt8#fd?dyvJO`2AZZ zv1zhVQ)^L2wTb&ndS;ZI5kRoZS~E94>JE3P(z} zg^i}eaLYxWVXu4lra;Kj`bohM%?m{{9Gy>v%hLk5Ye^2_y8Y#zoyVEyavjH1AQ=d6 zv-NnFn)~!ddSN1o4ud$aFq7KQ>5Q_GxRGY(<|>|jM9<5MJN%F3?w`@l(~#4G`{iYHjvCjt`XdgzIKeG| zh!R7yG#j6n9W{)cTsiyo^YDH^7!}4_!9LsM=DmrX!y_Y*Su!m3-h8byHwa%Mnuiwp zfO|znb-o^Cl4=zD`)FowAdA&o;eVFJ^F{Lz@xO%cGhqB}x%h>i-OJAh4&gPSrD)V+ z|JFSIIsg&&s)P%oV%W2Xd{FeIobOT3UNh5T?y(kK8ZRb72-O1-q9j#=@RWd=$AuZ3~gI6S;(Ck?QD9e z{bK~3Hw4ZYHg8ssTYny>;rD3gGgym!f#Fw1Z{rPSW@bo?Lc;Y9yt~=6h@LopzrI$J zsGuX`99+;;9QYJ*8gFac;cFk-+n<3DO>D4OPjGw2+y8yHLC?`6q1P%&$V3APZN~|0 zq_E~rA*aC3!U+5om`A*3KSyI7MJ&f)8vu>k?cxr;(Ak69*!{x7LcURbKjcZuZWzPa zFiaa?xiSfQB~eI(^?1`mO_P5hoiW`$NGTePcNdX>ho)FQTm@<65Na4$O4eXEW@36e z7Uzuk|G6#ACy@Nl>qVN09?8zOh`29<^+Tj5M=^(A_lQq4IgJ^mFy0_|5)_cb!?(l5 zkO;!zr@-P6dY~XUMa2o4(742r*T}afiW4 z6}RFZ%O>~%LU)fiy+O>j8-?H{#DBO~!63>?Ng0YATr#!0>kOr6thfHZ)l(2!2v`N< zzMNEN#2Xxkd3t9jJ2Vu@r{*n!$~Wn7@dkg|9bH^p3=zCYiD3IQYKpBJ$BF_aU3_?7 zgSgwGDCP^0ha}$I4&g^psrKm#}o#6eRKLW>f8#XR=I_v3Q@g>`YA{3e#FMb8NnbLZ;^w81FF;tw)v`~Nb-7coF_Jr1Iw2XJT*#DL1nho3|@PCaY%TBO0$lyjDEerX ze@dzM#XB~j2F0V`NAUP~$i-lq%n2!Pmb6DGq%{@i~H*N8W`2(Q^yC2rvq_&j*< zHX|cR)1U;25AXSBa|$P#Tl6n!XzGB5Kx|M+Noh1UA|{5L#2i$zA0xzPHt%6{Cnu1Y z6CmX-gO-YZub9{-ikLIwf&O-mg}ZRiWs;^VM)Oej0ouFz^$n|T5;GnWLZMaIf~hyl zcxP2WZ~w0l4pPiLR7#&=Btu}Vdz^0nB>BA@8US(BB$d;WMd4$pdTg4JYzVbD$bqnZ_gVh8US&xKqt?akohf~Ve(`(1Nk!Di& z3Z^4}pY{D?@@~uDXOIm2pK2{SS#>o=FF4Y`xJ&`1W79)EPp488#4QD`;^6L-qn|@; zfBfjt4)+VqLcobuQ}`eI6H=|f;fHQE2zBxV!I?I1e&Miv6>{n0fPjf$SxIOBla37b z($^uN#jUt^H2BV))le58CZNRi6esohe`Lk39n5I-NLFm*L5crbkfv9q)0v2Be}1U| z5%a1C0$jgR@kvXJJJ20PIuEdt>K=A*jSu&{uEdH6^@lC05-)5XzjlOc3~Ig-GTqhE z3T(439fKb)X=`|RehOsyA>Va6egh;!AOZ;wyPlKhbajcF``S$$A8~7u5J{(m?0OLY zGBopmXZ&FjBIBPzy14SwCppCQkPz?xUWE0a(1#81^sgpOv`W!z#AOQ6{3aS{;z{E) zZU|{jRZ_P8G^oK9E~;y`S7v@D_*bS7F17@`D^o zKDdVXJh@*@sK3sVXl*?=k}c(JhNj{Wkyz#AtOJdThL-jc8l*LA))<=c3fTYhL8l6*Rc9zb z!Y?|b4d!p?F7c;|TU$dBVHGEG!_*nQa928~{t$ozWy2mU-@xWWTTf6u!7`%qSwKJ$ z!E=SbYQK~JHy4174sh!w)c8Uo=V)HAIrs1PbTS6gA!BC73%hA}vEuCzF;P@%^%3Z{ z#epAq!a5m#mS*xeE1Lf!p5Zh<7}uW@^Dx(@pBAoxu}B{onc2^xqErnHcR=$R3Kr}C zCfxhSap+;t)G_r3#@Ht4QQqjh^#H(i=Aw}L8EEB_;)R$BJyDf}2ld~C34D+^zdgiJ zFf#kGe+QT6Ys@f2-4?UIYqg_&f#xU*3IZCTBb9apu0NHUm35Y!g|*puATs`tl;ns| zxre1?L>um1yVhV^DWi8?5Z>v-l!AF5#G{NDt@{w!B)@KGC@Xx4f+-C|nqDewdA`U; z1(!#c+&X|xqz?UdbBazc*Dj&jDVkOU9+s#FwxD)~z&ac~sbO`GL^XlNfFw|Lc~aI* zs1XlENTYj=<^cpUx_sFSs?)${(99#n?Cf-w@?Ox^(K!XC57Odr=lfuUeId9tz(LI? zBO{}2LPo?Gc9#yGijFZK&g$q?LIT0O-{_4p;wd+GZ{&lUsON|QG63N3y}jIVhRu-i zbwEc*yssoz7Hx}*i(vw|4#<6lEb!f|!ke#wRDG z)EPl>4?XTKi62G2exn+ie56jS$dYji(hblQ;=RP0YwQ`nA76z9PQ|Fdh>vx~AFaN& zKDmIpZ{JoT$A~plms2ktfiK`R`dTo;NehRYMe?R73P{o#g3MBf+8LEe(xkitTt$Z?@dHMbKTwT4zHIJEAEXF4t??qx9V#w1m5h8%$4KA}$a7?U z!7@|H4&`AySUY7#J~G1r;b<6oNCA2GECS=0^xa z_N7Uk&Jgj&eV!=Ubo@V6Y&`*$VAx=agp{bErB#ZSxFyf|5S%W~iigoRNdYlMZ?qS| z1ZeoCzqQl-1*<{?MnNH=9#FQ8;E97e_`FJOav311P~2FgjvmDQSNQe*<*~8+C#&%9 z1-MgneIu7y=s%bvME!vxMUdgomMu9BBT!vmE31x{5JbbZ#Eg#|vQ@+b8>K5cFRS_A zrqIdO$+Bceh4Dmoz$;spAi5Z-K|SAaZn9rUEO)JqgTny!U&D{PFBhnmZJ?nc_6Ij% z@4aEWXsQo`&#&UDs{8viUkL)0z`c?~an)d!LEHzsyA9`-cs#SO<0eF}6-VO0Ilwvl z2UUc$G9|sZD`PwyKuu)K2+6{djDHtonqy)BfQc&~{f2Ai6&t{VU@C%}syNtezJQ6Ab@-Gp8 z2?`O<)yI@cFLDB+!WaZHt-XlbM@vg<<>-i-6vq~2DvH+1bXaDaAx(;6F!LYPEu`14 zz#2nH_=I;ML9&r{3BVQmrQq)SgBNE``rIV$CO|of9Uq_q?UP5<^+KOL+k#zCgl*(_ zl9KW!tM?PM8RJg<$gQ6A?g}{eT8G?+Kb^+M#NkHk|DwrkVto9$NnIp>fwf86`z;ow zXquQ98LhfMtol!ALp_Wv5qQrfvvkqaBdf(46uQ|| zJFx$*xSvP3$f#xZAZ-{!ar`e7rcR@-Ov>t@j<$&4kPyUHHrPNyavO#OxaT}R#f=G? zz-V3~6Oltja|f{7G$-{$Q|ej&R!By|I!gsxd18~48-M-n+CrX9*1`Ohnkwxz-7G6 zpvlye!q5~+?| z&i6=w1bX`U&ACGvz21tG&v7L)aH4Mf{(17$DepIRJjAUPVeJaU%5NegZAM@Xw=2=%Dffr<|Z!#|3@Ib`!^kFYG`;M!(%XmMiHVQ6O)sV%L2XVo4WodpaIAd zQt_{Nk~YD~OM{_1S~c@)0b^J)KSF{e0#4wUF*8=cP%2T9wRmI>r&bTV>v6QDY5(-| z236~1l`lh$A7)bcu6=lyCY6u4@B#wiUQb4~=nu#RPzyi<>U08CmF0%wSvc3DkpZ~- zyvYps*4%M=~TVO#A02mtP6Q zdGhIC0D!R}lnG>{kK1R9kq@pv8TSl!`{8u zlS*&zi&r|Ur=YM7UV153uaopse&CTvB84rCTnw4I0aK~!6GxY2^ceWhnKH~S_>)VG(U{P(YGM0|q&KMqA&LLEY4-@V|~QQCFYIo${p5sQzCe2&s;y z$GcCX4~au-fxkj_M0Caitr|XED&~iOOH1z8A<;a$_Uw`T?WUV~P2vCc_$i*o(V0`@ zK}nT&z$nP8Lhqlz<~i+nc`CY_emzkt$3M^xOp%kQ3a5FG-4w% z3g*rq@b&&qc_QiE2l0Q$@O#rOz~m?rV{M`rh}Aj)(SQnJj%v~PkedV07ZRdg>@eoP z=d$0wKZFVoE{FR278o<4{uxeu=UkbkhP$O09D30j3ITHhG81cL;H^s@_3J+~%z|P; z-+#a{x=v@*tq5-T5Wova#PsGpqo(!&F{cJSMGH_9M8Mr}PDKmZludb`eg)b;IjF8N ztwQd2&Hkf+6mf8_z-X!Vo^F(KBcD(7ICwGYphuvv;V?5mTtz|p4)b<2Kbm5zWFz1p zSc9X-iYgEt8v&ls+Xw-X!fkt)kPt}a8PDGrcdk+Nf3LD=CMps7(JB*LO`<=9SA~aq zhXEKYZ{J{i%TbBZ1)-LpOEkhfMdbBDAMp=Inn5NytMVk#2-T>SmDLWE`Wh;AcvRS! z1fdJ7{=htO0%KV)2=)jHK0<}_S8}5^Ocr~db;oA5qrD;UqXZ^p_svGWj^^R+V=@dT zGbeng(8O$$RevdtR^Ug_)2Aq4v2yRyl4H9Urj#U5qyM1_*lbNLmESI4z1rK0liqL3wOzdDJj4Y0imv@ZYz z)L~n9@yZo~kCQxKk+T;Om&86qm72E(nAmL`KVB&17+F|&98i={ zCuY*4Jt#9UJ0V6NkE5dMNF>HA9JOkqy7`lJ6VHJ+B{_JyJPitxMcF}uFe*h97rdH` zUdZs;za$NAPRK&JPD(?A0nqWAM%VQhkrv@ILuR5F^lspg069$>hYgI2Vnh}I^WMh+ zb*u0D-{*_VJWNvFh5s#n|K&_jQX11@lF*0>>=cg`8IZ;%tQauNccSIMamJx31yA|zJP*(gb2bRDbj7;6qk=Q z|FdXa!-<*&VhkXoHxAp?m0`k2DoVJh>nCinUV$dbIJTDp9zD9{@2?s=a@PAes3qJO z7^2W8ZQOeYe#ns=MSo-Rf5#l&=aFkVns7M&qdjR9clbfaB6thu=zCw^F7)R#6BL*y z7GomuAHzN3o&v*jjCM3g)#qXb_g{dWP6MGPEMhh<+pJ|oY>kR7FEh`fm?um(Qa7OK zdfNQGCm{Ra%ijMq$sh@yUpHM1=Z+G^L^m25G=;^?*@cDP z|Cl=B-RYt|0H+6V(p$$dePR=gph{f6|B^<~DS6ofvD5f_ECG?Dp zS_up6=-EjRvoJFjhY~(0_d3_IFQ$#aFn5DKiBeThbv zc0W26rqKw= zrK?39N4Fz02)!xUTN-gyI7~4j)#VP%yD)_#Gj|Z3E?v8JVX%U}r|!QDDyF7NN=oiv zJpCob;5g|g_9_IVJgu&&0nfF)o{TC^DJz#XHl~C81XiFIyzbsi??R$}o}P}Q&4_72 z34*Paot-g?K9~WfYpAv}NJ-p4!SPZxO49CU{cY>s@&tByEhAvQ@-ctj|3f^T1xl8~DGqFoXc7-)r_nKrKN z*YpiEjey73CnqNZc_i=(W=4Ph{P~-5bp`$b3xKa&#ZN})y^JR%Qweru8ufLk<&Aa9 zjbTBD2+|ZD&4cPmyQ;v?*VhTGLnm-lf1;+0RUT*RISGgO|EG@w977l_D6KKdx(!+k zpq2RPmz8JlT*$S%k9HiyE4Um<$9;hZnFlIjP$|%bVO-e#>lg3e<4@Dy*T-`7kB^TJ z3!FenNs7OVAO_J4dighkFpdIf6e4ul2j`xy9#FgD>C?mz5Y&gn-%HeM*M3HkO-ArQ z^Y^>1NOcGR5QObIhEBV|oq-^gp!@PoGKoSFw7>tyb0j@f6h9*~m*$63(1_MZb83D- z>Y>@NVU1y9ygH^>a{V$r{`f8En6_@U?I~R+?Wlk0(oVGS7>8kS-~m+lpDC48{4g*b zLN7tjg{FiUNh7w#fDj`zsOIgW1<0t-ZHYzDkOC?FLdJ3{$&LusE$!`xQ0kjSoYCTh zJt)d<{AUp390l?42BLfNV(s0@D*h0Z%;g$(|2CT3=I=pG0FL^A4)%bw9^xF~q` zGdFw*e+sepGp;+hdx^hwq$GGSzXDK55J!B*%izwS>&I6{q2~5{;~++7SS_22YYz+x zvc_oQ`J&w=So7gjR#jH+IDVYWwBEdVGkRm?A3BUIVkj=9uw_Sf@d(Pz3k5e>c?_P_ zV{i}XKtog0+R-sCJG&)ygmdMqz`lK40s@_K{Bo z4mR~7*doOL9T{z+_Qm z-M}g&Lkl3n6Hm9iJX2^p(k247B&Mo?Z2_Yg0DFVcbHJd0k4kdl11M7|&1VM>f`uak z)U-9-R5OS}-n-h+)U*nJBS|K>4&?8_X?P1lc(OsE{$Ry>ToZu9Ev>B$1+Fgm`+-cw zq577gdqq-Jc^c6Av>=_q%!-veg9Y6>Oy10TWdjRlf41i?P zc!SIr9uYy8WrA8)v>bD3x-4aM28)X~ODZq{V?myhk7+&cTXgxL9gnbiH5 zv4W}8W~JxPaSY|@;hW*lo}I>BhchGIBx!E}8k1LzLN9k@DhPBP)W~FhNZvjE{@=r` zPcXle{M{_2#Xs2Ratc@^{0pBVXP|ySG4ta2^ILcWgp~mZAD1ROC#Omro|fvX!ILCe z0JIQUw3lRf9p(CmH8V5QOnytWb|_#{G=0?6)Nm~Y!A6{S-@c0wWL*SP4@^!S+J&Cm zrWv|`f;IXpNXI_dnzomVi_5(&XqSvVs@y9fl`bIfppU75JVuD9^-j!vq=JO)pT&G5# zmYMm=eP#J3NYWI>=Elo1^pY zT-ta)LLA((h2R->LImBTl-+AV5;>YqrsVhdRKVFVR@{K-g>zJfVf@Aq2*(>_3!v)G zLVFW^IH5_R=br?R021%apKyIVsMb5dfOP+`J1!G2F0@(?(43+7#8|gjt#1K5dBVn1 zTYi59*Z0I++ig|Jxm{jfN7hr5ng&sc(1pRSeT&`@FJk=(P}4AZLBPBR=6L(s>m53j zMyNLKQho#Xz7#{W?3;5!m>UCze-s#4igyu;G(mxpVj&%s+U;r2<#?$%yqCC zPYj>Uh}h8fb^foQ-Z;9>EtAOb5nz_zU`mc}rzLtVfBZ~QpQ8=uNZ$UDVnPHr#Q#GDqI@54y3VTI`cR{TOr2T z=QmwHAjUsIpa#nfi8uV1%TvVhRj5-;dB#^Ei(qR=SzWz>o!SdN>^V}mH&x%W-oSn| z5Of}3*HUOO03I@_G_ixjY_R|e#ZErH;Y|PD-riNSSf&6Znb&JX4FvGWhBc-b?qgP- zZ1o;R7or<_ENe1n=jWdTxJ;Mj<>|?iDRS^23C&6oN2il3w)}ds(|~R|`^=$NId;^B zD`inSZW?DVSA+?zm0DdRaJ^;Y%KWChKMZS@yTq2h?@j%+acDue@m{|9htf6G)jM`j zx+UGc&hvH6^vCas{@2|We;=)d9vi;8q$DiGF`wjLNjt)E1xd6=kuBk4UHc(LgNE6E% z<7dmAGio0_M=`0&Ik4C~_HCj-`9@vQ$F_C10FrIGXDOhrt?QZK#eUYGy-(`1ZWvX* z>^X(aubyrU?_4bZOmHgMmP@#@g}3#9uTO=o6yJDH$~HKQ(=T&>kI0o?di0xNbtaTRzRoLm3u2SC7QJ$YD)ca9q>#N3Gm=khOJ`igV5mGgny3u=zdY@LY@WovvtH~l`$VA)-9Px`gB4+{kbdlSIX z*xyxGNmIUo^2fK}zy%?!XT1^}Ub`u- zZl&y)Cw)^HVPFl~VKu=Mee&7CT;ok<|78_qyLt^~O=!`y#4;6J&3}pIu2$35pg2(! zYWGVllhRi(&3YiqGbk{K`AlAv*D*T1B?@lE2g&MI$v0i!8EoQv^(H;!%BuDI`12H( z6O2E(r-k3}%fH&OKP=BzfM;=p<;O<8S8hhCzDh$U+4&~LesA}>ocM_%ymLZwZZGGX z%64HSpk?y$bg@cMOwiwok%Mi-VYc+00KKDdci@$T1paWFtG zV$+7GgiDDm0a+Y?sCiLk%V^z+u;}f5&J2&)xA9F*0lXcO}n{fIqOaD9h{NX7&!MnSkZtPdR^mi3x&(@Boh2>j5y9dVDt zqCnjGt?AX-#miB3M$M~7D@uyWQ=?puN8IgDq}5r?82sUf^?1jlN&h>GmJqz0$vf9A zZo{Z?#w|_T)cEr3jo7I7vmaz71*p%LxLvZfiLKl#L-!6{1sND3DO=mypAr9Lfzfgq zp)i5yy6;Q6Y>*mbe}Ga;{#n^zRZXh{4}hhxJUg#Zcy8OIt$Tc`-{sSsmRg?hm}k0N z)=yPjzMNus&9bQfqh5%vSb+Px#oDQ21NYL8DU3@qK7{vntN9KuW<)#HnIs zb6S0RMW*!JKJ`M^{LLK0`4((9?20~*tfO$7yEI{ub!4N{s*1SC-d_#7e2324EE%s2 z3{rm_CErkXh*59W>svwBsj2T%2|wbkCVN9K96#f7bndkjpsa%;BC*-oA?SCK_4Cbi z3UC~J4;qzQVetw(OF9{5O~*#QR91RpQDX{7hi!i~69m04UiHa6KQV%g{nIX&HjW*o z^arcfl9FCk{{$YN@9*GZf3drw6V2`SpFb;eY=2b3=^nF-;;~^cT9*fxyyms^23KY$ zdI_n3OZsl0fYZv}0x(l(IkA-T{L8V$eXl`iMs;5=_(HDZnR)Yd=0GK-=c-xlS?gw( zgo{3FwETV^w0$D(@F?GLCbk{fXPsxhCPj{Jd{yYE)& zzA3Lim##fg(w|;xF$fhc8OQvcSlK)ylWX9AWOT^(YBQ~RE1)7#*ZJRi)NA6uD}JiI zyY9hL$*fMxhIdo77VddDBPrBdr>5?rH|jK_b*n8cj@{3~YLs9X*L{JRxmH}=YFS>z z^5M(w3w!9o+s=%vPUv;>TkszCS`wT8#53t1_qjf8Tlq9!d~+kyA#-YctxMOmqr!KW zh-H~DeI1F^5VwhK!yFf6{=A_=>U~NeB4W2|Zc-;$z~Blf*LT|`=lZrRkF6~GqnGWR z{#f*$_24DX?~`F7=C>wAR@$@O4OD5Uuz;u>$$VumqNLO_GxuR;HM7?gabYm_|jnI@p|D2h2DvOz^{Mz>nyXSyV zPdGpLfk4rcX0nNF(q0DDB@YdlWBv*#ytSxH={VzJW353NcyGBvR>n4PN_LD-c&*>s zl5V;MjVeyYUy>qr#M?iKx$6v+@9{NjdnoB(DV%cMFDGDK9Ie2U8~qy+ZJx{y-YEN% zb(n73)TX$HPP(G?r5%qwJ~OCn)xWah)%!*=s5Iztu{cLX^-B(Q{`d8{7mZ5nxu0dk zJWyW$bSYdd!GG|sviSGbTaz^>-I&&|OX@tKx!gZs$j}#=@R)`|SWtv((c00Y^ZfI- z8I;_jH@*v<|LlIL_WLzO#hRZ#`#wizj9v13ZiJT~_dYwmg4jH+@^WVuUb{YOs_Yu8ZtgX|w&~c>+eu6p@?x#$zpAvo z=6jcAZYFOmuNrCPP?{Ub>P#hj6Kl6!&6gj|4eh3QWoJ>d<arH^$!ENglW9NL99*nLJKIds=Z8vC4O-Pk}n_nfuL66WTxf`aX2HGQHmj zy1p9_?LcfxV?bEV_TKkFJS!#;-~;YK7lp0Ocy{Vr`;V6!jcr-Sf))Rusc#*894xHQ zK0kM!V-Sg@I-C7xfvsD1tH0*Wks~GbeiBRCAGhsG-=HV2uzWqdJhS)=WvE{Y{HJ2Inj^438wmy7qp@-LG z_IJsL`Eo}64Swr$ldeX(d>bEKoM>9{OGw8a5C#yn2Ak*j-D@1~k$=mG$z^s5b&7{ZW!xdR6{EeXI5lFx?rw$@hb zMW;FQLHEU8&fjC-qEb@@uhd1>rd}+;AAAKQdjp%WH(*4`h2cXWi3lD(Onlw$YI*=G zD<}IVZf=smTu7h*=^DN0#Y(={De_;dwlXkCVHSusoRA}cxNnmaUX9Ja`tjNuiQ5D+ zms_fWAF1hQ=QcZ@$KDcMg+JP3kfgRFU8Hh+Y)vx}Lyx-pSmyhK<=!(LcbpQuHbp+& zC@g%iqN8Pvvg`SIF{g2bcCK`bmeN$$z2B92dp@WBeS`X_U zJ=&TkxtrRE`|3rbs$S(0I;z5D4*tQ8jsWJvPaBVjT&(@hY1*E>xz4F8yu(< z>V%EfSbF67^`b?`uInE@d|<3^_3Q{ksjH?v>y7tEr>Y+5|F*U-&()usXVM$!B=v$CA8*nGLRjlb;j<(uJQ((AXK)@{*YXo?kKzVeD?8|%SX^YbOs z2V0LndFB}OB8f?2yJd3F?;|GikrtX+%(waX-R6H^Bk(NjMAYb!>!^_;+}%qS7wjr4 z6T&vEN(xKkdbxF3cki2_psiPvhC(OO&)XiRbA4R6q~Egs&()I)uZ}c#-FS1K9q+*= zKigCF^Fy|Rf+xA@FA5UY?J1X2D(D|tmi<~6o)cD=W|5x$?)}{M=2%&ywJ!Uy{(1-O zA127$_JDF#T2-Y0#?3}G##pJner-M};r;WilbYVO*UlSW`O;x^e(8!u&E7%Evkyn& z-n_h-UvS7}K;3S_q{QL!ZrSkm73RH%eRs@ExobFm7cC2R=bD+?I_KPbRmD}z>+w*A z346+`BK4~~&(_y>QX5UJr3h&iE-F|M>@c}`JIB=)h4#g7t~y>l;b(>-g?~vrT8x^$ z@M&?a?Y*ZV;^{A2KTLkvtDMAs_45o>$+U^l@zR$krH9{dDruY_*hZhpekawmxng`G zlzq&4UrvvBu8AZ6kIL=tLO-aj&J{*|jviV}w~+|7b$OC+MKQNGv2MoD{tp=9L>PqS z>yOjQ%DX{l1&13^j-X_75N;6X^BFSlo^^B_JhMhdCborXJ#EXS)cs#|V#?*h6dDbp z4>puCnBP=ZQu38kzVv{mGBB$qU+E>ytXQ%jDIUuIkX`OgcgBUsI;FhkZ8&{@U~~rHjGU zE6}m?kEnN=^~I3H4Nh6Q>tC~kd@lX?@XD1lIrN;7Lps*12}-nGbu($`&w`59T}Y{ zd`j2f`|+qlKue&o1NFDP!Uy)9?e*Ih@HTd4M^ta!vGLkngO?0d1wvLHq<3^O4Mz@q zS#H~8Iyg5LBg)IlDp;5%enWB4amN4F`PpMmQd4G!H`v>sG>A2}jAORbe;+JXaiAgJ z!G5iTMc&&ZqJgeQr0*-L^rs|d4XVejwJ_5vDlets%*ypF+pW>8t2jUCoS zalxVA=G~{L<0ij3#q4-CQ$($Le9Q0O)#&o_^&CFur~kUByKS3LN{l#fEG1oP!*S2k zn*-9{Sk`>F`-$bX*2j@XkI|t&ho*kY<_&2DZ|Pg<$gX(th`KcP<&^vfe*C+NFGbAa zhm7P7CrR5W@*dFE*!uQE#lgiMN579sbev$8YP~YlPdCrV6yr1nPpM)iO=T8Rzw)M-4`=xFCDx>4=&_Rw{ zAAd7)aO#}=6(g>bF?`Rtq`4t2HMZ?mLWkb;@zLd@%+LPB2qtkiX}F|c-*-PNww3z3 zyStxk%%7j<&y>B<-}Z5v^Tmd2Zbj+Ag{!|qElR1jpS*mjs?(t2QnL%C+?lj(*LNMy zPQD{=kV#N=ugF#tY!U*p{ZSW1o&LXUK)3%j7q>YYCMeCO+5%D$P4~P zgfe{esLgQ8L1-Hzg9bO(h8(tCDYxYEiPSDksS zMS#G@WW8q-y|K-=GBWIw7uMy*ec2WD;sqCOaWByCwJ%FM(-b5zPbB-xU?QU?C~iL= z#u+B^I3-n~01}0koz=-oD}7a4PI;bhY~iMM*}FmrlD&3>vI9RNn^1E+1>p7^`4_5a_b}ww-5D;0>T`9WriN>mZs6n14XK?Ceb8^`WSaxpE6`|U; zKjDeBf3WbGEXVw?N#S<(=Fw`+D~|ew^7gh{+QhVD!*bRsCu$GOcG8&Xh8^r4biFQD zW_ry1%fs2X<~m!Ieos49BprC0@OfL(QO%OA_v!+YpPD(27P2a)y$d~~iu;+M>J2_sL%P~o zWeuNw?`8(g&ZuOEr>`Ege`(*fY2mGr&Q{-PEfSZi20t@Z`+ig^khsZV|4NwO@LR1~ zM`COJ=%8U+&8CimUfwt{JwpwQOgI%86%W`Y) zozir$-8C2a^{3|Eibsn8j@!t-*-uUME?1y(Dukv~Ay%k$>t$9Bgou%v+{JDR_o7f^QR%`!t?Lz0<4(xjh zwa3&!LEVR32gX?vHpdDp_cBf9-QD7B3Ph-22<*r3v7?|D0!)2CIb&gIso%Y9TU~wf zM3JU_8c&cS2nU$+@3RtzG%K3QjOy_IMi8n{yz0fXoSb(FYX0Np8t!5FQ@D!ys-74f zUG38D&XyLIBS(&49r+Fzhh1G=*mtt-y{qN)20gyl(NAP_#m^vywN&6XbCeBR`FOeC zXN+83xRU-Z{!`&WN72o;!7F;-r1!K=wTiIO;d9FJ{$$t^4z4}RQQ#z> z$(4LSI7v5DU}J>)ch^AK(9q9;oo`D%WC}TD7q{-OA2H<){}TLNg+nZr>d6kt%>xH& z@-06;6HMLK;Yu;4QnS*+|GMA3YioF(@6eacndk1ed35+GcUOEl8#<}Gg>S=je?np5 ztlr+A)*o$0BMQGOn`t(;d9XS+n%(St%PVPVU)6Zk$>iQohCMU_yu7?V*jxcr9aU(} z`qq9(n;;$FA8PD*zewBv4D?~}V!qh3=n64#K4GwCig}&Pa>m?8cQ0HRkx(37gJiTSIK)zrqLxnT@p;M zDQKaeuh`IdUtoDop65Nt`IUg}=YYwt+UGn2hr3^*yf5+^oF5DPsIOlTu(cjPI+d9WM!K zv~W5<>A(2nzEbdG&AW>`nwon0cSkd+XHWQFXNQDlciMn?83 zJA3?Y&-dqiuiqc%y3Tb@m)Gm{d_Ercc^iB;TNzYT;$+lc%?5=&(9RqG8enN|#|JTXsLUoyC#A)zXIc)a@SK6 z2SS3Ytm))UOgCO`e4MdM-fwV?@9fO698xqRMTPa$Clg!&S?_R0^;ThtkDl0_=v4HN4PbpfL3S+g#lecE6n*toNx{<(Q$_YjrKiblj&4j0 z9xm*ZzqR4P^~&QPk>Dp^sj)Vac*OQ8hf+%;1?d1xUbyKLR$wapEK~iH$L4L0GG4f;jHw|p+A2B zr*?uWR=r)_+jKVX^=H@BHEHDCpQjebn*{4!6%lxoTqN&qWVTmJcKu(;rRY;D+o+9$ zN)uYo^PU#=d+yDd<}iDKKnhcecF13NG6N6%0sU&JndPk}EpO>|?38#bK5o_RSr4R%q$oMk~h4O{f9GBS@P z)Y~D0W@I!d&6o(h$EVpUhPl5$Y-3qljd>fv9R?KSb5j%L>C@SU67-f_5l5a=bjyU- z<&LRz`s9U~8GZGdRp|I^YljujeF z8R0v^t(@tL*AhMNj1L*|IXRy?DVY$g?_h2#a^_)XvAaNvs?BOi??SR>c5AR(?16g| z!9t2fuHs`qF32xGbc{BbecZ)2RVqm5S0$TIpVFnSoRf)d33>nb(7a*n-LyltUXD{nD?zmhNn9@Ii*kjkKb938W9!^o_aeSnS-vF~fG| z5dl~PRxHkYOZP~m!lKNsY!Si37ZaXp91RlQK)!7gPI#^}`V@k;v^ zcl7bil!~gK7KsfG+E%)BEI9f5i_n>aS#J*#`a_V#*DOc{@J{j#9F8F%Q*U7W-r}HmxMp#%>zJ-AK)eM-QTfQXV z-5{j+bS+|*W?M^e3c$RYr+Z5I?so4_(wVXY%{fdrq`rkj&x8bfYHfD$DgH`VC*40} zlUZWK!zI^|MCVNtrxma`+4TIEs7!;iT6S#b58gU5=`RHgs`swnPLTe*t~7O6^6#Oz z3oiRSdupRMJ(*sb3mlU9J$P7btCWtNV*m37_g?cGk#kD(E}x1AcVvrwTAt@6r}YC* z6F4^@bDkrB)kSXrX}toHKQP9W6qh@>Ip#iK4LplnH3uvnP<-zv?|D-+e*NlGUS5vg zN|obs@WBHQg50SUj_SlWhwoBh>z1IP|GRW6gr4b!(8M#9Ce94KF!ON%N`a5b52gF4 zHssai0vprAtU9|HE;5W4UmEh;mlR^xbfQMiSle%+qVy#Yp>LJnn%wcJ_?=NuJm={X zP+QM?Ts!moCN#1T+X&_80y-LQh{R$U5YcC7WFTNEYQ7x}6nA4Z&$G#JDi( z2Lz)Yq}B7^gWXoZiVXEE1$Cs5*HZBRVF9-39O}u~xB4-$^PZ!K`yK{&YL(M%N1qi| zw7#fVXNZ;*Q}FXjdECi*<9N%#p&f5xRRR|^l=$r3hOe%cM@PPtJk@@g`{D(8?mrU2 zCH}dsG!d_}SL2L6*CJCFks}xNSzn#>fA>9d_uV6NSskrHG*_ZcuU>7u zTc|bKAK24HcUV=W|EGI_o|hzdqmOamM~2>aFV|ml(o(!H?R_q1`ELGg-Qdz}XQH*K z5uF61#kR8yi#L=Sf2LS`UVgb^;ys_D9`U|$$iw3Jk`eXy8ADAiO4KHR$W-A!kITwD zAO2zzv%k{2*plEid6r4k_EPWSz}Eq~tVa1H9-80l_LW=5n%HaxsT1suMaouKO46>+ z_^8G6Wzc&#i-sHB9-euhJ3ODN!u@Jw_{1Bh%S@vqo$Y-!?{jTjBWWa@r&)^AuhMx= zXzR=c$!;1vm=l{$Hp{i|osWpwFfP9HXLo4^Z5cDUbFstZA#4)kf#R|sZ}BPH+GNPp zRs^sC!&_!xi(;+&N{yvHF9B*K0LnPQt~ze4zazGw&d^uyh0Of6q~~cvJM zJu!{FnIdwg~#4YbH)FwZ9jiQZ=xy z8)}c_&oEl_UH-PMb9dXjyl`uPJBr~A5v_HrTB=X~l^u^M_iP$^CZC;Z_0luF zFJ$tNi&#fitfwo+Uo~~5fA6_#Yfc+pViP%@&~B+Qs`jAWOe4Yd@5JHB$^M(S>fW7E z(d5}T^UZT}-CpcPXyWJ}%lsmty!sf5m$^sZUbQ4EYELitchZed-d`~5c2YuDxX-_` z11(p6sp}*Jy=5-ZI7N}9|8cNO&+yHe+`Bpz0ex$(0*%=$scWT58;K788XrrJC_T5Q z73ZpU_UySXHvO2JHsIwQw-#+~*%!&*KQ=E>oO@j=-}U3W>vVpS?4!gC)n=*b9y9Ug zG!4i3_oK76De_mzbUbE@1AJm1zJAeYw!lIzod?{TabzqM{cZejCyeAyHjVc4opYj{w^b~!FSk8a~pFH=L!h<3BCTUKwIYTi5K{N$5lsOkJz zJ@@xne~`UnP;ep6c-4CzmK!}T7nTTTWb}Ufgj(WU^!s9zorO_Tc%skEG{{hmuS9nr zDOe#t5)q+G?zz2{^zGGCWS?ID>D-#{^Kp3{vp??@{aB`T!D8F2k(8v3PHBnq8kP6c zI4oe$hh$yDcu#rrbLi`v>}ZK z!6Fml1A2*KC}iG1I7}P@_6st zg&rLh-nXl!7Qgo2y|vkJbocP?wMPDAe;xa0 z%cV6tZ)mO}i7vf5JZ8U9Xcd%2 z#mSXiVc`=ed4ochm;^^V*jGjFRdy@CnOXkB^O`+@)6Wz7N1(I6DPw5{)TGFv-jiNN ziIU0q&D!(r@&|c4U${3m(Pqw-cm18|^ZD~@p5seA=ki_m&r9MqB3`8ol!7T*jM{41 zpS#ZojwOz||NDB=SxoXu^viSS?gjKKnj0AzUb!SK_K`M@x*w}O_hSZ~SaUq++`A_Xjc=C+Cf4_wQe0@x zB?Kf0ac`KKPAcm^0jm~`cRvW@gaTaAep#v<;DqlDR_z0Y^E$UdV`WOH@58wTZ3h*# z(qETj^aElo^+aDmIbhzEryQTcqLK0b}Zui9@KYvrfEWQcZ zH$4w87?Yiha*Y=fVrLg3s&D54*V!MZgf2xlmfkp0w7I8Y;tu8B>-2|1B3?EOJ}Qe7 z6s-OfL`MXZ7Wo-@LyAHBKQG-qczCDK~PbeDs( z;>SVTQ>0u6a!>v_TK}bSSbHfBd44O#WO{bJJ-O|p3xnI{{vA`>?NpOJI{2cV9ebl4 zf6Kg?G4D`ZVbx$I&52_t*X``)^qF=xJP;R{ef}%N>9Go_MWKuDR<1=&On#B!Ta`E! z?wt4g=+qw<-D^_6txzkG$ah?bH>j|BPx|F>W9xgIu8(r}koGnta>?qNiFxkouCwcL z*^2tGRHl`4-az1brjK}H{zZfJZfp1V->cfGf6L{BuDiw*XYOv0(kLkM5~X(0vbsE~ z7b4*wH4maJzE79oP?hO&mfA;WfOo*%0*8KKaWGBxE1_c8Iz(<9JEAt{A z+~zd%sbc`U5t1cfwl|loJrmYH=-qPr?p?@Y?uJg=*UrwBgrlb=&x9^?jM5n^xw}QT zR<|2SU&_dP_lvI3shH}Gr%;4Mcd@sxYq;qtrQJ=gLzgDvf4!5c+lXd4E$Kt%>ABC- zb3=NT!b{mE)GU~fH~qI#5I-Eurnm`{doiUNKS4i)pn(u6+-r9hTj-S|dLG7Wq4sI^1bZXI1lW zkh`en@Obwc-OS2HRM_RHDV;-5%J=wJ+&H*>Vw=V8Pmk2O&Qu)Op$A8hespH9#hsKk zAte18MHC@drRS?T0mX*9eWjAqpXQbP!fze$4n}Em3i~j9?ci%vby29iAdfUI(GI%i zb(CO)j2>82&tmk)11;-nYTnAzCI>x@j@FBfoCNXg1r)RJBx7UQJC4lm#2PIETekD& zorJ=J`1Ggv+&@j{wuS&CXw^Og;x!Jp&V&F(6UVB>9zMm#^qbR~;|9**C=^!*_GuV2 zn0RCdB)9P=Y!|+yvyjsm$ILz*gZ0ev=5VD-q)@qZpV#QA@V53uW*QApGJ&fbSznpA zRpG-oY5#hSl{H>)b~rYtr^Gc}DOwDQWfdR=+A{_))6_tR9WAw{*pcYD8E#5PcX=7z z<#~}q@_I6VjI1`!zJBg)s^>eW0=89s_IeC0_=FGu7KMl}!5l~0bmsCa{cs;7h9P4t z;tWB&eGvKzq=LLpywE6xVq{B8UDZu*urGC+#z9!JWnY2(S&(^gXs97g7oB|QmATzs z%;a9opA13UYe2zQ>-PRyK#dBS~ z1@nVIFf7)44X6;VZTkl9vKJ$d_4e>q6 zzwlIbcfW?-D#rR?DH4*W_@Mt&k8gIk&!-MO6XF{}7y&Bb#5d%mF)=X-IL7z(Z7wsp z511+l79Rqv>VfULRCceqZ<9316OuYWux%v4` zxk?Etna+0KDQ;)z(Tr473>7e?~^ zhCXMyg2OgTRRdGs%(pot6ic!><$7WE7RDWzK|8F<3S3Ov5joE9T51}k2%0;D1Dul>z;AdX)oD5vciW%T=ah3Ky+ zs2kl;Tj%RKA}ZEIA${TLeBqDg<`*~-^>U?<=&YLr;gKNrLx`mx|LcR$x8^-X!k`$u z1A#?*t;lY4f5-_%Lx8m*?7OYSUKiv#=*tCSp2*xb^rO9<>>b;0JR&UT_&UnY6_amZ zc@5p{J%n5{BzQm<3WkhvaxxbV%$y_q?M`avJb_Sx+1Fku6GF`9q9P)rBMK@=ly08UBEAs-8cGa8DWK$XL% z<9O2=zGxH%UUrbFSdg(zPjZv-b2;*5;O$ia?ZbhdpHwu_8#RwedUu{{0&v ztosG&hqpq4wsP=?4_QHk=w62bSkQ*G_5{L zS@uiG;$?5xd-dNlr@n7olKm33ft%$uMSQzn{(V|f*80G8jQ#7&^S6B@^jnSMHkx8h zZhI-FF7o{?ATfyU*e9lVn=2`fyIAvINM0XVm`eMtP_bu8vOcACoa8Z!ji>MA!?=^$%%BcEf0S13 zDEDdCJJw}t$Jd@dzdB-}ykpfxUM&CK$;HiDGQqN)9L)H$9FM9b^LW9%-{Fw-G8BAPOI#p?RnN zi?1FB0-~|S8vbPviV#HCiZ}ovTJzFs_m>YJZo`ixNZSSm2BNj$VQ#6aiTgoHb?$Y7 z#vbT@9GY=wppuQLr)~e2oyafzr%(UT&iHN$F#!?m-sZCY&$xmWhkY5O1`0;c%O`@} zz6xQ%3o$ztvo+0zuu{{QU!`xr)(dOAaEWxl*0}2VT0;c2uT7PemlCgAKTB5;K}m91 zSSE!mm{q#gdT`)oRv-ttv@>Ymk25kdy8C}NHC>zJc0UAWBv4T@z=#J0yV2@_-UU6D zuiw5Ag~1pRyaauuAL5#Vd(R#-q?2jp?)R6+eCWXWEy~gF`PO|0f9222L6^ew6MT!I z5c08Kt#&Uz=QD8732&95Uy6}J+X#@PtyfcHV*tqJk@&nUSebZiZ8)LYT$!n$#Pj0; zmG6UYA$H0LErHOvdaKL{xIXRwq*l=reZ(7-7+i*l2n3Qm)2kR9CR%Gn7GaKZS!pKX z)hm8DZQxH=fw{g6#0wP52-!QVAHc`Dnzq)9H3CSKGcdJ^RpuoAMWOTj2@n`j1RUb3 zC2K|q#w#0!#taFeSciI8Vj5Oe5~81%@IzW4LQec91P|i45HgzB%qbnc1IRfWq>A9} z^@PxS#+%Iocbw$?wKt6rs2T2hMqE~u5D^4M2+B90ayO!hUdA(XU<_s^m-Zn}f_9E+ zkhwD=Ih+bY84L+i2w^MlFVCJmi-#LAV5$N(1DEuCS;_etph*k%xpRu1Rx36t#JK0& z)_MiR0>*!)MQMyGU*JSQyTR-^DhI6~MB|Q*KM_|zbS2~|L2TTn66}r}I_K(uNegV7 zj6tROpNbK13wuxp4ZDMv{|Y+2Z`HW}vjRAF?gVOx@}*hu)q&wT-iH(2I6r?u2_Kxj zJ`b70;cb^0#q7c7tnn*_1I>{9T<5P}^B7Flu?`XO?Ap0fJd}sk->zc(v+eFvD~(4l zJ_NZzl;>yU)7K$j2jaO^b#+A&QfT!+6&g6-HqvmfF!5&;e+g_g$qaS@h|ZN*gUTr? z8bk9(A?i$8O<;W70XeA)oF;qs?IV1903yDP{}0A0+?tlVFHr4>T>vv`2c!syZ^1E- zJp*1tz$*%U1q1@{&#B4C$l&|K9doTeYy?gz!l%+c;9VRbqhuNaSi#Pf@$!btSa8>ld~AZ!S%26Emb!jK0@ zkhnlrz$TFtNRYB$r|PVOw?!Pnho6djU0htu6d&SYq_5jSV7^uRmLoE$Z{^Va2fDS(;lYNcb-dR2~2(>-$_k8 z7hjMkny~*Nq%uGrXMuv{KuRi#n5%KnoN>yfmRG_68C>=9*49uYqj+%X){c5uw@yK;_K`GqdAYsOpk&3YkEl#=+M5c%DX|X2*s52XKAo!ZzZl zc9#Z2C+t6s()M-}@Li98<_O(GK#PhU729vo)FBewv$JD`$RJ9NkQiPVmm60@9v3A- z&=wSyGVwx0Ka;5p@LkmxwzUheuXmvo5f&bXdht;yw^xw0sg>h>(HjoMa1+EWU zh1ITSxL(aKX3I5z_*Ngk%g17XI+!_B6%5L`V=Xw7WrD|!@4P@|e+q)K*qJtG8p$F( z9TBrPE&ZC{(-Mx~3gs0b1_trC8ilhkcv6Q^9tWs&Ff~J^uZy#YFnmx_QW9bn4c&!y zM3AD91ITgc&Bpq=EqjWc(#~(9kqWWO#9#MV9p{+pEvd(6NATbBdQJYxcKE4APsu=% zb}l!x5lOm>9Il!#uw0Q#`w-;e>@Rb2pJ<4iXq45^D6TRd(cuSh0l{xPf`cprp$LjU z+^>gmgS7Lr4?cSm_8fa55kk=1wepaJ8Udur~+<`Jn7IY`*4h=t|lzP#JmrCW` zO@vk)1zec&>S|vIUBoGKnnGMWowDmoeXK^RGt3By77)MP<1Q=Rj(%fh?l8nwBDGH{ zQxI7X0>67-6q(#nyhY=!`49jdI2w_>yPGd+BIfh>v02YlTPhu7bg@lGDF;IeNYKXP zLm&l8FwcQ(49@p-^w#nKFPcIZiI&ePTM17aiksoGyQ{u_1p(E->?__m`vQ}c8$l(@ zc{$8JI?C?u?q29<1u1t8#nN@c-m~;0J#?@G)|+E*EFPwW4;}Mwr+hMMGO(1 ztf3JLy4Em|qa=_;{DG8=Cj!JL8MBmB43TKR%N`?=7NO*Tnu&zanEol$YXbUL-MM49 z8#po$y^40reOd&qQdoBQI)xn7gc~jG*PkJ7^oA76^XXqoUjXi{90ve9(??wwfw>Ciw z6M)P&2!GdAC^NhN%N(OX?KJ?Ky0@GVl$smz3t&CC;{qoGl!6qX3~CA$O_0Ve0KR}< zNMNa;bwFqbBx|HardND;0a;)K7%9j=7~$FaFtM|cXGCfyR9GFt8zm%f0eD5aHGk+yK=g9{xTC1O2N?2c8X7L*cBoH#)O8Mlu>@rqv1$iAZp^$DM_ zFm;=lr^jnWZHeumwxUvuda??;b0uFnWYjx9-XcX5f>cXQy%RiiCarzt69(WYhi=dd zu-UrN1jHkY&a9pICD4e8fYKJ!f#u{RKiSGQI8%935Uy8*^$*JA)9{nh_;4e3@&#_9 zy%~&02tgeZ!l9}W<~qO)sp2~^%ASDkk`!Dxk=GzZM~>RlQ8FILQ49Sf~Tk)TD2P9TuGlW)IVw{E8 zX&&a2q+cS2SYn8wAmOD2u^K48%CbdoI=H7v)StXJ25r;(KTY+@}F_DBG4g`;q|CM$?Ab15Eq^YbM zIy&`}6!HzmgkaDo_n(8?mr=AVV<>6MIGCDIO7KQ@xCj5|JDs(v$Vux5L zwKR9)zsf*i!~UrM4EHZ^E^!6UC#%`6FWVG8G1Cg6O?>w3svm78EmGi56vl)C7lMW{ z+$Yr1wAI^Lc3;_zI<3lPVeC3MrH?`LPrrE3Tg731L=oj6+cjFZFhVwz@I6)L?0~>0 zl6#xOl`nSd|9=B8vpj1=6siwGpFe;4R60RLGT-XfORy$D%rr>QF9K z!UPG0DJBFd9c$uH(?;n={lj5rY#ii!3AIo{uoiXA&;%ztLRt_}PT{O&!+|O2H_t_M z>}S8%v4dtFPSGW+>K4$AZxVHWLAs43l8)PcAb4C-%ne7ps8qYQqZ7qi-ZB7@M@ zKw%{_Cx;F7F@6m(rJ>ppeXzD9%NW%R&b$wbPE%TLI`6rL@^TUsi9jF4PAWfDz!b(~ z!vz=PvWm(wDliB5PNh6Z4x745%D<*oSyn_K7xq6?u}m6A_@Ha6XN=2Mja zeM$p}#&>%^iK2w%OZ!(ujHQM~Q8A1XY!`^P#eK|EW`P3Me|e2x{{|i_(EgMN5pV-) zLDK+DW(UTX%;Xv5ki~^0@>8L>&M#lQp!u>1ab%U}f7;lB-Mo}PV}piE{ZKx^5Zxu+z_Cb+eo{ZFxic_u%l1CEG_MoUXd~* zM6Z}^!9y(VtSec_K)wiw@hZtP@K8cwj_Ks2*awQgkm4e2Nq1Vse>Wwh#gScH<*x7cQjR1# zFzzHY7jS1gk23=O@Medz_$goGG*XX9x(GqUwZ(x`nuV}2hIsu)u;VekAZDavq3C{* zLyp7Vwmp{S7n(Yvx|dG~#?>J(ZEz*AXVma%i@UrBQ@|s*Q=kyHiP5!Z7?|K?5Z4>! zbN|#-dc~EU7dW~8dxfF6Tiv&uYJ=H{lUaQLkM$FjM;`?SQUVe@xCJ`&CDm9#D?~6t zFrEmem$|b}kq?JqtPJgbO!mgaLJxvrKBomO{?DBwXDkk*#jB{PAxDFP zSxSNZcraf53#gc;JBVj=xi(p`l7C6P%C@p2)=MYr_HGo3P^u$@Qi-VHrOf8Jxk6~_ zO2;bCpipap-yJHNAtRhXVtTZWaRD)@{qG|OU=am*|MUvZ>j-;>h}ls2C#0o7+7h)N zdw~zWhY!~O%~^s(ixOe=AhLU`kRN8bYWE;Jj3#p51CYB8(9@?3KEZvZ7{$SR&Lj!B z@(R@cgtRC^;2Z(4AY3e=3{@E}ccJE@^Vr8IVwi3N8B!wdzy^{~{6Zno>TrWr9`owE z_%@}Jvd<6C9*jS*BQ}TlOmaff4TUbDLB#RKEpGB2J~D;}k^KOx%nPU$qlIFjw+g)l z2-$5^3@5P~!55z-<}e|3gI7vVLNpH?x8N!Cb;PocNu;;dP9;dV{UR4Ft~=&iuh9*W614^dy#3&cW<{FHOqjW3X4<3=W3w2G z04eqaBjJ|gxarI!GcZOZ3IP(JIL~6VWq&jXlaF2k2aGVXjTrQxhY<_7Qd192>M<1A zh@vO&rMzM$S4PG1+~2>@btxU6FATx#5BepMsKOvu+JN4L2sTg=7(P>|1yh$B%@`Hy z)_+ygyTIv5-1;4zcxOo}2^2&Th6ay_RXbSd@OtqAytLD*&?&&(Xuj(;D5XY4JweVFmHN+Dkl>r1g&Pd=@mTTu!~F=o^yvl*Q-~r#`uq4 zppt(?>{H$vgGTi~aX(Ip7cY2SR64Ae-`em6Q54#{@)%_OH)eX|waAPEn*!_gi z_Wi{x0%y*Q;2ht^18e=D6#~t2BwA?zbpY26#m71=EG!(Bxc|{jN1=_@drl5F`9h5` zJS$P`*`SdF4<-4(gMAw>iSTzO+!2cQU%p=j2ZR6Z*!ClH{=n&)spD#5blfxW_pjpl z^CYN!WKpg_dWdKmh`waco*xePhvhX4n~(%TRj9me<^TJ*1YAA0&6QCS;$4C9J=`Vp ze1;my_L4|X)-c5QiC+&L4t~TwBCKSvA%CJedOyi;{{`zCDC*e(D@B}M)Cc%7t!X;p zgtgF-BgvCg^*cc?n`qBy!dHN`J#l2bRhWr`JTwj!B4Ra#zHe02@DAcPyX|X;+^TeH zAlKg)g~lu#9R~bGdZ~Vt?T7{KFd2vJ#PQ?O?XhEo&M&bg1Ce4N(d#Q1 zuE9{CX#Z@O2u$~ST_#WI`2z68MYJeXbGJ=RYj9PsbJBb;D2qXmubqSxRXLh-3^~6_ zHA14P1A4Cy*Id?@1qcyvj5oeY#h@-wwndkD1)n(veM2>i0LO0?=jAGUg~jj#@B$DA~Hkqjq0)95^=nE!hPz~%1G4MxSZ!p2z)~GlA0K@ zVGl|W@xMx;93j5$IS-=k8WO|9rZGS#3iQ~77)|tP0fBx3fTPJ>GrAJ}R-5dD{DQCG`_E zD(;ZHmMn^L71=XIqE{Bdk zHxB+vWd8J&hzQ=>H;}!EnjH$+#I^`OqY%Sn5)>*tjsvg+_D7*eXwsCfPVhK8W0MF^ zyb$YWTsM)2jR|pQ4Ce_8PC`MBIF@z$WM!)<&IM0vA^bGF77an0H;W_s5gZI0#ZFia zp-X&^ZsOD0R53J4cVOPH=c{wO+HtG4ZJuM>l_$@>%g-geXR}+KY1}^DmtM`R5^_*W z%@g&-?4T?{k%uQ(R+Qu(0RG>M1-iwX}YPb8mLv(8yyS@F4%Cv313w)o9T|`*Y21 zyE*^CMt_pLH2q6h^X^x2xc|+Sx$4B79k0bduMDo8Ej2lf!#eQ2l%w%q6bTg_W9`GT zcki^rH{(C%ff#{WVr+m> z0E~x6agGijK70?2+xO6`R8(@Mg2P}>z7}(M3e!azFmfdFq0D;|u6wA`q1`th%u2)k z^aD=~8W3H`C8JT^X3D;Np1xM|AR$1Ht;qG!0^3)rP{h3Y?@=W2$cavi7@^C`%Az}b zhnOGRom_)(USD4R0J~KJTCsim_T_(owc+mGUl=<8A1O!&la7s#e}~)M>f+z4sFE<& zy5Lte1GWm>8EbK;ZSLLsjR^V{9Uz#+wFxE^6AQ{#gU(N+U}Gw zRhVbl5tsIpv+VVksU4)v@6FFSo24nLleHc(!&ATDW?}!(=4fYW3`*J$e~%s(H&2%E z|JfGLDCal3V|Ak%C-e7wryH(6Df7UlrD$z?K>}Ud(fODj)dX!tZHEcgKC8}55zR&> z4tCKXjsv%LR)wkJ7V6|#+(J`@9ivKW%o2VOVF(_(gyC5P7UwK@4<@jEBa}r^*J1ol z<{XVtAZ%%G;+S=pc^U{YQ^`YiAP&E9$4**ZyNdtT!wZJ(rNr>EzWy0u(^IIA1iRxx za|sd!kY+gGx6qs#R(dmsdSM!Y0H_FAQ9z-J_9r&jIXDa>U=Lx8XN3JG5tI=t2*X2^ zY6M_|1n)W?n{qtS?^uY@pRu^{;cgrj%$Py8#LH3=WTGN_E~4SW9tJ6tP3uYk?(UI!Q|EvDWVHRu!{Hz!_IaBJNzVAHOuCr!hPza(-qN z2=Q!g^^~#Wmtwt=G&7h0)&Goifwjs=u^ueyaJv)!%QPcM>vIZoFkZl;2{fEGATqZN z;1a}P222W9UgHnFJhHuFvKpL>DpAM47jo>!VIFQ&)3802!!5uXY8W*kCS8v(^`xe! zuLHJ#@J>UBA%zF|QPdC+-zQAbA+VN!$7ZsYB-8gJXWPnXe+vt;=jb59tcDT6YCI zl`X$qEa@Fu`km_Cx98T^Pkv+Lld8Fo_XOlJ3`~3v5cBVucRy+)5cDoFVf#&k3ikF@ z`7@4cS(!Ww^K&15lbrjx@gja|G5Tifk#Ql5-p4MAlJlRgX+)PGI?n^^Fv?_vYQ-?J zc&2W4X^Agv<0*nQVK0bj+1e;txrA>hr=Y?$1ELPi*)V9@X0rzmG5p_TiJgl}&U_`x zx|D0;8in-E1#Hi3w>I6uO{=tbXT2QiAc9&fnOPloWEo$^6%ln?CYQ3vu>By$Z z=CDVN!^&pcQS<9Ph3P*9;qC>^~9>oE4%4lKZfgSqu^orz92+lt$4s<<$N>xPEe-A;3jymq|6iHwE@`BUFoTV1Z6 z$`{J-)}%3(y?$t?agb$4#L9+%;M2Ep$4OnJ+P~7yODx$nIp5?zYu7g_WYvJgu?;33 zqgaR$Zw{g=2sE${yBPodMM%g+lsFJ(QK!EM4`ht?MiEPii7nPJ&woEECi$3?+3wJs z>u~RcIBWE2S;z3Jb0y1*pTvI-k=|RfsF|U=L-OKH;&G`HuQzl&*#(CVS36pdjBm2~ zN!UFfbj_bgzuT5f=O5Lls~8>;qixkY5O@43#r$>7z1$Y$wtV~6wI`=8>#2O{FB7p5 zm^*py)bb0Fb6d#`H(;iYGg(b+H8E-b0$9OWjEE)Q7@_ij26>N@h4k1DY<#|9-kj>D zFE`hA9;?ZQNOcdBf7vlFk4axfW)}%Yj76({ON1Zu38mLHfF8iF zm4i#_<-6rQB$cnlWXVHD|eAnEl`ra1ZI#lodM(oo3qF#MN*+#hWdiF3~wfKv1yY!29ILakw^2`g%$#9$1X5Pp=ie}--0ylw3 zqvCv$?o5x~4VCUIf@VVW6mxbw3UfVm@MR%Z^T>a8%QLs2qw);^8|@Q?MVK+j&F!{% z#I|C_4EYG8(<*H0>W?nX%y2H9BWFno0nUwR`3PARsF=oo{D}F}K`hBrhjNqExR1c3 zh|p|*K`S9G@sM!X!sea?CR4#N@y>!k*Ag=U3{nY=-apJ#aglA9D?;K?turcV;5+IK z(?TO0TB1Ft)4w14<&GbaHh5H?qq&otdrKZ)>SV zQ0pxnKIMJCdmY1am|jy;QJu$lWP!1f8deGX2<})OHejyB{+ZdE33qsKWJCsTKBX5A zI32)n5>6&>|32+b)#M{&!ZC|?hCDd8!pt_duhG@Sw!s9~WlUqrEj2*W=APrluxKA{ z9#S%s{9=z+ky_1}P5d%SF4Q_?CW1dy$3BjFD$gpm$W&){ddsf8M?jvH^}X8+ z`?iu@Mz;;+Wx9lesd>GmFFboNrLeXn@=E8Wo|biz+Jqs$8hu#QkGh}R@V9OCy!0kq z2FA4M@bO&CXI2V=`VfMOGvfMAqCu&`Y?KfinS>~)ezJc?%6ez{j4w}RN# zJqHIUqRKj*6cVDrQbAIN{gM`{H;J`B*~rdW7@2TK<>Rbk;_8n%ePP`QjdZIU4h1?D zY||?+c6FXA99>{+p&lh#JYw*TQ34QU-D}%fDp>{KsjBZX&W?YcIIUdC2#3@d)Lrwy zV+lIX-Nv*gWBLV2&9}zmtQ+))G|t@kCHhvmTbIpts$2BLwV3Cy1IAoBb!Iir%dI7y zJ@$K2rjuhurJ9D_o>|UM^Nr%L0t|#@PnO}=r@k@P=*Wp_Uf$$SHFVan1$%{Gzn#E{ z$<6rDS7XKg2y1)7FWqB(wwfLS1KoA>cBxM%JMTaw9Sb<%Hm2{F1o?5L|NF$G z4$CW9g+-5_H}kDJ!yYrly$8Tx0F6~fizrMp0vuBha98(V?0$%CBcMykHi&%QFW3zB{VaB}MuYK4CHXc0 z2*f`(vopV-&r%rz56~GD+@v+!rDls*w`D^C|0S=UOM4rm^pgp~9qa!6g3=0z-(Rn< zF=!Ef1-&N1Dj72^44THokFr1~Gf32JnIBaMu|CDwC!P-B@C5uGJ6?7L6!rT0$yuym z>v9}$6xgUde_kgdObF_8AD1iTR(la%KO)@Kbz8|{;`_e{UjuhK8k$RLYONQW?8Ma_ zPxOEGl`D2RVy;6*f5!ZoYP5^`rbL|MvuC>pD@*^9Ea$sMo;cMt*+@$}G+&+a)v2Vk zy7@f9c5U62!J0-z-2m}xi~ocazMYz}>hf~zAu}{GC_Kk~`iJV-96Q#`11v+MBR5CK zc z$^EhV{b>?CvVf}EZ}BRb6f9GHXfQ}Hw|`~fnR0@FjQ}`ng83;5i*Y=v%2R|nqP_hP zF#Lo^E%8*mli1m6HGLWx`3Jv(1c$&|9+Avno+IceImgFMp}Yv4r-kd zMMjV1S$73QNr!LCx-8#qKaTXI$l3csn7*Nu$UyIt;qH`vts2t=3o}G)7SJIG) zuoH+)TsEoaQ&^d~I^ir*I4KdtQhj4%Se9mJbUc8aLzit};lTdQf8u;4r;i2hDlM`r zK9g=(ol%boWRVfwm!^9bO^3vm7(O?T<_hn4^k1giNunbgE+|*3;~BF}+wl*VoD>UF zEP$NN=r-lfsD}UarTcffGTb~at5qIuLQ$}@KS$mml*i_u9#S`^jQQ5bI30-A$|g3}*n2Dff4o9)|7lVfrt=$G#NU z@Vn+MF6Wx_-{*h#{)?u;=kkY?H2Pt`0HMQL$p}_1VmE zckOFex?9P6ri)dBQ*BAzHnN&do4UELUVqrDJLw`|R`;tzyuIJd@F>Ge&(FC$vlbi9 zyZJl|W&${+Hp2fO7Qp7+366<_ZvWgOn;m{1em&7T*cEo)aou`na;@Kol?m!Qr*xmi z#s=4%mR*?)KSn~FR9;ef)v&H!0*LA(PM`cZe5VV)jim3P9EbQwe6np>_KtQwF3!30wuB%)qQ0d zY{DyojaEzx^itc%=>CfT+f8?T+u_x2`|c9@?>XvZjf`u)q3?DK7=1BU3!X+0BAjgr zpEZ~Z5jOVVR6NXe_-fhDWBZrBvPxPhV9`b`^o(eD^gf+@VDHXcr(@XjZ?fXAaJ8ZW zv!tG@l(4$1LV}k`%tLC9n~kp{Lw=K5YSzIC!3&6h?}+rM!w;hj0vd2h_}ZA?1`X}d z;Mhcf^sV#qD%)9PmuBs^f`+SHa0Y*9Y@=-q38mU|K8n{pxlg&6yREJ&hNVf-q+)L8 zP;UpXD@U7Fk@w}6ULzXrmMVBAkn!o_FW!xs+B)W;G5x(^GDM4^Aw&OGFUX!{ zUC7!!lm24DfxoVMx0lbpq<52NwcKFM)!wKn(^#sgtn?-n0fBVO22%(4z-nRzD|nk6 zy%nP>iP`SNVnj=qI)j1npD+XX(s z#=FO(PY^f6w%cFculT<-gfprT$tfC-n=P z!3B?)^gKN-QqqeG4Go!jxub&j?AzbqL4Qx`R7J7bTyR;uS1)HXT>yxtFG_bcsU81_ zD}?{%F`gXz(%n{ES82l~M)N?)-FN(;Tf*ZK{ztsEU19F|a~7=%QA5w9WKoUj`iWn= zersEd|2lguhxdI8i>@3$9O_IA?<0i@T_bj|cyBtpx@JQ-%-t84Gwx6jn*lAJGWCN) zj79yO9mg14IZy=9r~{>=SEXKQ-I$#p=*0W`{?3>T1!G8M$CaNa zS92PKal|50{lY)y*$sWoA04=n$fV=B8BpPAJrMZj(Dx5|nmrw8N-+Jr9R4s~u7K+E zwLMbDaY8FQI6p-Snfxx`G9C$#ata>uz7Z-TBeY<>@uXCm(rS68AG~V4rL%l|f!LA~ z-nAHY9TBx<19t#kGArrxw@ogVd0?Y?QfF7ZOpoVD`h5y9A9^;$Z*4#MX`WZLWCA1z zbiza3H%f2l>NWs_)RASp2QwXPF1%583*0Ox0n)o(?@e@slKq$*3F_nkY_{>**mR#T zZ)hJ}Y;>r5ZZO}*F+4J3(fwR=Yhz!}F8@Kdj=L@Cdbirbj{&%J@19L{`~_8{iucZI z>6|pXXnSR2xTCuNoFvhm?Cn;084`8@*WI{kRmjp$JN|;6%Jv7(icJbvP%dOT*`9Lh z?ktfSdHGZKY0C-nL8C9ZOU*TJa5>cuM^Q<)o&ZMTCX$33thUtkCpF1uO)+wXG?PD; z&E;%TnYV{M^4zG3s3@*yU(w!SZ_=^nG>PTGLBk;} zlE)HD^m+RmJH*b;tL1MVU0I{-5k|E`GIqxFL;7Op+wx=#T|*?)_tUXn<4%K+6J0KDv=I_oYc{EWbPgqLtfo5dXJuVp zP*vvSrrQG8Bv3vfrTaHG;s1Vhu7{h4=NSfxM7c<;Qvq9oT_SgO3hPjfek(is*`lJs zsZM2agJ>5#ihCn7RxW$@ojjcwmL^#xqVJhtVfE>yo14zu$X43Wa<65jcg&D{x3fwJ zwVa|7+w$+e<-GCAN|8~j18%RLi3D4aG)^%I-F43X?%RKg=S)W8iTPhkZ^E5R1^CKT z-}Aopi*QaT)1cVdYCR^q&`FJGZigG{BYj~*X3qB`Rovn)44er#+~)cUjC19QxXE1$ z)amKdr!j2=&Y;!lEUHnnZE|z!4_l_<-;W%6Ext+lz-n^fYmJ@p(-qRQeJ!!4g|&)l zo0ac+$D8Z*%ofEMM;#7zBymV{%3kqCHNU^nin#W7w4Yk7rM1+MU#y7g)9!isHOgOx zi;EXGuykamFAA02?@rH+sFA+Yi)IFQotvK!M+kXR(8mVc9?x~~S3 zEm>!2q+N)WbbW(Gh^E(rp>#cK{itP^srf>f`&85$gR=eKB0m?}@jISepQk*`(1&jg z(O1y)dR%V`gJWU@c}cXuzMv-%xIeJNKa=Gu-flRR*149LR}~%MLrap#b7jsyMsPAf zc!lJsZuj^($AcRYmymzL`DE;Fu2~~)KATbXEue7-c-5?Wv|}@BioWkBP9pC^YHorkm)++U7l{RXup7}TTt@x!R){6| zO{yWPaO1|iLS~IeJ1@3BG~)i0XP+BX^zK#Mm+j9Lb5diitYvN*nkjjtKkfdLmD;H$ z!o(2OTU@1`j27`_V?=F2l3nXRs|N2Q+4YvU7G`FWuIue54E1bmX7#GEU84eq3X?ck zq*i|Z9NVzs)aR4m>!p#F^UH#C`^|;1EIWk=6&uA;m7d~?#%O-S9qTI`4X<|_c&14v zeurt_N4LT=D^96EpV(l7UDsrA(jo29eYsh++JfuCDnw+or=V|3Xcm+oY=cVF0jp&8@u)ME!77S9%TYQ57d zmcF=khSOiczFn-?_2x~}CxO;H$D580zoIMZTmU_z(9dee1aqL0} z-F;7>Rj`L0@1?^{If{US(TREM^e}O8c;)6K-!0D{K$*L}*|>a6&}WnH@*5(?H6HDC zuV#BwqRRMOQGx4Z&1hw*rTv8XYp+kbN}Jgw4F#+FHi#@&y=W2Zyk>RyF#SP~395*3 zE>0Q>3HsIGAnn31L!Fim z3n=QoSvQQ$5s!)n&-AF2mfkc^{r_mX%BZZGXpN#EjS^CVN=bJMC?ZIQlype9bc>X< zAR!+$S*!5pV z_%MG)%{kop)=^DloiaM}wsLzrBK}{av378h{UO>%|9}*=LM8Z7bX3AD|z{;;;?3XIzhWvOiYT(w*B`aOXR2 za)o&%jqP?Qgx(Uie+y|j=U^AzW*r`>oSjWzpazOY(?^GyIVtb@MTUyk z$$2m%sG8lj%lq}s$J;*r+r~NAh!-|7MYkM%Qfpj1EpQ{YztfSlsbQ;ICzi( zG}bucm6p~}P8wYCDw0hT1-(mWGexai!f2RZ|zuiMNhmNlWc%guCwy zb&BahOJp#%K>Mb;WI_z zdfvjp@Vu32|GTEWMnhdCHFsQ6nCw?LI60PJU_#2jO@t!b+mp93O+sz-k#oO(bibvi z}XVLf<{kH>6C|yoBIpQNnp)6 zV5|tOk8O7y2rHjHB`S^8a})_4GP^VxKfyB;Q@Hja$~m@fCVWRQwKAEychbeEC2V~2 zd`mK#z%V%c*0qX*OHG$;xn>Tw*tOyUwF0F)6$lT^ww^<$;ocZyb-J4qH6e@sp-8db z(-LCi*FIQ0`6RACH6S1s2Su9ND7)c`cGBm>_v2Z0W>=qhY;tf}Faw-mgKy;@ohIiu zT#xmQEhA&Yoh@C;oplroWE9Cr7nG|-@;8~Mg%dxiddoE6i>=B4ZyOoP0rKwO2E8%5 zf9_U8Lj#x*47-qI#4)n67OPnApWS2f&#fhVYXT!lz%#F;_&pyPG8_JqLxY;8LZ+mm z@!n77-r;3g9E<`6IMdL5OMH}<6AL%;pdVwfvKsO=I=Fbk0$`>9GFXgoIgr=2dMa`up&>tIn*-q@hs2dA0tV0>pt=ew2bde|%XwcR z;N_QU!?O2 zD~5!Q-0RTDg#dZ)-D^a`rG6IwpJX zwEa>HZgfZvau<2fnu#D4kx4$#;WX-@tNF_us z5*!r)Fcfgg0|4egTF{fUnQ8?Hs2<>^Cg3&;(;~!hYh6I|fw<}in$#2k*3tEyhv~)l zEo_$g@O#MC-{@)S3ViB2WHwz}okBfaQqMD$8?5W8be67Axn)V`@5%`s^)LYM039}K zN714YG*JqNBYq?Xj3rUFb;Cb#xCp!&d)e8L5>lJR1C>Nw(R%>h=fF_hJwh32E(+4ie@2GMraa6jPfz{0;BUhs&SB5nv7TdsArP6>UUvpiv>CtL4U=unPXQtnw-Ya^R{Qj+Fa z*5O=O#b1B% z*u>=!92jU1cip1`Nd+(mcYhDtB=-eu6Rmn@7FyZ~!;#wBTC1rVrT=>nHcrkvC;%rF zmfU;YIqsr)RQnvg8MEV4_0fn|@dSbF_8*5&l~(V414yOmwf26nDS5J>8Y!sDdI>W$ zeDa*cCL4CUNvPL&`N-z!RLfExiaWEOLgdnhIk)#bYX8gTEB)!mlwPTjm!156K%rIf zy>*ll%D}#*8*e_c8ZAttOa{vRlnP_x(#`appQ6Cz9Hio>d>}pob)XU8U6&%d)pQucohPmKokeut11e9in_q6hRI|ld%#@ z0tF!U8NxK`90oYX?Fil0#pQ^zgP2wOC(I?gDb8S?YYr+`C=d)x!HbSaBu6=~6?p#; zVmaYJ$;!zwQK;*vcqlBeo8vzs{Dgk&>wA4TF$t9rvuA>5#JhO(L-`K(`qXvI41!Yf zS@MzZ<-i=(4A0IbDObbk#0$Oo>=_Do8*Og6!@%NQLzNESce%%y8ThKt(^U*#8(_Rm zFY83@H`0GynA8Dd`kvaB9&C6Gd7}F4osxJcipq*_eFtw%w##+C5BU|YdiU^K%8CYZwnGk&vVa^QX^1CG9B__Bj0T>C+1s z`X3O+!k}mOFRa(i{n_VAn38e z0E@xF3WnGq=#kM=rqyk#UFVoL^|)FKI@~}jMu_>qa8$Wm&eu>0l|VwB(j^af8F%NAjI06-+3}vp9=;!#C^7PNH@-@YY)oRE5~hwG}){ zYi>9>Q?8$jg{?~!wFMcZsTgie;}ei;g{TO;)_JU8uR9}L@{)v+!^v(~n6;-XjYd?@ zqwgEa%Z{Yk_>4{Fko`A10;YRWe%x+0oam-a@1nAZPy~YQ`73QCTZ)_6%PLWy=xB>a zF`_%!b1WCz;NS7oJ7V^Ct$=(Gq4m9Z@r!N_o+mW8)35pIegE79mmLKA_WEd%42VMkiVy}YzmPJ;$cBYi1Ta+CwuO)D zr-4b=O>+X6Zhpu>vy6Zo$2|0c1{ye^LV$w6&7pgP%=NQYJP6+$Cbxev_j*!qd^Adv zdcvaRs_J2Dv(+n1(RIKh#PD9K_1DC^LQ%XZ4{i!4XV#koyD_g96=Ob#>DqC_i;wnL zr5G3LrjvNSG_oT~k{Y$(Sg?U2jfoXJdx_4`T>SK@=Xh-OG@(_`Z@||TkG%K0k6bpp z5Z2p*4%~EZKKdO^&ct~`)}2{72}c@%V2s>o)=X}m%==t^J^M-;BS*_!m(w)&PFJFQ zl3@q}Q~0+)lH#yUIh?&Qp3l^}AY}m%9{`L%V^iIA8;~FQ%Vx-(lMl0NKR_nH?F1=+ z@IOzGZ9rt7C}*20iS^-(*Y(@#<)KelSEk89Ka8LM6VSx~7h?i2SnV3Sr$l{$T2tQMoSj*@Z$weJ*jIc)K(1`dHI3@dmaE3)N8GmhM_3c-_{H-{?%s zvEeh{bt!CSGaN%KHq4xrl?pucCon!cpa{pGS3;)E6&^=6kS{Vlz6+3u7cXA$NYcOy zRSAcd)_(KVMI_L})yF?0oCPm$k7zd7h=8xWt_Zwtz;LK+pF*SRI*5^o*=+Z2g-S+H zT>CaTdFD$H4bfz%7w^MoPmj!ybyd$T!IMAnDD37c3f016-zF%A$eGUf-eoe>rg-Hr zv{D-ynkx7yO^AQ4%l+`-`TqDt3yHkvDQ+7h5elVO%J<>#QASivd0Oe{_{~OGLhT~Y zmIFxMd@QlM93yv1Cc-2O_n?Fek=@Dh`u(jb^E*cuINzG${od==EAuG~ClYOvoM8vN zN*J;0fI0axkgEVEatjkv%wC35@7C@y@*W&LeuSS1v%#|us{jWoj6xt96xkS)%AtlB zE-8^y1Ys2@9(+YGaFE_(1cP{QEA;>=y2+gsU8LM!ChY?8Hf{3 z1TgT=2Dk;@V|7)ft@<))h4f!eLp-)SSZEV^SPnL@sq-Uc&g36qS29Tu99_!o{kb4q z$$x=|M{Cnz@D1I<&Ds6){kEtySD-RDX*k-8@l7ath*bX z3bp|e$kZ@1I!X*TJ)&d+0SFXq$Q5}z)dzUzChv)O*7W4JfLjDmKmw(+T3|Yq>V1k~ zX}N*k*vt44E;!s0?3<`Jm+zhbggqbXJn=d;yT<=BD$x!JdntR{7b&m3{uIfVG?N?! ziIpq8+01JzU8C>9rb|<5ijq(qwd{8!GSvv`7MZSJo)Mu*NYt^^4p^#3$G{*Mtbu@zChf*io~D zF^%{XNoLQ;lnNlb2}DF!fQ9?E%19s3jR?66co>9t?>g+xKZn?i;G1EMM;t2P9f|iM zh!K&ia|Z~jE&#uPsglkR4!{I~3gywUA>_0U4Rv4Vn=`<7_w6cK{BM<68m5s68OVEN z?W`y<%6Hxu-F%qc+rVKtNfHoz+r*k2^@GRb>rQGn@2TSu@;7i2ZkP(bG5>w`HTmdv zO(m}0ck4;xk+!znP={^Hrq8Xe%Ke{PPV#V32n$qbwx@TWlrdn(9?QEE#phiAy}Ox) zp7`uNr}Wo>#gwNX@1nJtpGGVP8(uft-bp3c{WPeL(!TwuCn=rX5)D%dPZ;-gy$wz| z2t5J(Miy#fU=}#;%-nkm>kQ_7@cC~6-^&zASoa7OqzDm!Ck=&yD{U)VOIcYA{vPbS zuQA%x*cS|n&qPs6Lbt6>)f4dU$7g5ijA($=-rU@Dq^cg>)dC`UN#mhrz*vC4e<`H( z(!!Feo#Q#v(U$rq@3wxYkW82x^^TKk+Z1-A6XZ4@8WpCx{k86%pOFr+DgIU8xZCbm zYW@304)2y@WGpeUD-NZbo&ypG-z=Xw>NH%Bciy|WtNP^Sd%Bfd>y|RVUhl+1iRhjc z*m8rLbW&08j96~le2YV=04aVL9zB{v$gv3d7hKU=I1u_1JZHndL7CiH0xO#1(^0AF%)5Hgx&-2muWNym2}JWr_b>LS6~+R(_z zDV&d(O}7UQS+_PqvIcFi79cb#gCjO$zE9svI zl92ZRNT>(*=VqE~t~qW^5TT-;93o{(md(yrt;EIsgM&uXA6TH#H#IedVr|`+Z&n97 z=`e+Giu3_xFcf$%^1ulMNtK7JtlQ}+3r`7iOrl-3ucKhTv=*iu8DJl-y}8t$?R`Mb zpjVjl=<>ToZE%;Au!HiETjLCBY2n5-GxdWLY#(CF9W)IOz9+GcwNsUF%#A7eNQ5b| zs_Rf_sD#896R9c|Vx-UYF7JkL25%(E@!OZrWW(Q8Ox8PC6d2V$UQx{vU!*v_&OL?^wzEhkOX)Ty%>FH=hul(H$rcgQKuKm zOp$kfm6K{zyq)MDpte72$bF%uI0GyT$mjREm`Wc1JI9l*EU~()Gm^5W<@EfZ^L+%( zQU&jTsg$xAMm z2N$!|*Lp7Z%u$+7o9ArBpWiLa@`n)tBOzBHl5ve>t^%UfHoSb9eoFUb&O2 zkBtj1Wl2Zv3sapbvHoEODl;l|jCy+HhkqWLt-E3TkHzBuo z0d)-_A)&s${u7wvFkVbSbZl*JUl)N6!7Csx0m*AL9O-GGfM}cnkaa!(4M@hOH0LjE z3WsNlpGj5=mY~4GH?}>j=wrPURen~{sTPoa$5MeRhL{-TOR})sstcBr5m&!tl#5(_ zowB(}#N?1Lds+F9tEI8ZNk?CQoynha@?x0-_~kZP+nTAj``6Vlvr{m#HL|XMgEFu> z$0J5-fWFmlaPhgB<<5wHsCvaxDD8yLMtqTZug_hlK?iX>`n?8fhvw~{mzk8Ux?hd9 z$L@_geUq6rP+hgE;X?jmcgRM(ouB<^BZ7i|Ien+}#@p;0a;H0rj&ie6@6TwqUjDaT zev?2s2qnsZu+2#R$i0oJnPSHgUGOGG0X0Q{5UQNkg6u#SRD?UOGe8XlaH5)fQ&80T z<AF%!djk0|M(4B>vBTVng|+Zyd! zgI4HI7ioy*_>tkD0R-CdMv(8m?Xn%YX)X4vrRDlaxT>6LnQFBP9#2E(razw5+w4&XDV4siWw2VMn}CoU+OQBY7I!aoqVeSX&85qq!sg`HCF(#=Z} zuBq#ZpEDmZPrUOTwk*P-0o(>u0T%Wk;t8-$AaA9C2TbIq8)UJP&|2IU%!cY8)M}fj zb|C?3YS{&}v3o#=kmIVIp3|8{Ig>5(sLL&}NZdvN)!7H{H5YB1j^Fjy-XS;5@vt&W zOHc8jV>vH2{L(P~c|q*+y*C5w=oHmm~wcHAh1$hVSM#$j0Td&MvI`2(-|jU0h* zvsbPk6Ra~ltPHiqP)i&Wd1YC+w#`a~On+$q?B&bwH5K%#YcsU^Qbg*(F%$RFbYt!n zn_m@6bUx4J1nc98sJ~|P(?$W;*XxW2T8DuZRf~7{!9e(?Lv5c$X&s&{jAvTrV z3c1KIM7=yN;E<5!d3O3-^-Oq}Eb$?C{?MaB3zx<&`T&{}WS1+kAU2)Q#ez<7jpbGy zJ(L^YV5Sa&Y+gYR{qa>ZX$a(=PTf-|(HM)~@wRxm?K%{gXQfvk;coM)=c109%4a5a zem=?TVXtiHtKB6N2|Qf-wM1H@Z0wvU7s1=;O&&DW_H4G^6QRFe0M zcP^`L>{`ck;fYooo}zm(JmMZs=2Omp#qUASrojK)CgnE#)Q~q@FnGUI6I!BeiX4BC5^Lp9m*eGp0BAt#PjZm#Nq=mpGmu9qg1_3_RcTM_<8O} zoxfZNGw!^m{V$N`2S{XIt0))~A7Nl+Z92LyEY;*rB;$n+Cqegla4gwqmuNy%zc|XT zpY-AKekm(fHCRe3_Y@0=96cGMD3jT){rXKGzGxW-x5&pR^V8giKKC2zTjytKZlb6b z-!8u#`uKtUuB=?;6>lUN;nNM1P88--EA)i%?B5zo8!R&&;d%=y(;K-j>u-?VkX?Z9 zzQQ)Hr1AR2Tib`I9&ISUelaCm<}H8bUy!h<6DaNzIK^%p9Uz2synMukWS;W>52#Yz zyAsFesjjXd`ayHJZ(N&gu`Kjv@fJ)=#KC?N@W{aNIaJ&?XO~RR)#CXcd{*T$D-w~) zFV#)VvO1Yl4efWlbjgCzQ{41H?l$uyFFDT)9Fz#$TJC@HAX>0L83z_RN$fQWu(P!P zEF_#M&E$@|Sh6%EE-Js{y*9u2LFS@vf^`L12kM;l!IH_FntTd|Th-(Tr1M5B+g}fz z7B_QS%5~}Dhy%_YzK94*pmA2{aVu2T^;oi{d~4OvA}Gslim)im;Qny)15e8TpRX9{ zt&)2H>k|Z=Vo&WemsxZWGwFh9d~isJVfUdme;xlmaP#zSSnnNJWC8&0&A;FF6CwwM z4$FN*F8uRSN*VdXrEN?ph6{)7goKX-yeDwp?*0(l-TOJWV#~XG=~G{BRDvgTc#5KL zncV*`UjFZHj}YI%uOJ3noYt0%5C1kyKI1yIwbuCG?iZiiih8=}hTe3Rha-Bm-QVAQ zhN{?XLx&}Dl9Yatg{G~lvG2i!o18176!n1q**ePK%~E!&qWzPtvst(OAzkFbuPE~H z-UhhcW(ybXJ;!t2;M$y!(y2|gPS;cZ2(xQ{{E-&$M1|5>Zh2>?A8tj2D2@W$lP}Qs zzk!avA;9@SyeK_S8^iawd-O(X(KC%00y(lTyCa*)kA;{C-@AdyPR#Jb*1wV({wZk%(n{FAMhIU+Ao#u)AFx~xL=!B>F>MwbTf?z~?dSXeFZO8g7 zqi!B0<<^TI5ksy9?f0k4uB>gO;Sc;yRr0Z|=PMBs6&qLA59MZB_w&X+ok+3m?N!?P z<6@I_cbB6nDrck_beMY{c-1qy_0`wLS)_aP)ejPi>}jZ~!dS~Nj;kNj4SQ30Z#wWG z2^`hISsPfp(^%g=zZCU7_v!6n`_4Zf`|Is^rxF5Zma1>~)PH4hAa*ib?v@;{DZj!cQLamKyz!$T#`|H+KsL!K=1NzrcO5#HaWi3wBRf3Evn}P zsoc75IijZ2pGig7zfL*b43IWlunG<@r?qPpaCdAHSF6G(v#(mb``B|RzDAt9U6xe- zs4w2tsEY$G;II1T=si6lC;f$9+D(Q5Wl9*B0lec|V>RcN1^L=?W%?_kGt8&y_r9-a zpPZIAvoEq9h!PI+7x8|W9sJ*W&&sNz@(rNs4k&_i!vNCi04{N$diRK#`87~}fol8_ z@VVf|AIcTeCb1_taqH5bbh9 zeLUW!(_4qAK5-p8*IW%s39E*C`^^}0jaSa}8xmX9G8nwAt%7NaW=X*+ug`3#E+;mg z1=pQksVK0N-%r-xIk8QB9U?%L^Q-y?fds8*(jK|@k(yvpZHwng&QdwH2 z;3l#Mn(rJM{oKzqAF;es;A7doL3Q}Oy1V=F)v10+2+plY=Zw;y9~gXdNQJn=PN=q* zuS6yS6fQQF41arl`-o-ljI#NArN9r%TuCygSySzTI?fv;P|1}pmD#NrA`F|V&Bv_29X?=K1 zP5rDt>V2052z4UbO0WXZJA+Pv2rS7b&>5qpr9H(}xTS>35QA4OS7rHxA`?A`!0G$7 zox_s(K`XSrOWVI=lvM5_UQtiIGME~+;Yv+8sIYx_;%ZJnXOF>)N7Vh})8(LjBKHfU zAER{qC(XVx<)qxVTw@^!dHyXy4R(o=3MF|;$WcD+SMv2_FCp(u(p@;=cwn3&ndu9;;RnZ&=->QW zf$|gqA2UPN*zTE*uaEXYdBVUrOt2=B$hgRU3du$IsCGDcv;D3F5iHgl(xUVQI1wgx1QuUW572GZ+Fp1 z)|iIx>dEIKRKq));&zsK)+;v*uF2_X@5~D z;>6-m9_=XDpt~52M!T%NQzRbOnw87O^${e`0nY=G4{5ZpXRBGupMN~3hkmlS&)UFq z@FHtreQe>nt(>q}MAxS)=FOgY0iP&Xk&$K|YMyCq^%V-SLuR8bG#|K7yQ@X3mYPEa zIShthL6TY9BH*&QpAM&OXW6Es^Fh2|p8y=}zenC|Dzy^F!_D%}5gZ~ZW8)Q#7(310 zw*tg|1j7d!YwkNrwWUq7eh3KqGAlVI6tshg2X>3K(zAEpw$yBUh2K4W06ZQDaVl6K z+X-Oo&i?)tsCGbGbxnxIo(gEUa=;6PPAvo2g9yO5Did-3g*FFZHpxNg&@1RzoQZ)U z{mU2evAJcNkkHTpGj0-6DKA>31423xmxA@@uw5OSp1o7x3%ZVW6mDWS8=}57u0h1k z@YgZH=&?-{qDeyecDr$j<7+SR`BP&k0~3x`HtrDcrzd-vum z+1ctA1PB(Qg;G3JCb$(cdl?B{pdC2094R=(OUSi}|Cw1qgaBvjW>8wL7v zxhwMDU8t;Q94tP;5@q;z(E5M{vDww52wT1HyIV}IUW2=5AzB98 zK6(oQmq^%7S3epueku3{Q|as;zuj9z2K{0wzHOoqj3>260vKyf_%Ed+iS5=7@w4q&1nw-Eq*3g^g|>IqvUjhuJMZa{2ix z(J`#Slh%gis*O*y${3&I6fs24P>mmc8{cttTg_v zQ_NbjGIZAsc7o@n4!RY2^Yg~hApxJx2-^Ls>D|Oj;XbYkAHs!f+#lmtI3Y6Fu1A&6 z95+ZhYW}4E^KhnsyQFkAD93E}loO|MD7b0vNzgU6xmCW87m^$M%U9(tv-Ya-Tc{nP zK_KPzFc;g!!s1k7LZ~M!HST)`rSr(S=Kwg$^9RUY@)*CmK#PJo! z$@>JFMVlz+O0~b;u9+zrbL(`Op536pbFZn{*+@L;mup7L4O4pM)OhGNUv3%!-NAUM!0QNj*;+K5Q6@f&D^a3e5X8M;|JnC9li}H2$TL4IdSnD z(7XBry0(bpihp3>9Z;(VI++Zdc$|AC<(%e&G%4T@RQ10ttnNcwP^1CKPK>p}XZ`{F zKfxyA#sg?Y32cO8P0ZD-m3U3!n@y1?M>Xgv+XB@ioWGOG7S2j6K9AF`D|CENVNLbC z%hbJhV>dFjh`#w7q1J|>tStBEnKO=@etvt&GnF(?^X;fDCUy?Bb+Nvb%6R%q2NWYC z7M6zc?sk>DxlBIZ-E#tu_re?gmBtubenX`Vn*DYw{g&*UgN9axOM9Izu=3Bd-cgBM zq!4fve=*e@%z7sM%DR;;{=8+X+AUvf*w4%M>X#wXSul)-{HDe|CBXGb)(vO<$`x$=XIzGzh=R4^!IRY24%4 z@?%pacc@RxKMN157;Ds?ZfPLI+9ylQCYenC8Y@K{9?U#OBu0WLrNWSQ86=z#eUitZ zPl*C^ENF}&d_!pVf*ioBIAV+kt=6*DDlPzyL9BEDyO6J106OhBK)wJMm-b%|c(DL= zSM8|%UeIVcpsP4CJ+mfTeoMJK`oTT1!POao@o%@M$>@WwZ*P7Mi!{YOJPeo`7f2wU z;pv~}q)}h-M82M*#o8AkJvBnxP|I%0`%-V<$Y0k8n-J5 zra=~I$S{8j(UpN#G~)4rND~9%&ln;H!txIT8w%4DH^4Y%RVjD@9FGtV%R|2Eb!GxM z4}kPEdYdcmko6TKU=X|FL=xwobU3ePS@GtEt?0?X+o<7iU;ym)w~rW45;ZqXV^iUG z|GidbJX!5f*c;VlaO2Yni6FMU-)p*5{FXU{$OwGy@D6|$%0ovHRRRX%^J<`pfk+Dg zND&bkg1`s%0f>Ac0uF&bu?B3ItSla8ygPioFj)YB{OSoK&>s3RK3?lfeWy3M%YcM& zgEbKM*)97I$F#=itK!{$`7y(~|&bPnZWH0H`UL_JDFEqErPuc?24D+mm-#3}}?}A%!yIgU68kLa?zGf5E6HhEQMGSHz04GKCN!`zk&7<(exaF>0y9PQu z9{}D^4r2Wvme~nJmKqRpq4NBD4fzefK!7$82A)Vl3@>Ygg|bL(@=INfq_}vg>#kv= z^#dF7Y|31Uz%)blm@jE8Qoe(-zX(YLIUgkQ#$tvQes3j>P2eLL)4r?2Ef3&Tu_<08 zax~JUf(ioMm3;c*Q2nnbB_xmnzQP1#wQ1<+_JAt63ymT>5E22p6Jq=VYtaHuRxq$d z1ldoIb|OE1e3{&?q^#TmnB`i47bEm)5b$*p^nSK6Rm%lXg*ku;0Y5sre!H&w*8>bC z9HLL|8Jq{)j`ziG4bc32qXP?*35}X55WO&-gcIL3s2pbx3*+!-W23vgUqlAHnqYE> z*a;zH!&vDya1YREbmxJO+74q7kTKx`S01=mq~+!NSY{_dEgexk{Dz7*2mK&Wdj%1k z0zXq@eh^JUi_y#A$$Ay?U$f)CmRWUdNO$mSSPD6H5Kn`bVG0=HK-}k3*+BPoUCWB+ zcU;8>cMoQrx){*^gFGWxvuGJapAqp&FrvT*wL3&aBr{T^3y}qK-XwwAksN z_yUp=pb$S~(X7x1tsn}1yEMuagNvlcZf{-<5g>Upx7U9wqShWs6$u(FFzW$}08&AU z2jaq@69!odi`8sXGFTYE*X)yEOu7U{3W&lJoXJw74qO=d0_0bior42Nyhu|4fH3XI z!334h(z3F2V0AtI_v;zrT?Pqw0@!*$if!2d#5_b_%y{zo^XH!6%!FvEgC<#-VH+kU zCYeGnJ4|B#yIN4QlJcN&KS1$94mm_^umM1$Xb2EwVDfhapI;SZ$)~)Irmeu$5a3Yz zQx5&4f;0m!J|!g~E)cLykVW{}-Ms*B4dlRr0OWw^CxeL0BUV;K!V7FaGUGEcP#7v1g@-eWuU7kH&(%Ku8VIvg; zb}W1vd?bF^yeDQk$;u+k8!@YxaIfv~TIjevSbQ%Yi`==&zqX7cuAT1Fi#JN|(GWN? zukB0^_;ms0)+VvCk_X8RL59!x3~_4%4955Z>}{Az!XN!$;a9V$|x8oB*Nfb;| z;0v{Ixw&nYb$~*b%RS+wI#a`kR7U}Db1#eCGy}`19#FBuxfhx%0e04)G6xPLf9B^6 zSM9{P;*?ax#cX)ZZ=S@MaUZy|0VXr5%)W6Zla5h^|JA1jYh%C2a2&=jinX+9-Z%dr z3xIz>%fy5QSdcdk4hZHPhF6@9FEWDbA!Ew6b7Ha z3G#DVH2~8i=>%-aN0ZeXkvqXtm85Ue7fkVgmo=2BNYB4&(m9kUGV1d@dDg?i9DM0Q zRqw5*pt&|cm}eR z33;wrr5~6A0$NvI4}9;uNx#1yZv*=v9Q5}Q6F}%20ErbYpNttdcXyDLe6=9}iqfE$ zEmFIz0djS(L7x$US^>Di(85B~S+-#0tH%(Ywv^oUxo>*S8?+}Op_n!JKKY}&m1H7YmvzJWB*LTN6+Ph1f!i^x)Y0@}XD3lD9~*$kWBd z@aJ2W%ASmE!!v`IAGDNPE-MHH`D^Eltc_Of{LP{%z5Xf1vFY_e(d`CCio1XRvcqN| z9WvuSKiw!9p?sOAf;eHo{ejRk5U|_=|H^{=8(JQWSm^+@f~D7sl<;sI1d-_Dy%rC%#i zzfH93+HRJ9&s-ID#73cQd;XcfG*oUwgqDZFx12-LeB>~KY~cL55 zrX1hxti&E-AMKltT&Him5Y?H!I?o$zg!6S4q%caL1YF%*JjAc zJOV8rFc?#*vNkYy3JnZU`x1fUgb30#0N%Kg?idAkX9n|0(6(3E*UHSA+z%g#Oomh! z=Qab&lhk8_q>aTPy(_b>_ct`OKzkb+#P@@~_sh%vOR!*VnlKvMnbix=#5pj1LGpTs;o-uaSl8xpVRP9iq6bz z^aZ0euuErZB11c*maYkOEsE-I# ze9Lf5{*0TZS`*%rc|H^k5N&5?UJ&yB0e1mF*btr;BKBTc3F%qxlT0-S$JtbrsoP`` z0kJ&jms3BRJ*tOl$Ane;L|sn#N#j}9x5^zHzfb4TvQmoB(6{b(-=&JS7YG=&-uagK z9L2gmaInhKtjPY0p7eu-uhBG{_D6piJE&7U0|uiF!@@R}JA7NY1`scqKMPbU^hQYI zaK4>8pJsNVvedzJYehrzT`1PSO^k~(zw^i59v(+J{9bjQ9&5@U=W>{}Z^`#sr1-^Av-uPEP}^;K`o zy*FirDwj{IxvMgS&Tx#Us2xaebA%%CQ|*-2`$GAoiJh0Dty-1+p2t60$6r?l+Rvg{ zH>5Sk$NIKa30AUAz`!&?ecY$f+4j`;M|~Aa+K9!^hUmb&1Oe)YCVv*yJf@!^VP-f| zB36F9kzB2?=5inP`rMF0?CaDT;fq7^^8@vh!)RJGtmi8Dsa_l1R_=KH)PjTWP$!E> z1bC8sTwW<_W@Pl=xCeC04iiU@USFvdFdCVdlI%A#P`=J0 z=kkyuOBTbj5$Ge2`V=oy>PL{REDI%FPEv0*WF4SGZ{NH0L2Q`z<;$1gP|yM=ErHjX z3@NO@ni&N4?Ldlejc25Wcv^Y-18XBY4_A)U8LP(lL{#d>-e5nW+VWY&#(gccs*Vcf zA`zF=IPh(pjTPAOk{kZ6)0%u9kF}dq^xUv=u(`khHd zsowGS)Z9$C#cSQ-oYhO5(EH8jLJ zzqy>nx4jfY!^x`C+A@JQ`*v=_HQ=ABRYN)h%TjfT=!5lNHr|>SA_~nXYdDerL0`1S zazA>Ch|KAx&)C^>(9k?_uDWdMp!D;(x*41Q$A~batdoKDY39hx9)BOD#e_4{Vyh@I z;UcO1DjU=JyAbo+8ig(?ACt%;iL=BdHPh4z6!Y%a1k%>fwlPl3y!zScN9_5p0js~k zXDx1_K`G(+bI6`EQou3@lOS*WKK3KnDnN~D^n!Y{;b_JK`HeV&L3t0O1pmlLh1iWu z+^y`@b+#_Y@-4;?p%b>+andm&88}sH&0KhIG0JsYrzi1Hv^?ko!&1LDS(Af`oHV`F z&qot3J37uqrlD&5wQdGGi_-8v9=cd(%bgXwFI?4FaO6bZLjt_`--4?yndpD5 ziNxUH-*66{9CT-eB?;Vxmo=P4yE*d@6@@N4yT-jLS9wJt=jg-0d6hG1lu-5D{GVt2 z9uh$3>6&2?wLks+6h-iG)5VLe&xw(hQLJDjBYL(N3l17iu@e1BzL5#q{>qO?QO025 zuZ|PA{$3m{O(qL59gTJN z1sZpERW~*o?7hBlWz`Ez^$BeBc)%j(hU4iKOBVPe@Tt?1X~aE0(aT4(l#fXSos@O^ z6-0c)u#eZ7E-c@?iOFWDcYdlCr_5x(_Zln?PRFE3;sUi&+j531Ur;jXIxU|W9oy{T_ zxqpAWC?;xysXeM8fAzjR_p{jHIO$X;A>Ad>^WjeYM1A#lg8InFXywUMZ^|s%uEJyA z%YXU_IE+)!OjbTz9aL&||Nhun|$ zQ0m;Wzev=M#*3lRIy*d-YAP)7x*;vCKgyi-K+=in4oJ)c^;qPx#zL8yi-1n<-K^2lCn%AyBTIo-LPUp8ED#p&LR`E|l)#nt{)U>S0sXn8k zGdYpo(J-XFSWtMJe&tEb7O1YPbjL2%>o`67J$S&ajwxLIPJec~ASm!XnBea>)HF4} zc<P16@fxqI*i<_UEhObX-$phb1$94hs zF8gOF*2{pUrrEy+v|;Ern82BW3qO9VImR>P(B)7fOZoOxtz_(m`;_o!kW^Ci4bCG? zb|0M~?fX6+9_PDN=45BGva|6*DvVxefs^-8zQ*IsUj6vR#O!#^D90T(?fYMjUTi)- ze-R=e3-y>{-rs*XX_8k4j?Eq#v7|0lhY`kQ;C!q~%9ZPY_gJf<(VZ`zgch^wl&+~A)q?MKneUD?l5!&WElRr0Ap5%uLqQt=>G)CfWpX}q88 z^%rlD^%ECagm29-M5@=%PK;h=Tbv5l{Pg09ZdnOS0&T5KC{=H+#ju{Eoh98}ttFtdrcDUbVrmbq>UXJ+xsocsW+2~1;VOX0jZKOZxeFG%I}r6;1WufqHk1JtHz-7E~! zdvwDhSW^ucY^aFa+kYn|d{ouuJJ@UqT)NF$xXwAP6#1HCJ!+5d+m%|*`aK^nYw7Tn zb=hQfndOF*8ZEW0Q@Bquc0)m?*(U%`5rA}Iq+Ee;(Sp%PR{Lj&2@h;M!Ua2BHc1Ng z^qO?Ev2zrdDpwgt$yX`XM*ai?v`))>LHJ9T?cvCDV;O$e(uUDLK34_9mj#~-39Hfd$U%T_ zqsjca(aLKZ-&n^>Lb3OB<{%OU1qlbxCqW6h1)5sm=fvlzfixQN$BLA$D(Xza_iExG zUPT)u3x2P2T0%%G0bf0;FTW0DU+O@G0)U5G#a@!|6n3WkmXGP02dO7masQA;I21sO`O%YOWP6*D* z8oX~j_iep{C~%}@BCZ_Z46&o^a{Ih1+gj2czb1reX2SH*I|Qj-!ejku>*yE)|AHCs zGiz;&jV3xIzp*E`9F}U@*zMt3!lBo!R-;U#nStm_X88FrMi5>++sh*PCHc}IydR%9 z^lgf{8%a3D*IrCEc^!=UzU|skY8R$;LE!VPTA*R~pSpw(LOa z#o=nEboJ!?{N6>ZX~$Z$XGg?Z|7!|Pank0mrR=oVKE-8QjUCp7QM}^$j^0(+5+V^< z>@M$G6HO6ITVG8DGU)WC>X%aW@3ZT0d8%vW8!=ah3v51m7Hv}4aaZRKaRlJ=u?h$z zRL{7Fg2Aw1UQ#w|D+}hWTZpqMGAe<^oR1?R6?}WDZ+sIK?OS{J-#($!4ptrIIOxYe z>efhg$5bm+G#MO;>n7oTAyulXrgB58nz38K?_CUy-qLu${xU;%08{;&n@uNZ4sB;RQg~`-9KvmO z@2N~%DkZ2D44)?nslO6rCX1-9#1WpF;^3-_XwId3akHYzWe)c)pOh1(eG)Q^OG!zg z039^o*&;d{8F^cx+^{(CG>O3y$&2L`HzXx5f|7N5SA0$xJ`1Wg2|F{>&@yzE^H}On z)g%hFtz%Qb=AjM5!X zu_+0Np25&rPx07(z{eL49Ij}Pv5!zY@Ba_$D)!zaxAT8m`^u=Qx-MK05EKv<=~B9p zZctKMxn7h|?8Jpwba!`N6!o~*uRHZp0 z9o_mAn4TA2ONi@=Z__Es08@SAcF)o3`eu#AHz(9ds`)rZI^oHJ_+zs!%EcU5oG_}R z%syl&|?7U z6QB#!+SY~$q&)!l$x-1xcluv*o2kcH09=JJ5iR?U6BrxZ1&@LaOMVxXIK%*@{{1y6 zs^I&5Q4?(evBt^Fy{LVRO&vjJ{1fu24-3>{ZMC)NdrR(a6!N@t8j3T)c7I!GeHR_C z?_$^V7zHJkyY!7Ty)Y^6cUWshb)e_|2f5^!JqGm{s$raX+|A976y84`teC~ zzQSI=4KB}P% zMtJ`@g%{RZUG4{<2Ld2WAP#y6B&x?+R%}V2Sp9`qlewBya~#uI%|Vm8 z6e$4_L)AjH&4h5VWoL#GUDYXhgZWavN%FH+EuTUlop7Dhet?#cv(mSgOu? zLO+;qaj56TytF?nos=ehq0HfjQI4$4*8zReUVK7AeZSKw2@V>utF6a`&3;7Wm$aP> zGq7(ttpf6<`GY6F-6E~n3!`RVC?Kv@_V1#u@Q zr|BsGfn}DKmiApjkX+0fI~SKJ2mt|5US8r;3#c5a+1Y^@m@JT=s{<^g00{$n(@*g5 zAez}7_$3tDiF{6iCX?7lr7?LLX8G+e1x98$j3!L9Z>xvXRHFF;Y_GV*~+|!-v zC!8CK0D~|ffp4y^{HU(48n@~nwRCmuxUNXTU6aqgz2B@Y6{=czl%t(rFxKQb0cNR( z7oYc^Rv8vL*6r)thU46U8LjPGZEg0bq+Av0_Lq#ZiB+i5J@Z{5XTxPRCEmTokdqbU zbJ}VR#3cF!G@Wf*Zn#>2wsA#@ATB7mSs)ZjPe@Dz;HeCN4v$VwASo%mSmC!i0bnxN zPE?;Fg1%Y6ZS$CnOaXM^0?v)b3{S|hiF{5TD~$5;zG}BDp_4Ikt~hlP5sCQ-Jl}fN z+S8{oS>qMM?If$rVN40q7!wL1J_3W0# z87-+e>qWh0g&Gz097A2O-&g|LtvB9!?&6wU{I+SMk+poTa~YJ|fS`kS!E*bTq9&n$ zf&VR=>048U=95R)$04KdAeWnq4gjtdz_CD1MFlA_iUv8#NXy8uveuP-ec#sB29Ua`q07-Yq;Qg+Uw z8)vuQ2Kd5yiETBmC2aje1G2$X0y>&$I=YO})O01WLsh1iXFJ)Arn{4L7rp&R!omqW zic{oAZqHN|WB}SIFs1;Un#g&@P>`(6r=?kA!|57YRaq$L zZf=}4fCitv=svKt#{4G9@Ri89Xw$|DBRrg&>w8lZTfa^F$K96hIUV66cR?$wrHr5$ zNfRYYFsz+>nSg)*C|^RF2!I3%nt8LcM1b%Sk}9jJ>N>~0T?JE}1CV+x0nF4ys%da&V2>{zlPuWxu9Sw0Jtl)m;elV4f5=qX$3M|OaQunzha0J!M$#nEXBssQY!!*A-QNsHxC5zkc?tZn#UKQ z+Vj$+?-}T<9^HygJZt;$<1^@F*e$9Y2tQR@Pt| z5bw6IurML9FfufJBP&Y@VoV_71_XNg>c>otjS>2kW{F1fRT-cPIRJw9KlK2ZWaoDW z%y3g?M#fMO0YL1okP0MNjvWAhzXU}2(7z+FnFY#`fM}TnxIxh%Pi-JKN={4rnboNK zCBS7yOC}w92L_A*3kHM+7HZdoLozjO@!z3rTjp&Z!X79fAOK|v8AeYsC1m1WDMcw= z*ma_bN>YH&dFh#$fQ&~DWAEn9j)zRYYu8ywc=!fTlw1p8X@1VjOMrlmjt=w-x`8)h zjm~UF0N^tyyZBn|5L71(wQPgFP9j=*$h@dBohDqw9wX z91p(=KeBD2g0v?d2ZB-Z2mElL@&y?g`Hg=GB53131iKeIFxR0PCrCovz~BX_+>b%l zM}bNv0s|*K7uWgC=*=1?rx0o0YyyN9401=0oqoER0Z2YOPKYLM0>0P3eabf6>T zq6a`h4D>EczC^ES>t{wlMn~-)H0@+$`rm&A=@f{{?0z^X76R!>Kqve6U=fZdpj8!v zgs*bO2(&V`c659LRaM|4G116>I-bF)-^j^0nB+kgd!$kdk}!abEVVKC4oC%KuL218 z0b$;F#u0=i}*Zh6~9K|YQN2#=)1UaLIL z49;uhP>hFP%*<^S6~h(<1ucdk3hr``Q!Gbo&OV;(^i_HYXm;A>fFBB4NrS5ImeW%xghh-f1!xf6D$JaA#;I`_cvjcSAUR_%ob z9f)qe9W3|5B|dwoDcz%yZ(jfy_`5BrD3Rm&|1KUyT)YqKY?B3_FKTx5^g}=y2m5O4 zwlZ<>S8gXBk@1#Oj9*pid0lj18EgLAg)e3VAcw0*uT0}IDDqTam$7d1evipjeTGXE znPwzyZ_(jJ>|9>8e&_M&-r@G7oHJjw);Ds&o|r}vBMzDCKt|9^rIa zOl!FG2LxeiCR?S1H1jQ(+XR8(VqgXQujgOP==(@}TZgn@@$B5St@g42T}l}0yqj%+ z@KAKSX3gxkwtZpmrFVd4;6&j#Z5^{!Ztf2K z-7hCucFbnUtUeG&q@3_Cc6Pl5sZHud@@->c&RF!ij`q5d)rG> zQHZW{uRY|$p~tWs6Vtj(F&YYjiX@)xBIdtSOT)xW^qN;xa>N`^WIbwWLB=-Hlx9fE z2$g-qgT-`%K@toWtp_>#-}dDfgarv2iHVK@z|JI9jhV%)gRg#(^lz)V2#18bdqk4y z7GU%UlTv)5^M=dm8TfoB_}^to_^p#tRPy;p^HAm=EhONgRir>s6}XJg-L!j3ML{qv zg7$rLNudN%IrBH}M&a(3_of{>KXK=zYz&?u8sn+l+1s5Ipt^4tyn56*b88GDhY4eyAFx~?91WS&hi_I4cB$UKwcuf(9Zt6eLjTU&KwQa zy9UrH-uK~@SXrwU_hs|2I4N10Ut(||JDpmn_IRSu9-D>$Q$5_f(r2jUcf+F3ye3Q^ zyK|CasBh}qD!~K;ir+j9iTlpjV+0YeTey zJvp){J!zM5r4n$fXb~KAIn!W`S z8?kRluAc+xIap3FN?eQ-Dk;Y9Q*xR6V?E#SKb{%775FTpX=-w%K>6z_ftiB6#k{ZV z@Bn-E9s2yGR|9uA7u6|R-Pv2`xvyBJ#*#Y$m6sapUyI60?JwuS+nnA9VEnwHrRx zX}6$3W~Y2nPo>3V%mu&F1&g>NBJm$j7bd5-z)55ix)a-H)IVExlumRY^bF~haSBOZ z;>Mh>I{mp?w#g=`k1rOu#;R~vy8h7R5@!mEsTkWdei64EhMHS}_shFVKXhLcz{Bh* zxu%o2^xVy>O1DeVtK+gUwmlMC6lmFNtr*9_R6#5PHL|~aXAp_dIqN=6a8J|Ha&0W2 z=myJI8och@wj8R@S0JeS-uPSfdB0g|t^*_ISjy@Hu1Y6{^eQtUgrF68?*My->nw)! zIQ=c}x?i$4tIc^AJlLgkwk{&=bz+ujQU&)6B3^@`M~$+n!+xm4+BKC7X(LVWgaRqj z5}3fRzn&TlKC+D)49$PqVbt*yf02d&E9hABlbKQ>kH;h~e}R%m&0y!J_;Ms1u@Lp15 z_^ithWoeWdPCn@V_rwXImE58I^Mw9fU z1UJ7E)n3#&m*5`-D`QVGwIDQaB>%Iv+UyT>FnqOA*^45eBcX1zlNONEeyWUy8u zibd9F`C*%~)A8_fUTmMzN;c8WOZJgtiB7uvjdQ8uYQG zdw_2-u=_XC%`6+y(Qhg(GmvJpS)Ev0Ad4&pCX&D>$47m^Z`<2G319CV|7McJqlZj? zBo3G@G|S`+b>ZUXu>Dy7Na_CW02`Ad(aV$32bjIDvi+Mz!j0FG9QdY_8vNPwLs1Wh z6mG9er;rqS$QSDPIz}Qk6vDe^LLY6}`NisTX$kgc|u+<6!zc zp>=tI6SH1F-y7J)_k(V_(yJ_r4_{ATxS5hsH_Zk&u|7h+TGMk>n{UQPUAXFk@!u=w zJ>1vBRS2Qp&%|LVvPNn(f>ly6EPiZR_%)no!hhsUXAF#=RHIc}Akseki-U3Byi{k& zb%;1tw;g?N5|-~-LmiE-RLN3k0Xqr_Ppq*Vh*Vrr%yv>M3@cb>zw)u$4iGT=lm%=f zVS^APdOca|Wt~2oLYM$*>#VX-!W>KDda_rrT=j-8_R8m5HyZ3KYHa6)z@oeQw;EQR zl~g3O%&JGz*gcoammR>v#m>tVsP3Tf&BWL(%{Re4cF78v#=Qls$9Q4Libg6p7wmBAm$TiQLIG~5|`V8UPZjx)&PU?3&a1A2YBgE@* zEC{%?xfq=A-<@X;C^NuQv{VtOFC=MZMiCGaK=i{cM(L#1{x+FwSIWG+71W?v(UEK| zsl)TkY$Wcy$4lKB0wpZx6FrMray-f8r@#Qk zP4K`~=|--05vercn`*7a(c#FKYlw%luYssX+}mp7`H5zHV97~IQ-0Jg(CPGX8L7u$ z_moV!$_{g`zfBPYR`+G|g*fTM7KGLe4bO$^W*h`96=FA67I*P(O1<)Ti*%^R55q>x zRW*|G>?_nwS-=!nveIr(XO0inKH`qA82shi_W>zq53$eGNG(R)JT$mW%{K8AlRc6Q z>C@v(x+vfIzqwdKq7X~Gb5y%(rsXvG_-Xrr#4}HUpXRC)%o8^j3(H!T$4zW6;_RDP zM)$VgrfE#aF&C8=SgWww$~|u(aiVy?UA*hdJoS63zAp zOX(1PW3~(DRXy+gjI3sJWOBaq?hB|tZu{h(J@w7-SW?x3#}f=5S=!q;WPwEUj^E;` zP+Zh4YQs4hhxA5Xedova4m?jKriqyb+ZrMJNq2|LY`pHOxw439+5_bdmc!9n9-;Y_ ze$wqM7Ie1>8P@|KF1WP!7Bpy~-#BfYHU-(r!HtIzaW9JnEW z!_qwGzZ1Q&$)d@2?3_Joe}&8Iads3n7F%{b-;ce5(||>kSLt=_iN3J-^Qk2|XOlmB zX_bm{e%|c?H~-?t$dd*KwvoWew9DW1DliTCL5`fw3RhN76#@33l#)HFym{waBEUqH zX&<>>5A8S~hlORMBLooDnN2^|&`_6Q**ZIlw1kMZ9Rxsv~2ajQfT@AulW9+oiA^Pxlt9RtA z`OAV2O4qoB<*8>%kIS$-neUQ=?(5__3`MBdKY7{2vN_6FawM&Ts-0{JgYr`&|3x(4 zls-g{Ij4H)533Bwa%7n#+JkqD1NlS#4R>M8Ac|;MSUtd2?STOcpSO(16K~qP)|L9x zJeETR=CWPrb${;`zvIR_9B_q4eUdZ;8P!lK$5{*f+inKrfs3%D*c#mE4 zvHh2nd)WKhKV#w7$0*DVnJHVdLL|WQ?C&N@%YS%q*F8Hi?U+AQY7?JN`OY~w%htu? zXj+YoRDU5Z_k1w~p8iylnL0`Zm@F(_1V4`Dnl%zEw!Fip_7+Gj@bfAg z&#f&d!8Wc17I?UEQS_$PIJilb?!u|QeLp(ft9d?oFi3anj}xuUlZyxt8Lq&zN8d~6 zr|IIzf;|!j_)B#+&p zS+AIfo?9U0_B&KCzI*`qI3Jan;}UfJyS*-OStdr#mZ{?DuB5=?tb;mqXon~?J)Q}# zwUID&s7C?+y&8=30?d%O3ElDeT`_+qU_fK$Ex|5F7=E@CcBUSGqxJ)RhyZ8UOo6y% zed+-=c4@)lMFogTJ0kwK&LV^OvvfVx(!R+(pzzEpyUAt8{I?kj~~wPg!}nz1=yji9bn53T#|n_ zQ72N@>1Ac|wAO*gP{Dvj-#O)-*7LSz5Q1IYVh^@yc!K3DLBB;1)7vDdbZOGUf2TS6 zfq&Nc31L)QuNYCN$~&v9u95G^$q$|Pz|uZ_Pd=mDg!Rg^g}B-M98JL&x4i*}Z>{=l zaJ}hHY;{lPjZ1UzLPGt8Us+__5aP7g=?|&2OU8sU^wvvPans%*v0pJ{&9*uYG+Z}_fu^+2yR^z|m>wP3av zrt>yfx9o@az`lZ5f%a|zKJ}u>s`rS=-c@;$8>Fkv9aQ{WK-&T6y8HRF46@83pLbQY z9%{S3%KJ03$rS+t>lYjT&S9LF8$}P+1`o84#u_`s^h*sEl3q1mA1G2^6!<(S&Ttd5 zcwp?Qrpe zPs&o0Yj^}>yw6c7CgiG@iqchz$dAfVEHpRV_;FU&b(}<3H}D9s9oq=aW}jZqom%!- zR6T~h7k6oX@It6g)ihkM*6OuVJ<9$g@!xg0DtQEGKQAsx;h_%Xd;NQ|H|Z2aSanYU z*J@LeYt0JS&zUi6jr6gTnQ7l>#q@4(Q0i4vcGBR1sHJ!URU)oYUw)pN?F-+BDRL zlpZVTQ`Lh2!p$Rly2iY#NgOavDb@vC;fRM0^z8-hPYu`Sl9ES3M78}y!<)wJttrD~ zXK(dK((3i88I9ZmYjfosVv@fpez}D4e{$jGg<7b7b(dAc*xbe{CC{qKoHyzw+6L~5 zIrnZH(2k2|#gZRm%4hq#ya)^j-ds#D>VJ8S*gQHXD^yf0BEF{x_nn$$gW`AtAFaj- z^0Ix9nclGCUy+S)CAC#(s;aja8OH&E1p9re(aD+(0)Nh9+q}Q1!HDs{Y5h#r6T2X; zqUYxWOqkJbmro<@?RlnWMsswpk_lHMlZ&3XBtzLM2atK(2?%h00CE?#N9d`k&na{F zkpOI9ul=7R!y`uP>WYYP8xw`|xWu3?>dOwNzXSWe+D8El+(vff0S$WJbwh~jtJV0r z?qAJyhGxymrmK_sw$zb<(A+Bp{qkz?hG!O+h_6t>vbTc<@3yOi%C;EN)xa$=y}W!tgR_AmKB^z!e$d(ULY zTdQAd&DDnE#Cc6k){5w$x^i!g63B*P{>_lyfBp}o@cBD=ZkUqe?v_1mOrnAa2T+gc z!&vl-lkUI-3wF@|-%M!IQ*QOGMbwp*WvhEJafE#2060<1YwcH-I@Hu8;=6%mS2PZ$j^?P4EpsW_W%2z&W4jBAha%)6~5IQPpvu?wkR1n+?(WTqpd-?x=mH*cb zR;#KuUCdaW{vDz>qn;)C`9YI2yXy|_q>X<^ulBX&-X6)rvR4C(Xvt&H7)goCixrE! G^Z!3ZVE`@w diff --git a/resources/images/headers_lite.svg b/resources/images/headers_lite.svg index d637326..54fd709 100644 --- a/resources/images/headers_lite.svg +++ b/resources/images/headers_lite.svg @@ -8,7 +8,7 @@ version="1.1" id="svg2909" inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" - sodipodi:docname="headers.svg" + sodipodi:docname="headers_lite.svg" inkscape:export-filename="headers.png" inkscape:export-xdpi="200" inkscape:export-ydpi="200" @@ -28,16 +28,36 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="1.3719136" - inkscape:cx="466.13723" - inkscape:cy="319.991" + inkscape:cx="404.17997" + inkscape:cy="301.03937" inkscape:window-width="1920" - inkscape:window-height="979" + inkscape:window-height="975" inkscape:window-x="0" - inkscape:window-y="0" + inkscape:window-y="32" inkscape:window-maximized="1" inkscape:current-layer="layer1" /> + + + + - - - - - - - DMB + + + DMB + Device / Cake - - + + Header - - Header + + + + Encrypted Slices + IVs - - VMB + y="45.311543" + id="text1911">Encrypted Slices + IVs + - + + VMB + 0 - - 0 + + + + EncryptedPosMap - EncryptedPosMap + 0 + y="56.927811" + id="text3537">0 + . . . - - + + VMB - VMB + 1 - - 1 + + + + EncryptedPosMap - EncryptedPosMap + 1 - - 1 + + + + VMB - VMB + 14 - - 14 + + + + EncryptedPosMap - EncryptedPosMap + 14 + y="56.927811" + id="text3585">14 + DMB - - IV - 1 . . . - - ctxt - 1 - - + + IV - IV + 14 - - 14 + + + + ctxt - ctxt + 14 + y="69.415131" + id="text6004">14 + - VMK - + + VMK + 1 - VMB - - IV - VMB - - ctxt + y="86.697533" + id="text11134">1 + AES-CTR - - + x="112.45213" + y="86.241196">AES-XTS 1 - VEK - VMK - 0 - NumSlices - Metadata - | - | - | - | - | - | AES-CTR + y="50.840267">AES-XTS Data I/O - 1 - 1 - 1 - + + VEK + VMK + 0 + NumSlices + Metadata + | + | + | + | + | + | + 1 - 1 + 1 - VMB + y="93.41362" + id="text1388">1 + - - IV - POS - - ctxt - 1 - 1 - POS + sodipodi:nodetypes="cssssc" /> AES-CTR - - + x="141.66179" + y="75.678764">AES-XTS PSI 0 PSI 1 | | PSI NumSlices-1 . . . | | | | - EncryptedPosition Map - 1 - - + + IV - IV + 1 - - 1 + + IV + 1 + + + + ctxt - ctxt + 1 - - 1 + + ctxt + 1 + + + + salt - - IV - 1 - - ctxt - 1 - - salt + + salt - - salt + + + + IV - IV + 0 - - 1 + + IV + 0 + + + + ctxt - ctxt + 0 + y="69.415131" + id="text722">1 + + ctxt + 0 + Shufflecake v0.4.x + + y="37.286942">Shufflecake "Lite" v0.5.x+ + . . . + + + From e2ac3f7bd231a9eb45e5cf68054d7149b5e48c07 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 14:43:37 +0200 Subject: [PATCH 80/98] doc:Minor fixes to README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 118da68..2c1cd95 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Volumes are password-protected, and embedded in the underlying device as data _s Up to 15 _ordered_ Shufflecake volumes can be created on a single device, with the implicit assumption that "lower-order" volumes (e.g. layer 0) are _less_ _secret_ than "higher-order" ones (e.g. layer 3). Shufflecake is designed in such a way that it _chains_ volumes in a backwards-linked list: volume __i__ "points to" (contains the key of) volume __i-1__. This way, providing the key of volume __i__ allows this tool to traverse the list and also automatically open volumes __0__ through __i-1__ (besides volume __i__). -Opened volumes appear as block devices of the form `sflc_x_y` in `/dev/mapper`. It is up to the user to format them with an appropriate filesystem and mounting them before use. It is recommended to always unlock the most sensitive volume for daily use ("home alone" scenario), even if that volume is not being used or even mounted. Only open a subset of volumes (with a "decoy password") if under coercion. This is due to the high possibility of corrupting hidden volumes otherwise. +Opened volumes appear as block devices of the form `sflc_x_y` in `/dev/mapper` and they can be used concurrently. It is up to the user to format them with an appropriate filesystem and mounting them before use. It is recommended to always unlock the most sensitive volume for daily use ("home alone" scenario), even if that volume is not being used or even mounted. Only open a subset of volumes (with a "decoy password") if under coercion. This is due to the high possibility of corrupting hidden volumes otherwise. For security and consistency reasons, you cannot init/open/close nested volumes within the same device one at a time; this tool only allows to perform these operations on a _chain_ of volumes in a device. @@ -77,10 +77,10 @@ First of all, you need the kernel headers, device-mapper userspace library, and sudo apt install linux-headers-$(uname -r) libdevmapper-dev libgcrypt-dev ``` -Important: you have to make sure to install an up-to-date version of `libgcrypt` that supports the Argon2id KDF algorithm. You need the 1.10.1 or later version, which might not be available in your local repositories (for example, Ubuntu 22.04 LTS), in which case you should find a workaround (either install manually or override your repositories by pinning an up-to-date package). You can check your current version with: +Important: you have to make sure to install an up-to-date version of `libgcrypt` that supports the Argon2id KDF algorithm. You need the 1.10.1 or later version, which might not be available in your local repositories (for example, Ubuntu 22.04 LTS), in which case you should find a workaround (either install manually or override your repositories by pinning an up-to-date package). You can check your current version (on Debian/Ubuntu) with: ``` -libgcrypt-config --version +dpkg -l libgcrypt20 ``` Also, make sure that the Kconfig options `CONFIG_CRYPTO_DRBG_HASH` (and possibly `CONFIG_CRYPTO_DRBG_CTR`) are enabled, as they are not default options in the kernel's defconfig, although they are enabled by default on some distributions such as Debian and Ubuntu. From e8c6d5eb1c9c41845a2d969affae35c553c4f163 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 14:48:05 +0200 Subject: [PATCH 81/98] doc:Fix to Lite headers scheme --- README.md | 4 ++-- resources/images/headers_lite.png | Bin 118256 -> 117180 bytes resources/images/headers_lite.svg | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2c1cd95..e7e7dc3 100644 --- a/README.md +++ b/README.md @@ -201,13 +201,13 @@ Shufflecake can operate in two different modes, with different features in terms #### Shufflecake Lite -As of v0.5.0, Shufflecake's default mode of operation is the "Lite" scheme. This mode introduces the AES-XTS encryption mode rather than AES-CTR, with a related change of header format. This mode has only advantages compared to the old "Legacy" mode: it offers the _same_ level of security, but with better performance and data resilience, and it is therefore the strongly recommended way to use Shufflecake. It will be used by default whenever a Shufflecake command is invoked (see [Usage](#usage)). +As of v0.5.0, Shufflecake's default mode of operation is the "Lite" scheme. This mode introduces the AES-XTS encryption mode rather than AES-CTR, with a related change of header format. This mode has only advantages compared to the old "Legacy" mode: it offers the _same_ level of security, but with better performance and data resilience, and it is therefore the strongly recommended way to use Shufflecake. It will be used by default whenever a Shufflecake command is invoked. ![Scheme of Shufflecake Lite device layout](resources/images/headers_lite.png) #### Shufflecake Legacy -For backward compatibility reasons, this release of Shufflecake still offers support for the old (pre-v0.5.x) "Legacy" scheme, which can be selected with the `--legacy` option when invoking `init` or `open` (see [Usage](#usage)), all other actions (including `close`) do not require specifying a Shufflecake mode. This scheme uses AES-CTR with explicit IVs written on disks, and it offers therefore ciphertext re-randomization, which is in turn a necessary ingredient for achieving future modes with better security. However, since these modes have not yet been implemented, currently the use of Shufflecake Legacy has only drawbacks compared to other modes: it does not offer better security, while being slower, less space-efficient, and more prone to data corruption. The use of this mode is therefore discouraged, and will be deprecated at some point in the future. However, you must use this option when trying to open volumes created with an old (pre-v0.5.x) release of Shufflecake. You are strongly encouraged to migrate Legacy volumes to the new Lite ones if you haven't done it yet. +For backward compatibility reasons, this release of Shufflecake still offers support for the old (pre-v0.5.x) "Legacy" scheme, which can be selected with the `--legacy` option when invoking `init` or `open`, all other actions (including `close`) do not require specifying a Shufflecake mode. This scheme uses AES-CTR with explicit IVs written on disks, and it offers therefore ciphertext re-randomization, which is in turn a necessary ingredient for achieving future modes with better security. However, since these modes have not yet been implemented, currently the use of Shufflecake Legacy has only drawbacks compared to other modes: it does not offer better security, while being slower, less space-efficient, and more prone to data corruption. The use of this mode is therefore discouraged, and will be deprecated at some point in the future. However, you must use this option when trying to open volumes created with an old (pre-v0.5.x) release of Shufflecake. You are strongly encouraged to migrate Legacy volumes to the new Lite ones if you haven't done it yet. ![Scheme of Shufflecake Legacy device layout](resources/images/headers_legacy.png) diff --git a/resources/images/headers_lite.png b/resources/images/headers_lite.png index 91e8ae575e3fd76a89580e7608b0f5b13ec51ba8..f33cf0b725d8b406038543d4f79186b8aa7676da 100644 GIT binary patch delta 99672 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

s)FI`4RO5pHs#ul5KM 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𝔛)=3fataU?{?jYy8uNBjnnVC1{noV5DFD}ov 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 Apa1{> diff --git a/resources/images/headers_lite.svg b/resources/images/headers_lite.svg index 54fd709..e92a7ae 100644 --- a/resources/images/headers_lite.svg +++ b/resources/images/headers_lite.svg @@ -28,8 +28,8 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="1.3719136" - inkscape:cx="404.17997" - inkscape:cy="301.03937" + inkscape:cx="512.78739" + inkscape:cy="354.97862" inkscape:window-width="1920" inkscape:window-height="975" inkscape:window-x="0" @@ -284,7 +284,7 @@ height="68.120209" x="27.126791" y="30.93387" - inkscape:export-filename="headers.png" + inkscape:export-filename="headers_lite.png" inkscape:export-xdpi="200" inkscape:export-ydpi="200" /> Header + id="g688"> Encrypted Slices + IVs + x="121.81499" + y="45.311543">Encrypted Slices Date: Sat, 31 Aug 2024 14:49:56 +0200 Subject: [PATCH 82/98] doc:Fix to Lite headers scheme --- resources/images/headers_lite.png | Bin 117180 -> 117212 bytes resources/images/headers_lite.svg | 14 +++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/images/headers_lite.png b/resources/images/headers_lite.png index f33cf0b725d8b406038543d4f79186b8aa7676da..c392073d06d56486b0d9b9d1c9e3391716e19e2c 100644 GIT binary patch delta 13887 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

tlKb~@KwYD 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 d&OT2{ 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∋bD-F=})rvXGq)%x^!73T481{Tk02 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 P1UT}qm0lIUH1hjjKCSG{ diff --git a/resources/images/headers_lite.svg b/resources/images/headers_lite.svg index e92a7ae..a4a5541 100644 --- a/resources/images/headers_lite.svg +++ b/resources/images/headers_lite.svg @@ -28,8 +28,8 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="1.3719136" - inkscape:cx="512.78739" - inkscape:cy="354.97862" + inkscape:cx="430.42069" + inkscape:cy="269.69628" inkscape:window-width="1920" inkscape:window-height="975" inkscape:window-x="0" @@ -866,11 +866,6 @@ style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:FreeSans;-inkscape-font-specification:'FreeSans Semi-Bold';fill:#73573d;fill-opacity:1;stroke-width:0.0830525" x="167.9277" y="50.840267">AES-XTS - + From 4f27a77f7b5907842a1143b2dbc095ac47fc06b2 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 14:57:09 +0200 Subject: [PATCH 83/98] chore:Update CHANGELOG --- CHANGELOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7d25a..4195369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Refactored - - Introduction of "Shufflecake Lite" scheme as default. - - Moving old Shufflescake scheme as "Legacy" option for backward support. - Global constants fully shared among components through `sflc_constants.h`. + + +## [0.5.0] - 2024-09-01 + +### Refactored + + - BREAKING CHANGE: introduction of "Shufflecake Lite" scheme as default. + - BREAKING CHANGE: moved old Shufflescake scheme as "Legacy" option for backward support. - Global refactoring and simplification of code tree. +### Added + + - Benchmark script for Shufflecake Lite VS Legacy ## [0.4.5] - 2024-06-03 From a36caf36cf8c2588df58c58546f7d077d8f79f4f Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 15:22:19 +0200 Subject: [PATCH 84/98] feat:Add benchmark scripts for Lite scheme --- benchmark-suite/sflc-legacy-benchmark.sh | 389 +++++++++++++++ benchmark-suite/sflc-legacy-fragmentation.sh | 457 ++++++++++++++++++ ...lc-benchmark.sh => sflc-lite-benchmark.sh} | 19 +- ...entation.sh => sflc-lite-fragmentation.sh} | 18 +- 4 files changed, 865 insertions(+), 18 deletions(-) create mode 100755 benchmark-suite/sflc-legacy-benchmark.sh create mode 100755 benchmark-suite/sflc-legacy-fragmentation.sh rename benchmark-suite/{sflc-benchmark.sh => sflc-lite-benchmark.sh} (95%) rename benchmark-suite/{sflc-fragmentation.sh => sflc-lite-fragmentation.sh} (96%) diff --git a/benchmark-suite/sflc-legacy-benchmark.sh b/benchmark-suite/sflc-legacy-benchmark.sh new file mode 100755 index 0000000..d1011ae --- /dev/null +++ b/benchmark-suite/sflc-legacy-benchmark.sh @@ -0,0 +1,389 @@ +#!/bin/bash + +# Copyright The Shufflecake Project Authors (2022) +# Copyright The Shufflecake Project Contributors (2022) +# Copyright Contributors to the The Shufflecake Project. + +# See the AUTHORS file at the top-level directory of this distribution and at +# + +# This file is part of the program shufflecake-c, which is part of the Shufflecake +# Project. Shufflecake is a plausible deniability (hidden storage) layer for +# Linux. See . + +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 2 of the License, or (at your option) +# any later version. This program 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 General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program. +# If not, see . + +# Benchmarking script for Shufflecake + +# Variables +SCRIPTNAME=$(basename "$0") +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +LOOP_FILENAME="$SCRIPT_DIR/sflc-legacy-benchmark-loop-file.img" +LOOP_DEVICE="" +TIMEFORMAT='%3R' +SFLCPATH="" +SFLCNAME="" +DMSFLC_INSTALLED=false + +# Colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No color + +# Help +print_help() { +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" + echo " " + echo "This script is used to benchmark Shufflecake Legacy on this machine." + echo "This script is part of the Shufflecake benchmark suite." + echo "Shufflecake is a plausible deniability (hidden storage) layer for Linux." + echo -e "Please visit ${BLUE}https://www.shufflecake.net${NC} for more info and documentation." + echo " " + echo "This script requires root because it operates on block devices, please run it " + echo -e "with ${BLUE}sudo${NC}. It does the following:" + echo "1) Creates a Shufflecake (Legacy) device with two volumes." + echo "2) Opens the second (hidden) volume, formats it with ext4 and mounts it." + echo "3) Performs various fio r/w stress operations on it." + echo "4) Unmounts and closes the used volume." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo "You must have already compiled and installed Shufflecake in order to run this." + echo "The script will search for Shufflecake either in the default installation " + echo "directory, or in the current directory, or in the parent directory (one level" + echo -e "above this). If the module ${BLUE}dm-sflc${NC} is not loaded, the script " + echo "will load it, execute, and then unload it." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo "You can pass the path to a block device as an optional argument, otherwise the " + echo "script will ask for one. If no path is provided, the script will create a 1 GiB" + echo "local file and use it to back a loop device as a virtual block device to be " + echo "formatted with the appropriate tools. The file will be removed at the end." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo -e "${BLUE}WARNING: ALL CONTENT OF THE PROVIDED BLOCK DEVICE WILL BE ERASED!${NC}" + echo " " + exit 0 +} + +# Function for debugging +bpx() { + if [ -z "$1" ]; then + echo "BPX: Paused. Press any key to continue..." >&2 + else + echo "BPX: $1. Press any key to continue..." >&2 + fi + read -n1 -s +} + +# Show usage +usage() { + echo -e "Use ${BLUE}${SCRIPTNAME} --help${NC} for usage and help." +} + +# Check that this is run as root +check_sudo() { + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: This script must be run as root.${NC}" + usage + exit 1 + fi +} + +# Find the path of Shufflecake executable +find_sflc_path() { + local cmd="shufflecake" + + # Check if the command exists in the current directory + if [[ -x "./$cmd" ]]; then + SFLCPATH=$(realpath ./) + SFLCNAME=$(realpath ./$cmd) + return + fi + + # Check if the command exists in the parent directory + if [[ -x "../$cmd" ]]; then + SFLCPATH=$(realpath ../) + SFLCNAME=$(realpath ../$cmd) + return + fi + + # Check if the command exists in the directories listed in PATH + IFS=':' read -ra dirs <<< "$PATH" + for dir in "${dirs[@]}"; do + if [[ -x "$dir/$cmd" ]]; then + SFLCPATH=$(realpath $dir) + SFLCNAME=$(realpath $dir/$cmd) + return + fi + done + + # If the command was not found, print an error message + echo -e "${RED}ERROR: Command '$cmd' not found${NC}." >&2 + exit 1 +} + +# Find and load module dm-sflc +load_dmsflc() { + local mod="dm-sflc" + + # Check if the module is already loaded + if lsmod | grep -q "^$mod "; then + DMSFLC_INSTALLED=true + echo "Module '$mod' is already loaded." + return + fi + + # If not, look for the module file and try to load it + local mod_file="$mod.ko" + + # Check if the module file exists in the current directory + if [[ -f "./$mod_file" ]]; then + insmod $(realpath ./$mod_file) || exit 1 + echo "Module '$mod' loaded from current directory." + return + fi + + # Check if the module file exists in the parent directory + if [[ -f "../$mod_file" ]]; then + insmod $(realpath ../$mod_file) || exit 1 + echo "Module '$mod' loaded from parent directory." + return + fi + + # If the module file was not found, print an error message + echo -e "${RED} ERROR: Module file '$mod_file' not found.${NC}." >&2 + exit 1 +} + +# Unload dm-sflc if it was loaded locally +unload_dmsflc() { + local mod="dm-sflc" + # Only unload dm-sflc if it was loaded manually when the script was invoked + if ! $DMSFLC_INSTALLED; then + rmmod $mod || exit 1 + echo "Module '$mod' unloaded." + else + echo "Module '$mod' was not unloaded because it was already loaded when the script was run." + fi +} + +# Function to check if argument is a block device +check_block_device() { + if [ -b "$1" ]; then + echo "OK, block device path $1 is valid." >&2 + else + echo -e "${RED}Error: $1 is not a valid block device, aborting.${NC}" + unload_dmsflc + usage + exit 1 + fi +} + +# Function to create loop device +create_loop_device() { + echo "I will now try to create a file $LOOP_FILENAME ..." >&2 + if [ -e "$LOOP_FILENAME" ]; then + echo -e "${RED}Error: Impossible to generate file, $LOOP_FILENAME already exists.${NC}" + exit 1 + fi + sudo dd if=/dev/zero of="$LOOP_FILENAME" bs=1M count=1024 > /dev/null + echo "Writing of empty file complete. I will now try to attach it to a new loop device..." >&2 + LOOP_DEVICE=$(sudo losetup -f --show "$LOOP_FILENAME") + echo "Successfully created loop device $LOOP_DEVICE ." >&2 + echo "$LOOP_DEVICE" +} + +# Finds the sflc volume that was created last +find_sflcvolname() { + # List all files in /dev/mapper, select those starting with sflc_, sort them, and pick the last one. + volname=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -t '_' -k 2n,2 -k 3n,3 | tail -n 1) + # Check if volname is empty (no sflc_ files found). + if [ -z "$volname" ]; then + echo -e "${RED}ERROR: No sflc_ devices found in /dev/mapper !${NC}" + return 1 + else + echo $volname + return 0 + fi +} + +# Function for user confirmation +confirm() { + while true; do + echo -e "${BLUE}Are you sure you want to proceed? All data on disk $BLOCK_DEVICE will be erased. (y/n)${NC}" + read -r response + case "$response" in + [yY]|[yY][eE][sS]) # Responded Yes + return 0 # Return 0 for Yes (success, convention for bash scripting) + ;; + [nN]|[nN][oO]) # Responded No + return 1 # Return 1 for No (error, convention for bash scripting) + ;; + *) # Responded something else + echo "Please press only (y)es or (n)o." + ;; + esac + done +} + +# Benchmarks +benchmark() { + + SFLCVOLUME="" + MNTPOINT="" + TESTNAME="sflc" + RUNTIME="20" # running time in seconds FOR EACH TEST + DATASIZE="500M" + TESTFILENAME="testfile" + echo "Starting benchmark for Shufflecake Legacy..." + echo "Initializing block device $BLOCK_DEVICE with two Shufflecake Legacy volumes (--skip-randfill)..." + etime=$( (time echo -e "passwd1\npasswd2" | $SFLCNAME --skip-randfill --legacy -n 2 init $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action init took $etime seconds.${NC}" + echo "Shufflecake device initialized. Opening hidden volume (nr. 2)..." + # open + etime=$( (time echo -e "passwd2" | $SFLCNAME --legacy open $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action open took $etime seconds.${NC}" + # fetch SFLCVOLUME + SFLCVOLUME=$(find_sflcvolname) + echo "Shufflecake Legacy volume opened as $SFLCVOLUME. Formatting with ext4..." + # format with ext4, but mute output (too verbose) + mkfs.ext4 $SFLCVOLUME > /dev/null + echo "Volume $SFLCVOLUME formatted. Mounting that..." + # assign and create MNTPOINT + MNTPOINT=$(realpath "./sflc_mnt") + mkdir $MNTPOINT + # mount + mount $SFLCVOLUME $MNTPOINT + echo "Volume mounted at $MNTPOINT. Starting fio tests..." + # TESTS HERE + # test 01: random read + echo "Test 01: random read with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-r-rnd --ioengine=libaio --iodepth=32 --rw=randread --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') + echo -e "${GREEN}${OUTPUT}${NC}" + # test 02: random write + echo "Test 02: random write with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-w-rnd --ioengine=libaio --iodepth=32 --rw=randwrite --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') + echo -e "${GREEN}${OUTPUT}${NC}" + # test 03: sequential read + echo "Test 03: sequential read with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-r-seq --ioengine=libaio --iodepth=32 --rw=read --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, read_iops: .read.iops, read_bw: .read.bw}') + echo -e "${GREEN}${OUTPUT}${NC}" + # test 04: sequential write + echo "Test 04: sequential write with a queue of 32 4kiB blocks on a file (${RUNTIME}s)..." + OUTPUT=$(fio --name=$TESTNAME-w-seq --ioengine=libaio --iodepth=32 --rw=write --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') + echo -e "${GREEN}${OUTPUT}${NC}" + # END TESTS + echo "Shufflecake Legacy fio tests ended. Unmounting volume." + # unmount + umount $MNTPOINT + rmdir $MNTPOINT + echo "Volume unmounted. Closing Shufflecake device..." + # close + etime=$( (time $SFLCNAME close $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action close took $etime seconds.${NC}" + #end + +} + +# Clean up +cleanup() { + echo "Exiting and cleaning..." + if [[ -n $LOOP_DEVICE ]]; then + echo "Detaching $LOOP_DEVICE ..." + sudo losetup -d "$LOOP_DEVICE" + echo "Deleting $LOOP_FILENAME ..." + sudo rm -f "$LOOP_FILENAME" + echo "Loop device detached and backing file deleted." + fi +} + + +##################################################################### + +# MAIN SCRIPT BODY STARTS HERE + +##################################################################### + +# BANNER +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars +echo -e "${BLUE}===============================================================================${NC}" +echo -e "${BLUE} Benchmark Suite Script for Shufflecake Legacy${NC}" +echo -e "${BLUE}===============================================================================${NC}" + + +# PRELIMINARY: PARSE HELP, SUDO, AND LOAD SHUFFLECAKE (IF IT EXISTS, OTHERWISE ERROR) + +case "$1" in + # help + --help|-?) + print_help + exit 0 + ;; +esac + +check_sudo + +echo -e "${BLUE}Initializing Shufflecake...${NC}" +echo "Searching Shufflecake executable..." +find_sflc_path +echo "Shufflecake executable found at $SFLCNAME ." +echo "Searching and loading dm-sflc module..." +load_dmsflc +echo "Module dm-sflc found and loaded. Status DMSFLC_INSTALLED: $DMSFLC_INSTALLED ." +echo " " + + +# PARSER + +case "$1" in + "") +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + + echo "Now you will be asked to enter the path for a block device to be used for the " + echo "benchmarks (all content will be erased). If no path is provided (default" + echo "choice), then the script will create a 1 GiB file in the current directory and " + echo "use it to back a loop device instead, then the file will be removed at the end." + echo " " + echo -n "Please enter the path for a block device (default: none): " + read BLOCK_DEVICE + if [ -z "$BLOCK_DEVICE" ]; then + echo "No path provided, creating a local file and loop device..." + LOOP_DEVICE=$(create_loop_device) + BLOCK_DEVICE=$LOOP_DEVICE + fi + + ;; + + # argument passed + *) + BLOCK_DEVICE="$1" + ;; +esac + +check_block_device "$BLOCK_DEVICE" + +# MAIN PROGRAM + +if confirm; then + benchmark +else + echo "Aborting..." +fi + +unload_dmsflc + +cleanup + + + + diff --git a/benchmark-suite/sflc-legacy-fragmentation.sh b/benchmark-suite/sflc-legacy-fragmentation.sh new file mode 100755 index 0000000..daf57ed --- /dev/null +++ b/benchmark-suite/sflc-legacy-fragmentation.sh @@ -0,0 +1,457 @@ +#!/bin/bash + +# Copyright The Shufflecake Project Authors (2022) +# Copyright The Shufflecake Project Contributors (2022) +# Copyright Contributors to the The Shufflecake Project. + +# See the AUTHORS file at the top-level directory of this distribution and at +# + +# This file is part of the program shufflecake-c, which is part of the Shufflecake +# Project. Shufflecake is a plausible deniability (hidden storage) layer for +# Linux. See . + +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 2 of the License, or (at your option) +# any later version. This program 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 General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program. +# If not, see . + +# Fragmentation evaluation script for Shufflecake + +# Global variables +SCRIPTNAME=$(basename "$0") +SCRIPT_DIR="$(dirname "$(realpath "$0")")" +LOOP_FILENAME="$SCRIPT_DIR/sflc-legacy-frag-loop-file.img" +LOOP_DEVICE="" +TIMEFORMAT='%3R' +SFLCPATH="" +SFLCNAME="" +DMSFLC_INSTALLED=false +VOLSIZE=0 +NUMPOINTS=11 # number of samples, max 65536 +FSTYPE="ext4" # fragmentation performance can depend on FS type in theory, so change it here if you want to test others + + +# Colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No color + +# Help +print_help() { +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" + echo " " + echo "This script is used to evaluate Shufflecake Legacy volume fragmentation." + echo "This script is part of the Shufflecake benchmark suite." + echo "Shufflecake is a plausible deniability (hidden storage) layer for Linux." + echo -e "Please visit ${BLUE}https://www.shufflecake.net${NC} for more info and documentation." + echo " " + echo "This script requires root because it operates on block devices, please run it " + echo -e "with ${BLUE}sudo${NC}. It does the following:" + echo "1) Creates a Shufflecake Legacy device with two volumes." + echo "2) Opens the second (hidden) volume, formats it with $FSTYPE and mounts it." + echo "3) Performs incremental random write up to filling the space." + echo "4) After every write round, checks fragmentation status and reports it." + echo "5) Unmounts and closes the used volume." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo "You must have already compiled and installed Shufflecake in order to run this." + echo "The script will search for Shufflecake either in the default installation " + echo "directory, or in the current directory, or in the parent directory (one level" + echo -e "above this). If the module ${BLUE}dm-sflc${NC} is not loaded, the script " + echo "will load it, execute, and then unload it." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo "You can pass the path to a block device as an optional argument, otherwise the " + echo "script will ask for one. If no path is provided, the script will create a 1 GiB" + echo "local file and use it to back a loop device as a virtual block device to be " + echo "formatted with the appropriate tools. The file will be removed at the end." + echo " " +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + echo -e "${BLUE}WARNING: ALL CONTENT OF THE PROVIDED BLOCK DEVICE WILL BE ERASED!${NC}" + echo " " + exit 0 +} + +# Function for debugging +bpx() { + if [ -z "$1" ]; then + echo "BPX: Paused. Press any key to continue..." >&2 + else + echo -e "BPX: $1. Press any key to continue..." >&2 + fi + read -n1 -s +} + +# Show usage +usage() { + echo -e "Use ${BLUE}${SCRIPTNAME} --help${NC} for usage and help." +} + +# Check that this is run as root +check_sudo() { + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: This script must be run as root.${NC}" + usage + exit 1 + fi +} + +# Find the path of Shufflecake executable +find_sflc_path() { + local cmd="shufflecake" + + # Check if the command exists in the current directory + if [[ -x "./$cmd" ]]; then + SFLCPATH=$(realpath ./) + SFLCNAME=$(realpath ./$cmd) + return + fi + + # Check if the command exists in the parent directory + if [[ -x "../$cmd" ]]; then + SFLCPATH=$(realpath ../) + SFLCNAME=$(realpath ../$cmd) + return + fi + + # Check if the command exists in the directories listed in PATH + IFS=':' read -ra dirs <<< "$PATH" + for dir in "${dirs[@]}"; do + if [[ -x "$dir/$cmd" ]]; then + SFLCPATH=$(realpath $dir) + SFLCNAME=$(realpath $dir/$cmd) + return + fi + done + + # If the command was not found, print an error message + echo -e "${RED}ERROR: Command '$cmd' not found${NC}." >&2 + exit 1 +} + +# Find and load module dm-sflc +load_dmsflc() { + local mod="dm-sflc" + + # Check if the module is already loaded + if lsmod | grep -q "^$mod "; then + DMSFLC_INSTALLED=true + echo "Module '$mod' is already loaded." + return + fi + + # If not, look for the module file and try to load it + local mod_file="$mod.ko" + + # Check if the module file exists in the current directory + if [[ -f "./$mod_file" ]]; then + insmod $(realpath ./$mod_file) || exit 1 + echo "Module '$mod' loaded from current directory." + return + fi + + # Check if the module file exists in the parent directory + if [[ -f "../$mod_file" ]]; then + insmod $(realpath ../$mod_file) || exit 1 + echo "Module '$mod' loaded from parent directory." + return + fi + + # If the module file was not found, print an error message + echo -e "${RED} ERROR: Module file '$mod_file' not found.${NC}." >&2 + exit 1 +} + +# Unload dm-sflc if it was loaded locally +unload_dmsflc() { + local mod="dm-sflc" + # Only unload dm-sflc if it was loaded manually when the script was invoked + if ! $DMSFLC_INSTALLED; then + rmmod $mod || exit 1 + echo "Module '$mod' unloaded." + else + echo "Module '$mod' was not unloaded because it was already loaded when the script was run." + fi +} + +# Function to check if argument is a block device +check_block_device() { + if [ -b "$1" ]; then + echo "OK, block device path $1 is valid." >&2 + else + echo -e "${RED}Error: $1 is not a valid block device, aborting.${NC}" + unload_dmsflc + usage + exit 1 + fi +} + +# Function to create loop device +create_loop_device() { + echo "I will now try to create a file $LOOP_FILENAME ..." >&2 + if [ -e "$LOOP_FILENAME" ]; then + echo -e "${RED}Error: Impossible to generate file, $LOOP_FILENAME already exists.${NC}" + exit 1 + fi + sudo dd if=/dev/zero of="$LOOP_FILENAME" bs=1M count=1024 > /dev/null + echo "Writing of empty file complete. I will now try to attach it to a new loop device..." >&2 + LOOP_DEVICE=$(sudo losetup -f --show "$LOOP_FILENAME") + echo "Successfully created loop device $LOOP_DEVICE ." >&2 + echo "$LOOP_DEVICE" +} + +# Finds the sflc volume that was created last +find_sflcvolname() { + # List all files in /dev/mapper, select those starting with sflc_, sort them, and pick the last one. + volname=$(ls /dev/mapper/sflc_* 2>/dev/null | sort -t '_' -k 2n,2 -k 3n,3 | tail -n 1) + # Check if volname is empty (no sflc_ files found). + if [ -z "$volname" ]; then + echo -e "${RED}ERROR: No sflc_ devices found in /dev/mapper !${NC}" + return 1 + else + echo $volname + return 0 + fi +} + +# Function for user confirmation +confirm() { + while true; do + echo -e "${BLUE}Are you sure you want to proceed? All data on disk $BLOCK_DEVICE will be erased. (y/n)${NC}" + read -r response + case "$response" in + [yY]|[yY][eE][sS]) # Responded Yes + return 0 # Return 0 for Yes (success, convention for bash scripting) + ;; + [nN]|[nN][oO]) # Responded No + return 1 # Return 1 for No (error, convention for bash scripting) + ;; + *) # Responded something else + echo "Please press only (y)es or (n)o." + ;; + esac + done +} + +# Benchmarks +benchmark() { + + SFLCVOLUME="" + VOLNAME="" + MNTPOINT="" + NUMPOINTS=21 # number of graph points + VOLSIZE=$(blockdev --getsize64 "$BLOCK_DEVICE") # first approximation, in bytes + + echo "Starting fragmentation test for Shufflecake Legacy..." + # init + echo "Initializing block device $BLOCK_DEVICE with two Shufflecake Legacy volumes (--skip-randfill)..." + etime=$( (time echo -e "passwd1\npasswd2" | $SFLCNAME --skip-randfill --legacy -n 2 init $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action init took $etime seconds.${NC}" + echo "Shufflecake Legacy device initialized. Opening hidden volume (nr. 2)..." + # open + etime=$( (time echo -e "passwd2" | $SFLCNAME --legacy open $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action open took $etime seconds.${NC}" + # fetch SFLCVOLUME + SFLCVOLUME=$(find_sflcvolname) + # trim path of SFLCVOLUME + VOLNAME=${SFLCVOLUME##*/} + echo "Shufflecake volume opened as $VOLNAME. Formatting with $FSTYPE..." + # format with FSTYPE, but mute output (too verbose) + mkfs.$FSTYPE $SFLCVOLUME > /dev/null + echo "Volume $SFLCVOLUME formatted. Mounting that..." + # assign and create MNTPOINT + MNTPOINT=$(realpath "./sflc_mnt") + mkdir $MNTPOINT + # mount + mount $SFLCVOLUME $MNTPOINT + echo "Volume mounted at $MNTPOINT. Starting fragmentation test..." + # TESTS HERE + + # Fragmentation test + # Shufflecake allocates data on disk by lazily allocating "slices", and then writing data on those slices. + # If no suitable slice is available for writing data, a new one is allocated. + # The number of allocated slices for a volume is tracked into /sys/devices/sflc/${VOLNAME}/mapped_slices + # At this point the volume is already formatted as FSTYPE and mounted at MNTPOINT + # sanity check: NUMPOINTS must be at least 1 and at most 65536, otherwise exit 1 + if [ $NUMPOINTS -lt 1 ] || [ $NUMPOINTS -gt 65536 ]; then + echo -e "${RED}Error: NUMPOINTS must be between 1 and 65536. Aborting...${NC}" + exit 1 + fi + + # read number of total available slices (round down) from /sys/module/dm_sflc/bdevs/SFLCDEVICENAME/tot_slices + MOST_RECENT_DIR=$(ls -td /sys/module/dm_sflc/bdevs/*/ | head -n 1) # include final slash / + read -r TOTSLICES < ${MOST_RECENT_DIR}tot_slices + + # refine down block device max size according to available slices, to make sure there is no overflow + NEWUPPERBOUND=$((TOTSLICES * 1048576 )) # in bytes + if ((VOLSIZE > NEWUPPERBOUND)); then + VOLSIZE=$NEWUPPERBOUND + fi + + # manage case NUMPOINTS = 1 + read -r OCCSLICES < /sys/devices/sflc/${VOLNAME}/mapped_slices + X_COORD=.0 + Y_COORD=$(echo "scale=3; $OCCSLICES/$TOTSLICES" | bc) + + # Print occupation table for graph + echo -e "${GREEN}Occupation table: X = percentage of data written, Y = percentage of slices used.${NC}" + echo -e "${GREEN}---------------------------------${NC}" + + echo -e "${GREEN}$X_COORD $Y_COORD${NC}" + + if [ $NUMPOINTS -gt 1 ]; then + # point 1 is always at X=0 and point NUMPOINT is always at X=1 + DATASIZE=$(( $VOLSIZE / ($NUMPOINTS-1) / 1048576 )) # data increments in MB, round down + + # loop for i = 1 to NUMPOINTS + for (( i=1; i<$NUMPOINTS-1; i++ )); do + X_COORD=$(echo "scale=3; $i / ($NUMPOINTS-1) " | bc) + # RNDDIRNAME = random name with 16 characters (loop while it does not exist already) + while :; do + RNDDIRNAME=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) + [ ! -e "${MNTPOINT}/${RNDDIRNAME}" ] && break + done + # create random dir + mkdir "${MNTPOINT}/${RNDDIRNAME}" + + # RNDFILENAME= random name with 16 characters (loop while it does not exist already) + while :; do + RNDFILENAME=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) + [ ! -e "${MNTPOINT}/${RNDDIRNAME}/${RNDFILENAME}" ] && break + done + + # create random file + dd if=/dev/zero of=${MNTPOINT}/${RNDDIRNAME}/${RNDFILENAME} bs=1M count=$DATASIZE >/dev/null 2>&1 + sync + + # compute slice occupation threshold + read -r OCCSLICES < /sys/devices/sflc/${VOLNAME}/mapped_slices + Y_COORD=$(echo "scale=3; $OCCSLICES / $TOTSLICES" | bc) + # sanity check i.e. Y_COORD is between 0 and 1 + if (( $(echo "$Y_COORD < 0" | bc -l) )) || (( $(echo "$Y_COORD > 1" | bc -l) )); then + echo "Error: Y_COORD is not between 0 and 1" + exit 1 + fi + # print point coords + echo -e "${GREEN}$X_COORD $Y_COORD${NC}" + done + # manually print last point to avoid rounding error artifacts at the last write + echo -e "${GREEN}1.0 1.0${NC}" + + fi + # end table + echo -e "${GREEN}---------------------------------${NC}" + + # END TESTS + echo "Shufflecake Legacy fragmentation test ended. Unmounting volume." + # unmount + umount $MNTPOINT + rmdir $MNTPOINT + echo "Volume unmounted. Closing Shufflecake device..." + # close + etime=$( (time $SFLCNAME close $BLOCK_DEVICE > /dev/null) 2>&1 ) + echo -e "${GREEN}Action close took $etime seconds.${NC}" + #end + +} + +# Clean up +cleanup() { + echo "Exiting and cleaning..." + if [[ -n $LOOP_DEVICE ]]; then + echo "Detaching $LOOP_DEVICE ..." + sudo losetup -d "$LOOP_DEVICE" + echo "Deleting $LOOP_FILENAME ..." + sudo rm -f "$LOOP_FILENAME" + echo "Loop device detached and backing file deleted." + fi +} + + +##################################################################### + +# MAIN SCRIPT BODY STARTS HERE + +##################################################################### + +# BANNER +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars +echo -e "${BLUE}===============================================================================${NC}" +echo -e "${BLUE} Evaluation Script for Shufflecake Legacy Volume Fragmentation${NC}" +echo -e "${BLUE}===============================================================================${NC}" + + +# PRELIMINARY: PARSE HELP, SUDO, AND LOAD SHUFFLECAKE (IF IT EXISTS, OTHERWISE ERROR) + +case "$1" in + # help + --help|-?) + print_help + exit 0 + ;; +esac + +check_sudo + +echo -e "${BLUE}Initializing Shufflecake...${NC}" +echo "Searching Shufflecake executable..." +find_sflc_path +echo "Shufflecake executable found at $SFLCNAME ." +echo "Searching and loading dm-sflc module..." +load_dmsflc +echo "Module dm-sflc found and loaded. Status DMSFLC_INSTALLED: $DMSFLC_INSTALLED ." +echo " " + + +# PARSER + +case "$1" in + "") +# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars + + echo "Now you will be asked to enter the path for a block device to be used for the " + echo "evaluation (all content will be erased). If no path is provided (default" + echo "choice), then the script will create a 1 GiB file in the current directory and " + echo "use it to back a loop device instead, then the file will be removed at the end." + echo " " + echo -n "Please enter the path for a block device (default: none): " + read BLOCK_DEVICE + if [ -z "$BLOCK_DEVICE" ]; then + echo "No path provided, creating a local file and loop device..." + LOOP_DEVICE=$(create_loop_device) + BLOCK_DEVICE=$LOOP_DEVICE + fi + + ;; + + # argument passed + *) + BLOCK_DEVICE="$1" + ;; +esac + +check_block_device "$BLOCK_DEVICE" + +# MAIN PROGRAM + +if confirm; then + benchmark +else + echo "Aborting..." +fi + +unload_dmsflc + +cleanup + + + + diff --git a/benchmark-suite/sflc-benchmark.sh b/benchmark-suite/sflc-lite-benchmark.sh similarity index 95% rename from benchmark-suite/sflc-benchmark.sh rename to benchmark-suite/sflc-lite-benchmark.sh index d703e84..b53dbce 100755 --- a/benchmark-suite/sflc-benchmark.sh +++ b/benchmark-suite/sflc-lite-benchmark.sh @@ -26,7 +26,7 @@ # Variables SCRIPTNAME=$(basename "$0") SCRIPT_DIR="$(dirname "$(realpath "$0")")" -LOOP_FILENAME="$SCRIPT_DIR/sflc-benchmark-loop-file.img" +LOOP_FILENAME="$SCRIPT_DIR/sflc-lite-benchmark-loop-file.img" LOOP_DEVICE="" TIMEFORMAT='%3R' SFLCPATH="" @@ -44,15 +44,15 @@ print_help() { # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" echo " " - echo "This script is used to benchmark Shufflecake on this machine." + echo "This script is used to benchmark Shufflecake Lite on this machine." echo "This script is part of the Shufflecake benchmark suite." echo "Shufflecake is a plausible deniability (hidden storage) layer for Linux." echo -e "Please visit ${BLUE}https://www.shufflecake.net${NC} for more info and documentation." echo " " echo "This script requires root because it operates on block devices, please run it " echo -e "with ${BLUE}sudo${NC}. It does the following:" - echo "1) Creates a Shufflecake device with two volumes." - echo "2) Opens the second (hidden) one, formats it with ext4 and mounts it." + echo "1) Creates a Shufflecake (Lite) device with two volumes." + echo "2) Opens the second (hidden) volume, formats it with ext4 and mounts it." echo "3) Performs various fio r/w stress operations on it." echo "4) Unmounts and closes the used volume." echo " " @@ -245,16 +245,17 @@ benchmark() { RUNTIME="20" # running time in seconds FOR EACH TEST DATASIZE="500M" TESTFILENAME="testfile" - echo "Starting benchmark for Shufflecake..." - echo "Initializing block device $BLOCK_DEVICE with two Shufflecake volumes (--skip-randfill)..." + echo "Starting benchmark for Shufflecake Lite..." + echo "Initializing block device $BLOCK_DEVICE with two Shufflecake Lite volumes (--skip-randfill)..." etime=$( (time echo -e "passwd1\npasswd2" | $SFLCNAME --skip-randfill -n 2 init $BLOCK_DEVICE > /dev/null) 2>&1 ) echo -e "${GREEN}Action init took $etime seconds.${NC}" echo "Shufflecake device initialized. Opening hidden volume (nr. 2)..." + # open etime=$( (time echo -e "passwd2" | $SFLCNAME open $BLOCK_DEVICE > /dev/null) 2>&1 ) echo -e "${GREEN}Action open took $etime seconds.${NC}" # fetch SFLCVOLUME SFLCVOLUME=$(find_sflcvolname) - echo "Shufflecake volume opened as $SFLCVOLUME. Formatting with ext4..." + echo "Shufflecake Lite volume opened as $SFLCVOLUME. Formatting with ext4..." # format with ext4, but mute output (too verbose) mkfs.ext4 $SFLCVOLUME > /dev/null echo "Volume $SFLCVOLUME formatted. Mounting that..." @@ -282,7 +283,7 @@ benchmark() { OUTPUT=$(fio --name=$TESTNAME-w-seq --ioengine=libaio --iodepth=32 --rw=write --bs=4k --numjobs=1 --direct=1 --size=$DATASIZE --runtime=$RUNTIME --time_based --end_fsync=1 --filename=$MNTPOINT/$TESTFILENAME --output-format=json | jq '.jobs[] | {name: .jobname, write_iops: .write.iops, write_bw: .write.bw}') echo -e "${GREEN}${OUTPUT}${NC}" # END TESTS - echo "Shufflecake fio tests ended. Unmounting volume." + echo "Shufflecake Lite fio tests ended. Unmounting volume." # unmount umount $MNTPOINT rmdir $MNTPOINT @@ -316,7 +317,7 @@ cleanup() { # BANNER # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}===============================================================================${NC}" -echo -e "${BLUE} Benchmark Suite Script for Shufflecake${NC}" +echo -e "${BLUE} Benchmark Suite Script for Shufflecake Lite${NC}" echo -e "${BLUE}===============================================================================${NC}" diff --git a/benchmark-suite/sflc-fragmentation.sh b/benchmark-suite/sflc-lite-fragmentation.sh similarity index 96% rename from benchmark-suite/sflc-fragmentation.sh rename to benchmark-suite/sflc-lite-fragmentation.sh index dcd7c1d..2e72322 100755 --- a/benchmark-suite/sflc-fragmentation.sh +++ b/benchmark-suite/sflc-lite-fragmentation.sh @@ -26,7 +26,7 @@ # Global variables SCRIPTNAME=$(basename "$0") SCRIPT_DIR="$(dirname "$(realpath "$0")")" -LOOP_FILENAME="$SCRIPT_DIR/sflc-frag-loop-file.img" +LOOP_FILENAME="$SCRIPT_DIR/sflc-lite-frag-loop-file.img" LOOP_DEVICE="" TIMEFORMAT='%3R' SFLCPATH="" @@ -48,15 +48,15 @@ print_help() { # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}Usage: ${SCRIPTNAME} [OPTION]... [BLOCKDEVICE]${NC}" echo " " - echo "This script is used to evaluate Shufflecake volume fragmentation." + echo "This script is used to evaluate Shufflecake Lite volume fragmentation." echo "This script is part of the Shufflecake benchmark suite." echo "Shufflecake is a plausible deniability (hidden storage) layer for Linux." echo -e "Please visit ${BLUE}https://www.shufflecake.net${NC} for more info and documentation." echo " " echo "This script requires root because it operates on block devices, please run it " echo -e "with ${BLUE}sudo${NC}. It does the following:" - echo "1) Creates a Shufflecake device with two volumes." - echo "2) Opens the second (hidden) one, formats it with $FSTYPE and mounts it." + echo "1) Creates a Shufflecake Lite device with two volumes." + echo "2) Opens the second (hidden) volume, formats it with $FSTYPE and mounts it." echo "3) Performs incremental random write up to filling the space." echo "4) After every write round, checks fragmentation status and reports it." echo "5) Unmounts and closes the used volume." @@ -250,12 +250,12 @@ benchmark() { NUMPOINTS=21 # number of graph points VOLSIZE=$(blockdev --getsize64 "$BLOCK_DEVICE") # first approximation, in bytes - echo "Starting fragmentation test for Shufflecake..." + echo "Starting fragmentation test for Shufflecake Lite..." # init - echo "Initializing block device $BLOCK_DEVICE with two Shufflecake volumes (--skip-randfill)..." + echo "Initializing block device $BLOCK_DEVICE with two Shufflecake Lite volumes (--skip-randfill)..." etime=$( (time echo -e "passwd1\npasswd2" | $SFLCNAME --skip-randfill -n 2 init $BLOCK_DEVICE > /dev/null) 2>&1 ) echo -e "${GREEN}Action init took $etime seconds.${NC}" - echo "Shufflecake device initialized. Opening hidden volume (nr. 2)..." + echo "Shufflecake Lite device initialized. Opening hidden volume (nr. 2)..." # open etime=$( (time echo -e "passwd2" | $SFLCNAME open $BLOCK_DEVICE > /dev/null) 2>&1 ) echo -e "${GREEN}Action open took $etime seconds.${NC}" @@ -351,7 +351,7 @@ benchmark() { echo -e "${GREEN}---------------------------------${NC}" # END TESTS - echo "Shufflecake fragmentation test ended. Unmounting volume." + echo "Shufflecake Lite fragmentation test ended. Unmounting volume." # unmount umount $MNTPOINT rmdir $MNTPOINT @@ -385,7 +385,7 @@ cleanup() { # BANNER # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 79 chars echo -e "${BLUE}===============================================================================${NC}" -echo -e "${BLUE} Evaluation Script for Shufflecake Volume Fragmentation${NC}" +echo -e "${BLUE} Evaluation Script for Shufflecake Lite Volume Fragmentation${NC}" echo -e "${BLUE}===============================================================================${NC}" From 3506564e29b0c818167b5d1bdce981c130094548 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 15:40:00 +0200 Subject: [PATCH 85/98] doc:Add bug description to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e7e7dc3..722cabb 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,7 @@ Bugs and other issues are tracked at Date: Sat, 31 Aug 2024 17:35:01 +0200 Subject: [PATCH 86/98] doc:Minor edit to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 722cabb..fdc70dc 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Shufflecake can operate in two different modes, with different features in terms #### Shufflecake Lite -As of v0.5.0, Shufflecake's default mode of operation is the "Lite" scheme. This mode introduces the AES-XTS encryption mode rather than AES-CTR, with a related change of header format. This mode has only advantages compared to the old "Legacy" mode: it offers the _same_ level of security, but with better performance and data resilience, and it is therefore the strongly recommended way to use Shufflecake. It will be used by default whenever a Shufflecake command is invoked. +As of v0.5.0, Shufflecake's default mode of operation is the "Lite" scheme. This mode introduces the AES-XTS encryption mode rather than AES-CTR, with a related change of header format. This mode has only advantages compared to the old "Legacy" scheme: it offers the _same_ level of security, but with better performance and data resilience, and it is therefore the strongly recommended way to use Shufflecake. It will be used by default whenever a Shufflecake command is invoked. ![Scheme of Shufflecake Lite device layout](resources/images/headers_lite.png) From 41f3c44076fc701b01c265ecdd273f37fcc293d7 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sat, 31 Aug 2024 22:01:27 +0200 Subject: [PATCH 87/98] doc:Minor edit to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdc70dc..a8dd22d 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ Outstanding known bugs and/or limitations: - Only a maximum of 15 volumes per device is supported. - I/O performance of Shufflecake Lite is slowed down by a missing optimization of the flush method. - - There is (currently) no protection against multi-snapshot attacks (like in TrueCrypt/VeraCrypt). + - There is (currently) no protection against multi-snapshot attacks. From 06e388fc5bd0979e891963392b2720f1feef4467 Mon Sep 17 00:00:00 2001 From: toninov Date: Sat, 31 Aug 2024 22:57:47 +0000 Subject: [PATCH 88/98] Add argument to sflite_vmb_seal/unseal --- shufflecake-userland/src/operations/volume_header_lite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shufflecake-userland/src/operations/volume_header_lite.c b/shufflecake-userland/src/operations/volume_header_lite.c index ce649ab..af25c1b 100644 --- a/shufflecake-userland/src/operations/volume_header_lite.c +++ b/shufflecake-userland/src/operations/volume_header_lite.c @@ -61,7 +61,7 @@ int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, int err; // Encrypt VMB - err = sflite_vmb_seal(vmb, vmb_key, enc_vmb); + err = sflite_vmb_seal(vmb, vmb_key, enc_vmb, vol_idx); if (err) { sflc_log_error("Could not seal VMB; error %d", err); goto out; @@ -125,7 +125,7 @@ int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t } /* Unseal it */ - err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb); + err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb, vol_idx); if (err) { sflc_log_error("Could not unseal VMB; error %d", err); return err; From 537623150a3155c008c38837911b25d872ef3543 Mon Sep 17 00:00:00 2001 From: toninov Date: Sat, 31 Aug 2024 22:58:45 +0000 Subject: [PATCH 89/98] Add argument to sflite_vmb_seal/unseal in .h --- shufflecake-userland/include/header.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shufflecake-userland/include/header.h b/shufflecake-userland/include/header.h index 1062a21..bb8b898 100644 --- a/shufflecake-userland/include/header.h +++ b/shufflecake-userland/include/header.h @@ -166,9 +166,9 @@ int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LITE version */ -int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block, size_t vol_idx); /* "Decrypt" a VMB coming from the disk, directly using its key. LITE version */ -int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LEGACY version */ int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); From 3abcc633b8ab4067c44872c35e7752fd741ce978 Mon Sep 17 00:00:00 2001 From: toninov Date: Sat, 31 Aug 2024 23:15:13 +0000 Subject: [PATCH 90/98] revert 537623150a3155c008c38837911b25d872ef3543 revert Add argument to sflite_vmb_seal/unseal in .h --- shufflecake-userland/include/header.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shufflecake-userland/include/header.h b/shufflecake-userland/include/header.h index bb8b898..1062a21 100644 --- a/shufflecake-userland/include/header.h +++ b/shufflecake-userland/include/header.h @@ -166,9 +166,9 @@ int sflc_dmb_setCell(char *disk_block, sflc_DmbCell *dmb_cell, char *pwd, size_t /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LITE version */ -int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block, size_t vol_idx); +int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); /* "Decrypt" a VMB coming from the disk, directly using its key. LITE version */ -int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LEGACY version */ int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); From 44dfd2729b9341845f392f30f3390e1f0a93f878 Mon Sep 17 00:00:00 2001 From: toninov Date: Sat, 31 Aug 2024 23:15:43 +0000 Subject: [PATCH 91/98] revert 06e388fc5bd0979e891963392b2720f1feef4467 revert Add argument to sflite_vmb_seal/unseal --- shufflecake-userland/src/operations/volume_header_lite.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shufflecake-userland/src/operations/volume_header_lite.c b/shufflecake-userland/src/operations/volume_header_lite.c index af25c1b..ce649ab 100644 --- a/shufflecake-userland/src/operations/volume_header_lite.c +++ b/shufflecake-userland/src/operations/volume_header_lite.c @@ -61,7 +61,7 @@ int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, int err; // Encrypt VMB - err = sflite_vmb_seal(vmb, vmb_key, enc_vmb, vol_idx); + err = sflite_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { sflc_log_error("Could not seal VMB; error %d", err); goto out; @@ -125,7 +125,7 @@ int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t } /* Unseal it */ - err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb, vol_idx); + err = sflite_vmb_unseal(enc_vmb, vmb_key, vmb); if (err) { sflc_log_error("Could not unseal VMB; error %d", err); return err; From 5fdc05873cec4c5e00dd2230ecffe56de473846d Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 14:09:51 +0200 Subject: [PATCH 92/98] doc:Update README.md and CHANGELOG.md and CLI usage text --- CHANGELOG.md | 92 ++++++++++++++----------- README.md | 2 +- shufflecake-userland/src/cli/dispatch.c | 2 +- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4195369..78d9d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,39 +16,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.0] - 2024-09-01 -### Refactored +### Added + + - Benchmark and fragmentation scripts for Shufflecake Lite. + - Schematics for new Lite headers. + +### Changed - BREAKING CHANGE: introduction of "Shufflecake Lite" scheme as default. - BREAKING CHANGE: moved old Shufflescake scheme as "Legacy" option for backward support. + - Edited README.md to show the difference between the Lite and Legacy modes, and also added a usability warning. + +### Fixed + + - Volumes offered by the same device now show as all related to that device with `lsblk`. + +### Refactored + - Global refactoring and simplification of code tree. - -### Added - - - Benchmark script for Shufflecake Lite VS Legacy + - Reordered entries in CHANGELOG.md with sections following the order Added -> Changed -> Fixed -> Refactored -> Removed. ## [0.4.5] - 2024-06-03 +### Changed + + - Minor edits to README.md. + ### Fixed - Fixed a compile error on some new releases of LTS kernels due to the removal of a function API. - Fixed segmentation fault when opening an already opened device. -### Changed - - - Minor edits to README.md. - ## [0.4.4] - 2023-12-31 -### Fixed - - - Fixed a memory allocation error on large devices due to the use of a function not meant for allocating large amount of memory. - ### Changed - Variables `shuffled_psi_array` and `shuffled_psi_ctr` renamed to `prmslices` and `prmslices_octr` respectively, to be more consistent with reference paper. - Minor edits to README.md. +### Fixed + + - Fixed a memory allocation error on large devices due to the use of a function not meant for allocating large amount of memory. + ## [0.4.3] - 2023-11-29 @@ -60,6 +70,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.2] - 2023-11-28 +### Changed + + - All schematics and references now consistently map array indices of size N from 0 to N-1 rather than from 1 to N. + ### Fixed - Fixed persistent slice allocation ambiguity after a volume corruption by allocating fresh slices for the corrupted volume. This is done in order to help external recovery tools (e.g. RAID). @@ -67,10 +81,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a missed deallocation problem which caused a kernel bug on volume close after some I/O errors. - Fixed a buggy swap procedure which made the permutation of PSIs not completely random. -### Changed - - - All schematics and references now consistently map array indices of size N from 0 to N-1 rather than from 1 to N. - ## [0.4.1] - 2023-07-30 @@ -89,21 +99,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved documentation in `README.md` on using `init` non-interactively. - `doc` section which for now includes figure of Shufflecake header structure. -### Refactored - - - Implemented reference slice allocation algorithm with much faster performance. - -### Fixed - - - Fixed a bug that made `--skip-randfill` option not work. - - Fixed a bug that made action `close` not work. - ### Changed - BREAKING CHANGE: slightly modified header field format, removing redundant MAC field and making it adherent to documentation. - Action `init` now reads password from secure interface (not echoing characters, etc). - Updated instructions in `SECURITY.md`. +### Fixed + + - Fixed a bug that made `--skip-randfill` option not work. + - Fixed a bug that made action `close` not work. + +### Refactored + + - Implemented reference slice allocation algorithm with much faster performance. + ## [0.3.1] - 2023-07-15 @@ -120,17 +130,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.0] - 2023-07-11 -### Refactored - - - Unified repositories of kernel module and userland tool into a single one. Makefile, docs, etc unified accordingly. - - Adopting SemVer as of release v0.3.0. - - Program version now defined within `sflc_constants.h`. - - CLI built with argp.h and following GNU behavior. - -### Fixed - - - Test routines available with `make install`, for now limited to low-level crypto operations. - ### Changed - License changed from GPLv3+ to GPLv2+. @@ -139,6 +138,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - One global salt for deriving keys for all headers in order to not slow down too much opening. This allows us to avoid a difficult choice between insecurity against brute-force attacks and unacceptably slow opening time. - Added a 1-block offset to accommodate for header format change. +### Fixed + + - Test routines available with `make install`, for now limited to low-level crypto operations. + +### Refactored + + - Unified repositories of kernel module and userland tool into a single one. Makefile, docs, etc unified accordingly. + - Adopting SemVer as of release v0.3.0. + - Program version now defined within `sflc_constants.h`. + - CLI built with argp.h and following GNU behavior. + ## [0.2.0] - 2023-04-17 @@ -148,10 +158,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support larger volumes with headers of variable size. - Add interactive option to skip random filling during init. -### Fixed - -- Compile correctly on Linux kernel 6.1. - ### Changed - Switch from `libsodium` to `libgcrypt`. @@ -159,6 +165,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Change syntax of commands. - Switch to Scrypt KDF. +### Fixed + +- Compile correctly on Linux kernel 6.1. + ### Removed - Removed flag `--no-randfill`. diff --git a/README.md b/README.md index a8dd22d..beee2f9 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,7 @@ Please see the file `CHANGELOG.md` for a detailed history of changes. ### [0.5.0] - 2024-09-01 - - BREAKING CHANGE: major rewrite, introduced Shufflecake "Lite" as a default mode of operation. + - BREAKING CHANGE: major rewrite, bugfixes, introduced Shufflecake "Lite" as a default mode of operation. ### [0.4.5] - 2024-06-03 diff --git a/shufflecake-userland/src/cli/dispatch.c b/shufflecake-userland/src/cli/dispatch.c index 356e26d..f5e3e17 100644 --- a/shufflecake-userland/src/cli/dispatch.c +++ b/shufflecake-userland/src/cli/dispatch.c @@ -97,7 +97,7 @@ static char doc[] = "\topen:\t\tOpen a hierarchy of Shufflecake volumes within a device by\n" "\t\t\tasking a single user password. Virtual devices will appear\n" - "\t\t\tin /dev/mapper. Notice: freshly created device must be\n" + "\t\t\tin /dev/mapper. Notice: freshly created devices must be\n" "\t\t\tuser-formatted and mounted in order to be used.\n\n" "\tclose:\t\tClose all open Shufflecake volumes supported by given\n" From ea3411e3cbc1cc5a83978fbbce1db63f7877961d Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 17:16:17 +0200 Subject: [PATCH 93/98] test:Rename old to legacy --- dm-sflc/bin/Kbuild | 12 ++++++------ dm-sflc/bin/{old => legacy}/crypto/rand/rand.c | 0 dm-sflc/bin/{old => legacy}/crypto/rand/rand.h | 0 .../bin/{old => legacy}/crypto/symkey/skreq_pool.c | 0 .../bin/{old => legacy}/crypto/symkey/skreq_pool.h | 0 dm-sflc/bin/{old => legacy}/crypto/symkey/symkey.c | 0 dm-sflc/bin/{old => legacy}/crypto/symkey/symkey.h | 0 dm-sflc/bin/{old => legacy}/device/device.c | 0 dm-sflc/bin/{old => legacy}/device/device.h | 0 dm-sflc/bin/{old => legacy}/device/iv.c | 0 dm-sflc/bin/{old => legacy}/device/rawio.c | 0 dm-sflc/bin/{old => legacy}/device/rmap.c | 0 dm-sflc/bin/{old => legacy}/device/volumes.c | 0 dm-sflc/bin/{old => legacy}/log/log.h | 0 dm-sflc/bin/{old => legacy}/sflc_old.c | 0 dm-sflc/bin/{old => legacy}/sflc_old.h | 0 dm-sflc/bin/{old => legacy}/sysfs.c | 0 dm-sflc/bin/{old => legacy}/target.c | 0 dm-sflc/bin/{old => legacy}/utils/bio.c | 0 dm-sflc/bin/{old => legacy}/utils/bio.h | 0 dm-sflc/bin/{old => legacy}/utils/pools.c | 0 dm-sflc/bin/{old => legacy}/utils/pools.h | 0 dm-sflc/bin/{old => legacy}/utils/string.c | 0 dm-sflc/bin/{old => legacy}/utils/string.h | 0 dm-sflc/bin/{old => legacy}/utils/vector.c | 0 dm-sflc/bin/{old => legacy}/utils/vector.h | 0 dm-sflc/bin/{old => legacy}/utils/workqueues.c | 0 dm-sflc/bin/{old => legacy}/utils/workqueues.h | 0 dm-sflc/bin/{old => legacy}/volume/fmap.c | 0 dm-sflc/bin/{old => legacy}/volume/io.c | 0 dm-sflc/bin/{old => legacy}/volume/read.c | 0 dm-sflc/bin/{old => legacy}/volume/volume.c | 0 dm-sflc/bin/{old => legacy}/volume/volume.h | 0 dm-sflc/bin/{old => legacy}/volume/write.c | 0 dm-sflc/src/{old => legacy}/crypto/rand/rand.c | 0 dm-sflc/src/{old => legacy}/crypto/rand/rand.h | 0 .../src/{old => legacy}/crypto/symkey/skreq_pool.c | 0 .../src/{old => legacy}/crypto/symkey/skreq_pool.h | 0 dm-sflc/src/{old => legacy}/crypto/symkey/symkey.c | 0 dm-sflc/src/{old => legacy}/crypto/symkey/symkey.h | 0 dm-sflc/src/{old => legacy}/device/device.c | 0 dm-sflc/src/{old => legacy}/device/device.h | 0 dm-sflc/src/{old => legacy}/device/iv.c | 0 dm-sflc/src/{old => legacy}/device/rawio.c | 0 dm-sflc/src/{old => legacy}/device/rmap.c | 0 dm-sflc/src/{old => legacy}/device/volumes.c | 0 dm-sflc/src/{old => legacy}/log/log.h | 0 dm-sflc/src/{old/sflc_old.c => legacy/sflc_legacy.c} | 0 dm-sflc/src/{old/sflc_old.h => legacy/sflc_legacy.h} | 0 dm-sflc/src/{old => legacy}/sysfs.c | 0 dm-sflc/src/{old => legacy}/target.c | 0 dm-sflc/src/{old => legacy}/utils/bio.c | 0 dm-sflc/src/{old => legacy}/utils/bio.h | 0 dm-sflc/src/{old => legacy}/utils/pools.c | 0 dm-sflc/src/{old => legacy}/utils/pools.h | 0 dm-sflc/src/{old => legacy}/utils/string.c | 0 dm-sflc/src/{old => legacy}/utils/string.h | 0 dm-sflc/src/{old => legacy}/utils/vector.c | 0 dm-sflc/src/{old => legacy}/utils/vector.h | 0 dm-sflc/src/{old => legacy}/utils/workqueues.c | 0 dm-sflc/src/{old => legacy}/utils/workqueues.h | 0 dm-sflc/src/{old => legacy}/volume/fmap.c | 0 dm-sflc/src/{old => legacy}/volume/io.c | 0 dm-sflc/src/{old => legacy}/volume/read.c | 0 dm-sflc/src/{old => legacy}/volume/volume.c | 0 dm-sflc/src/{old => legacy}/volume/volume.h | 0 dm-sflc/src/{old => legacy}/volume/write.c | 0 67 files changed, 6 insertions(+), 6 deletions(-) rename dm-sflc/bin/{old => legacy}/crypto/rand/rand.c (100%) rename dm-sflc/bin/{old => legacy}/crypto/rand/rand.h (100%) rename dm-sflc/bin/{old => legacy}/crypto/symkey/skreq_pool.c (100%) rename dm-sflc/bin/{old => legacy}/crypto/symkey/skreq_pool.h (100%) rename dm-sflc/bin/{old => legacy}/crypto/symkey/symkey.c (100%) rename dm-sflc/bin/{old => legacy}/crypto/symkey/symkey.h (100%) rename dm-sflc/bin/{old => legacy}/device/device.c (100%) rename dm-sflc/bin/{old => legacy}/device/device.h (100%) rename dm-sflc/bin/{old => legacy}/device/iv.c (100%) rename dm-sflc/bin/{old => legacy}/device/rawio.c (100%) rename dm-sflc/bin/{old => legacy}/device/rmap.c (100%) rename dm-sflc/bin/{old => legacy}/device/volumes.c (100%) rename dm-sflc/bin/{old => legacy}/log/log.h (100%) rename dm-sflc/bin/{old => legacy}/sflc_old.c (100%) rename dm-sflc/bin/{old => legacy}/sflc_old.h (100%) rename dm-sflc/bin/{old => legacy}/sysfs.c (100%) rename dm-sflc/bin/{old => legacy}/target.c (100%) rename dm-sflc/bin/{old => legacy}/utils/bio.c (100%) rename dm-sflc/bin/{old => legacy}/utils/bio.h (100%) rename dm-sflc/bin/{old => legacy}/utils/pools.c (100%) rename dm-sflc/bin/{old => legacy}/utils/pools.h (100%) rename dm-sflc/bin/{old => legacy}/utils/string.c (100%) rename dm-sflc/bin/{old => legacy}/utils/string.h (100%) rename dm-sflc/bin/{old => legacy}/utils/vector.c (100%) rename dm-sflc/bin/{old => legacy}/utils/vector.h (100%) rename dm-sflc/bin/{old => legacy}/utils/workqueues.c (100%) rename dm-sflc/bin/{old => legacy}/utils/workqueues.h (100%) rename dm-sflc/bin/{old => legacy}/volume/fmap.c (100%) rename dm-sflc/bin/{old => legacy}/volume/io.c (100%) rename dm-sflc/bin/{old => legacy}/volume/read.c (100%) rename dm-sflc/bin/{old => legacy}/volume/volume.c (100%) rename dm-sflc/bin/{old => legacy}/volume/volume.h (100%) rename dm-sflc/bin/{old => legacy}/volume/write.c (100%) rename dm-sflc/src/{old => legacy}/crypto/rand/rand.c (100%) rename dm-sflc/src/{old => legacy}/crypto/rand/rand.h (100%) rename dm-sflc/src/{old => legacy}/crypto/symkey/skreq_pool.c (100%) rename dm-sflc/src/{old => legacy}/crypto/symkey/skreq_pool.h (100%) rename dm-sflc/src/{old => legacy}/crypto/symkey/symkey.c (100%) rename dm-sflc/src/{old => legacy}/crypto/symkey/symkey.h (100%) rename dm-sflc/src/{old => legacy}/device/device.c (100%) rename dm-sflc/src/{old => legacy}/device/device.h (100%) rename dm-sflc/src/{old => legacy}/device/iv.c (100%) rename dm-sflc/src/{old => legacy}/device/rawio.c (100%) rename dm-sflc/src/{old => legacy}/device/rmap.c (100%) rename dm-sflc/src/{old => legacy}/device/volumes.c (100%) rename dm-sflc/src/{old => legacy}/log/log.h (100%) rename dm-sflc/src/{old/sflc_old.c => legacy/sflc_legacy.c} (100%) rename dm-sflc/src/{old/sflc_old.h => legacy/sflc_legacy.h} (100%) rename dm-sflc/src/{old => legacy}/sysfs.c (100%) rename dm-sflc/src/{old => legacy}/target.c (100%) rename dm-sflc/src/{old => legacy}/utils/bio.c (100%) rename dm-sflc/src/{old => legacy}/utils/bio.h (100%) rename dm-sflc/src/{old => legacy}/utils/pools.c (100%) rename dm-sflc/src/{old => legacy}/utils/pools.h (100%) rename dm-sflc/src/{old => legacy}/utils/string.c (100%) rename dm-sflc/src/{old => legacy}/utils/string.h (100%) rename dm-sflc/src/{old => legacy}/utils/vector.c (100%) rename dm-sflc/src/{old => legacy}/utils/vector.h (100%) rename dm-sflc/src/{old => legacy}/utils/workqueues.c (100%) rename dm-sflc/src/{old => legacy}/utils/workqueues.h (100%) rename dm-sflc/src/{old => legacy}/volume/fmap.c (100%) rename dm-sflc/src/{old => legacy}/volume/io.c (100%) rename dm-sflc/src/{old => legacy}/volume/read.c (100%) rename dm-sflc/src/{old => legacy}/volume/volume.c (100%) rename dm-sflc/src/{old => legacy}/volume/volume.h (100%) rename dm-sflc/src/{old => legacy}/volume/write.c (100%) diff --git a/dm-sflc/bin/Kbuild b/dm-sflc/bin/Kbuild index 5e5aa11..a2e7f25 100644 --- a/dm-sflc/bin/Kbuild +++ b/dm-sflc/bin/Kbuild @@ -27,12 +27,12 @@ obj-m := $(MODULE_NAME).o OBJ_LIST := sflc.o dev_vol.o sysfs.o -OBJ_LIST += old/sflc_old.o old/target.o old/sysfs.o -OBJ_LIST += old/device/device.o old/device/volumes.o old/device/rawio.o old/device/rmap.o old/device/iv.o -OBJ_LIST += old/volume/volume.o old/volume/io.o old/volume/read.o old/volume/write.o old/volume/fmap.o -OBJ_LIST += old/utils/string.o old/utils/bio.o old/utils/pools.o old/utils/workqueues.o old/utils/vector.o -OBJ_LIST += old/crypto/rand/rand.o -OBJ_LIST += old/crypto/symkey/symkey.o old/crypto/symkey/skreq_pool.o +OBJ_LIST += legacy/sflc_legacy.o legacy/target.o legacy/sysfs.o +OBJ_LIST += legacy/device/device.o legacy/device/volumes.o legacy/device/rawio.o legacy/device/rmap.o legacy/device/iv.o +OBJ_LIST += legacy/volume/volume.o legacy/volume/io.o legacy/volume/read.o legacy/volume/write.o legacy/volume/fmap.o +OBJ_LIST += legacy/utils/string.o legacy/utils/bio.o legacy/utils/pools.o legacy/utils/workqueues.o legacy/utils/vector.o +OBJ_LIST += legacy/crypto/rand/rand.o +OBJ_LIST += legacy/crypto/symkey/symkey.o legacy/crypto/symkey/skreq_pool.o OBJ_LIST += lite/sflc_lite.o lite/sysfs.o OBJ_LIST += lite/device.o lite/volume.o diff --git a/dm-sflc/bin/old/crypto/rand/rand.c b/dm-sflc/bin/legacy/crypto/rand/rand.c similarity index 100% rename from dm-sflc/bin/old/crypto/rand/rand.c rename to dm-sflc/bin/legacy/crypto/rand/rand.c diff --git a/dm-sflc/bin/old/crypto/rand/rand.h b/dm-sflc/bin/legacy/crypto/rand/rand.h similarity index 100% rename from dm-sflc/bin/old/crypto/rand/rand.h rename to dm-sflc/bin/legacy/crypto/rand/rand.h diff --git a/dm-sflc/bin/old/crypto/symkey/skreq_pool.c b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c similarity index 100% rename from dm-sflc/bin/old/crypto/symkey/skreq_pool.c rename to dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c diff --git a/dm-sflc/bin/old/crypto/symkey/skreq_pool.h b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h similarity index 100% rename from dm-sflc/bin/old/crypto/symkey/skreq_pool.h rename to dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h diff --git a/dm-sflc/bin/old/crypto/symkey/symkey.c b/dm-sflc/bin/legacy/crypto/symkey/symkey.c similarity index 100% rename from dm-sflc/bin/old/crypto/symkey/symkey.c rename to dm-sflc/bin/legacy/crypto/symkey/symkey.c diff --git a/dm-sflc/bin/old/crypto/symkey/symkey.h b/dm-sflc/bin/legacy/crypto/symkey/symkey.h similarity index 100% rename from dm-sflc/bin/old/crypto/symkey/symkey.h rename to dm-sflc/bin/legacy/crypto/symkey/symkey.h diff --git a/dm-sflc/bin/old/device/device.c b/dm-sflc/bin/legacy/device/device.c similarity index 100% rename from dm-sflc/bin/old/device/device.c rename to dm-sflc/bin/legacy/device/device.c diff --git a/dm-sflc/bin/old/device/device.h b/dm-sflc/bin/legacy/device/device.h similarity index 100% rename from dm-sflc/bin/old/device/device.h rename to dm-sflc/bin/legacy/device/device.h diff --git a/dm-sflc/bin/old/device/iv.c b/dm-sflc/bin/legacy/device/iv.c similarity index 100% rename from dm-sflc/bin/old/device/iv.c rename to dm-sflc/bin/legacy/device/iv.c diff --git a/dm-sflc/bin/old/device/rawio.c b/dm-sflc/bin/legacy/device/rawio.c similarity index 100% rename from dm-sflc/bin/old/device/rawio.c rename to dm-sflc/bin/legacy/device/rawio.c diff --git a/dm-sflc/bin/old/device/rmap.c b/dm-sflc/bin/legacy/device/rmap.c similarity index 100% rename from dm-sflc/bin/old/device/rmap.c rename to dm-sflc/bin/legacy/device/rmap.c diff --git a/dm-sflc/bin/old/device/volumes.c b/dm-sflc/bin/legacy/device/volumes.c similarity index 100% rename from dm-sflc/bin/old/device/volumes.c rename to dm-sflc/bin/legacy/device/volumes.c diff --git a/dm-sflc/bin/old/log/log.h b/dm-sflc/bin/legacy/log/log.h similarity index 100% rename from dm-sflc/bin/old/log/log.h rename to dm-sflc/bin/legacy/log/log.h diff --git a/dm-sflc/bin/old/sflc_old.c b/dm-sflc/bin/legacy/sflc_old.c similarity index 100% rename from dm-sflc/bin/old/sflc_old.c rename to dm-sflc/bin/legacy/sflc_old.c diff --git a/dm-sflc/bin/old/sflc_old.h b/dm-sflc/bin/legacy/sflc_old.h similarity index 100% rename from dm-sflc/bin/old/sflc_old.h rename to dm-sflc/bin/legacy/sflc_old.h diff --git a/dm-sflc/bin/old/sysfs.c b/dm-sflc/bin/legacy/sysfs.c similarity index 100% rename from dm-sflc/bin/old/sysfs.c rename to dm-sflc/bin/legacy/sysfs.c diff --git a/dm-sflc/bin/old/target.c b/dm-sflc/bin/legacy/target.c similarity index 100% rename from dm-sflc/bin/old/target.c rename to dm-sflc/bin/legacy/target.c diff --git a/dm-sflc/bin/old/utils/bio.c b/dm-sflc/bin/legacy/utils/bio.c similarity index 100% rename from dm-sflc/bin/old/utils/bio.c rename to dm-sflc/bin/legacy/utils/bio.c diff --git a/dm-sflc/bin/old/utils/bio.h b/dm-sflc/bin/legacy/utils/bio.h similarity index 100% rename from dm-sflc/bin/old/utils/bio.h rename to dm-sflc/bin/legacy/utils/bio.h diff --git a/dm-sflc/bin/old/utils/pools.c b/dm-sflc/bin/legacy/utils/pools.c similarity index 100% rename from dm-sflc/bin/old/utils/pools.c rename to dm-sflc/bin/legacy/utils/pools.c diff --git a/dm-sflc/bin/old/utils/pools.h b/dm-sflc/bin/legacy/utils/pools.h similarity index 100% rename from dm-sflc/bin/old/utils/pools.h rename to dm-sflc/bin/legacy/utils/pools.h diff --git a/dm-sflc/bin/old/utils/string.c b/dm-sflc/bin/legacy/utils/string.c similarity index 100% rename from dm-sflc/bin/old/utils/string.c rename to dm-sflc/bin/legacy/utils/string.c diff --git a/dm-sflc/bin/old/utils/string.h b/dm-sflc/bin/legacy/utils/string.h similarity index 100% rename from dm-sflc/bin/old/utils/string.h rename to dm-sflc/bin/legacy/utils/string.h diff --git a/dm-sflc/bin/old/utils/vector.c b/dm-sflc/bin/legacy/utils/vector.c similarity index 100% rename from dm-sflc/bin/old/utils/vector.c rename to dm-sflc/bin/legacy/utils/vector.c diff --git a/dm-sflc/bin/old/utils/vector.h b/dm-sflc/bin/legacy/utils/vector.h similarity index 100% rename from dm-sflc/bin/old/utils/vector.h rename to dm-sflc/bin/legacy/utils/vector.h diff --git a/dm-sflc/bin/old/utils/workqueues.c b/dm-sflc/bin/legacy/utils/workqueues.c similarity index 100% rename from dm-sflc/bin/old/utils/workqueues.c rename to dm-sflc/bin/legacy/utils/workqueues.c diff --git a/dm-sflc/bin/old/utils/workqueues.h b/dm-sflc/bin/legacy/utils/workqueues.h similarity index 100% rename from dm-sflc/bin/old/utils/workqueues.h rename to dm-sflc/bin/legacy/utils/workqueues.h diff --git a/dm-sflc/bin/old/volume/fmap.c b/dm-sflc/bin/legacy/volume/fmap.c similarity index 100% rename from dm-sflc/bin/old/volume/fmap.c rename to dm-sflc/bin/legacy/volume/fmap.c diff --git a/dm-sflc/bin/old/volume/io.c b/dm-sflc/bin/legacy/volume/io.c similarity index 100% rename from dm-sflc/bin/old/volume/io.c rename to dm-sflc/bin/legacy/volume/io.c diff --git a/dm-sflc/bin/old/volume/read.c b/dm-sflc/bin/legacy/volume/read.c similarity index 100% rename from dm-sflc/bin/old/volume/read.c rename to dm-sflc/bin/legacy/volume/read.c diff --git a/dm-sflc/bin/old/volume/volume.c b/dm-sflc/bin/legacy/volume/volume.c similarity index 100% rename from dm-sflc/bin/old/volume/volume.c rename to dm-sflc/bin/legacy/volume/volume.c diff --git a/dm-sflc/bin/old/volume/volume.h b/dm-sflc/bin/legacy/volume/volume.h similarity index 100% rename from dm-sflc/bin/old/volume/volume.h rename to dm-sflc/bin/legacy/volume/volume.h diff --git a/dm-sflc/bin/old/volume/write.c b/dm-sflc/bin/legacy/volume/write.c similarity index 100% rename from dm-sflc/bin/old/volume/write.c rename to dm-sflc/bin/legacy/volume/write.c diff --git a/dm-sflc/src/old/crypto/rand/rand.c b/dm-sflc/src/legacy/crypto/rand/rand.c similarity index 100% rename from dm-sflc/src/old/crypto/rand/rand.c rename to dm-sflc/src/legacy/crypto/rand/rand.c diff --git a/dm-sflc/src/old/crypto/rand/rand.h b/dm-sflc/src/legacy/crypto/rand/rand.h similarity index 100% rename from dm-sflc/src/old/crypto/rand/rand.h rename to dm-sflc/src/legacy/crypto/rand/rand.h diff --git a/dm-sflc/src/old/crypto/symkey/skreq_pool.c b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.c similarity index 100% rename from dm-sflc/src/old/crypto/symkey/skreq_pool.c rename to dm-sflc/src/legacy/crypto/symkey/skreq_pool.c diff --git a/dm-sflc/src/old/crypto/symkey/skreq_pool.h b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.h similarity index 100% rename from dm-sflc/src/old/crypto/symkey/skreq_pool.h rename to dm-sflc/src/legacy/crypto/symkey/skreq_pool.h diff --git a/dm-sflc/src/old/crypto/symkey/symkey.c b/dm-sflc/src/legacy/crypto/symkey/symkey.c similarity index 100% rename from dm-sflc/src/old/crypto/symkey/symkey.c rename to dm-sflc/src/legacy/crypto/symkey/symkey.c diff --git a/dm-sflc/src/old/crypto/symkey/symkey.h b/dm-sflc/src/legacy/crypto/symkey/symkey.h similarity index 100% rename from dm-sflc/src/old/crypto/symkey/symkey.h rename to dm-sflc/src/legacy/crypto/symkey/symkey.h diff --git a/dm-sflc/src/old/device/device.c b/dm-sflc/src/legacy/device/device.c similarity index 100% rename from dm-sflc/src/old/device/device.c rename to dm-sflc/src/legacy/device/device.c diff --git a/dm-sflc/src/old/device/device.h b/dm-sflc/src/legacy/device/device.h similarity index 100% rename from dm-sflc/src/old/device/device.h rename to dm-sflc/src/legacy/device/device.h diff --git a/dm-sflc/src/old/device/iv.c b/dm-sflc/src/legacy/device/iv.c similarity index 100% rename from dm-sflc/src/old/device/iv.c rename to dm-sflc/src/legacy/device/iv.c diff --git a/dm-sflc/src/old/device/rawio.c b/dm-sflc/src/legacy/device/rawio.c similarity index 100% rename from dm-sflc/src/old/device/rawio.c rename to dm-sflc/src/legacy/device/rawio.c diff --git a/dm-sflc/src/old/device/rmap.c b/dm-sflc/src/legacy/device/rmap.c similarity index 100% rename from dm-sflc/src/old/device/rmap.c rename to dm-sflc/src/legacy/device/rmap.c diff --git a/dm-sflc/src/old/device/volumes.c b/dm-sflc/src/legacy/device/volumes.c similarity index 100% rename from dm-sflc/src/old/device/volumes.c rename to dm-sflc/src/legacy/device/volumes.c diff --git a/dm-sflc/src/old/log/log.h b/dm-sflc/src/legacy/log/log.h similarity index 100% rename from dm-sflc/src/old/log/log.h rename to dm-sflc/src/legacy/log/log.h diff --git a/dm-sflc/src/old/sflc_old.c b/dm-sflc/src/legacy/sflc_legacy.c similarity index 100% rename from dm-sflc/src/old/sflc_old.c rename to dm-sflc/src/legacy/sflc_legacy.c diff --git a/dm-sflc/src/old/sflc_old.h b/dm-sflc/src/legacy/sflc_legacy.h similarity index 100% rename from dm-sflc/src/old/sflc_old.h rename to dm-sflc/src/legacy/sflc_legacy.h diff --git a/dm-sflc/src/old/sysfs.c b/dm-sflc/src/legacy/sysfs.c similarity index 100% rename from dm-sflc/src/old/sysfs.c rename to dm-sflc/src/legacy/sysfs.c diff --git a/dm-sflc/src/old/target.c b/dm-sflc/src/legacy/target.c similarity index 100% rename from dm-sflc/src/old/target.c rename to dm-sflc/src/legacy/target.c diff --git a/dm-sflc/src/old/utils/bio.c b/dm-sflc/src/legacy/utils/bio.c similarity index 100% rename from dm-sflc/src/old/utils/bio.c rename to dm-sflc/src/legacy/utils/bio.c diff --git a/dm-sflc/src/old/utils/bio.h b/dm-sflc/src/legacy/utils/bio.h similarity index 100% rename from dm-sflc/src/old/utils/bio.h rename to dm-sflc/src/legacy/utils/bio.h diff --git a/dm-sflc/src/old/utils/pools.c b/dm-sflc/src/legacy/utils/pools.c similarity index 100% rename from dm-sflc/src/old/utils/pools.c rename to dm-sflc/src/legacy/utils/pools.c diff --git a/dm-sflc/src/old/utils/pools.h b/dm-sflc/src/legacy/utils/pools.h similarity index 100% rename from dm-sflc/src/old/utils/pools.h rename to dm-sflc/src/legacy/utils/pools.h diff --git a/dm-sflc/src/old/utils/string.c b/dm-sflc/src/legacy/utils/string.c similarity index 100% rename from dm-sflc/src/old/utils/string.c rename to dm-sflc/src/legacy/utils/string.c diff --git a/dm-sflc/src/old/utils/string.h b/dm-sflc/src/legacy/utils/string.h similarity index 100% rename from dm-sflc/src/old/utils/string.h rename to dm-sflc/src/legacy/utils/string.h diff --git a/dm-sflc/src/old/utils/vector.c b/dm-sflc/src/legacy/utils/vector.c similarity index 100% rename from dm-sflc/src/old/utils/vector.c rename to dm-sflc/src/legacy/utils/vector.c diff --git a/dm-sflc/src/old/utils/vector.h b/dm-sflc/src/legacy/utils/vector.h similarity index 100% rename from dm-sflc/src/old/utils/vector.h rename to dm-sflc/src/legacy/utils/vector.h diff --git a/dm-sflc/src/old/utils/workqueues.c b/dm-sflc/src/legacy/utils/workqueues.c similarity index 100% rename from dm-sflc/src/old/utils/workqueues.c rename to dm-sflc/src/legacy/utils/workqueues.c diff --git a/dm-sflc/src/old/utils/workqueues.h b/dm-sflc/src/legacy/utils/workqueues.h similarity index 100% rename from dm-sflc/src/old/utils/workqueues.h rename to dm-sflc/src/legacy/utils/workqueues.h diff --git a/dm-sflc/src/old/volume/fmap.c b/dm-sflc/src/legacy/volume/fmap.c similarity index 100% rename from dm-sflc/src/old/volume/fmap.c rename to dm-sflc/src/legacy/volume/fmap.c diff --git a/dm-sflc/src/old/volume/io.c b/dm-sflc/src/legacy/volume/io.c similarity index 100% rename from dm-sflc/src/old/volume/io.c rename to dm-sflc/src/legacy/volume/io.c diff --git a/dm-sflc/src/old/volume/read.c b/dm-sflc/src/legacy/volume/read.c similarity index 100% rename from dm-sflc/src/old/volume/read.c rename to dm-sflc/src/legacy/volume/read.c diff --git a/dm-sflc/src/old/volume/volume.c b/dm-sflc/src/legacy/volume/volume.c similarity index 100% rename from dm-sflc/src/old/volume/volume.c rename to dm-sflc/src/legacy/volume/volume.c diff --git a/dm-sflc/src/old/volume/volume.h b/dm-sflc/src/legacy/volume/volume.h similarity index 100% rename from dm-sflc/src/old/volume/volume.h rename to dm-sflc/src/legacy/volume/volume.h diff --git a/dm-sflc/src/old/volume/write.c b/dm-sflc/src/legacy/volume/write.c similarity index 100% rename from dm-sflc/src/old/volume/write.c rename to dm-sflc/src/legacy/volume/write.c From c887455fdc236e6abcd4603c3883c9e1d14db4cb Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 17:16:57 +0200 Subject: [PATCH 94/98] test:Rename old to legacy --- dm-sflc/.Kbuild | 12 +-- dm-sflc/src/dev_vol.c | 20 ++-- dm-sflc/src/legacy/crypto/rand/rand.c | 54 +++++------ dm-sflc/src/legacy/crypto/rand/rand.h | 14 +-- dm-sflc/src/legacy/crypto/symkey/skreq_pool.c | 18 ++-- dm-sflc/src/legacy/crypto/symkey/skreq_pool.h | 10 +- dm-sflc/src/legacy/crypto/symkey/symkey.c | 50 +++++----- dm-sflc/src/legacy/crypto/symkey/symkey.h | 26 ++--- dm-sflc/src/legacy/device/device.c | 44 ++++----- dm-sflc/src/legacy/device/device.h | 58 ++++++------ dm-sflc/src/legacy/device/iv.c | 66 ++++++------- dm-sflc/src/legacy/device/rawio.c | 14 +-- dm-sflc/src/legacy/device/rmap.c | 14 +-- dm-sflc/src/legacy/device/volumes.c | 8 +- dm-sflc/src/legacy/log/log.h | 6 +- dm-sflc/src/legacy/sflc_legacy.c | 32 +++---- dm-sflc/src/legacy/sflc_legacy.h | 28 +++--- dm-sflc/src/legacy/sysfs.c | 44 ++++----- dm-sflc/src/legacy/target.c | 60 ++++++------ dm-sflc/src/legacy/utils/bio.c | 12 +-- dm-sflc/src/legacy/utils/bio.h | 10 +- dm-sflc/src/legacy/utils/pools.c | 94 +++++++++---------- dm-sflc/src/legacy/utils/pools.h | 22 ++--- dm-sflc/src/legacy/utils/string.c | 8 +- dm-sflc/src/legacy/utils/string.h | 10 +- dm-sflc/src/legacy/utils/vector.c | 10 +- dm-sflc/src/legacy/utils/vector.h | 8 +- dm-sflc/src/legacy/utils/workqueues.c | 30 +++--- dm-sflc/src/legacy/utils/workqueues.h | 14 +-- dm-sflc/src/legacy/volume/fmap.c | 86 ++++++++--------- dm-sflc/src/legacy/volume/io.c | 24 ++--- dm-sflc/src/legacy/volume/read.c | 82 ++++++++-------- dm-sflc/src/legacy/volume/volume.c | 46 ++++----- dm-sflc/src/legacy/volume/volume.h | 60 ++++++------ dm-sflc/src/legacy/volume/write.c | 66 ++++++------- dm-sflc/src/sflc.c | 12 +-- dm-sflc/src/sflc.h | 6 +- shufflecake-userland/include/commands.h | 4 +- shufflecake-userland/include/header.h | 8 +- shufflecake-userland/include/operations.h | 14 +-- shufflecake-userland/include/utils/disk.h | 2 +- shufflecake-userland/include/utils/sflc.h | 2 +- shufflecake-userland/src/cli/init.c | 2 +- shufflecake-userland/src/cli/open.c | 2 +- .../src/commands/init_legacy.c | 6 +- .../src/commands/open_legacy.c | 8 +- .../src/header/position_map_legacy.c | 2 +- .../src/header/volume_master_block_legacy.c | 4 +- .../src/operations/devmapper_legacy.c | 4 +- .../src/operations/volume_header_legacy.c | 16 ++-- 50 files changed, 626 insertions(+), 626 deletions(-) diff --git a/dm-sflc/.Kbuild b/dm-sflc/.Kbuild index 89a38fe..b909a50 100644 --- a/dm-sflc/.Kbuild +++ b/dm-sflc/.Kbuild @@ -27,12 +27,12 @@ obj-m := $(MODULE_NAME).o OBJ_LIST := sflc.o dev_vol.o sysfs.o -OBJ_LIST += old/sflc_old.o old/target.o old/sysfs.o -OBJ_LIST += old/device/device.o old/device/volumes.o old/device/rawio.o old/device/rmap.o old/device/iv.o -OBJ_LIST += old/volume/volume.o old/volume/io.o old/volume/read.o old/volume/write.o old/volume/fmap.o -OBJ_LIST += old/utils/string.o old/utils/bio.o old/utils/pools.o old/utils/workqueues.o old/utils/vector.o -OBJ_LIST += old/crypto/rand/rand.o -OBJ_LIST += old/crypto/symkey/symkey.o old/crypto/symkey/skreq_pool.o +OBJ_LIST += legacy/sflc_legacy.o legacy/target.o legacy/sysfs.o +OBJ_LIST += legacy/device/device.o legacy/device/volumes.o legacy/device/rawio.o legacy/device/rmap.o legacy/device/iv.o +OBJ_LIST += legacy/volume/volume.o legacy/volume/io.o legacy/volume/read.o legacy/volume/write.o legacy/volume/fmap.o +OBJ_LIST += legacy/utils/string.o legacy/utils/bio.o legacy/utils/pools.o legacy/utils/workqueues.o legacy/utils/vector.o +OBJ_LIST += legacy/crypto/rand/rand.o +OBJ_LIST += legacy/crypto/symkey/symkey.o legacy/crypto/symkey/skreq_pool.o OBJ_LIST += lite/sflc_lite.o lite/sysfs.o OBJ_LIST += lite/device.o lite/volume.o diff --git a/dm-sflc/src/dev_vol.c b/dm-sflc/src/dev_vol.c index 3c5e5b3..6c454b9 100644 --- a/dm-sflc/src/dev_vol.c +++ b/dm-sflc/src/dev_vol.c @@ -24,7 +24,7 @@ #include #include "sflc.h" -#include "old/sflc_old.h" +#include "legacy/sflc_legacy.h" /* Create a sflc_device containing the appropriate mode-specific struct */ @@ -69,9 +69,9 @@ struct sflc_device *sflc_dev_create(struct dm_target *ti, int argc, char **argv) switch (mode) { case SFLC_MODE_LEGACY: - sdev->sfold_dev = sfold_dev_create(ti, argc, argv, &sdev->kobj); - if (IS_ERR(sdev->sfold_dev)) { - err = PTR_ERR(sdev->sfold_dev); + sdev->sflegc_dev = sflegc_dev_create(ti, argc, argv, &sdev->kobj); + if (IS_ERR(sdev->sflegc_dev)) { + err = PTR_ERR(sdev->sflegc_dev); goto bad_inner; } break; @@ -106,7 +106,7 @@ void sflc_dev_destroy(struct sflc_device *sdev) switch (sdev->mode) { case SFLC_MODE_LEGACY: - sfold_dev_destroy(sdev->sfold_dev); + sflegc_dev_destroy(sdev->sflegc_dev); break; case SFLC_MODE_LITE: sflite_dev_destroy(sdev->sflite_dev); @@ -156,12 +156,12 @@ struct sflc_volume *sflc_vol_create(struct sflc_device *sdev, struct dm_target * switch (mode) { case SFLC_MODE_LEGACY: - svol->sfold_vol = sfold_vol_create(ti, sdev->sfold_dev, argc, argv, &svol->kobj); - if (IS_ERR(svol->sfold_vol)) { - err = PTR_ERR(svol->sfold_vol); + svol->sflegc_vol = sflegc_vol_create(ti, sdev->sflegc_dev, argc, argv, &svol->kobj); + if (IS_ERR(svol->sflegc_vol)) { + err = PTR_ERR(svol->sflegc_vol); goto bad_inner; } - svol->tt = &sfold_target_type; + svol->tt = &sflegc_target_type; break; case SFLC_MODE_LITE: svol->sflite_vol = sflite_vol_create(ti, sdev->sflite_dev, argc, argv, &svol->kobj); @@ -194,7 +194,7 @@ void sflc_vol_destroy(struct sflc_volume *svol) switch (svol->mode) { case SFLC_MODE_LEGACY: - sfold_vol_destroy(svol->sfold_vol); + sflegc_vol_destroy(svol->sflegc_vol); break; case SFLC_MODE_LITE: sflite_vol_destroy(svol->sflite_vol); diff --git a/dm-sflc/src/legacy/crypto/rand/rand.c b/dm-sflc/src/legacy/crypto/rand/rand.c index 4981967..7ad6f9b 100644 --- a/dm-sflc/src/legacy/crypto/rand/rand.c +++ b/dm-sflc/src/legacy/crypto/rand/rand.c @@ -28,55 +28,55 @@ #include #include -#include "old/crypto/rand/rand.h" -#include "old/log/log.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFOLD_RAND_RNG_NAME "drbg_nopr_sha256" +#define SFLEGC_RAND_RNG_NAME "drbg_nopr_sha256" /***************************************************** * PRIVATE VARIABLES * *****************************************************/ -static struct mutex sfold_rand_tfm_lock; -static struct crypto_rng * sfold_rand_tfm = NULL; +static struct mutex sflegc_rand_tfm_lock; +static struct crypto_rng * sflegc_rand_tfm = NULL; /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ /* Flexible to accommodate for both required and non-required reseeding */ -static int sfold_rand_reseed(void); +static int sflegc_rand_reseed(void); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ /* Init the submodule */ -int sfold_rand_init(void) +int sflegc_rand_init(void) { int err; /* Init the lock governing the SFLC RNG */ - mutex_init(&sfold_rand_tfm_lock); + mutex_init(&sflegc_rand_tfm_lock); /* Allocate module-wide RNG */ - sfold_rand_tfm = crypto_alloc_rng(SFOLD_RAND_RNG_NAME, CRYPTO_ALG_TYPE_RNG, 0); - if (IS_ERR(sfold_rand_tfm)) { - err = PTR_ERR(sfold_rand_tfm); - sfold_rand_tfm = NULL; - pr_err("Could not allocate RNG %s; error %d\n", SFOLD_RAND_RNG_NAME, err); + sflegc_rand_tfm = crypto_alloc_rng(SFLEGC_RAND_RNG_NAME, CRYPTO_ALG_TYPE_RNG, 0); + if (IS_ERR(sflegc_rand_tfm)) { + err = PTR_ERR(sflegc_rand_tfm); + sflegc_rand_tfm = NULL; + pr_err("Could not allocate RNG %s; error %d\n", SFLEGC_RAND_RNG_NAME, err); return err; } /* The new RNG comes not seeded, right? */ - err = sfold_rand_reseed(); + err = sflegc_rand_reseed(); if (err) { pr_err("Could not seed the RNG; error %d\n", err); - sfold_rand_exit(); + sflegc_rand_exit(); return err; } @@ -84,26 +84,26 @@ int sfold_rand_init(void) } /* Get random bytes. Might sleep for re-seeding (not implemented yet), or for contention (mutex). */ -int sfold_rand_getBytes(u8 * buf, unsigned count) +int sflegc_rand_getBytes(u8 * buf, unsigned count) { int ret; /* Acquire lock */ - if (mutex_lock_interruptible(&sfold_rand_tfm_lock)) { + if (mutex_lock_interruptible(&sflegc_rand_tfm_lock)) { pr_err("Got error while waiting for SFLC RNG\n"); return -EINTR; } - ret = crypto_rng_get_bytes(sfold_rand_tfm, buf, count); + ret = crypto_rng_get_bytes(sflegc_rand_tfm, buf, count); /* End of critical region */ - mutex_unlock(&sfold_rand_tfm_lock); + mutex_unlock(&sflegc_rand_tfm_lock); return ret; } /* Get a random s32 from 0 (inclusive) to max (exclusive). Returns < 0 if error. */ -s32 sfold_rand_uniform(s32 max) +s32 sflegc_rand_uniform(s32 max) { s32 rand; s32 thresh; @@ -120,7 +120,7 @@ s32 sfold_rand_uniform(s32 max) thresh = S32_MAX - (S32_MAX % max); do { /* Sample a random signed integer, then make it positive */ - int err = sfold_rand_getBytes((void *) &rand, sizeof(rand)); + int err = sflegc_rand_getBytes((void *) &rand, sizeof(rand)); /* Can't make it positive if it's all 1's */ if (rand == S32_MIN) { continue; @@ -140,11 +140,11 @@ s32 sfold_rand_uniform(s32 max) } /* Tear down the submodule */ -void sfold_rand_exit(void) +void sflegc_rand_exit(void) { - if (sfold_rand_tfm) { - crypto_free_rng(sfold_rand_tfm); - sfold_rand_tfm = NULL; + if (sflegc_rand_tfm) { + crypto_free_rng(sflegc_rand_tfm); + sflegc_rand_tfm = NULL; } return; @@ -155,12 +155,12 @@ void sfold_rand_exit(void) *****************************************************/ /* Flexible to accommodate for both required and non-required reseeding */ -static int sfold_rand_reseed(void) +static int sflegc_rand_reseed(void) { int err; /* Reseed the RNG */ - err = crypto_rng_reset(sfold_rand_tfm, NULL, crypto_rng_seedsize(sfold_rand_tfm)); + err = crypto_rng_reset(sflegc_rand_tfm, NULL, crypto_rng_seedsize(sflegc_rand_tfm)); if (err) { pr_err("Could not feed seed to the RNG; error %d\n", err); return err; diff --git a/dm-sflc/src/legacy/crypto/rand/rand.h b/dm-sflc/src/legacy/crypto/rand/rand.h index d7de86a..a661e9b 100644 --- a/dm-sflc/src/legacy/crypto/rand/rand.h +++ b/dm-sflc/src/legacy/crypto/rand/rand.h @@ -27,8 +27,8 @@ * no need to make it more fine grained. */ -#ifndef _SFOLD_CRYPTO_RAND_RAND_H_ -#define _SFOLD_CRYPTO_RAND_RAND_H_ +#ifndef _SFLEGC_CRYPTO_RAND_RAND_H_ +#define _SFLEGC_CRYPTO_RAND_RAND_H_ /***************************************************** * INCLUDE SECTION * @@ -41,16 +41,16 @@ *****************************************************/ /* Init the submodule */ -int sfold_rand_init(void); +int sflegc_rand_init(void); /* Get random bytes. Might sleep for re-seeding (not implemented yet), or for contention (mutex). */ -int sfold_rand_getBytes(u8 * buf, unsigned count); +int sflegc_rand_getBytes(u8 * buf, unsigned count); /* Get a random s32 from 0 (inclusive) to max (exclusive). Returns < 0 if error. */ -s32 sfold_rand_uniform(s32 max); +s32 sflegc_rand_uniform(s32 max); /* Tear down the submodule */ -void sfold_rand_exit(void); +void sflegc_rand_exit(void); -#endif /* _SFOLD_CRYPTO_RAND_RAND_H_ */ +#endif /* _SFLEGC_CRYPTO_RAND_RAND_H_ */ diff --git a/dm-sflc/src/legacy/crypto/symkey/skreq_pool.c b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.c index a91fbf8..fd27875 100644 --- a/dm-sflc/src/legacy/crypto/symkey/skreq_pool.c +++ b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.c @@ -25,8 +25,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/crypto/symkey/skreq_pool.h" -#include "old/log/log.h" +#include "legacy/crypto/symkey/skreq_pool.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * @@ -37,17 +37,17 @@ *****************************************************/ /* A mempool_alloc_t using skcipher_request_alloc as backend */ -static void * sfold_sk_allocRequest(gfp_t gfp_mask, void * pool_data); +static void * sflegc_sk_allocRequest(gfp_t gfp_mask, void * pool_data); /* A mempool_free_t using skcipher_request_free as backend */ -static void sfold_sk_freeRequest(void * element, void * pool_data); +static void sflegc_sk_freeRequest(void * element, void * pool_data); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -mempool_t * sfold_sk_createReqPool(int min_nr, sfold_sk_Context * ctx) +mempool_t * sflegc_sk_createReqPool(int min_nr, sflegc_sk_Context * ctx) { - return mempool_create(min_nr, sfold_sk_allocRequest, sfold_sk_freeRequest, (void *) ctx); + return mempool_create(min_nr, sflegc_sk_allocRequest, sflegc_sk_freeRequest, (void *) ctx); } /***************************************************** @@ -55,9 +55,9 @@ mempool_t * sfold_sk_createReqPool(int min_nr, sfold_sk_Context * ctx) *****************************************************/ /* A mempool_alloc_t using skcipher_request_alloc as backend */ -static void * sfold_sk_allocRequest(gfp_t gfp_mask, void * pool_data) +static void * sflegc_sk_allocRequest(gfp_t gfp_mask, void * pool_data) { - sfold_sk_Context * ctx = pool_data; + sflegc_sk_Context * ctx = pool_data; struct skcipher_request * skreq; skreq = skcipher_request_alloc(ctx->tfm, gfp_mask); @@ -70,7 +70,7 @@ static void * sfold_sk_allocRequest(gfp_t gfp_mask, void * pool_data) } /* A mempool_free_t using skcipher_request_free as backend */ -static void sfold_sk_freeRequest(void * element, void * pool_data) +static void sflegc_sk_freeRequest(void * element, void * pool_data) { struct skcipher_request * skreq = element; diff --git a/dm-sflc/src/legacy/crypto/symkey/skreq_pool.h b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.h index 5c5ebba..e2c2c0b 100644 --- a/dm-sflc/src/legacy/crypto/symkey/skreq_pool.h +++ b/dm-sflc/src/legacy/crypto/symkey/skreq_pool.h @@ -26,8 +26,8 @@ * functions to the mempool interface. */ -#ifndef _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ -#define _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ +#ifndef _SFLEGC_CRYPTO_SYMKEY_SKREQ_POOL_H_ +#define _SFLEGC_CRYPTO_SYMKEY_SKREQ_POOL_H_ /***************************************************** * INCLUDE SECTION * @@ -35,7 +35,7 @@ #include -#include "old/crypto/symkey/symkey.h" +#include "legacy/crypto/symkey/symkey.h" /***************************************************** * CONSTANTS * @@ -49,7 +49,7 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -mempool_t * sfold_sk_createReqPool(int min_nr, sfold_sk_Context * ctx); +mempool_t * sflegc_sk_createReqPool(int min_nr, sflegc_sk_Context * ctx); -#endif /* _SFOLD_CRYPTO_SYMKEY_SKREQ_POOL_H_ */ +#endif /* _SFLEGC_CRYPTO_SYMKEY_SKREQ_POOL_H_ */ diff --git a/dm-sflc/src/legacy/crypto/symkey/symkey.c b/dm-sflc/src/legacy/crypto/symkey/symkey.c index 668e1ec..3efcdf4 100644 --- a/dm-sflc/src/legacy/crypto/symkey/symkey.c +++ b/dm-sflc/src/legacy/crypto/symkey/symkey.c @@ -27,66 +27,66 @@ #include -#include "old/crypto/symkey/symkey.h" -#include "old/crypto/symkey/skreq_pool.h" -#include "old/log/log.h" +#include "legacy/crypto/symkey/symkey.h" +#include "legacy/crypto/symkey/skreq_pool.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFOLD_SK_REQ_POOL_SIZE 1024 +#define SFLEGC_SK_REQ_POOL_SIZE 1024 -#define SFOLD_SK_ENCRYPT 0 -#define SFOLD_SK_DECRYPT 1 +#define SFLEGC_SK_ENCRYPT 0 +#define SFLEGC_SK_DECRYPT 1 /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static int sfold_sk_encdec(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op); +static int sflegc_sk_encdec(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ /* Create a new context with the given key. Returns an ERR_PTR() on failure. */ -sfold_sk_Context * sfold_sk_createContext(u8 * key) +sflegc_sk_Context * sflegc_sk_createContext(u8 * key) { - sfold_sk_Context * ctx; + sflegc_sk_Context * ctx; int err; /* Allocate context */ - ctx = kzalloc(sizeof(sfold_sk_Context), GFP_KERNEL); + ctx = kzalloc(sizeof(sflegc_sk_Context), GFP_KERNEL); if (!ctx) { - pr_err("Could not allocate %lu bytes for the sfold_sk_Context\n", sizeof(sfold_sk_Context)); + pr_err("Could not allocate %lu bytes for the sflegc_sk_Context\n", sizeof(sflegc_sk_Context)); return ERR_PTR(-ENOMEM); } /* Allocate crypto transform */ - ctx->tfm = crypto_alloc_skcipher(SFOLD_SK_CIPHER_NAME, CRYPTO_ALG_ASYNC, 0); + ctx->tfm = crypto_alloc_skcipher(SFLEGC_SK_CIPHER_NAME, CRYPTO_ALG_ASYNC, 0); if (IS_ERR(ctx->tfm)) { err = PTR_ERR(ctx->tfm); ctx->tfm = NULL; pr_err("Could not allocate skcipher handle: error %d\n", err); - sfold_sk_destroyContext(ctx); + sflegc_sk_destroyContext(ctx); return ERR_PTR(err); } /* Copy and set key */ - memcpy(ctx->key, key, SFOLD_SK_KEY_LEN); - err = crypto_skcipher_setkey(ctx->tfm, ctx->key, SFOLD_SK_KEY_LEN); + memcpy(ctx->key, key, SFLEGC_SK_KEY_LEN); + err = crypto_skcipher_setkey(ctx->tfm, ctx->key, SFLEGC_SK_KEY_LEN); if (err) { pr_err("Could not set key in crypto transform: error %d\n", err); - sfold_sk_destroyContext(ctx); + sflegc_sk_destroyContext(ctx); return ERR_PTR(err); } /* Create request memory pool */ - ctx->sk_req_pool = sfold_sk_createReqPool(SFOLD_SK_REQ_POOL_SIZE, ctx); + ctx->sk_req_pool = sflegc_sk_createReqPool(SFLEGC_SK_REQ_POOL_SIZE, ctx); if (!ctx->sk_req_pool) { pr_err("Could not allocate skcipher_request memory pool\n"); - sfold_sk_destroyContext(ctx); + sflegc_sk_destroyContext(ctx); return ERR_PTR(-ENOMEM); } @@ -94,7 +94,7 @@ sfold_sk_Context * sfold_sk_createContext(u8 * key) } /* Destroy the given context */ -void sfold_sk_destroyContext(sfold_sk_Context * ctx) +void sflegc_sk_destroyContext(sflegc_sk_Context * ctx) { if (!ctx) { return; @@ -116,21 +116,21 @@ void sfold_sk_destroyContext(sfold_sk_Context * ctx) } /* Encrypt synchronously. Provide src = dst for in-place operation. */ -int sfold_sk_encrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) +int sflegc_sk_encrypt(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) { - return sfold_sk_encdec(ctx, src, dst, len, iv, SFOLD_SK_ENCRYPT); + return sflegc_sk_encdec(ctx, src, dst, len, iv, SFLEGC_SK_ENCRYPT); } -int sfold_sk_decrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) +int sflegc_sk_decrypt(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv) { - return sfold_sk_encdec(ctx, src, dst, len, iv, SFOLD_SK_DECRYPT); + return sflegc_sk_encdec(ctx, src, dst, len, iv, SFLEGC_SK_DECRYPT); } /***************************************************** * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static int sfold_sk_encdec(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op) +static int sflegc_sk_encdec(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv, int op) { struct skcipher_request * skreq; struct scatterlist srcsg; @@ -162,7 +162,7 @@ static int sfold_sk_encdec(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned crypto_req_done, &skreq_wait); /* Do it */ - if (op == SFOLD_SK_ENCRYPT) { + if (op == SFLEGC_SK_ENCRYPT) { ret = crypto_skcipher_encrypt(skreq); } else { ret = crypto_skcipher_decrypt(skreq); diff --git a/dm-sflc/src/legacy/crypto/symkey/symkey.h b/dm-sflc/src/legacy/crypto/symkey/symkey.h index 3f4d440..ec814fe 100644 --- a/dm-sflc/src/legacy/crypto/symkey/symkey.h +++ b/dm-sflc/src/legacy/crypto/symkey/symkey.h @@ -25,8 +25,8 @@ * A thin wrapper around the kernel's synchronous block cipher API. */ -#ifndef _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ -#define _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ +#ifndef _SFLEGC_CRYPTO_SYMKEY_SYMKEY_H_ +#define _SFLEGC_CRYPTO_SYMKEY_SYMKEY_H_ /***************************************************** * INCLUDE SECTION * @@ -39,9 +39,9 @@ * CONSTANTS * *****************************************************/ -#define SFOLD_SK_CIPHER_NAME "ctr(aes)" -#define SFOLD_SK_KEY_LEN 32 -#define SFOLD_SK_IV_LEN 16 +#define SFLEGC_SK_CIPHER_NAME "ctr(aes)" +#define SFLEGC_SK_KEY_LEN 32 +#define SFLEGC_SK_IV_LEN 16 /***************************************************** * TYPES * @@ -51,31 +51,31 @@ * There is one of these Context's for each volume. * No need for locking, methods can be called in parallel. */ -typedef struct sfold_sk_context_s +typedef struct sflegc_sk_context_s { /* Only one transform for now */ struct crypto_skcipher * tfm; /* 32-byte key */ - u8 key[SFOLD_SK_KEY_LEN]; + u8 key[SFLEGC_SK_KEY_LEN]; /* Memory pool for skcipher_request's */ mempool_t * sk_req_pool; -} sfold_sk_Context; +} sflegc_sk_Context; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ /* Create a new context with the given key. Returns an ERR_PTR() on failure. */ -sfold_sk_Context * sfold_sk_createContext(u8 * key); +sflegc_sk_Context * sflegc_sk_createContext(u8 * key); /* Destroy the given context */ -void sfold_sk_destroyContext(sfold_sk_Context * ctx); +void sflegc_sk_destroyContext(sflegc_sk_Context * ctx); /* Encrypt/decrypt synchronously. Provide src = dst for in-place operation. */ -int sfold_sk_encrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); -int sfold_sk_decrypt(sfold_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); +int sflegc_sk_encrypt(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); +int sflegc_sk_decrypt(sflegc_sk_Context * ctx, u8 * src, u8 * dst, unsigned int len, u8 * iv); -#endif /* _SFOLD_CRYPTO_SYMKEY_SYMKEY_H_ */ +#endif /* _SFLEGC_CRYPTO_SYMKEY_SYMKEY_H_ */ diff --git a/dm-sflc/src/legacy/device/device.c b/dm-sflc/src/legacy/device/device.c index b5045c1..1725d65 100644 --- a/dm-sflc/src/legacy/device/device.c +++ b/dm-sflc/src/legacy/device/device.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/sflc_old.h" -#include "old/device/device.h" -#include "old/utils/vector.h" -#include "old/log/log.h" +#include "legacy/sflc_legacy.h" +#include "legacy/device/device.h" +#include "legacy/utils/vector.h" +#include "legacy/log/log.h" #include @@ -46,7 +46,7 @@ /* Initialises and pre-shuffles the PSI array */ -static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); +static int sflegc_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); /***************************************************** @@ -63,18 +63,18 @@ static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len); * argv[4]: number of 1 MB slices in the underlying device * argv[5]: 32-byte encryption key (hex-encoded) */ -sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj) +sflegc_Device *sflegc_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj) { - sfold_Device * dev; + sflegc_Device * dev; u32 tot_slices; u32 dev_id; int err; int i; /* Allocate device */ - dev = kzalloc(sizeof(sfold_Device), GFP_KERNEL); + dev = kzalloc(sizeof(sflegc_Device), GFP_KERNEL); if (!dev) { - pr_err("Could not allocate %lu bytes for sfold_Device\n", sizeof(sfold_Device)); + pr_err("Could not allocate %lu bytes for sflegc_Device\n", sizeof(sflegc_Device)); err = -ENOMEM; goto err_alloc_dev; } @@ -115,7 +115,7 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru strcpy(dev->bdev_path, argv[2]); /* Init volumes */ - for (i = 0; i < SFOLD_DEV_MAX_VOLUMES; ++i) { + for (i = 0; i < SFLEGC_DEV_MAX_VOLUMES; ++i) { dev->vol[i] = NULL; } dev->vol_cnt = 0; @@ -124,10 +124,10 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru dev->tot_slices = tot_slices; dev->free_slices = tot_slices; /* Compute header info (like in userland tool) */ - u32 nr_pmbs_per_vol = DIV_ROUND_UP(tot_slices, SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK); - dev->vol_header_nr_iv_blocks = DIV_ROUND_UP(nr_pmbs_per_vol, SFOLD_VOL_LOG_SLICE_SIZE); + u32 nr_pmbs_per_vol = DIV_ROUND_UP(tot_slices, SFLEGC_VOL_HEADER_MAPPINGS_PER_BLOCK); + dev->vol_header_nr_iv_blocks = DIV_ROUND_UP(nr_pmbs_per_vol, SFLEGC_VOL_LOG_SLICE_SIZE); dev->vol_header_size = 1 + nr_pmbs_per_vol + dev->vol_header_nr_iv_blocks; - dev->dev_header_size = 1 + (SFOLD_DEV_MAX_VOLUMES * dev->vol_header_size); + dev->dev_header_size = 1 + (SFLEGC_DEV_MAX_VOLUMES * dev->vol_header_size); /* Init slices lock */ mutex_init(&dev->slices_lock); @@ -139,7 +139,7 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru goto err_alloc_rmap; } /* Initialise it */ - memset(dev->rmap, SFOLD_DEV_RMAP_INVALID_VOL, dev->tot_slices * sizeof(u8)); + memset(dev->rmap, SFLEGC_DEV_RMAP_INVALID_VOL, dev->tot_slices * sizeof(u8)); /* Allocate PSI array */ dev->shuffled_psi_array = vmalloc(dev->tot_slices * sizeof(u32)); if (!dev->shuffled_psi_array) { @@ -148,7 +148,7 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru goto err_alloc_psi_array; } /* Initialise it and pre-shuffle it */ - err = sfold_dev_initAndShufflePsiArray(dev->shuffled_psi_array, dev->tot_slices); + err = sflegc_dev_initAndShufflePsiArray(dev->shuffled_psi_array, dev->tot_slices); if (err) { pr_err("Could not init-and-shuffle PSI array: error %d", err); goto err_initshuffle_psi_array; @@ -161,7 +161,7 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru /* Init IV cache waitqueue */ init_waitqueue_head(&dev->iv_cache_waitqueue); /* Allocate IV cache */ - dev->iv_cache = kzalloc(dev->tot_slices * sizeof(sfold_dev_IvCacheEntry *), GFP_KERNEL); + dev->iv_cache = kzalloc(dev->tot_slices * sizeof(sflegc_dev_IvCacheEntry *), GFP_KERNEL); if (!dev->iv_cache) { pr_err("Could not allocate IV cache\n"); err = -ENOMEM; @@ -174,7 +174,7 @@ sfold_Device *sfold_dev_create(struct dm_target *ti, int argc, char **argv, stru /* Add to sysfs */ dev->kobj_parent = kobj; - err = sfold_sysfs_add_device(dev); + err = sflegc_sysfs_add_device(dev); if (err) { pr_err("Could not add device to sysfs; error %d\n", err); goto err_sysfs; @@ -203,7 +203,7 @@ err_alloc_dev: /* Returns false if still busy (not all volumes have been removed). Frees the Device. */ -bool sfold_dev_destroy(sfold_Device * dev) +bool sflegc_dev_destroy(sflegc_Device * dev) { /* Check if we actually have to put this device */ if (!dev) { @@ -215,13 +215,13 @@ bool sfold_dev_destroy(sfold_Device * dev) } /* Flush all IVs */ - sfold_dev_flushIvs(dev); + sflegc_dev_flushIvs(dev); /* List */ list_del(&dev->list_node); /* Sysfs */ - sfold_sysfs_remove_device(dev); + sflegc_sysfs_remove_device(dev); /* IV cache */ kfree(dev->iv_cache); @@ -244,7 +244,7 @@ bool sfold_dev_destroy(sfold_Device * dev) /* Initialises and pre-shuffles the PSI array */ -static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) +static int sflegc_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) { u32 i; @@ -254,6 +254,6 @@ static int sfold_dev_initAndShufflePsiArray(u32 *psi_array, u32 len) } /* Permute */ - return sfold_vec_u32shuffle(psi_array, len); + return sflegc_vec_u32shuffle(psi_array, len); } diff --git a/dm-sflc/src/legacy/device/device.h b/dm-sflc/src/legacy/device/device.h index f893772..a6109e7 100644 --- a/dm-sflc/src/legacy/device/device.h +++ b/dm-sflc/src/legacy/device/device.h @@ -30,8 +30,8 @@ * are stored in increasing degree of "secrecy"). */ -#ifndef _SFOLD_DEVICE_DEVICE_H_ -#define _SFOLD_DEVICE_DEVICE_H_ +#ifndef _SFLEGC_DEVICE_DEVICE_H_ +#define _SFLEGC_DEVICE_DEVICE_H_ /***************************************************** @@ -40,8 +40,8 @@ /* Necessary since device.h, volume.h, and sysfs.h all include each other */ -typedef struct sfold_device_s sfold_Device; -typedef struct sfold_dev_iv_cache_entry_s sfold_dev_IvCacheEntry; +typedef struct sflegc_device_s sflegc_Device; +typedef struct sflegc_dev_iv_cache_entry_s sflegc_dev_IvCacheEntry; /***************************************************** @@ -50,9 +50,9 @@ typedef struct sfold_dev_iv_cache_entry_s sfold_dev_IvCacheEntry; #include -#include "old/sflc_old.h" -#include "old/volume/volume.h" -#include "old/crypto/symkey/symkey.h" +#include "legacy/sflc_legacy.h" +#include "legacy/volume/volume.h" +#include "legacy/crypto/symkey/symkey.h" /***************************************************** @@ -60,30 +60,30 @@ typedef struct sfold_dev_iv_cache_entry_s sfold_dev_IvCacheEntry; *****************************************************/ /* We need 4096-byte sectors to amortise the space overhead of the IVs */ -#define SFOLD_DEV_SECTOR_SIZE 4096 +#define SFLEGC_DEV_SECTOR_SIZE 4096 /* A SFLC sector encompasses 8 kernel sectors */ -#define SFOLD_DEV_SECTOR_SCALE (SFOLD_DEV_SECTOR_SIZE / SECTOR_SIZE) +#define SFLEGC_DEV_SECTOR_SCALE (SFLEGC_DEV_SECTOR_SIZE / SECTOR_SIZE) /* An IV block holds IVs for 256 data blocks */ -#define SFOLD_DEV_SECTOR_TO_IV_RATIO (SFOLD_DEV_SECTOR_SIZE / SFOLD_SK_IV_LEN) +#define SFLEGC_DEV_SECTOR_TO_IV_RATIO (SFLEGC_DEV_SECTOR_SIZE / SFLEGC_SK_IV_LEN) /* Max number of volumes linked to a single device */ -#define SFOLD_DEV_MAX_VOLUMES 15 +#define SFLEGC_DEV_MAX_VOLUMES 15 /* A physical slice contains the 256 encrypted data blocks and the IV block */ -#define SFOLD_DEV_PHYS_SLICE_SIZE (SFOLD_VOL_LOG_SLICE_SIZE + (SFOLD_VOL_LOG_SLICE_SIZE / SFOLD_DEV_SECTOR_TO_IV_RATIO)) +#define SFLEGC_DEV_PHYS_SLICE_SIZE (SFLEGC_VOL_LOG_SLICE_SIZE + (SFLEGC_VOL_LOG_SLICE_SIZE / SFLEGC_DEV_SECTOR_TO_IV_RATIO)) /* Value marking a PSI as unassigned */ -#define SFOLD_DEV_RMAP_INVALID_VOL 0xFFU +#define SFLEGC_DEV_RMAP_INVALID_VOL 0xFFU /* Maximum number of open devices in total across shufflecake */ -#define SFOLD_DEV_MAX_DEVICES_TOT 1024 +#define SFLEGC_DEV_MAX_DEVICES_TOT 1024 /***************************************************** * TYPES * *****************************************************/ -struct sfold_dev_iv_cache_entry_s +struct sflegc_dev_iv_cache_entry_s { /* The PSI it refers to */ u32 psi; @@ -99,7 +99,7 @@ struct sfold_dev_iv_cache_entry_s struct list_head lru_node; }; -struct sfold_device_s +struct sflegc_device_s { /* Underlying block device */ struct dm_dev * bdev; @@ -110,7 +110,7 @@ struct sfold_device_s u32 dev_id; /* All volumes linked to this device */ - sfold_Volume * vol[SFOLD_DEV_MAX_VOLUMES]; + sflegc_Volume * vol[SFLEGC_DEV_MAX_VOLUMES]; int vol_cnt; /* Reverse slice map, associating PSIs to volume indices */ @@ -136,7 +136,7 @@ struct sfold_device_s /* LRU cache of IV blocks */ struct mutex iv_cache_lock; wait_queue_head_t iv_cache_waitqueue; - sfold_dev_IvCacheEntry ** iv_cache; + sflegc_dev_IvCacheEntry ** iv_cache; u32 iv_cache_nr_entries; struct list_head iv_lru_list; @@ -155,32 +155,32 @@ struct sfold_device_s */ /* Creates Device and adds it to the list. Returns an ERR_PTR() if unsuccessful. */ -sfold_Device * sfold_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj); +sflegc_Device * sflegc_dev_create(struct dm_target *ti, int argc, char **argv, struct kobject *kobj); /* Returns false if still busy (not all volumes have been removed) Frees the Device. */ -bool sfold_dev_destroy(sfold_Device * dev); +bool sflegc_dev_destroy(sflegc_Device * dev); /* Returns false if volume index was already occupied. */ -bool sfold_dev_addVolume(sfold_Device * dev, sfold_Volume * vol, int vol_idx); +bool sflegc_dev_addVolume(sflegc_Device * dev, sflegc_Volume * vol, int vol_idx); /* Does not put the volume. Returns false if was already NULL. */ -bool sfold_dev_removeVolume(sfold_Device * dev, int vol_idx); +bool sflegc_dev_removeVolume(sflegc_Device * dev, int vol_idx); /* Synchronously reads/writes one 4096-byte sector from/to the underlying device to/from the provided page */ -int sfold_dev_rwSector(sfold_Device * dev, struct page * page, sector_t sector, int rw); +int sflegc_dev_rwSector(sflegc_Device * dev, struct page * page, sector_t sector, int rw); /* The caller needs to hold slices_lock to call these functions */ /* Sets the PSI as owned by the given volume (also decreases free_slices). * Returns < 0 if already taken. */ -int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx); +int sflegc_dev_markPsiTaken(sflegc_Device * dev, u32 psi, u8 vol_idx); /* Returns a random free physical slice, or < 0 if error */ -s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev); +s32 sflegc_dev_getNextRandomFreePsi(sflegc_Device * dev); /* These functions provide concurrent-safe access to the entries of the IV cache. @@ -191,13 +191,13 @@ s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev); When the refcount reaches 0, the IV block is flushed. */ /* Get a pointer to the specified IV block. Increases the refcount and possibly the dirtyness (if WRITE). */ -u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw); +u8 * sflegc_dev_getIvBlockRef(sflegc_Device * dev, u32 psi, int rw); /* Signal end of usage of an IV block. Decreases the refcount. */ -int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi); +int sflegc_dev_putIvBlockRef(sflegc_Device * dev, u32 psi); /* Flush all dirty IV blocks */ -void sfold_dev_flushIvs(sfold_Device * dev); +void sflegc_dev_flushIvs(sflegc_Device * dev); -#endif /* _SFOLD_DEVICE_DEVICE_H_ */ +#endif /* _SFLEGC_DEVICE_DEVICE_H_ */ diff --git a/dm-sflc/src/legacy/device/iv.c b/dm-sflc/src/legacy/device/iv.c index c2c0fb1..5c01201 100644 --- a/dm-sflc/src/legacy/device/iv.c +++ b/dm-sflc/src/legacy/device/iv.c @@ -26,29 +26,29 @@ *****************************************************/ -#include "old/device/device.h" -#include "old/utils/pools.h" -#include "old/log/log.h" +#include "legacy/device/device.h" +#include "legacy/utils/pools.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ /* Capacity of IV cache */ -#define SFOLD_DEV_IV_CACHE_CAPACITY 1024 +#define SFLEGC_DEV_IV_CACHE_CAPACITY 1024 /***************************************************** * MACROS * *****************************************************/ -#define sfold_dev_psiToIvBlockSector(dev, psi) (dev->dev_header_size + (sector_t)(psi) * SFOLD_DEV_PHYS_SLICE_SIZE) +#define sflegc_dev_psiToIvBlockSector(dev, psi) (dev->dev_header_size + (sector_t)(psi) * SFLEGC_DEV_PHYS_SLICE_SIZE) /***************************************************** * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u32 psi); -static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEntry * entry); +static sflegc_dev_IvCacheEntry * sflegc_dev_newIvCacheEntry(sflegc_Device * dev, u32 psi); +static int sflegc_dev_destroyIvCacheEntry(sflegc_Device * dev, sflegc_dev_IvCacheEntry * entry); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * @@ -56,9 +56,9 @@ static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEn /* Get a read/write pointer to the specified IV block. Increases the refcount. Returns an ERR_PTR() if error. */ -u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw) +u8 * sflegc_dev_getIvBlockRef(sflegc_Device * dev, u32 psi, int rw) { - sfold_dev_IvCacheEntry * entry; + sflegc_dev_IvCacheEntry * entry; int err; /* Lock + waitqueue pattern */ @@ -71,13 +71,13 @@ u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw) } /* Check for either of two conditions in order to go through */ - while (dev->iv_cache[psi] == NULL && dev->iv_cache_nr_entries >= SFOLD_DEV_IV_CACHE_CAPACITY) { + while (dev->iv_cache[psi] == NULL && dev->iv_cache_nr_entries >= SFLEGC_DEV_IV_CACHE_CAPACITY) { /* We can't go through, yield the lock */ mutex_unlock(&dev->iv_cache_lock); /* Sleep in the waitqueue (same conditions) */ if (wait_event_interruptible(dev->iv_cache_waitqueue, dev->iv_cache[psi] != NULL || - dev->iv_cache_nr_entries < SFOLD_DEV_IV_CACHE_CAPACITY)) { + dev->iv_cache_nr_entries < SFLEGC_DEV_IV_CACHE_CAPACITY)) { err = -EINTR; pr_err("Interrupted while waiting in waitqueue\n"); goto err_wait_queue; @@ -99,7 +99,7 @@ u8 * sfold_dev_getIvBlockRef(sfold_Device * dev, u32 psi, int rw) entry = dev->iv_cache[psi]; if (!entry) { /* Create it */ - entry = sfold_dev_newIvCacheEntry(dev, psi); + entry = sflegc_dev_newIvCacheEntry(dev, psi); if (IS_ERR(entry)) { err = PTR_ERR(entry); pr_err("Could not create new cache entry; error %d\n", err); @@ -139,9 +139,9 @@ err_lock_cache: } /* Signal end of usage of an IV block. Decreases the refcount. */ -int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi) +int sflegc_dev_putIvBlockRef(sflegc_Device * dev, u32 psi) { - sfold_dev_IvCacheEntry * entry; + sflegc_dev_IvCacheEntry * entry; int err; /* No condition needed besides mutual exclusion: just grab the lock (no waitqueue) */ @@ -162,12 +162,12 @@ int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi) list_add(&entry->lru_node, &dev->iv_lru_list); /* If cache is not full, we can return now */ - if (dev->iv_cache_nr_entries < SFOLD_DEV_IV_CACHE_CAPACITY) { + if (dev->iv_cache_nr_entries < SFLEGC_DEV_IV_CACHE_CAPACITY) { goto out; } /* Otherwise, let's look for the least recent unreffed entry, and evict it */ - sfold_dev_IvCacheEntry * evicted; + sflegc_dev_IvCacheEntry * evicted; bool found = false; list_for_each_entry_reverse(evicted, &dev->iv_lru_list, lru_node) { if (evicted->refcnt == 0) { @@ -188,7 +188,7 @@ int sfold_dev_putIvBlockRef(sfold_Device * dev, u32 psi) /* Pull it out of the LRU list */ __list_del_entry(&evicted->lru_node); /* Destroy it (free and flush to disk) */ - err = sfold_dev_destroyIvCacheEntry(dev, evicted); + err = sflegc_dev_destroyIvCacheEntry(dev, evicted); if (err) { pr_err("Could not evict cache entry for PSI %u; error %d\n", evicted->psi, err); goto err_destroy_entry; @@ -216,9 +216,9 @@ err_lock_cache: } /* Flush all dirty IV blocks */ -void sfold_dev_flushIvs(sfold_Device * dev) +void sflegc_dev_flushIvs(sflegc_Device * dev) { - sfold_dev_IvCacheEntry * entry, * _next; + sflegc_dev_IvCacheEntry * entry, * _next; int err; /* Iterate over all entries */ @@ -227,7 +227,7 @@ void sfold_dev_flushIvs(sfold_Device * dev) __list_del_entry(&entry->lru_node); /* Destroy it */ - err = sfold_dev_destroyIvCacheEntry(dev, entry); + err = sflegc_dev_destroyIvCacheEntry(dev, entry); if (err) { pr_err("Could not destroy IV cache entry for PSI %u; error %d\n", entry->psi, err); } @@ -238,16 +238,16 @@ void sfold_dev_flushIvs(sfold_Device * dev) * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u32 psi) +static sflegc_dev_IvCacheEntry * sflegc_dev_newIvCacheEntry(sflegc_Device * dev, u32 psi) { - sfold_dev_IvCacheEntry * entry; + sflegc_dev_IvCacheEntry * entry; int err; sector_t sector; /* Allocate and init structure */ /* Allocate structure */ - entry = kmem_cache_alloc(sfold_pools_ivSlab, GFP_NOIO); + entry = kmem_cache_alloc(sflegc_pools_ivSlab, GFP_NOIO); if (!entry) { pr_err("Could not allocate IvCacheEntry structure\n"); err = -ENOMEM; @@ -257,7 +257,7 @@ static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u3 /* Set PSI */ entry->psi = psi; /* Allocate page */ - entry->iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + entry->iv_page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!entry->iv_page) { pr_err("Could not allocate IV page\n"); err = -ENOMEM; @@ -277,10 +277,10 @@ static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u3 /* Read from disk */ /* Position on disk */ - sector = sfold_dev_psiToIvBlockSector(dev, psi); + sector = sflegc_dev_psiToIvBlockSector(dev, psi); /* Read */ - err = sfold_dev_rwSector(dev, entry->iv_page, sector, READ); + err = sflegc_dev_rwSector(dev, entry->iv_page, sector, READ); if (err) { pr_err("Could not read IV block from disk; error %d\n", err); goto err_read; @@ -291,14 +291,14 @@ static sfold_dev_IvCacheEntry * sfold_dev_newIvCacheEntry(sfold_Device * dev, u3 err_read: kunmap(entry->iv_page); - mempool_free(entry->iv_page, sfold_pools_pagePool); + mempool_free(entry->iv_page, sflegc_pools_pagePool); err_alloc_page: - kmem_cache_free(sfold_pools_ivSlab, entry); + kmem_cache_free(sflegc_pools_ivSlab, entry); err_alloc_entry: return ERR_PTR(err); } -static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEntry * entry) +static int sflegc_dev_destroyIvCacheEntry(sflegc_Device * dev, sflegc_dev_IvCacheEntry * entry) { int err; sector_t sector; @@ -306,11 +306,11 @@ static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEn /* Write to disk */ /* Position on disk */ - sector = sfold_dev_psiToIvBlockSector(dev, entry->psi); + sector = sflegc_dev_psiToIvBlockSector(dev, entry->psi); /* Write (if necessary) */ if (entry->dirtyness) { - err = sfold_dev_rwSector(dev, entry->iv_page, sector, WRITE); + err = sflegc_dev_rwSector(dev, entry->iv_page, sector, WRITE); if (err) { pr_err("Could not write IV block to disk; error %d\n", err); return err; @@ -323,10 +323,10 @@ static int sfold_dev_destroyIvCacheEntry(sfold_Device * dev, sfold_dev_IvCacheEn /* Kunmap page */ kunmap(entry->iv_page); /* Free it */ - mempool_free(entry->iv_page, sfold_pools_pagePool); + mempool_free(entry->iv_page, sflegc_pools_pagePool); /* Free structure */ - kmem_cache_free(sfold_pools_ivSlab, entry); + kmem_cache_free(sflegc_pools_ivSlab, entry); return 0; } diff --git a/dm-sflc/src/legacy/device/rawio.c b/dm-sflc/src/legacy/device/rawio.c index ec70147..02da426 100644 --- a/dm-sflc/src/legacy/device/rawio.c +++ b/dm-sflc/src/legacy/device/rawio.c @@ -25,9 +25,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" -#include "old/utils/pools.h" -#include "old/log/log.h" +#include "legacy/device/device.h" +#include "legacy/utils/pools.h" +#include "legacy/log/log.h" #include #include @@ -46,7 +46,7 @@ /* Synchronously reads/writes one 4096-byte sector from/to the underlying device to/from the provided page */ -int sfold_dev_rwSector(sfold_Device * dev, struct page * page, sector_t sector, int rw) +int sflegc_dev_rwSector(sflegc_Device * dev, struct page * page, sector_t sector, int rw) { struct bio *bio; blk_opf_t opf; @@ -57,16 +57,16 @@ int sfold_dev_rwSector(sfold_Device * dev, struct page * page, sector_t sector, opf |= REQ_SYNC; /* Allocate bio */ - bio = bio_alloc_bioset(dev->bdev->bdev, 1, opf, GFP_NOIO, &sfold_pools_bioset); + bio = bio_alloc_bioset(dev->bdev->bdev, 1, opf, GFP_NOIO, &sflegc_pools_bioset); if (!bio) { pr_err("Could not allocate bio\n"); return -ENOMEM; } /* Set sector */ - bio->bi_iter.bi_sector = sector * SFOLD_DEV_SECTOR_SCALE; + bio->bi_iter.bi_sector = sector * SFLEGC_DEV_SECTOR_SCALE; /* Add page */ - if (!bio_add_page(bio, page, SFOLD_DEV_SECTOR_SIZE, 0)) { + if (!bio_add_page(bio, page, SFLEGC_DEV_SECTOR_SIZE, 0)) { pr_err("Catastrophe: could not add page to bio! WTF?\n"); err = EINVAL; goto out; diff --git a/dm-sflc/src/legacy/device/rmap.c b/dm-sflc/src/legacy/device/rmap.c index d196462..f953009 100644 --- a/dm-sflc/src/legacy/device/rmap.c +++ b/dm-sflc/src/legacy/device/rmap.c @@ -29,9 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" -#include "old/crypto/rand/rand.h" -#include "old/log/log.h" +#include "legacy/device/device.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * @@ -43,7 +43,7 @@ /* Sets the PSI as owned by the given volume (also decreases free_slices). * Returns < 0 if already taken. */ -int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx) +int sflegc_dev_markPsiTaken(sflegc_Device * dev, u32 psi, u8 vol_idx) { u8 prev_vol_idx; @@ -55,7 +55,7 @@ int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx) /* Check that it's free */ prev_vol_idx = dev->rmap[psi]; - if (prev_vol_idx != SFOLD_DEV_RMAP_INVALID_VOL) { + if (prev_vol_idx != SFLEGC_DEV_RMAP_INVALID_VOL) { pr_err("Requested to set ownership for already-owned PSI\n"); return -EINVAL; } @@ -69,7 +69,7 @@ int sfold_dev_markPsiTaken(sfold_Device * dev, u32 psi, u8 vol_idx) /* Returns a random free physical slice, or < 0 if error */ -s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev) +s32 sflegc_dev_getNextRandomFreePsi(sflegc_Device * dev) { u32 psi; @@ -89,7 +89,7 @@ s32 sfold_dev_getNextRandomFreePsi(sfold_Device * dev) pr_err("Double catastrophe! No free PSIs on the device, and didn't catch it before!\n"); return -ENOSPC; } - } while (dev->rmap[psi] != SFOLD_DEV_RMAP_INVALID_VOL); + } while (dev->rmap[psi] != SFLEGC_DEV_RMAP_INVALID_VOL); return psi; } diff --git a/dm-sflc/src/legacy/device/volumes.c b/dm-sflc/src/legacy/device/volumes.c index 7b12ab2..32efa1b 100644 --- a/dm-sflc/src/legacy/device/volumes.c +++ b/dm-sflc/src/legacy/device/volumes.c @@ -29,8 +29,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" -#include "old/log/log.h" +#include "legacy/device/device.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * @@ -41,7 +41,7 @@ *****************************************************/ /* Returns false if volume index was already occupied. */ -bool sfold_dev_addVolume(sfold_Device * dev, sfold_Volume * vol, int vol_idx) +bool sflegc_dev_addVolume(sflegc_Device * dev, sflegc_Volume * vol, int vol_idx) { if (dev->vol[vol_idx]) { pr_err("Something's wrong, asked to set volume number %d, already occupied\n", vol_idx); @@ -57,7 +57,7 @@ bool sfold_dev_addVolume(sfold_Device * dev, sfold_Volume * vol, int vol_idx) /* Does not put the volume. Returns false if was already NULL. */ -bool sfold_dev_removeVolume(sfold_Device * dev, int vol_idx) +bool sflegc_dev_removeVolume(sflegc_Device * dev, int vol_idx) { if (!dev->vol[vol_idx]) { pr_err("Something's wrong, asked to unset volume number %d, already NULL\n", vol_idx); diff --git a/dm-sflc/src/legacy/log/log.h b/dm-sflc/src/legacy/log/log.h index 8386d98..1d415d1 100644 --- a/dm-sflc/src/legacy/log/log.h +++ b/dm-sflc/src/legacy/log/log.h @@ -25,8 +25,8 @@ * Logging format */ -#ifndef _SFOLD_LOG_LOG_H_ -#define _SFOLD_LOG_LOG_H_ +#ifndef _SFLEGC_LOG_LOG_H_ +#define _SFLEGC_LOG_LOG_H_ /***************************************************** * INCLUDE SECTION * @@ -41,4 +41,4 @@ #undef pr_fmt #define pr_fmt(fmt) "[%s] %s in %s:%d: " fmt, KBUILD_MODNAME, __func__, __FILE__, __LINE__ -#endif /* _SFOLD_LOG_LOG_H_ */ +#endif /* _SFLEGC_LOG_LOG_H_ */ diff --git a/dm-sflc/src/legacy/sflc_legacy.c b/dm-sflc/src/legacy/sflc_legacy.c index 4f1a839..cd4167c 100644 --- a/dm-sflc/src/legacy/sflc_legacy.c +++ b/dm-sflc/src/legacy/sflc_legacy.c @@ -28,12 +28,12 @@ #include #include -#include "old/sflc_old.h" -#include "old/crypto/symkey/symkey.h" -#include "old/crypto/rand/rand.h" -#include "old/utils/pools.h" -#include "old/utils/workqueues.h" -#include "old/log/log.h" +#include "legacy/sflc_legacy.h" +#include "legacy/crypto/symkey/symkey.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/utils/pools.h" +#include "legacy/utils/workqueues.h" +#include "legacy/log/log.h" /***************************************************** @@ -41,25 +41,25 @@ *****************************************************/ /* Module entry point, called just once, at module-load time */ -int sfold_init(void) +int sflegc_init(void) { int ret; - ret = sfold_rand_init(); + ret = sflegc_rand_init(); if (ret) { pr_err("Could not init rand; error %d\n", ret); goto err_rand_init; } /* Init the memory pools */ - ret = sfold_pools_init(); + ret = sflegc_pools_init(); if (ret) { pr_err("Could not init memory pools; error %d\n", ret); goto err_pools; } /* Init the workqueues */ - ret = sfold_queues_init(); + ret = sflegc_queues_init(); if (ret) { pr_err("Could not init workqueues; error %d\n", ret); goto err_queues; @@ -69,19 +69,19 @@ int sfold_init(void) err_queues: - sfold_pools_exit(); + sflegc_pools_exit(); err_pools: - sfold_rand_exit(); + sflegc_rand_exit(); err_rand_init: return ret; } /* Module exit point, called just once, at module-unload time */ -void sfold_exit(void) +void sflegc_exit(void) { - sfold_queues_exit(); - sfold_pools_exit(); - sfold_rand_exit(); + sflegc_queues_exit(); + sflegc_pools_exit(); + sflegc_rand_exit(); return; } diff --git a/dm-sflc/src/legacy/sflc_legacy.h b/dm-sflc/src/legacy/sflc_legacy.h index 7f86748..70f0a95 100644 --- a/dm-sflc/src/legacy/sflc_legacy.h +++ b/dm-sflc/src/legacy/sflc_legacy.h @@ -21,26 +21,26 @@ * If not, see . */ -#ifndef _SFOLD_SFOLD_H -#define _SFOLD_SFOLD_H +#ifndef _SFLEGC_SFLEGC_H +#define _SFLEGC_SFLEGC_H -// For the definition of sfold_Device and its functions -#include "old/device/device.h" -// For the definition of sfold_Volume and its functions -#include "old/volume/volume.h" +// For the definition of sflegc_Device and its functions +#include "legacy/device/device.h" +// For the definition of sflegc_Volume and its functions +#include "legacy/volume/volume.h" -extern struct target_type sfold_target_type; +extern struct target_type sflegc_target_type; -int sfold_init(void); -void sfold_exit(void); +int sflegc_init(void); +void sflegc_exit(void); -int sfold_sysfs_add_device(sfold_Device *dev); -void sfold_sysfs_remove_device(sfold_Device *dev); -int sfold_sysfs_add_volume(sfold_Volume *vol); -void sfold_sysfs_remove_volume(sfold_Volume *vol); +int sflegc_sysfs_add_device(sflegc_Device *dev); +void sflegc_sysfs_remove_device(sflegc_Device *dev); +int sflegc_sysfs_add_volume(sflegc_Volume *vol); +void sflegc_sysfs_remove_volume(sflegc_Volume *vol); -#endif /* _SFOLD_SFOLD_H */ +#endif /* _SFLEGC_SFLEGC_H */ diff --git a/dm-sflc/src/legacy/sysfs.c b/dm-sflc/src/legacy/sysfs.c index 5445d8f..df6615e 100644 --- a/dm-sflc/src/legacy/sysfs.c +++ b/dm-sflc/src/legacy/sysfs.c @@ -27,8 +27,8 @@ #include -#include "old/sflc_old.h" -#include "old/log/log.h" +#include "legacy/sflc_legacy.h" +#include "legacy/log/log.h" // Only to import the definitions of structs sflc_volume and sflc_device #include "sflc.h" @@ -44,11 +44,11 @@ static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { struct sflc_device *top_dev; - sfold_Device * dev; + sflegc_Device * dev; ssize_t ret; top_dev = container_of(kobj, struct sflc_device, kobj); - dev = top_dev->sfold_dev; + dev = top_dev->sflegc_dev; /* Write the tot_slices */ ret = sysfs_emit(buf, "%u\n", dev->tot_slices); @@ -60,11 +60,11 @@ static ssize_t tot_slices_show(struct kobject *kobj, struct kobj_attribute *katt static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { struct sflc_device *top_dev; - sfold_Device * dev; + sflegc_Device * dev; ssize_t ret; top_dev = container_of(kobj, struct sflc_device, kobj); - dev = top_dev->sfold_dev; + dev = top_dev->sflegc_dev; /* Write the free_slices */ if (mutex_lock_interruptible(&dev->slices_lock)) @@ -77,23 +77,23 @@ static ssize_t free_slices_show(struct kobject *kobj, struct kobj_attribute *kat static struct kobj_attribute tot_slices_kattr = __ATTR_RO(tot_slices); static struct kobj_attribute free_slices_kattr = __ATTR_RO(free_slices); -static struct attribute *sfold_device_attrs[] = { +static struct attribute *sflegc_device_attrs[] = { &tot_slices_kattr.attr, &free_slices_kattr.attr, NULL }; -static const struct attribute_group sfold_device_attr_group = { - .attrs = sfold_device_attrs, +static const struct attribute_group sflegc_device_attr_group = { + .attrs = sflegc_device_attrs, }; -int sfold_sysfs_add_device(sfold_Device *dev) +int sflegc_sysfs_add_device(sflegc_Device *dev) { - return sysfs_create_group(dev->kobj_parent, &sfold_device_attr_group); + return sysfs_create_group(dev->kobj_parent, &sflegc_device_attr_group); } -void sfold_sysfs_remove_device(sfold_Device *dev) +void sflegc_sysfs_remove_device(sflegc_Device *dev) { - sysfs_remove_group(dev->kobj_parent, &sfold_device_attr_group); + sysfs_remove_group(dev->kobj_parent, &sflegc_device_attr_group); } @@ -107,11 +107,11 @@ void sfold_sysfs_remove_device(sfold_Device *dev) static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { struct sflc_volume *top_vol; - sfold_Volume * vol; + sflegc_Volume * vol; ssize_t ret; top_vol = container_of(kobj, struct sflc_volume, kobj); - vol = top_vol->sfold_vol; + vol = top_vol->sflegc_vol; /* Write the free_slices */ if (mutex_lock_interruptible(&vol->fmap_lock)) @@ -123,20 +123,20 @@ static ssize_t mapped_slices_show(struct kobject *kobj, struct kobj_attribute *k } static struct kobj_attribute mapped_slices_kattr = __ATTR_RO(mapped_slices); -static struct attribute *sfold_volume_attrs[] = { +static struct attribute *sflegc_volume_attrs[] = { &mapped_slices_kattr.attr, NULL }; -static const struct attribute_group sfold_volume_attr_group = { - .attrs = sfold_volume_attrs, +static const struct attribute_group sflegc_volume_attr_group = { + .attrs = sflegc_volume_attrs, }; -int sfold_sysfs_add_volume(sfold_Volume *vol) +int sflegc_sysfs_add_volume(sflegc_Volume *vol) { - return sysfs_create_group(vol->kobj_parent, &sfold_volume_attr_group); + return sysfs_create_group(vol->kobj_parent, &sflegc_volume_attr_group); } -void sfold_sysfs_remove_volume(sfold_Volume *vol) +void sflegc_sysfs_remove_volume(sflegc_Volume *vol) { - sysfs_remove_group(vol->kobj_parent, &sfold_volume_attr_group); + sysfs_remove_group(vol->kobj_parent, &sflegc_volume_attr_group); } diff --git a/dm-sflc/src/legacy/target.c b/dm-sflc/src/legacy/target.c index 4ceb71d..08a0d40 100644 --- a/dm-sflc/src/legacy/target.c +++ b/dm-sflc/src/legacy/target.c @@ -29,11 +29,11 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" -#include "old/volume/volume.h" -#include "old/utils/bio.h" -#include "old/utils/string.h" -#include "old/log/log.h" +#include "legacy/device/device.h" +#include "legacy/volume/volume.h" +#include "legacy/utils/bio.h" +#include "legacy/utils/string.h" +#include "legacy/log/log.h" // Only to import the definition of struct sflc_volume #include "sflc.h" @@ -46,19 +46,19 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static int sfold_tgt_map(struct dm_target *ti, struct bio *bio); -static void sfold_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits); -static int sfold_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, +static int sflegc_tgt_map(struct dm_target *ti, struct bio *bio); +static void sflegc_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits); +static int sflegc_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data); /***************************************************** * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -struct target_type sfold_target_type = { - .map = sfold_tgt_map, - .io_hints = sfold_tgt_ioHints, - .iterate_devices = sfold_tgt_iterateDevices, +struct target_type sflegc_target_type = { + .map = sflegc_tgt_map, + .io_hints = sflegc_tgt_ioHints, + .iterate_devices = sflegc_tgt_iterateDevices, }; /***************************************************** @@ -67,17 +67,17 @@ struct target_type sfold_target_type = { /* Callback for every bio submitted to our virtual block device */ -static int sfold_tgt_map(struct dm_target *ti, struct bio *bio) +static int sflegc_tgt_map(struct dm_target *ti, struct bio *bio) { int err; struct sflc_volume *top_vol = ti->private; - sfold_Volume *vol = top_vol->sfold_vol; + sflegc_Volume *vol = top_vol->sflegc_vol; /* If no data, just quickly remap the sector and the block device (no crypto) */ /* TODO: this is dangerous for deniability, will need more filtering */ if (unlikely(!bio_has_data(bio))) { pr_debug("No-data bio: bio_op = %d", bio_op(bio)); - err = sfold_vol_remapBioFast(vol, bio); + err = sflegc_vol_remapBioFast(vol, bio); if (err) { pr_err("Could not remap bio; error %d\n", err); return DM_MAPIO_KILL; @@ -89,23 +89,23 @@ static int sfold_tgt_map(struct dm_target *ti, struct bio *bio) /* TODO: I think we can get rid of all of them */ /* Check that it is properly aligned and it doesn't cross vector boundaries */ - if (unlikely(!sfold_bio_isAligned(bio))) { + if (unlikely(!sflegc_bio_isAligned(bio))) { pr_err("Unaligned bio!\n"); return DM_MAPIO_KILL; } /* If it contains more than one SFLC sector, complain with the DM layer and continue */ - if (unlikely(bio->bi_iter.bi_size > SFOLD_DEV_SECTOR_SIZE)) { + if (unlikely(bio->bi_iter.bi_size > SFLEGC_DEV_SECTOR_SIZE)) { pr_notice("Large bio of size %u\n", bio->bi_iter.bi_size); - dm_accept_partial_bio(bio, SFOLD_DEV_SECTOR_SCALE); + dm_accept_partial_bio(bio, SFLEGC_DEV_SECTOR_SCALE); } /* Check that it contains exactly one SFLC sector */ - if (unlikely(bio->bi_iter.bi_size != SFOLD_DEV_SECTOR_SIZE)) { + if (unlikely(bio->bi_iter.bi_size != SFLEGC_DEV_SECTOR_SIZE)) { pr_err("Wrong length (%u) of bio\n", bio->bi_iter.bi_size); return DM_MAPIO_KILL; } /* Now it is safe, process it */ - err = sfold_vol_processBio(vol, bio); + err = sflegc_vol_processBio(vol, bio); if (err) { pr_err("Could not enqueue bio\n"); return DM_MAPIO_KILL; @@ -115,29 +115,29 @@ static int sfold_tgt_map(struct dm_target *ti, struct bio *bio) } /* Callback executed to inform the DM about our 4096-byte sector size */ -static void sfold_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits) +static void sflegc_tgt_ioHints(struct dm_target *ti, struct queue_limits *limits) { struct sflc_volume *top_vol = ti->private; - sfold_Volume *vol = top_vol->sfold_vol; + sflegc_Volume *vol = top_vol->sflegc_vol; pr_info("Called io_hints on volume \"%s\"\n", vol->vol_name); - limits->logical_block_size = SFOLD_DEV_SECTOR_SIZE; - limits->physical_block_size = SFOLD_DEV_SECTOR_SIZE; + limits->logical_block_size = SFLEGC_DEV_SECTOR_SIZE; + limits->physical_block_size = SFLEGC_DEV_SECTOR_SIZE; - limits->io_min = SFOLD_DEV_SECTOR_SIZE; - limits->io_opt = SFOLD_DEV_SECTOR_SIZE; + limits->io_min = SFLEGC_DEV_SECTOR_SIZE; + limits->io_opt = SFLEGC_DEV_SECTOR_SIZE; return; } /* Callback needed for God knows what, otherwise io_hints never gets called */ -static int sfold_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, +static int sflegc_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) { struct sflc_volume *top_vol = ti->private; - sfold_Volume *vol = top_vol->sfold_vol; - sfold_Device * dev = vol->dev; + sflegc_Volume *vol = top_vol->sflegc_vol; + sflegc_Device * dev = vol->dev; pr_debug("Called iterate_devices on volume \"%s\"\n", vol->vol_name); @@ -145,6 +145,6 @@ static int sfold_tgt_iterateDevices(struct dm_target *ti, iterate_devices_callou return -EINVAL; } return fn(ti, vol->dev->bdev, 0, - (dev->dev_header_size + dev->tot_slices * SFOLD_DEV_PHYS_SLICE_SIZE) * SFOLD_DEV_SECTOR_SCALE, + (dev->dev_header_size + dev->tot_slices * SFLEGC_DEV_PHYS_SLICE_SIZE) * SFLEGC_DEV_SECTOR_SCALE, data); } diff --git a/dm-sflc/src/legacy/utils/bio.c b/dm-sflc/src/legacy/utils/bio.c index 6607f89..ff2ce25 100644 --- a/dm-sflc/src/legacy/utils/bio.c +++ b/dm-sflc/src/legacy/utils/bio.c @@ -29,8 +29,8 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/utils/bio.h" -#include "old/log/log.h" +#include "legacy/utils/bio.h" +#include "legacy/log/log.h" /***************************************************** @@ -41,7 +41,7 @@ * Checks whether each of the bio's segments contains a whole * number of 4096-byte sectors. */ -bool sfold_bio_isAligned(struct bio * bio) +bool sflegc_bio_isAligned(struct bio * bio) { bool ret = true; @@ -51,12 +51,12 @@ bool sfold_bio_isAligned(struct bio * bio) return false; } /* Unlikely because we tell the DM layer about our sector size */ - if (unlikely(bio->bi_iter.bi_size % SFOLD_DEV_SECTOR_SIZE != 0)) { + if (unlikely(bio->bi_iter.bi_size % SFLEGC_DEV_SECTOR_SIZE != 0)) { pr_err("Abnormal bi_size = %u\n", bio->bi_iter.bi_size); return false; } /* Unlikely because we tell the DM layer about our sector size */ - if (unlikely(bio->bi_iter.bi_sector % SFOLD_DEV_SECTOR_SCALE != 0)) { + if (unlikely(bio->bi_iter.bi_sector % SFLEGC_DEV_SECTOR_SCALE != 0)) { pr_err("Abnormal bi_sector = %llu\n", bio->bi_iter.bi_sector); return false; } @@ -64,7 +64,7 @@ bool sfold_bio_isAligned(struct bio * bio) struct bio_vec bvl; struct bvec_iter iter; bio_for_each_segment(bvl, bio, iter) { - if ((bvl.bv_len == 0) || (bvl.bv_len % SFOLD_DEV_SECTOR_SIZE != 0)) { + if ((bvl.bv_len == 0) || (bvl.bv_len % SFLEGC_DEV_SECTOR_SIZE != 0)) { pr_err("Abnormal vector: bv_len = %u\n", bvl.bv_len); ret = false; } diff --git a/dm-sflc/src/legacy/utils/bio.h b/dm-sflc/src/legacy/utils/bio.h index 51917e6..b5af43d 100644 --- a/dm-sflc/src/legacy/utils/bio.h +++ b/dm-sflc/src/legacy/utils/bio.h @@ -25,14 +25,14 @@ * A collection of utility bio functions */ -#ifndef _SFOLD_UTILS_BIO_H_ -#define _SFOLD_UTILS_BIO_H_ +#ifndef _SFLEGC_UTILS_BIO_H_ +#define _SFLEGC_UTILS_BIO_H_ /***************************************************** * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" +#include "legacy/device/device.h" /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * @@ -42,7 +42,7 @@ * Checks whether each of the bio's segments contains a whole * number of 4096-byte sectors. */ -bool sfold_bio_isAligned(struct bio * bio); +bool sflegc_bio_isAligned(struct bio * bio); -#endif /* _SFOLD_UTILS_BIO_H_ */ +#endif /* _SFLEGC_UTILS_BIO_H_ */ diff --git a/dm-sflc/src/legacy/utils/pools.c b/dm-sflc/src/legacy/utils/pools.c index 5e67faf..c141729 100644 --- a/dm-sflc/src/legacy/utils/pools.c +++ b/dm-sflc/src/legacy/utils/pools.c @@ -29,100 +29,100 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/utils/pools.h" -#include "old/log/log.h" +#include "legacy/utils/pools.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ /* Pool sizes */ -#define SFOLD_POOLS_BIOSET_POOL_SIZE 1024 -#define SFOLD_POOLS_PAGE_POOL_SIZE 1024 -#define SFOLD_POOLS_WRITE_WORK_POOL_SIZE 1024 -#define SFOLD_POOLS_DECRYPT_WORK_POOL_SIZE 1024 +#define SFLEGC_POOLS_BIOSET_POOL_SIZE 1024 +#define SFLEGC_POOLS_PAGE_POOL_SIZE 1024 +#define SFLEGC_POOLS_WRITE_WORK_POOL_SIZE 1024 +#define SFLEGC_POOLS_DECRYPT_WORK_POOL_SIZE 1024 /* Slab cache names */ -#define SFOLD_POOLS_WRITE_WORK_SLAB_NAME "sfold_write_work_slab" -#define SFOLD_POOLS_DECRYPT_WORK_SLAB_NAME "sfold_decrypt_work_slab" -#define SFOLD_POOLS_IV_SLAB_NAME "sfold_iv_slab" +#define SFLEGC_POOLS_WRITE_WORK_SLAB_NAME "sflegc_write_work_slab" +#define SFLEGC_POOLS_DECRYPT_WORK_SLAB_NAME "sflegc_decrypt_work_slab" +#define SFLEGC_POOLS_IV_SLAB_NAME "sflegc_iv_slab" /***************************************************** * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -struct bio_set sfold_pools_bioset; -mempool_t * sfold_pools_pagePool; -mempool_t * sfold_pools_writeWorkPool; -mempool_t * sfold_pools_decryptWorkPool; -struct kmem_cache * sfold_pools_ivSlab; +struct bio_set sflegc_pools_bioset; +mempool_t * sflegc_pools_pagePool; +mempool_t * sflegc_pools_writeWorkPool; +mempool_t * sflegc_pools_decryptWorkPool; +struct kmem_cache * sflegc_pools_ivSlab; /***************************************************** * PRIVATE VARIABLES * *****************************************************/ -static struct kmem_cache * sfold_pools_writeWorkSlab; -static struct kmem_cache * sfold_pools_decryptWorkSlab; +static struct kmem_cache * sflegc_pools_writeWorkSlab; +static struct kmem_cache * sflegc_pools_decryptWorkSlab; /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sfold_pools_init(void) +int sflegc_pools_init(void) { int err; /* Memory pools: bioset */ - err = bioset_init(&sfold_pools_bioset, SFOLD_POOLS_BIOSET_POOL_SIZE, 0, BIOSET_NEED_BVECS); + err = bioset_init(&sflegc_pools_bioset, SFLEGC_POOLS_BIOSET_POOL_SIZE, 0, BIOSET_NEED_BVECS); if (err) { pr_err("Could not init bioset: error %d\n", err); goto err_bioset; } /* Memory pools: page_pool */ - sfold_pools_pagePool = mempool_create_page_pool(SFOLD_POOLS_PAGE_POOL_SIZE, 0); - if (!sfold_pools_pagePool) { + sflegc_pools_pagePool = mempool_create_page_pool(SFLEGC_POOLS_PAGE_POOL_SIZE, 0); + if (!sflegc_pools_pagePool) { pr_err("Could not create page pool\n"); err = -ENOMEM; goto err_pagepool; } /* Memory pools: writeWork slab cache */ - sfold_pools_writeWorkSlab = kmem_cache_create(SFOLD_POOLS_WRITE_WORK_SLAB_NAME, sizeof(sfold_vol_WriteWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sfold_pools_writeWorkSlab)) { - err = PTR_ERR(sfold_pools_writeWorkSlab); + sflegc_pools_writeWorkSlab = kmem_cache_create(SFLEGC_POOLS_WRITE_WORK_SLAB_NAME, sizeof(sflegc_vol_WriteWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sflegc_pools_writeWorkSlab)) { + err = PTR_ERR(sflegc_pools_writeWorkSlab); pr_err("Could not create writeWork slab cache; error %d\n", err); goto err_create_write_work_slab; } /* Memory pools: writeWork pool */ - sfold_pools_writeWorkPool = mempool_create_slab_pool(SFOLD_POOLS_WRITE_WORK_POOL_SIZE, sfold_pools_writeWorkSlab); - if (!sfold_pools_writeWorkPool) { + sflegc_pools_writeWorkPool = mempool_create_slab_pool(SFLEGC_POOLS_WRITE_WORK_POOL_SIZE, sflegc_pools_writeWorkSlab); + if (!sflegc_pools_writeWorkPool) { pr_err("Could not create writeWork pool\n"); err = -ENOMEM; goto err_write_work_pool; } /* Memory pools: decryptWork slab cache */ - sfold_pools_decryptWorkSlab = kmem_cache_create(SFOLD_POOLS_DECRYPT_WORK_SLAB_NAME, sizeof(sfold_vol_DecryptWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sfold_pools_decryptWorkSlab)) { - err = PTR_ERR(sfold_pools_decryptWorkSlab); + sflegc_pools_decryptWorkSlab = kmem_cache_create(SFLEGC_POOLS_DECRYPT_WORK_SLAB_NAME, sizeof(sflegc_vol_DecryptWork), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sflegc_pools_decryptWorkSlab)) { + err = PTR_ERR(sflegc_pools_decryptWorkSlab); pr_err("Could not create decryptWork slab cache; error %d\n", err); goto err_create_decrypt_work_slab; } /* Memory pools: decryptWork pool */ - sfold_pools_decryptWorkPool = mempool_create_slab_pool(SFOLD_POOLS_DECRYPT_WORK_POOL_SIZE, sfold_pools_decryptWorkSlab); - if (!sfold_pools_decryptWorkPool) { + sflegc_pools_decryptWorkPool = mempool_create_slab_pool(SFLEGC_POOLS_DECRYPT_WORK_POOL_SIZE, sflegc_pools_decryptWorkSlab); + if (!sflegc_pools_decryptWorkPool) { pr_err("Could not create decryptWork pool\n"); err = -ENOMEM; goto err_decrypt_work_pool; } /* Memory pools: IV slab cache */ - sfold_pools_ivSlab = kmem_cache_create(SFOLD_POOLS_IV_SLAB_NAME, sizeof(sfold_dev_IvCacheEntry), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); - if (IS_ERR(sfold_pools_ivSlab)) { - err = PTR_ERR(sfold_pools_ivSlab); + sflegc_pools_ivSlab = kmem_cache_create(SFLEGC_POOLS_IV_SLAB_NAME, sizeof(sflegc_dev_IvCacheEntry), 0, SLAB_POISON | SLAB_RED_ZONE, NULL); + if (IS_ERR(sflegc_pools_ivSlab)) { + err = PTR_ERR(sflegc_pools_ivSlab); pr_err("Could not create IV slab cache; error %d\n", err); goto err_create_iv_slab; } @@ -131,28 +131,28 @@ int sfold_pools_init(void) err_create_iv_slab: - mempool_destroy(sfold_pools_decryptWorkPool); + mempool_destroy(sflegc_pools_decryptWorkPool); err_decrypt_work_pool: - kmem_cache_destroy(sfold_pools_decryptWorkSlab); + kmem_cache_destroy(sflegc_pools_decryptWorkSlab); err_create_decrypt_work_slab: - mempool_destroy(sfold_pools_writeWorkPool); + mempool_destroy(sflegc_pools_writeWorkPool); err_write_work_pool: - kmem_cache_destroy(sfold_pools_writeWorkSlab); + kmem_cache_destroy(sflegc_pools_writeWorkSlab); err_create_write_work_slab: - mempool_destroy(sfold_pools_pagePool); + mempool_destroy(sflegc_pools_pagePool); err_pagepool: - bioset_exit(&sfold_pools_bioset); + bioset_exit(&sflegc_pools_bioset); err_bioset: return err; } -void sfold_pools_exit(void) +void sflegc_pools_exit(void) { - kmem_cache_destroy(sfold_pools_ivSlab); - mempool_destroy(sfold_pools_decryptWorkPool); - kmem_cache_destroy(sfold_pools_decryptWorkSlab); - mempool_destroy(sfold_pools_writeWorkPool); - kmem_cache_destroy(sfold_pools_writeWorkSlab); - mempool_destroy(sfold_pools_pagePool); - bioset_exit(&sfold_pools_bioset); + kmem_cache_destroy(sflegc_pools_ivSlab); + mempool_destroy(sflegc_pools_decryptWorkPool); + kmem_cache_destroy(sflegc_pools_decryptWorkSlab); + mempool_destroy(sflegc_pools_writeWorkPool); + kmem_cache_destroy(sflegc_pools_writeWorkSlab); + mempool_destroy(sflegc_pools_pagePool); + bioset_exit(&sflegc_pools_bioset); } diff --git a/dm-sflc/src/legacy/utils/pools.h b/dm-sflc/src/legacy/utils/pools.h index b87d6c6..46427d5 100644 --- a/dm-sflc/src/legacy/utils/pools.h +++ b/dm-sflc/src/legacy/utils/pools.h @@ -25,31 +25,31 @@ * A set of memory pools */ -#ifndef _SFOLD_UTILS_POOLS_H_ -#define _SFOLD_UTILS_POOLS_H_ +#ifndef _SFLEGC_UTILS_POOLS_H_ +#define _SFLEGC_UTILS_POOLS_H_ /***************************************************** * INCLUDE SECTION * *****************************************************/ -#include "old/device/device.h" +#include "legacy/device/device.h" /***************************************************** * PUBLIC VARIABLES DECLARATIONS * *****************************************************/ -extern struct bio_set sfold_pools_bioset; -extern mempool_t * sfold_pools_pagePool; -extern mempool_t * sfold_pools_writeWorkPool; -extern mempool_t * sfold_pools_decryptWorkPool; -extern struct kmem_cache * sfold_pools_ivSlab; +extern struct bio_set sflegc_pools_bioset; +extern mempool_t * sflegc_pools_pagePool; +extern mempool_t * sflegc_pools_writeWorkPool; +extern mempool_t * sflegc_pools_decryptWorkPool; +extern struct kmem_cache * sflegc_pools_ivSlab; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sfold_pools_init(void); -void sfold_pools_exit(void); +int sflegc_pools_init(void); +void sflegc_pools_exit(void); -#endif /* _SFOLD_UTILS_POOLS_H_ */ +#endif /* _SFLEGC_UTILS_POOLS_H_ */ diff --git a/dm-sflc/src/legacy/utils/string.c b/dm-sflc/src/legacy/utils/string.c index 8d533c6..cd231a2 100644 --- a/dm-sflc/src/legacy/utils/string.c +++ b/dm-sflc/src/legacy/utils/string.c @@ -31,15 +31,15 @@ #include -#include "old/utils/string.h" -#include "old/log/log.h" +#include "legacy/utils/string.h" +#include "legacy/log/log.h" /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sfold_str_hexDecode(char * hex, u8 * bin) +int sflegc_str_hexDecode(char * hex, u8 * bin) { char buf[3]; unsigned len; @@ -64,7 +64,7 @@ int sfold_str_hexDecode(char * hex, u8 * bin) } -void sfold_str_replaceAll(char * str, char old, char new) +void sflegc_str_replaceAll(char * str, char old, char new) { int i; diff --git a/dm-sflc/src/legacy/utils/string.h b/dm-sflc/src/legacy/utils/string.h index 98e0565..40aa5c1 100644 --- a/dm-sflc/src/legacy/utils/string.h +++ b/dm-sflc/src/legacy/utils/string.h @@ -25,8 +25,8 @@ * A collection of utility string functions */ -#ifndef _SFOLD_UTILS_STRING_H_ -#define _SFOLD_UTILS_STRING_H_ +#ifndef _SFLEGC_UTILS_STRING_H_ +#define _SFLEGC_UTILS_STRING_H_ /***************************************************** * INCLUDE SECTION * @@ -38,8 +38,8 @@ * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sfold_str_hexDecode(char * hex, u8 * bin); -void sfold_str_replaceAll(char * str, char old, char new); +int sflegc_str_hexDecode(char * hex, u8 * bin); +void sflegc_str_replaceAll(char * str, char old, char new); -#endif /* _SFOLD_UTILS_STRING_H_ */ +#endif /* _SFLEGC_UTILS_STRING_H_ */ diff --git a/dm-sflc/src/legacy/utils/vector.c b/dm-sflc/src/legacy/utils/vector.c index 4c73814..536997d 100644 --- a/dm-sflc/src/legacy/utils/vector.c +++ b/dm-sflc/src/legacy/utils/vector.c @@ -29,9 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/utils/vector.h" -#include "old/crypto/rand/rand.h" -#include "old/log/log.h" +#include "legacy/utils/vector.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/log/log.h" /***************************************************** @@ -39,13 +39,13 @@ *****************************************************/ /* Shuffle a vector of u32's with the Fisher-Yates algorithm */ -int sfold_vec_u32shuffle(u32 *v, u32 len) +int sflegc_vec_u32shuffle(u32 *v, u32 len) { u32 i; for (i = len-1; i >= 1; i--) { /* Sample a random index from 0 to i (inclusive) */ - s32 j = sfold_rand_uniform(i+1); + s32 j = sflegc_rand_uniform(i+1); if (j < 0) { pr_err("Could not sample j; error %d", j); return j; diff --git a/dm-sflc/src/legacy/utils/vector.h b/dm-sflc/src/legacy/utils/vector.h index ae75ca0..a734cfc 100644 --- a/dm-sflc/src/legacy/utils/vector.h +++ b/dm-sflc/src/legacy/utils/vector.h @@ -25,8 +25,8 @@ * A collection of utility vector functions */ -#ifndef _SFOLD_UTILS_VECTOR_H_ -#define _SFOLD_UTILS_VECTOR_H_ +#ifndef _SFLEGC_UTILS_VECTOR_H_ +#define _SFLEGC_UTILS_VECTOR_H_ /***************************************************** * INCLUDE SECTION * @@ -39,7 +39,7 @@ *****************************************************/ /* Shuffle a vector of u32's */ -int sfold_vec_u32shuffle(u32 *v, u32 len); +int sflegc_vec_u32shuffle(u32 *v, u32 len); -#endif /* _SFOLD_UTILS_VECTOR_H_ */ +#endif /* _SFLEGC_UTILS_VECTOR_H_ */ diff --git a/dm-sflc/src/legacy/utils/workqueues.c b/dm-sflc/src/legacy/utils/workqueues.c index 942b765..d069027 100644 --- a/dm-sflc/src/legacy/utils/workqueues.c +++ b/dm-sflc/src/legacy/utils/workqueues.c @@ -28,42 +28,42 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/utils/workqueues.h" -#include "old/log/log.h" +#include "legacy/utils/workqueues.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * *****************************************************/ -#define SFOLD_QUEUES_WRITE_WQ_NAME "sfold_write_workqueue" -#define SFOLD_QUEUES_DECRYPT_WQ_NAME "sfold_decrypt_workqueue" +#define SFLEGC_QUEUES_WRITE_WQ_NAME "sflegc_write_workqueue" +#define SFLEGC_QUEUES_DECRYPT_WQ_NAME "sflegc_decrypt_workqueue" /***************************************************** * PUBLIC VARIABLES DEFINITIONS * *****************************************************/ -struct workqueue_struct * sfold_queues_writeQueue; -struct workqueue_struct * sfold_queues_decryptQueue; +struct workqueue_struct * sflegc_queues_writeQueue; +struct workqueue_struct * sflegc_queues_decryptQueue; /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -int sfold_queues_init(void) +int sflegc_queues_init(void) { int err; /* Write workqueue */ - sfold_queues_writeQueue = create_workqueue(SFOLD_QUEUES_WRITE_WQ_NAME); - if (!sfold_queues_writeQueue) { + sflegc_queues_writeQueue = create_workqueue(SFLEGC_QUEUES_WRITE_WQ_NAME); + if (!sflegc_queues_writeQueue) { pr_err("Could not create write workqueue\n"); err = -ENOMEM; goto err_write_queue; } /* Decrypt workqueue */ - sfold_queues_decryptQueue = create_workqueue(SFOLD_QUEUES_DECRYPT_WQ_NAME); - if (!sfold_queues_decryptQueue) { + sflegc_queues_decryptQueue = create_workqueue(SFLEGC_QUEUES_DECRYPT_WQ_NAME); + if (!sflegc_queues_decryptQueue) { pr_err("Could not create decrypt workqueue\n"); err = -ENOMEM; goto err_decrypt_queue; @@ -73,13 +73,13 @@ int sfold_queues_init(void) err_decrypt_queue: - destroy_workqueue(sfold_queues_writeQueue); + destroy_workqueue(sflegc_queues_writeQueue); err_write_queue: return err; } -void sfold_queues_exit(void) +void sflegc_queues_exit(void) { - destroy_workqueue(sfold_queues_decryptQueue); - destroy_workqueue(sfold_queues_writeQueue); + destroy_workqueue(sflegc_queues_decryptQueue); + destroy_workqueue(sflegc_queues_writeQueue); } diff --git a/dm-sflc/src/legacy/utils/workqueues.h b/dm-sflc/src/legacy/utils/workqueues.h index 3026a6d..8c04dcd 100644 --- a/dm-sflc/src/legacy/utils/workqueues.h +++ b/dm-sflc/src/legacy/utils/workqueues.h @@ -25,8 +25,8 @@ * A set of workqueues */ -#ifndef _SFOLD_UTILS_QUEUES_H_ -#define _SFOLD_UTILS_QUEUES_H_ +#ifndef _SFLEGC_UTILS_QUEUES_H_ +#define _SFLEGC_UTILS_QUEUES_H_ /***************************************************** * INCLUDE SECTION * @@ -38,15 +38,15 @@ * PUBLIC VARIABLES DECLARATIONS * *****************************************************/ -extern struct workqueue_struct * sfold_queues_writeQueue; -extern struct workqueue_struct * sfold_queues_decryptQueue; +extern struct workqueue_struct * sflegc_queues_writeQueue; +extern struct workqueue_struct * sflegc_queues_decryptQueue; /***************************************************** * PUBLIC FUNCTIONS PROTOTYPES * *****************************************************/ -int sfold_queues_init(void); -void sfold_queues_exit(void); +int sflegc_queues_init(void); +void sflegc_queues_exit(void); -#endif /* _SFOLD_UTILS_QUEUES_H_ */ +#endif /* _SFLEGC_UTILS_QUEUES_H_ */ diff --git a/dm-sflc/src/legacy/volume/fmap.c b/dm-sflc/src/legacy/volume/fmap.c index 63b837b..c0ca78c 100644 --- a/dm-sflc/src/legacy/volume/fmap.c +++ b/dm-sflc/src/legacy/volume/fmap.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/volume/volume.h" -#include "old/crypto/rand/rand.h" -#include "old/utils/pools.h" -#include "old/log/log.h" +#include "legacy/volume/volume.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/utils/pools.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * @@ -42,7 +42,7 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op); +static s32 sflegc_vol_mapSlice(sflegc_Volume * vol, u32 lsi, int op); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * @@ -50,28 +50,28 @@ static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op); /* Maps a logical 512-byte sector to a physical 512-byte sector. Returns < 0 if error. * Specifically, if op == READ, and the logical slice is unmapped, -ENXIO is returned. */ -s64 sfold_vol_remapSector(sfold_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out) +s64 sflegc_vol_remapSector(sflegc_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out) { u32 lsi; u32 off_in_slice; s32 psi; sector_t phys_sector; - sfold_Device *dev = vol->dev; + sflegc_Device *dev = vol->dev; /* Start by scaling down to a Shufflecake sector */ - log_sector /= SFOLD_DEV_SECTOR_SCALE; + log_sector /= SFLEGC_DEV_SECTOR_SCALE; /* Get the logical slice index it belongs to */ - lsi = log_sector / SFOLD_VOL_LOG_SLICE_SIZE; + lsi = log_sector / SFLEGC_VOL_LOG_SLICE_SIZE; /* Get which block it is within the slice */ - off_in_slice = log_sector % SFOLD_VOL_LOG_SLICE_SIZE; + off_in_slice = log_sector % SFLEGC_VOL_LOG_SLICE_SIZE; /* Output the off_in_slice */ if (off_in_slice_out) { *off_in_slice_out = off_in_slice; } /* Map it to a physical slice */ - psi = sfold_vol_mapSlice(vol, lsi, op); + psi = sflegc_vol_mapSlice(vol, lsi, op); /* -ENXIO is a special case */ if (psi == -ENXIO) { pr_debug("mapSlice returned -ENXIO: stupid READ\n"); @@ -88,20 +88,20 @@ s64 sfold_vol_remapSector(sfold_Volume * vol, sector_t log_sector, int op, u32 * } /* Get the physical sector (the first of every slice contains the IVs) */ - phys_sector = ((sector_t)psi * SFOLD_DEV_PHYS_SLICE_SIZE) + 1 + off_in_slice; + phys_sector = ((sector_t)psi * SFLEGC_DEV_PHYS_SLICE_SIZE) + 1 + off_in_slice; /* Add the device header */ phys_sector += dev->dev_header_size; /* Scale it back up to a kernel sector */ - phys_sector *= SFOLD_DEV_SECTOR_SCALE; + phys_sector *= SFLEGC_DEV_SECTOR_SCALE; return phys_sector; } /* Loads (and decrypts) the position map from the volume's header */ -int sfold_vol_loadFmap(sfold_Volume * vol) +int sflegc_vol_loadFmap(sflegc_Volume * vol) { - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; sector_t sector; struct page * iv_page; u8 * iv_ptr; @@ -111,12 +111,12 @@ int sfold_vol_loadFmap(sfold_Volume * vol) int err; /* Allocate pages */ - iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + iv_page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!iv_page) { pr_err("Could not allocate IV page\n"); return -ENOMEM; } - data_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + data_page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!data_page) { pr_err("Could not allocate data page\n"); return -ENOMEM; @@ -145,7 +145,7 @@ int sfold_vol_loadFmap(sfold_Volume * vol) int i; for (i = 0; i < dev->vol_header_nr_iv_blocks && lsi < dev->tot_slices; i++) { /* Load the IV block */ - err = sfold_dev_rwSector(dev, iv_page, sector, READ); + err = sflegc_dev_rwSector(dev, iv_page, sector, READ); if (err) { pr_err("Could not read IV block i=%d at sector %llu; error %d\n", i, sector, err); goto out; @@ -154,9 +154,9 @@ int sfold_vol_loadFmap(sfold_Volume * vol) /* Loop over the 256 data blocks */ int j; - for (j = 0; j < SFOLD_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { + for (j = 0; j < SFLEGC_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { /* Load the data block */ - err = sfold_dev_rwSector(dev, data_page, sector, READ); + err = sflegc_dev_rwSector(dev, data_page, sector, READ); if (err) { pr_err("Could not read data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -164,7 +164,7 @@ int sfold_vol_loadFmap(sfold_Volume * vol) sector += 1; /* Decrypt it in place */ - err = sfold_sk_decrypt(vol->skctx, data_ptr, data_ptr, SFOLD_DEV_SECTOR_SIZE, (iv_ptr + j*SFOLD_SK_IV_LEN)); + err = sflegc_sk_decrypt(vol->skctx, data_ptr, data_ptr, SFLEGC_DEV_SECTOR_SIZE, (iv_ptr + j*SFLEGC_SK_IV_LEN)); if (err) { pr_err("Could not decrypt data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -172,7 +172,7 @@ int sfold_vol_loadFmap(sfold_Volume * vol) /* Loop over the 1024 fmap entries in this data block */ int k; - for (k = 0; k < SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { + for (k = 0; k < SFLEGC_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { /* An entry is just a single big-endian PSI, the LSI is implicitly the index of this entry */ __be32 * be_psi = (void *) (data_ptr + (k * sizeof(__be32))); @@ -181,8 +181,8 @@ int sfold_vol_loadFmap(sfold_Volume * vol) /* Add mapping to the volume's fmap */ vol->fmap[lsi] = psi; /* Also add it to the device's rmap and to the count, if LSI is actually mapped */ - if (psi != SFOLD_VOL_FMAP_INVALID_PSI) { - sfold_dev_markPsiTaken(dev, psi, vol->vol_idx); + if (psi != SFLEGC_VOL_FMAP_INVALID_PSI) { + sflegc_dev_markPsiTaken(dev, psi, vol->vol_idx); vol->mapped_slices += 1; } @@ -202,16 +202,16 @@ out: kunmap(iv_page); kunmap(data_page); /* Free them */ - mempool_free(iv_page, sfold_pools_pagePool); - mempool_free(data_page, sfold_pools_pagePool); + mempool_free(iv_page, sflegc_pools_pagePool); + mempool_free(data_page, sflegc_pools_pagePool); return err; } /* Stores (and encrypts) the position map to the volume's header */ -int sfold_vol_storeFmap(sfold_Volume * vol) +int sflegc_vol_storeFmap(sflegc_Volume * vol) { - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; sector_t sector; struct page * iv_page; u8 * iv_ptr; @@ -221,12 +221,12 @@ int sfold_vol_storeFmap(sfold_Volume * vol) int err; /* Allocate pages */ - iv_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + iv_page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!iv_page) { pr_err("Could not allocate IV page\n"); return -ENOMEM; } - data_page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + data_page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!data_page) { pr_err("Could not allocate data page\n"); return -ENOMEM; @@ -255,13 +255,13 @@ int sfold_vol_storeFmap(sfold_Volume * vol) int i; for (i = 0; i < dev->vol_header_nr_iv_blocks && lsi < dev->tot_slices; i++) { /* Fill the IV block with random bytes */ - err = sfold_rand_getBytes(iv_ptr, SFOLD_DEV_SECTOR_SIZE); + err = sflegc_rand_getBytes(iv_ptr, SFLEGC_DEV_SECTOR_SIZE); if (err) { pr_err("Could not sample random IV for block i=%d at sector %llu; error %d\n", i, sector, err); goto out; } /* Store it on the disk (before it gets changed by the encryption) */ - err = sfold_dev_rwSector(dev, iv_page, sector, WRITE); + err = sflegc_dev_rwSector(dev, iv_page, sector, WRITE); if (err) { pr_err("Could not read IV block i=%d at sector %llu; error %d\n", i, sector, err); goto out; @@ -270,10 +270,10 @@ int sfold_vol_storeFmap(sfold_Volume * vol) /* Loop over the 256 data blocks */ int j; - for (j = 0; j < SFOLD_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { + for (j = 0; j < SFLEGC_DEV_SECTOR_TO_IV_RATIO && lsi < dev->tot_slices; j++) { /* Loop over the 1024 fmap entries that fit in this data block */ int k; - for (k = 0; k < SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { + for (k = 0; k < SFLEGC_VOL_HEADER_MAPPINGS_PER_BLOCK && lsi < dev->tot_slices; k++) { /* Get the PSI for the current LSI */ u32 psi = vol->fmap[lsi]; /* Write it into the block as big-endian */ @@ -285,14 +285,14 @@ int sfold_vol_storeFmap(sfold_Volume * vol) } /* Encrypt it in place */ - err = sfold_sk_encrypt(vol->skctx, data_ptr, data_ptr, SFOLD_DEV_SECTOR_SIZE, (iv_ptr + j*SFOLD_SK_IV_LEN)); + err = sflegc_sk_encrypt(vol->skctx, data_ptr, data_ptr, SFLEGC_DEV_SECTOR_SIZE, (iv_ptr + j*SFLEGC_SK_IV_LEN)); if (err) { pr_err("Could not encrypt data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; } /* Store the data block */ - err = sfold_dev_rwSector(dev, data_page, sector, WRITE); + err = sflegc_dev_rwSector(dev, data_page, sector, WRITE); if (err) { pr_err("Could not write data block i=%d, j=%d at sector %llu; error %d\n", i, j, sector, err); goto out; @@ -311,8 +311,8 @@ out: kunmap(iv_page); kunmap(data_page); /* Free them */ - mempool_free(iv_page, sfold_pools_pagePool); - mempool_free(data_page, sfold_pools_pagePool); + mempool_free(iv_page, sflegc_pools_pagePool); + mempool_free(data_page, sflegc_pools_pagePool); return err; } @@ -321,10 +321,10 @@ out: * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op) +static s32 sflegc_vol_mapSlice(sflegc_Volume * vol, u32 lsi, int op) { s32 psi; - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; /* Lock the volume's forward map */ if (mutex_lock_interruptible(&vol->fmap_lock)) { @@ -333,7 +333,7 @@ static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op) } /* If slice is already mapped, just return the mapping */ - if (vol->fmap[lsi] != SFOLD_VOL_FMAP_INVALID_PSI) { + if (vol->fmap[lsi] != SFLEGC_VOL_FMAP_INVALID_PSI) { mutex_unlock(&vol->fmap_lock); return vol->fmap[lsi]; } @@ -354,7 +354,7 @@ static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op) } /* Get a free physical slice */ - psi = sfold_dev_getNextRandomFreePsi(dev); + psi = sflegc_dev_getNextRandomFreePsi(dev); if (psi < 0) { pr_err("Could not get a random free physical slice; error %d\n", psi); mutex_unlock(&dev->slices_lock); @@ -366,7 +366,7 @@ static s32 sfold_vol_mapSlice(sfold_Volume * vol, u32 lsi, int op) vol->fmap[lsi] = psi; vol->mapped_slices += 1; /* And in the device's rmap */ - sfold_dev_markPsiTaken(dev, psi, vol->vol_idx); + sflegc_dev_markPsiTaken(dev, psi, vol->vol_idx); /* Unlock both maps */ mutex_unlock(&dev->slices_lock); diff --git a/dm-sflc/src/legacy/volume/io.c b/dm-sflc/src/legacy/volume/io.c index 6296834..3f97de7 100644 --- a/dm-sflc/src/legacy/volume/io.c +++ b/dm-sflc/src/legacy/volume/io.c @@ -29,10 +29,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/volume/volume.h" -#include "old/utils/pools.h" -#include "old/utils/workqueues.h" -#include "old/log/log.h" +#include "legacy/volume/volume.h" +#include "legacy/utils/pools.h" +#include "legacy/utils/workqueues.h" +#include "legacy/log/log.h" /***************************************************** * CONSTANTS * @@ -47,7 +47,7 @@ *****************************************************/ /* Remaps the underlying block device and the sector number */ -int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio) +int sflegc_vol_remapBioFast(sflegc_Volume * vol, struct bio * bio) { s64 phys_sector; int err; @@ -56,7 +56,7 @@ int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio) bio_set_dev(bio, vol->dev->bdev->bdev); /* Remap the starting sector (we don't care about PSI and off_in_slice). Also no slice allocation */ - phys_sector = sfold_vol_remapSector(vol, bio->bi_iter.bi_sector, READ, NULL, NULL); + phys_sector = sflegc_vol_remapSector(vol, bio->bi_iter.bi_sector, READ, NULL, NULL); if (phys_sector < 0) { err = (int) phys_sector; pr_err("Could not remap sector; error %d\n", err); @@ -68,18 +68,18 @@ int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio) } /* Submits the bio to the device's workqueue */ -int sfold_vol_processBio(sfold_Volume * vol, struct bio * bio) +int sflegc_vol_processBio(sflegc_Volume * vol, struct bio * bio) { - sfold_vol_WriteWork * write_work; + sflegc_vol_WriteWork * write_work; /* If it is a READ, no need to pass it through a workqueue */ if (bio_data_dir(bio) == READ) { - sfold_vol_doRead(vol, bio); + sflegc_vol_doRead(vol, bio); return 0; } /* Allocate writeWork structure */ - write_work = mempool_alloc(sfold_pools_writeWorkPool, GFP_NOIO); + write_work = mempool_alloc(sflegc_pools_writeWorkPool, GFP_NOIO); if (!write_work) { pr_err("Failed allocation of work structure\n"); return -ENOMEM; @@ -88,10 +88,10 @@ int sfold_vol_processBio(sfold_Volume * vol, struct bio * bio) /* Set fields */ write_work->vol = vol; write_work->orig_bio = bio; - INIT_WORK(&write_work->work, sfold_vol_doWrite); + INIT_WORK(&write_work->work, sflegc_vol_doWrite); /* Enqueue */ - queue_work(sfold_queues_writeQueue, &write_work->work); + queue_work(sflegc_queues_writeQueue, &write_work->work); return 0; } diff --git a/dm-sflc/src/legacy/volume/read.c b/dm-sflc/src/legacy/volume/read.c index 30e2927..0fb424a 100644 --- a/dm-sflc/src/legacy/volume/read.c +++ b/dm-sflc/src/legacy/volume/read.c @@ -32,10 +32,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/volume/volume.h" -#include "old/utils/pools.h" -#include "old/utils/workqueues.h" -#include "old/log/log.h" +#include "legacy/volume/volume.h" +#include "legacy/utils/pools.h" +#include "legacy/utils/workqueues.h" +#include "legacy/log/log.h" #include @@ -47,28 +47,28 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static void sfold_vol_fillBioWithZeros(struct bio * orig_bio); -static void sfold_vol_readEndIo(struct bio * phys_bio); -static void sfold_vol_readEndIoBottomHalf(struct work_struct * work); -static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice); -static int sfold_vol_fetchIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); +static void sflegc_vol_fillBioWithZeros(struct bio * orig_bio); +static void sflegc_vol_readEndIo(struct bio * phys_bio); +static void sflegc_vol_readEndIoBottomHalf(struct work_struct * work); +static int sflegc_vol_decryptBio(sflegc_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice); +static int sflegc_vol_fetchIv(sflegc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); /***************************************************** * PUBLIC FUNCTIONS DEFINITIONS * *****************************************************/ -/* Executed in context from sfold_tgt_map() */ -void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio) +/* Executed in context from sflegc_tgt_map() */ +void sflegc_vol_doRead(sflegc_Volume * vol, struct bio * bio) { - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; struct bio * orig_bio = bio; struct bio * phys_bio; s64 phys_sector; - sfold_vol_DecryptWork * dec_work; + sflegc_vol_DecryptWork * dec_work; blk_status_t status; /* Allocate decryptWork structure */ - dec_work = mempool_alloc(sfold_pools_decryptWorkPool, GFP_NOIO); + dec_work = mempool_alloc(sflegc_pools_decryptWorkPool, GFP_NOIO); if (!dec_work) { pr_err("Could not allocate decryptWork structure\n"); status = BLK_STS_IOERR; @@ -83,7 +83,7 @@ void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio) we can decrypt in place. */ /* Shallow copy */ - phys_bio = bio_alloc_clone(dev->bdev->bdev, orig_bio, GFP_NOIO, &sfold_pools_bioset); + phys_bio = bio_alloc_clone(dev->bdev->bdev, orig_bio, GFP_NOIO, &sflegc_pools_bioset); if (!phys_bio) { pr_err("Could not clone original bio\n"); status = BLK_STS_IOERR; @@ -91,11 +91,11 @@ void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio) } /* Remap sector */ - phys_sector = sfold_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, READ, &dec_work->psi, &dec_work->off_in_slice); + phys_sector = sflegc_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, READ, &dec_work->psi, &dec_work->off_in_slice); /* If -ENXIO, special case: stupid READ */ if (phys_sector == -ENXIO) { pr_debug("Stupid READ. Returning all zeros\n"); - sfold_vol_fillBioWithZeros(orig_bio); + sflegc_vol_fillBioWithZeros(orig_bio); status = BLK_STS_OK; goto err_stupid_read; } @@ -113,7 +113,7 @@ void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio) dec_work->orig_bio = orig_bio; dec_work->phys_bio = phys_bio; /* Set fields for the endio */ - phys_bio->bi_end_io = sfold_vol_readEndIo; + phys_bio->bi_end_io = sflegc_vol_readEndIo; phys_bio->bi_private = dec_work; /* Only submit the physical bio */ @@ -127,7 +127,7 @@ err_stupid_read: bio_put(phys_bio); err_clone_orig_bio: bio_put(orig_bio); - mempool_free(dec_work, sfold_pools_decryptWorkPool); + mempool_free(dec_work, sflegc_pools_decryptWorkPool); err_alloc_dec_work: orig_bio->bi_status = status; @@ -139,42 +139,42 @@ err_alloc_dec_work: * PRIVATE FUNCTIONS DEFINITIONS * *****************************************************/ -static void sfold_vol_fillBioWithZeros(struct bio * orig_bio) +static void sflegc_vol_fillBioWithZeros(struct bio * orig_bio) { struct bio_vec bvl = bio_iovec(orig_bio); void * sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - memset(sector_ptr, 0, SFOLD_DEV_SECTOR_SIZE); - bio_advance(orig_bio, SFOLD_DEV_SECTOR_SIZE); + memset(sector_ptr, 0, SFLEGC_DEV_SECTOR_SIZE); + bio_advance(orig_bio, SFLEGC_DEV_SECTOR_SIZE); kunmap(bvl.bv_page); return; } /* Pushes all the decryption work to a workqueue bottom half */ -static void sfold_vol_readEndIo(struct bio * phys_bio) +static void sflegc_vol_readEndIo(struct bio * phys_bio) { - sfold_vol_DecryptWork * dec_work = phys_bio->bi_private; + sflegc_vol_DecryptWork * dec_work = phys_bio->bi_private; /* Init work structure */ - INIT_WORK(&dec_work->work, sfold_vol_readEndIoBottomHalf); + INIT_WORK(&dec_work->work, sflegc_vol_readEndIoBottomHalf); /* Enqueue it */ - queue_work(sfold_queues_decryptQueue, &dec_work->work); + queue_work(sflegc_queues_decryptQueue, &dec_work->work); return; } -static void sfold_vol_readEndIoBottomHalf(struct work_struct * work) +static void sflegc_vol_readEndIoBottomHalf(struct work_struct * work) { - sfold_vol_DecryptWork * dec_work = container_of(work, sfold_vol_DecryptWork, work); - sfold_Volume * vol = dec_work->vol; + sflegc_vol_DecryptWork * dec_work = container_of(work, sflegc_vol_DecryptWork, work); + sflegc_Volume * vol = dec_work->vol; struct bio * orig_bio = dec_work->orig_bio; struct bio * phys_bio = dec_work->phys_bio; blk_status_t status = phys_bio->bi_status; int err; /* Decrypt the physical bio and advance the original bio */ - err = sfold_vol_decryptBio(vol, orig_bio, dec_work->psi, dec_work->off_in_slice); + err = sflegc_vol_decryptBio(vol, orig_bio, dec_work->psi, dec_work->off_in_slice); if (err) { pr_err("Could not decrypt bio; error %d\n", err); status = BLK_STS_IOERR; @@ -189,19 +189,19 @@ static void sfold_vol_readEndIoBottomHalf(struct work_struct * work) /* Free the physical bio */ bio_put(phys_bio); /* Free the work item */ - mempool_free(dec_work, sfold_pools_decryptWorkPool); + mempool_free(dec_work, sflegc_pools_decryptWorkPool); return; } /* Decrypts the content of the physical bio, and at the same time it advances the original bio */ -static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice) +static int sflegc_vol_decryptBio(sflegc_Volume * vol, struct bio * orig_bio, u32 psi, u32 off_in_slice) { - u8 iv[SFOLD_SK_IV_LEN]; + u8 iv[SFLEGC_SK_IV_LEN]; int err; /* Fetch IV */ - err = sfold_vol_fetchIv(vol, iv, psi, off_in_slice); + err = sflegc_vol_fetchIv(vol, iv, psi, off_in_slice); if (err) { pr_err("Could not fetch IV; error %d\n", err); return err; @@ -210,7 +210,7 @@ static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 p /* Decrypt sector in place */ struct bio_vec bvl = bio_iovec(orig_bio); void * sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - err = sfold_sk_decrypt(vol->skctx, sector_ptr, sector_ptr, SFOLD_DEV_SECTOR_SIZE, iv); + err = sflegc_sk_decrypt(vol->skctx, sector_ptr, sector_ptr, SFLEGC_DEV_SECTOR_SIZE, iv); kunmap(bvl.bv_page); if (err) { pr_err("Error while decrypting sector: %d\n", err); @@ -218,19 +218,19 @@ static int sfold_vol_decryptBio(sfold_Volume * vol, struct bio * orig_bio, u32 p } /* Advance original bio by one sector */ - bio_advance(orig_bio, SFOLD_DEV_SECTOR_SIZE); + bio_advance(orig_bio, SFLEGC_DEV_SECTOR_SIZE); return 0; } -static int sfold_vol_fetchIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) +static int sflegc_vol_fetchIv(sflegc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) { - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; u8 * iv_block; int err; /* Acquire a reference to the whole relevant IV block */ - iv_block = sfold_dev_getIvBlockRef(dev, psi, READ); + iv_block = sflegc_dev_getIvBlockRef(dev, psi, READ); if (IS_ERR(iv_block)) { err = PTR_ERR(iv_block); pr_err("Could not acquire reference to IV block; error %ld\n", PTR_ERR(iv_block)); @@ -238,10 +238,10 @@ static int sfold_vol_fetchIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_sl } /* Copy the relevant portion */ - memcpy(iv, iv_block + (off_in_slice * SFOLD_SK_IV_LEN), SFOLD_SK_IV_LEN); + memcpy(iv, iv_block + (off_in_slice * SFLEGC_SK_IV_LEN), SFLEGC_SK_IV_LEN); /* Release reference to the IV block */ - err = sfold_dev_putIvBlockRef(dev, psi); + err = sflegc_dev_putIvBlockRef(dev, psi); if (err) { pr_err("Could not release reference to IV block; error %d\n", err); goto err_put_iv_block_ref; diff --git a/dm-sflc/src/legacy/volume/volume.c b/dm-sflc/src/legacy/volume/volume.c index 862d8f5..e229312 100644 --- a/dm-sflc/src/legacy/volume/volume.c +++ b/dm-sflc/src/legacy/volume/volume.c @@ -29,9 +29,9 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/volume/volume.h" -#include "old/utils/string.h" -#include "old/log/log.h" +#include "legacy/volume/volume.h" +#include "legacy/utils/string.h" +#include "legacy/log/log.h" #include @@ -50,18 +50,18 @@ * argv[4]: number of 1 MB slices in the underlying device * argv[5]: 32-byte encryption key (hex-encoded) */ -sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, +sflegc_Volume * sflegc_vol_create(struct dm_target * ti, sflegc_Device* dev, int argc, char **argv, struct kobject *kobj) { - sfold_Volume * vol; + sflegc_Volume * vol; int vol_idx; - u8 enckey[SFOLD_SK_KEY_LEN]; + u8 enckey[SFLEGC_SK_KEY_LEN]; int err; /* Allocate volume */ - vol = kzalloc(sizeof(sfold_Volume), GFP_KERNEL); + vol = kzalloc(sizeof(sflegc_Volume), GFP_KERNEL); if (!vol) { - pr_err("Could not allocate %lu bytes for sfold_Volume\n", sizeof(sfold_Volume)); + pr_err("Could not allocate %lu bytes for sflegc_Volume\n", sizeof(sflegc_Volume)); err = -ENOMEM; goto err_alloc_vol; } @@ -78,12 +78,12 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, goto err_parse; } /* Decode the encryption key */ - if (strlen(argv[5]) != 2 * SFOLD_SK_KEY_LEN) { + if (strlen(argv[5]) != 2 * SFLEGC_SK_KEY_LEN) { pr_err("Hexadecimal key (length %lu): %s\n", strlen(argv[5]), argv[5]); err = -EINVAL; goto err_parse; } - err = sfold_str_hexDecode(argv[5], enckey); + err = sflegc_str_hexDecode(argv[5], enckey); if (err) { pr_err("Could not decode hexadecimal encryption key"); err = -EINVAL; @@ -95,14 +95,14 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, /* Sysfs stuff */ vol->kobj_parent = kobj; - err = sfold_sysfs_add_volume(vol); + err = sflegc_sysfs_add_volume(vol); if (err) { pr_err("Could not add volume to sysfs; error %d\n", err); goto err_sysfs; } /* Backing device */ - if (!sfold_dev_addVolume(dev, vol, vol_idx)) { + if (!sflegc_dev_addVolume(dev, vol, vol_idx)) { pr_err("Could not add volume to device\n"); err = -EINVAL; goto err_add_to_dev; @@ -111,7 +111,7 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, vol->vol_idx = vol_idx; /* Crypto */ - vol->skctx = sfold_sk_createContext(enckey); + vol->skctx = sflegc_sk_createContext(enckey); if (IS_ERR(vol->skctx)) { err = PTR_ERR(vol->skctx); pr_err("Could not create crypto context\n"); @@ -132,7 +132,7 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, /* Initialise fmap */ pr_notice("Volume opening for volume %s: loading fmap from header\n", vol->vol_name); - err = sfold_vol_loadFmap(vol); + err = sflegc_vol_loadFmap(vol); if (err) { pr_err("Could not load position map; error %d\n", err); goto err_load_fmap; @@ -141,7 +141,7 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, /* Tell DM we want one SFLC sector at a time */ - ti->max_io_len = SFOLD_DEV_SECTOR_SCALE; + ti->max_io_len = SFLEGC_DEV_SECTOR_SCALE; /* Enable REQ_OP_FLUSH bios */ ti->num_flush_bios = 1; /* Disable REQ_OP_WRITE_ZEROES and REQ_OP_SECURE_ERASE (can't be passed through as @@ -158,11 +158,11 @@ sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, err_load_fmap: vfree(vol->fmap); err_alloc_fmap: - sfold_sk_destroyContext(vol->skctx); + sflegc_sk_destroyContext(vol->skctx); err_create_skctx: - sfold_dev_removeVolume(vol->dev, vol->vol_idx); + sflegc_dev_removeVolume(vol->dev, vol->vol_idx); err_add_to_dev: - sfold_sysfs_remove_volume(vol); + sflegc_sysfs_remove_volume(vol); err_sysfs: err_parse: kfree(vol); @@ -171,13 +171,13 @@ err_alloc_vol: } /* Removes the volume from the device and frees it. */ -void sfold_vol_destroy(sfold_Volume * vol) +void sflegc_vol_destroy(sflegc_Volume * vol) { int err; /* Store fmap */ pr_notice("Going to store position map of volume %s\n", vol->vol_name); - err = sfold_vol_storeFmap(vol); + err = sflegc_vol_storeFmap(vol); if (err) { pr_err("Could not store position map; error %d\n", err); } @@ -186,13 +186,13 @@ void sfold_vol_destroy(sfold_Volume * vol) vfree(vol->fmap); /* Skctx */ - sfold_sk_destroyContext(vol->skctx); + sflegc_sk_destroyContext(vol->skctx); /* Remove from device */ - sfold_dev_removeVolume(vol->dev, vol->vol_idx); + sflegc_dev_removeVolume(vol->dev, vol->vol_idx); /* Destroy sysfs entries */ - sfold_sysfs_remove_volume(vol); + sflegc_sysfs_remove_volume(vol); /* Free volume structure */ kfree(vol); diff --git a/dm-sflc/src/legacy/volume/volume.h b/dm-sflc/src/legacy/volume/volume.h index 38faf40..3222433 100644 --- a/dm-sflc/src/legacy/volume/volume.h +++ b/dm-sflc/src/legacy/volume/volume.h @@ -26,8 +26,8 @@ * a "real" device represented by a device. */ -#ifndef _SFOLD_VOLUME_VOLUME_H_ -#define _SFOLD_VOLUME_VOLUME_H_ +#ifndef _SFLEGC_VOLUME_VOLUME_H_ +#define _SFLEGC_VOLUME_VOLUME_H_ /***************************************************** @@ -36,9 +36,9 @@ /* Necessary since device.h, volume.h, and sysfs.h all include each other */ -typedef struct sfold_vol_write_work_s sfold_vol_WriteWork; -typedef struct sfold_vol_decrypt_work_s sfold_vol_DecryptWork; -typedef struct sfold_volume_s sfold_Volume; +typedef struct sflegc_vol_write_work_s sflegc_vol_WriteWork; +typedef struct sflegc_vol_decrypt_work_s sflegc_vol_DecryptWork; +typedef struct sflegc_volume_s sflegc_Volume; /***************************************************** @@ -47,9 +47,9 @@ typedef struct sfold_volume_s sfold_Volume; #include -#include "old/sflc_old.h" -#include "old/device/device.h" -#include "old/crypto/symkey/symkey.h" +#include "legacy/sflc_legacy.h" +#include "legacy/device/device.h" +#include "legacy/crypto/symkey/symkey.h" /***************************************************** @@ -57,26 +57,26 @@ typedef struct sfold_volume_s sfold_Volume; *****************************************************/ /* A single header data block contains 1024 fmap mappings */ -#define SFOLD_VOL_HEADER_MAPPINGS_PER_BLOCK (SFOLD_DEV_SECTOR_SIZE / sizeof(u32)) +#define SFLEGC_VOL_HEADER_MAPPINGS_PER_BLOCK (SFLEGC_DEV_SECTOR_SIZE / sizeof(u32)) /* We split the volume's logical addressing space into 1 MB slices */ -#define SFOLD_VOL_LOG_SLICE_SIZE 256 // In 4096-byte sectors +#define SFLEGC_VOL_LOG_SLICE_SIZE 256 // In 4096-byte sectors /* Value marking an LSI as unassigned */ -#define SFOLD_VOL_FMAP_INVALID_PSI 0xFFFFFFFFU +#define SFLEGC_VOL_FMAP_INVALID_PSI 0xFFFFFFFFU /* The volume name is "sflc--" */ -#define SFOLD_VOL_NAME_MAX_LEN 12 +#define SFLEGC_VOL_NAME_MAX_LEN 12 /***************************************************** * TYPES * *****************************************************/ -struct sfold_vol_write_work_s +struct sflegc_vol_write_work_s { /* Essential information */ - sfold_Volume * vol; + sflegc_Volume * vol; struct bio * orig_bio; /* Write requests need to allocate own page */ @@ -86,10 +86,10 @@ struct sfold_vol_write_work_s struct work_struct work; }; -struct sfold_vol_decrypt_work_s +struct sflegc_vol_decrypt_work_s { /* Essential information */ - sfold_Volume * vol; + sflegc_Volume * vol; struct bio * orig_bio; struct bio * phys_bio; @@ -101,13 +101,13 @@ struct sfold_vol_decrypt_work_s struct work_struct work; }; -struct sfold_volume_s +struct sflegc_volume_s { /* Name of the volume, sflc__*/ - char vol_name[SFOLD_VOL_NAME_MAX_LEN + 1]; + char vol_name[SFLEGC_VOL_NAME_MAX_LEN + 1]; /* Backing device */ - sfold_Device * dev; + sflegc_Device * dev; /* Index of this volume within the device's volume array */ u32 vol_idx; @@ -121,7 +121,7 @@ struct sfold_volume_s struct kobject *kobj_parent; /* Crypto */ - sfold_sk_Context * skctx; + sflegc_sk_Context * skctx; }; @@ -130,28 +130,28 @@ struct sfold_volume_s *****************************************************/ /* Creates volume and adds it to the device. Returns an ERR_PTR() if unsuccessful */ -sfold_Volume * sfold_vol_create(struct dm_target * ti, sfold_Device* dev, +sflegc_Volume * sflegc_vol_create(struct dm_target * ti, sflegc_Device* dev, int argc, char **argv, struct kobject *kobj); /* Removes the volume from the device and frees it. */ -void sfold_vol_destroy(sfold_Volume * vol); +void sflegc_vol_destroy(sflegc_Volume * vol); /* Remaps the underlying block device and the sector number */ -int sfold_vol_remapBioFast(sfold_Volume * vol, struct bio * bio); +int sflegc_vol_remapBioFast(sflegc_Volume * vol, struct bio * bio); /* Processes the bio in the normal indirection+crypto way */ -int sfold_vol_processBio(sfold_Volume * vol, struct bio * bio); +int sflegc_vol_processBio(sflegc_Volume * vol, struct bio * bio); /* Executed in top half */ -void sfold_vol_doRead(sfold_Volume * vol, struct bio * bio); +void sflegc_vol_doRead(sflegc_Volume * vol, struct bio * bio); /* Executed in bottom half */ -void sfold_vol_doWrite(struct work_struct * work); +void sflegc_vol_doWrite(struct work_struct * work); /* Maps a logical 512-byte sector to a physical 512-byte sector. Returns < 0 if error. * Specifically, if op == READ, and the logical slice is unmapped, -ENXIO is returned. */ -s64 sfold_vol_remapSector(sfold_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out); +s64 sflegc_vol_remapSector(sflegc_Volume * vol, sector_t log_sector, int op, u32 * psi_out, u32 * off_in_slice_out); /* Loads (and decrypts) the position map from the volume's header */ -int sfold_vol_loadFmap(sfold_Volume * vol); +int sflegc_vol_loadFmap(sflegc_Volume * vol); /* Stores (and encrypts) the position map to the volume's header */ -int sfold_vol_storeFmap(sfold_Volume * vol); +int sflegc_vol_storeFmap(sflegc_Volume * vol); -#endif /* _SFOLD_VOLUME_VOLUME_H_ */ +#endif /* _SFLEGC_VOLUME_VOLUME_H_ */ diff --git a/dm-sflc/src/legacy/volume/write.c b/dm-sflc/src/legacy/volume/write.c index bbf113c..cbfb7c2 100644 --- a/dm-sflc/src/legacy/volume/write.c +++ b/dm-sflc/src/legacy/volume/write.c @@ -32,10 +32,10 @@ * INCLUDE SECTION * *****************************************************/ -#include "old/volume/volume.h" -#include "old/crypto/rand/rand.h" -#include "old/utils/pools.h" -#include "old/log/log.h" +#include "legacy/volume/volume.h" +#include "legacy/crypto/rand/rand.h" +#include "legacy/utils/pools.h" +#include "legacy/log/log.h" #include @@ -47,9 +47,9 @@ * PRIVATE FUNCTIONS PROTOTYPES * *****************************************************/ -static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice); -static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); -static void sfold_vol_writeEndIo(struct bio * phys_bio); +static int sflegc_vol_encryptOrigBio(sflegc_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice); +static int sflegc_vol_sampleAndWriteIv(sflegc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice); +static void sflegc_vol_writeEndIo(struct bio * phys_bio); /***************************************************** * PRIVATE VARIABLES * @@ -60,11 +60,11 @@ static void sfold_vol_writeEndIo(struct bio * phys_bio); *****************************************************/ /* Executed in workqueue bottom half */ -void sfold_vol_doWrite(struct work_struct * work) +void sflegc_vol_doWrite(struct work_struct * work) { - sfold_vol_WriteWork * write_work = container_of(work, sfold_vol_WriteWork, work); - sfold_Volume * vol = write_work->vol; - sfold_Device * dev = vol->dev; + sflegc_vol_WriteWork * write_work = container_of(work, sflegc_vol_WriteWork, work); + sflegc_Volume * vol = write_work->vol; + sflegc_Device * dev = vol->dev; struct bio * orig_bio = write_work->orig_bio; struct bio * phys_bio; s64 phys_sector; @@ -80,14 +80,14 @@ void sfold_vol_doWrite(struct work_struct * work) non-owned bio's. So we need our own. */ /* Allocate an empty bio */ - phys_bio = bio_alloc_bioset(dev->bdev->bdev, bio_segments(orig_bio), orig_bio->bi_opf, GFP_NOIO, &sfold_pools_bioset); + phys_bio = bio_alloc_bioset(dev->bdev->bdev, bio_segments(orig_bio), orig_bio->bi_opf, GFP_NOIO, &sflegc_pools_bioset); if (!phys_bio) { pr_err("Could not allocate bio\n"); goto err_alloc_phys_bio; } /* Remap sector */ - phys_sector = sfold_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, WRITE, &psi, &off_in_slice); + phys_sector = sflegc_vol_remapSector(vol, orig_bio->bi_iter.bi_sector, WRITE, &psi, &off_in_slice); if (phys_sector < 0) { pr_err("Could not remap sector for physical bio; error %d\n", (int) phys_sector); goto err_remap_sector; @@ -95,11 +95,11 @@ void sfold_vol_doWrite(struct work_struct * work) phys_bio->bi_iter.bi_sector = phys_sector; /* Set fields for the endio */ - phys_bio->bi_end_io = sfold_vol_writeEndIo; + phys_bio->bi_end_io = sflegc_vol_writeEndIo; phys_bio->bi_private = write_work; /* Encrypt the original bio into the physical bio (newly-allocated pages) */ - int err = sfold_vol_encryptOrigBio(vol, orig_bio, phys_bio, psi, off_in_slice); + int err = sflegc_vol_encryptOrigBio(vol, orig_bio, phys_bio, psi, off_in_slice); if (err) { pr_err("Could not encrypt original bio; error %d\n", err); goto err_encrypt_orig_bio; @@ -127,14 +127,14 @@ err_alloc_phys_bio: *****************************************************/ /* Encrypts the contents of the original bio into newly-allocated pages for the physical bio */ -static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice) +static int sflegc_vol_encryptOrigBio(sflegc_Volume * vol, struct bio * orig_bio, struct bio * phys_bio, u32 psi, u32 off_in_slice) { - sfold_vol_WriteWork * write_work = phys_bio->bi_private; - u8 iv[SFOLD_SK_IV_LEN]; + sflegc_vol_WriteWork * write_work = phys_bio->bi_private; + u8 iv[SFLEGC_SK_IV_LEN]; int err; /* Allocate new page for the physical bio */ - write_work->page = mempool_alloc(sfold_pools_pagePool, GFP_NOIO); + write_work->page = mempool_alloc(sflegc_pools_pagePool, GFP_NOIO); if (!write_work->page) { pr_err("Could not allocate page\n"); err = -ENOMEM; @@ -142,14 +142,14 @@ static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, s } /* Add it to physical bio */ - if (!bio_add_page(phys_bio, write_work->page, SFOLD_DEV_SECTOR_SIZE, 0)) { + if (!bio_add_page(phys_bio, write_work->page, SFLEGC_DEV_SECTOR_SIZE, 0)) { pr_err("Catastrophe. Could not add page to copy bio. WTF?\n"); err = -EIO; goto err_bio_add_page; } /* Sample a random IV and write it in the IV block */ - err = sfold_vol_sampleAndWriteIv(vol, iv, psi, off_in_slice); + err = sflegc_vol_sampleAndWriteIv(vol, iv, psi, off_in_slice); if (err) { pr_err("Could not sample and write IV; error %d\n", err); err = -EIO; @@ -160,7 +160,7 @@ static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, s struct bio_vec bvl = bio_iovec(orig_bio); void * enc_sector_ptr = kmap(write_work->page); void * plain_sector_ptr = kmap(bvl.bv_page) + bvl.bv_offset; - err = sfold_sk_encrypt(vol->skctx, plain_sector_ptr, enc_sector_ptr, SFOLD_DEV_SECTOR_SIZE, iv); + err = sflegc_sk_encrypt(vol->skctx, plain_sector_ptr, enc_sector_ptr, SFLEGC_DEV_SECTOR_SIZE, iv); kunmap(bvl.bv_page); kunmap(write_work->page); if (err) { @@ -174,21 +174,21 @@ static int sfold_vol_encryptOrigBio(sfold_Volume * vol, struct bio * orig_bio, s err_encrypt: err_sample_and_write_iv: err_bio_add_page: - mempool_free(write_work->page, sfold_pools_pagePool); + mempool_free(write_work->page, sflegc_pools_pagePool); err_alloc_page: return err; } /* Allocates the io_work's IV (will need to be freed afterwards), fills it with random bytes, and writes it into the IV block pointed by the io_work's PSI and off_in_slice. */ -static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) +static int sflegc_vol_sampleAndWriteIv(sflegc_Volume * vol, u8 * iv, u32 psi, u32 off_in_slice) { - sfold_Device * dev = vol->dev; + sflegc_Device * dev = vol->dev; u8 * iv_block; int err; /* Sample IV */ - err = sfold_rand_getBytes(iv, SFOLD_SK_IV_LEN); + err = sflegc_rand_getBytes(iv, SFLEGC_SK_IV_LEN); if (err) { pr_err("Could not sample IV; error %d\n", err); err = -EIO; @@ -196,7 +196,7 @@ static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 } /* Acquire a reference to the whole relevant IV block */ - iv_block = sfold_dev_getIvBlockRef(dev, psi, WRITE); + iv_block = sflegc_dev_getIvBlockRef(dev, psi, WRITE); if (IS_ERR(iv_block)) { err = PTR_ERR(iv_block); pr_err("Could not acquire reference to IV block; error %d\n", err); @@ -204,10 +204,10 @@ static int sfold_vol_sampleAndWriteIv(sfold_Volume * vol, u8 * iv, u32 psi, u32 } /* Copy it into the relevant portion of the block */ - memcpy(iv_block + (off_in_slice * SFOLD_SK_IV_LEN), iv, SFOLD_SK_IV_LEN); + memcpy(iv_block + (off_in_slice * SFLEGC_SK_IV_LEN), iv, SFLEGC_SK_IV_LEN); /* Release reference to the IV block */ - err = sfold_dev_putIvBlockRef(dev, psi); + err = sflegc_dev_putIvBlockRef(dev, psi); if (err) { pr_err("Could not release reference to IV block; error %d\n", err); goto err_put_iv_block_ref; @@ -222,9 +222,9 @@ err_sample_iv: return err; } -static void sfold_vol_writeEndIo(struct bio * phys_bio) +static void sflegc_vol_writeEndIo(struct bio * phys_bio) { - sfold_vol_WriteWork * write_work = phys_bio->bi_private; + sflegc_vol_WriteWork * write_work = phys_bio->bi_private; struct bio * orig_bio = write_work->orig_bio; unsigned completed_bytes; @@ -245,8 +245,8 @@ static void sfold_vol_writeEndIo(struct bio * phys_bio) if (unlikely(page_ref_count(write_work->page) != 1)) { pr_err("WTF: page_ref_count = %d\n", page_ref_count(write_work->page)); } - mempool_free(write_work->page, sfold_pools_pagePool); - mempool_free(write_work, sfold_pools_writeWorkPool); + mempool_free(write_work->page, sflegc_pools_pagePool); + mempool_free(write_work, sflegc_pools_writeWorkPool); return; } diff --git a/dm-sflc/src/sflc.c b/dm-sflc/src/sflc.c index 67676b2..1a7e802 100644 --- a/dm-sflc/src/sflc.c +++ b/dm-sflc/src/sflc.c @@ -26,7 +26,7 @@ #include #include "sflc.h" -#include "old/sflc_old.h" +#include "legacy/sflc_legacy.h" #include "lite/sflc_lite.h" #include @@ -240,9 +240,9 @@ static int sflc_init(void) goto err_sysfs; /* Init the Legacy module */ - ret = sfold_init(); + ret = sflegc_init(); if (ret) - goto err_sfold; + goto err_sflegc; /* Init the Lite module */ ret = sflite_init(); @@ -261,8 +261,8 @@ static int sflc_init(void) err_dm: sflite_exit(); err_sflite: - sfold_exit(); -err_sfold: + sflegc_exit(); +err_sflegc: sflc_sysfs_exit(); err_sysfs: vfree(sflc_alldevs); @@ -276,7 +276,7 @@ bad_alldevs_alloc: static void sflc_exit(void) { dm_unregister_target(&sflc_target_type); - sfold_exit(); + sflegc_exit(); sflite_exit(); sflc_sysfs_exit(); vfree(sflc_alldevs); diff --git a/dm-sflc/src/sflc.h b/dm-sflc/src/sflc.h index 1462d16..1ff8499 100644 --- a/dm-sflc/src/sflc.h +++ b/dm-sflc/src/sflc.h @@ -28,7 +28,7 @@ #include #include "sflc_constants.h" -#include "old/sflc_old.h" +#include "legacy/sflc_legacy.h" #include "lite/sflc_lite.h" @@ -50,7 +50,7 @@ struct sflc_device /* Mode-specific device struct */ int mode; union { - sfold_Device *sfold_dev; + sflegc_Device *sflegc_dev; struct sflite_device *sflite_dev; }; @@ -69,7 +69,7 @@ struct sflc_volume /* Mode-specific volume struct */ int mode; union { - sfold_Volume *sfold_vol; + sflegc_Volume *sflegc_vol; struct sflite_volume *sflite_vol; }; /* Pointers to concrete, mode-specific callbacks */ diff --git a/shufflecake-userland/include/commands.h b/shufflecake-userland/include/commands.h index 6531bf0..aed2c45 100644 --- a/shufflecake-userland/include/commands.h +++ b/shufflecake-userland/include/commands.h @@ -98,12 +98,12 @@ typedef struct /* Create N volumes (only formats the device header, does not open the volumes). LITE version */ int sflite_cmd_initVolumes(sflc_cmd_InitArgs *args); /* Create N volumes (only formats the device header, does not open the volumes). LEGACY version */ -int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args); +int sflegc_cmd_initVolumes(sflc_cmd_InitArgs *args); /* Open M volumes, from the first down to the one whose pwd is provided. LITE version */ int sflite_cmd_openVolumes(sflc_cmd_OpenArgs *args); /* Open M volumes, from the first down to the one whose pwd is provided. LEGACY version */ -int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args); +int sflegc_cmd_openVolumes(sflc_cmd_OpenArgs *args); /* Close all volumes on the device (reads the list from sysfs) */ int sflc_cmd_closeVolumes(char *bdev_path); diff --git a/shufflecake-userland/include/header.h b/shufflecake-userland/include/header.h index 1062a21..bd77fd3 100644 --- a/shufflecake-userland/include/header.h +++ b/shufflecake-userland/include/header.h @@ -136,7 +136,7 @@ typedef struct { // The number of PosMapBlocks in the last array size_t nr_last_pmbs; -} sfold_EncPosMap; +} sflegc_EncPosMap; /***************************************************** @@ -171,16 +171,16 @@ int sflite_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); int sflite_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); /* "Encrypt" a VMB with a VMB key, so it's ready to be written on-disk. LEGACY version */ -int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); +int sflegc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block); /* "Decrypt" a VMB coming from the disk, directly using its key. LEGACY version */ -int sfold_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); +int sflegc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb); /* Create an encrypted empty position map for the given number of slices (allocates memory). LITE version */ void *sflite_epm_create(size_t nr_slices, size_t vol_idx, char *volume_key); /* Create an encrypted empty position map for the given number of slices (allocates memory). LEGACY version */ -int sfold_epm_create(size_t nr_slices, char *volume_key, sfold_EncPosMap *epm); +int sflegc_epm_create(size_t nr_slices, char *volume_key, sflegc_EncPosMap *epm); #endif /* _HEADER_H_ */ diff --git a/shufflecake-userland/include/operations.h b/shufflecake-userland/include/operations.h index c4aaa81..c1c9fd6 100644 --- a/shufflecake-userland/include/operations.h +++ b/shufflecake-userland/include/operations.h @@ -42,7 +42,7 @@ *****************************************************/ // Size, in 4096-byte blocks, of a whole volume header (VMB+PM). LEGACY version -static inline size_t sfold_volHeaderSize(size_t nr_slices) +static inline size_t sflegc_volHeaderSize(size_t nr_slices) { // Each PosMapBlock holds up to 1024 PSIs (Physical Slice Index) size_t nr_pmbs = ceil(nr_slices, SFLC_SLICE_IDX_PER_BLOCK); @@ -54,9 +54,9 @@ static inline size_t sfold_volHeaderSize(size_t nr_slices) } // Position of the VMB for the given volume. LEGACY version -static inline uint64_t sfold_vmbPosition(size_t vol_idx, size_t nr_slices) +static inline uint64_t sflegc_vmbPosition(size_t vol_idx, size_t nr_slices) { - return 1 + ((uint64_t) vol_idx) * ((uint64_t) sfold_volHeaderSize(nr_slices)); + return 1 + ((uint64_t) vol_idx) * ((uint64_t) sflegc_volHeaderSize(nr_slices)); } @@ -78,9 +78,9 @@ int sflite_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, int sflite_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); /* Encrypts and writes a volume header (VMB+PM) on-disk. LEGACY version */ -int sfold_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); +int sflegc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx); /* Reads a VMB from disk and unlocks it. LEGACY version */ -int sfold_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); +int sflegc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb); /* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device. LITE version */ @@ -89,8 +89,8 @@ int sflite_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_V int sflite_ops_closeVolume(char *label); /* Build parameter list for ctor in dm_sflc, and send DM ioctl to create virtual block device. LEGACY version */ -int sfold_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); +int sflegc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb); /* Close the volume via the appropriate ioctl to DM. LEGACY version */ -int sfold_ops_closeVolume(char *label); +int sflegc_ops_closeVolume(char *label); #endif /* _OPERATIONS_H_ */ diff --git a/shufflecake-userland/include/utils/disk.h b/shufflecake-userland/include/utils/disk.h index 426921f..3bb1033 100644 --- a/shufflecake-userland/include/utils/disk.h +++ b/shufflecake-userland/include/utils/disk.h @@ -68,7 +68,7 @@ static inline uint32_t sflite_disk_maxSlices(uint64_t size) { } /* LEGACY version */ -#define sfold_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFOLD_BLOCKS_PER_PHYS_SLICE) +#define sflegc_disk_maxSlices(size) (size - 3*SFLC_DEV_MAX_VOLUMES) / (1 + SFLEGC_BLOCKS_PER_PHYS_SLICE) /* We need this inequality to hold, in order for the previous bound to be true */ #if SFLC_DEV_MAX_VOLUMES * 2 > SFLC_SLICE_IDX_PER_BLOCK #error "Invalid combination of parameters, probably SFLC_DEV_MAX_VOLUMES is too big" diff --git a/shufflecake-userland/include/utils/sflc.h b/shufflecake-userland/include/utils/sflc.h index 499f5ae..285dde3 100644 --- a/shufflecake-userland/include/utils/sflc.h +++ b/shufflecake-userland/include/utils/sflc.h @@ -54,7 +54,7 @@ #define SFLC_MAX_SLICES (UINT32_MAX - 1) /* 0xFFFFFFFF is reserved */ /* For legacy */ -#define SFOLD_BLOCKS_PER_PHYS_SLICE (SFLC_SLICE_SCALE + 1) +#define SFLEGC_BLOCKS_PER_PHYS_SLICE (SFLC_SLICE_SCALE + 1) /* Max number of volumes in a device */ #define SFLC_DEV_MAX_VOLUMES 15 diff --git a/shufflecake-userland/src/cli/init.c b/shufflecake-userland/src/cli/init.c index 85f5074..36a9bd7 100644 --- a/shufflecake-userland/src/cli/init.c +++ b/shufflecake-userland/src/cli/init.c @@ -118,7 +118,7 @@ int sflc_cli_init(char *block_device, int sflc_mode, int num_volumes, int skip_r switch(sflc_mode) { case SFLC_MODE_LEGACY: - return sfold_cmd_initVolumes(&args); + return sflegc_cmd_initVolumes(&args); case SFLC_MODE_LITE: return sflite_cmd_initVolumes(&args); default: diff --git a/shufflecake-userland/src/cli/open.c b/shufflecake-userland/src/cli/open.c index ae1cb8d..b2d963f 100644 --- a/shufflecake-userland/src/cli/open.c +++ b/shufflecake-userland/src/cli/open.c @@ -77,7 +77,7 @@ int sflc_cli_open(char *block_device, int sflc_mode) switch(sflc_mode) { case SFLC_MODE_LEGACY: - return sfold_cmd_openVolumes(&args); + return sflegc_cmd_openVolumes(&args); case SFLC_MODE_LITE: return sflite_cmd_openVolumes(&args); default: diff --git a/shufflecake-userland/src/commands/init_legacy.c b/shufflecake-userland/src/commands/init_legacy.c index 3ee1c81..b258267 100644 --- a/shufflecake-userland/src/commands/init_legacy.c +++ b/shufflecake-userland/src/commands/init_legacy.c @@ -72,7 +72,7 @@ static int _fillDiskWithRandom(char *bdev_path, uint64_t dev_size); * * @return Error code, 0 on success */ -int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args) +int sflegc_cmd_initVolumes(sflc_cmd_InitArgs *args) { sflc_Dmb dmb; sflc_Vmb vmb; @@ -94,7 +94,7 @@ int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args) return err; } /* Convert to number of slices */ - nr_slices = sfold_disk_maxSlices(dev_size); + nr_slices = sflegc_disk_maxSlices(dev_size); sflc_log_debug("Device %s has %ld blocks, corresponding to %lu logical slices", args->bdev_path, dev_size, nr_slices); /* Fill disk with random bytes, if requested */ @@ -135,7 +135,7 @@ int sfold_cmd_initVolumes(sflc_cmd_InitArgs *args) sflc_rand_getStrongBytes(vmb.volume_key_legacy, SFLC_STANDARD_KEYLEN); /* Write complete volume header (VMB + PM) */ - err = sfold_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); + err = sflegc_ops_writeVolumeHeader(args->bdev_path, dmb.vmb_keys[i], &vmb, i); if (err) { sflc_log_error("Error writing volume header %lu on device %s; error %d", i, args->bdev_path, err); return err; diff --git a/shufflecake-userland/src/commands/open_legacy.c b/shufflecake-userland/src/commands/open_legacy.c index 66660d4..44ab82f 100644 --- a/shufflecake-userland/src/commands/open_legacy.c +++ b/shufflecake-userland/src/commands/open_legacy.c @@ -65,7 +65,7 @@ static int _getNextDevId(size_t *next_dev_id); * * @return Error code (also if no volume could be opened), 0 on success */ -int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) +int sflegc_cmd_openVolumes(sflc_cmd_OpenArgs *args) { int64_t dev_size; size_t nr_slices; @@ -103,7 +103,7 @@ int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) sflc_log_error("Could not read device size for %s; error %d", args->bdev_path, err); return err; } - nr_slices = sfold_disk_maxSlices(dev_size); + nr_slices = sflegc_disk_maxSlices(dev_size); /* Find volume opened by the pwd */ err = sflc_ops_readDmb(args->bdev_path, args->pwd, args->pwd_len, &dmb_cell); @@ -132,7 +132,7 @@ int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) } /* Read and unlock VMB */ - err = sfold_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); + err = sflegc_ops_readVmb(args->bdev_path, vmb_key, nr_slices, (size_t) i, &vmbs[i]); if (err) { sflc_log_error("Could not read VMB %d on device %s; error %d", i, args->bdev_path, err); @@ -150,7 +150,7 @@ int sfold_cmd_openVolumes(sflc_cmd_OpenArgs *args) /* Open volumes "in order" */ for (i = 0; i <= dmb_cell.vol_idx; i++) { - err = sfold_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); + err = sflegc_ops_openVolume(args->bdev_path, dev_id, (size_t) i, &vmbs[i]); if (err) { sflc_log_error("Could not open volume %d; error %d. " "Previous volumes on the device might have already " diff --git a/shufflecake-userland/src/header/position_map_legacy.c b/shufflecake-userland/src/header/position_map_legacy.c index 923a830..6aab1c7 100644 --- a/shufflecake-userland/src/header/position_map_legacy.c +++ b/shufflecake-userland/src/header/position_map_legacy.c @@ -50,7 +50,7 @@ * @param epm The EncPosMap struct to be initialised. * * @return Error code, 0 on success */ -int sfold_epm_create(size_t nr_slices, char *volume_key, sfold_EncPosMap *epm) +int sflegc_epm_create(size_t nr_slices, char *volume_key, sflegc_EncPosMap *epm) { size_t nr_pmbs; size_t nr_arrays; diff --git a/shufflecake-userland/src/header/volume_master_block_legacy.c b/shufflecake-userland/src/header/volume_master_block_legacy.c index d8b7529..ec6cc70 100644 --- a/shufflecake-userland/src/header/volume_master_block_legacy.c +++ b/shufflecake-userland/src/header/volume_master_block_legacy.c @@ -61,7 +61,7 @@ static int _deserialiseVmb(char *clear_vmb, sflc_Vmb *vmb); * * @return The error code, 0 on success */ -int sfold_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) +int sflegc_vmb_seal(sflc_Vmb *vmb, char *vmb_key, char *disk_block) { // Pointers inside the block char *iv = disk_block; @@ -123,7 +123,7 @@ bad_clear_alloc: * * @return An error code, 0 on success */ -int sfold_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) +int sflegc_vmb_unseal(char *disk_block, char *vmb_key, sflc_Vmb *vmb) { // Pointers inside the block char *iv = disk_block; diff --git a/shufflecake-userland/src/operations/devmapper_legacy.c b/shufflecake-userland/src/operations/devmapper_legacy.c index a0417da..5fe3bb3 100644 --- a/shufflecake-userland/src/operations/devmapper_legacy.c +++ b/shufflecake-userland/src/operations/devmapper_legacy.c @@ -56,7 +56,7 @@ * * @return Error code, 0 on success */ -int sfold_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) +int sflegc_ops_openVolume(char *bdev_path, size_t dev_id, size_t vol_idx, sflc_Vmb *vmb) { char label[SFLC_BIGBUFSIZE]; char *hex_key; @@ -104,7 +104,7 @@ err_hexkey: * * @return Error code, 0 on success */ -int sfold_ops_closeVolume(char *label) +int sflegc_ops_closeVolume(char *label) { /* Issue ioctl */ return sflc_dm_destroy(label); diff --git a/shufflecake-userland/src/operations/volume_header_legacy.c b/shufflecake-userland/src/operations/volume_header_legacy.c index 44ec50c..41bc54a 100644 --- a/shufflecake-userland/src/operations/volume_header_legacy.c +++ b/shufflecake-userland/src/operations/volume_header_legacy.c @@ -53,22 +53,22 @@ * * @return Error code, 0 on success */ -int sfold_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) +int sflegc_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, size_t vol_idx) { char enc_vmb[SFLC_BLOCK_SIZE]; - sfold_EncPosMap epm; + sflegc_EncPosMap epm; uint64_t sector; int err; // Encrypt VMB - err = sfold_vmb_seal(vmb, vmb_key, enc_vmb); + err = sflegc_vmb_seal(vmb, vmb_key, enc_vmb); if (err) { sflc_log_error("Could not seal VMB; error %d", err); goto out; } // Write it to disk - sector = sfold_vmbPosition(vol_idx, vmb->nr_slices); + sector = sflegc_vmbPosition(vol_idx, vmb->nr_slices); err = sflc_disk_writeBlock(bdev_path, sector, enc_vmb); if (err) { sflc_log_error("Could not write VMB to disk; error %d", err); @@ -77,7 +77,7 @@ int sfold_ops_writeVolumeHeader(char *bdev_path, char *vmb_key, sflc_Vmb *vmb, s sector += 1; // Create encrypted empty position map - err = sfold_epm_create(vmb->nr_slices, vmb->volume_key_legacy, &epm); + err = sflegc_epm_create(vmb->nr_slices, vmb->volume_key_legacy, &epm); if (err) { sflc_log_error("Could not create encrypted empty position map; error %d", err); goto out; @@ -133,14 +133,14 @@ out: * * @return Error code, 0 on success */ -int sfold_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) +int sflegc_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t vol_idx, sflc_Vmb *vmb) { char enc_vmb[SFLC_BLOCK_SIZE]; uint64_t sector; int err; /* Read encrypted VMB from disk */ - sector = sfold_vmbPosition(vol_idx, nr_slices); + sector = sflegc_vmbPosition(vol_idx, nr_slices); err = sflc_disk_readBlock(bdev_path, sector, enc_vmb); if (err) { sflc_log_error("Could not read VMB from disk; error %d", err); @@ -148,7 +148,7 @@ int sfold_ops_readVmb(char *bdev_path, char *vmb_key, size_t nr_slices, size_t v } /* Unseal it */ - err = sfold_vmb_unseal(enc_vmb, vmb_key, vmb); + err = sflegc_vmb_unseal(enc_vmb, vmb_key, vmb); if (err) { sflc_log_error("Could not unseal VMB; error %d", err); return err; From 06f2f2573698e8698a19ace6f319a3fe676dfb2e Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 17:37:10 +0200 Subject: [PATCH 95/98] test:Rename old to legacy --- dm-sflc/bin/legacy/crypto/rand/rand.c | 1 - dm-sflc/bin/legacy/crypto/rand/rand.h | 1 - dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c | 1 - dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h | 1 - dm-sflc/bin/legacy/crypto/symkey/symkey.c | 1 - dm-sflc/bin/legacy/crypto/symkey/symkey.h | 1 - dm-sflc/bin/legacy/device/device.c | 1 - dm-sflc/bin/legacy/device/device.h | 1 - dm-sflc/bin/legacy/device/iv.c | 1 - dm-sflc/bin/legacy/device/rawio.c | 1 - dm-sflc/bin/legacy/device/rmap.c | 1 - dm-sflc/bin/legacy/device/volumes.c | 1 - dm-sflc/bin/legacy/log/log.h | 1 - dm-sflc/bin/legacy/sflc_old.c | 1 - dm-sflc/bin/legacy/sflc_old.h | 1 - dm-sflc/bin/legacy/sysfs.c | 1 - dm-sflc/bin/legacy/target.c | 1 - dm-sflc/bin/legacy/utils/bio.c | 1 - dm-sflc/bin/legacy/utils/bio.h | 1 - dm-sflc/bin/legacy/utils/pools.c | 1 - dm-sflc/bin/legacy/utils/pools.h | 1 - dm-sflc/bin/legacy/utils/string.c | 1 - dm-sflc/bin/legacy/utils/string.h | 1 - dm-sflc/bin/legacy/utils/vector.c | 1 - dm-sflc/bin/legacy/utils/vector.h | 1 - dm-sflc/bin/legacy/utils/workqueues.c | 1 - dm-sflc/bin/legacy/utils/workqueues.h | 1 - dm-sflc/bin/legacy/volume/fmap.c | 1 - dm-sflc/bin/legacy/volume/io.c | 1 - dm-sflc/bin/legacy/volume/read.c | 1 - dm-sflc/bin/legacy/volume/volume.c | 1 - dm-sflc/bin/legacy/volume/volume.h | 1 - dm-sflc/bin/legacy/volume/write.c | 1 - 33 files changed, 33 deletions(-) delete mode 120000 dm-sflc/bin/legacy/crypto/rand/rand.c delete mode 120000 dm-sflc/bin/legacy/crypto/rand/rand.h delete mode 120000 dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c delete mode 120000 dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h delete mode 120000 dm-sflc/bin/legacy/crypto/symkey/symkey.c delete mode 120000 dm-sflc/bin/legacy/crypto/symkey/symkey.h delete mode 120000 dm-sflc/bin/legacy/device/device.c delete mode 120000 dm-sflc/bin/legacy/device/device.h delete mode 120000 dm-sflc/bin/legacy/device/iv.c delete mode 120000 dm-sflc/bin/legacy/device/rawio.c delete mode 120000 dm-sflc/bin/legacy/device/rmap.c delete mode 120000 dm-sflc/bin/legacy/device/volumes.c delete mode 120000 dm-sflc/bin/legacy/log/log.h delete mode 120000 dm-sflc/bin/legacy/sflc_old.c delete mode 120000 dm-sflc/bin/legacy/sflc_old.h delete mode 120000 dm-sflc/bin/legacy/sysfs.c delete mode 120000 dm-sflc/bin/legacy/target.c delete mode 120000 dm-sflc/bin/legacy/utils/bio.c delete mode 120000 dm-sflc/bin/legacy/utils/bio.h delete mode 120000 dm-sflc/bin/legacy/utils/pools.c delete mode 120000 dm-sflc/bin/legacy/utils/pools.h delete mode 120000 dm-sflc/bin/legacy/utils/string.c delete mode 120000 dm-sflc/bin/legacy/utils/string.h delete mode 120000 dm-sflc/bin/legacy/utils/vector.c delete mode 120000 dm-sflc/bin/legacy/utils/vector.h delete mode 120000 dm-sflc/bin/legacy/utils/workqueues.c delete mode 120000 dm-sflc/bin/legacy/utils/workqueues.h delete mode 120000 dm-sflc/bin/legacy/volume/fmap.c delete mode 120000 dm-sflc/bin/legacy/volume/io.c delete mode 120000 dm-sflc/bin/legacy/volume/read.c delete mode 120000 dm-sflc/bin/legacy/volume/volume.c delete mode 120000 dm-sflc/bin/legacy/volume/volume.h delete mode 120000 dm-sflc/bin/legacy/volume/write.c diff --git a/dm-sflc/bin/legacy/crypto/rand/rand.c b/dm-sflc/bin/legacy/crypto/rand/rand.c deleted file mode 120000 index cd1f529..0000000 --- a/dm-sflc/bin/legacy/crypto/rand/rand.c +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/rand/rand.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/rand/rand.h b/dm-sflc/bin/legacy/crypto/rand/rand.h deleted file mode 120000 index f515491..0000000 --- a/dm-sflc/bin/legacy/crypto/rand/rand.h +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/rand/rand.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c deleted file mode 120000 index 86e2c3a..0000000 --- a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/symkey/skreq_pool.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h deleted file mode 120000 index 26bf22c..0000000 --- a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/symkey/skreq_pool.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/symkey.c b/dm-sflc/bin/legacy/crypto/symkey/symkey.c deleted file mode 120000 index f9290b7..0000000 --- a/dm-sflc/bin/legacy/crypto/symkey/symkey.c +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/symkey/symkey.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/symkey.h b/dm-sflc/bin/legacy/crypto/symkey/symkey.h deleted file mode 120000 index 9db23d7..0000000 --- a/dm-sflc/bin/legacy/crypto/symkey/symkey.h +++ /dev/null @@ -1 +0,0 @@ -../../../../src/old/crypto/symkey/symkey.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/device.c b/dm-sflc/bin/legacy/device/device.c deleted file mode 120000 index 597ebc4..0000000 --- a/dm-sflc/bin/legacy/device/device.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/device.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/device.h b/dm-sflc/bin/legacy/device/device.h deleted file mode 120000 index 17a90f4..0000000 --- a/dm-sflc/bin/legacy/device/device.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/device.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/iv.c b/dm-sflc/bin/legacy/device/iv.c deleted file mode 120000 index ba70509..0000000 --- a/dm-sflc/bin/legacy/device/iv.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/iv.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/rawio.c b/dm-sflc/bin/legacy/device/rawio.c deleted file mode 120000 index d1e7e2b..0000000 --- a/dm-sflc/bin/legacy/device/rawio.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/rawio.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/rmap.c b/dm-sflc/bin/legacy/device/rmap.c deleted file mode 120000 index 1b346c1..0000000 --- a/dm-sflc/bin/legacy/device/rmap.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/rmap.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/volumes.c b/dm-sflc/bin/legacy/device/volumes.c deleted file mode 120000 index 531eab0..0000000 --- a/dm-sflc/bin/legacy/device/volumes.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/device/volumes.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/log/log.h b/dm-sflc/bin/legacy/log/log.h deleted file mode 120000 index be4c94e..0000000 --- a/dm-sflc/bin/legacy/log/log.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/log/log.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sflc_old.c b/dm-sflc/bin/legacy/sflc_old.c deleted file mode 120000 index f003fde..0000000 --- a/dm-sflc/bin/legacy/sflc_old.c +++ /dev/null @@ -1 +0,0 @@ -../../src/old/sflc_old.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sflc_old.h b/dm-sflc/bin/legacy/sflc_old.h deleted file mode 120000 index 17e112f..0000000 --- a/dm-sflc/bin/legacy/sflc_old.h +++ /dev/null @@ -1 +0,0 @@ -../../src/old/sflc_old.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sysfs.c b/dm-sflc/bin/legacy/sysfs.c deleted file mode 120000 index a61025a..0000000 --- a/dm-sflc/bin/legacy/sysfs.c +++ /dev/null @@ -1 +0,0 @@ -../../src/old/sysfs.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/target.c b/dm-sflc/bin/legacy/target.c deleted file mode 120000 index d853b61..0000000 --- a/dm-sflc/bin/legacy/target.c +++ /dev/null @@ -1 +0,0 @@ -../../src/old/target.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/bio.c b/dm-sflc/bin/legacy/utils/bio.c deleted file mode 120000 index 3290da9..0000000 --- a/dm-sflc/bin/legacy/utils/bio.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/bio.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/bio.h b/dm-sflc/bin/legacy/utils/bio.h deleted file mode 120000 index 287e38d..0000000 --- a/dm-sflc/bin/legacy/utils/bio.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/bio.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/pools.c b/dm-sflc/bin/legacy/utils/pools.c deleted file mode 120000 index fe89f0f..0000000 --- a/dm-sflc/bin/legacy/utils/pools.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/pools.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/pools.h b/dm-sflc/bin/legacy/utils/pools.h deleted file mode 120000 index ccc41b3..0000000 --- a/dm-sflc/bin/legacy/utils/pools.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/pools.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/string.c b/dm-sflc/bin/legacy/utils/string.c deleted file mode 120000 index 7e3ad7b..0000000 --- a/dm-sflc/bin/legacy/utils/string.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/string.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/string.h b/dm-sflc/bin/legacy/utils/string.h deleted file mode 120000 index 8391130..0000000 --- a/dm-sflc/bin/legacy/utils/string.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/string.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/vector.c b/dm-sflc/bin/legacy/utils/vector.c deleted file mode 120000 index f340f39..0000000 --- a/dm-sflc/bin/legacy/utils/vector.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/vector.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/vector.h b/dm-sflc/bin/legacy/utils/vector.h deleted file mode 120000 index ee1ff17..0000000 --- a/dm-sflc/bin/legacy/utils/vector.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/vector.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/workqueues.c b/dm-sflc/bin/legacy/utils/workqueues.c deleted file mode 120000 index d60d1a3..0000000 --- a/dm-sflc/bin/legacy/utils/workqueues.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/workqueues.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/workqueues.h b/dm-sflc/bin/legacy/utils/workqueues.h deleted file mode 120000 index b8d2f7e..0000000 --- a/dm-sflc/bin/legacy/utils/workqueues.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/utils/workqueues.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/fmap.c b/dm-sflc/bin/legacy/volume/fmap.c deleted file mode 120000 index 5b46520..0000000 --- a/dm-sflc/bin/legacy/volume/fmap.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/fmap.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/io.c b/dm-sflc/bin/legacy/volume/io.c deleted file mode 120000 index d7221c0..0000000 --- a/dm-sflc/bin/legacy/volume/io.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/io.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/read.c b/dm-sflc/bin/legacy/volume/read.c deleted file mode 120000 index efdcf3b..0000000 --- a/dm-sflc/bin/legacy/volume/read.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/read.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/volume.c b/dm-sflc/bin/legacy/volume/volume.c deleted file mode 120000 index 8e26280..0000000 --- a/dm-sflc/bin/legacy/volume/volume.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/volume.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/volume.h b/dm-sflc/bin/legacy/volume/volume.h deleted file mode 120000 index 8f28ef6..0000000 --- a/dm-sflc/bin/legacy/volume/volume.h +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/volume.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/write.c b/dm-sflc/bin/legacy/volume/write.c deleted file mode 120000 index 24e67c7..0000000 --- a/dm-sflc/bin/legacy/volume/write.c +++ /dev/null @@ -1 +0,0 @@ -../../../src/old/volume/write.c \ No newline at end of file From 2c124c65a05ee604be6a17ab635dcb409aad0e8f Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 18:01:58 +0200 Subject: [PATCH 96/98] test:Rename old to legacy --- dm-sflc/bin/legacy/crypto/rand/rand.c | 1 + dm-sflc/bin/legacy/crypto/rand/rand.h | 1 + dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c | 1 + dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h | 1 + dm-sflc/bin/legacy/crypto/symkey/symkey.c | 1 + dm-sflc/bin/legacy/crypto/symkey/symkey.h | 1 + dm-sflc/bin/legacy/device/device.c | 1 + dm-sflc/bin/legacy/device/device.h | 1 + dm-sflc/bin/legacy/device/iv.c | 1 + dm-sflc/bin/legacy/device/rawio.c | 1 + dm-sflc/bin/legacy/device/rmap.c | 1 + dm-sflc/bin/legacy/device/volumes.c | 1 + dm-sflc/bin/legacy/log/log.h | 1 + dm-sflc/bin/legacy/sflc_legacy.c | 1 + dm-sflc/bin/legacy/sflc_legacy.h | 1 + dm-sflc/bin/legacy/sysfs.c | 1 + dm-sflc/bin/legacy/target.c | 1 + dm-sflc/bin/legacy/utils/bio.c | 1 + dm-sflc/bin/legacy/utils/bio.h | 1 + dm-sflc/bin/legacy/utils/pools.c | 1 + dm-sflc/bin/legacy/utils/pools.h | 1 + dm-sflc/bin/legacy/utils/string.c | 1 + dm-sflc/bin/legacy/utils/string.h | 1 + dm-sflc/bin/legacy/utils/vector.c | 1 + dm-sflc/bin/legacy/utils/vector.h | 1 + dm-sflc/bin/legacy/utils/workqueues.c | 1 + dm-sflc/bin/legacy/utils/workqueues.h | 1 + dm-sflc/bin/legacy/volume/fmap.c | 1 + dm-sflc/bin/legacy/volume/io.c | 1 + dm-sflc/bin/legacy/volume/read.c | 1 + dm-sflc/bin/legacy/volume/volume.c | 1 + dm-sflc/bin/legacy/volume/volume.h | 1 + dm-sflc/bin/legacy/volume/write.c | 1 + 33 files changed, 33 insertions(+) create mode 120000 dm-sflc/bin/legacy/crypto/rand/rand.c create mode 120000 dm-sflc/bin/legacy/crypto/rand/rand.h create mode 120000 dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c create mode 120000 dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h create mode 120000 dm-sflc/bin/legacy/crypto/symkey/symkey.c create mode 120000 dm-sflc/bin/legacy/crypto/symkey/symkey.h create mode 120000 dm-sflc/bin/legacy/device/device.c create mode 120000 dm-sflc/bin/legacy/device/device.h create mode 120000 dm-sflc/bin/legacy/device/iv.c create mode 120000 dm-sflc/bin/legacy/device/rawio.c create mode 120000 dm-sflc/bin/legacy/device/rmap.c create mode 120000 dm-sflc/bin/legacy/device/volumes.c create mode 120000 dm-sflc/bin/legacy/log/log.h create mode 120000 dm-sflc/bin/legacy/sflc_legacy.c create mode 120000 dm-sflc/bin/legacy/sflc_legacy.h create mode 120000 dm-sflc/bin/legacy/sysfs.c create mode 120000 dm-sflc/bin/legacy/target.c create mode 120000 dm-sflc/bin/legacy/utils/bio.c create mode 120000 dm-sflc/bin/legacy/utils/bio.h create mode 120000 dm-sflc/bin/legacy/utils/pools.c create mode 120000 dm-sflc/bin/legacy/utils/pools.h create mode 120000 dm-sflc/bin/legacy/utils/string.c create mode 120000 dm-sflc/bin/legacy/utils/string.h create mode 120000 dm-sflc/bin/legacy/utils/vector.c create mode 120000 dm-sflc/bin/legacy/utils/vector.h create mode 120000 dm-sflc/bin/legacy/utils/workqueues.c create mode 120000 dm-sflc/bin/legacy/utils/workqueues.h create mode 120000 dm-sflc/bin/legacy/volume/fmap.c create mode 120000 dm-sflc/bin/legacy/volume/io.c create mode 120000 dm-sflc/bin/legacy/volume/read.c create mode 120000 dm-sflc/bin/legacy/volume/volume.c create mode 120000 dm-sflc/bin/legacy/volume/volume.h create mode 120000 dm-sflc/bin/legacy/volume/write.c diff --git a/dm-sflc/bin/legacy/crypto/rand/rand.c b/dm-sflc/bin/legacy/crypto/rand/rand.c new file mode 120000 index 0000000..113cc5e --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/rand/rand.c @@ -0,0 +1 @@ +../../../../src/legacy/crypto/rand/rand.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/rand/rand.h b/dm-sflc/bin/legacy/crypto/rand/rand.h new file mode 120000 index 0000000..79a32c9 --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/rand/rand.h @@ -0,0 +1 @@ +../../../../src/legacy/crypto/rand/rand.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c new file mode 120000 index 0000000..d7fd86c --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.c @@ -0,0 +1 @@ +../../../../src/legacy/crypto/symkey/skreq_pool.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h new file mode 120000 index 0000000..bbf5677 --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/symkey/skreq_pool.h @@ -0,0 +1 @@ +../../../../src/legacy/crypto/symkey/skreq_pool.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/symkey.c b/dm-sflc/bin/legacy/crypto/symkey/symkey.c new file mode 120000 index 0000000..e930fb4 --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/symkey/symkey.c @@ -0,0 +1 @@ +../../../../src/legacy/crypto/symkey/symkey.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/crypto/symkey/symkey.h b/dm-sflc/bin/legacy/crypto/symkey/symkey.h new file mode 120000 index 0000000..127df1b --- /dev/null +++ b/dm-sflc/bin/legacy/crypto/symkey/symkey.h @@ -0,0 +1 @@ +../../../../src/legacy/crypto/symkey/symkey.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/device.c b/dm-sflc/bin/legacy/device/device.c new file mode 120000 index 0000000..4770b03 --- /dev/null +++ b/dm-sflc/bin/legacy/device/device.c @@ -0,0 +1 @@ +../../../src/legacy/device/device.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/device.h b/dm-sflc/bin/legacy/device/device.h new file mode 120000 index 0000000..f268245 --- /dev/null +++ b/dm-sflc/bin/legacy/device/device.h @@ -0,0 +1 @@ +../../../src/legacy/device/device.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/iv.c b/dm-sflc/bin/legacy/device/iv.c new file mode 120000 index 0000000..3a8fe09 --- /dev/null +++ b/dm-sflc/bin/legacy/device/iv.c @@ -0,0 +1 @@ +../../../src/legacy/device/iv.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/rawio.c b/dm-sflc/bin/legacy/device/rawio.c new file mode 120000 index 0000000..2ec4dfb --- /dev/null +++ b/dm-sflc/bin/legacy/device/rawio.c @@ -0,0 +1 @@ +../../../src/legacy/device/rawio.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/rmap.c b/dm-sflc/bin/legacy/device/rmap.c new file mode 120000 index 0000000..b13a00b --- /dev/null +++ b/dm-sflc/bin/legacy/device/rmap.c @@ -0,0 +1 @@ +../../../src/legacy/device/rmap.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/device/volumes.c b/dm-sflc/bin/legacy/device/volumes.c new file mode 120000 index 0000000..56e34fc --- /dev/null +++ b/dm-sflc/bin/legacy/device/volumes.c @@ -0,0 +1 @@ +../../../src/legacy/device/volumes.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/log/log.h b/dm-sflc/bin/legacy/log/log.h new file mode 120000 index 0000000..b1a5082 --- /dev/null +++ b/dm-sflc/bin/legacy/log/log.h @@ -0,0 +1 @@ +../../../src/legacy/log/log.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sflc_legacy.c b/dm-sflc/bin/legacy/sflc_legacy.c new file mode 120000 index 0000000..971b2cc --- /dev/null +++ b/dm-sflc/bin/legacy/sflc_legacy.c @@ -0,0 +1 @@ +../../src/legacy/sflc_legacy.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sflc_legacy.h b/dm-sflc/bin/legacy/sflc_legacy.h new file mode 120000 index 0000000..0b29c03 --- /dev/null +++ b/dm-sflc/bin/legacy/sflc_legacy.h @@ -0,0 +1 @@ +../../src/legacy/sflc_legacy.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/sysfs.c b/dm-sflc/bin/legacy/sysfs.c new file mode 120000 index 0000000..b34d0a3 --- /dev/null +++ b/dm-sflc/bin/legacy/sysfs.c @@ -0,0 +1 @@ +../../src/legacy/sysfs.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/target.c b/dm-sflc/bin/legacy/target.c new file mode 120000 index 0000000..0553cf6 --- /dev/null +++ b/dm-sflc/bin/legacy/target.c @@ -0,0 +1 @@ +../../src/legacy/target.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/bio.c b/dm-sflc/bin/legacy/utils/bio.c new file mode 120000 index 0000000..013aaf7 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/bio.c @@ -0,0 +1 @@ +../../../src/legacy/utils/bio.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/bio.h b/dm-sflc/bin/legacy/utils/bio.h new file mode 120000 index 0000000..7d66dc7 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/bio.h @@ -0,0 +1 @@ +../../../src/legacy/utils/bio.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/pools.c b/dm-sflc/bin/legacy/utils/pools.c new file mode 120000 index 0000000..51b1230 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/pools.c @@ -0,0 +1 @@ +../../../src/legacy/utils/pools.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/pools.h b/dm-sflc/bin/legacy/utils/pools.h new file mode 120000 index 0000000..63520d0 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/pools.h @@ -0,0 +1 @@ +../../../src/legacy/utils/pools.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/string.c b/dm-sflc/bin/legacy/utils/string.c new file mode 120000 index 0000000..8d1e9ce --- /dev/null +++ b/dm-sflc/bin/legacy/utils/string.c @@ -0,0 +1 @@ +../../../src/legacy/utils/string.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/string.h b/dm-sflc/bin/legacy/utils/string.h new file mode 120000 index 0000000..41c5867 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/string.h @@ -0,0 +1 @@ +../../../src/legacy/utils/string.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/vector.c b/dm-sflc/bin/legacy/utils/vector.c new file mode 120000 index 0000000..e9c99ee --- /dev/null +++ b/dm-sflc/bin/legacy/utils/vector.c @@ -0,0 +1 @@ +../../../src/legacy/utils/vector.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/vector.h b/dm-sflc/bin/legacy/utils/vector.h new file mode 120000 index 0000000..f89b563 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/vector.h @@ -0,0 +1 @@ +../../../src/legacy/utils/vector.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/workqueues.c b/dm-sflc/bin/legacy/utils/workqueues.c new file mode 120000 index 0000000..2ce7811 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/workqueues.c @@ -0,0 +1 @@ +../../../src/legacy/utils/workqueues.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/utils/workqueues.h b/dm-sflc/bin/legacy/utils/workqueues.h new file mode 120000 index 0000000..596b3c3 --- /dev/null +++ b/dm-sflc/bin/legacy/utils/workqueues.h @@ -0,0 +1 @@ +../../../src/legacy/utils/workqueues.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/fmap.c b/dm-sflc/bin/legacy/volume/fmap.c new file mode 120000 index 0000000..829e2e9 --- /dev/null +++ b/dm-sflc/bin/legacy/volume/fmap.c @@ -0,0 +1 @@ +../../../src/legacy/volume/fmap.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/io.c b/dm-sflc/bin/legacy/volume/io.c new file mode 120000 index 0000000..b7e0d76 --- /dev/null +++ b/dm-sflc/bin/legacy/volume/io.c @@ -0,0 +1 @@ +../../../src/legacy/volume/io.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/read.c b/dm-sflc/bin/legacy/volume/read.c new file mode 120000 index 0000000..4d76d13 --- /dev/null +++ b/dm-sflc/bin/legacy/volume/read.c @@ -0,0 +1 @@ +../../../src/legacy/volume/read.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/volume.c b/dm-sflc/bin/legacy/volume/volume.c new file mode 120000 index 0000000..a67a330 --- /dev/null +++ b/dm-sflc/bin/legacy/volume/volume.c @@ -0,0 +1 @@ +../../../src/legacy/volume/volume.c \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/volume.h b/dm-sflc/bin/legacy/volume/volume.h new file mode 120000 index 0000000..6555e2a --- /dev/null +++ b/dm-sflc/bin/legacy/volume/volume.h @@ -0,0 +1 @@ +../../../src/legacy/volume/volume.h \ No newline at end of file diff --git a/dm-sflc/bin/legacy/volume/write.c b/dm-sflc/bin/legacy/volume/write.c new file mode 120000 index 0000000..a24d88e --- /dev/null +++ b/dm-sflc/bin/legacy/volume/write.c @@ -0,0 +1 @@ +../../../src/legacy/volume/write.c \ No newline at end of file From 353fc6110bbe47560624e0517e3e3d38a6579498 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 18:09:34 +0200 Subject: [PATCH 97/98] chore:Change label name of benchmark tests --- benchmark-suite/sflc-legacy-benchmark.sh | 2 +- benchmark-suite/sflc-lite-benchmark.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark-suite/sflc-legacy-benchmark.sh b/benchmark-suite/sflc-legacy-benchmark.sh index d1011ae..b5a7b1d 100755 --- a/benchmark-suite/sflc-legacy-benchmark.sh +++ b/benchmark-suite/sflc-legacy-benchmark.sh @@ -241,7 +241,7 @@ benchmark() { SFLCVOLUME="" MNTPOINT="" - TESTNAME="sflc" + TESTNAME="sflc-lgc" RUNTIME="20" # running time in seconds FOR EACH TEST DATASIZE="500M" TESTFILENAME="testfile" diff --git a/benchmark-suite/sflc-lite-benchmark.sh b/benchmark-suite/sflc-lite-benchmark.sh index b53dbce..05805a0 100755 --- a/benchmark-suite/sflc-lite-benchmark.sh +++ b/benchmark-suite/sflc-lite-benchmark.sh @@ -241,7 +241,7 @@ benchmark() { SFLCVOLUME="" MNTPOINT="" - TESTNAME="sflc" + TESTNAME="sflc-lite" RUNTIME="20" # running time in seconds FOR EACH TEST DATASIZE="500M" TESTFILENAME="testfile" From 8136a2d677d1150a81652e9df3d236fff5735975 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Sun, 1 Sep 2024 18:19:00 +0200 Subject: [PATCH 98/98] doc:Minor edit to Legacy headers schematics --- CHANGELOG.md | 1 + resources/images/headers_legacy.png | Bin 141795 -> 143704 bytes resources/images/headers_legacy.svg | 52 ++++++++++++++-------------- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d9d44..c4434fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Refactored - Global refactoring and simplification of code tree. + - Changed all occurrences of "Shufflecake Old" to "Shufflecake Legacy", including code paths and variables. - Reordered entries in CHANGELOG.md with sections following the order Added -> Changed -> Fixed -> Refactored -> Removed. ## [0.4.5] - 2024-06-03 diff --git a/resources/images/headers_legacy.png b/resources/images/headers_legacy.png index 7a1f4fc5cb22afe227dda844d948a022e626cf2f..2192b21516a2d7ab4895fa2d4fb03da07aa7a3e9 100644 GIT binary patch delta 125437 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

    ^;EI5{ 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

    #~@< z9yG;U$tnrQkKfgOC2acIUhe@oX@wCE%W&}pS6HFUvU|=xQ(&EkWpjq0Bhg&nItfx3Uq?Gu S(CUfS+E^X=vB2WgmA?Qn#|h5> delta 123560 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 i4s~w=LylSW(fWnP`N7lG&t;ucLK6VHFG)23 diff --git a/resources/images/headers_legacy.svg b/resources/images/headers_legacy.svg index cb1a69d..081c1a0 100644 --- a/resources/images/headers_legacy.svg +++ b/resources/images/headers_legacy.svg @@ -28,8 +28,8 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="1.9401788" - inkscape:cx="424.96083" - inkscape:cy="242.76113" + inkscape:cx="383.72752" + inkscape:cy="239.15322" inkscape:window-width="1920" inkscape:window-height="975" inkscape:window-x="0" @@ -268,7 +268,7 @@ inkscape:export-xdpi="200" inkscape:export-ydpi="200" /> Device / Cake Header Encrypted Slices + IVs 0 . . . 1 1 14 1 14 VMB VMB POS 1 1 1 salt 0