Skip to content

Commit cb0f568

Browse files
onikombroz
authored andcommitted
Optimize LUKS2 metadata writes.
LUKS2 supports several jsom area length configurations. With the largest size supported in megabytes we do not want to write full metadata area unconditionaly (current code) with every metadata update. This might generate noticeble overhead with LUKS2 reencryption. With this patch we write only the real used json area length plus necessary padding to overwrite remaining previous metadata stored on the disk. During LUKS2 format and LUKS2 autorecovery we always overwrite whole json metadata area no matter the used size.
1 parent 000f03a commit cb0f568

File tree

2 files changed

+64
-28
lines changed

2 files changed

+64
-28
lines changed

lib/luks2/luks2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ struct luks2_hdr {
111111
char uuid[LUKS2_UUID_L];
112112
void *jobj;
113113
void *jobj_rollback;
114+
size_t on_disk_json_end_offset;
114115
};
115116

116117
struct luks2_keyslot_params {

lib/luks2/luks2_disk_metadata.c

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -289,22 +289,25 @@ static int hdr_read_disk(struct crypt_device *cd,
289289
*/
290290
static int hdr_write_disk(struct crypt_device *cd,
291291
struct device *device, struct luks2_hdr *hdr,
292-
const char *json_area, int secondary)
292+
const char *json_area, size_t write_area_len,
293+
int secondary)
293294
{
294295
struct luks2_hdr_disk hdr_disk;
295296
uint64_t offset = secondary ? hdr->hdr_size : 0;
296297
size_t hdr_json_len;
297298
int devfd, r;
298299

300+
hdr_json_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN;
301+
302+
assert(write_area_len <= hdr_json_len);
303+
299304
log_dbg(cd, "Trying to write LUKS2 header (%zu bytes) at offset %" PRIu64 ".",
300-
hdr->hdr_size, offset);
305+
write_area_len, offset);
301306

302307
devfd = device_open_locked(cd, device, O_RDWR);
303308
if (devfd < 0)
304309
return devfd == -1 ? -EINVAL : devfd;
305310

306-
hdr_json_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN;
307-
308311
hdr_to_disk(hdr, &hdr_disk, secondary, offset);
309312

310313
/*
@@ -321,8 +324,8 @@ static int hdr_write_disk(struct crypt_device *cd,
321324
*/
322325
if (write_lseek_blockwise(devfd, device_block_size(cd, device),
323326
device_alignment(device),
324-
CONST_CAST(char*)json_area, hdr_json_len,
325-
LUKS2_HDR_BIN_LEN + offset) < (ssize_t)hdr_json_len) {
327+
CONST_CAST(char*)json_area, write_area_len,
328+
LUKS2_HDR_BIN_LEN + offset) < (ssize_t)write_area_len) {
326329
return -EIO;
327330
}
328331

