diff --git a/lib/luks2/luks2_internal.h b/lib/luks2/luks2_internal.h index 7c21700e..2daf5aa5 100644 --- a/lib/luks2/luks2_internal.h +++ b/lib/luks2/luks2_internal.h @@ -354,6 +354,10 @@ bool LUKS2_segment_set_size(struct luks2_hdr *hdr, int segment, 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 segment, const char *type); diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c index 42bccef7..8b2e9bea 100644 --- a/lib/luks2/luks2_json_metadata.c +++ b/lib/luks2/luks2_json_metadata.c @@ -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) { 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; struct interval *intervals = NULL; @@ -787,9 +787,25 @@ static int hdr_validate_segments(struct crypt_device *cd, json_object *hdr_jobj) /* opal */ if (!strncmp(json_object_get_string(jobj_type), "hw-opal", 7)) { - 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)) + if (!size) { + log_dbg(cd, "segment type %s does not support dynamic size.", + json_object_get_string(jobj_type)); 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") && hdr_validate_crypt_segment(cd, val, key, jobj_digests, size)) 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)); json_object_object_get_ex(jobj_segment, "opal_key_size", &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); @@ -2617,7 +2636,7 @@ int LUKS2_activate(struct crypt_device *cd, int r; bool dynamic; 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 crypt_dm_active_device dmdi = {}, dmd = { .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))) return r; + if (dynamic && opal_key) { + log_err(cd, _("OPAL device must have static device size.")); + return -EINVAL; + } + if (!dynamic) dmd.size = device_length_bytes / SECTOR_SIZE; @@ -2648,9 +2672,23 @@ int LUKS2_activate(struct crypt_device *cd, if (r < 0) 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))); 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 */); if (r < 0) return r; @@ -2660,10 +2698,6 @@ int LUKS2_activate(struct crypt_device *cd, 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") || LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "hw-opal-crypt")) { r = dm_crypt_target_set(&dmd.segment, 0, @@ -2704,10 +2738,17 @@ int LUKS2_activate(struct crypt_device *cd, if (r) 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.uuid = dmd.uuid; dmd.segment.u.crypt.offset = 0; - dmd.segment.size = dmdi.segment.size; + if (dynamic) + dmd.segment.size = dmdi.segment.size; r = create_or_reload_device_with_integrity(cd, name, CRYPT_LUKS2, &dmd, &dmdi); } else diff --git a/lib/luks2/luks2_segment.c b/lib/luks2/luks2_segment.c index 4e395809..7920b1d7 100644 --- a/lib/luks2/luks2_segment.c +++ b/lib/luks2/luks2_segment.c @@ -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); } +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) { json_object *jobj; @@ -338,6 +349,17 @@ json_object *json_segment_create_crypt(uint64_t offset, 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, 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) return NULL; - json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); - json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size)); + json_add_opal_fields(jobj, length, segment_number, key_size); return jobj; } @@ -361,8 +382,7 @@ json_object *json_segment_create_opal_crypt(uint64_t offset, const uint64_t *len if (!jobj) return NULL; - json_object_object_add(jobj, "opal_segment_number", json_object_new_int(segment_number)); - json_object_object_add(jobj, "opal_key_size", json_object_new_int(key_size)); + json_add_opal_fields(jobj, length, segment_number, key_size); if (json_add_crypt_fields(jobj, iv_offset, cipher, integrity, sector_size, reencryption)) 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); } +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) { return json_segment_set_size(LUKS2_get_segment_jobj(hdr, segment), segment_size_bytes); diff --git a/lib/setup.c b/lib/setup.c index 0370a304..8f303102 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -2278,8 +2278,9 @@ int crypt_format_luks2_opal(struct crypt_device *cd, 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 */ uint64_t alignment_offset_bytes, data_offset_bytes, device_size_bytes, opal_alignment_granularity_blocks, - partition_offset_sectors, range_offset_blocks, range_length_blocks, - required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes; + partition_offset_sectors, range_offset_blocks, range_size_bytes, + required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes, + provided_data_sectors; struct volume_key *user_key = NULL; if (!cd || !params || !opal_params || @@ -2418,15 +2419,14 @@ int crypt_format_luks2_opal(struct crypt_device *cd, } device_size_bytes -= data_offset_bytes; - if (MISALIGNED(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."), - (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) + range_size_bytes = device_size_bytes - (device_size_bytes % (opal_block_bytes * opal_alignment_granularity_blocks)); + if (!range_size_bytes) 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) { r = LUKS2_check_encryption_sector(cd, device_size_bytes, data_offset_bytes, sector_size, 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_length_blocks = device_size_bytes / opal_block_bytes; - 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); if (r < 0) { if (r == -EPERM) @@ -2514,13 +2512,30 @@ int crypt_format_luks2_opal(struct crypt_device *cd, 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) log_err(cd, _("Cannot format integrity for device %s."), data_device_path(cd)); if (r < 0) 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); if (r < 0) goto out;