Properly handle authenticated encryption on OPAL device.

This commit is contained in:
Ondrej Kozina
2023-06-02 10:39:47 +02:00
committed by Luca Boccassi
parent 33bf0c6ae9
commit 4d487d5dcf
4 changed files with 112 additions and 27 deletions

View File

@@ -354,6 +354,10 @@ bool LUKS2_segment_set_size(struct luks2_hdr *hdr,
int segment, int segment,
const uint64_t *segment_size_bytes); const uint64_t *segment_size_bytes);
uint64_t LUKS2_opal_segment_size(struct luks2_hdr *hdr,
int segment,
unsigned blockwise);
int LUKS2_segment_is_type(struct luks2_hdr *hdr, int LUKS2_segment_is_type(struct luks2_hdr *hdr,
int segment, int segment,
const char *type); const char *type);

View File

@@ -704,7 +704,7 @@ static int validate_reencrypt_segments(struct crypt_device *cd, json_object *hdr
static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj)
{ {
json_object *jobj_segments, *jobj_digests, *jobj_offset, *jobj_size, *jobj_type, *jobj_flags, *jobj; json_object *jobj_segments, *jobj_digests, *jobj_offset, *jobj_size, *jobj_type, *jobj_flags, *jobj;
uint64_t offset, size; uint64_t offset, size, opal_segment_size;
int i, r, count, first_backup = -1; int i, r, count, first_backup = -1;
struct interval *intervals = NULL; struct interval *intervals = NULL;
@@ -787,9 +787,25 @@ static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj)
/* opal */ /* opal */
if (!strncmp(json_object_get_string(jobj_type), "hw-opal", 7)) { if (!strncmp(json_object_get_string(jobj_type), "hw-opal", 7)) {
if (!json_contains(cd, val, key, "Segment", "opal_segment_number", json_type_int) || if (!size) {
!json_contains(cd, val, key, "Segment", "opal_key_size", json_type_int)) log_dbg(cd, "segment type %s does not support dynamic size.",
json_object_get_string(jobj_type));
return 1; return 1;
}
if (!json_contains(cd, val, key, "Segment", "opal_segment_number", json_type_int) ||
!json_contains(cd, val, key, "Segment", "opal_key_size", json_type_int) ||
!(jobj_size = json_contains_string(cd, val, key, "Segment", "opal_segment_size")))
return 1;
if (!numbered(cd, "opal_segment_size", json_object_get_string(jobj_size)))
return 1;
if (!json_str_to_uint64(jobj_size, &opal_segment_size) || !opal_segment_size) {
log_dbg(cd, "Illegal opal segment size value.");
return 1;
}
if (size > opal_segment_size) {
log_dbg(cd, "segment size overflows opal locking range size.");
return 1;
}
if (!strcmp(json_object_get_string(jobj_type), "hw-opal-crypt") && if (!strcmp(json_object_get_string(jobj_type), "hw-opal-crypt") &&
hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) hdr_validate_crypt_segment(cd, val, key, jobj_digests, size))
return 1; return 1;
@@ -2100,6 +2116,9 @@ static void hdr_dump_segments(struct crypt_device *cd, json_object *hdr_jobj)
log_std(cd, "\tsegment number: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); log_std(cd, "\tsegment number: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1));
json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj1); json_object_object_get_ex(jobj_segment, "opal_key_size", &jobj1);
log_std(cd, "\topal key size: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1)); log_std(cd, "\topal key size: %" PRIu32 "\n", crypt_jobj_get_uint32(jobj1));
json_object_object_get_ex(jobj_segment, "opal_segment_size", &jobj1);
json_str_to_uint64(jobj1, &value);
log_std(cd, "\topal length: %" PRIu64 " [bytes]\n", value);
} }
json_object_object_get_ex(jobj_segment, "offset", &jobj1); json_object_object_get_ex(jobj_segment, "offset", &jobj1);
@@ -2617,7 +2636,7 @@ int LUKS2_activate(struct crypt_device *cd,
int r; int r;
bool dynamic; bool dynamic;
uint32_t opal_segment_number; uint32_t opal_segment_number;
uint64_t range_offset_sectors, device_length_bytes; uint64_t range_offset_sectors, range_length_sectors, device_length_bytes;
struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2); struct luks2_hdr *hdr = crypt_get_hdr(cd, CRYPT_LUKS2);
struct crypt_dm_active_device dmdi = {}, dmd = { struct crypt_dm_active_device dmdi = {}, dmd = {
.uuid = crypt_get_uuid(cd) .uuid = crypt_get_uuid(cd)
@@ -2636,6 +2655,11 @@ int LUKS2_activate(struct crypt_device *cd,
if ((r = LUKS2_get_data_size(hdr, &device_length_bytes, &dynamic))) if ((r = LUKS2_get_data_size(hdr, &device_length_bytes, &dynamic)))
return r; return r;
if (dynamic && opal_key) {
log_err(cd, _("OPAL device must have static device size."));
return -EINVAL;
}
if (!dynamic) if (!dynamic)
dmd.size = device_length_bytes / SECTOR_SIZE; dmd.size = device_length_bytes / SECTOR_SIZE;
@@ -2648,9 +2672,23 @@ int LUKS2_activate(struct crypt_device *cd,
if (r < 0) if (r < 0)
return -EINVAL; return -EINVAL;
range_length_sectors = LUKS2_opal_segment_size(hdr, CRYPT_DEFAULT_SEGMENT, 1);
if (crypt_get_integrity_tag_size(cd)) {
if (dmd.size >= range_length_sectors) {
log_err(cd, _("Encrypted OPAL device with integrity must be smaller than locking range."));
return -EINVAL;
}
} else {
if (range_length_sectors != dmd.size) {
log_err(cd, _("OPAL device must have same size as locking range."));
return -EINVAL;
}
}
range_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd))); range_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd)));
r = opal_range_check_attributes(cd, crypt_data_device(cd), opal_segment_number, r = opal_range_check_attributes(cd, crypt_data_device(cd), opal_segment_number,
opal_key, &range_offset_sectors, &dmd.size, opal_key, &range_offset_sectors, &range_length_sectors,
NULL /* read locked */, NULL /* write locked */); NULL /* read locked */, NULL /* write locked */);
if (r < 0) if (r < 0)
return r; return r;
@@ -2660,10 +2698,6 @@ int LUKS2_activate(struct crypt_device *cd,
return r; return r;
} }
/* FIXME: temporary workaround for dm-integrity */
if (crypt_get_integrity_tag_size(cd))
dmd.size = 0;
if (LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "crypt") || if (LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "crypt") ||
LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "hw-opal-crypt")) { LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "hw-opal-crypt")) {
r = dm_crypt_target_set(&dmd.segment, 0, r = dm_crypt_target_set(&dmd.segment, 0,
@@ -2704,9 +2738,16 @@ int LUKS2_activate(struct crypt_device *cd,
if (r) if (r)
goto out; goto out;
if (!dynamic && dmdi.size != dmd.size) {
log_err(cd, _("Underlying dm-integrity device with unexpected provided data sectors."));
r = -EINVAL;
goto out;
}
dmdi.flags |= CRYPT_ACTIVATE_PRIVATE; dmdi.flags |= CRYPT_ACTIVATE_PRIVATE;
dmdi.uuid = dmd.uuid; dmdi.uuid = dmd.uuid;
dmd.segment.u.crypt.offset = 0; dmd.segment.u.crypt.offset = 0;
if (dynamic)
dmd.segment.size = dmdi.segment.size; dmd.segment.size = dmdi.segment.size;
r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi); r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi);

View File

@@ -91,6 +91,17 @@ uint64_t json_segment_get_size(json_object *jobj_segment, unsigned blockwise)
return blockwise ? crypt_jobj_get_uint64(jobj) >> SECTOR_SHIFT : crypt_jobj_get_uint64(jobj); return blockwise ? crypt_jobj_get_uint64(jobj) >> SECTOR_SHIFT : crypt_jobj_get_uint64(jobj);
} }
static uint64_t json_segment_get_opal_size(json_object *jobj_segment, unsigned blockwise)
{
json_object *jobj;
if (!jobj_segment ||
!json_object_object_get_ex(jobj_segment, "opal_segment_size", &jobj))
return 0;
return blockwise ? crypt_jobj_get_uint64(jobj) >> SECTOR_SHIFT : crypt_jobj_get_uint64(jobj);
}
static bool json_segment_set_size(json_object *jobj_segment, const uint64_t *size_bytes) static bool json_segment_set_size(json_object *jobj_segment, const uint64_t *size_bytes)
{ {
json_object *jobj; json_object *jobj;
@@ -338,6 +349,17 @@ json_object *json_segment_create_crypt(uint64_t offset,
return NULL; return NULL;
} }
static void json_add_opal_fields(json_object *jobj_segment, const uint64_t *length,
uint32_t segment_number, uint32_t key_size)
{
assert(jobj_segment);
assert(length);
json_object_object_add(jobj_segment, "opal_segment_number", json_object_new_int(segment_number));
json_object_object_add(jobj_segment, "opal_key_size", json_object_new_int(key_size));
json_object_object_add(jobj_segment, "opal_segment_size", crypt_jobj_new_uint64(*length));
}
json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length, json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length,
uint32_t segment_number, uint32_t key_size) uint32_t segment_number, uint32_t key_size)
{ {
@@ -345,8 +367,7 @@ json_object *json_segment_create_opal(uint64_t offset, const uint64_t *length,
if (!jobj) if (!jobj)
return NULL; return NULL;
json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); json_add_opal_fields(jobj, length, segment_number, key_size);
json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size));
return jobj; return jobj;
} }
@@ -361,8 +382,7 @@ json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *len
if (!jobj) if (!jobj)
return NULL; return NULL;
json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); json_add_opal_fields(jobj, length, segment_number, key_size);
json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size));
if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption)) if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption))
return jobj; return jobj;
@@ -396,6 +416,11 @@ uint64_t LUKS2_segment_size(struct luks2_hdr *hdr, int segment, unsigned blockwi
return json_segment_get_size(LUKS2_get_segment_jobj(hdr, segment), blockwise); return json_segment_get_size(LUKS2_get_segment_jobj(hdr, segment), blockwise);
} }
uint64_t LUKS2_opal_segment_size(struct luks2_hdr *hdr, int segment, unsigned blockwise)
{
return json_segment_get_opal_size(LUKS2_get_segment_jobj(hdr, segment), blockwise);
}
bool LUKS2_segment_set_size(struct luks2_hdr *hdr, int segment, const uint64_t *segment_size_bytes) bool LUKS2_segment_set_size(struct luks2_hdr *hdr, int segment, const uint64_t *segment_size_bytes)
{ {
return json_segment_set_size(LUKS2_get_segment_jobj(hdr, segment), segment_size_bytes); return json_segment_set_size(LUKS2_get_segment_jobj(hdr, segment), segment_size_bytes);

View File

@@ -2278,8 +2278,9 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
const char *integrity = params ? params->integrity : NULL; const char *integrity = params ? params->integrity : NULL;
uint32_t sector_size, opal_block_bytes, opal_segment_number = 1; /* We'll use the partition number if available later */ uint32_t sector_size, opal_block_bytes, opal_segment_number = 1; /* We'll use the partition number if available later */
uint64_t alignment_offset_bytes, data_offset_bytes, device_size_bytes, opal_alignment_granularity_blocks, uint64_t alignment_offset_bytes, data_offset_bytes, device_size_bytes, opal_alignment_granularity_blocks,
partition_offset_sectors, range_offset_blocks, range_length_blocks, partition_offset_sectors, range_offset_blocks, range_size_bytes,
required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes; required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes,
provided_data_sectors;
struct volume_key *user_key = NULL; struct volume_key *user_key = NULL;
if (!cd || !params || !opal_params || if (!cd || !params || !opal_params ||
@@ -2418,15 +2419,14 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
} }
device_size_bytes -= data_offset_bytes; device_size_bytes -= data_offset_bytes;
if (MISALIGNED(device_size_bytes, opal_block_bytes * opal_alignment_granularity_blocks)) { range_size_bytes = device_size_bytes - (device_size_bytes % (opal_block_bytes * opal_alignment_granularity_blocks));
log_err(cd, _("Compensating device size by %" PRIu64 " sectors to align it with OPAL alignment granularity."), if (!range_size_bytes)
(device_size_bytes % (opal_alignment_granularity_blocks * opal_block_bytes)) / SECTOR_SIZE);
device_size_bytes -= (device_size_bytes % (opal_block_bytes * opal_alignment_granularity_blocks));
}
if (!device_size_bytes)
goto out; goto out;
if (device_size_bytes != range_size_bytes)
log_err(cd, _("Compensating device size by %" PRIu64 " sectors to align it with OPAL alignment granularity."),
(device_size_bytes - range_size_bytes) / SECTOR_SIZE);
if (cipher) { if (cipher) {
r = LUKS2_check_encryption_sector(cd, device_size_bytes, data_offset_bytes, sector_size, r = LUKS2_check_encryption_sector(cd, device_size_bytes, data_offset_bytes, sector_size,
sector_size_autodetect, integrity == NULL, sector_size_autodetect, integrity == NULL,
@@ -2475,10 +2475,8 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
range_offset_blocks = (data_offset_bytes + partition_offset_sectors * SECTOR_SIZE) / opal_block_bytes; range_offset_blocks = (data_offset_bytes + partition_offset_sectors * SECTOR_SIZE) / opal_block_bytes;
range_length_blocks = device_size_bytes / opal_block_bytes;
r = opal_setup_ranges(cd, crypt_data_device(cd), user_key ?: cd->volume_key, r = opal_setup_ranges(cd, crypt_data_device(cd), user_key ?: cd->volume_key,
range_offset_blocks, range_length_blocks, range_offset_blocks, range_size_bytes / opal_block_bytes,
opal_segment_number, opal_params->admin_key, opal_params->admin_key_size); opal_segment_number, opal_params->admin_key, opal_params->admin_key_size);
if (r < 0) { if (r < 0) {
if (r == -EPERM) if (r == -EPERM)
@@ -2514,13 +2512,30 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
goto out; goto out;
} }
r = INTEGRITY_format(cd, params->integrity_params, NULL, NULL, 0); r = INTEGRITY_format(cd, params->integrity_params, NULL, NULL,
/*
* 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);
if (r) if (r)
log_err(cd, _("Cannot format integrity for device %s."), log_err(cd, _("Cannot format integrity for device %s."),
data_device_path(cd)); data_device_path(cd));
if (r < 0) if (r < 0)
goto out; goto out;
r = INTEGRITY_data_sectors(cd, crypt_data_device(cd),
crypt_get_data_offset(cd) * SECTOR_SIZE,
&provided_data_sectors);
if (r < 0)
goto out;
if (!LUKS2_segment_set_size(&cd->u.luks2.hdr, CRYPT_DEFAULT_SEGMENT,
&(uint64_t) {provided_data_sectors * SECTOR_SIZE})) {
r = -EINVAL;
goto out;
}
r = opal_lock(cd, crypt_data_device(cd), opal_segment_number); r = opal_lock(cd, crypt_data_device(cd), opal_segment_number);
if (r < 0) if (r < 0)
goto out; goto out;