Skip to content

Commit 68d4749

Browse files
Maxim Suhanovmbroz
authored andcommitted
bitlk: implement validation of FVE metadata
This commit implements FVE metadata block validation based on: * CRC-32 (to detect random corruption); * AES-CCM-encrypted SHA-256 (to detect malicious manipulations). The hash-based validation requires us to decrypt the VMK first, so it's only performed when obtaining the volume key. This allows us to detect corrupted/altered FVE metadata blocks and pick the valid one (before this commit: the first FVE metadata block is always selected). Fixes: #953 tests: add BitLocker image with corrupted headers The image contains 2 manually corrupted metadata blocks (out of 3), the library should use the third one to correctly load the volume. Signed-off-by: Maxim Suhanov <dfirblog@gmail.com>
1 parent 9cfdd6b commit 68d4749

3 files changed

Lines changed: 205 additions & 14 deletions

File tree

lib/bitlk/bitlk.c

Lines changed: 194 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ struct bitlk_superblock {
111111
struct bitlk_fve_metadata {
112112
/* FVE metadata block header */
113113
uint8_t signature[8];
114+
/* size of this block (in 16-byte units) */
114115
uint16_t fve_size;
115116
uint16_t fve_version;
116117
uint16_t curr_state;
@@ -132,6 +133,32 @@ struct bitlk_fve_metadata {
132133
uint64_t creation_time;
133134
} __attribute__ ((packed));
134135

136+
struct bitlk_validation_hash {
137+
uint16_t size;
138+
uint16_t role;
139+
uint16_t type;
140+
uint16_t flags;
141+
/* likely a hash type code, anything other than 0x2005 isn't supported */
142+
uint16_t hash_type;
143+
uint16_t unknown1;
144+
/* SHA-256 */
145+
uint8_t hash[32];
146+
} __attribute__ ((packed));
147+
148+
struct bitlk_fve_metadata_validation {
149+
/* FVE metadata validation block header */
150+
uint16_t validation_size;
151+
uint16_t validation_version;
152+
uint32_t fve_crc32;
153+
/* this is a single nested structure's header defined here for simplicity */
154+
uint16_t nested_struct_size;
155+
uint16_t nested_struct_role;
156+
uint16_t nested_struct_type;
157+
uint16_t nested_struct_flags;
158+
/* datum containing a similar nested structure (encrypted using VMK) with hash (SHA256) */
159+
uint8_t nested_struct_data[BITLK_VALIDATION_VMK_DATA_SIZE];
160+
} __attribute__ ((packed));
161+
135162
struct bitlk_entry_header_block {
136163
uint64_t offset;
137164
uint64_t size;
@@ -361,6 +388,54 @@ static int parse_vmk_entry(struct crypt_device *cd, uint8_t *data, int start, in
361388
return 0;
362389
}
363390

391+
static bool check_fve_metadata(struct bitlk_fve_metadata *fve)
392+
{
393+
if (memcmp(fve->signature, BITLK_SIGNATURE, sizeof(fve->signature)) || le16_to_cpu(fve->fve_version) != 2 ||
394+
(fve->fve_size << 4) > BITLK_FVE_METADATA_SIZE)
395+
return false;
396+
397+
return true;
398+
}
399+
400+
static bool check_fve_metadata_validation(struct bitlk_fve_metadata_validation *validation)
401+
{
402+
/* only check if there is room for CRC-32, the actual size must be larger */
403+
if (le16_to_cpu(validation->validation_size) < 8 || le16_to_cpu(validation->validation_version > 2))
404+
return false;
405+
406+
return true;
407+
}
408+
409+
static bool parse_fve_metadata_validation(struct bitlk_metadata *params, struct bitlk_fve_metadata_validation *validation)
410+
{
411+
/* extra checks for a nested structure (MAC) and BITLK FVE metadata */
412+
413+
if (le16_to_cpu(validation->validation_size) < sizeof(struct bitlk_fve_metadata_validation))
414+
return false;
415+
416+
if (le16_to_cpu(validation->nested_struct_size != BITLK_VALIDATION_VMK_HEADER_SIZE + BITLK_VALIDATION_VMK_DATA_SIZE) ||
417+
le16_to_cpu(validation->nested_struct_role) != 0 ||
418+
le16_to_cpu(validation->nested_struct_type) != 5)
419+
return false;
420+
421+
/* nonce */
422+
memcpy(params->validation->nonce,
423+
validation->nested_struct_data,
424+
BITLK_NONCE_SIZE);
425+
426+
/* MAC tag */
427+
memcpy(params->validation->mac_tag,
428+
validation->nested_struct_data + BITLK_NONCE_SIZE,
429+
BITLK_VMK_MAC_TAG_SIZE);
430+
431+
/* AES-CCM encrypted datum with SHA256 hash */
432+
memcpy(params->validation->enc_datum,
433+
validation->nested_struct_data + BITLK_NONCE_SIZE + BITLK_VMK_MAC_TAG_SIZE,
434+
BITLK_VALIDATION_VMK_DATA_SIZE - BITLK_NONCE_SIZE - BITLK_VMK_MAC_TAG_SIZE);
435+
436+
return true;
437+
}
438+
364439
void BITLK_bitlk_fvek_free(struct bitlk_fvek *fvek)
365440
{
366441
if (!fvek)
@@ -391,6 +466,7 @@ void BITLK_bitlk_metadata_free(struct bitlk_metadata *metadata)
391466

392467
free(metadata->guid);
393468
free(metadata->description);
469+
free(metadata->validation);
394470
BITLK_bitlk_vmk_free(metadata->vmks);
395471
BITLK_bitlk_fvek_free(metadata->fvek);
396472
}
@@ -402,20 +478,25 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
402478
struct bitlk_signature sig = {};
403479
struct bitlk_superblock sb = {};
404480
struct bitlk_fve_metadata fve = {};
481+
struct bitlk_fve_metadata_validation validation = {};
405482
struct bitlk_entry_vmk entry_vmk = {};
406483
uint8_t *fve_entries = NULL;
484+
uint8_t *fve_validated_block = NULL;
407485
size_t fve_entries_size = 0;
408486
uint32_t fve_metadata_size = 0;
487+
uint32_t fve_size_real = 0;
409488
int fve_offset = 0;
410489
char guid_buf[UUID_STR_LEN] = {0};
411490
uint16_t entry_size = 0;
412491
uint16_t entry_type = 0;
413492
int i = 0;
414493
int r = 0;
494+
int valid_fve_metadata_idx = -1;
415495
int start = 0;
416496
size_t key_size = 0;
417497
const char *key = NULL;
418498
char *description = NULL;
499+
struct crypt_hash *hash;
419500

420501
struct bitlk_vmk *vmk = NULL;
421502
struct bitlk_vmk *vmk_p = params->vmks;
@@ -490,15 +571,80 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
490571
for (i = 0; i < 3; i++)
491572
params->metadata_offset[i] = le64_to_cpu(sb.fve_offset[i]);
492573

493-
log_dbg(cd, "Reading BITLK FVE metadata of size %zu on device %s, offset %" PRIu64 ".",
494-
sizeof(fve), device_path(device), params->metadata_offset[0]);
574+
fve_validated_block = malloc(BITLK_FVE_METADATA_SIZE);
575+
if (fve_validated_block == NULL) {
576+
r = -ENOMEM;
577+
goto out;
578+
}
495579

496-
/* read FVE metadata from the first metadata area */
497-
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
498-
device_alignment(device), &fve, sizeof(fve), params->metadata_offset[0]) != sizeof(fve) ||
499-
memcmp(fve.signature, BITLK_SIGNATURE, sizeof(fve.signature)) ||
500-
le16_to_cpu(fve.fve_version) != 2) {
501-
log_err(cd, _("Failed to read BITLK FVE metadata from %s."), device_path(device));
580+
for (i = 0; i < 3; i++) {
581+
/* iterate over FVE metadata copies and pick the valid one */
582+
log_dbg(cd, "Reading BITLK FVE metadata copy #%d of size %zu on device %s, offset %" PRIu64 ".",
583+
i, sizeof(fve), device_path(device), params->metadata_offset[i]);
584+
585+
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
586+
device_alignment(device), &fve, sizeof(fve), params->metadata_offset[i]) != sizeof(fve) ||
587+
!check_fve_metadata(&fve) ||
588+
(fve_size_real = le16_to_cpu(fve.fve_size) << 4, read_lseek_blockwise(devfd, device_block_size(cd, device),
589+
device_alignment(device), &validation, sizeof(validation), params->metadata_offset[i] + fve_size_real) != sizeof(validation)) ||
590+
!check_fve_metadata_validation(&validation) ||
591+
/* double-fetch is here, but we aren't validating MAC */
592+
read_lseek_blockwise(devfd, device_block_size(cd, device), device_alignment(device), fve_validated_block, fve_size_real,
593+
params->metadata_offset[i]) != fve_size_real ||
594+
(crypt_crc32(~0, fve_validated_block, fve_size_real) ^ ~0) != le32_to_cpu(validation.fve_crc32)) {
595+
/* found an invalid FVE metadata copy, log and skip */
596+
log_dbg(cd, _("Failed to read or validate BITLK FVE metadata copy #%d from %s."), i, device_path(device));
597+
} else {
598+
/* found a valid FVE metadata copy, use it */
599+
valid_fve_metadata_idx = i;
600+
break;
601+
}
602+
}
603+
604+
if (valid_fve_metadata_idx < 0) {
605+
/* all FVE metadata copies are invalid, fail */
606+
log_err(cd, _("Failed to read and validate BITLK FVE metadata from %s."), device_path(device));
607+
r = -EINVAL;
608+
goto out;
609+
}
610+
611+
/* check that a valid FVE metadata block is in its expected location */
612+
if (params->metadata_offset[valid_fve_metadata_idx] != le64_to_cpu(fve.fve_offset[valid_fve_metadata_idx])) {
613+
log_err(cd, _("Failed to validate the location of BITLK FVE metadata from %s."), device_path(device));
614+
r = -EINVAL;
615+
goto out;
616+
}
617+
618+
/* update offsets from a valid FVE metadata copy */
619+
for (i = 0; i < 3; i++)
620+
params->metadata_offset[i] = le64_to_cpu(fve.fve_offset[i]);
621+
622+
/* check that the FVE metadata hasn't changed between reads, because we are preparing for the MAC check */
623+
if (memcmp(&fve, fve_validated_block, sizeof(fve)) != 0) {
624+
log_err(cd, _("BITLK FVE metadata changed between reads from %s."), device_path(device));
625+
r = -EINVAL;
626+
goto out;
627+
}
628+
629+
crypt_backend_memzero(&params->sha256_fve, 32);
630+
if (crypt_hash_init(&hash, "sha256")) {
631+
log_err(cd, _("Failed to hash BITLK FVE metadata read from %s."), device_path(device));
632+
r = -EINVAL;
633+
goto out;
634+
}
635+
crypt_hash_write(hash, (const char *)fve_validated_block, fve_size_real);
636+
crypt_hash_final(hash, (char *)&params->sha256_fve, 32);
637+
crypt_hash_destroy(hash);
638+
639+
/* do some extended checks against FVE metadata, but not including MAC verification */
640+
params->validation = malloc(sizeof(struct bitlk_validation));
641+
if (!params->validation) {
642+
r = -ENOMEM;
643+
goto out;
644+
}
645+
646+
if (!parse_fve_metadata_validation(params, &validation)) {
647+
log_err(cd, _("Failed to parse BITLK FVE validation metadata from %s."), device_path(device));
502648
r = -EINVAL;
503649
goto out;
504650
}
@@ -583,17 +729,18 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
583729
}
584730
memset(fve_entries, 0, fve_entries_size);
585731

