diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 22:42:47 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 22:42:47 +0100 |
| commit | 692f367d6033168e32c5ea2139683794e8ffa8ad (patch) | |
| tree | 6327e98f52ee78960e31f3f92490d84fe8eeaf5b | |
| parent | a25555d13a5d8806e71102629d875a3d5e955f90 (diff) | |
| parent | f9f57971da38afbcfa82a9502fb3eb5f1f100e73 (diff) | |
| download | linux-next-history-692f367d6033168e32c5ea2139683794e8ffa8ad.tar.gz | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm.git
| -rw-r--r-- | Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst | 129 | ||||
| -rw-r--r-- | Documentation/admin-guide/device-mapper/index.rst | 1 | ||||
| -rw-r--r-- | block/blk-crypto.c | 3 | ||||
| -rw-r--r-- | drivers/md/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/md/Makefile | 1 | ||||
| -rw-r--r-- | drivers/md/dm-ima.c | 492 | ||||
| -rw-r--r-- | drivers/md/dm-ima.h | 68 | ||||
| -rw-r--r-- | drivers/md/dm-inlinecrypt.c | 618 | ||||
| -rw-r--r-- | drivers/md/dm-ioctl.c | 151 | ||||
| -rw-r--r-- | drivers/md/dm-raid.c | 6 | ||||
| -rw-r--r-- | drivers/md/dm-vdo/indexer/index-layout.c | 2 | ||||
| -rw-r--r-- | drivers/md/dm.c | 15 | ||||
| -rw-r--r-- | drivers/md/md.h | 2 | ||||
| -rw-r--r-- | drivers/md/raid5.c | 7 |
14 files changed, 1202 insertions, 304 deletions
diff --git a/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst new file mode 100644 index 0000000000000..76b3aae21eb4c --- /dev/null +++ b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst @@ -0,0 +1,129 @@ +============== +dm-inlinecrypt +============== + +Device-Mapper's "inlinecrypt" target provides transparent encryption of block devices +using the inline encryption hardware. + +For a more detailed description of inline encryption, see: +https://docs.kernel.org/block/inline-encryption.html + +Parameters:: + + <cipher> <key> <iv_offset> <device path> \ + <offset> [<#opt_params> <opt_params>] + +<cipher> + Encryption cipher type. + + The cipher specifications format is:: + + cipher + + Examples:: + + aes-xts-plain64 + + The cipher type corresponds to the encryption modes supported by + inline crypto in the block layer. Currently, only + BLK_ENCRYPTION_MODE_AES_256_XTS (i.e. aes-xts-plain64) is supported. + +<key> + Key used for encryption. It is encoded either as a hexadecimal number + or it can be passed as <key_string> prefixed with single colon + character (':') for keys residing in kernel keyring service. + You can only use key sizes that are valid for the selected cipher. + Note that the size in bytes of a valid key must be in bellow range. + + [BLK_CRYPTO_KEY_TYPE_RAW, BLK_CRYPTO_KEY_TYPE_HW_WRAPPED] + +<key_string> + The kernel keyring key is identified by string in following format: + <key_size>:<keyring_type>:<key_description>. + +<key_size> + The encryption key size in bytes. The kernel key payload size must match + the value passed in <key_size>. + +<keyring_type> + The type of the key inside the kernel keyring. It can be either 'logon', + or 'trusted' kernel key type. + +<key_description> + The kernel keyring key description inlinecrypt target should look for + when loading key of <keyring_type>. + +<iv_offset> + The IV offset is a sector count that is added to the sector number + before creating the IV. + +<device path> + This is the device that is going to be used as backend and contains the + encrypted data. You can specify it as a path like /dev/xxx or a device + number <major>:<minor>. + +<offset> + Starting sector within the device where the encrypted data begins. + +<#opt_params> + Number of optional parameters. If there are no optional parameters, + the optional parameters section can be skipped or #opt_params can be zero. + Otherwise #opt_params is the number of following arguments. + + Example of optional parameters section: + keytype:raw allow_discards sector_size:4096 iv_large_sectors + +<key_type> + The type of the key as seen by the block layer, either standard or + hardware-wrapped. The string is supplied in the table as <keytype:raw> + or <keytype:hw-wrapped>. + +allow_discards + Block discard requests (a.k.a. TRIM) are passed through the inlinecrypt + device. The default is to ignore discard requests. + + WARNING: Assess the specific security risks carefully before enabling this + option. For example, allowing discards on encrypted devices may lead to + the leak of information about the ciphertext device (filesystem type, + used space etc.) if the discarded blocks can be located easily on the + device later. + +sector_size:<bytes> + Use <bytes> as the encryption unit instead of 512 bytes sectors. + This option can be in range 512 - 4096 bytes and must be power of two. + Virtual device will announce this size as a minimal IO and logical sector. + +iv_large_sectors + Use <sector_size>-based sector numbers for IV generation instead of + 512-byte sectors. + + For dm-inlinecrypt, this flag must be specified when <sector_size> + is larger than 512 bytes. The legacy 512-byte-based IV behavior is + not supported. + + When specified, if <sector_size> is 4096 bytes, plain64 IV for the + second sector will be 1, and <iv_offset> must be a multiple of + <sector_size> (in 512-byte units). + +Example scripts +=============== +Currently, dm-inlinecrypt devices must be set up directly using dmsetup. +There is no userspace support yet to integrate dm-inlinecrypt with LUKS +or cryptsetup. In particular, cryptsetup currently only supports +dm-crypt, and cannot be used to create dm-inlinecrypt mappings. + +The following examples demonstrate how to create dm-inlinecrypt devices +using dmsetup + +:: + + #!/bin/sh + # Create a inlinecrypt device using dmsetup + dmsetup create inlinecrypt1 --table "0 `blockdev --getsz $1` inlinecrypt aes-xts-plain64 babebabebabebabebabebabebabebabebabebabebabebabebabebabebabebabe 0 0 $1 0 1 keytype:raw" + +:: + + #!/bin/sh + # Create a inlinecrypt device using dmsetup when encryption key is stored in keyring service + dmsetup create inlinecrypt2 --table "0 `blockdev --getsz $1` inlinecrypt aes-xts-plain64 :64:logon:fde:dminlinecrypt_test_key 0 0 $1 0 1 keytype:raw" + diff --git a/Documentation/admin-guide/device-mapper/index.rst b/Documentation/admin-guide/device-mapper/index.rst index 030d854628ac2..73c1f20021343 100644 --- a/Documentation/admin-guide/device-mapper/index.rst +++ b/Documentation/admin-guide/device-mapper/index.rst @@ -15,6 +15,7 @@ Device Mapper dm-flakey dm-ima dm-init + dm-inlinecrypt dm-integrity dm-io dm-log diff --git a/block/blk-crypto.c b/block/blk-crypto.c index 165c9d2cce073..15e25e41b1669 100644 --- a/block/blk-crypto.c +++ b/block/blk-crypto.c @@ -116,6 +116,7 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key, bio->bi_crypt_context = bc; } +EXPORT_SYMBOL_GPL(bio_crypt_set_ctx); void __bio_crypt_free_ctx(struct bio *bio) { @@ -348,6 +349,7 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key, return 0; } +EXPORT_SYMBOL_GPL(blk_crypto_init_key); bool blk_crypto_config_supported_natively(struct block_device *bdev, const struct blk_crypto_config *cfg) @@ -398,6 +400,7 @@ int blk_crypto_start_using_key(struct block_device *bdev, } return blk_crypto_fallback_start_using_mode(key->crypto_cfg.crypto_mode); } +EXPORT_SYMBOL_GPL(blk_crypto_start_using_key); /** * blk_crypto_evict_key() - Evict a blk_crypto_key from a block_device diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig index a3fcdca7e6db3..df27c7d066d23 100644 --- a/drivers/md/Kconfig +++ b/drivers/md/Kconfig @@ -315,6 +315,17 @@ config DM_CRYPT If unsure, say N. +config DM_INLINECRYPT + tristate "Inline encryption target support" + depends on BLK_DEV_DM + depends on (KEYS || KEYS=n) + depends on BLK_INLINE_ENCRYPTION + help + This device-mapper target is similar to dm-crypt, but it uses the + blk-crypto API instead of the regular crypto API. This allows it to + take advantage of inline encryption hardware such as that commonly + built into UFS host controllers. + config DM_SNAPSHOT tristate "Snapshot target" depends on BLK_DEV_DM diff --git a/drivers/md/Makefile b/drivers/md/Makefile index c338cc6fbe2eb..517d1f7d82888 100644 --- a/drivers/md/Makefile +++ b/drivers/md/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_DM_UNSTRIPED) += dm-unstripe.o obj-$(CONFIG_DM_BUFIO) += dm-bufio.o obj-$(CONFIG_DM_BIO_PRISON) += dm-bio-prison.o obj-$(CONFIG_DM_CRYPT) += dm-crypt.o +obj-$(CONFIG_DM_INLINECRYPT) += dm-inlinecrypt.o obj-$(CONFIG_DM_DELAY) += dm-delay.o obj-$(CONFIG_DM_DUST) += dm-dust.o obj-$(CONFIG_DM_FLAKEY) += dm-flakey.o diff --git a/drivers/md/dm-ima.c b/drivers/md/dm-ima.c index 9495ca0350563..5e7232582feb7 100644 --- a/drivers/md/dm-ima.c +++ b/drivers/md/dm-ima.c @@ -21,25 +21,32 @@ * character, so that they don't interfere with the construction of key-value pairs, * and clients can split the key1=val1,key2=val2,key3=val3; pairs properly. */ -static void fix_separator_chars(char **buf) +static void fix_separator_chars(char *buf) { - int l = strlen(*buf); + int l = strlen(buf); int i, j, sp = 0; for (i = 0; i < l; i++) - if ((*buf)[i] == '\\' || (*buf)[i] == ';' || (*buf)[i] == '=' || (*buf)[i] == ',') + if (buf[i] == '\\' || buf[i] == ';' || buf[i] == '=' || buf[i] == ',') sp++; if (!sp) return; + buf[l + sp] = '\0'; for (i = l-1, j = i+sp; i >= 0; i--) { - (*buf)[j--] = (*buf)[i]; - if ((*buf)[i] == '\\' || (*buf)[i] == ';' || (*buf)[i] == '=' || (*buf)[i] == ',') - (*buf)[j--] = '\\'; + buf[j--] = buf[i]; + if (buf[i] == '\\' || buf[i] == ';' || buf[i] == '=' || buf[i] == ',') + buf[j--] = '\\'; } } +static void fix_context_strings(struct dm_ima_context *context) +{ + fix_separator_chars(context->dev_name); + fix_separator_chars(context->dev_uuid); +} + /* * Internal function to allocate memory for IMA measurements. */ @@ -59,68 +66,85 @@ static void *dm_ima_alloc(size_t len, bool noio) return ptr; } -/* - * Internal function to allocate and copy name and uuid for IMA measurements. - */ -static int dm_ima_alloc_and_copy_name_uuid(struct mapped_device *md, char **dev_name, - char **dev_uuid, bool noio) +void dm_ima_init(struct mapped_device *md) { - int r; - *dev_name = dm_ima_alloc(DM_NAME_LEN*2, noio); - if (!(*dev_name)) { - r = -ENOMEM; - goto error; - } + md->ima.update_idx = 0; + md->ima.measure_idx = 0; + init_waitqueue_head(&md->ima.ima_wq); + spin_lock_init(&md->ima.ima_lock); +} - *dev_uuid = dm_ima_alloc(DM_UUID_LEN*2, noio); - if (!(*dev_uuid)) { - r = -ENOMEM; - goto error; - } +void dm_ima_alloc_context(struct dm_ima_context **context, bool noio) +{ + *context = dm_ima_alloc(sizeof(struct dm_ima_context), noio); +} - r = dm_copy_name_and_uuid(md, *dev_name, *dev_uuid); - if (r) - goto error; +void dm_ima_free_context(struct dm_ima_context *context) +{ + if (likely(context)) { + kfree(context->table.device_metadata); + kfree(context->table.hash); + kfree(context); + } +} - fix_separator_chars(dev_name); - fix_separator_chars(dev_uuid); +static void wait_to_measure(struct dm_ima_measurements *ima, + unsigned int update_idx) +{ + spin_lock_irq(&ima->ima_lock); + wait_event_lock_irq(ima->ima_wq, + ima->measure_idx == update_idx, + ima->ima_lock); + spin_unlock_irq(&ima->ima_lock); +} - return 0; -error: - kfree(*dev_name); - kfree(*dev_uuid); - *dev_name = NULL; - *dev_uuid = NULL; - return r; +static void wake_next_measure(struct dm_ima_measurements *ima) +{ + spin_lock_irq(&ima->ima_lock); + ima->measure_idx++; + spin_unlock_irq(&ima->ima_lock); + wake_up_all(&ima->ima_wq); } /* - * Internal function to allocate and copy device data for IMA measurements. + * Helper function for swapping the table, to make sure that the + * correct table metadata is saved and restored. */ -static int dm_ima_alloc_and_copy_device_data(struct mapped_device *md, char **device_data, - unsigned int num_targets, bool noio) +void dm_ima_context_table_op(struct mapped_device *md, + struct dm_ima_context *context, + enum dm_ima_table_op op) { - char *dev_name = NULL, *dev_uuid = NULL; - int r; + struct dm_ima_measurements *ima = &md->ima; - r = dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio); - if (r) - return r; + if (unlikely(!context)) + return; - *device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); - if (!(*device_data)) { - r = -ENOMEM; - goto error; + wait_to_measure(ima, context->update_idx); + + if (op == DM_IMA_TABLE_SAVE) { + context->table = ima->inactive_table; + memset(&ima->inactive_table, 0, sizeof(ima->inactive_table)); + } else { + ima->inactive_table = context->table; + memset(&context->table, 0, sizeof(context->table)); } - scnprintf(*device_data, DM_IMA_DEVICE_BUF_LEN, + wake_next_measure(ima); +} + +/* + * Internal function to copy device data for IMA measurements. + */ +static void dm_ima_copy_device_data(struct mapped_device *md, char *device_data, + struct dm_ima_context *context, + unsigned int num_targets) +{ + memset(device_data, 0, DM_IMA_DEVICE_BUF_LEN); + scnprintf(device_data, DM_IMA_DEVICE_BUF_LEN, "name=%s,uuid=%s,major=%d,minor=%d,minor_count=%d,num_targets=%u;", - dev_name, dev_uuid, md->disk->major, md->disk->first_minor, - md->disk->minors, num_targets); -error: - kfree(dev_name); - kfree(dev_uuid); - return r; + context->dev_name, context->dev_uuid, md->disk->major, + md->disk->first_minor, md->disk->minors, num_targets); + } /* @@ -141,42 +165,21 @@ static void dm_ima_measure_data(const char *event_name, const void *buf, size_t memalloc_noio_restore(noio_flag); } -/* - * Internal function to allocate and copy current device capacity for IMA measurements. - */ -static int dm_ima_alloc_and_copy_capacity_str(struct mapped_device *md, char **capacity_str, - bool noio) -{ - sector_t capacity; - - capacity = get_capacity(md->disk); - - *capacity_str = dm_ima_alloc(DM_IMA_DEVICE_CAPACITY_BUF_LEN, noio); - if (!(*capacity_str)) - return -ENOMEM; - - return scnprintf(*capacity_str, DM_IMA_DEVICE_BUF_LEN, "current_device_capacity=%llu;", - capacity); -} - -/* - * Initialize/reset the dm ima related data structure variables. - */ -void dm_ima_reset_data(struct mapped_device *md) +static sector_t dm_ima_capacity(struct mapped_device *md) { - memset(&(md->ima), 0, sizeof(md->ima)); - md->ima.dm_version_str_len = strlen(DM_IMA_VERSION_STR); + return (md->ima.active_table.device_metadata) ? + md->ima.active_table.capacity : get_capacity(md->disk); } /* * Build up the IMA data for each target, and finally measure. */ -void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_flags) +void dm_ima_measure_on_table_load(struct dm_table *table, + struct dm_ima_context *context) { size_t device_data_buf_len, target_metadata_buf_len, target_data_buf_len, l = 0; char *target_metadata_buf = NULL, *target_data_buf = NULL, *digest_buf = NULL; char *ima_buf = NULL, *device_data_buf = NULL; - int last_target_measured = -1; status_type_t type = STATUSTYPE_IMA; size_t cur_total_buf_len = 0; unsigned int num_targets, i; @@ -185,9 +188,14 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl bool noio = false; char table_load_event_name[] = "dm_table_load"; + if (unlikely(!context)) + return; + + wait_to_measure(&table->md->ima, context->update_idx); + ima_buf = dm_ima_alloc(DM_IMA_MEASUREMENT_BUF_LEN, noio); if (!ima_buf) - return; + goto error; target_metadata_buf = dm_ima_alloc(DM_IMA_TARGET_METADATA_BUF_LEN, noio); if (!target_metadata_buf) @@ -199,13 +207,18 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl num_targets = table->num_targets; - if (dm_ima_alloc_and_copy_device_data(table->md, &device_data_buf, num_targets, noio)) + device_data_buf = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); + if (!device_data_buf) goto error; + fix_context_strings(context); + dm_ima_copy_device_data(table->md, device_data_buf, context, + num_targets); + sha256_init(&hash_ctx); - memcpy(ima_buf + l, DM_IMA_VERSION_STR, table->md->ima.dm_version_str_len); - l += table->md->ima.dm_version_str_len; + memcpy(ima_buf + l, DM_IMA_VERSION_STR, strlen(DM_IMA_VERSION_STR)); + l += strlen(DM_IMA_VERSION_STR); device_data_buf_len = strlen(device_data_buf); memcpy(ima_buf + l, device_data_buf, device_data_buf_len); @@ -214,8 +227,6 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl for (i = 0; i < num_targets; i++) { struct dm_target *ti = dm_table_get_target(table, i); - last_target_measured = 0; - /* * First retrieve the target metadata. */ @@ -229,7 +240,7 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl * Then retrieve the actual target data. */ if (ti->type->status) - ti->type->status(ti, type, status_flags, target_data_buf, + ti->type->status(ti, type, 0, target_data_buf, DM_IMA_TARGET_DATA_BUF_LEN); else target_data_buf[0] = '\0'; @@ -260,19 +271,11 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl * prefix, so that multiple records from the same "dm_table_load" for * a given device can be linked together. */ - memcpy(ima_buf + l, DM_IMA_VERSION_STR, table->md->ima.dm_version_str_len); - l += table->md->ima.dm_version_str_len; + memcpy(ima_buf + l, DM_IMA_VERSION_STR, strlen(DM_IMA_VERSION_STR)); + l += strlen(DM_IMA_VERSION_STR); memcpy(ima_buf + l, device_data_buf, device_data_buf_len); l += device_data_buf_len; - - /* - * If this iteration of the for loop turns out to be the last target - * in the table, dm_ima_measure_data("dm_table_load", ...) doesn't need - * to be called again, just the hash needs to be finalized. - * "last_target_measured" tracks this state. - */ - last_target_measured = 1; } /* @@ -286,11 +289,8 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl l += target_data_buf_len; } - if (!last_target_measured) { - dm_ima_measure_data(table_load_event_name, ima_buf, l, noio); - - sha256_update(&hash_ctx, (const u8 *)ima_buf, l); - } + dm_ima_measure_data(table_load_event_name, ima_buf, l, noio); + sha256_update(&hash_ctx, (const u8 *)ima_buf, l); /* * Finalize the table hash, and store it in table->md->ima.inactive_table.hash, @@ -304,17 +304,14 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl if (!digest_buf) goto error; - if (table->md->ima.active_table.hash != table->md->ima.inactive_table.hash) - kfree(table->md->ima.inactive_table.hash); - + kfree(table->md->ima.inactive_table.hash); table->md->ima.inactive_table.hash = digest_buf; table->md->ima.inactive_table.hash_len = strlen(digest_buf); table->md->ima.inactive_table.num_targets = num_targets; + table->md->ima.inactive_table.capacity = dm_table_get_size(table); - if (table->md->ima.active_table.device_metadata != - table->md->ima.inactive_table.device_metadata) - kfree(table->md->ima.inactive_table.device_metadata); + kfree(table->md->ima.inactive_table.device_metadata); table->md->ima.inactive_table.device_metadata = device_data_buf; table->md->ima.inactive_table.device_metadata_len = device_data_buf_len; @@ -326,66 +323,55 @@ exit: kfree(ima_buf); kfree(target_metadata_buf); kfree(target_data_buf); + + wake_next_measure(&table->md->ima); } /* * Measure IMA data on device resume. */ -void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap) +void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap, + struct dm_ima_context *context) { - char *device_table_data, *dev_name = NULL, *dev_uuid = NULL, *capacity_str = NULL; + char *device_table_data = NULL; char active[] = "active_table_hash="; unsigned int active_len = strlen(active); unsigned int l = 0; bool noio = true; bool nodata = true; - int capacity_len; - device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); - if (!device_table_data) + if (unlikely(!context)) return; - capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio); - if (capacity_len < 0) - goto error; - - memcpy(device_table_data + l, DM_IMA_VERSION_STR, md->ima.dm_version_str_len); - l += md->ima.dm_version_str_len; + wait_to_measure(&md->ima, context->update_idx); if (swap) { - if (md->ima.active_table.hash != md->ima.inactive_table.hash) - kfree(md->ima.active_table.hash); - - md->ima.active_table.hash = NULL; - md->ima.active_table.hash_len = 0; - - if (md->ima.active_table.device_metadata != - md->ima.inactive_table.device_metadata) - kfree(md->ima.active_table.device_metadata); - - md->ima.active_table.device_metadata = NULL; - md->ima.active_table.device_metadata_len = 0; - md->ima.active_table.num_targets = 0; - - if (md->ima.inactive_table.hash) { - md->ima.active_table.hash = md->ima.inactive_table.hash; - md->ima.active_table.hash_len = md->ima.inactive_table.hash_len; - md->ima.inactive_table.hash = NULL; - md->ima.inactive_table.hash_len = 0; - } - - if (md->ima.inactive_table.device_metadata) { - md->ima.active_table.device_metadata = - md->ima.inactive_table.device_metadata; - md->ima.active_table.device_metadata_len = - md->ima.inactive_table.device_metadata_len; - md->ima.active_table.num_targets = md->ima.inactive_table.num_targets; - md->ima.inactive_table.device_metadata = NULL; - md->ima.inactive_table.device_metadata_len = 0; - md->ima.inactive_table.num_targets = 0; + kfree(md->ima.active_table.hash); + kfree(md->ima.active_table.device_metadata); + md->ima.active_table = context->table; + memset(&context->table, 0, sizeof(context->table)); + if (md->ima.active_table.device_metadata) { + /* + * A rename could have happened while the swap was + * going on. In that case, the saved table info would + * still have the old name. Update the metadata to be + * sure that it has the current name + */ + struct dm_ima_device_table_metadata *table = &md->ima.active_table; + fix_context_strings(context); + dm_ima_copy_device_data(md, table->device_metadata, + context, table->num_targets); + table->device_metadata_len = strlen(table->device_metadata); } } + device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); + if (!device_table_data) + goto error; + + memcpy(device_table_data + l, DM_IMA_VERSION_STR, strlen(DM_IMA_VERSION_STR)); + l += strlen(DM_IMA_VERSION_STR); + if (md->ima.active_table.device_metadata) { memcpy(device_table_data + l, md->ima.active_table.device_metadata, md->ima.active_table.device_metadata_len); @@ -409,59 +395,54 @@ void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap) } if (nodata) { - if (dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio)) - goto error; - + fix_context_strings(context); l = scnprintf(device_table_data, DM_IMA_DEVICE_BUF_LEN, "%sname=%s,uuid=%s;device_resume=no_data;", - DM_IMA_VERSION_STR, dev_name, dev_uuid); + DM_IMA_VERSION_STR, context->dev_name, + context->dev_uuid); } - - memcpy(device_table_data + l, capacity_str, capacity_len); - l += capacity_len; + l += scnprintf(device_table_data + l, DM_IMA_DEVICE_BUF_LEN - l, + "current_device_capacity=%llu;", dm_ima_capacity(md)); dm_ima_measure_data("dm_device_resume", device_table_data, l, noio); - kfree(dev_name); - kfree(dev_uuid); error: - kfree(capacity_str); kfree(device_table_data); + + wake_next_measure(&md->ima); } /* * Measure IMA data on remove. */ -void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all) +void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all, + struct dm_ima_context *context, + unsigned int idx) { - char *device_table_data, *dev_name = NULL, *dev_uuid = NULL, *capacity_str = NULL; + char *device_table_data; char active_table_str[] = "active_table_hash="; char inactive_table_str[] = "inactive_table_hash="; char device_active_str[] = "device_active_metadata="; char device_inactive_str[] = "device_inactive_metadata="; - char remove_all_str[] = "remove_all="; unsigned int active_table_len = strlen(active_table_str); unsigned int inactive_table_len = strlen(inactive_table_str); unsigned int device_active_len = strlen(device_active_str); unsigned int device_inactive_len = strlen(device_inactive_str); - unsigned int remove_all_len = strlen(remove_all_str); unsigned int l = 0; bool noio = true; bool nodata = true; - int capacity_len; - device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN*2, noio); - if (!device_table_data) + wait_to_measure(&md->ima, idx); + + if (unlikely(!context)) goto exit; - capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio); - if (capacity_len < 0) { - kfree(device_table_data); + device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN*2, noio); + if (!device_table_data) goto exit; - } - memcpy(device_table_data + l, DM_IMA_VERSION_STR, md->ima.dm_version_str_len); - l += md->ima.dm_version_str_len; + memcpy(device_table_data + l, DM_IMA_VERSION_STR, strlen(DM_IMA_VERSION_STR)); + l += strlen(DM_IMA_VERSION_STR); if (md->ima.active_table.device_metadata) { memcpy(device_table_data + l, device_active_str, device_active_len); @@ -518,68 +499,57 @@ void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all) * in IMA measurements. */ if (nodata) { - if (dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio)) - goto error; - + fix_context_strings(context); l = scnprintf(device_table_data, DM_IMA_DEVICE_BUF_LEN, "%sname=%s,uuid=%s;device_remove=no_data;", - DM_IMA_VERSION_STR, dev_name, dev_uuid); + DM_IMA_VERSION_STR, context->dev_name, + context->dev_uuid); } - memcpy(device_table_data + l, remove_all_str, remove_all_len); - l += remove_all_len; - memcpy(device_table_data + l, remove_all ? "y;" : "n;", 2); - l += 2; - - memcpy(device_table_data + l, capacity_str, capacity_len); - l += capacity_len; + l += scnprintf(device_table_data + l, (DM_IMA_DEVICE_BUF_LEN * 2) - l, + "remove_all=%c;current_device_capacity=%llu;", + remove_all ? 'y' : 'n', dm_ima_capacity(md)); dm_ima_measure_data("dm_device_remove", device_table_data, l, noio); -error: kfree(device_table_data); - kfree(capacity_str); exit: kfree(md->ima.active_table.device_metadata); - - if (md->ima.active_table.device_metadata != - md->ima.inactive_table.device_metadata) - kfree(md->ima.inactive_table.device_metadata); + kfree(md->ima.inactive_table.device_metadata); kfree(md->ima.active_table.hash); + kfree(md->ima.inactive_table.hash); - if (md->ima.active_table.hash != md->ima.inactive_table.hash) - kfree(md->ima.inactive_table.hash); - - dm_ima_reset_data(md); + memset(&md->ima.active_table, 0, sizeof(md->ima.active_table)); + memset(&md->ima.inactive_table, 0, sizeof(md->ima.inactive_table)); - kfree(dev_name); - kfree(dev_uuid); + wake_next_measure(&md->ima); } /* * Measure ima data on table clear. */ -void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map) +void dm_ima_measure_on_table_clear(struct mapped_device *md, + struct dm_ima_context *context) { unsigned int l = 0; - char *device_table_data = NULL, *dev_name = NULL, *dev_uuid = NULL, *capacity_str = NULL; + char *device_table_data = NULL; char inactive_str[] = "inactive_table_hash="; unsigned int inactive_len = strlen(inactive_str); bool noio = true; bool nodata = true; - int capacity_len; - device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); - if (!device_table_data) + if (unlikely(!context)) return; - capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio); - if (capacity_len < 0) - goto error1; + wait_to_measure(&md->ima, context->update_idx); - memcpy(device_table_data + l, DM_IMA_VERSION_STR, md->ima.dm_version_str_len); - l += md->ima.dm_version_str_len; + device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio); + if (!device_table_data) + goto error; + + memcpy(device_table_data + l, DM_IMA_VERSION_STR, strlen(DM_IMA_VERSION_STR)); + l += strlen(DM_IMA_VERSION_STR); if (md->ima.inactive_table.device_metadata_len && md->ima.inactive_table.hash_len) { @@ -602,101 +572,79 @@ void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map) } if (nodata) { - if (dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio)) - goto error2; - + fix_context_strings(context); l = scnprintf(device_table_data, DM_IMA_DEVICE_BUF_LEN, "%sname=%s,uuid=%s;table_clear=no_data;", - DM_IMA_VERSION_STR, dev_name, dev_uuid); + DM_IMA_VERSION_STR, context->dev_name, + context->dev_uuid); } - memcpy(device_table_data + l, capacity_str, capacity_len); - l += capacity_len; + l += scnprintf(device_table_data + l, DM_IMA_DEVICE_BUF_LEN - l, + "current_device_capacity=%llu;", dm_ima_capacity(md)); dm_ima_measure_data("dm_table_clear", device_table_data, l, noio); - if (new_map) { - if (md->ima.inactive_table.hash && - md->ima.inactive_table.hash != md->ima.active_table.hash) - kfree(md->ima.inactive_table.hash); - - md->ima.inactive_table.hash = NULL; - md->ima.inactive_table.hash_len = 0; - - if (md->ima.inactive_table.device_metadata && - md->ima.inactive_table.device_metadata != md->ima.active_table.device_metadata) - kfree(md->ima.inactive_table.device_metadata); - - md->ima.inactive_table.device_metadata = NULL; - md->ima.inactive_table.device_metadata_len = 0; - md->ima.inactive_table.num_targets = 0; - - if (md->ima.active_table.hash) { - md->ima.inactive_table.hash = md->ima.active_table.hash; - md->ima.inactive_table.hash_len = md->ima.active_table.hash_len; - } - - if (md->ima.active_table.device_metadata) { - md->ima.inactive_table.device_metadata = - md->ima.active_table.device_metadata; - md->ima.inactive_table.device_metadata_len = - md->ima.active_table.device_metadata_len; - md->ima.inactive_table.num_targets = - md->ima.active_table.num_targets; - } - } +error: + kfree(md->ima.inactive_table.hash); + kfree(md->ima.inactive_table.device_metadata); + memset(&md->ima.inactive_table, 0, sizeof(md->ima.inactive_table)); - kfree(dev_name); - kfree(dev_uuid); -error2: - kfree(capacity_str); -error1: kfree(device_table_data); + + wake_next_measure(&md->ima); } /* * Measure IMA data on device rename. */ -void dm_ima_measure_on_device_rename(struct mapped_device *md) +void dm_ima_measure_on_device_rename(struct mapped_device *md, + struct dm_ima_context *context) { - char *old_device_data = NULL, *new_device_data = NULL, *combined_device_data = NULL; - char *new_dev_name = NULL, *new_dev_uuid = NULL, *capacity_str = NULL; + char *old_device_data = NULL; + char *combined_device_data = NULL; bool noio = true; int len; + struct dm_ima_device_table_metadata *table; - if (dm_ima_alloc_and_copy_device_data(md, &new_device_data, - md->ima.active_table.num_targets, noio)) + if (unlikely(!context)) return; - if (dm_ima_alloc_and_copy_name_uuid(md, &new_dev_name, &new_dev_uuid, noio)) - goto error; + wait_to_measure(&md->ima, context->update_idx); + + fix_context_strings(context); combined_device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN * 2, noio); if (!combined_device_data) - goto error; - - if (dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio) < 0) - goto error; - - old_device_data = md->ima.active_table.device_metadata; - - md->ima.active_table.device_metadata = new_device_data; - md->ima.active_table.device_metadata_len = strlen(new_device_data); + goto exit; + if (md->ima.active_table.device_metadata) + old_device_data = md->ima.active_table.device_metadata; + else if (md->ima.inactive_table.device_metadata) + old_device_data = md->ima.inactive_table.device_metadata; + else + old_device_data = "device_rename=no_data;"; len = scnprintf(combined_device_data, DM_IMA_DEVICE_BUF_LEN * 2, - "%s%snew_name=%s,new_uuid=%s;%s", DM_IMA_VERSION_STR, old_device_data, - new_dev_name, new_dev_uuid, capacity_str); + "%s%snew_name=%s,new_uuid=%s;current_device_capacity=%llu;", + DM_IMA_VERSION_STR, old_device_data, context->dev_name, + context->dev_uuid, dm_ima_capacity(md)); dm_ima_measure_data("dm_device_rename", combined_device_data, len, noio); + kfree(combined_device_data); - goto exit; - -error: - kfree(new_device_data); exit: - kfree(capacity_str); - kfree(combined_device_data); - kfree(old_device_data); - kfree(new_dev_name); - kfree(new_dev_uuid); + if (md->ima.active_table.device_metadata) { + table = &md->ima.active_table; + dm_ima_copy_device_data(md, table->device_metadata, context, + table->num_targets); + table->device_metadata_len = strlen(table->device_metadata); + } + + if (md->ima.inactive_table.device_metadata) { + table = &md->ima.inactive_table; + dm_ima_copy_device_data(md, table->device_metadata, context, + table->num_targets); + table->device_metadata_len = strlen(table->device_metadata); + } + + wake_next_measure(&md->ima); } diff --git a/drivers/md/dm-ima.h b/drivers/md/dm-ima.h index a403deca6093e..0ec013d1545cc 100644 --- a/drivers/md/dm-ima.h +++ b/drivers/md/dm-ima.h @@ -24,6 +24,11 @@ __dm_ima_str(DM_VERSION_MINOR) "." \ __dm_ima_str(DM_VERSION_PATCHLEVEL) ";" +enum dm_ima_table_op { + DM_IMA_TABLE_SAVE, + DM_IMA_TABLE_RESTORE, +}; + #ifdef CONFIG_IMA struct dm_ima_device_table_metadata { @@ -36,6 +41,7 @@ struct dm_ima_device_table_metadata { char *device_metadata; unsigned int device_metadata_len; unsigned int num_targets; + sector_t capacity; /* * Contains the sha256 hashes of the IMA measurements of the target @@ -45,31 +51,67 @@ struct dm_ima_device_table_metadata { unsigned int hash_len; }; +struct dm_ima_context { + struct dm_ima_device_table_metadata table; + unsigned int update_idx; + char dev_name[DM_NAME_LEN*2]; + char dev_uuid[DM_UUID_LEN*2]; +}; + /* * This structure contains device metadata, and table hash for * active and inactive tables for ima measurements. */ struct dm_ima_measurements { + unsigned int update_idx; + unsigned int measure_idx; + struct wait_queue_head ima_wq; + spinlock_t ima_lock; struct dm_ima_device_table_metadata active_table; struct dm_ima_device_table_metadata inactive_table; - unsigned int dm_version_str_len; }; -void dm_ima_reset_data(struct mapped_device *md); -void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_flags); -void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap); -void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all); -void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map); -void dm_ima_measure_on_device_rename(struct mapped_device *md); +void dm_ima_init(struct mapped_device *md); +void dm_ima_alloc_context(struct dm_ima_context **context, bool noio); +void dm_ima_free_context(struct dm_ima_context *context); +void dm_ima_context_table_op(struct mapped_device *md, + struct dm_ima_context *context, + enum dm_ima_table_op op); +void dm_ima_measure_on_table_load(struct dm_table *table, + struct dm_ima_context *context); +void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap, + struct dm_ima_context *context); +void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all, + struct dm_ima_context *context, + unsigned int idx); +void dm_ima_measure_on_table_clear(struct mapped_device *md, + struct dm_ima_context *context); +void dm_ima_measure_on_device_rename(struct mapped_device *md, + struct dm_ima_context *context); #else -static inline void dm_ima_reset_data(struct mapped_device *md) {} -static inline void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_flags) {} -static inline void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap) {} -static inline void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all) {} -static inline void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map) {} -static inline void dm_ima_measure_on_device_rename(struct mapped_device *md) {} +struct dm_ima_context; + +static inline void dm_ima_init(struct mapped_device *md) {} +static inline void dm_ima_alloc_context(struct dm_ima_context **context, bool noio) {} +static inline void dm_ima_free_context(struct dm_ima_context *context) {} +static inline void dm_ima_context_table_op(struct mapped_device *md, + struct dm_ima_context *context, + enum dm_ima_table_op op) {} +static inline void dm_ima_measure_on_table_load(struct dm_table *table, + struct dm_ima_context *context) {} +static inline void dm_ima_measure_on_device_resume(struct mapped_device *md, + bool swap, + struct dm_ima_context *context) {} +static inline void dm_ima_measure_on_device_remove(struct mapped_device *md, + bool remove_all, + struct dm_ima_context *context, + unsigned int idx) {} +static inline void dm_ima_measure_on_table_clear(struct mapped_device *md, + struct dm_ima_context *context) {} +static inline void dm_ima_measure_on_device_rename(struct mapped_device *md, + struct dm_ima_context *context) {} #endif /* CONFIG_IMA */ diff --git a/drivers/md/dm-inlinecrypt.c b/drivers/md/dm-inlinecrypt.c new file mode 100644 index 0000000000000..be1b4aa8f28bc --- /dev/null +++ b/drivers/md/dm-inlinecrypt.c @@ -0,0 +1,618 @@ +// 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"); diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index a529174c94cf3..ac77dc0ca2255 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -259,6 +259,80 @@ static void free_cell(struct hash_cell *hc) } } +#ifdef CONFIG_IMA + +/* + * Called while holding to _hash_lock, to guarantee the ordering of the + * following dm_ima_measure_on_* functions, which should be called + * right after dropping the _hash_lock + */ +static unsigned int dm_ima_init_context(struct hash_cell *hc, + struct dm_ima_context *context, + bool need_idx) +{ + lockdep_assert_held(&_hash_lock); + + if (unlikely(!context)) + return need_idx ? hc->md->ima.update_idx++ : 0; + + context->update_idx = hc->md->ima.update_idx++; + strcpy(context->dev_name, hc->name); + strcpy(context->dev_uuid, hc->uuid ? : ""); + + return context->update_idx; +} + +/* + * Called by do_resume() to guarantee correct ordering, since do_resume() + * does not grab the _hash_lock when the table is not getting swapped or + * when actually swapping the active table + */ +static bool dm_ima_need_measure(struct mapped_device *md, + struct dm_table *table, + struct dm_ima_context *context) +{ + int srcu_idx; + struct hash_cell *hc; + bool need_measure = false; + + if (unlikely(!context)) + return false; + + down_write(&_hash_lock); + /* Check if the device has been removed */ + hc = dm_get_mdptr(md); + if (hc) { + /* + * If we have a table, we need to make sure that it's the + * active table. Otherwise we raced with another process + * setting the active table and it will do the measurement + */ + if (!table || dm_get_live_table(md, &srcu_idx) == table) { + dm_ima_init_context(hc, context, false); + need_measure = true; + } + if (table) + dm_put_live_table(md, srcu_idx); + } + up_write(&_hash_lock); + + return need_measure; +} +#else +static inline unsigned int dm_ima_init_context(struct hash_cell *hc, + struct dm_ima_context *context, + bool neex_idx) +{ + return 0; +} +static inline bool dm_ima_need_measure(struct mapped_device *md, + struct dm_table *table, + struct dm_ima_context *context) +{ + return false; +} +#endif + /* * The kdev_t and uuid of a device can never change once it is * initially inserted. @@ -344,7 +418,10 @@ static int dm_hash_remove_all(unsigned flags) struct hash_cell *hc; struct mapped_device *md; struct dm_table *t; + struct dm_ima_context *ima_context = NULL; + unsigned int ima_idx; + dm_ima_alloc_context(&ima_context, true); retry: dev_skipped = 0; @@ -353,6 +430,7 @@ retry: for (n = rb_first(&name_rb_tree); n; n = rb_next(n)) { if (flags & DM_REMOVE_INTERRUPTIBLE && fatal_signal_pending(current)) { up_write(&_hash_lock); + dm_ima_free_context(ima_context); return -EINTR; } @@ -367,6 +445,7 @@ retry: continue; } + ima_idx = dm_ima_init_context(hc, ima_context, true); t = __hash_remove(hc); up_write(&_hash_lock); @@ -375,7 +454,7 @@ retry: dm_sync_table(md); dm_table_destroy(t); } - dm_ima_measure_on_device_remove(md, true); + dm_ima_measure_on_device_remove(md, true, ima_context, ima_idx); dm_put(md); if (likely(flags & DM_REMOVE_KEEP_OPEN_DEVICES)) dm_destroy(md); @@ -396,6 +475,7 @@ retry: if (dev_skipped && !(flags & DM_REMOVE_ONLY_DEFERRED)) DMWARN("remove_all left %d open device(s)", dev_skipped); + dm_ima_free_context(ima_context); return 0; } @@ -443,6 +523,7 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, struct mapped_device *md; unsigned int change_uuid = (param->flags & DM_UUID_FLAG) ? 1 : 0; int srcu_idx; + struct dm_ima_context *ima_context = NULL; /* * duplicate new. @@ -451,6 +532,7 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, if (!new_data) return ERR_PTR(-ENOMEM); + dm_ima_alloc_context(&ima_context, true); down_write(&_hash_lock); /* @@ -467,6 +549,7 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, param->name, new); dm_put(hc->md); up_write(&_hash_lock); + dm_ima_free_context(ima_context); kfree(new_data); return ERR_PTR(-EBUSY); } @@ -479,6 +562,7 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, DMERR("Unable to rename non-existent device, %s to %s%s", param->name, change_uuid ? "uuid " : "", new); up_write(&_hash_lock); + dm_ima_free_context(ima_context); kfree(new_data); return ERR_PTR(-ENXIO); } @@ -492,6 +576,7 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, param->name, new, hc->uuid); dm_put(hc->md); up_write(&_hash_lock); + dm_ima_free_context(ima_context); kfree(new_data); return ERR_PTR(-EINVAL); } @@ -514,9 +599,11 @@ static struct mapped_device *dm_hash_rename(struct dm_ioctl *param, md = hc->md; - dm_ima_measure_on_device_rename(md); + dm_ima_init_context(hc, ima_context, false); up_write(&_hash_lock); + dm_ima_measure_on_device_rename(md, ima_context); + dm_ima_free_context(ima_context); kfree(old_name); return md; @@ -995,13 +1082,17 @@ static int dev_remove(struct file *filp, struct dm_ioctl *param, size_t param_si struct mapped_device *md; int r; struct dm_table *t; + struct dm_ima_context *ima_context = NULL; + unsigned int ima_idx; + dm_ima_alloc_context(&ima_context, true); down_write(&_hash_lock); hc = __find_device_hash_cell(param); if (!hc) { DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table."); up_write(&_hash_lock); + dm_ima_free_context(ima_context); return -ENXIO; } @@ -1015,14 +1106,17 @@ static int dev_remove(struct file *filp, struct dm_ioctl *param, size_t param_si if (r == -EBUSY && param->flags & DM_DEFERRED_REMOVE) { up_write(&_hash_lock); dm_put(md); + dm_ima_free_context(ima_context); return 0; } DMDEBUG_LIMIT("unable to remove open device %s", hc->name); up_write(&_hash_lock); dm_put(md); + dm_ima_free_context(ima_context); return r; } + ima_idx = dm_ima_init_context(hc, ima_context, true); t = __hash_remove(hc); up_write(&_hash_lock); @@ -1033,7 +1127,8 @@ static int dev_remove(struct file *filp, struct dm_ioctl *param, size_t param_si param->flags &= ~DM_DEFERRED_REMOVE; - dm_ima_measure_on_device_remove(md, false); + dm_ima_measure_on_device_remove(md, false, ima_context, ima_idx); + dm_ima_free_context(ima_context); if (!dm_kobject_uevent(md, KOBJ_REMOVE, param->event_nr, false)) param->flags |= DM_UEVENT_GENERATED_FLAG; @@ -1169,13 +1264,16 @@ static int do_resume(struct dm_ioctl *param) struct mapped_device *md; struct dm_table *new_map, *old_map = NULL; bool need_resize_uevent = false; + struct dm_ima_context *ima_context = NULL; + dm_ima_alloc_context(&ima_context, true); down_write(&_hash_lock); hc = __find_device_hash_cell(param); if (!hc) { DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table."); up_write(&_hash_lock); + dm_ima_free_context(ima_context); return -ENXIO; } @@ -1184,13 +1282,15 @@ static int do_resume(struct dm_ioctl *param) new_map = hc->new_map; hc->new_map = NULL; param->flags &= ~DM_INACTIVE_PRESENT_FLAG; - + if (new_map) + dm_ima_init_context(hc, ima_context, false); up_write(&_hash_lock); /* Do we need to load a new map ? */ if (new_map) { sector_t old_size, new_size; + dm_ima_context_table_op(md, ima_context, DM_IMA_TABLE_SAVE); /* Suspend if it isn't already suspended */ if (param->flags & DM_SKIP_LOCKFS_FLAG) suspend_flags &= ~DM_SUSPEND_LOCKFS_FLAG; @@ -1204,6 +1304,8 @@ static int do_resume(struct dm_ioctl *param) if (hc && !hc->new_map) { hc->new_map = new_map; new_map = NULL; + dm_ima_init_context(hc, ima_context, + false); } else { r = -ENXIO; } @@ -1211,7 +1313,9 @@ static int do_resume(struct dm_ioctl *param) if (new_map) { dm_sync_table(md); dm_table_destroy(new_map); - } + } else + dm_ima_context_table_op(md, ima_context, DM_IMA_TABLE_RESTORE); + dm_ima_free_context(ima_context); dm_put(md); return r; } @@ -1222,9 +1326,12 @@ static int do_resume(struct dm_ioctl *param) if (IS_ERR(old_map)) { dm_sync_table(md); dm_table_destroy(new_map); + dm_ima_free_context(ima_context); dm_put(md); return PTR_ERR(old_map); } + if (dm_ima_need_measure(md, new_map, ima_context)) + dm_ima_measure_on_device_resume(md, true, ima_context); new_size = dm_get_size(md); if (old_size && new_size && old_size != new_size) need_resize_uevent = true; @@ -1238,7 +1345,10 @@ static int do_resume(struct dm_ioctl *param) if (dm_suspended_md(md)) { r = dm_resume(md); if (!r) { - dm_ima_measure_on_device_resume(md, new_map ? true : false); + if (!new_map && dm_ima_need_measure(md, NULL, + ima_context)) + dm_ima_measure_on_device_resume(md, false, + ima_context); if (!dm_kobject_uevent(md, KOBJ_CHANGE, param->event_nr, need_resize_uevent)) param->flags |= DM_UEVENT_GENERATED_FLAG; @@ -1255,6 +1365,7 @@ static int do_resume(struct dm_ioctl *param) if (!r) __dev_status(md, param); + dm_ima_free_context(ima_context); dm_put(md); return r; } @@ -1532,11 +1643,12 @@ static bool is_valid_type(enum dm_queue_mode cur, enum dm_queue_mode new) static int table_load(struct file *filp, struct dm_ioctl *param, size_t param_size) { - int r; + int r, srcu_idx; struct hash_cell *hc; struct dm_table *t, *old_map = NULL; struct mapped_device *md; struct target_type *immutable_target_type; + struct dm_ima_context *ima_context = NULL; md = find_device(param); if (!md) @@ -1552,8 +1664,6 @@ static int table_load(struct file *filp, struct dm_ioctl *param, size_t param_si if (r) goto err_unlock_md_type; - dm_ima_measure_on_table_load(t, STATUSTYPE_IMA); - immutable_target_type = dm_get_immutable_target_type(md); if (immutable_target_type && (immutable_target_type != dm_table_get_immutable_target_type(t)) && @@ -1580,12 +1690,14 @@ static int table_load(struct file *filp, struct dm_ioctl *param, size_t param_si dm_unlock_md_type(md); + dm_ima_alloc_context(&ima_context, false); /* stage inactive table */ down_write(&_hash_lock); hc = dm_get_mdptr(md); if (!hc) { DMERR("device has been removed from the dev hash table."); up_write(&_hash_lock); + dm_ima_free_context(ima_context); r = -ENXIO; goto err_destroy_table; } @@ -1593,8 +1705,15 @@ static int table_load(struct file *filp, struct dm_ioctl *param, size_t param_si if (hc->new_map) old_map = hc->new_map; hc->new_map = t; + dm_ima_init_context(hc, ima_context, false); + /* Make sure new_map doesn't get freed before we measure it*/ + dm_get_live_table(md, &srcu_idx); up_write(&_hash_lock); + dm_ima_measure_on_table_load(t, ima_context); + dm_ima_free_context(ima_context); + dm_put_live_table(md, srcu_idx); + param->flags |= DM_INACTIVE_PRESENT_FLAG; __dev_status(md, param); @@ -1622,25 +1741,29 @@ static int table_clear(struct file *filp, struct dm_ioctl *param, size_t param_s struct hash_cell *hc; struct mapped_device *md; struct dm_table *old_map = NULL; - bool has_new_map = false; + struct dm_ima_context *ima_context = NULL; + dm_ima_alloc_context(&ima_context, true); down_write(&_hash_lock); hc = __find_device_hash_cell(param); if (!hc) { DMDEBUG_LIMIT("device doesn't appear to be in the dev hash table."); up_write(&_hash_lock); + dm_ima_free_context(ima_context); return -ENXIO; } if (hc->new_map) { old_map = hc->new_map; hc->new_map = NULL; - has_new_map = true; } + dm_ima_init_context(hc, ima_context, false); md = hc->md; up_write(&_hash_lock); + dm_ima_measure_on_table_clear(md, ima_context); + dm_ima_free_context(ima_context); param->flags &= ~DM_INACTIVE_PRESENT_FLAG; __dev_status(md, param); @@ -1649,7 +1772,6 @@ static int table_clear(struct file *filp, struct dm_ioctl *param, size_t param_s dm_sync_table(md); dm_table_destroy(old_map); } - dm_ima_measure_on_table_clear(md, has_new_map); dm_put(md); return 0; @@ -1816,8 +1938,11 @@ static int target_message(struct file *filp, struct dm_ioctl *param, size_t para goto out_argv; table = dm_get_live_table(md, &srcu_idx); - if (!table) + if (!table) { + DMERR("The device has no table."); + r = -EINVAL; goto out_table; + } if (dm_deleting_md(md)) { r = -ENXIO; diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index c5dc083c72441..8f5a5e1342a95 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -3831,6 +3831,7 @@ static void raid_presuspend(struct dm_target *ti) * resume, raid_postsuspend() is too late. */ set_bit(RT_FLAG_RS_FROZEN, &rs->runtime_flags); + set_bit(MD_DM_SUSPENDING, &mddev->flags); if (!reshape_interrupted(mddev)) return; @@ -3847,13 +3848,16 @@ static void raid_presuspend(struct dm_target *ti) static void raid_presuspend_undo(struct dm_target *ti) { struct raid_set *rs = ti->private; + struct mddev *mddev = &rs->md; + clear_bit(MD_DM_SUSPENDING, &mddev->flags); clear_bit(RT_FLAG_RS_FROZEN, &rs->runtime_flags); } static void raid_postsuspend(struct dm_target *ti) { struct raid_set *rs = ti->private; + struct mddev *mddev = &rs->md; if (!test_and_set_bit(RT_FLAG_RS_SUSPENDED, &rs->runtime_flags)) { /* @@ -3864,6 +3868,8 @@ static void raid_postsuspend(struct dm_target *ti) mddev_suspend(&rs->md, false); rs->md.ro = MD_RDONLY; } + clear_bit(MD_DM_SUSPENDING, &mddev->flags); + } static void attempt_restore_of_faulty_devices(struct raid_set *rs) diff --git a/drivers/md/dm-vdo/indexer/index-layout.c b/drivers/md/dm-vdo/indexer/index-layout.c index 5f4ce4ab1b1ea..2d529250000eb 100644 --- a/drivers/md/dm-vdo/indexer/index-layout.c +++ b/drivers/md/dm-vdo/indexer/index-layout.c @@ -282,7 +282,7 @@ static void create_unique_nonce_data(u8 *buffer) u32 rand; size_t offset = 0; - get_random_bytes(&rand, sizeof(u32)); + rand = get_random_u32(); memcpy(buffer + offset, &now, sizeof(now)); offset += sizeof(now); memcpy(buffer + offset, &rand, sizeof(rand)); diff --git a/drivers/md/dm.c b/drivers/md/dm.c index e178fe19973ea..7287bed6eb642 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c @@ -2098,8 +2098,17 @@ static bool dm_poll_dm_io(struct dm_io *io, struct io_comp_batch *iob, WARN_ON_ONCE(!dm_tio_is_normal(&io->tio)); /* don't poll if the mapped io is done */ - if (atomic_read(&io->io_count) > 1) - bio_poll(&io->tio.clone, iob, flags); + if (atomic_read(&io->io_count) > 1) { + /* + * DM hides the target queues from the upper poller, which may + * decide it is safe to spin on a single stacked queue. Do not + * pass that spinning policy down to a target queue: one slow + * clone could keep the task inside dm_poll_bio() for a long + * time. Poll target bios once and let the caller decide + * whether to keep polling, reap completions or reschedule. + */ + bio_poll(&io->tio.clone, iob, flags | BLK_POLL_ONESHOT); + } /* bio_poll holds the last reference */ return atomic_read(&io->io_count) == 1; @@ -2546,7 +2555,7 @@ int dm_create(int minor, struct mapped_device **result) if (!md) return -ENXIO; - dm_ima_reset_data(md); + dm_ima_init(md); *result = md; return 0; diff --git a/drivers/md/md.h b/drivers/md/md.h index 52c3780860464..9e5100609d120 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -346,6 +346,7 @@ struct md_cluster_operations; * @MD_HAS_SUPERBLOCK: There is persistence sb in member disks. * @MD_FAILLAST_DEV: Allow last rdev to be removed. * @MD_SERIALIZE_POLICY: Enforce write IO is not reordered, just used by raid1. + * @MD_DM_SUSPENDING: This DM raid device is suspending. * * change UNSUPPORTED_MDDEV_FLAGS for each array type if new flag is added */ @@ -365,6 +366,7 @@ enum mddev_flags { MD_HAS_SUPERBLOCK, MD_FAILLAST_DEV, MD_SERIALIZE_POLICY, + MD_DM_SUSPENDING, }; enum mddev_sb_flags { diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index ebcb193176702..d0d7f96fd7cd9 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -6042,8 +6042,11 @@ out_release: raid5_release_stripe(sh); out: if (ret == STRIPE_SCHEDULE_AND_RETRY && reshape_interrupted(mddev)) { - bi->bi_status = BLK_STS_RESOURCE; - ret = STRIPE_WAIT_RESHAPE; + if (!mddev_is_dm(mddev) || + test_bit(MD_DM_SUSPENDING, &mddev->flags)) { + bi->bi_status = BLK_STS_RESOURCE; + ret = STRIPE_WAIT_RESHAPE; + } pr_err_ratelimited("dm-raid456: io across reshape position while reshape can't make progress"); } return ret; |
