diff mbox series

[v2] efi_loader: Measure the loaded DTB

Message ID 20230216162948.2343115-1-etienne.carriere@linaro.org
State Accepted
Commit aa2d3945ce6df43903d76cadde1c0669d6d5d43b
Headers show
Series [v2] efi_loader: Measure the loaded DTB | expand

Commit Message

Etienne Carriere Feb. 16, 2023, 4:29 p.m. UTC
Measures the DTB passed to the EFI application upon new boolean config
switch CONFIG_EFI_TCG2_PROTOCOL_MEASURE_DTB. For platforms where the
content of the DTB passed to the OS can change across reboots, there is
not point measuring it hence the config switch to allow platform to not
embed this feature.

Co-developed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Signed-off-by: Etienne Carriere <etienne.carriere@linaro.org>
---
Changes since v1
- Moved measurement to after DTB tweaks in efi_install_fdt() and change
  its measure to hash only populated areas in the DTB (header, structs,
  strings and reserved memory maps). efi_tcg2_measure_dtb() computes
  the hash of these concatenated areas and used it as DTB  measurement.
- Remove useless default value 'n' for EFI_TCG2_PROTOCOL_MEASURE_DTB.
- I did not add EFI_TCG2_PROTOCOL_MEASURE_DTB dependencies on
  !NET_RANDOM_ETHADDR neither on !CMD_KASLRSEED. If ethernet address
  is random but always the same at each boot as saved in environment,
  it's measure is meaningful. CMD_KASLRSEED effect in DT if already
  addressed by efi_try_purge_kaslr_seed() prior measurement.
---
 cmd/bootefi.c             |  8 +++++
 include/efi_loader.h      |  2 ++
 include/efi_tcg2.h        | 10 ++++++
 include/tpm-v2.h          |  2 ++
 lib/efi_loader/Kconfig    | 11 ++++++
 lib/efi_loader/efi_tcg2.c | 73 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 106 insertions(+)

Comments

Ilias Apalodimas Feb. 17, 2023, 5:18 p.m. UTC | #1
Hi Etienne

On Thu, Feb 16, 2023 at 05:29:48PM +0100, Etienne Carriere wrote:
> Measures the DTB passed to the EFI application upon new boolean config
> switch CONFIG_EFI_TCG2_PROTOCOL_MEASURE_DTB. For platforms where the
> content of the DTB passed to the OS can change across reboots, there is
> not point measuring it hence the config switch to allow platform to not
> embed this feature.
>
> Co-developed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
> Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
> Signed-off-by: Etienne Carriere <etienne.carriere@linaro.org>
> ---
> Changes since v1
> - Moved measurement to after DTB tweaks in efi_install_fdt() and change
>   its measure to hash only populated areas in the DTB (header, structs,
>   strings and reserved memory maps). efi_tcg2_measure_dtb() computes
>   the hash of these concatenated areas and used it as DTB  measurement.
> - Remove useless default value 'n' for EFI_TCG2_PROTOCOL_MEASURE_DTB.
> - I did not add EFI_TCG2_PROTOCOL_MEASURE_DTB dependencies on
>   !NET_RANDOM_ETHADDR neither on !CMD_KASLRSEED. If ethernet address
>   is random but always the same at each boot as saved in environment,
>   it's measure is meaningful. CMD_KASLRSEED effect in DT if already
>   addressed by efi_try_purge_kaslr_seed() prior measurement.

That's correct, and since the TPM is guaranteed to have an RNG, we will
always install the EFI_RNG protocol and get rid of kaslr-seed for the DT.