586-
log_dbg(cd, "Reading BITLK FVE metadata entries of size %zu on device %s, offset %" PRIu64 ".",
587-
fve_entries_size, device_path(device), params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN);
732+
log_dbg(cd, "Getting BITLK FVE metadata entries of size %zu on device %s, offset %" PRIu64 ".",
733+
fve_entries_size, device_path(device), params->metadata_offset[valid_fve_metadata_idx] + BITLK_FVE_METADATA_HEADERS_LEN);
588734

589-
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
590-
device_alignment(device), fve_entries, fve_entries_size,
591-
params->metadata_offset[0] + BITLK_FVE_METADATA_HEADERS_LEN) != (ssize_t)fve_entries_size) {
592-
log_err(cd, _("Failed to read BITLK metadata entries from %s."), device_path(device));
735+
if (BITLK_FVE_METADATA_HEADERS_LEN + fve_entries_size > fve_size_real) {
736+
log_err(cd, _("Failed to check BITLK metadata entries previously read from %s."), device_path(device));
593737
r = -EINVAL;
594738
goto out;
595739
}
596740

741+
/* fetch these entries from validated buffer to avoid double-fetch */
742+
memcpy(fve_entries, fve_validated_block + BITLK_FVE_METADATA_HEADERS_LEN, fve_entries_size);
743+
597744
while ((fve_entries_size - start) >= (sizeof(entry_size) + sizeof(entry_type))) {
598745

599746
/* size of this entry */
@@ -716,6 +863,8 @@ int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params)
716863
}
717864
out:
718865
free(fve_entries);
866+
free(fve_validated_block);
867+
719868
return r;
720869
}
721870

