Fix dm-integrity HMAC recalculation problem.

This patch adds support for Linux kernel (since version 5.11) dm-integrity
fixes that disables integrity recalculation if keyed algorithms (HMAC) is used.

Original dm-integrity superblock version <=4 is recalculation offset
field not protected by HMAC. An attacker can move this pointer and force
the kernel to recalculate the data area, ignoring original HMAC tags.

N.B. dm-integrity was not intended to protect against intentional changes.
Better use authenticated encryption (AEAD) in combination with dm-crypt.
It is designed to protect against random data corruption caused by hardware
or storage medium faults.

Despite that, we try to keep the system secure if keyed algorithms are used.

There are two possible keyed algorithms in dm-integrity - algorithm used
to protect journal and superblock (--journal-integrity) and algorithms
for protecting data (--integrity).
The dm-integrity superblock is guarded by --journal-integrity, so if you want
to protect data with HMAC, you should always also use HMAC for --journal-integrity.
The keys are independent. If HMAC is used for data but not for the journal,
recalculation is disabled by default.

For new kernel dm-integrity, the HMAC option also uses salt in superblock
to avoid an easy way to distinguish that the HMAC key is the same for two devices
(if data are the same).

The new HMAC and superblock are enabled automatically if the kernel supports it
(you can see superblock version 5 and fix_hmac flag in dump command).

If you need to use (insecure) backward compatibility, then two new integritysetup
options are introduced:

 Use --integrity-legacy-recalc (instead of --integrity-recalc) to allow recalculation
 on legacy devices.

 Use --integrity-legacy-hmac in format action to force old insecure version
 format (with HMAC).

Libcryptsetup API also introduces flags
  CRYPT_COMPAT_LEGACY_INTEGRITY_HMAC and
  CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC
to set these through crypt_set_compatibility() call.
This commit is contained in:
Milan Broz
2021-01-23 20:08:19 +01:00
parent 6df3488654
commit 586b0a39d8
10 changed files with 117 additions and 5 deletions

View File

