aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Washburn <development@efficientek.com>2022-06-08 10:34:03 -0500
committerDaniel Kiper <daniel.kiper@oracle.com>2022-07-04 14:43:25 +0200
commit1deb521452b288fe8256dcc7bc14228aa42b568e (patch)
treef2b9d65b3db0d1d6995fb2106652840f547bba6e
parentf5a92e6040266015bfccaafd1f89bf14d92ef013 (diff)
downloadgrub-1deb521452b288fe8256dcc7bc14228aa42b568e.tar.gz
cryptodisk: Add support for using detached header files
Using the disk read hook mechanism, setup a read hook on the source disk which will read from the given header file during the scan and recovery cryptodisk backend functions. Disk read hooks are executed after the data has been read from the disk. This is okay, because the read hook is given the read buffer before its sent back to the caller. In this case, the hook can then overwrite the data read from the disk device with data from the header file sent in as the read hook data. This is transparent to the read caller. Since the callers of this function have just opened the source disk, there are no current read hooks, so there's no need to save/restore them nor consider if they should be called or not. This hook assumes that the header is at the start of the volume, which is not the case for some formats (e.g. GELI). So GELI will return an error if a detached header is specified. It also can only be used with formats where the detached header file can be written to the first blocks of the volume and the volume could still be unlocked. So the header file can not be formatted differently from the on-disk header. If these assumpts are not met, detached header file processing must be specially handled in the cryptodisk backend module. The hook will be called potentially many times by a backend. This is fine because of the assumptions mentioned and the read hook reads from absolute offsets and is stateless. Also add a --header (short -H) option to cryptomount which takes a file argument. Signed-off-by: Glenn Washburn <development@efficientek.com> Reviewed-by: Patrick Steinhardt <ps@pks.im> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
-rw-r--r--grub-core/disk/cryptodisk.c92
-rw-r--r--grub-core/disk/geli.c4
-rw-r--r--include/grub/cryptodisk.h2
-rw-r--r--include/grub/file.h2
4 files changed, 96 insertions, 4 deletions
diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
index c80cf9907..f1fe0d390 100644
--- a/grub-core/disk/cryptodisk.c
+++ b/grub-core/disk/cryptodisk.c
@@ -43,7 +43,8 @@ enum
OPTION_PASSWORD,
OPTION_KEYFILE,
OPTION_KEYFILE_OFFSET,
- OPTION_KEYFILE_SIZE
+ OPTION_KEYFILE_SIZE,
+ OPTION_HEADER
};
static const struct grub_arg_option options[] =
@@ -56,9 +57,16 @@ static const struct grub_arg_option options[] =
{"key-file", 'k', 0, N_("Key file"), 0, ARG_TYPE_STRING},
{"keyfile-offset", 'O', 0, N_("Key file offset (bytes)"), 0, ARG_TYPE_INT},
{"keyfile-size", 'S', 0, N_("Key file data size (bytes)"), 0, ARG_TYPE_INT},
+ {"header", 'H', 0, N_("Read header from file"), 0, ARG_TYPE_STRING},
{0, 0, 0, 0, 0, 0}
};
+struct cryptodisk_read_hook_ctx
+{
+ grub_file_t hdr_file;
+};
+typedef struct cryptodisk_read_hook_ctx *cryptodisk_read_hook_ctx_t;
+
/* Our irreducible polynom is x^128+x^7+x^2+x+1. Lowest byte of it is: */
#define GF_POLYNOM 0x87
static inline int GF_PER_SECTOR (const struct grub_cryptodisk *dev)
@@ -1004,6 +1012,30 @@ cryptodisk_close (grub_cryptodisk_t dev)
grub_free (dev);
}
+static grub_err_t
+cryptodisk_read_hook (grub_disk_addr_t sector, unsigned offset,
+ unsigned length, char *buf, void *data)
+{
+ cryptodisk_read_hook_ctx_t ctx = data;
+
+ if (ctx->hdr_file == NULL)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("header file not found"));
+
+ if (grub_file_seek (ctx->hdr_file,
+ (sector * GRUB_DISK_SECTOR_SIZE) + offset)
+ == (grub_off_t) -1)
+ return grub_errno;
+
+ if (grub_file_read (ctx->hdr_file, buf, length) != (grub_ssize_t) length)
+ {
+ if (grub_errno == GRUB_ERR_NONE)
+ grub_error (GRUB_ERR_OUT_OF_RANGE, N_("header file too small"));
+ return grub_errno;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
static grub_cryptodisk_t
grub_cryptodisk_scan_device_real (const char *name,
grub_disk_t source,
@@ -1012,6 +1044,7 @@ grub_cryptodisk_scan_device_real (const char *name,
grub_err_t ret = GRUB_ERR_NONE;
grub_cryptodisk_t dev;
grub_cryptodisk_dev_t cr;
+ struct cryptodisk_read_hook_ctx read_hook_data = {0};
int askpass = 0;
char *part = NULL;
@@ -1020,11 +1053,46 @@ grub_cryptodisk_scan_device_real (const char *name,
if (dev)
return dev;
+ if (cargs->hdr_file != NULL)
+ {
+ /*
+ * Set read hook to read header from a file instead of the source disk.
+ * Disk read hooks are executed after the data has been read from the
+ * disk. This is okay, because the read hook is given the read buffer
+ * before its sent back to the caller. In this case, the hook can then
+ * overwrite the data read from the disk device with data from the
+ * header file sent in as the read hook data. This is transparent to the
+ * read caller. Since the callers of this function have just opened the
+ * source disk, there are no current read hooks, so there's no need to
+ * save/restore them nor consider if they should be called or not.
+ *
+ * This hook assumes that the header is at the start of the volume, which
+ * is not the case for some formats (eg. GELI). It also can only be used
+ * with formats where the detached header file can be written to the
+ * first blocks of the volume and the volume could still be unlocked.
+ * So the header file can not be formatted differently from the on-disk
+ * header. If these assumpts are not met, detached header file processing
+ * must be specially handled in the cryptodisk backend module.
+ *
+ * This hook needs only be set once and will be called potentially many
+ * times by a backend. This is fine because of the assumptions mentioned
+ * and the read hook reads from absolute offsets and is stateless.
+ */
+ read_hook_data.hdr_file = cargs->hdr_file;
+ source->read_hook = cryptodisk_read_hook;
+ source->read_hook_data = (void *) &read_hook_data;
+ }
+
FOR_CRYPTODISK_DEVS (cr)
{
+ /*
+ * Loop through each cryptodisk backend that is registered (ie. loaded).
+ * If the scan returns NULL, then the backend being tested does not
+ * recognize the source disk, so move on to the next backend.
+ */
dev = cr->scan (source, cargs);
if (grub_errno)
- return NULL;
+ goto error_no_close;
if (!dev)
continue;
@@ -1041,7 +1109,7 @@ grub_cryptodisk_scan_device_real (const char *name,
cargs->key_data = grub_malloc (GRUB_CRYPTODISK_MAX_PASSPHRASE);
if (cargs->key_data == NULL)
- return NULL;
+ goto error_no_close;
if (!grub_password_get ((char *) cargs->key_data, GRUB_CRYPTODISK_MAX_PASSPHRASE))
{
@@ -1066,9 +1134,13 @@ grub_cryptodisk_scan_device_real (const char *name,
error:
cryptodisk_close (dev);
+ error_no_close:
dev = NULL;
cleanup:
+ if (cargs->hdr_file != NULL)
+ source->read_hook = NULL;
+
if (askpass)
{
cargs->key_len = 0;
@@ -1260,6 +1332,18 @@ grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int argc, char **args)
return grub_error (GRUB_ERR_FILE_READ_ERROR, (N_("failed to read key file")));
}
+ if (state[OPTION_HEADER].set) /* header */
+ {
+ if (state[OPTION_UUID].set)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ N_("cannot use UUID lookup with detached header"));
+
+ cargs.hdr_file = grub_file_open (state[OPTION_HEADER].arg,
+ GRUB_FILE_TYPE_CRYPTODISK_DETACHED_HEADER);
+ if (cargs.hdr_file == NULL)
+ return grub_errno;
+ }
+
if (state[OPTION_UUID].set) /* uuid */
{
int found_uuid;
@@ -1473,7 +1557,7 @@ GRUB_MOD_INIT (cryptodisk)
grub_disk_dev_register (&grub_cryptodisk_dev);
cmd = grub_register_extcmd ("cryptomount", grub_cmd_cryptomount, 0,
N_("[ [-p password] | [-k keyfile"
- " [-O keyoffset] [-S keysize] ] ]"
+ " [-O keyoffset] [-S keysize] ] ] [-H file]"
" <SOURCE|-u UUID|-a|-b>"),
N_("Mount a crypto device."), options);
grub_procfs_register ("luks_script", &luks_script);
diff --git a/grub-core/disk/geli.c b/grub-core/disk/geli.c
index 91eb10122..b3c9bbd80 100644
--- a/grub-core/disk/geli.c
+++ b/grub-core/disk/geli.c
@@ -252,6 +252,10 @@ geli_scan (grub_disk_t disk, grub_cryptomount_args_t cargs)
grub_disk_addr_t sector;
grub_err_t err;
+ /* Detached headers are not implemented yet */
+ if (cargs->hdr_file != NULL)
+ return NULL;
+
if (2 * GRUB_MD_SHA256->mdlen + 1 > GRUB_CRYPTODISK_MAX_UUID_LENGTH)
return NULL;
diff --git a/include/grub/cryptodisk.h b/include/grub/cryptodisk.h
index 467065f00..d94df68b6 100644
--- a/include/grub/cryptodisk.h
+++ b/include/grub/cryptodisk.h
@@ -20,6 +20,7 @@
#define GRUB_CRYPTODISK_HEADER 1
#include <grub/disk.h>
+#include <grub/file.h>
#include <grub/crypto.h>
#include <grub/list.h>
#ifdef GRUB_UTIL
@@ -79,6 +80,7 @@ struct grub_cryptomount_args
grub_uint8_t *key_data;
/* recover_key: Length of key_data */
grub_size_t key_len;
+ grub_file_t hdr_file;
};
typedef struct grub_cryptomount_args *grub_cryptomount_args_t;
diff --git a/include/grub/file.h b/include/grub/file.h
index 153bea86f..a5bf3a792 100644
--- a/include/grub/file.h
+++ b/include/grub/file.h
@@ -92,6 +92,8 @@ enum grub_file_type
GRUB_FILE_TYPE_ZFS_ENCRYPTION_KEY,
/* File holding the encryption key. */
GRUB_FILE_TYPE_CRYPTODISK_ENCRYPTION_KEY,
+ /* File holding the encryption metadata header */
+ GRUB_FILE_TYPE_CRYPTODISK_DETACHED_HEADER,
/* File we open n grub-fstest. */
GRUB_FILE_TYPE_FSTEST,
/* File we open n grub-mount. */