@@ -1110,6 +1259,7 @@ int BITLK_get_volume_key(struct crypt_device *cd,
11101259
struct volume_key *open_vmk_key = NULL;
11111260
struct volume_key *vmk_dec_key = NULL;
11121261
struct volume_key *recovery_key = NULL;
1262+
struct bitlk_validation_hash dec_hash = {};
11131263
const struct bitlk_vmk *next_vmk = NULL;
11141264

11151265
next_vmk = params->vmks;
@@ -1172,6 +1322,36 @@ int BITLK_get_volume_key(struct crypt_device *cd,
11721322
}
11731323
crypt_free_volume_key(vmk_dec_key);
11741324

1325+
log_dbg(cd, "Trying to decrypt validation metadata using VMK.");
1326+
r = crypt_bitlk_decrypt_key(crypt_volume_key_get_key(open_vmk_key),
1327+
crypt_volume_key_length(open_vmk_key),
1328+
(const char*)params->validation->enc_datum,
1329+
(char *)&dec_hash,
1330+
BITLK_VALIDATION_VMK_DATA_SIZE - BITLK_NONCE_SIZE - BITLK_VMK_MAC_TAG_SIZE,
1331+
(const char*)params->validation->nonce, BITLK_NONCE_SIZE,
1332+
(const char*)params->validation->mac_tag, BITLK_VMK_MAC_TAG_SIZE);
1333+
if (r < 0) {
1334+
log_dbg(cd, "Failed to decrypt validation metadata using VMK.");
1335+
crypt_free_volume_key(open_vmk_key);
1336+
if (r == -ENOTSUP)
1337+
return r;
1338+
break;
1339+
}
1340+
1341+
/* now, do the MAC validation */
1342+
if (le16_to_cpu(dec_hash.role) != 0 ||le16_to_cpu(dec_hash.type) != 1 ||
1343+
(le16_to_cpu(dec_hash.hash_type) != 0x2005)) {
1344+
log_dbg(cd, "Failed to parse decrypted validation metadata.");
1345+
crypt_free_volume_key(open_vmk_key);
1346+
return -ENOTSUP;
1347+
}
1348+
1349+
if (memcmp(dec_hash.hash, params->sha256_fve, sizeof(dec_hash.hash)) != 0) {
1350+
log_dbg(cd, "Failed MAC validation of BITLK FVE metadata.");
1351+
crypt_free_volume_key(open_vmk_key);
1352+
return -EINVAL;
1353+
}
1354+
11751355
r = decrypt_key(cd, open_fvek_key, params->fvek->vk, open_vmk_key,
11761356
params->fvek->mac_tag, BITLK_VMK_MAC_TAG_SIZE,
11771357
params->fvek->nonce, BITLK_NONCE_SIZE, true);

lib/bitlk/bitlk.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ struct volume_key;
2121
#define BITLK_NONCE_SIZE 12
2222
#define BITLK_SALT_SIZE 16
2323
#define BITLK_VMK_MAC_TAG_SIZE 16
24+
#define BITLK_VALIDATION_VMK_HEADER_SIZE 8
25+
#define BITLK_VALIDATION_VMK_DATA_SIZE 72
2426

2527
#define BITLK_STATE_NORMAL 0x0004
2628

@@ -85,6 +87,13 @@ struct bitlk_fvek {
8587
struct volume_key *vk;
8688
};
8789

90+
struct bitlk_validation {
91+
uint8_t mac_tag[BITLK_VMK_MAC_TAG_SIZE];
92+
uint8_t nonce[BITLK_NONCE_SIZE];
93+
/* technically, this is not "VMK", but some sources call it this way */
94+
uint8_t enc_datum[BITLK_VALIDATION_VMK_DATA_SIZE];
95+
};
96+
8897
struct bitlk_metadata {
8998
uint16_t sector_size;
9099
uint64_t volume_size;
@@ -101,8 +110,10 @@ struct bitlk_metadata {
101110
uint32_t metadata_version;
102111
uint64_t volume_header_offset;
103112
uint64_t volume_header_size;
113+
const char *sha256_fve[32];
104114
struct bitlk_vmk *vmks;
105115
struct bitlk_fvek *fvek;
116+
struct bitlk_validation *validation;
106117
};
107118

108119
int BITLK_read_sb(struct crypt_device *cd, struct bitlk_metadata *params);

tests/bitlk-images.tar.xz

16 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)