From 2a2166cb1708ae9fc77edbea2c04a1d7c365fe28 Mon Sep 17 00:00:00 2001 From: toninov Date: Thu, 11 Sep 2025 12:30:28 +0200 Subject: [PATCH 01/11] Add Secure Boot paragraph to README --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 369c326..cade487 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ __WARNING__: Shufflecake is a strong encryption and plausible deniability soluti - [Enabling R/W Workqueues](#workqueues) - [Volume Corruption Mitigation](#mitigcorr) - [Providing Passwords Non-Interactively](#passwordnonint) + - [Using with Secure Boot](#secureboot) - [Changelog](#changelog) - [Bugs](#bugs) - [Maintainers](#maintainers) @@ -229,7 +230,7 @@ This section documents advanced use cases and features of Shufflecake. Once a new slice is assigned to a Shufflecake volume, that assignment is static: that slice stays in the position map of that volume even if all its content (i.e. the files in the filesystem area mapped onto the slice) is deleted. On the long run, this can lead to a situation where there are no free available slices left for Shufflecake volume allocation, even if the filesystems of the volumes show plenty of available virtual space. In order to avoid this, Shufflecake implements slice garbage collection on `close`: all slices that only contain sectors unused by the filesystem on top are marked as free and removed from the slice mapping. -Usually, filesystems do not mark unused sectors automatically. To force this, call `fstrim` inside the filesystems of the open volumes, and then `close` the underlying device. Once the device is `open`ed again, those slices will be newly available for allocation. This should be done once in a while, especially after deleting large amount of data from the volumes. +Usually, filesystems do not mark unused sectors automatically. To force this, call `fstrim` inside the filesystems of the open volumes, and then `close` the underlying device. Once the device is `open`ed again, those slices will be newly available for allocation. This should be done every once in a while, especially after deleting large amount of data from the volumes. #### Enabling R/W Workqueues @@ -280,6 +281,16 @@ sudo ./shufflecake -n 3 init < passwords.txt Both methods works with the `init` and `open` action, and we do not have current plans to change this behavior for the near future. +#### Using with Secure Boot + +Running under Secure Boot means that your kernel will most likely (and most wisely) refuse to load an unsigned kernel module, such as `dm-sflc.ko`. You can confirm that that is your case if `insmod dm-sflc.ko` returns an error like "Key was rejected by service" (the kernel logs in `dmesg` might have more information). The only two solutions are: + +1. You (temporarily) disable Secure Boot. + +2. You enroll your own MOK (machine-owner key) and sign the kernel module `dm-sflc.ko` with it; you may need to recompile before signing. + +We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard and peaceful operation, that is well supported by many distributions, so online guides should be easy to find. + From 95a72fc8e7185b39b8eac915536789fad97054c8 Mon Sep 17 00:00:00 2001 From: toninov Date: Thu, 11 Sep 2025 13:02:08 +0200 Subject: [PATCH 02/11] Add explicit error message for non-inserted dm-sflc --- shufflecake-userland/Makefile.sources | 2 +- shufflecake-userland/include/utils/kernmod.h | 42 +++++++++++++ shufflecake-userland/include/utils/sflc.h | 2 + shufflecake-userland/src/cli/dispatch.c | 7 ++- shufflecake-userland/src/utils/kernmod.c | 65 ++++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 shufflecake-userland/include/utils/kernmod.h create mode 100644 shufflecake-userland/src/utils/kernmod.c diff --git a/shufflecake-userland/Makefile.sources b/shufflecake-userland/Makefile.sources index d2313c8..b34553d 100644 --- a/shufflecake-userland/Makefile.sources +++ b/shufflecake-userland/Makefile.sources @@ -27,7 +27,7 @@ #### Main files #### -PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c) +PROJ_SRCS := $(addprefix utils/,crypto.c disk.c dm.c file.c string.c input.c kernmod.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) diff --git a/shufflecake-userland/include/utils/kernmod.h b/shufflecake-userland/include/utils/kernmod.h new file mode 100644 index 0000000..a5c42a9 --- /dev/null +++ b/shufflecake-userland/include/utils/kernmod.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 _UTILS_KERNMOD_H_ +#define _UTILS_KERNMOD_H_ + + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include + + +/***************************************************** + * PUBLIC FUNCTIONS PROTOTYPES * + *****************************************************/ + +bool sflc_isKernelModuleLoaded(char *module_name); + + +#endif /* _UTILS_KERNOMD_H_ */ diff --git a/shufflecake-userland/include/utils/sflc.h b/shufflecake-userland/include/utils/sflc.h index f97fde2..6ab11b4 100644 --- a/shufflecake-userland/include/utils/sflc.h +++ b/shufflecake-userland/include/utils/sflc.h @@ -40,6 +40,8 @@ /* Name of the DM target in the kernel */ #define SFLC_DM_TARGET_NAME "shufflecake" +/* Name of the kernel module once inserted */ +#define SFLC_KERNMOD_NAME "dm_sflc" /* Modes */ #define SFLC_MODE_LEGACY 0 diff --git a/shufflecake-userland/src/cli/dispatch.c b/shufflecake-userland/src/cli/dispatch.c index d28e01f..24a002e 100644 --- a/shufflecake-userland/src/cli/dispatch.c +++ b/shufflecake-userland/src/cli/dispatch.c @@ -35,6 +35,7 @@ #include "cli.h" #include "utils/sflc.h" #include "utils/disk.h" +#include "utils/kernmod.h" #include "utils/log.h" @@ -185,7 +186,11 @@ int sflc_cli_dispatch(int argc, char **argv) { return EINVAL; } - // TODO check that dm_sflc is loaded + /* Check that the kernel module is loaded */ + if (!sflc_isKernelModuleLoaded(SFLC_KERNMOD_NAME)) { + printf("Error: kernel module dm-sflc is not inserted.\n"); + return EINVAL; + } /* Check that input is actually a block device */ if (strncmp(args.block_device, "/dev/", 5) != 0 || !sflc_disk_isBlockDevice(args.block_device)) { printf("Error: '%s' is not a valid block device.\n", args.block_device); diff --git a/shufflecake-userland/src/utils/kernmod.c b/shufflecake-userland/src/utils/kernmod.c new file mode 100644 index 0000000..6ecde3d --- /dev/null +++ b/shufflecake-userland/src/utils/kernmod.c @@ -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 . + */ + +/***************************************************** + * INCLUDE SECTION * + *****************************************************/ + +#include +#include +#include + +#include "utils/kernmod.h" +#include "utils/sflc.h" +#include "utils/log.h" + + +/***************************************************** + * PUBLIC FUNCTIONS DEFINITIONS * + *****************************************************/ + +bool sflc_isKernelModuleLoaded(char *module_name) { + FILE *fp; + char line[SFLC_BIGBUFSIZE]; + + // Open /proc/modules to check loaded modules + fp = fopen("/proc/modules", "r"); + if (fp == NULL) { + sflc_log_error("Failed to open /proc/modules"); + return false; + } + + // Read each line in /proc/modules + while (fgets(line, sizeof(line), fp) != NULL) { + // Compare the module name in the current line with the input module name + char *current_module = strtok(line, " "); // First token in the line is the module name + if (current_module != NULL && strcmp(current_module, module_name) == 0) { + fclose(fp); + return true; // Module is loaded + } + } + + fclose(fp); + return false; // Module not found +} + From 53b9c79e87de2db099070776a438fe6219b29c1a Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Thu, 11 Sep 2025 22:02:55 +0200 Subject: [PATCH 03/11] doc: Minor change to README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cade487..69d4487 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ sudo modprobe dm_mod sudo insmod dm-sflc.ko ``` +(Note: if you have Secure Boot enabled, this step might fail. See [this section](#secureboot) for workarounds.) + At this point you can run the `shufflecake` command as `sudo shufflecake `. When finished, you can unload the module with @@ -283,13 +285,13 @@ Both methods works with the `init` and `open` action, and we do not have current #### Using with Secure Boot -Running under Secure Boot means that your kernel will most likely (and most wisely) refuse to load an unsigned kernel module, such as `dm-sflc.ko`. You can confirm that that is your case if `insmod dm-sflc.ko` returns an error like "Key was rejected by service" (the kernel logs in `dmesg` might have more information). The only two solutions are: +If you have Secure Boot enabled, then your kernel will most likely refuse to load an unsigned kernel module, such as `dm-sflc.ko`. You can confirm that that is the case if `insmod dm-sflc.ko` returns an error like "Key was rejected by service" (the kernel logs in `dmesg` might have more information). The only two solutions are: 1. You (temporarily) disable Secure Boot. 2. You enroll your own MOK (machine-owner key) and sign the kernel module `dm-sflc.ko` with it; you may need to recompile before signing. -We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard and peaceful operation, that is well supported by many distributions, so online guides should be easy to find. +We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard operation, that is well supported by many distributions, so online guides should be easy to find. From be8d9d5e3a0f62620411913dee494fb055e90006 Mon Sep 17 00:00:00 2001 From: toninov Date: Fri, 12 Sep 2025 10:02:35 +0200 Subject: [PATCH 04/11] Add a sentence in README on Secure Boot --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 69d4487..8f44470 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,7 @@ If you have Secure Boot enabled, then your kernel will most likely refuse to loa 2. You enroll your own MOK (machine-owner key) and sign the kernel module `dm-sflc.ko` with it; you may need to recompile before signing. +Both these options have security implications, of course, which you should be aware of. We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard operation, that is well supported by many distributions, so online guides should be easy to find. From 75c0f30ca9a84d986e4298fc572c6b68700288bc Mon Sep 17 00:00:00 2001 From: toninov Date: Fri, 12 Sep 2025 11:24:30 +0200 Subject: [PATCH 05/11] Use perror to properly format the errno --- shufflecake-userland/src/utils/kernmod.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shufflecake-userland/src/utils/kernmod.c b/shufflecake-userland/src/utils/kernmod.c index 6ecde3d..07e6c5f 100644 --- a/shufflecake-userland/src/utils/kernmod.c +++ b/shufflecake-userland/src/utils/kernmod.c @@ -45,7 +45,7 @@ bool sflc_isKernelModuleLoaded(char *module_name) { // Open /proc/modules to check loaded modules fp = fopen("/proc/modules", "r"); if (fp == NULL) { - sflc_log_error("Failed to open /proc/modules"); + perror("Failed to open /proc/modules: "); return false; } From 51f5c2b9c13fc0ac8083fed91474a399e047c91c Mon Sep 17 00:00:00 2001 From: toninov Date: Sun, 14 Sep 2025 19:45:29 +0200 Subject: [PATCH 06/11] Define functions to use vmalloc_to_page() --- dm-sflc/src/lite/crypto.c | 133 ++++++++++++++++++++++++++++++++------ 1 file changed, 112 insertions(+), 21 deletions(-) diff --git a/dm-sflc/src/lite/crypto.c b/dm-sflc/src/lite/crypto.c index 8e27203..7897a8a 100644 --- a/dm-sflc/src/lite/crypto.c +++ b/dm-sflc/src/lite/crypto.c @@ -92,34 +92,125 @@ int sflite_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, } -/* 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) +/* + *---------------------------- + * Memory buffer enc/dec + *---------------------------- + */ + +/* Setup sg list: allocate, if more than one entry, otherwise assign to prealloc_sg */ +static int sgtable_setup(struct sg_table *sgt, struct scatterlist *prealloc_sg, + unsigned nents, gfp_t gfp_flags) { - 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 (nents > 1) { + err = sg_alloc_table(sgt, nents, gfp_flags); if (err) return err; - - src_buf += SFLITE_BLOCK_SIZE; - dst_buf += SFLITE_BLOCK_SIZE; + } else { + sg_init_table(prealloc_sg, 1); + sgt->sgl = prealloc_sg; + sgt->nents = sgt->orig_nents = 1; } return 0; } +/* Populate the sg list: every sg entry will map to a page */ +static void sgtable_populate(struct sg_table *sgt, void *buf, unsigned buf_len) +{ + struct scatterlist *sg; + unsigned i, off = offset_in_page(buf); + bool is_vmalloc = is_vmalloc_addr(buf); + + for_each_sg(sgt->sgl, sg, sgt->orig_nents, i) { + struct page *page; + unsigned int len = min(PAGE_SIZE - off, buf_len); + + if (is_vmalloc) + page = vmalloc_to_page(buf); + else + page = virt_to_page(buf); + + sg_set_page(sg, page, len, off); + + off = 0; + buf += len; + buf_len -= len; + } + WARN_ON(buf_len != 0); +} + +/* Teardown sg list: free if necessary */ +static void sgtable_teardown(struct sg_table *sgt) +{ + if (sgt->orig_nents > 1) + sg_free_table(sgt); +} + + +/* Encrypt-decrypt consecutive blocks in a memory buffer (can be vmalloc'ed) */ +int sflite_crypt_blocks_buf(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw, gfp_t gfp_flags) +{ + struct sg_table sgt_dst, sgt_src, *p_sgt_dst; + struct scatterlist prealloc_sg_dst, prealloc_sg_src; + unsigned nents; + u64 pblk_num; + bool is_inplace; + int err = 0; + + /* Use same sg_table if in-place */ + is_inplace = (src_buf == dst_buf); + p_sgt_dst = is_inplace ? &sgt_src : &sgt_dst; + + /* Setup sg_table. + * Since both SFLITE_BLOCK_SIZE and PAGE_SIZE are powers of 2, one + * has to be multiple of the other; also, we enforce that blocks start + * to be written from the start of the page (true for the PosMap); + * therefore, a single block will span exactly + * ceil(SFLITE_BLOCK_SIZE / PAGE_SIZE) pages. + * If this > 1, we make no effort to detect contiguous pages, + * for simplicity. */ + BUG_ON((offset_in_page(src_buf) % SFLITE_BLOCK_SIZE) || + (offset_in_page(dst_buf) % SFLITE_BLOCK_SIZE)); + nents = DIV_ROUND_UP(SFLITE_BLOCK_SIZE, PAGE_SIZE); + err = sgtable_setup(&sgt_src, &prealloc_sg_src, nents, gfp_flags); + if (err) { + DMERR("Could not setup sg_table src"); + sgtable_teardown(&sgt_src); + return err; + } + if (!is_inplace) { + err = sgtable_setup(&sgt_dst, &prealloc_sg_dst, nents, gfp_flags); + if (err) { + DMERR("Could not setup sg_table dst"); + goto out; + } + } + + /* Encrypt every block separately, with its own IV */ + for (pblk_num = first_pblk_num; + pblk_num < first_pblk_num + num_blocks; + pblk_num++) { + sgtable_populate(&sgt_src, src_buf, SFLITE_BLOCK_SIZE); + if (!is_inplace) + sgtable_populate(&sgt_dst, dst_buf, SFLITE_BLOCK_SIZE); + + err = crypt_sg(tfm, &sgt_src.sgl, &p_sgt_dst->sgl, pblk_num, rw); + if (err) + goto out; + + src_buf += SFLITE_BLOCK_SIZE; + dst_buf += SFLITE_BLOCK_SIZE; + } + +out: + sgtable_teardown(&sgt_dst); + if (!is_inplace) + sgtable_teardown(&sgt_src); + return err; +} + + From fda27f7b52ff5ab5977651734b234498556b87c4 Mon Sep 17 00:00:00 2001 From: toninov Date: Sun, 21 Sep 2025 12:36:59 +0200 Subject: [PATCH 07/11] Use the new sflite_crypt_blocks_buf everywhere --- dm-sflc/src/lite/crypto.c | 8 +++---- dm-sflc/src/lite/flush.c | 2 +- dm-sflc/src/lite/posmap.c | 41 ++++++++++++++++++++---------------- dm-sflc/src/lite/sflc_lite.h | 8 +++---- dm-sflc/src/lite/volume.c | 4 ++-- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/dm-sflc/src/lite/crypto.c b/dm-sflc/src/lite/crypto.c index 7897a8a..d3e92af 100644 --- a/dm-sflc/src/lite/crypto.c +++ b/dm-sflc/src/lite/crypto.c @@ -171,7 +171,7 @@ int sflite_crypt_blocks_buf(struct crypto_skcipher *tfm, void *src_buf, void *ds * to be written from the start of the page (true for the PosMap); * therefore, a single block will span exactly * ceil(SFLITE_BLOCK_SIZE / PAGE_SIZE) pages. - * If this > 1, we make no effort to detect contiguous pages, + * If this is > 1, we make no effort to detect contiguous pages, * for simplicity. */ BUG_ON((offset_in_page(src_buf) % SFLITE_BLOCK_SIZE) || (offset_in_page(dst_buf) % SFLITE_BLOCK_SIZE)); @@ -198,7 +198,7 @@ int sflite_crypt_blocks_buf(struct crypto_skcipher *tfm, void *src_buf, void *ds if (!is_inplace) sgtable_populate(&sgt_dst, dst_buf, SFLITE_BLOCK_SIZE); - err = crypt_sg(tfm, &sgt_src.sgl, &p_sgt_dst->sgl, pblk_num, rw); + err = crypt_sg(tfm, sgt_src.sgl, p_sgt_dst->sgl, pblk_num, rw); if (err) goto out; @@ -207,9 +207,9 @@ int sflite_crypt_blocks_buf(struct crypto_skcipher *tfm, void *src_buf, void *ds } out: - sgtable_teardown(&sgt_dst); if (!is_inplace) - sgtable_teardown(&sgt_src); + sgtable_teardown(&sgt_dst); + sgtable_teardown(&sgt_src); return err; } diff --git a/dm-sflc/src/lite/flush.c b/dm-sflc/src/lite/flush.c index 1612674..b5b91c7 100644 --- a/dm-sflc/src/lite/flush.c +++ b/dm-sflc/src/lite/flush.c @@ -40,7 +40,7 @@ void sflite_flush_work_fn(struct work_struct *work) goto endio; } // Just flush the position map: this function sleeps. - err = sflite_flush_posmap(svol); + err = sflite_flush_posmap(svol, GFP_NOIO); if (err) { DMERR("Could not flush position map; error %d", err); bi_status = BLK_STS_IOERR; diff --git a/dm-sflc/src/lite/posmap.c b/dm-sflc/src/lite/posmap.c index 8c0b57a..cf7cdef 100644 --- a/dm-sflc/src/lite/posmap.c +++ b/dm-sflc/src/lite/posmap.c @@ -179,7 +179,7 @@ static int __send_empty_flush(struct sflite_volume *svol) return sflc_dm_io(&req, 1, ®ion, NULL); } -static int __serialise_and_encrypt_posmap_blocks(struct sflite_volume *svol, u32 i, u32 j) +static int __serialise_and_encrypt_posmap_blocks(struct sflite_volume *svol, u32 i, u32 j, gfp_t gfp_flags) { u32 first_lsi = i * SFLITE_PSIS_PER_BLOCK; u32 last_lsi = min(j * SFLITE_PSIS_PER_BLOCK, svol->sdev->tot_slices); @@ -195,17 +195,17 @@ static int __serialise_and_encrypt_posmap_blocks(struct sflite_volume *svol, u32 } // Encrypt `crypt_entries` in-place - return sflite_crypt_blocks_vm(svol->tfm, + return sflite_crypt_blocks_buf(svol->tfm, start_ptr, start_ptr, j - i, (SFLITE_POSMAP_START_SECTOR(svol) >> SFLITE_BLOCK_SHIFT) + i, - WRITE); + WRITE, gfp_flags); } /** * Synchronously store (and flush) all the dirty posmap blocks. */ -int sflite_flush_posmap(struct sflite_volume *svol) +int sflite_flush_posmap(struct sflite_volume *svol, gfp_t gfp_flags) { struct sflite_device *sdev = svol->sdev; struct dm_io_region region; @@ -228,8 +228,8 @@ int sflite_flush_posmap(struct sflite_volume *svol) break; j = find_next_zero_bit(svol->posmap.dirty_bitmap, bitmap_bits, i); - // Encrypt from block i to j (exclusive) - err = __serialise_and_encrypt_posmap_blocks(svol, i, j); + // Encrypt from block i (inclusive) to j (exclusive) + err = __serialise_and_encrypt_posmap_blocks(svol, i, j, gfp_flags); if (unlikely(err)) break; @@ -304,15 +304,16 @@ static int __read_encrypted_posmap(struct sflite_volume *svol) /** * 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 (only once, at the end). + * resolve it locally by sampling a new PSI. + * Returns a boolean (by ref) signalling whether the posmap had corruption. */ -static int __deserialise_and_sanitise_posmap(struct sflite_volume *svol) +static int __deserialise_and_sanitise_posmap_local(struct sflite_volume *svol, bool *had_corruption) { struct sflite_device *sdev = svol->sdev; - bool had_corruption = false; int err; u32 lsi; + *had_corruption = false; for (lsi = 0; lsi < sdev->tot_slices; lsi++) { /* De-serialise posmap entry */ __be32 *be_psi = (__be32*) (svol->posmap.entries + lsi); @@ -338,16 +339,12 @@ static int __deserialise_and_sanitise_posmap(struct sflite_volume *svol) err = __peek_next_free_psi(sdev, &psi); if (err) return err; - had_corruption = sanitised = true; + *had_corruption = sanitised = true; } /* Whether sanitised or not, create the mapping locally */ __create_local_slice_mapping(svol, lsi, psi, sanitised); // Set dirty bitmap only if PSI has just been sampled } - // Only write back at the end, if at least one sanitisation happened - if (had_corruption) - err = sflite_flush_posmap(svol); - return err; } @@ -357,8 +354,9 @@ static int __deserialise_and_sanitise_posmap(struct sflite_volume *svol) * (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. */ -int sflite_load_and_sanitise_posmap(struct sflite_volume *svol) +int sflite_load_and_sanitise_posmap(struct sflite_volume *svol, gfp_t gfp_flags) { + bool had_corruption; int err; /* Read raw posmap from disk onto `crypt_entries` */ @@ -367,18 +365,25 @@ int sflite_load_and_sanitise_posmap(struct sflite_volume *svol) return err; /* Decrypt from `crypt_entries` onto cleartext `entries` */ - err = sflite_crypt_blocks_vm(svol->tfm, svol->posmap.crypt_entries, svol->posmap.entries, + err = sflite_crypt_blocks_buf(svol->tfm, svol->posmap.crypt_entries, svol->posmap.entries, svol->sdev->posmap_size_blocks, SFLITE_POSMAP_START_SECTOR(svol) >> SFLITE_BLOCK_SHIFT, - READ); + READ, gfp_flags); if (err) return err; /* Deserialise and sanitise as you go */ - err = __deserialise_and_sanitise_posmap(svol); + err = __deserialise_and_sanitise_posmap_local(svol, &had_corruption); if (err) return err; + // Write back if at least one sanitisation happened + if (had_corruption) { + err = sflite_flush_posmap(svol, gfp_flags); + if (err) + return err; + } + // print_hex_dump(KERN_CRIT, "posmap(REV) ", DUMP_PREFIX_OFFSET, 32, 4, svol->posmap.entries, 4*sdev->tot_slices, false); // msleep(2000); diff --git a/dm-sflc/src/lite/sflc_lite.h b/dm-sflc/src/lite/sflc_lite.h index 0aa1ea0..6f7ff23 100644 --- a/dm-sflc/src/lite/sflc_lite.h +++ b/dm-sflc/src/lite/sflc_lite.h @@ -67,14 +67,14 @@ void sflite_flush_work_fn(struct work_struct *work); void sflite_discard_work_fn(struct work_struct *work); /* Position map */ -int sflite_load_and_sanitise_posmap(struct sflite_volume *svol); +int sflite_load_and_sanitise_posmap(struct sflite_volume *svol, gfp_t gfp_flags); int sflite_create_local_slice_mapping(struct sflite_volume *svol, u32 lsi, u32 *psi); int sflite_destroy_local_slice_mapping(struct sflite_volume *svol, u32 lsi); -int sflite_flush_posmap(struct sflite_volume *svol); +int sflite_flush_posmap(struct sflite_volume *svol, gfp_t gfp_flags); /* 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_blocks_buf(struct crypto_skcipher *tfm, void *src_buf, void *dst_buf, + u64 num_blocks, u64 first_pblk_num, int rw, gfp_t gfp_flags); int sflite_crypt_block_page(struct crypto_skcipher *tfm, struct page *src_page, struct page *dst_page, u64 pblk_num, int rw); diff --git a/dm-sflc/src/lite/volume.c b/dm-sflc/src/lite/volume.c index d87a9f6..2a3e0e9 100644 --- a/dm-sflc/src/lite/volume.c +++ b/dm-sflc/src/lite/volume.c @@ -98,7 +98,7 @@ struct sflc_volume_base *sflite_vol_ctr(struct sflc_device_base *sd_base, goto bad_posmap_dirty_bitmap_alloc; } /* Load from disk */ - err = sflite_load_and_sanitise_posmap(svol); + err = sflite_load_and_sanitise_posmap(svol, GFP_KERNEL); if (err) { DMERR("Could not load position map from disk; error %d", err); goto bad_posmap_load; @@ -153,7 +153,7 @@ void sflite_vol_dtr(struct sflc_volume_base *sv_base) /* Flush on close */ if (!down_write_killable(&svol->posmap.flush_lock)) { - sflite_flush_posmap(svol); + sflite_flush_posmap(svol, GFP_KERNEL); up_write(&svol->posmap.flush_lock); } From ad843ec97de9ac0932015b53e2ca611768e0f181 Mon Sep 17 00:00:00 2001 From: toninov Date: Sun, 21 Sep 2025 12:54:43 +0200 Subject: [PATCH 08/11] Fix compilation on older kernels (reenable errors in bad DISCARDs) --- dm-sflc/src/lite/sflc_lite.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/dm-sflc/src/lite/sflc_lite.c b/dm-sflc/src/lite/sflc_lite.c index 50f4bfd..342e940 100644 --- a/dm-sflc/src/lite/sflc_lite.c +++ b/dm-sflc/src/lite/sflc_lite.c @@ -30,6 +30,8 @@ // Only to import the definition of struct sflc_volume and the args struct #include "sflc.h" +#include + /* *---------------------------- @@ -135,9 +137,13 @@ static int sflite_map(struct dm_target *ti, struct bio *bio) if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) { /* Ensure the discard request falls within a single slice */ if (unlikely(bio->bi_iter.bi_size != SFLITE_BLOCK_SIZE * SFLITE_SLICE_SCALE)) { - DMINFO("Wrong discard bio size: %u", bio->bi_iter.bi_size); - // TODO investigate where these are coming from - //bio->bi_status = BLK_STS_INVAL; // BUG: this only seems to work on recent (>6.8) kernels + DMINFO("Wrong discard bio size: %u", bio->bi_iter.bi_size); // TODO investigate where these are coming from +#if LINUX_VERSION_MAJOR >= 6 && LINUX_VERSION_PATCHLEVEL >= 11 + bio->bi_status = BLK_STS_INVAL; +#else + bio->bi_status = BLK_STS_NOTSUPP; +#endif + bio_endio(bio); return DM_MAPIO_SUBMITTED; } @@ -145,7 +151,11 @@ static int sflite_map(struct dm_target *ti, struct bio *bio) /* Ensure the logical block number is aligned with the beginning of a slice */ if (unlikely(lblk_num % SFLITE_SLICE_SCALE != 0)) { DMWARN("Unaligned discard bio!"); - //bio->bi_status = BLK_STS_INVAL; // BUG: this only seems to work on recent (>6.8) kernels +#if LINUX_VERSION_MAJOR >= 6 && LINUX_VERSION_PATCHLEVEL >= 11 + bio->bi_status = BLK_STS_INVAL; +#else + bio->bi_status = BLK_STS_NOTSUPP; +#endif bio_endio(bio); return DM_MAPIO_SUBMITTED; } From 7bad410e036cd8d3df4c229833c4cd6a4b46c2a5 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Tue, 23 Sep 2025 17:16:26 +0200 Subject: [PATCH 09/11] fix: Only set max discard granularity for newer kernels --- dm-sflc/src/lite/volume.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dm-sflc/src/lite/volume.c b/dm-sflc/src/lite/volume.c index 2a3e0e9..dc6a387 100644 --- a/dm-sflc/src/lite/volume.c +++ b/dm-sflc/src/lite/volume.c @@ -22,6 +22,7 @@ */ #include +#include #include "sflc_lite.h" #include "sflc.h" @@ -115,7 +116,9 @@ struct sflc_volume_base *sflite_vol_ctr(struct sflc_device_base *sd_base, ti->max_io_len = SFLITE_BLOCK_SCALE; ti->flush_supported = true; ti->num_flush_bios = 1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) // temporary workaround for older kernels - TODO ti->max_discard_granularity = SFLITE_BLOCK_SIZE * SFLITE_SLICE_SCALE; +#endif ti->discards_supported = true; ti->num_discard_bios = 1; ti->num_secure_erase_bios = 0; From e767b8ca81d69b4b86b3375363f80ce498e433cd Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Wed, 24 Sep 2025 10:20:35 +0200 Subject: [PATCH 10/11] style: Fix paragraph line break in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f44470..5f32aa5 100644 --- a/README.md +++ b/README.md @@ -291,8 +291,7 @@ If you have Secure Boot enabled, then your kernel will most likely refuse to loa 2. You enroll your own MOK (machine-owner key) and sign the kernel module `dm-sflc.ko` with it; you may need to recompile before signing. -Both these options have security implications, of course, which you should be aware of. -We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard operation, that is well supported by many distributions, so online guides should be easy to find. +Both these options have security implications, of course, which you should be aware of. We cannot provide a step-by-step guide for option 2, because the procedure is distro-specific. However, enrolling a MOK is a relatively standard operation, that is well supported by many distributions, so online guides should be easy to find. From a9cb3b474eabb20727edd5bb0c88a702dde51169 Mon Sep 17 00:00:00 2001 From: Tommaso Gagliardoni Date: Wed, 24 Sep 2025 10:37:14 +0200 Subject: [PATCH 11/11] chore: Prepare release v0.5.5 --- CHANGELOG.md | 11 +++++++++++ README.md | 12 ++++++++---- dm-sflc/src/sflc_constants.h | 2 +- resources/images/badges/badge_version_0.5.4.png | Bin 2330 -> 0 bytes resources/images/badges/badge_version_0.5.5.png | Bin 0 -> 2425 bytes resources/images/badges/badges.svg | 8 ++++---- 6 files changed, 24 insertions(+), 9 deletions(-) delete mode 100644 resources/images/badges/badge_version_0.5.4.png create mode 100644 resources/images/badges/badge_version_0.5.5.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 436ac88..e68082b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.5] - 2025-09-24 + +### Fixed + + - Fixed issue of bad memory allocation on some archs due to the improper use of vmalloc. The fix is experimental, as we could not reproduce the bug on RISCV architecture, so feedback is welcome. + +### Added + + - Added explanation on how to deal with unsigned kernel modules with Secure Boot in README.md. + + ## [0.5.4] - 2025-09-06 ### Fixed diff --git a/README.md b/README.md index 5f32aa5..c2b4ab5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Status](resources/images/badges/badge_status_active.png)](https://codeberg.org/shufflecake/shufflecake-c)  -[![Version](resources/images/badges/badge_version_0.5.4.png)](https://codeberg.org/shufflecake/shufflecake-c/releases/tag/v0.5.4)  +[![Version](resources/images/badges/badge_version_0.5.5.png)](https://codeberg.org/shufflecake/shufflecake-c/releases/tag/v0.5.5)  [![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/)  @@ -12,7 +12,7 @@ -# Shufflecake - Full C Implementation - v0.5.4 +# Shufflecake - Full C Implementation - v0.5.5 _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: . @@ -298,7 +298,12 @@ Both these options have security implications, of course, which you should be aw ## Changelog -Please see the file `CHANGELOG.md` for a detailed history of changes. +See the file `CHANGELOG.md` for a detailed history of changes. + +## [0.5.5] - 2025-09-24 + + - Experimental fix for the issue of bad memory allocation on some archs due to the improper use of vmalloc. + - Added instructions on how to deal with unsigned kernel modules with Secure Boot on. ## [0.5.4] - 2025-09-06 @@ -336,7 +341,6 @@ Bugs and other issues are tracked at ). - Crash consistency is currently not guaranteed (see issue ). - Only a maximum of 15 volumes per device is supported. - There is (currently) no protection against multi-snapshot attacks. diff --git a/dm-sflc/src/sflc_constants.h b/dm-sflc/src/sflc_constants.h index 4680012..97044f0 100644 --- a/dm-sflc/src/sflc_constants.h +++ b/dm-sflc/src/sflc_constants.h @@ -33,7 +33,7 @@ #define SFLC_VER_MAJOR 0 #define SFLC_VER_MINOR 5 -#define SFLC_VER_REVISION 4 +#define SFLC_VER_REVISION 5 #define SFLC_VER_SPECIAL "" #define STRINGIFY0(s) # s diff --git a/resources/images/badges/badge_version_0.5.4.png b/resources/images/badges/badge_version_0.5.4.png deleted file mode 100644 index c9a83b912f4b1cc10b1453e49872b7598024cca6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2330 zcmXArc{r5a8^<3}QK8UaVnT27qDd5E8e0ZqL}HpOk(x-1eTHn4rIPHjjhzUIkZmGN zkrFYACQJ4(=(R<*>36z*f1Kw$*Lkk%KIi`4pYMI1NOLnI!F}+35CjPtNx&0l?E^tVZ~qRSc8}tlU{R7{U`4U?aH9BK_jZK*{QQu&-Ccc1*F7DP z9^SVyeq!Jd#9w5L)gw5EF3@k|lD5R#b-Ky1mJ^f2l0LDMq|d6Rj(OGQ1XyoAmd-k_ zXV7zA>`TT$rHcCjtYta=XZx=^68KV51|FH@r;xr)emR47B$84d#m4$vdnnvRefK=@ z4`F-fO=H?i%(UmH|NK&>5Bbis$A0_~Gm1mF@!o76h|9S@dF5PBR8G5xKL>Ww=H55* z)BODL)XhAWGY*H-E$y?gwf*>O?rY`X!Y~JGU;x%%^^_5nl9Q81YD&J&2?z`C&8?`2 zKXt9?chb(u#~S|2H{nN)+`YX_K}&0DYTl6H5kg>aK|vV5pF>L}ezU#D9|%bg+|N!= z@5t5)d{rYUCf52oaGhU3fXAUD>0bNJAyGwYDHC`c?FfX*NOe zxUzEe=4L<>ZHL1=qT&D{Q={UJ>g>uTma=9k3dwjE7ncev<@#awgd&TQJAb%Z=q#c8 z=k=_uC83?Ib)(Wgxnw-Zo^bhcq*3V^LxBt0yc$d?}i)OCKESJljXIF;L z&y%aFsw8x17$xF!D3`&YhlD`(O+g>%YPX!-+}fPas8TtE3-Y2qKRTIYbxR`g-4Mek zGA5=lFE12?JOdXLMqow;8D%BU-@)}o}M1o(9nDP zx7WPyIhUGrHrq;7O^p$}r4ChAR(57+#ARk?A_dWCbUH{XAv@d5-X0+md9S`6b%R8z zv{JTGhCxo9X?j5Kwzf9@u$JYik96%|w4|hD{nc{B?kA#5CKC$USZ;fQ>HqR2_wy{4 zt8+SF)frm*Z{(t`ZV0ZKItPmqKecA#!b5yvZwlBnNksyY^V(^fR}X`8n=6RTP|yiZ zXW;%D%g3oyDsa)5H={n_*E6f}PA9e9yLU^(7+x|0T4!g+_PG?+)Eus{0i_7qTs2N& z1g@8NYioFoV}XkP{{Ff~m*2mCpWD=wU0keisr=cqN8{r+awit)7vzCp01ho7!xOuB zd3l`4Q&Yh`;h|Mks$X2r9y5#{eRF?5bT1-ef`X=>^DB?AcXn0)%JYXo6uzy6 z!8*A=0Btr|Juxvc4NX<-&enPhh_ka>=!=VwKe%uIel2HY>i&$bcP*j31~#UqrsDJ* z14~QM9#&xU{hXYmA>G$GJ$d?>+QI4Kr|j>nE_P*Ty!4*J$4Y7T8l2G7l#DTan{v{Y z#_o1cT?Of`2570$4}>#yO-!Otp16NRhTJt2lRGA+Jy8mZiszB3ywbrFgSxM-BF!>U z%&Cv5Ug)g9EM%SYtMXs@fngSpq5X%OP_ zgss@&!(P*$q~lttYn=(li~{=TT7l~L!o-daJlIY>!*ud63|8efsGdXfZBR@~NvU#R z?y58v7Z;B@-aF=b5DxDEJ8Mz4cXlo*CTq_%06QU5EzQk!_4IfEf&!xQ@o8x|zzgV- zh-IOOx2|yl0s^zXp*tEg^H1=FBA}P}`)kr@v}j4SJuP!pWfmnU-+76i%FnN9JMfvb zL>>-@(>U;~y!@%hNYnUT`h|D#vic#hzw(=!z8B>Te{Tf6Xz54S5vNa?k;Kwp3fG9G z$=sc-z)Xb_LdUz9Bi2fEJ)d8(_c9aJRZ z@r9rUz6s61`{3$uoa-+WgTdrdKY})zS6fSzy1{j=uB&?)xbEKD+Y7EX8ChAqNonn% zw1EMeToRlAzyWNb1y68shWu=n@OpumIMM8hvRpz%b4!Y@fmI5iObV3Ay^Oyf3MTx;^Gz% zCL=>>&9Gtk6EM7WgKyby3xFW0ZqR>q<`9Fyu$$i(79zus30QqU$C;EJ+%le74>Dkj puF^y{!&w=PIufrxZ2kY`E^iF?8O*iw&EG#C$k@OPTYlal?0;Xpa2Nmp diff --git a/resources/images/badges/badge_version_0.5.5.png b/resources/images/badges/badge_version_0.5.5.png new file mode 100644 index 0000000000000000000000000000000000000000..588a6bc1f04077643d5557bb6029efcd9f22aab4 GIT binary patch literal 2425 zcmV-<35NEGP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12?j|- zK~!jg?U`+CjA<9ge{)Y~Ivtr--;`<%p{A`eMm5qVh^0yd+kWs;HiRf?LN*dfMIs_3 z>^6K@>w|=3?M8|UvRR>+vPfSuMW=K}htYOuOYf@fH0^X=o)44Wk1;ddUD+qdRom0J+U(qot)Ks7_K+5~-=F0rG)?0q)+t z8`9P%8NAuEXX9`6DCXuY3q{=-qE8+85kJ&s6HG&ew@U_L>@nW%#T0*NMT_i zIXOA}^wUpFnluTg(;1+nR;!sXVFEopJv@E-)NgF08=IDv27t+A!t4ES3DViwIiin2 zgD02E88>bmy}iBs`RAX0V+jcfxZQ4EzkUsXMx#Ne)1gwS=;-LcYPCuZXfztsYBimm zo#=Erlu9Lyjg0_|88Zg0R*PIN$8NXN*48H3P^nb7TrNMm$;ruRwOU**7gnp4SFc|A z`PARv&*0!7>FMc=8Z`>L-Hy#>8*%ZWFn;`azWVAbz;`Qt|NVC^U%pIhYb%L~i7Z&K zfNR&T1?cH?Is`%B^y$-l`|Y=X+g@sFDz9I^CNnehANd(Hcpi_34I4Jl+SgoG=#xmV9uO5n9XJuEn0-d zVxh6Ik=)!|^78WN>+8eg@emai#hp8MxOC|f07XSb+`W63n>TMFlgU`Sb}gBinK&E{ z!otE(DwR}JR8Ub-0l+ukd_!|{GwJE+$mMbbK_Dh3hFiC8@$0X@{-X^6m@#7p07FAV zR99C6(B9sT#bQCH(_t_ef(CCmZ&|HY^7HdaN=l-ssfo(UO1~TB7l5!Zrc9YaZEY=? znVDo~XVcx?&2PW`#@n}VNAxpj@Vs6x4Gj&UZLvNB926Ec~M z)vH%CckW!ST)BeV?H+Lx-bZX~EC6rczV)NWj*bp=I-NBBK8EShG za^=dPR6BX{WRyxJdcB^yx;kVs8Q`PLB}oSgQTNWDk(27$7ZuJG&F=pqd_K<(cRrG&AD~! zR>(XzYt}5RRx4FiRe+Bbu3Wi7Z*MPISy|G&wzf7*CKKR0d}gy5xm->}L`2BNheC98 zGyo2V!%se(LWalR$MDU2^X3gDB_$j`ejK;k&5|Wc*s)_rP@Um`j~X?KW5aA&3z;)#4v!u^!eX(|-rgS6kGQxvYHMr#WNx>c z*49?WjvXt>IyyQ6=6opn`}+Z3?tK^G;o$%wi>dFv`;HSQPVmPcfB5C_;^Jb~ty@QF zX=#8?eSJN-xw-WA_EJ|@2f&>>cer=&9tMMfw6rvunwo;z8?o9nnM^EPxR9{0F!Xvo zgM))o!666&UauFmTJ5JJiXx&YqEILRU*33kBi(K{$BrFi_UzeYWMr^-@nTl2Sb^1Q z<4(W6HL__4;jsIRXNkX2V# zQ(Rn3N=iylo%Z(jfP-6GTZ_lzK@bE|Qc^xFc=zw$XX(!21`JyNW%`tItaetZ6 zKNQ1xRHagpo}NxqQxoCg;Y^!04IpT*=07dj|NbWPqNYo}jk2Z{JQv zMg~Tsk+Wyd0={Z@m@ePEc@xsl5ueApySsV*{5kXI&qu4(`q7uoW@F5lF@7gXE|;@) z>sG%0`s#z<~pd8#fN{(dvT-4{$o2em7|t&!0a>uh*l|Xe3#3axxPqPW+!W z@n*AG+Wh_d_cLwUH1_Y`k6Nw9>2yk!W@BR`q9}6Y$PxPc`&qVZ8AU}!96EG}w6rvq zFJCUT48v9L-o1MfMNy*F;!mxugbOG^uVeSK`&w27LU8U_ak z(d+fdD9Fjlp{uJ)Dq{X^csw3TN=hgzE2E&GfP#Vo zY3%#&zo)CK3-CRkMMp-ADyUG2y9Uayx24;~+6$f2*Bxd(%jrkY-}tkDJhH}KVH%sE;T0#~tRaI3ULaSyz^%>00000NkvXXu0mjfrK6`| literal 0 HcmV?d00001 diff --git a/resources/images/badges/badges.svg b/resources/images/badges/badges.svg index 2575c03..81f8c8a 100644 --- a/resources/images/badges/badges.svg +++ b/resources/images/badges/badges.svg @@ -29,8 +29,8 @@ inkscape:document-units="mm" showgrid="false" inkscape:zoom="2.7438272" - inkscape:cx="235.98425" - inkscape:cy="228.69516" + inkscape:cx="236.3487" + inkscape:cy="229.05961" inkscape:window-width="2560" inkscape:window-height="1377" inkscape:window-x="0" @@ -333,7 +333,7 @@ height="5.2916675" x="61.548569" y="79.569435" - inkscape:export-filename="badge_version_0.5.4.png" + inkscape:export-filename="badge_version_0.5.5.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" /> 0.5.4 + y="83.4077">0.5.5