@@ -40,7 +40,7 @@ static int INTEGRITY_read_superblock(struct crypt_device *cd,
if (read_lseek_blockwise(devfd, device_block_size(cd, device),
device_alignment(device), sb, sizeof(*sb), offset) != sizeof(*sb) ||
memcmp(sb->magic, SB_MAGIC, sizeof(sb->magic)) ||
sb->version < SB_VERSION_1 || sb->version > SB_VERSION_4) {
sb->version < SB_VERSION_1 || sb->version > SB_VERSION_5) {
log_std(cd, "No integrity superblock detected on %s.\n",
device_path(device));
r = -EINVAL;
@@ -95,11 +95,12 @@ int INTEGRITY_dump(struct crypt_device *cd, struct device *device, uint64_t offs
if (sb.version >= SB_VERSION_2 && (sb.flags & SB_FLAG_RECALCULATING))
log_std(cd, "recalc_sector %" PRIu64 "\n", sb.recalc_sector);
log_std(cd, "log2_blocks_per_bitmap %u\n", sb.log2_blocks_per_bitmap_bit);
log_std(cd, "flags %s%s%s%s\n",
log_std(cd, "flags %s%s%s%s%s\n",
sb.flags & SB_FLAG_HAVE_JOURNAL_MAC ? "have_journal_mac " : "",
sb.flags & SB_FLAG_RECALCULATING ? "recalculating " : "",
sb.flags & SB_FLAG_DIRTY_BITMAP ? "dirty_bitmap " : "",
sb.flags & SB_FLAG_FIXED_PADDING ? "fix_padding " : "");
sb.flags & SB_FLAG_FIXED_PADDING ? "fix_padding " : "",
sb.flags & SB_FLAG_FIXED_HMAC ? "fix_hmac " : "");
return 0;
}
@@ -278,6 +279,15 @@ int INTEGRITY_activate_dmd_device(struct crypt_device *cd,
return -ENOTSUP;
}
if (r < 0 && (dmd->flags & CRYPT_ACTIVATE_RECALCULATE) &&
!(crypt_get_compatibility(cd) & CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC) &&
(sb_flags & SB_FLAG_FIXED_HMAC) ?
(tgt->u.integrity.vk && !tgt->u.integrity.journal_integrity_key) :
(tgt->u.integrity.vk || tgt->u.integrity.journal_integrity_key)) {
log_err(cd, _("Kernel refuses to activate insecure recalculate option (see legacy activation options to override)."));
return -ENOTSUP;
}
return r;
}

View File

@@ -35,11 +35,13 @@ struct crypt_dm_active_device;
#define SB_VERSION_2 2
#define SB_VERSION_3 3
#define SB_VERSION_4 4
#define SB_VERSION_5 5
#define SB_FLAG_HAVE_JOURNAL_MAC (1 << 0)
#define SB_FLAG_RECALCULATING (1 << 1) /* V2 only */
#define SB_FLAG_DIRTY_BITMAP (1 << 2) /* V3 only */
#define SB_FLAG_FIXED_PADDING (1 << 3) /* V4 only */
#define SB_FLAG_FIXED_HMAC (1 << 4) /* V5 only */
struct superblock {
uint8_t magic[8];

View File

@@ -661,6 +661,10 @@ uint32_t crypt_get_compatibility(struct crypt_device *cd);
/** dm-integrity device uses less effective (legacy) padding (old kernels) */
#define CRYPT_COMPAT_LEGACY_INTEGRITY_PADDING (1 << 0)
/** dm-integrity device does not protect superblock with HMAC (old kernels) */
#define CRYPT_COMPAT_LEGACY_INTEGRITY_HMAC (1 << 1)
/** dm-integrity allow recalculating of volumes with HMAC keys (old kernels) */
#define CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC (1 << 2)
/**
* Convert to new type for already existing device.

View File

@@ -239,6 +239,9 @@ static void _dm_set_integrity_compat(struct crypt_device *cd,
if (_dm_satisfies_version(1, 6, 0, integrity_maj, integrity_min, integrity_patch))
_dm_flags |= DM_INTEGRITY_DISCARDS_SUPPORTED;
if (_dm_satisfies_version(1, 7, 0, integrity_maj, integrity_min, integrity_patch))
_dm_flags |= DM_INTEGRITY_FIX_HMAC_SUPPORTED;
_dm_integrity_checked = true;
}
@@ -912,12 +915,25 @@ static char *get_dm_integrity_params(const struct dm_target *tgt, uint32_t flags
strncat(features, feature, sizeof(features) - strlen(features) - 1);
crypt_safe_free(hexkey);
}
if (tgt->u.integrity.fix_padding) {
num_options++;
snprintf(feature, sizeof(feature), "fix_padding ");
strncat(features, feature, sizeof(features) - strlen(features) - 1);
}
if (tgt->u.integrity.fix_hmac) {
num_options++;
snprintf(feature, sizeof(feature), "fix_hmac ");
strncat(features, feature, sizeof(features) - strlen(features) - 1);
}
if (tgt->u.integrity.legacy_recalc) {
num_options++;
snprintf(feature, sizeof(feature), "legacy_recalculate ");
strncat(features, feature, sizeof(features) - strlen(features) - 1);
}
if (flags & CRYPT_ACTIVATE_RECALCULATE) {
num_options++;
snprintf(feature, sizeof(feature), "recalculate ");
@@ -2474,6 +2490,10 @@ static int _dm_target_query_integrity(struct crypt_device *cd,
*act_flags |= CRYPT_ACTIVATE_RECALCULATE;
} else if (!strcmp(arg, "fix_padding")) {
tgt->u.integrity.fix_padding = true;
} else if (!strcmp(arg, "fix_hmac")) {
tgt->u.integrity.fix_hmac = true;
} else if (!strcmp(arg, "legacy_recalculate")) {
tgt->u.integrity.legacy_recalc = true;
} else if (!strcmp(arg, "allow_discards")) {
*act_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
} else /* unknown option */
@@ -3065,6 +3085,15 @@ int dm_integrity_target_set(struct crypt_device *cd,
!(crypt_get_compatibility(cd) & CRYPT_COMPAT_LEGACY_INTEGRITY_PADDING))
tgt->u.integrity.fix_padding = true;
if (!dm_flags(cd, DM_INTEGRITY, &dmi_flags) &&
(dmi_flags & DM_INTEGRITY_FIX_HMAC_SUPPORTED) &&
!(crypt_get_compatibility(cd) & CRYPT_COMPAT_LEGACY_INTEGRITY_HMAC))
tgt->u.integrity.fix_hmac = true;
/* This flag can be backported, just try to set it always */
if (crypt_get_compatibility(cd) & CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC)
tgt->u.integrity.legacy_recalc = true;
if (ip) {
tgt->u.integrity.journal_size = ip->journal_size;
tgt->u.integrity.journal_watermark = ip->journal_watermark;

View File

@@ -72,6 +72,7 @@ static inline uint32_t act2dmflags(uint32_t act_flags)
#define DM_INTEGRITY_DISCARDS_SUPPORTED (1 << 23) /* dm-integrity discards/TRIM option is supported */
#define DM_VERITY_PANIC_CORRUPTION_SUPPORTED (1 << 24) /* dm-verity panic on corruption */
#define DM_CRYPT_NO_WORKQUEUE_SUPPORTED (1 << 25) /* dm-crypt suppot for bypassing workqueues */
#define DM_INTEGRITY_FIX_HMAC_SUPPORTED (1 << 26) /* hmac covers also superblock */
typedef enum { DM_CRYPT = 0, DM_VERITY, DM_INTEGRITY, DM_LINEAR, DM_ERROR, DM_ZERO, DM_UNKNOWN } dm_target_type;
enum tdirection { TARGET_SET = 1, TARGET_QUERY };
@@ -149,6 +150,8 @@ struct dm_target {
struct device *meta_device;
bool fix_padding;
bool fix_hmac;
bool legacy_recalc;
} integrity;
struct {
uint64_t offset;

View File

@@ -193,6 +193,21 @@ The dm-integrity target is available since Linux kernel version 4.12.
Format and activation of an integrity device always require superuser
privilege because the superblock is calculated and handled in dm-integrity kernel target.
.SH LEGACY COMPATIBILITY OPTIONS
.TP
\fBWARNING:\fR
Do not use these options until you need compatibility with specific old kernel.
.TP
.B "\-\-integrity\-legacy\-padding"
Use inefficient legacy padding.
.TP
.B "\-\-integrity\-legacy\-hmac"
Use old flawed HMAC calclation (also does not protect superblock).
.TP
.B "\-\-integrity\-legacy\-recalculate"
Allow insecure recalculating of volumes with HMAC keys (recalcualtion offset in superblock
is not protected).
.SH RETURN CODES
Integritysetup returns 0 on success and a non-zero value on error.

View File

@@ -193,6 +193,9 @@ static int action_format(int arg)
if (ARG_SET(OPT_INTEGRITY_LEGACY_PADDING_ID))
crypt_set_compatibility(cd, CRYPT_COMPAT_LEGACY_INTEGRITY_PADDING);
if (ARG_SET(OPT_INTEGRITY_LEGACY_HMAC_ID))
crypt_set_compatibility(cd, CRYPT_COMPAT_LEGACY_INTEGRITY_HMAC);
r = crypt_format(cd, CRYPT_INTEGRITY, NULL, NULL, NULL, NULL, 0, &params);
if (r < 0) /* FIXME: call wipe signatures again */
goto out;
@@ -258,8 +261,9 @@ static int action_open(int arg)
if (ARG_SET(OPT_INTEGRITY_BITMAP_MODE_ID))
activate_flags |= CRYPT_ACTIVATE_NO_JOURNAL_BITMAP;
if (ARG_SET(OPT_INTEGRITY_RECALCULATE_ID))
if (ARG_SET(OPT_INTEGRITY_RECALCULATE_ID) || ARG_SET(OPT_INTEGRITY_LEGACY_RECALC_ID))
activate_flags |= CRYPT_ACTIVATE_RECALCULATE;
if (ARG_SET(OPT_ALLOW_DISCARDS_ID))
activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
@@ -274,6 +278,9 @@ static int action_open(int arg)
if (r)
goto out;
if (ARG_SET(OPT_INTEGRITY_LEGACY_RECALC_ID))
crypt_set_compatibility(cd, CRYPT_COMPAT_LEGACY_INTEGRITY_RECALC);
r = crypt_activate_by_volume_key(cd, action_argv[1], integrity_key,
ARG_UINT32(OPT_INTEGRITY_KEY_SIZE_ID), activate_flags);
out:

View File

@@ -47,6 +47,10 @@ ARG(OPT_INTEGRITY_KEY_SIZE, '\0', POPT_ARG_STRING, N_("The size of the data inte
ARG(OPT_INTEGRITY_LEGACY_PADDING, '\0', POPT_ARG_NONE, N_("Use inefficient legacy padding (old kernels)"), NULL, CRYPT_ARG_BOOL, {}, {})
ARG(OPT_INTEGRITY_LEGACY_HMAC, '\0', POPT_ARG_NONE, N_("Do not protect superblock with HMAC (old kernels)"), NULL, CRYPT_ARG_BOOL, {}, {})
ARG(OPT_INTEGRITY_LEGACY_RECALC, '\0', POPT_ARG_NONE, N_("Allow recalculating of volumes with HMAC keys (old kernels)"), NULL, CRYPT_ARG_BOOL, {}, OPT_INTEGRITY_RECALCULATE_ACTIONS)
ARG(OPT_INTEGRITY_NO_JOURNAL, 'D', POPT_ARG_NONE, N_("Disable journal for integrity device"), NULL, CRYPT_ARG_BOOL, {}, {})
ARG(OPT_INTERLEAVE_SECTORS, '\0', POPT_ARG_STRING, N_("Interleave sectors"), N_("SECTORS"), CRYPT_ARG_UINT32, {}, OPT_INTERLEAVE_SECTORS_ACTIONS)

View File

@@ -64,6 +64,8 @@
#define OPT_INTEGRITY_KEY_FILE "integrity-key-file"
#define OPT_INTEGRITY_KEY_SIZE "integrity-key-size"
#define OPT_INTEGRITY_LEGACY_PADDING "integrity-legacy-padding"
#define OPT_INTEGRITY_LEGACY_HMAC "integrity-legacy-hmac"
#define OPT_INTEGRITY_LEGACY_RECALC "integrity-legacy-recalculate"
#define OPT_INTEGRITY_NO_JOURNAL "integrity-no-journal"
#define OPT_INTEGRITY_NO_WIPE "integrity-no-wipe"
#define OPT_INTEGRITY_RECALCULATE "integrity-recalculate"

View File

@@ -14,6 +14,7 @@ DEV_LOOP=""
DEV=test123.img
DEV2=test124.img
KEY_FILE=key.img
KEY_FILE2=key2.img
dmremove() { # device
udevadm settle >/dev/null 2>&1
@@ -25,7 +26,7 @@ cleanup() {
[ -b /dev/mapper/$DEV_NAME2 ] && dmremove $DEV_NAME2
[ -n "$DEV_LOOP" ] && losetup -d "$DEV_LOOP"
DEV_LOOP=""
rm -f $DEV $DEV2 $KEY_FILE >/dev/null 2>&1
rm -f $DEV $DEV2 $KEY_FILE $KEY_FILE2 >/dev/null 2>&1
}
fail()
@@ -60,11 +61,15 @@ function dm_integrity_features()
[ $VER_MIN -gt 2 ] && {
DM_INTEGRITY_BITMAP=1
}
[ $VER_MIN -gt 6 ] && {
DM_INTEGRITY_HMAC_FIX=1
}
}
add_device() {
cleanup
dd if=/dev/urandom of=$KEY_FILE bs=1 count=512 >/dev/null 2>&1
dd if=/dev/urandom of=$KEY_FILE2 bs=1 count=32 >/dev/null 2>&1
dd if=/dev/zero of=$DEV bs=1M count=32 >/dev/null 2>&1
dd if=/dev/zero of=$DEV2 bs=1M count=32 >/dev/null 2>&1
sync
@@ -459,4 +464,35 @@ else
echo "[N/A]"
fi
echo -n "Fixed HMAC and legacy flags:"
if [ -n "$DM_INTEGRITY_HMAC_FIX" ] ; then
add_device
# only data HMAC
ARGS="--integrity hmac-sha256 --integrity-key-file $KEY_FILE --integrity-key-size 32"
$INTSETUP format -q $DEV --integrity-legacy-hmac --no-wipe --tag-size 32 $ARGS || fail "Cannot format device."
$INTSETUP open $DEV $DEV_NAME --integrity-recalculate $ARGS >/dev/null 2>&1 && fail "Cannot activate device."
$INTSETUP open $DEV $DEV_NAME --integrity-legacy-recalculate $ARGS || fail "Cannot activate device."
$INTSETUP close $DEV_NAME || fail "Cannot deactivate device."
# New version - must fail (no journal HMAC)
$INTSETUP format -q $DEV --no-wipe --tag-size 32 $ARGS || fail "Cannot format device."
$INTSETUP open $DEV $DEV_NAME --integrity-recalculate $ARGS >/dev/null 2>&1 && fail "Cannot activate device."
$INTSETUP open $DEV $DEV_NAME --integrity-legacy-recalculate $ARGS || fail "Cannot activate device."
$INTSETUP close $DEV_NAME || fail "Cannot deactivate device."
# data and journal HMAC
ARGS="$ARGS --journal-integrity hmac-sha256 --journal-integrity-key-file $KEY_FILE2 --journal-integrity-key-size 32"
$INTSETUP format -q $DEV --integrity-legacy-hmac --no-wipe --tag-size 32 $ARGS || fail "Cannot format device."
$INTSETUP open $DEV $DEV_NAME --integrity-recalculate $ARGS >/dev/null 2>&1 && fail "Cannot activate device."
$INTSETUP open $DEV $DEV_NAME --integrity-legacy-recalculate $ARGS || fail "Cannot activate device."
$INTSETUP close $DEV_NAME || fail "Cannot deactivate device."
# New fixed version
$INTSETUP format -q $DEV --no-wipe --tag-size 32 $ARGS || fail "Cannot format device."
$INTSETUP dump $DEV | grep "flags" | grep -q "fix_hmac" || fail "Flag for HMAC not set."
$INTSETUP open $DEV $DEV_NAME --integrity-recalculate $ARGS || fail "Cannot activate device."
$INTSETUP close $DEV_NAME || fail "Cannot deactivate device."
echo "[OK]"
else
echo "[N/A]"
fi
cleanup