Commit 0113ac2d authored by Ondrej Kozina's avatar Ondrej Kozina Committed by Milan Broz
Browse files

Fix CVE-2021-4122 - LUKS2 reencryption crash recovery attack

Fix possible attacks against data confidentiality through LUKS2 online
reencryption extension crash recovery.

An attacker can modify on-disk metadata to simulate decryption in
progress with crashed (unfinished) reencryption step and persistently
decrypt part of the LUKS device.

This attack requires repeated physical access to the LUKS device but
no knowledge of user passphrases.

The decryption step is performed after a valid user activates
the device with a correct passphrase and modified metadata.
There are no visible warnings for the user that such recovery happened
(except using the luksDump command). The attack can also be reversed
afterward (simulating crashed encryption from a plaintext) with
possible modification of revealed plaintext.

The problem was caused by reusing a mechanism designed for actual
reencryption operation without reassessing the security impact for new
encryption and decryption operations. While the reencryption requires
calculating and verifying both key digests, no digest was needed to
initiate decryption recovery if the destination is plaintext (no
encryption key). Also, some metadata (like encryption cipher) is not
protected, and an attacker could change it. Note that LUKS2 protects
visible metadata only when a random change occurs. It does not protect
against intentional modification but such modification must not cause
a violation of data confidentiality.

