/* * LUKS - Linux Unified Key Setup v2 * * Copyright (C) 2015-2019 Red Hat, Inc. All rights reserved. * Copyright (C) 2015-2019 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <assert.h> #include "luks2_internal.h" /* * Helper functions */ json_object *parse_json_len(struct crypt_device *cd, const char *json_area, uint64_t max_length, int *json_len) { json_object *jobj; struct json_tokener *jtok; /* INT32_MAX is internal (json-c) json_tokener_parse_ex() limit */ if (!json_area || max_length > INT32_MAX) return NULL; jtok = json_tokener_new(); if (!jtok) { log_dbg(cd, "ERROR: Failed to init json tokener"); return NULL; } jobj = json_tokener_parse_ex(jtok, json_area, max_length); if (!jobj) log_dbg(cd, "ERROR: Failed to parse json data (%d): %s", json_tokener_get_error(jtok), json_tokener_error_desc(json_tokener_get_error(jtok))); else *json_len = jtok->char_offset; json_tokener_free(jtok); return jobj; } static void log_dbg_checksum(struct crypt_device *cd, const uint8_t *csum, const char *csum_alg, const char *info) { char csum_txt[2*LUKS2_CHECKSUM_L+1]; int i; for (i = 0; i < crypt_hash_size(csum_alg); i++) snprintf(&csum_txt[i*2], 3, "%02hhx", (const char)csum[i]); csum_txt[i*2+1] = '\0'; /* Just to be safe, sprintf should write \0 there. */ log_dbg(cd, "Checksum:%s (%s)", &csum_txt[0], info); } /* * Calculate hash (checksum) of |LUKS2_bin|LUKS2_JSON_area| from in-memory structs. * LUKS2 on-disk header contains uniques salt both for primary and secondary header. * Checksum is always calculated with zeroed checksum field in binary header. */ static int hdr_checksum_calculate(const char *alg, struct luks2_hdr_disk *hdr_disk, const char *json_area, size_t json_len) { struct crypt_hash *hd = NULL; int hash_size, r; hash_size = crypt_hash_size(alg); if (hash_size <= 0 || crypt_hash_init(&hd, alg)) return -EINVAL; /* Binary header, csum zeroed. */ r = crypt_hash_write(hd, (char*)hdr_disk, LUKS2_HDR_BIN_LEN); /* JSON area (including unused space) */ if (!r) r = crypt_hash_write(hd, json_area, json_len); if (!r) r = crypt_hash_final(hd, (char*)hdr_disk->csum, (size_t)hash_size); crypt_hash_destroy(hd); return r; } /* * Compare hash (checksum) of on-disk and in-memory header. */ static int hdr_checksum_check(struct crypt_device *cd, const char *alg, struct luks2_hdr_disk *hdr_disk, const char *json_area, size_t json_len) { struct luks2_hdr_disk hdr_tmp; int hash_size, r; hash_size = crypt_hash_size(alg); if (hash_size <= 0) return -EINVAL; /* Copy header and zero checksum. */ memcpy(&hdr_tmp, hdr_disk, LUKS2_HDR_BIN_LEN); memset(&hdr_tmp.csum, 0, sizeof(hdr_tmp.csum)); r = hdr_checksum_calculate(alg, &hdr_tmp, json_area, json_len); if (r < 0) return r; log_dbg_checksum(cd, hdr_disk->csum, alg, "on-disk"); log_dbg_checksum(cd, hdr_tmp.csum, alg, "in-memory"); if (memcmp(hdr_tmp.csum, hdr_disk->csum, (size_t)hash_size)) return -EINVAL; return 0; } /* * Convert header from on-disk format to in-memory struct */ static void hdr_from_disk(struct luks2_hdr_disk *hdr_disk1, struct luks2_hdr_disk *hdr_disk2, struct luks2_hdr *hdr, int secondary) { hdr->version = be16_to_cpu(hdr_disk1->version); hdr->hdr_size = be64_to_cpu(hdr_disk1->hdr_size); hdr->seqid = be64_to_cpu(hdr_disk1->seqid); memcpy(hdr->label, hdr_disk1->label, LUKS2_LABEL_L); hdr->label[LUKS2_LABEL_L - 1] = '\0'; memcpy(hdr->subsystem, hdr_disk1->subsystem, LUKS2_LABEL_L); hdr->subsystem[LUKS2_LABEL_L - 1] = '\0'; memcpy(hdr->checksum_alg, hdr_disk1->checksum_alg, LUKS2_CHECKSUM_ALG_L); hdr->checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0'; memcpy(hdr->uuid, hdr_disk1->uuid, LUKS2_UUID_L); hdr->uuid[LUKS2_UUID_L - 1] = '\0'; if (secondary) { memcpy(hdr->salt1, hdr_disk2->salt, LUKS2_SALT_L); memcpy(hdr->salt2, hdr_disk1->salt, LUKS2_SALT_L); } else { memcpy(hdr->salt1, hdr_disk1->salt, LUKS2_SALT_L); memcpy(hdr->salt2, hdr_disk2->salt, LUKS2_SALT_L); } } /* * Convert header from in-memory struct to on-disk format */ static void hdr_to_disk(struct luks2_hdr *hdr, struct luks2_hdr_disk *hdr_disk, int secondary, uint64_t offset) { assert(((char*)&(hdr_disk->_padding4096) - (char*)&(hdr_disk->magic)) == 512); memset(hdr_disk, 0, LUKS2_HDR_BIN_LEN); memcpy(&hdr_disk->magic, secondary ? LUKS2_MAGIC_2ND : LUKS2_MAGIC_1ST, LUKS2_MAGIC_L); hdr_disk->version = cpu_to_be16(hdr->version); hdr_disk->hdr_size = cpu_to_be64(hdr->hdr_size); hdr_disk->hdr_offset = cpu_to_be64(offset); hdr_disk->seqid = cpu_to_be64(hdr->seqid); strncpy(hdr_disk->label, hdr->label, LUKS2_LABEL_L); hdr_disk->label[LUKS2_LABEL_L - 1] = '\0'; strncpy(hdr_disk->subsystem, hdr->subsystem, LUKS2_LABEL_L); hdr_disk->subsystem[LUKS2_LABEL_L - 1] = '\0'; strncpy(hdr_disk->checksum_alg, hdr->checksum_alg, LUKS2_CHECKSUM_ALG_L); hdr_disk->checksum_alg[LUKS2_CHECKSUM_ALG_L - 1] = '\0'; strncpy(hdr_disk->uuid, hdr->uuid, LUKS2_UUID_L); hdr_disk->uuid[LUKS2_UUID_L - 1] = '\0'; memcpy(hdr_disk->salt, secondary ? hdr->salt2 : hdr->salt1, LUKS2_SALT_L); } /* * Sanity checks before checksum is validated */ static int hdr_disk_sanity_check_pre(struct crypt_device *cd, struct luks2_hdr_disk *hdr, size_t *hdr_json_size, int secondary, uint64_t offset) { if (memcmp(hdr->magic, secondary ? LUKS2_MAGIC_2ND : LUKS2_MAGIC_1ST, LUKS2_MAGIC_L)) return -EINVAL; if (be16_to_cpu(hdr->version) != 2) { log_dbg(cd, "Unsupported LUKS2 header version %u.", be16_to_cpu(hdr->version)); return -EINVAL; } if (offset != be64_to_cpu(hdr->hdr_offset)) { log_dbg(cd, "LUKS2 offset 0x%04x on device differs to expected offset 0x%04x.", (unsigned)be64_to_cpu(hdr->hdr_offset), (unsigned)offset); return -EINVAL; } if (secondary && (offset != be64_to_cpu(hdr->hdr_size))) { log_dbg(cd, "LUKS2 offset 0x%04x in secondary header doesn't match size 0x%04x.", (unsigned)offset, (unsigned)be64_to_cpu(hdr->hdr_size)); return -EINVAL; } /* FIXME: sanity check checksum alg. */ log_dbg(cd, "LUKS2 header version %u of size %u bytes, checksum %s.", (unsigned)be16_to_cpu(hdr->version), (unsigned)be64_to_cpu(hdr->hdr_size), hdr->checksum_alg); *hdr_json_size = be64_to_cpu(hdr->hdr_size) - LUKS2_HDR_BIN_LEN; return 0; } /* * Read LUKS2 header from disk at specific offset. */ static int hdr_read_disk(struct crypt_device *cd, struct device *device, struct luks2_hdr_disk *hdr_disk, char **json_area, uint64_t offset, int secondary) { size_t hdr_json_size = 0; int devfd = -1, r; log_dbg(cd, "Trying to read %s LUKS2 header at offset 0x%" PRIx64 ".", secondary ? "secondary" : "primary", offset); devfd = device_open_locked(cd, device, O_RDONLY); if (devfd < 0) return devfd == -1 ? -EIO : devfd; /* * Read binary header and run sanity check before reading * JSON area and validating checksum. */ if (read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), hdr_disk, LUKS2_HDR_BIN_LEN, offset) != LUKS2_HDR_BIN_LEN) { close(devfd); return -EIO; } r = hdr_disk_sanity_check_pre(cd, hdr_disk, &hdr_json_size, secondary, offset); if (r < 0) { close(devfd); return r; } /* * Allocate and read JSON area. Always the whole area must be read. */ *json_area = malloc(hdr_json_size); if (!*json_area) { close(devfd); return -ENOMEM; } if (read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), *json_area, hdr_json_size, offset + LUKS2_HDR_BIN_LEN) != (ssize_t)hdr_json_size) { close(devfd); free(*json_area); *json_area = NULL; return -EIO; } close(devfd); /* * Calculate and validate checksum and zero it afterwards. */ if (hdr_checksum_check(cd, hdr_disk->checksum_alg, hdr_disk, *json_area, hdr_json_size)) { log_dbg(cd, "LUKS2 header checksum error (offset %" PRIu64 ").", offset); r = -EINVAL; } memset(hdr_disk->csum, 0, LUKS2_CHECKSUM_L); return r; } /* * Write LUKS2 header to disk at specific offset. */ static int hdr_write_disk(struct crypt_device *cd, struct device *device, struct luks2_hdr *hdr, const char *json_area, int secondary) { struct luks2_hdr_disk hdr_disk; uint64_t offset = secondary ? hdr->hdr_size : 0; size_t hdr_json_len; int devfd = -1, r; log_dbg(cd, "Trying to write LUKS2 header (%zu bytes) at offset %" PRIu64 ".", hdr->hdr_size, offset); /* FIXME: read-only device silent fail? */ devfd = device_open_locked(cd, device, O_RDWR); if (devfd < 0) return devfd == -1 ? -EINVAL : devfd; hdr_json_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN; hdr_to_disk(hdr, &hdr_disk, secondary, offset); /* * Write header without checksum but with proper seqid. */ if (write_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), (char *)&hdr_disk, LUKS2_HDR_BIN_LEN, offset) < (ssize_t)LUKS2_HDR_BIN_LEN) { close(devfd); return -EIO; } /* * Write json area. */ if (write_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), CONST_CAST(char*)json_area, hdr_json_len, LUKS2_HDR_BIN_LEN + offset) < (ssize_t)hdr_json_len) { close(devfd); return -EIO; } /* * Calculate checksum and write header with checksum. */ r = hdr_checksum_calculate(hdr_disk.checksum_alg, &hdr_disk, json_area, hdr_json_len); if (r < 0) { close(devfd); return r; } log_dbg_checksum(cd, hdr_disk.csum, hdr_disk.checksum_alg, "in-memory"); if (write_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), (char *)&hdr_disk, LUKS2_HDR_BIN_LEN, offset) < (ssize_t)LUKS2_HDR_BIN_LEN) r = -EIO; device_sync(cd, device, devfd); close(devfd); return r; } /* * Convert in-memory LUKS2 header and write it to disk. * This will increase sequence id, write both header copies and calculate checksum. */ int LUKS2_disk_hdr_write(struct crypt_device *cd, struct luks2_hdr *hdr, struct device *device) { char *json_area; const char *json_text; size_t json_area_len; int r; if (hdr->version != 2) { log_dbg(cd, "Unsupported LUKS2 header version (%u).", hdr->version); return -EINVAL; } r = device_check_size(cd, crypt_metadata_device(cd), LUKS2_hdr_and_areas_size(hdr->jobj), 1); if (r) return r; /* * Allocate and zero JSON area (of proper header size). */ json_area_len = hdr->hdr_size - LUKS2_HDR_BIN_LEN; json_area = malloc(json_area_len); if (!json_area) return -ENOMEM; memset(json_area, 0, json_area_len); /* * Generate text space-efficient JSON representation to json area. */ json_text = json_object_to_json_string_ext(hdr->jobj, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); if (!json_text || !*json_text) { log_dbg(cd, "Cannot parse JSON object to text representation."); free(json_area); return -ENOMEM; } if (strlen(json_text) > (json_area_len - 1)) { log_dbg(cd, "JSON is too large (%zu > %zu).", strlen(json_text), json_area_len); free(json_area); return -EINVAL; } strncpy(json_area, json_text, json_area_len); /* Increase sequence id before writing it to disk. */ hdr->seqid++; r = device_write_lock(cd, device); if (r) { log_err(cd, _("Failed to acquire write device lock.")); free(json_area); return r; } /* Write primary and secondary header */ r = hdr_write_disk(cd, device, hdr, json_area, 0); if (!r) r = hdr_write_disk(cd, device, hdr, json_area, 1); if (r) log_dbg(cd, "LUKS2 header write failed (%d).", r); device_write_unlock(cd, device); /* FIXME: try recovery here? */ free(json_area); return r; } static int validate_json_area(struct crypt_device *cd, const char *json_area, uint64_t json_len, uint64_t max_length) { char c; /* Enforce there are no needless opening bytes */ if (*json_area != '{') { log_dbg(cd, "ERROR: Opening character must be left curly bracket: '{'."); return -EINVAL; } if (json_len >= max_length) { log_dbg(cd, "ERROR: Missing trailing null byte beyond parsed json data string."); return -EINVAL; } /* * TODO: * validate there are legal json format characters between * 'json_area' and 'json_area + json_len' */ do { c = *(json_area + json_len); if (c != '\0') { log_dbg(cd, "ERROR: Forbidden ascii code 0x%02hhx found beyond json data string at offset %" PRIu64, c, json_len); return -EINVAL; } } while (++json_len < max_length); return 0; } static int validate_luks2_json_object(struct crypt_device *cd, json_object *jobj_hdr, uint64_t length) { int r; /* we require top level object to be of json_type_object */ r = !json_object_is_type(jobj_hdr, json_type_object); if (r) { log_dbg(cd, "ERROR: Resulting object is not a json object type"); return r; } r = LUKS2_hdr_validate(cd, jobj_hdr, length); if (r) { log_dbg(cd, "Repairing JSON metadata."); /* try to correct known glitches */ LUKS2_hdr_repair(cd, jobj_hdr); /* run validation again */ r = LUKS2_hdr_validate(cd, jobj_hdr, length); } if (r) log_dbg(cd, "ERROR: LUKS2 validation failed"); return r; } static json_object *parse_and_validate_json(struct crypt_device *cd, const char *json_area, uint64_t max_length) { int json_len, r; json_object *jobj = parse_json_len(cd, json_area, max_length, &json_len); if (!jobj) return NULL; /* successful parse_json_len must not return offset <= 0 */ assert(json_len > 0); r = validate_json_area(cd, json_area, json_len, max_length); if (!r) r = validate_luks2_json_object(cd, jobj, max_length); if (r) { json_object_put(jobj); jobj = NULL; } return jobj; } static int detect_device_signatures(struct crypt_device *cd, const char *path) { blk_probe_status prb_state; int r; struct blkid_handle *h; if (!blk_supported()) { log_dbg(cd, "Blkid probing of device signatures disabled."); return 0; } if ((r = blk_init_by_path(&h, path))) { log_dbg(cd, "Failed to initialize blkid_handle by path."); return -EINVAL; } /* We don't care about details. Be fast. */ blk_set_chains_for_fast_detection(h); /* Filter out crypto_LUKS. we don't care now */ blk_superblocks_filter_luks(h); prb_state = blk_safeprobe(h); switch (prb_state) { case PRB_AMBIGUOUS: log_dbg(cd, "Blkid probe couldn't decide device type unambiguously."); /* fall through */ case PRB_FAIL: log_dbg(cd, "Blkid probe failed."); r = -EINVAL; break; case PRB_OK: /* crypto_LUKS type is filtered out */ r = -EINVAL; if (blk_is_partition(h)) log_dbg(cd, "Blkid probe detected partition type '%s'", blk_get_partition_type(h)); else if (blk_is_superblock(h)) log_dbg(cd, "blkid probe detected superblock type '%s'", blk_get_superblock_type(h)); break; case PRB_EMPTY: log_dbg(cd, "Blkid probe detected no foreign device signature."); } blk_free(h); return r; } /* * Read and convert on-disk LUKS2 header to in-memory representation.. * Try to do recovery if on-disk state is not consistent. */ int LUKS2_disk_hdr_read(struct crypt_device *cd, struct luks2_hdr *hdr, struct device *device, int do_recovery, int do_blkprobe) { enum { HDR_OK, HDR_OBSOLETE, HDR_FAIL, HDR_FAIL_IO } state_hdr1, state_hdr2; struct luks2_hdr_disk hdr_disk1, hdr_disk2; char *json_area1 = NULL, *json_area2 = NULL; json_object *jobj_hdr1 = NULL, *jobj_hdr2 = NULL; unsigned int i; int r; uint64_t hdr_size; uint64_t hdr2_offsets[] = LUKS2_HDR2_OFFSETS; /* Skip auto-recovery if locks are disabled and we're not doing LUKS2 explicit repair */ if (do_recovery && do_blkprobe && !crypt_metadata_locking_enabled()) { do_recovery = 0; log_dbg(cd, "Disabling header auto-recovery due to locking being disabled."); } /* * Read primary LUKS2 header (offset 0). */ state_hdr1 = HDR_FAIL; r = hdr_read_disk(cd, device, &hdr_disk1, &json_area1, 0, 0); if (r == 0) { jobj_hdr1 = parse_and_validate_json(cd, json_area1, be64_to_cpu(hdr_disk1.hdr_size) - LUKS2_HDR_BIN_LEN); state_hdr1 = jobj_hdr1 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr1 = HDR_FAIL_IO; /* * Read secondary LUKS2 header (follows primary). */ state_hdr2 = HDR_FAIL; if (state_hdr1 != HDR_FAIL && state_hdr1 != HDR_FAIL_IO) { r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, be64_to_cpu(hdr_disk1.hdr_size), 1); if (r == 0) { jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr2 = HDR_FAIL_IO; } else { /* * No header size, check all known offsets. */ for (r = -EINVAL,i = 0; r < 0 && i < ARRAY_SIZE(hdr2_offsets); i++) r = hdr_read_disk(cd, device, &hdr_disk2, &json_area2, hdr2_offsets[i], 1); if (r == 0) { jobj_hdr2 = parse_and_validate_json(cd, json_area2, be64_to_cpu(hdr_disk2.hdr_size) - LUKS2_HDR_BIN_LEN); state_hdr2 = jobj_hdr2 ? HDR_OK : HDR_OBSOLETE; } else if (r == -EIO) state_hdr2 = HDR_FAIL_IO; } /* * Check sequence id if both headers are read correctly. */ if (state_hdr1 == HDR_OK && state_hdr2 == HDR_OK) { if (be64_to_cpu(hdr_disk1.seqid) > be64_to_cpu(hdr_disk2.seqid)) state_hdr2 = HDR_OBSOLETE; else if (be64_to_cpu(hdr_disk1.seqid) < be64_to_cpu(hdr_disk2.seqid)) state_hdr1 = HDR_OBSOLETE; } /* check header with keyslots to fit the device */ if (state_hdr1 == HDR_OK) hdr_size = LUKS2_hdr_and_areas_size(jobj_hdr1); else if (state_hdr2 == HDR_OK) hdr_size = LUKS2_hdr_and_areas_size(jobj_hdr2); else { r = (state_hdr1 == HDR_FAIL_IO && state_hdr2 == HDR_FAIL_IO) ? -EIO : -EINVAL; goto err; } r = device_check_size(cd, device, hdr_size, 0); if (r) goto err; /* * Try to rewrite (recover) bad header. Always regenerate salt for bad header. */ if (state_hdr1 == HDR_OK && state_hdr2 != HDR_OK) { log_dbg(cd, "Secondary LUKS2 header requires recovery."); if (do_blkprobe && (r = detect_device_signatures(cd, device_path(device)))) { log_err(cd, _("Device contains ambiguous signatures, cannot auto-recover LUKS2.\n" "Please run \"cryptsetup repair\" for recovery.")); goto err; } if (do_recovery) { memcpy(&hdr_disk2, &hdr_disk1, LUKS2_HDR_BIN_LEN); r = crypt_random_get(cd, (char*)hdr_disk2.salt, sizeof(hdr_disk2.salt), CRYPT_RND_SALT); if (r) log_dbg(cd, "Cannot generate master salt."); else { hdr_from_disk(&hdr_disk1, &hdr_disk2, hdr, 0); r = hdr_write_disk(cd, device, hdr, json_area1, 1); } if (r) log_dbg(cd, "Secondary LUKS2 header recovery failed."); } } else if (state_hdr1 != HDR_OK && state_hdr2 == HDR_OK) { log_dbg(cd, "Primary LUKS2 header requires recovery."); if (do_blkprobe && (r = detect_device_signatures(cd, device_path(device)))) { log_err(cd, _("Device contains ambiguous signatures, cannot auto-recover LUKS2.\n" "Please run \"cryptsetup repair\" for recovery.")); goto err; } if (do_recovery) { memcpy(&hdr_disk1, &hdr_disk2, LUKS2_HDR_BIN_LEN); r = crypt_random_get(cd, (char*)hdr_disk1.salt, sizeof(hdr_disk1.salt), CRYPT_RND_SALT); if (r) log_dbg(cd, "Cannot generate master salt."); else { hdr_from_disk(&hdr_disk2, &hdr_disk1, hdr, 1); r = hdr_write_disk(cd, device, hdr, json_area2, 0); } if (r) log_dbg(cd, "Primary LUKS2 header recovery failed."); } } free(json_area1); json_area1 = NULL; free(json_area2); json_area2 = NULL; /* wrong lock for write mode during recovery attempt */ if (r == -EAGAIN) goto err; /* * Even if status is failed, the second header includes salt. */ if (state_hdr1 == HDR_OK) { hdr_from_disk(&hdr_disk1, &hdr_disk2, hdr, 0); hdr->jobj = jobj_hdr1; json_object_put(jobj_hdr2); } else if (state_hdr2 == HDR_OK) { hdr_from_disk(&hdr_disk2, &hdr_disk1, hdr, 1); hdr->jobj = jobj_hdr2; json_object_put(jobj_hdr1); } /* * FIXME: should this fail? At least one header was read correctly. * r = (state_hdr1 == HDR_FAIL_IO || state_hdr2 == HDR_FAIL_IO) ? -EIO : -EINVAL; */ return 0; err: log_dbg(cd, "LUKS2 header read failed (%d).", r); free(json_area1); free(json_area2); json_object_put(jobj_hdr1); json_object_put(jobj_hdr2); hdr->jobj = NULL; return r; } int LUKS2_hdr_version_unlocked(struct crypt_device *cd, const char *backup_file) { struct { char magic[LUKS2_MAGIC_L]; uint16_t version; } __attribute__ ((packed)) hdr; struct device *device = NULL; int r = 0, devfd = -1, flags; if (!backup_file) device = crypt_metadata_device(cd); else if (device_alloc(cd, &device, backup_file) < 0) return 0; if (!device) return 0; flags = O_RDONLY; if (device_direct_io(device)) flags |= O_DIRECT; devfd = open(device_path(device), flags); if (devfd < 0) goto err; if ((read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), &hdr, sizeof(hdr), 0) == sizeof(hdr)) && !memcmp(hdr.magic, LUKS2_MAGIC_1ST, LUKS2_MAGIC_L)) r = (int)be16_to_cpu(hdr.version); err: if (devfd != -1) close(devfd); if (backup_file) device_free(cd, device); return r; }