From 324926e2d877c3d19c8aa7ec104c9d02448f0469 Mon Sep 17 00:00:00 2001 From: Milan Broz Date: Wed, 19 Feb 2025 15:53:15 +0100 Subject: [PATCH] LUKS2: support Inline tags format and activation for integrity protection --- lib/libcryptsetup.h | 2 + lib/luks2/luks2_json_metadata.c | 24 ++++++++++-- lib/setup.c | 65 ++++++++++++++++++++++++--------- man/common_options.adoc | 15 ++++++++ src/cryptsetup.c | 16 +++++++- src/cryptsetup_arg_list.h | 2 + src/cryptsetup_args.h | 1 + 7 files changed, 103 insertions(+), 22 deletions(-) diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index 9ad6b4e1..77eb29ef 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -1596,6 +1596,8 @@ uint64_t crypt_get_active_integrity_failures(struct crypt_device *cd, #define CRYPT_REQUIREMENT_ONLINE_REENCRYPT (UINT32_C(1) << 1) /** Device configured with OPAL support */ #define CRYPT_REQUIREMENT_OPAL (UINT32_C(1) << 2) +/** Device configured with inline HW tags */ +#define CRYPT_REQUIREMENT_INLINE_HW_TAGS (UINT32_C(1) << 3) /** unknown requirement in header (output only) */ #define CRYPT_REQUIREMENT_UNKNOWN (UINT32_C(1) << 31) diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c index cde66ed0..f01119c7 100644 --- a/lib/luks2/luks2_json_metadata.c +++ b/lib/luks2/luks2_json_metadata.c @@ -635,6 +635,11 @@ static int reqs_opal(uint32_t reqs) return reqs & CRYPT_REQUIREMENT_OPAL; } +static int reqs_inline_hw_tags(uint32_t reqs) +{ + return reqs & CRYPT_REQUIREMENT_INLINE_HW_TAGS; +} + /* * Config section requirements object must be valid. * Also general segments section must be validated first. @@ -1423,7 +1428,8 @@ int LUKS2_hdr_restore(struct crypt_device *cd, struct luks2_hdr *hdr, } /* do not allow header restore from backup with unmet requirements */ - if (LUKS2_unmet_requirements(cd, &hdr_file, CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1)) { + if (LUKS2_unmet_requirements(cd, &hdr_file, + CRYPT_REQUIREMENT_ONLINE_REENCRYPT | CRYPT_REQUIREMENT_INLINE_HW_TAGS, 1)) { log_err(cd, _("Forbidden LUKS2 requirements detected in backup %s."), backup_file); r = -ETXTBSY; @@ -1638,6 +1644,7 @@ static const struct requirement_flag requirements_flags[] = { { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 2, "online-reencrypt-v2" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 3, "online-reencrypt-v3" }, { CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1, "online-reencrypt" }, + { CRYPT_REQUIREMENT_INLINE_HW_TAGS, 1, "inline-hw-tags" }, { CRYPT_REQUIREMENT_OPAL, 1, "opal" }, { 0, 0, NULL } }; @@ -2656,7 +2663,7 @@ int LUKS2_activate(struct crypt_device *cd, { int r; bool dynamic, read_lock, write_lock, opal_lock_on_error = false; - uint32_t opal_segment_number; + uint32_t opal_segment_number, requirements_flags; uint64_t range_offset_sectors, range_length_sectors, device_length_bytes; struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); struct crypt_dm_active_device dmdi = {}, dmd = { @@ -2665,7 +2672,8 @@ int LUKS2_activate(struct crypt_device *cd, struct crypt_lock_handle *opal_lh = NULL; /* do not allow activation when particular requirements detected */ - if ((r = LUKS2_unmet_requirements(cd, hdr, CRYPT_REQUIREMENT_OPAL, 0))) + if ((r = LUKS2_unmet_requirements(cd, hdr, + CRYPT_REQUIREMENT_OPAL | CRYPT_REQUIREMENT_INLINE_HW_TAGS, 0))) return r; /* Check that cipher is in compatible format */ @@ -2754,7 +2762,13 @@ int LUKS2_activate(struct crypt_device *cd, dmd.flags |= flags; - if (crypt_get_integrity_tag_size(cd)) { + if (crypt_persistent_flags_get(cd, CRYPT_FLAGS_REQUIREMENTS, &requirements_flags)) { + r = -EINVAL; + goto out; + } + + if (crypt_get_integrity_tag_size(cd) && + !(requirements_flags & CRYPT_REQUIREMENT_INLINE_HW_TAGS)) { if (!LUKS2_integrity_compatible(hdr)) { log_err(cd, _("Unsupported device integrity configuration.")); r = -EINVAL; @@ -3002,6 +3016,8 @@ int LUKS2_unmet_requirements(struct crypt_device *cd, struct luks2_hdr *hdr, uin log_err(cd, _("Operation incompatible with device marked for LUKS2 reencryption. Aborting.")); if (reqs_opal(reqs) && !quiet) log_err(cd, _("Operation incompatible with device using OPAL. Aborting.")); + if (reqs_inline_hw_tags(reqs) && !quiet) + log_err(cd, _("Operation incompatible with device using inline HW tags. Aborting.")); /* any remaining unmasked requirement fails the check */ return reqs ? -EINVAL : 0; diff --git a/lib/setup.c b/lib/setup.c index 6d3a3f2a..732d5d83 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -414,7 +414,7 @@ static int onlyLUKSnoRequirements(struct crypt_device *cd) static int onlyLUKS(struct crypt_device *cd) { - return _onlyLUKS(cd, 0, CRYPT_REQUIREMENT_OPAL); + return _onlyLUKS(cd, 0, CRYPT_REQUIREMENT_OPAL | CRYPT_REQUIREMENT_INLINE_HW_TAGS); } static int _onlyLUKS2(struct crypt_device *cd, uint32_t cdflags, uint32_t mask) @@ -447,7 +447,7 @@ static int onlyLUKS2unrestricted(struct crypt_device *cd) /* Internal only */ int onlyLUKS2(struct crypt_device *cd) { - return _onlyLUKS2(cd, 0, CRYPT_REQUIREMENT_OPAL); + return _onlyLUKS2(cd, 0, CRYPT_REQUIREMENT_OPAL | CRYPT_REQUIREMENT_INLINE_HW_TAGS); } /* Internal only */ @@ -1929,7 +1929,7 @@ static int _crypt_format_luks2(struct crypt_device *cd, const char *volume_key, size_t volume_key_size, struct crypt_params_luks2 *params, - bool sector_size_autodetect) + bool sector_size_autodetect, bool integrity_inline) { int r; unsigned long required_alignment = DEFAULT_DISK_ALIGNMENT; @@ -2066,6 +2066,14 @@ static int _crypt_format_luks2(struct crypt_device *cd, if (r < 0) goto out; + if (integrity_inline) { + log_dbg(cd, "Adding LUKS2 inline HW tags requirement flag."); + r = LUKS2_config_set_requirement_version(cd, &cd->u.luks2.hdr, + CRYPT_REQUIREMENT_INLINE_HW_TAGS, 1, false); + if (r < 0) + goto out; + } + if (params && (params->label || params->subsystem)) { r = LUKS2_hdr_labels(cd, &cd->u.luks2.hdr, params->label, params->subsystem, 0); @@ -2103,7 +2111,10 @@ static int _crypt_format_luks2(struct crypt_device *cd, goto out; } + } + /* Format underlying virtual dm-integrity device */ + if (!integrity_inline && crypt_get_integrity_tag_size(cd)) { if (integrity_key_size) { integrity_key = crypt_alloc_volume_key(integrity_key_size, crypt_volume_key_get_key(cd->volume_key) + volume_key_size - integrity_key_size); @@ -2979,8 +2990,9 @@ int crypt_format_inline(struct crypt_device *cd, size_t volume_key_size, void *params) { + struct crypt_params_luks2 *lparams; const struct crypt_params_integrity *iparams; - uint32_t device_tag_size; + uint32_t device_tag_size, required_tag_size; struct device *idevice; size_t sector_size, required_sector_size; int r; @@ -2996,13 +3008,25 @@ int crypt_format_inline(struct crypt_device *cd, log_dbg(cd, "Formatting device %s as type %s with inline tags.", mdata_device_path(cd) ?: "(none)", type); if (isINTEGRITY(type)) { + lparams = NULL; iparams = params; idevice = crypt_metadata_device(cd); required_sector_size = iparams->sector_size; + required_tag_size = iparams->tag_size; /* Unused in standalone integrity */ if (cipher || cipher_mode) return -EINVAL; + } else if (isLUKS2(type)) { + lparams = params; + iparams = lparams->integrity_params; + idevice = crypt_data_device(cd); + required_sector_size = lparams->sector_size; + + if (!lparams->integrity || !idevice) + return -EINVAL; + + required_tag_size = INTEGRITY_tag_size(lparams->integrity, cipher, cipher_mode); } else { log_err(cd, _("Unknown or unsupported device type %s requested."), type); return -EINVAL; @@ -3015,30 +3039,37 @@ int crypt_format_inline(struct crypt_device *cd, iparams->journal_integrity_key_size)) return -EINVAL; + if (!device_is_nop_dif(idevice, &device_tag_size)) { + log_err(cd, _("Device %s does not provide inline integrity data fields."), mdata_device_path(cd)); + return -EINVAL; + } + + /* We can get device_tag_size = 0 as kernel provides this info only for some block devices */ + if (device_tag_size > 0 && device_tag_size < required_tag_size) { + log_err(cd, _("Inline tag size %" PRIu32 " [bytes] is larger than %" PRIu32 " provided by device %s."), + required_tag_size, device_tag_size, mdata_device_path(cd)); + return -EINVAL; + } + log_dbg(cd, "Inline integrity is supported (%" PRIu32 ").", device_tag_size); + /* Inline must use sectors size as hardware device */ sector_size = device_block_size(cd, idevice); if (!sector_size) return -EINVAL; /* No autodetection, use device sector size */ - if (sector_size != required_sector_size) { + if (isLUKS2(type) && lparams && !required_sector_size) + lparams->sector_size = sector_size; + else if (sector_size != required_sector_size) { log_err(cd, _("Sector must be the same as device hardware sector (%zu bytes)."), sector_size); return -EINVAL; } - if (!device_is_nop_dif(crypt_metadata_device(cd), &device_tag_size)) { - log_err(cd, _("Device %s does not provide inline integrity data fields."), mdata_device_path(cd)); - return -EINVAL; - } - - if (device_tag_size < iparams->tag_size) { - log_err(cd, _("Inline tag size %" PRIu32 " [bytes] is larger than %" PRIu32 " provided by device %s."), - iparams->tag_size, device_tag_size, mdata_device_path(cd)); - return -EINVAL; - } - if (isINTEGRITY(type)) r = _crypt_format_integrity(cd, uuid, params, volume_key, volume_key_size, true); + else if (isLUKS2(type)) + r = _crypt_format_luks2(cd, cipher, cipher_mode, + uuid, volume_key, volume_key_size, params, false, true); else r = -EINVAL; @@ -3087,7 +3118,7 @@ static int _crypt_format(struct crypt_device *cd, uuid, volume_key, volume_key_size, params); else if (isLUKS2(type)) r = _crypt_format_luks2(cd, cipher, cipher_mode, - uuid, volume_key, volume_key_size, params, sector_size_autodetect); + uuid, volume_key, volume_key_size, params, sector_size_autodetect, false); else if (isLOOPAES(type)) r = _crypt_format_loopaes(cd, cipher, uuid, volume_key_size, params); else if (isVERITY(type)) diff --git a/man/common_options.adoc b/man/common_options.adoc index ea6d7dc9..914b218d 100644 --- a/man/common_options.adoc +++ b/man/common_options.adoc @@ -396,6 +396,21 @@ option). For more info, see _AUTHENTICATED DISK ENCRYPTION_ section in *cryptsetup*(8). endif::[] +ifdef::ACTION_LUKSFORMAT[] +*--integrity-inline*:: +Store integrity tags to hardware sector integrity fields. +The device must support sectors with additional protection information +(PI, also known as DIF - data integrity field) of the requested size. +Another storage subsystem must not use the additional field +(the device must present a "nop" profile in the kernel). +Note that some devices must be reformatted at a low level to support +this option; for NVMe devices, see nvme(1) id-ns LBA profiles. ++ +No journal or bitmap is used in this mode. The device should operate +with native speed (without any overhead). +This option is available since the Linux kernel version 6.11. +endif::[] + ifdef::ACTION_LUKSFORMAT[] *--integrity-key-size BYTES*:: The size of the data integrity key. Configurable only for HMAC integrity. diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 6d8931cf..52b143fa 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -997,7 +997,8 @@ static int action_status(void) if (ip.integrity_key_size) log_std(" integrity keysize: %d [bits]\n", ip.integrity_key_size * 8); if (ip.tag_size) - log_std(" integrity tag size: %u [bytes]\n", ip.tag_size); + log_std(" integrity tag size: %u [bytes] %s\n", ip.tag_size, + (cad.flags & CRYPT_ACTIVATE_INLINE_MODE) ? " (inline HW tags)" : ""); device = crypt_get_device_name(cd); log_std(" device: %s\n", device); if ((backing_file = crypt_loop_backing_file(device))) { @@ -1488,6 +1489,11 @@ int luksFormat(struct crypt_device **r_cd, struct crypt_keyslot_context **r_kc) log_err(_("OPAL is supported only for LUKS2 format.")); return -EINVAL; } + + if (ARG_SET(OPT_INTEGRITY_INLINE_ID)) { + log_err(_("Inline hw tags are supported only for LUKS2 format.")); + return -EINVAL; + } } else return -EINVAL; @@ -1645,6 +1651,9 @@ int luksFormat(struct crypt_device **r_cd, struct crypt_keyslot_context **r_kc) ARG_SET(OPT_HW_OPAL_ONLY_ID) ? NULL : cipher, ARG_SET(OPT_HW_OPAL_ONLY_ID) ? NULL : cipher_mode, ARG_STR(OPT_UUID_ID), key, keysize, params, &opal_params); + else if (ARG_SET(OPT_INTEGRITY_INLINE_ID)) + r = crypt_format_inline(cd, type, cipher, cipher_mode, + ARG_STR(OPT_UUID_ID), key, keysize, params); else r = crypt_format(cd, type, cipher, cipher_mode, ARG_STR(OPT_UUID_ID), key, keysize, params); @@ -3931,6 +3940,11 @@ int main(int argc, const char **argv) _("Cannot use keyring key description when keyring is disabled."), poptGetInvocationName(popt_context)); + if (ARG_SET(OPT_INTEGRITY_INLINE_ID) && !ARG_SET(OPT_INTEGRITY_ID)) + usage(popt_context, EXIT_FAILURE, + _("Inline integrity must be used together with --integrity option."), + poptGetInvocationName(popt_context)); + if (ARG_SET(OPT_DEBUG_ID) || ARG_SET(OPT_DEBUG_JSON_ID)) { crypt_set_debug_level(ARG_SET(OPT_DEBUG_JSON_ID)? CRYPT_DEBUG_JSON : CRYPT_DEBUG_ALL); dbg_version_and_cmd(argc, argv); diff --git a/src/cryptsetup_arg_list.h b/src/cryptsetup_arg_list.h index 25de767d..26652dd8 100644 --- a/src/cryptsetup_arg_list.h +++ b/src/cryptsetup_arg_list.h @@ -72,6 +72,8 @@ ARG(OPT_INIT_ONLY, '\0', POPT_ARG_NONE, N_("Initialize LUKS2 reencryption in met ARG(OPT_INTEGRITY, 'I', POPT_ARG_STRING, N_("Data integrity algorithm (LUKS2 only)"), NULL, CRYPT_ARG_STRING, {}, OPT_INTEGRITY_ACTIONS) +ARG(OPT_INTEGRITY_INLINE, '\0', POPT_ARG_NONE, N_("Use inline mode (use HW integrity field)"), NULL, CRYPT_ARG_BOOL, {}, OPT_INTEGRITY_INLINE_ACTIONS) + ARG(OPT_INTEGRITY_KEY_SIZE, '\0', POPT_ARG_STRING, N_("The size of the data integrity key"), N_("BITS"), CRYPT_ARG_UINT32, {}, {}) ARG(OPT_INTEGRITY_LEGACY_PADDING,'\0', POPT_ARG_NONE, N_("Use inefficient legacy padding (old kernels)"), NULL, CRYPT_ARG_BOOL, {}, {}) diff --git a/src/cryptsetup_args.h b/src/cryptsetup_args.h index 5a59b7ee..43926f99 100644 --- a/src/cryptsetup_args.h +++ b/src/cryptsetup_args.h @@ -55,6 +55,7 @@ #define OPT_HW_OPAL_ACTIONS { FORMAT_ACTION } #define OPT_HW_OPAL_ONLY_ACTIONS OPT_HW_OPAL_ACTIONS #define OPT_INTEGRITY_ACTIONS { FORMAT_ACTION } +#define OPT_INTEGRITY_INLINE_ACTIONS { FORMAT_ACTION } #define OPT_INTEGRITY_NO_WIPE_ACTIONS { FORMAT_ACTION } #define OPT_ITER_TIME_ACTIONS { BENCHMARK_ACTION, FORMAT_ACTION, ADDKEY_ACTION, CHANGEKEY_ACTION, CONVERTKEY_ACTION, REENCRYPT_ACTION } #define OPT_IV_LARGE_SECTORS_ACTIONS { OPEN_ACTION }