The fix introduces additional digest protection of reencryption
metadata. The digest is calculated from known keys and critical
reencryption metadata. Now an attacker cannot create correct metadata
digest without knowledge of a passphrase for used keyslots.
For more details, see LUKS2 On-Disk Format Specification version 1.1.0.
parent 5a17d677
......@@ -100,6 +100,7 @@ libcryptsetup_la_SOURCES = \
lib/luks2/luks2_keyslot_luks2.c \
lib/luks2/luks2_keyslot_reenc.c \
lib/luks2/luks2_reencrypt.c \
lib/luks2/luks2_reencrypt_digest.c \
lib/luks2/luks2_segment.c \
lib/luks2/luks2_token_keyring.c \
lib/luks2/luks2_token.c \
......
......@@ -453,4 +453,8 @@ int LUKS2_reencrypt_check_device_size(struct crypt_device *cd,
bool activation,
bool dynamic);
int LUKS2_reencrypt_digest_verify(struct crypt_device *cd,
struct luks2_hdr *hdr,
struct volume_key *vks);
#endif
......@@ -239,9 +239,15 @@ int LUKS2_keyslot_reencrypt_create(struct crypt_device *cd,
int keyslot,
const struct crypt_params_reencrypt *params);
int LUKS2_keyslot_reencrypt_digest_create(struct crypt_device *cd,
struct luks2_hdr *hdr,
struct volume_key *vks);
int LUKS2_keyslot_dump(struct crypt_device *cd,
int keyslot);
int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length);
/* JSON helpers */
uint64_t json_segment_get_offset(json_object *jobj_segment, unsigned blockwise);
const char *json_segment_type(json_object *jobj_segment);
......
......@@ -1389,24 +1389,63 @@ int LUKS2_config_set_flags(struct crypt_device *cd, struct luks2_hdr *hdr, uint3
*/
/* LUKS2 library requirements */
static const struct {
struct requirement_flag {
uint32_t flag;
uint32_t version;
const char *description;
} requirements_flags[] = {
{ CRYPT_REQUIREMENT_OFFLINE_REENCRYPT, "offline-reencrypt" },
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, "online-reencrypt" },
{ 0, NULL }
};
static uint32_t get_requirement_by_name(const char *requirement)
static const struct requirement_flag unknown_requirement_flag = { CRYPT_REQUIREMENT_UNKNOWN, 0, NULL };
static const struct requirement_flag requirements_flags[] = {
{ CRYPT_REQUIREMENT_OFFLINE_REENCRYPT,1, "offline-reencrypt" },
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 2, "online-reencrypt-v2" },
{ CRYPT_REQUIREMENT_ONLINE_REENCRYPT, 1, "online-reencrypt" },
{ 0, 0, NULL }
};
static const struct requirement_flag *get_requirement_by_name(const char *requirement)
{
int i;
for (i = 0; requirements_flags[i].description; i++)
if (!strcmp(requirement, requirements_flags[i].description))
return requirements_flags[i].flag;
return requirements_flags + i;
return CRYPT_REQUIREMENT_UNKNOWN;
return &unknown_requirement_flag;
}
static const struct requirement_flag *stored_requirement_name_by_id(struct crypt_device *cd, struct luks2_hdr *hdr, uint32_t req_id)
{
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
int i, len;
const struct requirement_flag *req;
assert(hdr);
if (!hdr)
return NULL;
if (!json_object_object_get_ex(hdr->jobj, "config", &jobj_config))
return NULL;
if (!json_object_object_get_ex(jobj_config, "requirements", &jobj_requirements))
return NULL;
if (!json_object_object_get_ex(jobj_requirements, "mandatory", &jobj_mandatory))
return NULL;
len = (int) json_object_array_length(jobj_mandatory);
if (len <= 0)
return 0;
for (i = 0; i < len; i++) {
jobj = json_object_array_get_idx(jobj_mandatory, i);
req = get_requirement_by_name(json_object_get_string(jobj));
if (req->flag == req_id)
return req;
}
return NULL;
}
/*
......@@ -1416,7 +1455,7 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
{
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
int i, len;
uint32_t req;
const struct requirement_flag *req;
assert(hdr);
if (!hdr || !reqs)
......@@ -1443,8 +1482,8 @@ int LUKS2_config_get_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
jobj = json_object_array_get_idx(jobj_mandatory, i);
req = get_requirement_by_name(json_object_get_string(jobj));
log_dbg(cd, "%s - %sknown", json_object_get_string(jobj),
reqs_unknown(req) ? "un" : "");
*reqs |= req;
reqs_unknown(req->flag) ? "un" : "");
*reqs |= req->flag;
}
return 0;
......@@ -1454,6 +1493,8 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
{
json_object *jobj_config, *jobj_requirements, *jobj_mandatory, *jobj;
int i, r = -EINVAL;
const struct requirement_flag *req;
uint32_t req_id;
if (!hdr)
return -EINVAL;
......@@ -1463,8 +1504,14 @@ int LUKS2_config_set_requirements(struct crypt_device *cd, struct luks2_hdr *hdr
return -ENOMEM;
for (i = 0; requirements_flags[i].description; i++) {
if (reqs & requirements_flags[i].flag) {
jobj = json_object_new_string(requirements_flags[i].description);
req_id = reqs & requirements_flags[i].flag;
if (req_id) {
/* retain already stored version of requirement flag */
req = stored_requirement_name_by_id(cd, hdr, req_id);
if (req)
jobj = json_object_new_string(req->description);
else
jobj = json_object_new_string(requirements_flags[i].description);
if (!jobj) {
r = -ENOMEM;
goto err;
......
......@@ -288,19 +288,9 @@ crypt_keyslot_info LUKS2_keyslot_info(struct luks2_hdr *hdr, int keyslot)
return CRYPT_SLOT_ACTIVE;
}
int LUKS2_keyslot_area(struct luks2_hdr *hdr,
int keyslot,
uint64_t *offset,
uint64_t *length)
int LUKS2_keyslot_jobj_area(json_object *jobj_keyslot, uint64_t *offset, uint64_t *length)
{
json_object *jobj_keyslot, *jobj_area, *jobj;
if(LUKS2_keyslot_info(hdr, keyslot) == CRYPT_SLOT_INVALID)
return -EINVAL;
jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot);
if (!jobj_keyslot)
return -ENOENT;
json_object *jobj_area, *jobj;
if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
return -EINVAL;
......@@ -316,6 +306,23 @@ int LUKS2_keyslot_area(struct luks2_hdr *hdr,
return 0;
}
int LUKS2_keyslot_area(struct luks2_hdr *hdr,
int keyslot,
uint64_t *offset,
uint64_t *length)
{
json_object *jobj_keyslot;
if (LUKS2_keyslot_info(hdr, keyslot) == CRYPT_SLOT_INVALID)
return -EINVAL;
jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot);
if (!jobj_keyslot)
return -ENOENT;
return LUKS2_keyslot_jobj_area(jobj_keyslot, offset, length);
}
static int _open_and_verify(struct crypt_device *cd,
struct luks2_hdr *hdr,
const keyslot_handler *h,
......
......@@ -176,9 +176,17 @@ static int reenc_keyslot_store(struct crypt_device *cd,
return r < 0 ? r : keyslot;
}
static int reenc_keyslot_wipe(struct crypt_device *cd __attribute__((unused)),
int keyslot __attribute__((unused)))
static int reenc_keyslot_wipe(struct crypt_device *cd,
int keyslot)
{
struct luks2_hdr *hdr;
if (!(hdr = crypt_get_hdr(cd, CRYPT_LUKS2)))
return -EINVAL;
/* remove reencryption verification data */
LUKS2_digest_assign(cd, hdr, keyslot, CRYPT_ANY_DIGEST, 0, 0);
return 0;
}
......
......@@ -95,6 +95,7 @@ struct luks2_reencrypt {
static int reencrypt_keyslot_update(struct crypt_device *cd,
const struct luks2_reencrypt *rh)
{
int r;
json_object *jobj_keyslot, *jobj_area, *jobj_area_type;
struct luks2_hdr *hdr;
......@@ -124,7 +125,11 @@ static int reencrypt_keyslot_update(struct crypt_device *cd,
} else
log_dbg(cd, "No update of reencrypt keyslot needed.");
return 0;
r = LUKS2_keyslot_reencrypt_digest_create(cd, hdr, rh->vks);
if (r < 0)
log_err(cd, "Failed to refresh reencryption verification digest.");
return r;
}
static json_object *reencrypt_segment(struct luks2_hdr *hdr, unsigned new)
......@@ -2484,6 +2489,10 @@ static int reencrypt_init(struct crypt_device *cd,
if (r < 0)
goto out;
r = LUKS2_keyslot_reencrypt_digest_create(cd, hdr, *vks);
if (r < 0)
goto out;
if (name && params->mode != CRYPT_REENCRYPT_ENCRYPT) {
r = reencrypt_verify_and_upload_keys(cd, hdr, LUKS2_reencrypt_digest_old(hdr), LUKS2_reencrypt_digest_new(hdr), *vks);
if (r)
......@@ -2614,20 +2623,28 @@ static int reencrypt_context_update(struct crypt_device *cd,
static int reencrypt_load(struct crypt_device *cd, struct luks2_hdr *hdr,
uint64_t device_size,
const struct crypt_params_reencrypt *params,
struct volume_key *vks,
struct luks2_reencrypt **rh)
{
int r;
struct luks2_reencrypt *tmp = NULL;
crypt_reencrypt_info ri = LUKS2_reencrypt_status(hdr);
if (ri == CRYPT_REENCRYPT_NONE) {
log_err(cd, _("Device not marked for LUKS2 reencryption."));
return -EINVAL;
} else if (ri == CRYPT_REENCRYPT_INVALID)
return -EINVAL;
r = LUKS2_reencrypt_digest_verify(cd, hdr, vks);
if (r < 0)
return r;
if (ri == CRYPT_REENCRYPT_CLEAN)
r = reencrypt_load_clean(cd, hdr, device_size, &tmp, params);
else if (ri == CRYPT_REENCRYPT_CRASH)
r = reencrypt_load_crashed(cd, hdr, device_size, &tmp);
else if (ri == CRYPT_REENCRYPT_NONE) {
log_err(cd, _("Device not marked for LUKS2 reencryption."));
return -EINVAL;
} else
else
r = -EINVAL;
if (r < 0 || !tmp) {
......@@ -2876,7 +2893,7 @@ static int reencrypt_load_by_passphrase(struct crypt_device *cd,
rparams.device_size = required_size;
}
r = reencrypt_load(cd, hdr, device_size, &rparams, &rh);
r = reencrypt_load(cd, hdr, device_size, &rparams, *vks, &rh);
if (r < 0 || !rh)
goto err;
......@@ -3096,13 +3113,6 @@ static reenc_status_t reencrypt_step(struct crypt_device *cd,
{
int r;
/* update reencrypt keyslot protection parameters in memory only */
r = reencrypt_keyslot_update(cd, rh);
if (r < 0) {
log_dbg(cd, "Keyslot update failed.");
return REENC_ERR;
}
/* in memory only */
r = reencrypt_make_segments(cd, hdr, rh, device_size);
if (r)
......@@ -3370,6 +3380,15 @@ int crypt_reencrypt_run(
rs = REENC_OK;
/* update reencrypt keyslot protection parameters in memory only */
if (!quit && (rh->device_size > rh->progress)) {
r = reencrypt_keyslot_update(cd, rh);
if (r < 0) {
log_dbg(cd, "Keyslot update failed.");
return reencrypt_teardown(cd, hdr, rh, REENC_ERR, quit, progress, usrptr);
}
}
while (!quit && (rh->device_size > rh->progress)) {
rs = reencrypt_step(cd, hdr, rh, rh->device_size, rh->online);
if (rs != REENC_OK)
......@@ -3409,7 +3428,7 @@ static int reencrypt_recovery(struct crypt_device *cd,
int r;
struct luks2_reencrypt *rh = NULL;
r = reencrypt_load(cd, hdr, device_size, NULL, &rh);
r = reencrypt_load(cd, hdr, device_size, NULL, vks, &rh);
if (r < 0) {
log_err(cd, _("Failed to load LUKS2 reencryption context."));
return r;
......
/*
* LUKS - Linux Unified Key Setup v2, reencryption digest helpers
*
* Copyright (C) 2022, Red Hat, Inc. All rights reserved.
* Copyright (C) 2022, Ondrej Kozina
* Copyright (C) 2022, 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 "luks2_internal.h"
#include <assert.h>
#define MAX_STR 64
struct jtype {
enum { JNONE = 0, JSTR, JU64, JX64, JU32 } type;
json_object *jobj;
const char *id;
};
static size_t sr(struct jtype *j, uint8_t *ptr)
{
json_object *jobj;
size_t len = 0;
uint64_t u64;
uint32_t u32;
if (!json_object_is_type(j->jobj, json_type_object))
return 0;
if (!json_object_object_get_ex(j->jobj, j->id, &jobj))
return 0;
switch(j->type) {
case JSTR: /* JSON string */
if (!json_object_is_type(jobj, json_type_string))
return 0;
len = strlen(json_object_get_string(jobj));
if (len > MAX_STR)
return 0;
if (ptr)
memcpy(ptr, json_object_get_string(jobj), len);
break;
case JU64: /* Unsigned 64bit integer stored as string */
if (!json_object_is_type(jobj, json_type_string))
break;
len = sizeof(u64);
if (ptr) {
u64 = cpu_to_be64(crypt_jobj_get_uint64(jobj));
memcpy(ptr, &u64, len);
}
break;
case JX64: /* Unsigned 64bit segment size (allows "dynamic") */
if (!json_object_is_type(jobj, json_type_string))
break;
if (!strcmp(json_object_get_string(jobj), "dynamic")) {
len = strlen("dynamic");
if (ptr)
memcpy(ptr, json_object_get_string(jobj), len);
} else {
len = sizeof(u64);
u64 = cpu_to_be64(crypt_jobj_get_uint64(jobj));
if (ptr)
memcpy(ptr, &u64, len);
}
break;
case JU32: /* Unsigned 32bit integer, stored as JSON int */
if (!json_object_is_type(jobj, json_type_int))
return 0;
len = sizeof(u32);
if (ptr) {
u32 = cpu_to_be32(crypt_jobj_get_uint32(jobj));
memcpy(ptr, &u32, len);
}
break;
case JNONE:
return 0;
};
return len;
}
static size_t srs(struct jtype j[], uint8_t *ptr)
{
size_t l, len = 0;
while(j->jobj) {
l = sr(j, ptr);
if (!l)
return 0;
len += l;
if (ptr)
ptr += l;
j++;
}
return len;
}
static size_t segment_linear_serialize(json_object *jobj_segment, uint8_t *buffer)
{
struct jtype j[] = {
{ JSTR, jobj_segment, "type" },
{ JU64, jobj_segment, "offset" },
{ JX64, jobj_segment, "size" },
{}
};
return srs(j, buffer);
}
static size_t segment_crypt_serialize(json_object *jobj_segment, uint8_t *buffer)
{
struct jtype j[] = {
{ JSTR, jobj_segment, "type" },
{ JU64, jobj_segment, "offset" },
{ JX64, jobj_segment, "size" },
{ JU64, jobj_segment, "iv_tweak" },
{ JSTR, jobj_segment, "encryption" },
{ JU32, jobj_segment, "sector_size" },
{}
};
return srs(j, buffer);
}
static size_t segment_serialize(json_object *jobj_segment, uint8_t *buffer)
{
json_object *jobj_type;
const char *segment_type;
if (!json_object_object_get_ex(jobj_segment, "type", &jobj_type))
return 0;
if (!(segment_type = json_object_get_string(jobj_type)))
return 0;
if (!strcmp(segment_type, "crypt"))
return segment_crypt_serialize(jobj_segment, buffer);
else if (!strcmp(segment_type, "linear"))
return segment_linear_serialize(jobj_segment, buffer);
return 0;
}
static size_t backup_segments_serialize(struct luks2_hdr *hdr, uint8_t *buffer)
{
json_object *jobj_segment;
size_t l, len = 0;
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-previous");
if (!jobj_segment || !(l = segment_serialize(jobj_segment, buffer)))
return 0;
len += l;
if (buffer)
buffer += l;
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-final");
if (!jobj_segment || !(l = segment_serialize(jobj_segment, buffer)))
return 0;
len += l;
if (buffer)
buffer += l;
jobj_segment = LUKS2_get_segment_by_flag(hdr, "backup-moved-segment");
if (jobj_segment) {
if (!(l = segment_serialize(jobj_segment, buffer)))
return 0;
len += l;
}
return len;
}
static size_t reenc_keyslot_serialize(struct luks2_hdr *hdr, uint8_t *buffer)
{
json_object *jobj_keyslot, *jobj_area, *jobj_type;
const char *area_type;
int keyslot_reencrypt;
keyslot_reencrypt = LUKS2_find_keyslot(hdr, "reencrypt");
if (keyslot_reencrypt < 0)
return 0;
if (!(jobj_keyslot = LUKS2_get_keyslot_jobj(hdr, keyslot_reencrypt)))
return 0;
if (!json_object_object_get_ex(jobj_keyslot, "area", &jobj_area))
return 0;
if (!json_object_object_get_ex(jobj_area, "type", &jobj_type))
return 0;
if (!(area_type = json_object_get_string(jobj_type)))
return 0;
struct jtype j[] = {
{ JSTR, jobj_keyslot, "mode" },
{ JSTR, jobj_keyslot, "direction" },
{ JSTR, jobj_area, "type" },
{ JU64, jobj_area, "offset" },
{ JU64, jobj_area, "size" },
{}
};
struct jtype j_datashift[] = {
{ JSTR, jobj_keyslot, "mode" },
{ JSTR, jobj_keyslot, "direction" },
{ JSTR, jobj_area, "type" },
{ JU64, jobj_area, "offset" },
{ JU64, jobj_area, "size" },
{ JU64, jobj_area, "shift_size" },
{}
};
struct jtype j_checksum[] = {
{ JSTR, jobj_keyslot, "mode" },
{ JSTR, jobj_keyslot, "direction" },
{ JSTR, jobj_area, "type" },
{ JU64, jobj_area, "offset" },
{ JU64, jobj_area, "size" },
{ JSTR, jobj_area, "hash" },
{ JU32, jobj_area, "sector_size" },
{}
};
if (!strcmp(area_type, "datashift"))
return srs(j_datashift, buffer);
else if (!strcmp(area_type, "checksum"))
return srs(j_checksum, buffer);
return srs(j, buffer);
}
static size_t blob_serialize(void *blob, size_t length, uint8_t *buffer)
{
if (buffer)
memcpy(buffer, blob, length);
return length;
}
static int reencrypt_assembly_verification_data(struct crypt_device *cd,
struct luks2_hdr *hdr,
struct volume_key *vks,
struct volume_key **verification_data)
{
uint8_t *ptr;
int digest_new, digest_old;
struct volume_key *data = NULL, *vk_old = NULL, *vk_new = NULL;
size_t keyslot_data_len, segments_data_len, data_len = 2;
/* Keys - calculate length */
digest_new = LUKS2_reencrypt_digest_new(hdr);
digest_old = LUKS2_reencrypt_digest_old(hdr);
if (digest_old >= 0) {
vk_old = crypt_volume_key_by_id(vks, digest_old);
if (!vk_old)
return -EINVAL;
data_len += blob_serialize(vk_old->key, vk_old->keylength, NULL);
}
if (digest_new >= 0 && digest_old != digest_new) {
vk_new = crypt_volume_key_by_id(vks, digest_new);
if (!vk_new)
return -EINVAL;