Blob Blame History Raw
From 9576549fee9228cabd9ceee27739a30caab5a7f6 Mon Sep 17 00:00:00 2001
From: Milan Broz <gmazyland@gmail.com>
Date: Tue, 9 Nov 2021 11:54:27 +0100
Subject: [PATCH] Fix bogus memory allocation if LUKS2 header size is invalid.

LUKS2 code read the whole header to buffer to verify checksum,
so malloc is called on unvalidated input size parameter.

This can cause out of memory or unintentional device reads.
(Header validation will fail later anyway - the size is unsupported.)

Just do not allow too small and too big allocations here and fail quickly.

Fixes: #683.
---
 lib/luks2/luks2_disk_metadata.c               | 20 +++-
 ...ks2-metadata-size-invalid-secondary.img.sh | 96 +++++++++++++++++++
 ...enerate-luks2-metadata-size-invalid.img.sh | 94 ++++++++++++++++++
 tests/luks2-validation-test                   |  2 +
 4 files changed, 208 insertions(+), 4 deletions(-)
 create mode 100755 tests/generators/generate-luks2-metadata-size-invalid-secondary.img.sh
 create mode 100755 tests/generators/generate-luks2-metadata-size-invalid.img.sh

diff --git a/lib/luks2/luks2_disk_metadata.c b/lib/luks2/luks2_disk_metadata.c
index 502b0226..0500d5c7 100644
--- a/lib/luks2/luks2_disk_metadata.c
+++ b/lib/luks2/luks2_disk_metadata.c
@@ -195,6 +195,8 @@ static int hdr_disk_sanity_check_pre(struct crypt_device *cd,
 				     size_t *hdr_json_size, int secondary,
 				     uint64_t offset)
 {
+	uint64_t hdr_size;
+
 	if (memcmp(hdr->magic, secondary ? LUKS2_MAGIC_2ND : LUKS2_MAGIC_1ST, LUKS2_MAGIC_L))
 		return -EINVAL;
 
@@ -209,19 +211,26 @@ static int hdr_disk_sanity_check_pre(struct crypt_device *cd,
 		return -EINVAL;
 	}
 
-	if (secondary && (offset != be64_to_cpu(hdr->hdr_size))) {
+	hdr_size = be64_to_cpu(hdr->hdr_size);
+
+	if (hdr_size < LUKS2_HDR_16K_LEN || hdr_size > LUKS2_HDR_OFFSET_MAX) {
+		log_dbg(cd, "LUKS2 header has bogus size 0x%04x.", (unsigned)hdr_size);
+		return -EINVAL;
+	}
+
+	if (secondary && (offset != hdr_size)) {
 		log_dbg(cd, "LUKS2 offset 0x%04x in secondary header does not match size 0x%04x.",
-			(unsigned)offset, (unsigned)be64_to_cpu(hdr->hdr_size));
+			(unsigned)offset, (unsigned)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),
+		(unsigned)be16_to_cpu(hdr->version), (unsigned)hdr_size,
 		hdr->checksum_alg);
 
-	*hdr_json_size = be64_to_cpu(hdr->hdr_size) - LUKS2_HDR_BIN_LEN;
+	*hdr_json_size = hdr_size - LUKS2_HDR_BIN_LEN;
 	return 0;
 }
 
@@ -252,6 +261,9 @@ static int hdr_read_disk(struct crypt_device *cd,
 		return -EIO;
 	}
 
