integrity: support Inline tags format and activation

Support inline format in API with crypt_format_inline().

Add --integrity-inline option to integritysetup.
This commit is contained in:
Milan Broz
2025-02-17 13:32:27 +01:00
parent 49ccafe38a
commit 006ebd832f
10 changed files with 218 additions and 44 deletions

View File

@@ -31,6 +31,8 @@ static int INTEGRITY_read_superblock(struct crypt_device *cd,
{
int devfd, r;
log_dbg(cd, "Reading kernel dm-integrity metadata on %s.", device_path(device));
devfd = device_open(cd, device, O_RDONLY);
if(devfd < 0)
return -EINVAL;
@@ -71,8 +73,10 @@ int INTEGRITY_read_sb(struct crypt_device *cd,
if (r)
return r;
params->sector_size = SECTOR_SIZE << sb.log2_sectors_per_block;
params->tag_size = sb.integrity_tag_size;
if (params) {
params->sector_size = SECTOR_SIZE << sb.log2_sectors_per_block;
params->tag_size = sb.integrity_tag_size;
}
if (flags)
*flags = sb.flags;
@@ -248,6 +252,9 @@ int INTEGRITY_create_dmd_device(struct crypt_device *cd,
if (sb_flags & SB_FLAG_RECALCULATING)
dmd->flags |= CRYPT_ACTIVATE_RECALCULATE;
if (sb_flags & SB_FLAG_INLINE)
dmd->flags |= (CRYPT_ACTIVATE_NO_JOURNAL | CRYPT_ACTIVATE_INLINE_MODE);
r = INTEGRITY_data_sectors(cd, INTEGRITY_metadata_device(cd),
crypt_get_data_offset(cd) * SECTOR_SIZE, &dmd->size);
if (r < 0)
@@ -273,8 +280,9 @@ int INTEGRITY_activate_dmd_device(struct crypt_device *cd,
if (!single_segment(dmd) || tgt->type != DM_INTEGRITY)
return -EINVAL;
log_dbg(cd, "Trying to activate INTEGRITY device on top of %s, using name %s, tag size %d, provided sectors %" PRIu64".",
device_path(tgt->data_device), name, tgt->u.integrity.tag_size, dmd->size);
log_dbg(cd, "Trying to activate INTEGRITY device on top of %s, using name %s, tag size %d%s, provided sectors %" PRIu64".",
device_path(tgt->data_device), name, tgt->u.integrity.tag_size,
(sb_flags & SB_FLAG_INLINE) ? " (inline)" :"", dmd->size);
r = create_or_reload_device(cd, name, type, dmd);
@@ -298,6 +306,12 @@ int INTEGRITY_activate_dmd_device(struct crypt_device *cd,
return -ENOTSUP;
}
if (r < 0 && (sb_flags & SB_FLAG_INLINE) && !dm_flags(cd, DM_INTEGRITY, &dmi_flags) &&
!(dmi_flags & DM_INTEGRITY_INLINE_MODE_SUPPORTED)) {
log_err(cd, _("Kernel does not support dm-integrity inline mode."));
return -ENOTSUP;
}
return r;
}
@@ -393,7 +407,9 @@ int INTEGRITY_format(struct crypt_device *cd,
struct volume_key *integrity_key,
struct volume_key *journal_crypt_key,
struct volume_key *journal_mac_key,
uint64_t backing_device_sectors)
uint64_t backing_device_sectors,
uint32_t *sb_flags,
bool integrity_inline)
{
uint64_t dmi_flags;
char reduced_device_name[70], tmp_name[64], tmp_uuid[40];
@@ -440,6 +456,9 @@ int INTEGRITY_format(struct crypt_device *cd,
p_data_device = crypt_data_device(cd);
}
if (integrity_inline)
dmdi.flags |= (CRYPT_ACTIVATE_NO_JOURNAL | CRYPT_ACTIVATE_INLINE_MODE);
r = dm_integrity_target_set(cd, tgt, 0, dmdi.size, p_metadata_device,
p_data_device, crypt_get_integrity_tag_size(cd),
data_offset_sectors, crypt_get_sector_size(cd), integrity_key,
@@ -447,8 +466,8 @@ int INTEGRITY_format(struct crypt_device *cd,
if (r < 0)
goto err;
log_dbg(cd, "Trying to format INTEGRITY device on top of %s, tmp name %s, tag size %d.",
device_path(tgt->data_device), tmp_name, tgt->u.integrity.tag_size);
log_dbg(cd, "Trying to format INTEGRITY device on top of %s, tmp name %s, tag size %d%s.",
device_path(tgt->data_device), tmp_name, tgt->u.integrity.tag_size, integrity_inline ? " (inline)" : "");
r = device_block_adjust(cd, tgt->data_device, DEV_EXCL, tgt->u.integrity.offset, NULL, NULL);
if (r < 0 && (dm_flags(cd, DM_INTEGRITY, &dmi_flags) || !(dmi_flags & DM_INTEGRITY_SUPPORTED))) {
@@ -469,6 +488,12 @@ int INTEGRITY_format(struct crypt_device *cd,
goto err;
r = dm_remove_device(cd, tmp_name, CRYPT_DEACTIVATE_FORCE);
if (r)
goto err;
/* reload sb_flags from superblock (important for SB_FLAG_INLINE) */
if (sb_flags)
r = INTEGRITY_read_sb(cd, NULL, sb_flags);
err:
dm_targets_free(cd, &dmdi);
if (reduced_device) {

View File

@@ -9,6 +9,7 @@
#define _CRYPTSETUP_INTEGRITY_H
#include <stdint.h>
#include <stdbool.h>
struct crypt_device;
struct device;
@@ -68,7 +69,9 @@ int INTEGRITY_format(struct crypt_device *cd,
struct volume_key *integrity_key,
struct volume_key *journal_crypt_key,
struct volume_key *journal_mac_key,
uint64_t backing_device_sectors);
uint64_t backing_device_sectors,
uint32_t *sb_flags,
bool integrity_inline);
int INTEGRITY_activate(struct crypt_device *cd,
const char *name,

View File

@@ -703,6 +703,35 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
struct crypt_params_luks2 *params,
struct crypt_params_hw_opal *opal_params);
/**
* Create (format) new integrity-protected device using integrity inline mode (HW sector tags).
* This can be used for @e INTEGRITY and @e LUKS2 with integrity protection
*
* @pre @e cd contains initialized and not formatted device context (device type must @b not be set)
*
* @param cd crypt device handle
* @param type type of device (optional params struct must be of this type)
* @param cipher (e.g. "aes") or @e NULL for @e INTEGRITY
* @param cipher_mode including IV specification (e.g. "xts-plain") or @e NULL for @e INTEGRITY
* @param uuid requested UUID or @e NULL if it should be generated
* @param volume_key pre-generated integrity/volume key (if needed) or @e NULL
* @param volume_key_size size of volume/integrity key in bytes.
* @param params crypt type specific parameters (see @link crypt-type @endlink)
*
* @returns @e 0 on success or negative errno value otherwise.
*
* @note Journal parameters must be set to zero in integrity part of @e params.
* Only tag_size, sector_size, buffer_sectors, integrity options should be set.
*/
int crypt_format_inline(struct crypt_device *cd,
const char *type,
const char *cipher,
const char *cipher_mode,
const char *uuid,
const char *volume_key,
size_t volume_key_size,
void *params);
/**
* Set format compatibility flags.
*

View File

@@ -193,4 +193,5 @@ CRYPTSETUP_2.8 {
crypt_keyslot_context_init_by_vk_in_keyring;
crypt_reencrypt_init_by_keyslot_context;
crypt_get_old_volume_key_size;
crypt_format_inline;
} CRYPTSETUP_2.7;

View File

@@ -2200,7 +2200,8 @@ static int _crypt_format_luks2(struct crypt_device *cd,
goto out;
}
}
r = INTEGRITY_format(cd, params ? params->integrity_params : NULL, integrity_key, NULL, NULL, 0);
r = INTEGRITY_format(cd, params ? params->integrity_params : NULL,
integrity_key, NULL, NULL, 0, NULL, false);
if (r)
log_err(cd, _("Cannot format integrity for device %s."),
data_device_path(cd));
@@ -2658,7 +2659,7 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
* Create reduced dm-integrity device only if locking range size does
* not match device size.
*/
device_size_bytes != range_size_bytes ? range_size_bytes / SECTOR_SIZE : 0);
device_size_bytes != range_size_bytes ? range_size_bytes / SECTOR_SIZE : 0, NULL, false);
if (r)
log_err(cd, _("Cannot format integrity for device %s."),
data_device_path(cd));
@@ -2940,7 +2941,8 @@ out:
static int _crypt_format_integrity(struct crypt_device *cd,
const char *uuid,
struct crypt_params_integrity *params,
const char *integrity_key, size_t integrity_key_size)
const char *integrity_key, size_t integrity_key_size,
bool integrity_inline)
{
int r;
uint32_t integrity_tag_size;
@@ -3037,7 +3039,9 @@ static int _crypt_format_integrity(struct crypt_device *cd,
}
}
r = INTEGRITY_format(cd, params, ik, cd->u.integrity.journal_crypt_key, cd->u.integrity.journal_mac_key, 0);
r = INTEGRITY_format(cd, params, ik, cd->u.integrity.journal_crypt_key,
cd->u.integrity.journal_mac_key, 0, &cd->u.integrity.sb_flags,
integrity_inline);
if (r)
log_err(cd, _("Cannot format integrity for device %s."), mdata_device_path(cd));
@@ -3054,6 +3058,75 @@ out:
return r;
}
int crypt_format_inline(struct crypt_device *cd,
const char *type,
const char *cipher,
const char *cipher_mode,
const char *uuid,
const char *volume_key,
size_t volume_key_size,
void *params)
{
const struct crypt_params_integrity *iparams;
struct device *idevice;
size_t sector_size, required_sector_size;
int r;
if (!cd || !params)
return -EINVAL;
if (cd->type) {
log_dbg(cd, "Context already formatted as %s.", cd->type);
return -EINVAL;
}
log_dbg(cd, "Formatting device %s as type %s with inline tags.", mdata_device_path(cd) ?: "(none)", type);
if (isINTEGRITY(type)) {
iparams = params;
idevice = crypt_metadata_device(cd);
required_sector_size = iparams->sector_size;
/* Unused in standalone integrity */
if (cipher || cipher_mode)
return -EINVAL;
} else {
log_err(cd, _("Unknown or unsupported device type %s requested."), type);
return -EINVAL;
}
/* In inline mode journal will be never used, check that params are not set */
if (iparams && (iparams->journal_size || iparams->journal_watermark || iparams->journal_commit_time ||
iparams->interleave_sectors || iparams->journal_integrity || iparams->journal_integrity_key ||
iparams->journal_integrity_key_size || iparams->journal_crypt || iparams->journal_crypt_key ||
iparams->journal_integrity_key_size))
return -EINVAL;
/* 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) {
log_err(cd, _("Sector must be the same as device hardware sector (%zu bytes)."), sector_size);
return -EINVAL;
}
if (isINTEGRITY(type))
r = _crypt_format_integrity(cd, uuid, params, volume_key, volume_key_size, true);
else
r = -EINVAL;
if (r < 0) {
crypt_set_null_type(cd);
crypt_free_volume_key(cd->volume_key);
cd->volume_key = NULL;
}
return r;
}
static int _crypt_format(struct crypt_device *cd,
const char *type,
const char *cipher,
@@ -3096,9 +3169,9 @@ static int _crypt_format(struct crypt_device *cd,
else if (isVERITY(type))
r = _crypt_format_verity(cd, uuid, params);
else if (isINTEGRITY(type))
r = _crypt_format_integrity(cd, uuid, params, volume_key, volume_key_size);
r = _crypt_format_integrity(cd, uuid, params, volume_key, volume_key_size, false);
else {
log_err(cd, _("Unknown crypt device type %s requested."), type);
log_err(cd, _("Unknown or unsupported device type %s requested."), type);
r = -EINVAL;
}

View File

@@ -155,6 +155,19 @@ because we don't have to write the data twice, but it is also less
reliable, because if data corruption happens when the machine crashes,
it may not be detected.
*--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.
*--integrity-key-file FILE*::
The file with the integrity key.

View File

@@ -112,11 +112,6 @@ static int action_format(void)
{
struct crypt_device *cd = NULL;
struct crypt_params_integrity params = {
.journal_size = ARG_UINT64(OPT_JOURNAL_SIZE_ID),
.interleave_sectors = ARG_UINT32(OPT_INTERLEAVE_SECTORS_ID),
/* in bitmap mode we have to overload these values... */
.journal_watermark = ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID) ? ARG_UINT32(OPT_BITMAP_SECTORS_PER_BIT_ID) : ARG_UINT32(OPT_JOURNAL_WATERMARK_ID),
.journal_commit_time = ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID) ? ARG_UINT32(OPT_BITMAP_FLUSH_TIME_ID) : ARG_UINT32(OPT_JOURNAL_COMMIT_TIME_ID),
.buffer_sectors = ARG_UINT32(OPT_BUFFER_SECTORS_ID),
.tag_size = ARG_UINT32(OPT_TAG_SIZE_ID),
.sector_size = ARG_UINT32(OPT_SECTOR_SIZE_ID),
@@ -133,22 +128,31 @@ static int action_format(void)
}
params.integrity = integrity;
if (ARG_SET(OPT_JOURNAL_INTEGRITY_ID)) {
r = crypt_parse_hash_integrity_mode(ARG_STR(OPT_JOURNAL_INTEGRITY_ID), journal_integrity);
if (r < 0) {
log_err(_("No known integrity specification pattern detected."));
return r;
}
params.journal_integrity = journal_integrity;
}
if (!ARG_SET(OPT_INTEGRITY_INLINE_ID)) {
params.journal_size = ARG_UINT64(OPT_JOURNAL_SIZE_ID);
params.interleave_sectors = ARG_UINT32(OPT_INTERLEAVE_SECTORS_ID);
/* in bitmap mode we have to overload these values... */
params.journal_watermark = ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID) ? ARG_UINT32(OPT_BITMAP_SECTORS_PER_BIT_ID) : ARG_UINT32(OPT_JOURNAL_WATERMARK_ID);
params.journal_commit_time = ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID) ? ARG_UINT32(OPT_BITMAP_FLUSH_TIME_ID) : ARG_UINT32(OPT_JOURNAL_COMMIT_TIME_ID);
if (ARG_SET(OPT_JOURNAL_CRYPT_ID)) {
r = crypt_parse_hash_integrity_mode(ARG_STR(OPT_JOURNAL_CRYPT_ID), journal_crypt);
if (r < 0) {
log_err(_("No known integrity specification pattern detected."));
return r;
if (ARG_SET(OPT_JOURNAL_INTEGRITY_ID)) {
r = crypt_parse_hash_integrity_mode(ARG_STR(OPT_JOURNAL_INTEGRITY_ID), journal_integrity);
if (r < 0) {
log_err(_("No known integrity specification pattern detected."));
return r;
}
params.journal_integrity = journal_integrity;
}
params.journal_crypt = journal_crypt;
if (ARG_SET(OPT_JOURNAL_CRYPT_ID)) {
r = crypt_parse_hash_integrity_mode(ARG_STR(OPT_JOURNAL_CRYPT_ID), journal_crypt);
if (r < 0) {
log_err(_("No known integrity specification pattern detected."));
return r;
}
params.journal_crypt = journal_crypt;
}
}
r = _read_keys(&integrity_key, &params);
@@ -196,13 +200,18 @@ static int action_format(void)
if (ARG_SET(OPT_INTEGRITY_LEGACY_HMAC_ID))
crypt_set_compatibility(cd, CRYPT_COMPAT_LEGACY_INTEGRITY_HMAC);
r = crypt_format(cd, CRYPT_INTEGRITY, NULL, NULL, NULL, integrity_key, params.integrity_key_size, &params);
if (ARG_SET(OPT_INTEGRITY_INLINE_ID))
r = crypt_format_inline(cd, CRYPT_INTEGRITY, NULL, NULL, NULL,
integrity_key, params.integrity_key_size, &params);
else
r = crypt_format(cd, CRYPT_INTEGRITY, NULL, NULL, NULL,
integrity_key, params.integrity_key_size, &params);
if (r < 0) /* FIXME: call wipe signatures again */
goto out;
if (!ARG_SET(OPT_BATCH_MODE_ID) && !crypt_get_integrity_info(cd, &params2))
log_std(_("Formatted with tag size %u, internal integrity %s.\n"),
params2.tag_size, params2.integrity);
log_std(_("Formatted with tag size %u%s, internal integrity %s.\n"),
params2.tag_size, ARG_SET(OPT_INTEGRITY_INLINE_ID) ? " (inline hw tags)" : "", params2.integrity);
if (!ARG_SET(OPT_NO_WIPE_ID)) {
r = _wipe_data_device(cd, integrity_key);
@@ -227,6 +236,7 @@ static int action_resize(void)
uint64_t old_dev_size;
char path[PATH_MAX];
char *backing_file = NULL;
uint32_t reactivate_flags;
struct tools_progress_params prog_parms = {
.frequency = ARG_UINT32(OPT_PROGRESS_FREQUENCY_ID),
.batch_mode = ARG_SET(OPT_BATCH_MODE_ID),
@@ -256,12 +266,14 @@ static int action_resize(void)
if (r)
goto out;
if (!new_dev_size) {
r = crypt_get_active_device(cd, action_argv[0], &cad);
if (r)
goto out;
r = crypt_get_active_device(cd, action_argv[0], &cad);
if (r)
goto out;
reactivate_flags = CRYPT_ACTIVATE_REFRESH | (cad.flags & CRYPT_ACTIVATE_INLINE_MODE);
if (!new_dev_size)
new_dev_size = cad.size;
}
if (new_dev_size > old_dev_size) {
if (ARG_SET(OPT_WIPE_ID)) {
@@ -279,8 +291,8 @@ static int action_resize(void)
set_int_block(0);
} else {
log_dbg("Setting recalculate flag");
r = crypt_activate_by_volume_key(cd, action_argv[0], NULL, 0, CRYPT_ACTIVATE_REFRESH | CRYPT_ACTIVATE_RECALCULATE);
reactivate_flags |= CRYPT_ACTIVATE_RECALCULATE;
r = crypt_activate_by_volume_key(cd, action_argv[0], NULL, 0, reactivate_flags);
if (r == -ENOTSUP)
log_err(_("Setting recalculate flag is not supported, you may consider using --wipe instead."));
}
@@ -482,7 +494,11 @@ static int action_status(void)
if (cad.flags & CRYPT_ACTIVATE_NO_JOURNAL_BITMAP) {
log_std(" bitmap 512-byte sectors per bit: %u\n", ip.journal_watermark);
log_std(" bitmap flush interval: %u [ms]\n", ip.journal_commit_time);
} if (cad.flags & CRYPT_ACTIVATE_NO_JOURNAL) {
}
if (cad.flags & CRYPT_ACTIVATE_INLINE_MODE) {
log_std(" inline mode\n");
}
if (cad.flags & CRYPT_ACTIVATE_NO_JOURNAL) {
log_std(" journal: not active\n");
} else {
log_std(" journal size: %" PRIu64 " [bytes]\n", ip.journal_size);
@@ -753,6 +769,16 @@ int main(int argc, const char **argv)
usage(popt_context, EXIT_FAILURE, _("Bitmap options can be used only in bitmap mode."),
poptGetInvocationName(popt_context));
if (ARG_SET(OPT_INTEGRITY_INLINE_ID) && (ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID) ||
ARG_SET(OPT_JOURNAL_INTEGRITY_ID) || ARG_SET(OPT_JOURNAL_CRYPT_ID) ||
ARG_SET(OPT_JOURNAL_WATERMARK_ID) || ARG_SET(OPT_JOURNAL_COMMIT_TIME_ID)))
usage(popt_context, EXIT_FAILURE, _("Inline mode cannot be combined with journal or bitmap options."),
poptGetInvocationName(popt_context));
if (ARG_SET(OPT_INTEGRITY_INLINE_ID) && ARG_SET(OPT_DATA_DEVICE_ID))
usage(popt_context, EXIT_FAILURE, _("Inline mode cannot be combined with separate data device."),
poptGetInvocationName(popt_context));
if (ARG_SET(OPT_CANCEL_DEFERRED_ID) && ARG_SET(OPT_DEFERRED_ID))
usage(popt_context, EXIT_FAILURE,
_("Options --cancel-deferred and --deferred cannot be used at the same time."),

View File

@@ -34,6 +34,8 @@ ARG(OPT_INTEGRITY, 'I', POPT_ARG_STRING, N_("Data integrity algorithm"), NULL, C
ARG(OPT_INTEGRITY_BITMAP_MODE, 'B', POPT_ARG_NONE, N_("Use bitmap to track changes and disable journal for integrity device"), NULL, CRYPT_ARG_BOOL, {}, {})
ARG(OPT_INTEGRITY_INLINE, '\0', POPT_ARG_NONE, N_("Use inline integrity mode (HW sector tags)"), NULL, CRYPT_ARG_BOOL, {}, OPT_INTEGRITY_INLINE_ACTIONS)
ARG(OPT_INTEGRITY_KEY_FILE, '\0', POPT_ARG_STRING, N_("Read the integrity key from a file"), NULL, CRYPT_ARG_STRING, {}, {})
ARG(OPT_INTEGRITY_KEY_SIZE, '\0', POPT_ARG_STRING, N_("The size of the data integrity key"), N_("BITS"), CRYPT_ARG_UINT32, {}, {})

View File

@@ -23,6 +23,7 @@
#define OPT_DEFERRED_ACTIONS { CLOSE_ACTION }
#define OPT_DEVICE_SIZE_ACTIONS { RESIZE_ACTION }
#define OPT_DISABLE_BLKID_ACTIONS { FORMAT_ACTION }
#define OPT_INTEGRITY_INLINE_ACTIONS { FORMAT_ACTION }
#define OPT_INTEGRITY_RECALCULATE_ACTIONS { OPEN_ACTION }
#define OPT_INTERLEAVE_SECTORS_ACTIONS { FORMAT_ACTION }
#define OPT_JOURNAL_SIZE_ACTIONS { FORMAT_ACTION }

View File

@@ -62,6 +62,7 @@
#define OPT_INTEGRITY_BITMAP_MODE "integrity-bitmap-mode"
#define OPT_INTEGRITY_KEY_FILE "integrity-key-file"
#define OPT_INTEGRITY_KEY_SIZE "integrity-key-size"
#define OPT_INTEGRITY_INLINE "integrity-inline"
#define OPT_INTEGRITY_LEGACY_PADDING "integrity-legacy-padding"
#define OPT_INTEGRITY_LEGACY_HMAC "integrity-legacy-hmac"
#define OPT_INTEGRITY_LEGACY_RECALC "integrity-legacy-recalculate"