blob: be1b4aa8f28bcf1682b20bcd3a8c5cb9e0815bc9 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2024 Google LLC
*/
#include <linux/blk-crypto.h>
#include <linux/ctype.h>
#include <linux/device-mapper.h>
#include <linux/hex.h>
#include <linux/module.h>
#include <keys/user-type.h>
#define DM_MSG_PREFIX "inlinecrypt"
static const struct dm_inlinecrypt_cipher {
const char *name;
enum blk_crypto_mode_num mode_num;
} dm_inlinecrypt_ciphers[] = {
{
.name = "aes-xts-plain64",
.mode_num = BLK_ENCRYPTION_MODE_AES_256_XTS,
},
};
/**
* struct inlinecrypt_ctx - private data of an inlinecrypt target
* @dev: the underlying device
* @start: starting sector of the range of @dev which this target actually maps.
* For this purpose a "sector" is 512 bytes.
* @cipher_string: the name of the encryption algorithm being used
* @key_size: size of the encryption key in bytes
* @iv_offset: starting offset for IVs. IVs are generated as if the target were
* preceded by @iv_offset 512-byte sectors.
* @sector_size: crypto sector size in bytes (usually 4096)
* @sector_bits: log2(sector_size)
* @key_type: type of the key -- either raw or hardware-wrapped
* @key: the encryption key to use
* @max_dun: the maximum DUN that may be used (computed from other params)
*/
struct inlinecrypt_ctx {
struct dm_dev *dev;
sector_t start;
const char *cipher_string;
unsigned int key_size;
u64 iv_offset;
unsigned int sector_size;
unsigned int sector_bits;
enum blk_crypto_key_type key_type;
struct blk_crypto_key key;
u64 max_dun;
};
static const struct dm_inlinecrypt_cipher *
lookup_cipher(const char *cipher_string)
{
int i;
for (i = 0; i < ARRAY_SIZE(dm_inlinecrypt_ciphers); i++) {
if (strcmp(cipher_string, dm_inlinecrypt_ciphers[i].name) == 0)
return &dm_inlinecrypt_ciphers[i];
}
return NULL;
}
static void inlinecrypt_dtr(struct dm_target *ti)
{
struct inlinecrypt_ctx *ctx = ti->private;
if (ctx->dev) {
if (ctx->key.size)
blk_crypto_evict_key(ctx->dev->bdev, &ctx->key);
dm_put_device(ti, ctx->dev);
}
kfree_sensitive(ctx->cipher_string);
kfree_sensitive(ctx);
}
#ifdef CONFIG_KEYS
static bool contains_whitespace(const char *str)
{
while (*str)
if (isspace(*str++))
return true;
return false;
}
static int set_key_user(struct key *key, char *key_bytes,
const unsigned int key_bytes_size)
{
const struct user_key_payload *ukp;
ukp = user_key_payload_locked(key);
if (!ukp)
return -EKEYREVOKED;
if (key_bytes_size != ukp->datalen)
return -EINVAL;
memcpy(key_bytes, ukp->data, key_bytes_size);
return 0;
}
static int inlinecrypt_get_keyring_key(const char *key_string, u8 *key_bytes,
const unsigned int key_bytes_size)
{
char *key_desc;
int ret;
struct key_type *type;
struct key *key;
int (*set_key)(struct key *key, char *key_bytes,
const unsigned int key_bytes_size);
/*
* Reject key_string with whitespace. dm core currently lacks code for
* proper whitespace escaping in arguments on DM_TABLE_STATUS path.
*/
if (contains_whitespace(key_string)) {
DMERR("whitespace chars not allowed in key string");
return -EINVAL;
}
/* look for next ':' separating key_type from key_description */
key_desc = strchr(key_string, ':');
if (!key_desc || key_desc == key_string || !strlen(key_desc + 1))
return -EINVAL;
if (!strncmp(key_string, "logon:", key_desc - key_string + 1)) {
type = &key_type_logon;
set_key = set_key_user;
} else {
return -EINVAL;
}
key = request_key(type, key_desc + 1, NULL);
if (IS_ERR(key))
return PTR_ERR(key);
down_read(&key->sem);
ret = set_key(key, (char *)key_bytes, key_bytes_size);
up_read(&key->sem);
key_put(key);
return ret;
}
static int get_key_size(char **key_string)
{
char *colon, dummy;
int ret;
if (*key_string[0] != ':') {
ret = strlen(*key_string);
if (ret > 2 * BLK_CRYPTO_MAX_ANY_KEY_SIZE
|| ret % 2
|| !ret) {
DMERR("Invalid keysize");
return -EINVAL;
}
return ret >> 1;
}
/* look for next ':' in key string */
colon = strpbrk(*key_string + 1, ":");
if (!colon)
return -EINVAL;
if (sscanf(*key_string + 1, "%u%c", &ret, &dummy) != 2 || dummy != ':')
return -EINVAL;
/* remaining key string should be :<logon|user>:<key_desc> */
*key_string = colon;
return ret;
}
#else
static int inlinecrypt_get_keyring_key(const char *key_string, u8 *key_bytes,
const unsigned int key_bytes_size)
{
return -EINVAL;
}
static int get_key_size(char **key_string)
{
int key_hex_size = strlen(*key_string);
if (*key_string[0] == ':')
return -EINVAL;
if (key_hex_size > 2 * BLK_CRYPTO_MAX_ANY_KEY_SIZE
|| key_hex_size % 2
|| !key_hex_size) {
DMERR("Invalid keysize");
return -EINVAL;
}
return key_hex_size >> 1;
}
#endif /* CONFIG_KEYS */
static int inlinecrypt_get_key(const char *key_string,
u8 key[BLK_CRYPTO_MAX_ANY_KEY_SIZE],
const unsigned int key_size)
{
int ret = 0;
if (key_size > BLK_CRYPTO_MAX_ANY_KEY_SIZE) {
DMERR("Invalid keysize");
return -EINVAL;
}
/* ':' means the key is in kernel keyring, short-circuit normal key processing */
if (key_string[0] == ':') {
/* key string should be :<logon|user>:<key_desc> */
ret = inlinecrypt_get_keyring_key(key_string + 1, key, key_size);
goto out;
}
if (hex2bin(key, key_string, key_size) != 0)
ret = -EINVAL;
out:
return ret;
}
static int inlinecrypt_ctr_optional(struct dm_target *ti,
unsigned int argc, char **argv)
{
struct inlinecrypt_ctx *ctx = ti->private;
struct dm_arg_set as;
static const struct dm_arg _args[] = {
{0, 4, "Invalid number of feature args"},
};
unsigned int opt_params;
const char *opt_string;
bool iv_large_sectors = false;
char dummy;
int err;
as.argc = argc;
as.argv = argv;
err = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
if (err)
return err;
while (opt_params--) {
opt_string = dm_shift_arg(&as);
if (!opt_string) {
ti->error = "Not enough feature arguments";
return -EINVAL;
}
if (str_has_prefix(opt_string, "keytype:")) {
const char *val = opt_string + strlen("keytype:");
if (!*val) {
ti->error = "Invalid block key type";
return -EINVAL;
}
if (!strcmp(val, "raw")) {
ctx->key_type = BLK_CRYPTO_KEY_TYPE_RAW;
} else if (!strcmp(val, "hw-wrapped")) {
ctx->key_type = BLK_CRYPTO_KEY_TYPE_HW_WRAPPED;
} else {
ti->error = "Invalid block key type";
return -EINVAL;
}
} else if (!strcmp(opt_string, "allow_discards")) {
ti->num_discard_bios = 1;
} else if (sscanf(opt_string, "sector_size:%u%c",
&ctx->sector_size, &dummy) == 1) {
if (ctx->sector_size < SECTOR_SIZE ||
ctx->sector_size > 4096 ||
!is_power_of_2(ctx->sector_size)) {
ti->error = "Invalid sector_size";
return -EINVAL;
}
} else if (!strcmp(opt_string, "iv_large_sectors")) {
iv_large_sectors = true;
} else {
ti->error = "Invalid feature arguments";
return -EINVAL;
}
}
/* dm-inlinecrypt doesn't implement iv_large_sectors=false. */
if (ctx->sector_size != SECTOR_SIZE && !iv_large_sectors) {
ti->error = "iv_large_sectors must be specified";
return -EINVAL;
}
return 0;
}
/*
* Construct an inlinecrypt mapping:
* <cipher> [<key>|:<key_size>:<logon>:<key_description>] <iv_offset> <dev_path> <start>
*
* This syntax matches dm-crypt's, but the set of supported functionality has
* been stripped down.
*/
static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
struct inlinecrypt_ctx *ctx;
const struct dm_inlinecrypt_cipher *cipher;
u8 key_bytes[BLK_CRYPTO_MAX_ANY_KEY_SIZE];
unsigned int dun_bytes;
unsigned long long tmpll;
char dummy;
int err;
if (argc < 5) {
ti->error = "Not enough arguments";
return -EINVAL;
}
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
ti->error = "Out of memory";
return -ENOMEM;
}
ti->private = ctx;
/* <cipher> */
ctx->cipher_string = kstrdup(argv[0], GFP_KERNEL);
if (!ctx->cipher_string) {
ti->error = "Out of memory";
err = -ENOMEM;
goto bad;
}
cipher = lookup_cipher(ctx->cipher_string);
if (!cipher) {
ti->error = "Unsupported cipher";
err = -EINVAL;
goto bad;
}
/* <key> */
err = get_key_size(&argv[1]);
if (err < 0) {
ti->error = "Cannot parse key size";
return -EINVAL;
}
ctx->key_size = err;
err = inlinecrypt_get_key(argv[1], key_bytes, ctx->key_size);
if (err) {
ti->error = "Malformed key string";
goto bad;
}
/* <iv_offset> */
if (sscanf(argv[2], "%llu%c", &ctx->iv_offset, &dummy) != 1) {
ti->error = "Invalid iv_offset sector";
err = -EINVAL;
goto bad;
}
/* <dev_path> */
err = dm_get_device(ti, argv[3], dm_table_get_mode(ti->table),
&ctx->dev);
if (err) {
ti->error = "Device lookup failed";
goto bad;
}
/* <start> */
if (sscanf(argv[4], "%llu%c", &tmpll, &dummy) != 1 ||
tmpll != (sector_t)tmpll) {
ti->error = "Invalid start sector";
err = -EINVAL;
goto bad;
}
ctx->start = tmpll;
/* optional arguments */
ctx->sector_size = SECTOR_SIZE;
ctx->key_type = BLK_CRYPTO_KEY_TYPE_RAW;
if (argc > 5) {
err = inlinecrypt_ctr_optional(ti, argc - 5, &argv[5]);
if (err)
goto bad;
}
ctx->sector_bits = ilog2(ctx->sector_size);
if (ti->len & ((ctx->sector_size >> SECTOR_SHIFT) - 1)) {
ti->error = "Device size is not a multiple of sector_size";
err = -EINVAL;
goto bad;
}
if (ctx->iv_offset & ((ctx->sector_size >> SECTOR_SHIFT) - 1)) {
ti->error = "Wrong alignment of iv_offset sector";
err = -EINVAL;
}
ctx->max_dun = (ctx->iv_offset + ti->len - 1) >>
(ctx->sector_bits - SECTOR_SHIFT);
dun_bytes = DIV_ROUND_UP(fls64(ctx->max_dun), 8);
err = blk_crypto_init_key(&ctx->key, key_bytes, ctx->key_size,
ctx->key_type, cipher->mode_num,
dun_bytes, ctx->sector_size);
if (err) {
ti->error = "Error initializing blk-crypto key";
goto bad;
}
err = blk_crypto_start_using_key(ctx->dev->bdev, &ctx->key);
if (err) {
ti->error = "Error starting to use blk-crypto";
goto bad;
}
ti->num_flush_bios = 1;
err = 0;
goto out;
bad:
inlinecrypt_dtr(ti);
out:
memzero_explicit(key_bytes, sizeof(key_bytes));
return err;
}
static int inlinecrypt_map(struct dm_target *ti, struct bio *bio)
{
const struct inlinecrypt_ctx *ctx = ti->private;
sector_t sector_in_target;
u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = {};
bio_set_dev(bio, ctx->dev->bdev);
/*
* If the bio is a device-level request which doesn't target a specific
* sector, there's nothing more to do.
*/
if (bio_sectors(bio) == 0)
return DM_MAPIO_REMAPPED;
/*
* The bio should never have an encryption context already, since
* dm-inlinecrypt doesn't pass through any inline encryption
* capabilities to the layer above it.
*/
if (WARN_ON_ONCE(bio_has_crypt_ctx(bio)))
return DM_MAPIO_KILL;
/* Map the bio's sector to the underlying device. (512-byte sectors) */
sector_in_target = dm_target_offset(ti, bio->bi_iter.bi_sector);
bio->bi_iter.bi_sector = ctx->start + sector_in_target;
/*
* If the bio doesn't have any data (e.g. if it's a DISCARD request),
* there's nothing more to do.
*/
if (!bio_has_data(bio))
return DM_MAPIO_REMAPPED;
/* Calculate the DUN and enforce data-unit (crypto sector) alignment. */
dun[0] = ctx->iv_offset + sector_in_target; /* 512-byte sectors */
if (dun[0] & ((ctx->sector_size >> SECTOR_SHIFT) - 1))
return DM_MAPIO_KILL;
dun[0] >>= ctx->sector_bits - SECTOR_SHIFT; /* crypto sectors */
/*
* This check isn't necessary as we should have calculated max_dun
* correctly, but be safe.
*/
if (WARN_ON_ONCE(dun[0] > ctx->max_dun))
return DM_MAPIO_KILL;
bio_crypt_set_ctx(bio, &ctx->key, dun, GFP_NOIO);
/*
* Since we've added an encryption context to the bio and
* blk-crypto-fallback may be needed to process it, it's necessary to
* use the fallback-aware bio submission code rather than
* unconditionally returning DM_MAPIO_REMAPPED.
*
* To get the correct accounting for a dm target in the case where
* __blk_crypto_submit_bio() doesn't take ownership of the bio (returns
* true), call __blk_crypto_submit_bio() directly and return
* DM_MAPIO_REMAPPED in that case, rather than relying on
* blk_crypto_submit_bio() which calls submit_bio() in that case.
*
* TODO: blk-crypto fallback write slow-path currently double-accounts
* IO in vmstat, as encrypted bios are submitted via submit_bio().
* This does not affect data correctness. Consider fixing this if
* a cleaner accounting model for derived bios is introduced.
*/
if (__blk_crypto_submit_bio(bio))
return DM_MAPIO_REMAPPED;
return DM_MAPIO_SUBMITTED;
}
static void inlinecrypt_status(struct dm_target *ti, status_type_t type,
unsigned int status_flags, char *result,
unsigned int maxlen)
{
const struct inlinecrypt_ctx *ctx = ti->private;
unsigned int sz = 0;
int num_feature_args = 0;
switch (type) {
case STATUSTYPE_INFO:
case STATUSTYPE_IMA:
result[0] = '\0';
break;
case STATUSTYPE_TABLE:
/*
* Warning: like dm-crypt, dm-inlinecrypt includes the key in
* the returned table. Userspace is responsible for redacting
* the key when needed.
*/
DMEMIT("%s %*phN %u %llu %s %llu", ctx->cipher_string,
ctx->key.size, ctx->key.bytes,
ctx->key_type, ctx->iv_offset,
ctx->dev->name, ctx->start);
num_feature_args += !!ti->num_discard_bios;
if (ctx->sector_size != SECTOR_SIZE)
num_feature_args += 2;
if (num_feature_args != 0) {
DMEMIT(" %d", num_feature_args);
if (ti->num_discard_bios)
DMEMIT(" allow_discards");
if (ctx->sector_size != SECTOR_SIZE) {
DMEMIT(" sector_size:%u", ctx->sector_size);
DMEMIT(" iv_large_sectors");
}
}
break;
}
}
static int inlinecrypt_prepare_ioctl(struct dm_target *ti,
struct block_device **bdev, unsigned int cmd,
unsigned long arg, bool *forward)
{
const struct inlinecrypt_ctx *ctx = ti->private;
const struct dm_dev *dev = ctx->dev;
*bdev = dev->bdev;
/* Only pass ioctls through if the device sizes match exactly. */
return ctx->start != 0 || ti->len != bdev_nr_sectors(dev->bdev);
}
static int inlinecrypt_iterate_devices(struct dm_target *ti,
iterate_devices_callout_fn fn,
void *data)
{
const struct inlinecrypt_ctx *ctx = ti->private;
return fn(ti, ctx->dev, ctx->start, ti->len, data);
}
#ifdef CONFIG_BLK_DEV_ZONED
static int inlinecrypt_report_zones(struct dm_target *ti,
struct dm_report_zones_args *args,
unsigned int nr_zones)
{
const struct inlinecrypt_ctx *ctx = ti->private;
return dm_report_zones(ctx->dev->bdev, ctx->start,
ctx->start + dm_target_offset(ti, args->next_sector),
args, nr_zones);
}
#else
#define inlinecrypt_report_zones NULL
#endif
static void inlinecrypt_io_hints(struct dm_target *ti,
struct queue_limits *limits)
{
const struct inlinecrypt_ctx *ctx = ti->private;
const unsigned int sector_size = ctx->sector_size;
limits->logical_block_size =
max_t(unsigned int, limits->logical_block_size, sector_size);
limits->physical_block_size =
max_t(unsigned int, limits->physical_block_size, sector_size);
limits->io_min = max_t(unsigned int, limits->io_min, sector_size);
limits->dma_alignment = limits->logical_block_size - 1;
}
static struct target_type inlinecrypt_target = {
.name = "inlinecrypt",
.version = {1, 0, 0},
/*
* Do not set DM_TARGET_PASSES_CRYPTO, since dm-inlinecrypt consumes the
* crypto capability itself.
*/
.features = DM_TARGET_ZONED_HM,
.module = THIS_MODULE,
.ctr = inlinecrypt_ctr,
.dtr = inlinecrypt_dtr,
.map = inlinecrypt_map,
.status = inlinecrypt_status,
.prepare_ioctl = inlinecrypt_prepare_ioctl,
.iterate_devices = inlinecrypt_iterate_devices,
.report_zones = inlinecrypt_report_zones,
.io_hints = inlinecrypt_io_hints,
};
module_dm(inlinecrypt);
MODULE_AUTHOR("Eric Biggers <ebiggers@google.com>");
MODULE_AUTHOR("Linlin Zhang <linlin.zhang@oss.qualcomm.com>");
MODULE_DESCRIPTION(DM_NAME " target for inline encryption");
MODULE_LICENSE("GPL");