+	/*
+	 * hdr_json_size is validated if this call succeeds
+	 */
 	r = hdr_disk_sanity_check_pre(cd, hdr_disk, &hdr_json_size, secondary, offset);
 	if (r < 0) {
 		return r;
diff --git a/tests/generators/generate-luks2-metadata-size-invalid-secondary.img.sh b/tests/generators/generate-luks2-metadata-size-invalid-secondary.img.sh
new file mode 100755
index 00000000..4dd484e9
--- /dev/null
+++ b/tests/generators/generate-luks2-metadata-size-invalid-secondary.img.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+. lib.sh
+
+#
+# *** Description ***
+#
+# generate primary with predefined json_size. There's only limited
+# set of values allowed as json size in config section of LUKS2
+# metadata
+#
+# secondary header is corrupted on purpose as well
+#
+
+# $1 full target dir
+# $2 full source luks2 image
+
+function prepare()
+{
+	cp $SRC_IMG $TGT_IMG
+	test -d $TMPDIR || mkdir $TMPDIR
+	read_luks2_json0 $TGT_IMG $TMPDIR/json0
+	read_luks2_bin_hdr0 $TGT_IMG $TMPDIR/hdr0
+	read_luks2_bin_hdr1 $TGT_IMG $TMPDIR/hdr1
+}
+
+function generate()
+{
+	TEST_MDA_SIZE=$LUKS2_HDR_SIZE_1M
+
+	TEST_MDA_SIZE_BYTES=$((TEST_MDA_SIZE*512))
+	TEST_MDA_SIZE_BOGUS_BYTES=$((TEST_MDA_SIZE*512*2*1024))
+	TEST_JSN_SIZE=$((TEST_MDA_SIZE-LUKS2_BIN_HDR_SIZE))
+	KEYSLOTS_OFFSET=$((TEST_MDA_SIZE*1024))
+	JSON_DIFF=$(((TEST_MDA_SIZE-LUKS2_HDR_SIZE)*1024))
+	JSON_SIZE=$((TEST_JSN_SIZE*512))
+	DATA_OFFSET=16777216
+
+	json_str=$(jq -c --arg jdiff $JSON_DIFF --arg jsize $JSON_SIZE --arg off $DATA_OFFSET \
+		   '.keyslots[].area.offset |= ( . | tonumber + ($jdiff | tonumber) | tostring) |
+		    .config.json_size = $jsize |
+		    .segments."0".offset = $off' $TMPDIR/json0)
+	test -n "$json_str" || exit 2
+	test ${#json_str} -lt $((LUKS2_JSON_SIZE*512)) || exit 2
+
+	write_luks2_json "$json_str" $TMPDIR/json0 $TEST_JSN_SIZE
+
+	write_bin_hdr_size $TMPDIR/hdr0 $TEST_MDA_SIZE_BYTES
+	write_bin_hdr_size $TMPDIR/hdr1 $TEST_MDA_SIZE_BOGUS_BYTES
+
+	write_bin_hdr_offset $TMPDIR/hdr1 $TEST_MDA_SIZE_BYTES
+
+	merge_bin_hdr_with_json $TMPDIR/hdr0 $TMPDIR/json0 $TMPDIR/area0 $TEST_JSN_SIZE
+	merge_bin_hdr_with_json $TMPDIR/hdr1 $TMPDIR/json0 $TMPDIR/area1 $TEST_JSN_SIZE
+
+	erase_checksum $TMPDIR/area0
+	chks0=$(calc_sha256_checksum_file $TMPDIR/area0)
+	write_checksum $chks0 $TMPDIR/area0
+
+	erase_checksum $TMPDIR/area1
+	chks0=$(calc_sha256_checksum_file $TMPDIR/area1)
+	write_checksum $chks0 $TMPDIR/area1
+
+	kill_bin_hdr $TMPDIR/area0
+
+	write_luks2_hdr0 $TMPDIR/area0 $TGT_IMG $TEST_MDA_SIZE
+	write_luks2_hdr1 $TMPDIR/area1 $TGT_IMG $TEST_MDA_SIZE
+}
+
+function check()
+{
+	read_luks2_bin_hdr0 $TGT_IMG $TMPDIR/hdr_res0 $TEST_MDA_SIZE
+	local str_res0=$(head -c 6 $TMPDIR/hdr_res0)
+	test "$str_res0" = "VACUUM" || exit 2
+	read_luks2_json1 $TGT_IMG $TMPDIR/json_res1 $TEST_JSN_SIZE
+	jq -c --arg koff $KEYSLOTS_OFFSET --arg jsize $JSON_SIZE \
+		'if ([.keyslots[].area.offset] | map(tonumber) | min | tostring != $koff) or
+		    (.config.json_size != $jsize)
+		then error("Unexpected value in result json") else empty end' $TMPDIR/json_res1 || exit 5
+}
+
+function cleanup()
+{
+	rm -f $TMPDIR/*
+	rm -fd $TMPDIR
+}
+
+test $# -eq 2 || exit 1
+
+TGT_IMG=$1/$(test_img_name $0)
+SRC_IMG=$2
+
+prepare
+generate
+check
+cleanup
diff --git a/tests/generators/generate-luks2-metadata-size-invalid.img.sh b/tests/generators/generate-luks2-metadata-size-invalid.img.sh
new file mode 100755
index 00000000..6b9c0cf7
--- /dev/null
+++ b/tests/generators/generate-luks2-metadata-size-invalid.img.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+. lib.sh
+
+#
+# *** Description ***
+#
+# generate primary with predefined json_size. There's only limited
+# set of values allowed as json size in config section of LUKS2
+# metadata
+#
+# secondary header is corrupted on purpose as well
+#
+
+# $1 full target dir
+# $2 full source luks2 image
+
+function prepare()
+{
+	cp $SRC_IMG $TGT_IMG
+	test -d $TMPDIR || mkdir $TMPDIR
+	read_luks2_json0 $TGT_IMG $TMPDIR/json0
+	read_luks2_bin_hdr0 $TGT_IMG $TMPDIR/hdr0
+	read_luks2_bin_hdr1 $TGT_IMG $TMPDIR/hdr1
+}
+
+function generate()
+{
+	TEST_MDA_SIZE=$LUKS2_HDR_SIZE_1M
+
+	TEST_MDA_SIZE_BYTES=$((TEST_MDA_SIZE*512))
+	TEST_MDA_SIZE_BOGUS_BYTES=$((TEST_MDA_SIZE*512*2*1024))
+	TEST_JSN_SIZE=$((TEST_MDA_SIZE-LUKS2_BIN_HDR_SIZE))
+	KEYSLOTS_OFFSET=$((TEST_MDA_SIZE*1024))
+	JSON_DIFF=$(((TEST_MDA_SIZE-LUKS2_HDR_SIZE)*1024))
+	JSON_SIZE=$((TEST_JSN_SIZE*512))
+	DATA_OFFSET=16777216
+
+	json_str=$(jq -c --arg jdiff $JSON_DIFF --arg jsize $JSON_SIZE --arg off $DATA_OFFSET \
+		   '.keyslots[].area.offset |= ( . | tonumber + ($jdiff | tonumber) | tostring) |
+		    .config.json_size = $jsize |
+		    .segments."0".offset = $off' $TMPDIR/json0)
+	test -n "$json_str" || exit 2
+	test ${#json_str} -lt $((LUKS2_JSON_SIZE*512)) || exit 2
+
+	write_luks2_json "$json_str" $TMPDIR/json0 $TEST_JSN_SIZE
+
+	write_bin_hdr_size $TMPDIR/hdr0 $TEST_MDA_SIZE_BOGUS_BYTES
+	write_bin_hdr_size $TMPDIR/hdr1 $TEST_MDA_SIZE_BOGUS_BYTES
+
+	merge_bin_hdr_with_json $TMPDIR/hdr0 $TMPDIR/json0 $TMPDIR/area0 $TEST_JSN_SIZE
+	merge_bin_hdr_with_json $TMPDIR/hdr1 $TMPDIR/json0 $TMPDIR/area1 $TEST_JSN_SIZE
+
+	erase_checksum $TMPDIR/area0
+	chks0=$(calc_sha256_checksum_file $TMPDIR/area0)
+	write_checksum $chks0 $TMPDIR/area0
+
+	erase_checksum $TMPDIR/area1
+	chks0=$(calc_sha256_checksum_file $TMPDIR/area1)
+	write_checksum $chks0 $TMPDIR/area1
+
+	kill_bin_hdr $TMPDIR/area1
+
+	write_luks2_hdr0 $TMPDIR/area0 $TGT_IMG $TEST_MDA_SIZE
+	write_luks2_hdr1 $TMPDIR/area1 $TGT_IMG $TEST_MDA_SIZE
+}
+
+function check()
+{
+	read_luks2_bin_hdr1 $TGT_IMG $TMPDIR/hdr_res1 $TEST_MDA_SIZE
+	local str_res1=$(head -c 6 $TMPDIR/hdr_res1)
+	test "$str_res1" = "VACUUM" || exit 2
+	read_luks2_json0 $TGT_IMG $TMPDIR/json_res0 $TEST_JSN_SIZE
+	jq -c --arg koff $KEYSLOTS_OFFSET --arg jsize $JSON_SIZE \
+		'if ([.keyslots[].area.offset] | map(tonumber) | min | tostring != $koff) or
+		    (.config.json_size != $jsize)
+		then error("Unexpected value in result json") else empty end' $TMPDIR/json_res0 || exit 5
+}
+
+function cleanup()
+{
+	rm -f $TMPDIR/*
+	rm -fd $TMPDIR
+}
+
+test $# -eq 2 || exit 1
+
+TGT_IMG=$1/$(test_img_name $0)
+SRC_IMG=$2
+
+prepare
+generate
+check
+cleanup
diff --git a/tests/luks2-validation-test b/tests/luks2-validation-test
index 04183fbc..f771e1f9 100755
--- a/tests/luks2-validation-test
+++ b/tests/luks2-validation-test
@@ -229,6 +229,8 @@ RUN luks2-metadata-size-512k-secondary.img		"R" "Valid 512KiB metadata size in s
 RUN luks2-metadata-size-1m-secondary.img		"R" "Valid 1MiB metadata size in secondary hdr failed to validate"
 RUN luks2-metadata-size-2m-secondary.img		"R" "Valid 2MiB metadata size in secondary hdr failed to validate"
 RUN luks2-metadata-size-4m-secondary.img		"R" "Valid 4MiB metadata size in secondary hdr failed to validate"
+RUN luks2-metadata-size-invalid.img			"F" "Invalid metadata size in secondary hdr not rejected"
+RUN luks2-metadata-size-invalid-secondary.img		"F" "Invalid metadata size in secondary hdr not rejected"
 
 remove_mapping
 
-- 
2.27.0