@@ -401,7 +404,7 @@ int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, struct
401404
{
402405
char *json_area;
403406
const char *json_text;
404-
size_t json_area_len;
407+
size_t json_data_len, json_area_len, json_area_write_len;
405408
int r;
406409

407410
if (hdr->version != 2) {
@@ -413,28 +416,47 @@ int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, struct
413416
if (r)
414417
return r;
415418

416-
/*
417-
* Allocate and zero JSON area (of proper header size).
418-
*/
419-
json_area_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN;
420-
json_area = crypt_zalloc(json_area_len);
421-
if (!json_area)
422-
return -ENOMEM;
423-
424419
/*
425420
* Generate text space-efficient JSON representation to json area.
426421
*/
427422
json_text = crypt_jobj_to_string_on_disk(hdr->jobj);
428423
if (!json_text || !*json_text) {
429424
log_dbg(cd, "Cannot parse JSON object to text representation.");
430-
free(json_area);
431425
return -ENOMEM;
432426
}
433-
if (strlen(json_text) > (json_area_len - 1)) {
434-
log_dbg(cd, "JSON is too large (%zu > %zu).", strlen(json_text), json_area_len);
435-
free(json_area);
427+
428+
json_area_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN;
429+
json_area_write_len = json_data_len = strlen(json_text);
430+
431+
if (json_data_len > (json_area_len - 1)) {
432+
log_dbg(cd, "JSON is too large (%zu > %zu).", json_data_len, json_area_len);
436433
return -EINVAL;
437434
}
435+
436+
/*
437+
* Allocate and zero JSON area (of proper header size).
438+
*/
439+
json_area = crypt_zalloc(json_area_len);
440+
if (!json_area)
441+
return -ENOMEM;
442+
443+
/*
444+
* If the metadata in 'json_area' buffer is smaller than last on-disk
445+
* metadata we also have to erase the remaining bytes between the tail
446+
* of current metadata and the on-disk metadata end pointer. Set write
447+
* area length large enough to overwrite it.
448+
*
449+
* If seqid_check is turned off (during LUKS2 format) write entire
450+
* LUKS2 metadata size instead.
451+
*
452+
* Turn off the optimization also during metadata upconversion
453+
* (hdr->on_disk_json_end_offset == 0).
454+
*/
455+
if (seqid_check && (json_data_len < hdr->on_disk_json_end_offset))
456+
json_area_write_len = hdr->on_disk_json_end_offset;
457+
else if (!seqid_check || !hdr->on_disk_json_end_offset)
458+
json_area_write_len = json_area_len;
459+
438460
strncpy(json_area, json_text, json_area_len);
439461

440462
if (seqid_check)
@@ -450,13 +472,16 @@ int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, struct
450472
hdr->seqid++;
451473

452474
/* Write primary and secondary header */
453-
r = hdr_write_disk(cd, device, hdr, json_area, 0);
475+
r = hdr_write_disk(cd, device, hdr, json_area, json_area_write_len, 0);
454476
if (!r)
455-
r = hdr_write_disk(cd, device, hdr, json_area, 1);
477+
r = hdr_write_disk(cd, device, hdr, json_area, json_area_write_len, 1);
456478

457479
if (r)
458480
log_dbg(cd, "LUKS2 header write failed (%d).", r);
459481

482+
/* store new json end pointer or reset it on error */
483+
hdr->on_disk_json_end_offset = r ? 0 : json_data_len;
484+
460485
device_write_unlock(cd, device);
461486

462487
free(json_area);
@@ -524,12 +549,15 @@ static int validate_luks2_json_object(struct crypt_device *cd, json_object *jobj
524549
}
525550

526551
static json_object *parse_and_validate_json(struct crypt_device *cd,
527-
const char *json_area, uint64_t hdr_size)
552+
const char *json_area, uint64_t hdr_size,
553+
uint64_t *json_area_end)
528554
{
529555
int json_len, r;
530556
json_object *jobj;
531557
uint64_t max_length;
532558

559+
assert(json_area_end);
560+
533561
if (hdr_size <= LUKS2_HDR_BIN_LEN || hdr_size > LUKS2_HDR_OFFSET_MAX) {
534562
log_dbg(cd, "LUKS2 header JSON has bogus size 0x%04" PRIx64 ".", hdr_size);
535563
return NULL;
@@ -553,6 +581,8 @@ static json_object *parse_and_validate_json(struct crypt_device *cd,
553581
jobj = NULL;
554582
}
555583

584+
*json_area_end = json_len;
585+
556586
return jobj;
557587
}
558588

@@ -616,7 +646,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
616646
json_object *jobj_hdr1 = NULL, *jobj_hdr2 = NULL;
617647
unsigned int i;
618648
int r;
619-
uint64_t hdr_size;
649+
uint64_t hdr_size, json_area_end1 = 0, json_area_end2 = 0;
620650
uint64_t hdr2_offsets[] = LUKS2_HDR2_OFFSETS;
621651

622652
/* Skip auto-recovery if locks are disabled and we're not doing LUKS2 explicit repair */
@@ -631,7 +661,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
631661
state_hdr1 = HDR_FAIL;
632662
r = hdr_read_disk(cd, device, &hdr_disk1, &json_area1, 0, 0);
633663
if (r == 0) {
634-
jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size));
664+
jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size), &json_area_end1);
635665
state_hdr1 = jobj_hdr1 ? HDR_OK : HDR_OBSOLETE;
636666
} else if (r == -EIO)
637667
state_hdr1 = HDR_FAIL_IO;
@@ -643,7 +673,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
643673
if (state_hdr1 != HDR_FAIL && state_hdr1 != HDR_FAIL_IO) {
644674
r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, be64_to_cpu(hdr_disk1.hdr_size), 1);
645675
if (r == 0) {
646-
jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size));
676+
jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size), &json_area_end2);
647677
state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE;
648678
} else if (r == -EIO)
649679
state_hdr2 = HDR_FAIL_IO;
@@ -656,7 +686,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
656686
r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, hdr2_offsets[i], 1);
657687

658688
if (r == 0) {
659-
jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size));
689+
jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size), &json_area_end2);
660690
state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE;
661691
} else if (r == -EIO)
662692
state_hdr2 = HDR_FAIL_IO;
@@ -705,7 +735,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
705735
log_dbg(cd, "Cannot generate header salt.");
706736
else {
707737
hdr_from_disk(&hdr_disk1, &hdr_disk2, hdr, 0);
708-
r = hdr_write_disk(cd, device, hdr, json_area1, 1);
738+
r = hdr_write_disk(cd, device, hdr, json_area1, hdr->hdr_size - LUKS2_HDR_BIN_LEN, 1);
709739
}
710740
if (r)
711741
log_dbg(cd, "Secondary LUKS2 header recovery failed.");
@@ -726,7 +756,7 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
726756
log_dbg(cd, "Cannot generate header salt.");
727757
else {
728758
hdr_from_disk(&hdr_disk2, &hdr_disk1, hdr, 1);
729-
r = hdr_write_disk(cd, device, hdr, json_area2, 0);
759+
r = hdr_write_disk(cd, device, hdr, json_area2, hdr->hdr_size - LUKS2_HDR_BIN_LEN, 0);
730760
}
731761
if (r)
732762
log_dbg(cd, "Primary LUKS2 header recovery failed.");
@@ -755,6 +785,11 @@ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr,
755785
json_object_put(jobj_hdr1);
756786
}
757787

788+
if (json_area_end1 > json_area_end2)
789+
hdr->on_disk_json_end_offset = json_area_end1;
790+
else
791+
hdr->on_disk_json_end_offset = json_area_end2;
792+
758793
/*
759794
* FIXME: should this fail? At least one header was read correctly.
760795
* r = (state_hdr1 == HDR_FAIL_IO || state_hdr2 == HDR_FAIL_IO) ? -EIO : -EINVAL;

0 commit comments

Comments
 (0)