> ---
>  cmd/bootefi.c             |  8 +++++
>  include/efi_loader.h      |  2 ++
>  include/efi_tcg2.h        | 10 ++++++
>  include/tpm-v2.h          |  2 ++
>  lib/efi_loader/Kconfig    | 11 ++++++
>  lib/efi_loader/efi_tcg2.c | 73 +++++++++++++++++++++++++++++++++++++++
>  6 files changed, 106 insertions(+)
>
> diff --git a/cmd/bootefi.c b/cmd/bootefi.c
> index 2a7d42925d..6618335ddf 100644
> --- a/cmd/bootefi.c
> +++ b/cmd/bootefi.c
> @@ -332,6 +332,14 @@ efi_status_t efi_install_fdt(void *fdt)
>
>  	efi_try_purge_kaslr_seed(fdt);
>
> +	if (CONFIG_IS_ENABLED(EFI_TCG2_PROTOCOL_MEASURE_DTB)) {
> +		ret = efi_tcg2_measure_dtb(fdt);
> +		if (ret == EFI_SECURITY_VIOLATION) {
> +			log_err("ERROR: failed to measure DTB\n");
> +			return ret;
> +		}
> +	}
> +
>  	/* Install device tree as UEFI table */
>  	ret = efi_install_configuration_table(&efi_guid_fdt, fdt);
>  	if (ret != EFI_SUCCESS) {
> diff --git a/include/efi_loader.h b/include/efi_loader.h
> index 4560b0d04c..4ecfdf919b 100644
> --- a/include/efi_loader.h
> +++ b/include/efi_loader.h
> @@ -531,6 +531,8 @@ efi_status_t efi_tcg2_notify_exit_boot_services_failed(void);
>  efi_status_t efi_tcg2_measure_efi_app_invocation(struct efi_loaded_image_obj *handle);
>  /* Measure efi application exit */
>  efi_status_t efi_tcg2_measure_efi_app_exit(void);
> +/* Measure DTB */
> +efi_status_t efi_tcg2_measure_dtb(void *dtb);
>  /* Called by bootefi to initialize root node */
>  efi_status_t efi_root_node_register(void);
>  /* Called by bootefi to initialize runtime */
> diff --git a/include/efi_tcg2.h b/include/efi_tcg2.h
> index 874306dc11..b1c3abd097 100644
> --- a/include/efi_tcg2.h
> +++ b/include/efi_tcg2.h
> @@ -233,6 +233,16 @@ struct efi_gpt_data {
>  	gpt_entry partitions[];
>  } __packed;
>
> +/**
> + * struct tdUEFI_PLATFORM_FIRMWARE_BLOB2
> + * @blob_description_size:	Byte size of @data
> + * @data:			Description data
> + */
> +struct uefi_platform_firmware_blob2 {
> +	u8 blob_description_size;
> +	u8 data[];
> +} __packed;
> +
>  struct efi_tcg2_protocol {
>  	efi_status_t (EFIAPI * get_capability)(struct efi_tcg2_protocol *this,
>  					       struct efi_tcg2_boot_service_capability *capability);
> diff --git a/include/tpm-v2.h b/include/tpm-v2.h
> index 737e57551d..2df3dad553 100644
> --- a/include/tpm-v2.h
> +++ b/include/tpm-v2.h
> @@ -105,6 +105,8 @@ struct udevice;
>  	"Exit Boot Services Returned with Failure"
>  #define EFI_EXIT_BOOT_SERVICES_SUCCEEDED    \
>  	"Exit Boot Services Returned with Success"
> +#define EFI_DTB_EVENT_STRING \
> +	"DTB DATA"
>
>  /* TPMS_TAGGED_PROPERTY Structure */
>  struct tpms_tagged_property {
> diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
> index c56904afc2..c05a54df16 100644
> --- a/lib/efi_loader/Kconfig
> +++ b/lib/efi_loader/Kconfig
> @@ -346,6 +346,17 @@ config EFI_TCG2_PROTOCOL_EVENTLOG_SIZE
>  		this is going to be allocated twice. One for the eventlog it self
>  		and one for the configuration table that is required from the spec
>
> +config EFI_TCG2_PROTOCOL_MEASURE_DTB
> +	bool "Measure DTB with EFI_TCG2_PROTOCOL"
> +	depends on EFI_TCG2_PROTOCOL
> +	help
> +	  When enabled, the DTB image passed to the booted EFI image is
> +	  measured using the EFI TCG2 protocol. Do not enable this feature if
> +	  the passed DTB contains data that change across platform reboots
> +	  and cannot be used has a predictable measurement. Otherwise
> +	  this feature allows better measurement of the system boot
> +	  sequence.
> +
>  config EFI_LOAD_FILE2_INITRD
>  	bool "EFI_FILE_LOAD2_PROTOCOL for Linux initial ramdisk"
>  	default y
> diff --git a/lib/efi_loader/efi_tcg2.c b/lib/efi_loader/efi_tcg2.c
> index 918e9a2686..2dcc317157 100644
> --- a/lib/efi_loader/efi_tcg2.c
> +++ b/lib/efi_loader/efi_tcg2.c
> @@ -2175,6 +2175,79 @@ out1:
>  	return ret;
>  }
>
> +/* Return the byte size of reserved map area in DTB or -1 upon error */
> +static ssize_t size_of_rsvmap(void *dtb)
> +{
> +	struct fdt_reserve_entry e;
> +	ssize_t size_max;
> +	ssize_t size;
> +	u8 *rsvmap_base;
> +
> +	rsvmap_base = (u8 *)dtb + fdt_off_mem_rsvmap(dtb);
> +	size_max = fdt_totalsize(dtb) - fdt_off_mem_rsvmap(dtb);
> +	size = 0;
> +
> +	do {
> +		memcpy(&e, rsvmap_base + size, sizeof(e));
> +		size += sizeof(e);
> +		if (size > size_max)
> +			return -1;
> +	} while (e.size);
> +
> +	return size;
> +}
> +
> +/**
> + * efi_tcg2_measure_dtb() - measure DTB passed to the OS
> + *
> + * @dtb: pointer to the device tree blob
> + *
> + * Return:	status code
> + */
> +efi_status_t efi_tcg2_measure_dtb(void *dtb)
> +{
> +	struct uefi_platform_firmware_blob2 *blob;
> +	struct fdt_header *header;
> +	sha256_context hash_ctx;
> +	struct udevice *dev;
> +	ssize_t rsvmap_size;
> +	efi_status_t ret;
> +	u32 event_size;
> +
> +	if (!is_tcg2_protocol_installed())
> +		return EFI_SUCCESS;
> +
> +	ret = platform_get_tpm2_device(&dev);
> +	if (ret != EFI_SUCCESS)
> +		return EFI_SECURITY_VIOLATION;
> +
> +	rsvmap_size = size_of_rsvmap(dtb);
> +	if (rsvmap_size < 0)
> +		return EFI_SECURITY_VIOLATION;

Why is this a security violation? Maybe EFI_INVALID_PARAMETER?

> +
> +	event_size = sizeof(*blob) + sizeof(EFI_DTB_EVENT_STRING) + SHA256_SUM_LEN;
> +	blob = calloc(1, event_size);
> +	if (!blob)
> +		return EFI_OUT_OF_RESOURCES;
> +
> +	blob->blob_description_size = sizeof(EFI_DTB_EVENT_STRING);
> +	memcpy(blob->data, EFI_DTB_EVENT_STRING, blob->blob_description_size);
> +
> +	/* Measure populated areas of the DTB */
> +	header = dtb;
> +	sha256_starts(&hash_ctx);
> +	sha256_update(&hash_ctx, (u8 *)header, sizeof(struct fdt_header));
> +	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_struct(dtb), fdt_size_dt_strings(dtb));
> +	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_strings(dtb), fdt_size_dt_struct(dtb));
> +	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_mem_rsvmap(dtb), rsvmap_size);
> +	sha256_finish(&hash_ctx, blob->data + blob->blob_description_size);
> +
> +	ret = tcg2_measure_event(dev, 0, EV_POST_CODE, event_size, (u8 *)blob);
> +
> +	free(blob);
> +	return ret;
> +}
> +
>  /**
>   * efi_tcg2_measure_efi_app_invocation() - measure efi app invocation
>   *
> --
> 2.25.1
>

With the return code changed

Tested-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
diff mbox series

Patch

diff --git a/cmd/bootefi.c b/cmd/bootefi.c
index 2a7d42925d..6618335ddf 100644
--- a/cmd/bootefi.c
+++ b/cmd/bootefi.c
@@ -332,6 +332,14 @@  efi_status_t efi_install_fdt(void *fdt)
 
 	efi_try_purge_kaslr_seed(fdt);
 
+	if (CONFIG_IS_ENABLED(EFI_TCG2_PROTOCOL_MEASURE_DTB)) {
+		ret = efi_tcg2_measure_dtb(fdt);
+		if (ret == EFI_SECURITY_VIOLATION) {
+			log_err("ERROR: failed to measure DTB\n");
+			return ret;
+		}
+	}
+
 	/* Install device tree as UEFI table */
 	ret = efi_install_configuration_table(&efi_guid_fdt, fdt);
 	if (ret != EFI_SUCCESS) {
diff --git a/include/efi_loader.h b/include/efi_loader.h
index 4560b0d04c..4ecfdf919b 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -531,6 +531,8 @@  efi_status_t efi_tcg2_notify_exit_boot_services_failed(void);
 efi_status_t efi_tcg2_measure_efi_app_invocation(struct efi_loaded_image_obj *handle);
 /* Measure efi application exit */
 efi_status_t efi_tcg2_measure_efi_app_exit(void);
+/* Measure DTB */
+efi_status_t efi_tcg2_measure_dtb(void *dtb);
 /* Called by bootefi to initialize root node */
 efi_status_t efi_root_node_register(void);
 /* Called by bootefi to initialize runtime */
diff --git a/include/efi_tcg2.h b/include/efi_tcg2.h
index 874306dc11..b1c3abd097 100644
--- a/include/efi_tcg2.h
+++ b/include/efi_tcg2.h
@@ -233,6 +233,16 @@  struct efi_gpt_data {
 	gpt_entry partitions[];
 } __packed;
 
+/**
+ * struct tdUEFI_PLATFORM_FIRMWARE_BLOB2
+ * @blob_description_size:	Byte size of @data
+ * @data:			Description data
+ */
+struct uefi_platform_firmware_blob2 {
+	u8 blob_description_size;
+	u8 data[];
+} __packed;
+
 struct efi_tcg2_protocol {
 	efi_status_t (EFIAPI * get_capability)(struct efi_tcg2_protocol *this,
 					       struct efi_tcg2_boot_service_capability *capability);
diff --git a/include/tpm-v2.h b/include/tpm-v2.h
index 737e57551d..2df3dad553 100644
--- a/include/tpm-v2.h
+++ b/include/tpm-v2.h
@@ -105,6 +105,8 @@  struct udevice;
 	"Exit Boot Services Returned with Failure"
 #define EFI_EXIT_BOOT_SERVICES_SUCCEEDED    \
 	"Exit Boot Services Returned with Success"
+#define EFI_DTB_EVENT_STRING \
+	"DTB DATA"
 
 /* TPMS_TAGGED_PROPERTY Structure */
 struct tpms_tagged_property {
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
index c56904afc2..c05a54df16 100644
--- a/lib/efi_loader/Kconfig
+++ b/lib/efi_loader/Kconfig
@@ -346,6 +346,17 @@  config EFI_TCG2_PROTOCOL_EVENTLOG_SIZE
 		this is going to be allocated twice. One for the eventlog it self
 		and one for the configuration table that is required from the spec
 
+config EFI_TCG2_PROTOCOL_MEASURE_DTB
+	bool "Measure DTB with EFI_TCG2_PROTOCOL"
+	depends on EFI_TCG2_PROTOCOL
+	help
+	  When enabled, the DTB image passed to the booted EFI image is
+	  measured using the EFI TCG2 protocol. Do not enable this feature if
+	  the passed DTB contains data that change across platform reboots
+	  and cannot be used has a predictable measurement. Otherwise
+	  this feature allows better measurement of the system boot
+	  sequence.
+
 config EFI_LOAD_FILE2_INITRD
 	bool "EFI_FILE_LOAD2_PROTOCOL for Linux initial ramdisk"
 	default y
diff --git a/lib/efi_loader/efi_tcg2.c b/lib/efi_loader/efi_tcg2.c
index 918e9a2686..2dcc317157 100644
--- a/lib/efi_loader/efi_tcg2.c
+++ b/lib/efi_loader/efi_tcg2.c
@@ -2175,6 +2175,79 @@  out1:
 	return ret;
 }
 
+/* Return the byte size of reserved map area in DTB or -1 upon error */
+static ssize_t size_of_rsvmap(void *dtb)
+{
+	struct fdt_reserve_entry e;
+	ssize_t size_max;
+	ssize_t size;
+	u8 *rsvmap_base;
+
+	rsvmap_base = (u8 *)dtb + fdt_off_mem_rsvmap(dtb);
+	size_max = fdt_totalsize(dtb) - fdt_off_mem_rsvmap(dtb);
+	size = 0;
+
+	do {
+		memcpy(&e, rsvmap_base + size, sizeof(e));
+		size += sizeof(e);
+		if (size > size_max)
+			return -1;
+	} while (e.size);
+
+	return size;
+}
+
+/**
+ * efi_tcg2_measure_dtb() - measure DTB passed to the OS
+ *
+ * @dtb: pointer to the device tree blob
+ *
+ * Return:	status code
+ */
+efi_status_t efi_tcg2_measure_dtb(void *dtb)
+{
+	struct uefi_platform_firmware_blob2 *blob;
+	struct fdt_header *header;
+	sha256_context hash_ctx;
+	struct udevice *dev;
+	ssize_t rsvmap_size;
+	efi_status_t ret;
+	u32 event_size;
+
+	if (!is_tcg2_protocol_installed())
+		return EFI_SUCCESS;
+
+	ret = platform_get_tpm2_device(&dev);
+	if (ret != EFI_SUCCESS)
+		return EFI_SECURITY_VIOLATION;
+
+	rsvmap_size = size_of_rsvmap(dtb);
+	if (rsvmap_size < 0)
+		return EFI_SECURITY_VIOLATION;
+
+	event_size = sizeof(*blob) + sizeof(EFI_DTB_EVENT_STRING) + SHA256_SUM_LEN;
+	blob = calloc(1, event_size);
+	if (!blob)
+		return EFI_OUT_OF_RESOURCES;
+
+	blob->blob_description_size = sizeof(EFI_DTB_EVENT_STRING);
+	memcpy(blob->data, EFI_DTB_EVENT_STRING, blob->blob_description_size);
+
+	/* Measure populated areas of the DTB */
+	header = dtb;
+	sha256_starts(&hash_ctx);
+	sha256_update(&hash_ctx, (u8 *)header, sizeof(struct fdt_header));
+	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_struct(dtb), fdt_size_dt_strings(dtb));
+	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_dt_strings(dtb), fdt_size_dt_struct(dtb));
+	sha256_update(&hash_ctx, (u8 *)dtb + fdt_off_mem_rsvmap(dtb), rsvmap_size);
+	sha256_finish(&hash_ctx, blob->data + blob->blob_description_size);
+
+	ret = tcg2_measure_event(dev, 0, EV_POST_CODE, event_size, (u8 *)blob);
+
+	free(blob);
+	return ret;
+}
+
 /**
  * efi_tcg2_measure_efi_app_invocation() - measure efi app invocation
  *