From 33466d86f588e26a666333f0c97806fdf013d3f7 Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:30:00 +0530 Subject: [PATCH 1/8] pta: qcom: pas: prepare for PAS signature authentication Signature authentication will trust the carveout more heavily than hash-only verification does, and its TA-side policy will need the same PAS IDs core code already uses, so put both in place before the rest of the series builds on them. The carveout base and size come from the REE, so the range must be proven non-secure memory before it is mapped for hashing, and the REE writes it outside this mapping's coherency domain, so hashing could otherwise see a stale cached copy. Also reject an ELF with inconsistent header geometry, so segment collection cannot run on a malformed image. PAS authentication verifies the firmware contents. TOCTOU protection for the carveout is expected to come from the kernel/HYP MMU/SMMU configuration, which controls isolation between non-secure masters. Move the PAS_ID_* constants out of the core-only pas_data.h and into the shared pta_qcom_pas.h interface header, so the TA-side policy code added later in this series references the same values instead of a second, duplicated set. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- core/pta/qcom/pas/pas_auth.c | 23 ++++++++++++++++++---- core/pta/qcom/pas/platform/kodiak/dsp_fw.c | 1 + core/pta/qcom/pas/platform/pas_data.h | 9 --------- lib/libutee/include/pta_qcom_pas.h | 9 +++++++++ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/core/pta/qcom/pas/pas_auth.c b/core/pta/qcom/pas/pas_auth.c index 8968f75f47e..e8186bd10c8 100644 --- a/core/pta/qcom/pas/pas_auth.c +++ b/core/pta/qcom/pas/pas_auth.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -124,8 +125,9 @@ static TEE_Result parse_elf(const uint8_t *fw, size_t fw_size, return TEE_ERROR_BAD_FORMAT; PARSE_EHDR(e_info, ehdr, true); - if (e_info->elf_hdr_len < sizeof(*ehdr) || - e_info->phdr_entry_len < sizeof(Elf64_Phdr)) + if (e_info->elf_hdr_len != sizeof(*ehdr) || + e_info->phdr_offset != e_info->elf_hdr_len || + e_info->phdr_entry_len != sizeof(Elf64_Phdr)) return TEE_ERROR_BAD_FORMAT; break; } @@ -136,8 +138,9 @@ static TEE_Result parse_elf(const uint8_t *fw, size_t fw_size, return TEE_ERROR_BAD_FORMAT; PARSE_EHDR(e_info, ehdr, false); - if (e_info->elf_hdr_len < sizeof(*ehdr) || - e_info->phdr_entry_len < sizeof(Elf32_Phdr)) + if (e_info->elf_hdr_len != sizeof(*ehdr) || + e_info->phdr_offset != e_info->elf_hdr_len || + e_info->phdr_entry_len != sizeof(Elf32_Phdr)) return TEE_ERROR_BAD_FORMAT; break; } @@ -325,6 +328,8 @@ static TEE_Result verify_segments(const struct verify_ctx *ctx) expected = ctx->hash_table + i * ctx->hash_len; + dcache_inv_range(ctx->fw + offset, p_info.file_len); + res = hash_verify(ctx->hash_algo, ctx->fw + offset, p_info.file_len, expected, ctx->hash_len); if (res) { @@ -379,6 +384,16 @@ TEE_Result pas_platform_verify_image(uint32_t pas_id, } fw_size = data->fw_size; + if (!core_pbuf_is(CORE_MEM_NON_SEC, fw->base, fw_size)) { + EMSG("PAS auth: carveout %#"PRIxPA"/%#"PRIx32" not non-secure", + fw->base, fw_size); + return TEE_ERROR_SECURITY; + } + + /* + * TOCTOU protection for the carveout is expected to come from + * the kernel's MMU/SMMU configuration. + */ fw_va = core_mmu_add_mapping(MEM_AREA_RAM_NSEC, fw->base, fw_size); if (!fw_va) { EMSG("PAS auth: can't map carveout %#"PRIxPA"/%#"PRIx32, diff --git a/core/pta/qcom/pas/platform/kodiak/dsp_fw.c b/core/pta/qcom/pas/platform/kodiak/dsp_fw.c index df970e1b382..2ff3d63efe7 100644 --- a/core/pta/qcom/pas/platform/kodiak/dsp_fw.c +++ b/core/pta/qcom/pas/platform/kodiak/dsp_fw.c @@ -4,6 +4,7 @@ */ #include +#include #include #include diff --git a/core/pta/qcom/pas/platform/pas_data.h b/core/pta/qcom/pas/platform/pas_data.h index 1cf99887bee..cbd22921139 100644 --- a/core/pta/qcom/pas/platform/pas_data.h +++ b/core/pta/qcom/pas/platform/pas_data.h @@ -11,15 +11,6 @@ #include #include -#define PAS_ID_QDSP6 1 -#define PAS_ID_WPSS 6 -#define PAS_ID_IRIS 9 -#define PAS_ID_TURING 18 -#define PAS_ID_TURING1 30 -#define PAS_ID_CAMERA 33 -#define PAS_ID_GPDSP0 39 -#define PAS_ID_GPDSP1 40 - struct qcom_pas_data { uint32_t pas_id; struct io_pa_va base; diff --git a/lib/libutee/include/pta_qcom_pas.h b/lib/libutee/include/pta_qcom_pas.h index 4a1bd6c5fc6..39ec73ab981 100644 --- a/lib/libutee/include/pta_qcom_pas.h +++ b/lib/libutee/include/pta_qcom_pas.h @@ -14,6 +14,15 @@ * of the remote processor management */ +#define PAS_ID_QDSP6 1 +#define PAS_ID_WPSS 6 +#define PAS_ID_IRIS 9 +#define PAS_ID_TURING 18 +#define PAS_ID_TURING1 30 +#define PAS_ID_CAMERA 33 +#define PAS_ID_GPDSP0 39 +#define PAS_ID_GPDSP1 40 + #define PTA_QCOM_PAS_UUID { 0xdaedbae4, 0xcf3e, 0x4b76, \ { 0xa5, 0xc5, 0xdb, 0xf8, 0xb6, 0xfd, 0x5a, 0xf4} } From d5f42cac45906f68d94d43ba73fff34a67f3474e Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:31:00 +0530 Subject: [PATCH 2/8] drivers: qcom: qfprom: add Lemans platform configuration The secure-boot fuse accessors need per-target register offsets, masks and layout to operate. Keep the Lemans values as driver platform data so a future target only needs its own register layout, not a copy of the reader code. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover Reviewed-by: Kishore Batta --- .../qcom/qfprom/lemans/qfprom_target.h | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/drivers/qcom/qfprom/lemans/qfprom_target.h b/core/drivers/qcom/qfprom/lemans/qfprom_target.h index 8207330b4af..7f655f399bf 100644 --- a/core/drivers/qcom/qfprom/lemans/qfprom_target.h +++ b/core/drivers/qcom/qfprom/lemans/qfprom_target.h @@ -228,4 +228,30 @@ enum qfprom_perm_bit_pos { #define OEM_SPARE_30_PERM_MASK BIT(OEM_SPARE_30) #define OEM_SPARE_31_PERM_MASK BIT(OEM_SPARE_31) +#define OEM_CONFIG2_OFFSET 0x6054 +#define EKU_ENFORCEMENT_EN_SHFT 30 +#define SEGMENT_HASH_FUNCTION_SELECT0_SHFT 16 + +#define SECURE_BOOT_APPS_OFFSET 0x606c +#define SECURE_BOOT_AUTH_EN_BMSK BIT(5) +#define SECURE_BOOT_USE_SERIAL_NUM_BMSK BIT(6) + +#define JTAG_ID_OFFSET 0x6130 +#define JTAG_ID_AUTH_BMSK GENMASK_32(27, 0) + +#define SERIAL_NUM_OFFSET 0x6134 + +#define OEM_ID_OFFSET 0x6138 +#define OEM_ID_BMSK GENMASK_32(31, 16) +#define OEM_ID_SHFT 16 +#define MODEL_ID_BMSK GENMASK_32(15, 0) +#define MODEL_ID_SHFT 0 + +#define PK_HASH0_OFFSET 0x6190 +#define QFPROM_ROOT_OF_TRUST_BYTE_SIZE 48 + +#define TCSR_SOC_HW_VERSION_ADDR 0x01FC8000 +#define SOC_HW_VERSION_FAM_DEV_BMSK 0xffff0000 +#define SOC_HW_VERSION_FAM_DEV_SHFT 16 + #endif /* __QFPROM_TARGET_H__ */ From 56c5c9963cfd1df0c0034f782bd60dc2a10b9d10 Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:32:00 +0530 Subject: [PATCH 3/8] drivers: qcom: qfprom: add secure-boot fuse accessors Signature authentication has to bind an image to the device it runs on, which requires the OEM root-of-trust anchor and enable state, the device identity, and the EKU enforcement fuse. They are gated on CFG_QCOM_FUSE_PTA so a target without the fuse PTA carries none of this code. No caller reads them yet. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover Reviewed-by: Kishore Batta --- core/drivers/qcom/qfprom/qfprom_secboot.c | 192 ++++++++++++++++++++++ core/drivers/qcom/qfprom/sub.mk | 1 + core/include/drivers/qcom/qfprom/qfprom.h | 32 ++++ 3 files changed, 225 insertions(+) create mode 100644 core/drivers/qcom/qfprom/qfprom_secboot.c diff --git a/core/drivers/qcom/qfprom/qfprom_secboot.c b/core/drivers/qcom/qfprom/qfprom_secboot.c new file mode 100644 index 00000000000..661a211f44e --- /dev/null +++ b/core/drivers/qcom/qfprom/qfprom_secboot.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qfprom_priv.h" +#include "qfprom_target.h" + +register_phys_mem_pgdir(MEM_AREA_IO_SEC, TCSR_SOC_HW_VERSION_ADDR, + CORE_MMU_PGDIR_SIZE); + +static TEE_Result read_sense_reg(uint32_t offset, uint32_t *out) +{ + struct qfprom_context *drv = qfprom_get_context(); + + if (!drv->raw_base_va) + return TEE_ERROR_BAD_STATE; + + *out = io_read32(drv->raw_base_va + offset); + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_is_enabled(bool *enabled) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t val = 0; + + if (!enabled) + return TEE_ERROR_BAD_PARAMETERS; + + res = read_sense_reg(SECURE_BOOT_APPS_OFFSET, &val); + if (res) + return res; + + *enabled = (val & SECURE_BOOT_AUTH_EN_BMSK) != 0; + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_is_use_serial_num_enabled(bool *enabled) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t val = 0; + + if (!enabled) + return TEE_ERROR_BAD_PARAMETERS; + + res = read_sense_reg(SECURE_BOOT_APPS_OFFSET, &val); + if (res) + return res; + + *enabled = (val & SECURE_BOOT_USE_SERIAL_NUM_BMSK) != 0; + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_get_root_of_trust(uint8_t *hash, size_t len) +{ + size_t off = 0; + + if (!hash) + return TEE_ERROR_BAD_PARAMETERS; + + if (len != QFPROM_ROOT_OF_TRUST_BYTE_SIZE) + return TEE_ERROR_BAD_PARAMETERS; + + for (off = 0; off < len; off += sizeof(uint32_t)) { + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t word = 0; + + res = read_sense_reg(PK_HASH0_OFFSET + off, &word); + if (res) + return res; + + memcpy(hash + off, &word, sizeof(word)); + } + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_get_device_ids(struct qcom_secboot_device_ids *ids) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t val = 0; + + if (!ids) + return TEE_ERROR_BAD_PARAMETERS; + + res = read_sense_reg(OEM_ID_OFFSET, &val); + if (res) + return res; + ids->oem_id = (val & OEM_ID_BMSK) >> OEM_ID_SHFT; + ids->model_id = (val & MODEL_ID_BMSK) >> MODEL_ID_SHFT; + + res = read_sense_reg(JTAG_ID_OFFSET, &val); + if (res) + return res; + ids->jtag_id = val & JTAG_ID_AUTH_BMSK; + + res = read_sense_reg(SERIAL_NUM_OFFSET, &ids->serial_num); + if (res) + return res; + + return TEE_SUCCESS; +} + +#define SEGMENT_HASH_ROOT_CERT_SEL_MAX 3U + +/* + * Return the hash algorithm's digest size (SHA-256 or SHA-384) selected + * for the root cert at @root_cert_sel, per the OEM_CONFIG2 fuse row. + */ +TEE_Result qcom_secboot_get_segment_hash_len(uint32_t root_cert_sel, + uint32_t *hash_len) +{ + if (!hash_len) + return TEE_ERROR_BAD_PARAMETERS; + + if (root_cert_sel > SEGMENT_HASH_ROOT_CERT_SEL_MAX) + return TEE_ERROR_BAD_PARAMETERS; + + if (IS_ENABLED(CFG_QCOM_SEGMENT_HASH_SELECT)) { + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t val = 0; + + res = read_sense_reg(OEM_CONFIG2_OFFSET, &val); + if (res) + return res; + + if (val & BIT32(SEGMENT_HASH_FUNCTION_SELECT0_SHFT + + root_cert_sel)) + *hash_len = TEE_SHA256_HASH_SIZE; + else + *hash_len = TEE_SHA384_HASH_SIZE; + } else { + *hash_len = TEE_SHA384_HASH_SIZE; + } + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_get_eku_enforcement_en(bool *enabled) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t val = 0; + + if (!enabled) + return TEE_ERROR_BAD_PARAMETERS; + + res = read_sense_reg(OEM_CONFIG2_OFFSET, &val); + if (res) + return res; + + *enabled = val & BIT32(EKU_ENFORCEMENT_EN_SHFT); + + return TEE_SUCCESS; +} + +TEE_Result qcom_secboot_get_soc_hw_version(uint32_t *fam_dev) +{ + static vaddr_t soc_hw_version_addr; + uint32_t val = 0; + + if (!fam_dev) + return TEE_ERROR_BAD_PARAMETERS; + + if (!soc_hw_version_addr) { + soc_hw_version_addr = + (vaddr_t)phys_to_virt(TCSR_SOC_HW_VERSION_ADDR, + MEM_AREA_IO_SEC, + sizeof(uint32_t)); + if (!soc_hw_version_addr) + return TEE_ERROR_GENERIC; + } + + val = io_read32(soc_hw_version_addr); + *fam_dev = (val & SOC_HW_VERSION_FAM_DEV_BMSK) >> + SOC_HW_VERSION_FAM_DEV_SHFT; + + return TEE_SUCCESS; +} diff --git a/core/drivers/qcom/qfprom/sub.mk b/core/drivers/qcom/qfprom/sub.mk index f4bde830f83..5c83e7bba69 100644 --- a/core/drivers/qcom/qfprom/sub.mk +++ b/core/drivers/qcom/qfprom/sub.mk @@ -7,6 +7,7 @@ srcs-y += qfprom_core.c srcs-y += qfprom_hal.c srcs-y += qfprom_target.c srcs-y += $(PLATFORM_FLAVOR)/qfprom_fuse_region.c +srcs-$(CFG_QCOM_FUSE_PTA) += qfprom_secboot.c global-incdirs-y += . global-incdirs-y += $(PLATFORM_FLAVOR) diff --git a/core/include/drivers/qcom/qfprom/qfprom.h b/core/include/drivers/qcom/qfprom/qfprom.h index ff6f27ad8bf..f987f96a778 100644 --- a/core/include/drivers/qcom/qfprom/qfprom.h +++ b/core/include/drivers/qcom/qfprom/qfprom.h @@ -31,11 +31,43 @@ enum qfprom_error { QFPROM_ERROR_TIMEOUT = 0x11, }; +struct qcom_secboot_device_ids { + uint32_t oem_id; + uint32_t model_id; + uint32_t jtag_id; + uint32_t serial_num; +}; + /* Read QFPROM row data */ TEE_Result qfprom_read_row(uint32_t addr, enum qfprom_addr_space type, uint32_t *data); +/* Is secure boot (authentication) enabled on this device? */ +TEE_Result qcom_secboot_is_enabled(bool *enabled); + +/* Is the serial-number fuse used as part of device binding? */ +TEE_Result qcom_secboot_is_use_serial_num_enabled(bool *enabled); + +/* Read the OEM root-of-trust anchor hash (PK_HASH0). */ +TEE_Result qcom_secboot_get_root_of_trust(uint8_t *hash, size_t len); + +/* Read the OEM/model/JTAG/serial device-identity fuses. */ +TEE_Result qcom_secboot_get_device_ids(struct qcom_secboot_device_ids *ids); + +/* Read the SoC family/device word from TCSR_SOC_HW_VERSION. */ +TEE_Result qcom_secboot_get_soc_hw_version(uint32_t *fam_dev); + +/* + * Segment/hash-table digest size for @root_cert_sel; unrelated to + * cert-chain or root-of-trust hashing. + */ +TEE_Result qcom_secboot_get_segment_hash_len(uint32_t root_cert_sel, + uint32_t *hash_len); + +/* Is code-signing EKU enforcement required for this device? */ +TEE_Result qcom_secboot_get_eku_enforcement_en(bool *enabled); + /* Write QFPROM row data */ TEE_Result qfprom_write_row(uint32_t addr, uint32_t *data); From 57395ac59d5aa83008f3b3258890385c6b41e31e Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:33:00 +0530 Subject: [PATCH 4/8] pta: qcom: fuse: expose secure-boot fuses to user TAs The secure-boot fuse readers live in the OP-TEE core, but the PAS TA that needs them runs in user space. Expose them through a pseudo-TA restricted to a REE_KERNEL-only login domain, so the TA can obtain fuse-backed values without a driver dependency of its own. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- core/pta/qcom/fuse/pta_qcom_fuse.c | 221 ++++++++++++++++++++++++++++ core/pta/qcom/fuse/sub.mk | 1 + core/pta/qcom/sub.mk | 4 + lib/libutee/include/pta_qcom_fuse.h | 73 +++++++++ 4 files changed, 299 insertions(+) create mode 100644 core/pta/qcom/fuse/pta_qcom_fuse.c create mode 100644 core/pta/qcom/fuse/sub.mk create mode 100644 lib/libutee/include/pta_qcom_fuse.h diff --git a/core/pta/qcom/fuse/pta_qcom_fuse.c b/core/pta/qcom/fuse/pta_qcom_fuse.c new file mode 100644 index 00000000000..aafad06d54a --- /dev/null +++ b/core/pta/qcom/fuse/pta_qcom_fuse.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include + +#define TA_PAS_UUID { 0xcff7d191, 0x7ca0, 0x4784, \ + { 0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe} } + +#define PTA_NAME "qcom_fuse.pta" + +static TEE_Result get_secboot_state(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Result res = TEE_ERROR_GENERIC; + bool en = false; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_is_enabled(&en); + if (res) + return res; + + params[0].value.a = en; + + return TEE_SUCCESS; +} + +static TEE_Result get_root_of_trust(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + if (params[0].memref.size < PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE) { + params[0].memref.size = PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE; + return TEE_ERROR_SHORT_BUFFER; + } + + params[0].memref.size = PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE; + + return qcom_secboot_get_root_of_trust(params[0].memref.buffer, + PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE); +} + +static TEE_Result get_device_ids(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + struct qcom_secboot_device_ids ids = { }; + TEE_Result res = TEE_ERROR_GENERIC; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_get_device_ids(&ids); + if (res) + return res; + + params[0].value.a = ids.oem_id; + params[0].value.b = ids.model_id; + params[1].value.a = ids.jtag_id; + params[1].value.b = ids.serial_num; + + return TEE_SUCCESS; +} + +static TEE_Result get_soc_hw_version(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t fam_dev = 0; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_get_soc_hw_version(&fam_dev); + if (res) + return res; + + params[0].value.a = fam_dev; + return TEE_SUCCESS; +} + +static TEE_Result get_segment_hash_len(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_INOUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t hash_len = 0; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_get_segment_hash_len(params[0].value.a, + &hash_len); + if (res) + return res; + + params[0].value.b = hash_len; + return TEE_SUCCESS; +} + +static TEE_Result get_eku_enforcement_en(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Result res = TEE_ERROR_GENERIC; + bool en = false; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_get_eku_enforcement_en(&en); + if (res) + return res; + + params[0].value.a = en; + + return TEE_SUCCESS; +} + +static TEE_Result get_use_serial_num(uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + const uint32_t exp_pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + TEE_Result res = TEE_ERROR_GENERIC; + bool en = false; + + if (param_types != exp_pt) + return TEE_ERROR_BAD_PARAMETERS; + + res = qcom_secboot_is_use_serial_num_enabled(&en); + if (res) + return res; + + params[0].value.a = en; + + return TEE_SUCCESS; +} + +static TEE_Result invoke_command(void *sess_ctx __unused, + uint32_t cmd_id, + uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + switch (cmd_id) { + case PTA_QCOM_FUSE_GET_SECBOOT_STATE: + return get_secboot_state(param_types, params); + case PTA_QCOM_FUSE_GET_ROOT_OF_TRUST: + return get_root_of_trust(param_types, params); + case PTA_QCOM_FUSE_GET_DEVICE_IDS: + return get_device_ids(param_types, params); + case PTA_QCOM_FUSE_GET_SOC_HW_VERSION: + return get_soc_hw_version(param_types, params); + case PTA_QCOM_FUSE_GET_SEGMENT_HASH_LEN: + return get_segment_hash_len(param_types, params); + case PTA_QCOM_FUSE_GET_EKU_ENFORCEMENT_EN: + return get_eku_enforcement_en(param_types, params); + case PTA_QCOM_FUSE_GET_USE_SERIAL_NUM: + return get_use_serial_num(param_types, params); + default: + return TEE_ERROR_NOT_IMPLEMENTED; + } +} + +static TEE_Result open_session(uint32_t pt __unused, + TEE_Param params[TEE_NUM_PARAMS] __unused, + void **sess_ctx __unused) +{ + struct ts_session *s = ts_get_calling_session(); + TEE_UUID ta_uuid = TA_PAS_UUID; + struct ts_ctx *ctx = NULL; + + if (!s) + return TEE_ERROR_ACCESS_DENIED; + + ctx = s->ctx; + if (memcmp(&ctx->uuid, &ta_uuid, sizeof(TEE_UUID))) + return TEE_ERROR_ACCESS_DENIED; + + return TEE_SUCCESS; +} + +pseudo_ta_register(.uuid = PTA_QCOM_FUSE_UUID, + .name = PTA_NAME, + .flags = PTA_DEFAULT_FLAGS, + .open_session_entry_point = open_session, + .invoke_command_entry_point = invoke_command); diff --git a/core/pta/qcom/fuse/sub.mk b/core/pta/qcom/fuse/sub.mk new file mode 100644 index 00000000000..d30ac3d4f58 --- /dev/null +++ b/core/pta/qcom/fuse/sub.mk @@ -0,0 +1 @@ +srcs-y += pta_qcom_fuse.c diff --git a/core/pta/qcom/sub.mk b/core/pta/qcom/sub.mk index 2f0ac9af9fa..2e09c26303e 100644 --- a/core/pta/qcom/sub.mk +++ b/core/pta/qcom/sub.mk @@ -1 +1,5 @@ subdirs-$(CFG_QCOM_PAS_PTA) += pas + +# Exposes qfprom-backed fuse reads to user TAs. +CFG_QCOM_FUSE_PTA ?= n +subdirs-$(CFG_QCOM_FUSE_PTA) += fuse diff --git a/lib/libutee/include/pta_qcom_fuse.h b/lib/libutee/include/pta_qcom_fuse.h new file mode 100644 index 00000000000..863ad5cbfff --- /dev/null +++ b/lib/libutee/include/pta_qcom_fuse.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __PTA_QCOM_FUSE_H +#define __PTA_QCOM_FUSE_H + +/* + * Interface to the pseudo TA which exposes Qualcomm fuse state to + * user-mode TAs that cannot access the fuse driver directly. + */ + +#define PTA_QCOM_FUSE_UUID { 0x6b46384c, 0x4a3e, 0x4b9d, \ + { 0xa8, 0x2f, 0x1c, 0x3d, 0xe5, 0x9f, 0xa2, 0x11 } } + +/* + * Query whether secure boot is enabled. + * + * [out] params[0].value.a: 1 if secure boot is enabled, 0 otherwise + */ +#define PTA_QCOM_FUSE_GET_SECBOOT_STATE 1 + +#define PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE 48 + +/* + * Read the OEM root-of-trust digest. + * + * [out] params[0].memref: buffer receiving the digest; must be at least + * PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE bytes + */ +#define PTA_QCOM_FUSE_GET_ROOT_OF_TRUST 2 + +/* + * Read device-identity fields. + * + * [out] params[0].value.a: OEM_ID + * [out] params[0].value.b: MODEL_ID + * [out] params[1].value.a: JTAG_ID (masked to authentication bits) + * [out] params[1].value.b: serial number + */ +#define PTA_QCOM_FUSE_GET_DEVICE_IDS 3 + +/* + * Read the SOC hardware version family|device field. + * + * [out] params[0].value.a: family|device number + */ +#define PTA_QCOM_FUSE_GET_SOC_HW_VERSION 4 + +/* + * Read the firmware-segment hash digest size for a root_cert_sel index. + * + * [in] params[0].value.a: root_cert_sel (0-3) + * [out] params[0].value.b: digest size in bytes (32=SHA-256, 48=SHA-384) + */ +#define PTA_QCOM_FUSE_GET_SEGMENT_HASH_LEN 5 + +/* + * Query whether Extended Key Usage enforcement is fused on. + * + * [out] params[0].value.a: 1 if EKU enforcement is enabled, 0 otherwise + */ +#define PTA_QCOM_FUSE_GET_EKU_ENFORCEMENT_EN 6 + +/* + * Query the APPS serial-number binding override fuse. + * + * [out] params[0].value.a: 1 if the override is blown, 0 otherwise + */ +#define PTA_QCOM_FUSE_GET_USE_SERIAL_NUM 7 + +#endif /* __PTA_QCOM_FUSE_H */ From c06891fd396c1ff298ac7f9507f07129c2222bbc Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:34:00 +0530 Subject: [PATCH 5/8] ta: qcom_pas: add MBN metadata decoder and retype the hash-segment parser Signature authentication binds an image to its device using fields in the OEM metadata block. Decode the block into typed values, including anti-rollback and secondary SW ID fields for follow-on checks, so the authentication backend never re-parses the raw block. Metadata version and root_cert_sel are readable on their own, because each selects the hash size the full parse needs as input. The OEM-signed region masks out the QC-controlled fields, so the signature covers exactly what the OEM signed. Replace the hash-segment parser's raw byte-offset macros and untyped u32 reads with a typed MBN header struct, since the decoder above and the parser's other callers all dereference the same fields and byte-offset arithmetic is easy to get wrong on review. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- ta/qcom_pas/include/auth/pas_mbn.h | 63 ++++---- ta/qcom_pas/include/auth/pas_meta.h | 46 +++++- ta/qcom_pas/src/auth/pas_mbn.c | 125 +++++++-------- ta/qcom_pas/src/auth/pas_meta.c | 229 +++++++++++++++++++++++++++- 4 files changed, 363 insertions(+), 100 deletions(-) diff --git a/ta/qcom_pas/include/auth/pas_mbn.h b/ta/qcom_pas/include/auth/pas_mbn.h index d48306d95b4..c5fe19a5268 100644 --- a/ta/qcom_pas/include/auth/pas_mbn.h +++ b/ta/qcom_pas/include/auth/pas_mbn.h @@ -17,8 +17,8 @@ * [ hash-segment bytes ] verbatim MBN hash-segment phdr content * * MBN hash segment, v6 (48-byte header): - * [header][qti meta][oem meta][hash table] - * [qti sig][qti certs][oem sig][oem certs] + * [header][qc meta][oem meta][hash table] + * [qc sig][qc certs][oem sig][oem certs] * * Hash table: one digest per ELF program header; entry 0 = digest of the ELF * header plus program-header table, entry i = digest of the segment at phdr i. @@ -26,57 +26,52 @@ #define PAS_MBN_VERSION_6 6 -/* MBN header field offsets (bytes from hash-segment start) */ -#define MBN_OFF_VERSION 0x04 -#define MBN_OFF_QC_SIG_SIZE 0x08 -#define MBN_OFF_QC_CERT_SIZE 0x0c -#define MBN_OFF_CODE_SIZE 0x14 -#define MBN_OFF_OEM_SIG_SIZE 0x1c -#define MBN_OFF_OEM_CERT_SIZE 0x24 -#define MBN_OFF_QC_META_SIZE 0x28 -#define MBN_OFF_OEM_META_SIZE 0x2c - -#define MBN_HDR_SIZE_V6 0x30 - -struct pas_mbn { +struct pas_mbn_header_v6 { + uint32_t reserved0; uint32_t version; + uint32_t qc_signature_size; + uint32_t qc_cert_chain_size; + uint32_t image_size; + uint32_t code_size; + uint32_t reserved1; + uint32_t oem_signature_size; + uint32_t reserved2; + uint32_t oem_cert_chain_size; + uint32_t qc_metadata_size; + uint32_t oem_metadata_size; +}; +struct pas_hash_segment_info { + uint32_t version; const uint8_t *hash_table; size_t hash_table_size; uint32_t num_entries; uint32_t hash_len; - const uint8_t *signed_region; size_t signed_region_size; - const uint8_t *oem_meta; size_t oem_meta_size; const uint8_t *oem_sig; size_t oem_sig_size; const uint8_t *oem_certs; size_t oem_certs_size; - - const uint8_t *qti_meta; - size_t qti_meta_size; - const uint8_t *qti_sig; - size_t qti_sig_size; - const uint8_t *qti_certs; - size_t qti_certs_size; - + const uint8_t *qc_meta; + size_t qc_meta_size; + const uint8_t *qc_sig; + size_t qc_sig_size; + const uint8_t *qc_certs; + size_t qc_certs_size; bool uie_encrypted; }; -/* Read a little-endian uint32_t from @p. */ -uint32_t pas_mbn_read_u32(const uint8_t *p); - -TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, - const uint8_t **seg, size_t *seg_size); +TEE_Result pas_mbn_get_hash_segment(const uint8_t *md, size_t md_size, + const uint8_t **seg, size_t *seg_size); -TEE_Result pas_mbn_reserve_region(const uint8_t *segment, size_t segment_size, - size_t *offset, size_t len, - const uint8_t **region, size_t *region_len); +TEE_Result pas_mbn_get_region(const uint8_t *segment, size_t segment_size, + size_t *offset, size_t len, + const uint8_t **region, size_t *region_len); TEE_Result pas_mbn_parse(const uint8_t *md, size_t md_size, - uint32_t hash_len, struct pas_mbn *out); + uint32_t hash_len, struct pas_hash_segment_info *out); #endif /* __AUTH_PAS_MBN_H */ diff --git a/ta/qcom_pas/include/auth/pas_meta.h b/ta/qcom_pas/include/auth/pas_meta.h index 6423e9dddd5..176158835e8 100644 --- a/ta/qcom_pas/include/auth/pas_meta.h +++ b/ta/qcom_pas/include/auth/pas_meta.h @@ -12,15 +12,43 @@ #include #include +#define PAS_META_FLAG_IN_USE_SOC_HW_VERSION 1 +#define PAS_META_FLAG_USE_SERIAL_NUMBER 2 +#define PAS_META_FLAG_OEM_ID_INDEPENDENT 3 +#define PAS_META_FLAG_IN_USE_JTAG_ID 10 +#define PAS_META_FLAG_MODEL_ID_INDEPENDENT 11 + +#define PAS_META_FLAG_ROOT_REVOKE_ACTIVATE_SHIFT 4 +#define PAS_META_FLAG_UIE_KEY_SWITCH_SHIFT 6 +#define PAS_META_FLAG_DEBUG_SHIFT 8 +#define PAS_META_OPTION_MASK 3U +#define PAS_META_OPTION_MAX 2U +#define PAS_META_OPTION_ENABLE_SN 2U + struct pas_md_slot { void *meta_data; size_t meta_data_size; uint32_t pas_id; bool in_use; - struct pas_mbn mbn; + struct pas_hash_segment_info mbn; bool ready; }; +struct pas_oem_metadata { + uint32_t major; + uint32_t minor; + uint32_t sw_id; + uint32_t hw_id; + uint32_t oem_id; + uint32_t model_id; + uint32_t secondary_sw_id; + uint32_t flags; + uint32_t soc_vers[12]; + uint32_t serial_num[8]; + uint32_t root_cert_sel; + uint32_t anti_rollback; +}; + TEE_Result pas_meta_get_version(const uint8_t *meta_data, size_t meta_data_size, uint32_t *version); @@ -28,4 +56,20 @@ TEE_Result pas_meta_segment_hash_len(const uint8_t *meta_data, size_t meta_data_size, uint32_t *hash_len); +TEE_Result pas_meta_get_root_cert_sel(const uint8_t *meta_data, + size_t meta_data_size, + uint32_t *root_cert_sel); + +TEE_Result pas_meta_verify_elf_headers_hash(const uint8_t *meta_data, + size_t meta_data_size, + const uint8_t *hash_table, + uint32_t hash_len); + +TEE_Result pas_meta_get(const struct pas_hash_segment_info *hs, + struct pas_oem_metadata *meta); + +TEE_Result +pas_meta_get_signed_region_copy(const struct pas_hash_segment_info *hs, + uint8_t **out, size_t *out_len); + #endif /* __AUTH_PAS_META_H */ diff --git a/ta/qcom_pas/src/auth/pas_mbn.c b/ta/qcom_pas/src/auth/pas_mbn.c index 72cbdf2db71..ecff228daca 100644 --- a/ta/qcom_pas/src/auth/pas_mbn.c +++ b/ta/qcom_pas/src/auth/pas_mbn.c @@ -17,26 +17,18 @@ #define MBN_PT_FLAG_TYPE_MASK 0x07000000U #define MBN_PT_FLAG_HASH_TYPE_MASK 0x02000000U -uint32_t pas_mbn_read_u32(const uint8_t *p) +TEE_Result pas_mbn_get_hash_segment(const uint8_t *metadata, + size_t metadata_size, + const uint8_t **seg, size_t *seg_size) { - uint32_t v = 0; - - memcpy(&v, p, sizeof(v)); - - return v; -} - -TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, - const uint8_t **seg, size_t *seg_size) -{ - const unsigned char *ident = md; + const unsigned char *ident = metadata; size_t phdr_entry_len = 0; size_t phdr_entry_min = 0; size_t phdr_table_end = 0; size_t phdr_offset = 0; size_t phdr_count = 0; uint32_t hash_flags = 0; - size_t hash_seg_len = 0; + size_t hash_seg_size = 0; size_t elf_hdr_len = 0; size_t hash_seg_offset = 0; size_t hash_seg_end = 0; @@ -44,7 +36,7 @@ TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, bool found = false; size_t i = 0; - if (md_size < EI_NIDENT) + if (metadata_size < EI_NIDENT) return TEE_ERROR_BAD_FORMAT; if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 || @@ -66,17 +58,17 @@ TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, return TEE_ERROR_BAD_FORMAT; } - if (md_size < elf_hdr_len) + if (metadata_size < elf_hdr_len) return TEE_ERROR_BAD_FORMAT; if (is_64) { - const Elf64_Ehdr *ehdr = (const void *)md; + const Elf64_Ehdr *ehdr = (const void *)metadata; phdr_offset = ehdr->e_phoff; phdr_entry_len = ehdr->e_phentsize; phdr_count = ehdr->e_phnum; } else { - const Elf32_Ehdr *ehdr = (const void *)md; + const Elf32_Ehdr *ehdr = (const void *)metadata; phdr_offset = ehdr->e_phoff; phdr_entry_len = ehdr->e_phentsize; @@ -89,22 +81,22 @@ TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, if (MUL_OVERFLOW(phdr_entry_len, phdr_count, &phdr_table_end) || ADD_OVERFLOW(phdr_table_end, phdr_offset, &phdr_table_end) || - phdr_table_end > md_size) + phdr_table_end > metadata_size) return TEE_ERROR_BAD_FORMAT; for (i = 0; i < phdr_count; i++) { - const uint8_t *p = md + phdr_offset + i * phdr_entry_len; + const uint8_t *p = metadata + phdr_offset + i * phdr_entry_len; if (is_64) { const Elf64_Phdr *phdr = (const void *)p; hash_flags = phdr->p_flags; - hash_seg_len = phdr->p_filesz; + hash_seg_size = phdr->p_filesz; } else { const Elf32_Phdr *phdr = (const void *)p; hash_flags = phdr->p_flags; - hash_seg_len = phdr->p_filesz; + hash_seg_size = phdr->p_filesz; } if ((hash_flags & MBN_PT_FLAG_TYPE_MASK) == @@ -126,19 +118,19 @@ TEE_Result pas_mbn_locate(const uint8_t *md, size_t md_size, ADD_OVERFLOW(hash_seg_offset, elf_hdr_len, &hash_seg_offset)) return TEE_ERROR_BAD_FORMAT; - if (ADD_OVERFLOW(hash_seg_offset, hash_seg_len, &hash_seg_end) || - hash_seg_end > md_size || !hash_seg_len) + if (ADD_OVERFLOW(hash_seg_offset, hash_seg_size, &hash_seg_end) || + hash_seg_end > metadata_size || !hash_seg_size) return TEE_ERROR_BAD_FORMAT; - *seg = md + hash_seg_offset; - *seg_size = hash_seg_len; + *seg = metadata + hash_seg_offset; + *seg_size = hash_seg_size; return TEE_SUCCESS; } -TEE_Result pas_mbn_reserve_region(const uint8_t *segment, size_t segment_size, - size_t *offset, size_t len, - const uint8_t **region, size_t *region_len) +TEE_Result pas_mbn_get_region(const uint8_t *segment, size_t segment_size, + size_t *offset, size_t len, + const uint8_t **region, size_t *region_len) { if (!len) { *region = NULL; @@ -156,51 +148,54 @@ TEE_Result pas_mbn_reserve_region(const uint8_t *segment, size_t segment_size, return TEE_SUCCESS; } -TEE_Result pas_mbn_parse(const uint8_t *md, size_t md_size, - uint32_t hash_len, struct pas_mbn *out) +TEE_Result pas_mbn_parse(const uint8_t *metadata, size_t metadata_size, + uint32_t hash_len, struct pas_hash_segment_info *out) { + struct pas_mbn_header_v6 header = { }; TEE_Result res = TEE_ERROR_GENERIC; + const uint8_t *hash_seg = NULL; uint32_t oem_cert_size = 0; uint32_t oem_meta_size = 0; - const uint8_t *seg = NULL; uint32_t oem_sig_size = 0; uint32_t qc_cert_size = 0; uint32_t qc_meta_size = 0; + size_t hash_seg_size = 0; uint32_t qc_sig_size = 0; size_t signed_size = 0; uint32_t code_size = 0; uint32_t version = 0; size_t hdr_size = 0; - size_t seg_size = 0; size_t offset = 0; - if (!md || !md_size || !out || !hash_len) + if (!metadata || !metadata_size || !out || !hash_len) return TEE_ERROR_BAD_PARAMETERS; memset(out, 0, sizeof(*out)); - res = pas_mbn_locate(md, md_size, &seg, &seg_size); + res = pas_mbn_get_hash_segment(metadata, metadata_size, &hash_seg, + &hash_seg_size); if (res) return res; - if (seg_size < MBN_HDR_SIZE_V6) + if (hash_seg_size < sizeof(header)) return TEE_ERROR_BAD_FORMAT; - version = pas_mbn_read_u32(seg + MBN_OFF_VERSION); + memcpy(&header, hash_seg, sizeof(header)); + version = header.version; if (version != PAS_MBN_VERSION_6) { EMSG("PAS auth: unsupported MBN version %#"PRIx32, version); return TEE_ERROR_BAD_FORMAT; } - hdr_size = MBN_HDR_SIZE_V6; + hdr_size = sizeof(header); /* "code_size" is the hash-table length in bytes, not a code length. */ - code_size = pas_mbn_read_u32(seg + MBN_OFF_CODE_SIZE); - qc_sig_size = pas_mbn_read_u32(seg + MBN_OFF_QC_SIG_SIZE); - qc_cert_size = pas_mbn_read_u32(seg + MBN_OFF_QC_CERT_SIZE); - oem_sig_size = pas_mbn_read_u32(seg + MBN_OFF_OEM_SIG_SIZE); - oem_cert_size = pas_mbn_read_u32(seg + MBN_OFF_OEM_CERT_SIZE); - qc_meta_size = pas_mbn_read_u32(seg + MBN_OFF_QC_META_SIZE); - oem_meta_size = pas_mbn_read_u32(seg + MBN_OFF_OEM_META_SIZE); + code_size = header.code_size; + qc_sig_size = header.qc_signature_size; + qc_cert_size = header.qc_cert_chain_size; + oem_sig_size = header.oem_signature_size; + oem_cert_size = header.oem_cert_chain_size; + qc_meta_size = header.qc_metadata_size; + oem_meta_size = header.oem_metadata_size; if (!code_size || code_size % hash_len) return TEE_ERROR_BAD_FORMAT; @@ -212,54 +207,60 @@ TEE_Result pas_mbn_parse(const uint8_t *md, size_t md_size, * [qc_meta || oem_meta || hash table]. */ offset = hdr_size; - out->signed_region = seg; + out->signed_region = hash_seg; if (ADD_OVERFLOW(qc_meta_size, oem_meta_size, &signed_size) || ADD_OVERFLOW(signed_size, code_size, &signed_size) || ADD_OVERFLOW(signed_size, hdr_size, &signed_size)) return TEE_ERROR_BAD_FORMAT; - if (signed_size > seg_size) + if (signed_size > hash_seg_size) return TEE_ERROR_BAD_FORMAT; out->signed_region_size = signed_size; - res = pas_mbn_reserve_region(seg, seg_size, &offset, qc_meta_size, - &out->qti_meta, &out->qti_meta_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, qc_meta_size, + &out->qc_meta, &out->qc_meta_size); if (res) return res; - res = pas_mbn_reserve_region(seg, seg_size, &offset, oem_meta_size, - &out->oem_meta, &out->oem_meta_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, + oem_meta_size, &out->oem_meta, + &out->oem_meta_size); if (res) return res; - out->hash_table = seg + offset; + out->hash_table = hash_seg + offset; out->hash_table_size = code_size; out->hash_len = hash_len; out->num_entries = code_size / hash_len; offset += code_size; - res = pas_mbn_reserve_region(seg, seg_size, &offset, qc_sig_size, - &out->qti_sig, &out->qti_sig_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, qc_sig_size, + &out->qc_sig, &out->qc_sig_size); if (res) return res; - res = pas_mbn_reserve_region(seg, seg_size, &offset, qc_cert_size, - &out->qti_certs, &out->qti_certs_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, qc_cert_size, + &out->qc_certs, &out->qc_certs_size); if (res) return res; - res = pas_mbn_reserve_region(seg, seg_size, &offset, oem_sig_size, - &out->oem_sig, &out->oem_sig_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, oem_sig_size, + &out->oem_sig, &out->oem_sig_size); if (res) return res; - res = pas_mbn_reserve_region(seg, seg_size, &offset, oem_cert_size, - &out->oem_certs, &out->oem_certs_size); + res = pas_mbn_get_region(hash_seg, hash_seg_size, &offset, + oem_cert_size, &out->oem_certs, + &out->oem_certs_size); if (res) return res; out->version = version; - if (offset + sizeof(uint32_t) <= seg_size && - pas_mbn_read_u32(seg + offset) == UIE_ENC_PARAM_MAGIC) - out->uie_encrypted = true; + if (offset + sizeof(uint32_t) <= hash_seg_size) { + uint32_t uie_magic = 0; + + memcpy(&uie_magic, hash_seg + offset, sizeof(uie_magic)); + if (uie_magic == UIE_ENC_PARAM_MAGIC) + out->uie_encrypted = true; + } return TEE_SUCCESS; } diff --git a/ta/qcom_pas/src/auth/pas_meta.c b/ta/qcom_pas/src/auth/pas_meta.c index d8a3af905c5..61b9b60112e 100644 --- a/ta/qcom_pas/src/auth/pas_meta.c +++ b/ta/qcom_pas/src/auth/pas_meta.c @@ -5,8 +5,14 @@ #include #include +#include +#include +#include +#include +#include #include #include +#include TEE_Result pas_meta_get_version(const uint8_t *meta_data, size_t meta_data_size, uint32_t *version) @@ -18,14 +24,17 @@ TEE_Result pas_meta_get_version(const uint8_t *meta_data, if (!meta_data || !meta_data_size || !version) return TEE_ERROR_BAD_PARAMETERS; - res = pas_mbn_locate(meta_data, meta_data_size, &seg, &seg_size); + res = pas_mbn_get_hash_segment(meta_data, meta_data_size, &seg, + &seg_size); if (res) return res; - if (seg_size < MBN_OFF_VERSION + sizeof(uint32_t)) + if (seg_size < offsetof(struct pas_mbn_header_v6, version) + + sizeof(uint32_t)) return TEE_ERROR_BAD_FORMAT; - *version = pas_mbn_read_u32(seg + MBN_OFF_VERSION); + memcpy(version, seg + offsetof(struct pas_mbn_header_v6, version), + sizeof(*version)); return TEE_SUCCESS; } @@ -52,3 +61,217 @@ TEE_Result pas_meta_segment_hash_len(const uint8_t *meta_data, return TEE_ERROR_NOT_SUPPORTED; } } + +TEE_Result pas_meta_get_root_cert_sel(const uint8_t *meta_data, + size_t meta_data_size, + uint32_t *root_cert_sel) +{ + struct pas_mbn_header_v6 header = { }; + TEE_Result res = TEE_ERROR_GENERIC; + const uint8_t *oem_meta = NULL; + const uint8_t *qc_meta = NULL; + const uint8_t *segment = NULL; + size_t oem_metadata_size = 0; + size_t qc_metadata_size = 0; + size_t segment_size = 0; + uint32_t version = 0; + size_t hdr_size = 0; + size_t offset = 0; + + if (!meta_data || !meta_data_size || !root_cert_sel) + return TEE_ERROR_BAD_PARAMETERS; + + res = pas_mbn_get_hash_segment(meta_data, meta_data_size, &segment, + &segment_size); + if (res) + return res; + + if (segment_size < sizeof(header)) + return TEE_ERROR_BAD_FORMAT; + + memcpy(&header, segment, sizeof(header)); + version = header.version; + if (version != PAS_MBN_VERSION_6) { + EMSG("PAS auth: unsupported MBN version %#"PRIx32, version); + return TEE_ERROR_BAD_FORMAT; + } + + hdr_size = sizeof(header); + if (segment_size < hdr_size) + return TEE_ERROR_BAD_FORMAT; + + offset = hdr_size; + res = pas_mbn_get_region(segment, segment_size, &offset, + header.qc_metadata_size, &qc_meta, + &qc_metadata_size); + if (res) + return res; + + res = pas_mbn_get_region(segment, segment_size, &offset, + header.oem_metadata_size, &oem_meta, + &oem_metadata_size); + if (res) + return res; + + if (!oem_meta) + return TEE_ERROR_NO_DATA; + + if (oem_metadata_size < sizeof(struct pas_oem_metadata)) + return TEE_ERROR_BAD_FORMAT; + + memcpy(root_cert_sel, oem_meta + + offsetof(struct pas_oem_metadata, root_cert_sel), + sizeof(*root_cert_sel)); + + return TEE_SUCCESS; +} + +TEE_Result pas_meta_verify_elf_headers_hash(const uint8_t *meta_data, + size_t meta_data_size, + const uint8_t *hash_table, + uint32_t hash_len) +{ + uint8_t digest[TEE_SHA384_HASH_SIZE] = { }; + TEE_OperationHandle op = TEE_HANDLE_NULL; + TEE_Result res = TEE_ERROR_GENERIC; + size_t digest_len = sizeof(digest); + size_t phdr_entry_len = 0; + size_t elf_hdr_len = 0; + size_t phdr_count = 0; + size_t hdr_len = 0; + uint32_t algo = 0; + + if (!meta_data || !meta_data_size || !hash_table) + return TEE_ERROR_BAD_PARAMETERS; + + switch (hash_len) { + case TEE_SHA256_HASH_SIZE: + algo = TEE_ALG_SHA256; + break; + case TEE_SHA384_HASH_SIZE: + algo = TEE_ALG_SHA384; + break; + default: + return TEE_ERROR_NOT_SUPPORTED; + } + + if (meta_data_size < EI_NIDENT) + return TEE_ERROR_BAD_FORMAT; + + if (meta_data[EI_MAG0] != ELFMAG0 || meta_data[EI_MAG1] != ELFMAG1 || + meta_data[EI_MAG2] != ELFMAG2 || meta_data[EI_MAG3] != ELFMAG3) + return TEE_ERROR_BAD_FORMAT; + + if (meta_data[EI_CLASS] == ELFCLASS64) { + const Elf64_Ehdr *ehdr = (const void *)meta_data; + + if (meta_data_size < sizeof(*ehdr)) + return TEE_ERROR_BAD_FORMAT; + elf_hdr_len = ehdr->e_ehsize; + phdr_entry_len = ehdr->e_phentsize; + phdr_count = ehdr->e_phnum; + } else if (meta_data[EI_CLASS] == ELFCLASS32) { + const Elf32_Ehdr *ehdr = (const void *)meta_data; + + if (meta_data_size < sizeof(*ehdr)) + return TEE_ERROR_BAD_FORMAT; + elf_hdr_len = ehdr->e_ehsize; + phdr_entry_len = ehdr->e_phentsize; + phdr_count = ehdr->e_phnum; + } else { + return TEE_ERROR_BAD_FORMAT; + } + + if (MUL_OVERFLOW(phdr_entry_len, phdr_count, &hdr_len) || + ADD_OVERFLOW(hdr_len, elf_hdr_len, &hdr_len) || + hdr_len > meta_data_size) + return TEE_ERROR_BAD_FORMAT; + + res = TEE_AllocateOperation(&op, algo, TEE_MODE_DIGEST, 0); + if (res != TEE_SUCCESS) + return res; + + res = TEE_DigestDoFinal(op, meta_data, hdr_len, digest, &digest_len); + if (res != TEE_SUCCESS) + goto out; + + if (digest_len != hash_len || + consttime_memcmp(digest, hash_table, hash_len) != 0) + res = TEE_ERROR_SECURITY; + else + res = TEE_SUCCESS; +out: + TEE_FreeOperation(op); + memzero_explicit(digest, sizeof(digest)); + + return res; +} + +TEE_Result pas_meta_get(const struct pas_hash_segment_info *hs, + struct pas_oem_metadata *meta) +{ + const uint8_t *m = NULL; + + if (!hs || !meta) + return TEE_ERROR_BAD_PARAMETERS; + + if (!hs->oem_meta || !hs->oem_meta_size) + return TEE_ERROR_NO_DATA; + + if (hs->oem_meta_size < sizeof(struct pas_oem_metadata)) + return TEE_ERROR_BAD_FORMAT; + + m = hs->oem_meta; + memcpy(meta, m, sizeof(*meta)); + + return TEE_SUCCESS; +} + +static void mask_meta_block(uint8_t *copy, size_t copy_len, + const uint8_t *block, size_t block_len, + const uint8_t *base) +{ + size_t off = 0; + + if (!block || !block_len) + return; + + off = (size_t)(block - base); + if (off < copy_len && block_len <= copy_len - off) + memset(copy + off, 0, block_len); +} + +static void zero_field(uint8_t *copy, size_t copy_len, size_t off) +{ + if (off + sizeof(uint32_t) <= copy_len) + memset(copy + off, 0, sizeof(uint32_t)); +} + +TEE_Result +pas_meta_get_signed_region_copy(const struct pas_hash_segment_info *hs, + uint8_t **out, size_t *out_len) +{ + uint8_t *copy = NULL; + + if (!hs || !hs->signed_region || !hs->signed_region_size || !out || + !out_len) + return TEE_ERROR_BAD_PARAMETERS; + + copy = TEE_Malloc(hs->signed_region_size, TEE_MALLOC_FILL_ZERO); + if (!copy) + return TEE_ERROR_OUT_OF_MEMORY; + + memcpy(copy, hs->signed_region, hs->signed_region_size); + + zero_field(copy, hs->signed_region_size, + offsetof(struct pas_mbn_header_v6, qc_signature_size)); + zero_field(copy, hs->signed_region_size, + offsetof(struct pas_mbn_header_v6, qc_cert_chain_size)); + mask_meta_block(copy, hs->signed_region_size, hs->qc_meta, + hs->qc_meta_size, hs->signed_region); + + *out = copy; + *out_len = hs->signed_region_size; + + return TEE_SUCCESS; +} From f93f2504148b847081a8ca53ecaa858a26cf6a75 Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:35:00 +0530 Subject: [PATCH 6/8] ta: qcom_pas: add fuse-PTA session helper The signature-authentication path makes several fuse reads per image, so hold one fuse-PTA session per TA session rather than opening one per read, matching how the TA already holds its PAS PTA session. The helper applies no policy and fails closed: a read failure propagates instead of substituting a default, so an untrustworthy fuse value aborts authentication rather than silently weakening it. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- ta/qcom_pas/include/auth/pas_fuse.h | 63 ++++++++++ ta/qcom_pas/src/auth/pas_fuse.c | 174 ++++++++++++++++++++++++++++ ta/qcom_pas/src/auth/sub.mk | 2 +- 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 ta/qcom_pas/include/auth/pas_fuse.h create mode 100644 ta/qcom_pas/src/auth/pas_fuse.c diff --git a/ta/qcom_pas/include/auth/pas_fuse.h b/ta/qcom_pas/include/auth/pas_fuse.h new file mode 100644 index 00000000000..f334d31bdd5 --- /dev/null +++ b/ta/qcom_pas/include/auth/pas_fuse.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __AUTH_PAS_FUSE_H +#define __AUTH_PAS_FUSE_H + +#include +#include +#include + +struct pas_device_ids { + uint32_t oem_id; + uint32_t model_id; + uint32_t jtag_id; + uint32_t serial_num; +}; + +struct pas_fuse_hw_binding_info { + struct pas_device_ids ids; + bool use_serial_num_override; +}; + +enum pas_secboot_state { + PAS_SECBOOT_OFF, + PAS_SECBOOT_ON, + PAS_SECBOOT_UNKNOWN, +}; + +#ifdef CFG_QCOM_PAS_AUTH +TEE_Result pas_fuse_open(void); + +void pas_fuse_close(void); +#else +static inline TEE_Result pas_fuse_open(void) +{ + return TEE_SUCCESS; +} + +static inline void pas_fuse_close(void) +{ +} +#endif /* CFG_QCOM_PAS_AUTH */ + +TEE_Result pas_fuse_get_secboot_state(enum pas_secboot_state *state); + +TEE_Result pas_fuse_get_root_anchor(uint8_t *anchor); + +TEE_Result pas_fuse_get_hw_binding_info(struct pas_fuse_hw_binding_info *info); + +TEE_Result pas_fuse_get_soc_hw_version(uint32_t *fam_dev); + +TEE_Result pas_fuse_get_eku_enforcement_en(bool *eku_enforced); + +/* + * Segment/hash-table digest size for @root_cert_sel; unrelated to + * cert-chain or root-of-trust hashing. + */ +TEE_Result pas_fuse_get_segment_hash_len(uint32_t root_cert_sel, + uint32_t *hash_len); + +#endif /* __AUTH_PAS_FUSE_H */ diff --git a/ta/qcom_pas/src/auth/pas_fuse.c b/ta/qcom_pas/src/auth/pas_fuse.c new file mode 100644 index 00000000000..06e410e29aa --- /dev/null +++ b/ta/qcom_pas/src/auth/pas_fuse.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +static TEE_TASessionHandle fuse_session; + +TEE_Result pas_fuse_open(void) +{ + static const TEE_UUID fuse_uuid = PTA_QCOM_FUSE_UUID; + TEE_Result res = TEE_ERROR_GENERIC; + + if (fuse_session != TEE_HANDLE_NULL) + return TEE_SUCCESS; + + res = TEE_OpenTASession(&fuse_uuid, TEE_TIMEOUT_INFINITE, 0, NULL, + &fuse_session, NULL); + if (res) + EMSG("PAS fuse: cannot open fuse PTA: %#"PRIx32, res); + + return res; +} + +void pas_fuse_close(void) +{ + if (fuse_session == TEE_HANDLE_NULL) + return; + + TEE_CloseTASession(fuse_session); + fuse_session = TEE_HANDLE_NULL; +} + +static TEE_Result fuse_pta_invoke(uint32_t cmd, uint32_t param_types, + TEE_Param params[TEE_NUM_PARAMS]) +{ + if (fuse_session == TEE_HANDLE_NULL) + return TEE_ERROR_BAD_STATE; + + return TEE_InvokeTACommand(fuse_session, TEE_TIMEOUT_INFINITE, cmd, + param_types, params, NULL); +} + +TEE_Result pas_fuse_get_secboot_state(enum pas_secboot_state *state) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_SECBOOT_STATE, pt, params); + if (res) { + EMSG("PAS fuse: cannot read secboot state: %#"PRIx32, res); + return res; + } + *state = params[0].value.a ? PAS_SECBOOT_ON : PAS_SECBOOT_OFF; + + return TEE_SUCCESS; +} + +TEE_Result pas_fuse_get_root_anchor(uint8_t *anchor) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + params[0].memref.buffer = anchor; + params[0].memref.size = PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE; + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_ROOT_OF_TRUST, pt, params); + if (res) + EMSG("PAS fuse: cannot read root of trust: %#"PRIx32, res); + + return res; +} + +TEE_Result pas_fuse_get_hw_binding_info(struct pas_fuse_hw_binding_info *info) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, + TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_DEVICE_IDS, pt, params); + if (res) { + EMSG("PAS fuse: cannot read device ids: %#"PRIx32, res); + return res; + } + info->ids.oem_id = params[0].value.a; + info->ids.model_id = params[0].value.b; + info->ids.jtag_id = params[1].value.a; + info->ids.serial_num = params[1].value.b; + + memset(params, 0, sizeof(params)); + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_USE_SERIAL_NUM, pt, params); + if (res) { + EMSG("PAS fuse: cannot read USE_SERIAL_NUM fuse: %#"PRIx32, + res); + return res; + } + info->use_serial_num_override = params[0].value.a; + + return TEE_SUCCESS; +} + +TEE_Result pas_fuse_get_soc_hw_version(uint32_t *fam_dev) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_SOC_HW_VERSION, pt, params); + if (res) { + EMSG("PAS fuse: cannot read SOC_HW_VERSION: %#"PRIx32, res); + return res; + } + + *fam_dev = params[0].value.a; + + return TEE_SUCCESS; +} + +TEE_Result pas_fuse_get_eku_enforcement_en(bool *eku_enforced) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_OUTPUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_EKU_ENFORCEMENT_EN, pt, params); + if (res) { + EMSG("PAS fuse: cannot read EKU enforcement fuse: %#"PRIx32, + res); + return res; + } + + *eku_enforced = params[0].value.a; + + return TEE_SUCCESS; +} + +TEE_Result pas_fuse_get_segment_hash_len(uint32_t root_cert_sel, + uint32_t *hash_len) +{ + TEE_Param params[TEE_NUM_PARAMS] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t pt = 0; + + params[0].value.a = root_cert_sel; + pt = TEE_PARAM_TYPES(TEE_PARAM_TYPE_VALUE_INOUT, TEE_PARAM_TYPE_NONE, + TEE_PARAM_TYPE_NONE, TEE_PARAM_TYPE_NONE); + res = fuse_pta_invoke(PTA_QCOM_FUSE_GET_SEGMENT_HASH_LEN, pt, params); + if (res) { + EMSG("PAS fuse: cannot read segment hash size: %#"PRIx32, res); + return res; + } + + *hash_len = params[0].value.b; + + return TEE_SUCCESS; +} diff --git a/ta/qcom_pas/src/auth/sub.mk b/ta/qcom_pas/src/auth/sub.mk index 29db1a53d6b..d6905c1d341 100644 --- a/ta/qcom_pas/src/auth/sub.mk +++ b/ta/qcom_pas/src/auth/sub.mk @@ -1 +1 @@ -srcs-y += pas_auth.c pas_mbn.c pas_meta.c +srcs-y += pas_auth.c pas_fuse.c pas_mbn.c pas_meta.c From d563d160be49b5060bbe69cb61f2090199706c36 Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:36:00 +0530 Subject: [PATCH 7/8] ta: qcom_pas: verify image signature and device bindings Segment-hash verification proves an image matches its own digest table; it cannot prove who signed it or that it is permitted on this device, so establish provenance before the peripheral leaves reset. The fuse read selecting this path fails closed: an error means secure boot enabled, so a transient failure cannot downgrade a fused board to hash-only verification. UIE-encrypted and QC-countersigned images are refused, as neither is implemented here, and the chain is limited to one ECDSA P-384 root. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- ta/qcom_pas/include/auth/pas_auth.h | 7 +- ta/qcom_pas/include/auth/pas_meta.h | 7 - ta/qcom_pas/include/auth/pas_policy.h | 27 + ta/qcom_pas/include/auth/pas_sig.h | 43 ++ ta/qcom_pas/include/auth/pas_sig_auth.h | 32 ++ .../src/auth/lemans/pas_policy_platform.h | 22 + ta/qcom_pas/src/auth/pas_auth.c | 43 +- ta/qcom_pas/src/auth/pas_meta.c | 48 -- ta/qcom_pas/src/auth/pas_policy.c | 26 + ta/qcom_pas/src/auth/pas_sig.c | 517 ++++++++++++++++++ ta/qcom_pas/src/auth/pas_sig_auth.c | 360 ++++++++++++ ta/qcom_pas/src/auth/sub.mk | 10 +- ta/qcom_pas/src/qcom_pas.c | 14 +- ta/qcom_pas/src/user_ta_header_defines.h | 12 +- ta/qcom_pas/user_ta.mk | 2 +- 15 files changed, 1095 insertions(+), 75 deletions(-) create mode 100644 ta/qcom_pas/include/auth/pas_policy.h create mode 100644 ta/qcom_pas/include/auth/pas_sig.h create mode 100644 ta/qcom_pas/include/auth/pas_sig_auth.h create mode 100644 ta/qcom_pas/src/auth/lemans/pas_policy_platform.h create mode 100644 ta/qcom_pas/src/auth/pas_policy.c create mode 100644 ta/qcom_pas/src/auth/pas_sig.c create mode 100644 ta/qcom_pas/src/auth/pas_sig_auth.c diff --git a/ta/qcom_pas/include/auth/pas_auth.h b/ta/qcom_pas/include/auth/pas_auth.h index 1f5c88db14c..88685df6966 100644 --- a/ta/qcom_pas/include/auth/pas_auth.h +++ b/ta/qcom_pas/include/auth/pas_auth.h @@ -19,7 +19,8 @@ struct qcom_pas_session { TEE_Result pas_auth_save_metadata(struct qcom_pas_session *s, uint32_t pt, TEE_Param params[TEE_NUM_PARAMS]); -TEE_Result pas_auth_prepare(struct qcom_pas_session *s, uint32_t pas_id); +TEE_Result pas_auth_prepare_and_authenticate(struct qcom_pas_session *s, + uint32_t pas_id); TEE_Result pas_auth_verify(struct qcom_pas_session *s, TEE_TASessionHandle pta_session, @@ -38,8 +39,8 @@ pas_auth_save_metadata(struct qcom_pas_session *s __unused, } static inline TEE_Result -pas_auth_prepare(struct qcom_pas_session *s __unused, - uint32_t pas_id __unused) +pas_auth_prepare_and_authenticate(struct qcom_pas_session *s __unused, + uint32_t pas_id __unused) { return TEE_SUCCESS; } diff --git a/ta/qcom_pas/include/auth/pas_meta.h b/ta/qcom_pas/include/auth/pas_meta.h index 176158835e8..96dd55ac95f 100644 --- a/ta/qcom_pas/include/auth/pas_meta.h +++ b/ta/qcom_pas/include/auth/pas_meta.h @@ -49,13 +49,6 @@ struct pas_oem_metadata { uint32_t anti_rollback; }; -TEE_Result pas_meta_get_version(const uint8_t *meta_data, - size_t meta_data_size, uint32_t *version); - -TEE_Result pas_meta_segment_hash_len(const uint8_t *meta_data, - size_t meta_data_size, - uint32_t *hash_len); - TEE_Result pas_meta_get_root_cert_sel(const uint8_t *meta_data, size_t meta_data_size, uint32_t *root_cert_sel); diff --git a/ta/qcom_pas/include/auth/pas_policy.h b/ta/qcom_pas/include/auth/pas_policy.h new file mode 100644 index 00000000000..bcf2f6ea6c6 --- /dev/null +++ b/ta/qcom_pas/include/auth/pas_policy.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __AUTH_PAS_POLICY_H +#define __AUTH_PAS_POLICY_H + +#include +#include + +#define SECBOOT_ADSP_SW_TYPE 0x04 +#define SECBOOT_IRIS_SW_TYPE 0x0E +#define SECBOOT_TURING_SW_TYPE 0x17 +#define SECBOOT_CAMERA_SW_TYPE 0x34 +#define SECBOOT_TURING1_SW_TYPE 0x44 +#define SECBOOT_GPDSP0_SW_TYPE 0x58 +#define SECBOOT_GPDSP1_SW_TYPE 0x5A + +struct pas_swid_entry { + uint32_t pas_id; + uint32_t swid; +}; + +TEE_Result pas_policy_expected_swid(uint32_t pas_id, uint32_t *swid); + +#endif /* __AUTH_PAS_POLICY_H */ diff --git a/ta/qcom_pas/include/auth/pas_sig.h b/ta/qcom_pas/include/auth/pas_sig.h new file mode 100644 index 00000000000..c35a7cd4af3 --- /dev/null +++ b/ta/qcom_pas/include/auth/pas_sig.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __AUTH_PAS_SIG_H +#define __AUTH_PAS_SIG_H + +#include +#include +#include +#include + +#define PAS_SIG_MAX_HASH_SIZE 48U +#define PAS_SIG_MAX_SIG_SIZE 512U + +TEE_Result pas_sig_verify_cert_chain(const uint8_t *chain_der, + size_t chain_der_len, bool eku_enforced, + uint32_t num_roots, + uint32_t root_cert_sel, + const uint8_t **leaf_der, + size_t *leaf_der_len, + const uint8_t **roots_der, + size_t *roots_der_len); + +TEE_Result pas_sig_check_root_of_trust(uint32_t rot_hash_algo, + size_t rot_hash_len, + const uint8_t *root_der, + size_t root_der_len, + const uint8_t *expected); + +TEE_Result pas_sig_algo_from_leaf(const uint8_t *leaf_der, + size_t leaf_der_len, uint32_t *sig_algo, + uint32_t *sig_hash_algo); + +TEE_Result pas_sig_verify_signature(uint32_t sig_algo, + uint32_t sig_hash_algo, + const uint8_t *leaf_der, + size_t leaf_der_len, + const uint8_t *msg, size_t msg_len, + const uint8_t *sig, size_t sig_len); + +#endif /* __AUTH_PAS_SIG_H */ diff --git a/ta/qcom_pas/include/auth/pas_sig_auth.h b/ta/qcom_pas/include/auth/pas_sig_auth.h new file mode 100644 index 00000000000..9c076f5d189 --- /dev/null +++ b/ta/qcom_pas/include/auth/pas_sig_auth.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __AUTH_PAS_SIG_AUTH_H +#define __AUTH_PAS_SIG_AUTH_H + +#include + +#include "pas_mbn.h" +#include "pas_meta.h" + +/* + * Pick the firmware-segment hash-table digest size for @slot's signed + * root cert, so the caller can size the hash table before parsing it. + */ +TEE_Result pas_sig_auth_segment_hash_len(const struct pas_md_slot *slot, + uint32_t *segment_hash_len); + +/* + * Authenticate @hs's signing chain, root-of-trust, and device bindings, + * then check the metadata against its ELF-header hash-table entry. + */ +TEE_Result pas_sig_auth_verify_image(const struct pas_hash_segment_info *hs, + const uint8_t *meta_data, + size_t meta_data_size, + uint32_t pas_id, + uint32_t segment_hash_len, + const uint8_t *anchor); + +#endif /* __AUTH_PAS_SIG_AUTH_H */ diff --git a/ta/qcom_pas/src/auth/lemans/pas_policy_platform.h b/ta/qcom_pas/src/auth/lemans/pas_policy_platform.h new file mode 100644 index 00000000000..5b7f47b9055 --- /dev/null +++ b/ta/qcom_pas/src/auth/lemans/pas_policy_platform.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef __PAS_POLICY_PLATFORM_H +#define __PAS_POLICY_PLATFORM_H + +#include +#include + +static const struct pas_swid_entry pas_swid_map[] = { + { PAS_ID_QDSP6, SECBOOT_ADSP_SW_TYPE }, + { PAS_ID_IRIS, SECBOOT_IRIS_SW_TYPE }, + { PAS_ID_TURING, SECBOOT_TURING_SW_TYPE }, + { PAS_ID_TURING1, SECBOOT_TURING1_SW_TYPE }, + { PAS_ID_CAMERA, SECBOOT_CAMERA_SW_TYPE }, + { PAS_ID_GPDSP0, SECBOOT_GPDSP0_SW_TYPE }, + { PAS_ID_GPDSP1, SECBOOT_GPDSP1_SW_TYPE }, +}; + +#endif /* __PAS_POLICY_PLATFORM_H */ diff --git a/ta/qcom_pas/src/auth/pas_auth.c b/ta/qcom_pas/src/auth/pas_auth.c index 38c23134801..7bacaa399d6 100644 --- a/ta/qcom_pas/src/auth/pas_auth.c +++ b/ta/qcom_pas/src/auth/pas_auth.c @@ -4,10 +4,14 @@ */ #include +#include #include #include +#include +#include #include #include +#include #include #include @@ -77,11 +81,14 @@ TEE_Result pas_auth_save_metadata(struct qcom_pas_session *s, uint32_t pt, return TEE_SUCCESS; } -TEE_Result pas_auth_prepare(struct qcom_pas_session *s, uint32_t pas_id) +TEE_Result pas_auth_prepare_and_authenticate(struct qcom_pas_session *s, + uint32_t pas_id) { struct pas_md_slot *slot = get_meta_data_slot(s, pas_id); + uint8_t anchor[PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE] = { }; TEE_Result res = TEE_ERROR_GENERIC; - uint32_t hash_len = 0; + uint32_t segment_hash_len = 0; + enum pas_secboot_state secboot_state = PAS_SECBOOT_UNKNOWN; if (!slot) { EMSG("PAS auth: no metadata for pas_id=%#"PRIx32 @@ -89,23 +96,41 @@ TEE_Result pas_auth_prepare(struct qcom_pas_session *s, uint32_t pas_id) return TEE_ERROR_BAD_STATE; } - res = pas_meta_segment_hash_len(slot->meta_data, slot->meta_data_size, - &hash_len); + res = pas_fuse_get_secboot_state(&secboot_state); + if (res) + secboot_state = PAS_SECBOOT_UNKNOWN; + + res = pas_sig_auth_segment_hash_len(slot, &segment_hash_len); if (res) { EMSG("PAS auth: cannot pick hash size: %#"PRIx32, res); - return res; + goto out; } - res = pas_mbn_parse(slot->meta_data, slot->meta_data_size, hash_len, - &slot->mbn); + res = pas_mbn_parse(slot->meta_data, slot->meta_data_size, + segment_hash_len, &slot->mbn); if (res) { EMSG("PAS auth: MBN parse failed: %#"PRIx32, res); - return res; + goto out; + } + + if (secboot_state != PAS_SECBOOT_OFF) { + res = pas_fuse_get_root_anchor(anchor); + if (res) + goto out; + + res = pas_sig_auth_verify_image(&slot->mbn, slot->meta_data, + slot->meta_data_size, pas_id, + segment_hash_len, anchor); + if (res) + goto out; } slot->ready = true; + res = TEE_SUCCESS; +out: + memzero_explicit(anchor, sizeof(anchor)); - return TEE_SUCCESS; + return res; } TEE_Result pas_auth_verify(struct qcom_pas_session *s, diff --git a/ta/qcom_pas/src/auth/pas_meta.c b/ta/qcom_pas/src/auth/pas_meta.c index 61b9b60112e..c89978ff610 100644 --- a/ta/qcom_pas/src/auth/pas_meta.c +++ b/ta/qcom_pas/src/auth/pas_meta.c @@ -14,54 +14,6 @@ #include #include -TEE_Result pas_meta_get_version(const uint8_t *meta_data, - size_t meta_data_size, uint32_t *version) -{ - TEE_Result res = TEE_ERROR_GENERIC; - const uint8_t *seg = NULL; - size_t seg_size = 0; - - if (!meta_data || !meta_data_size || !version) - return TEE_ERROR_BAD_PARAMETERS; - - res = pas_mbn_get_hash_segment(meta_data, meta_data_size, &seg, - &seg_size); - if (res) - return res; - - if (seg_size < offsetof(struct pas_mbn_header_v6, version) + - sizeof(uint32_t)) - return TEE_ERROR_BAD_FORMAT; - - memcpy(version, seg + offsetof(struct pas_mbn_header_v6, version), - sizeof(*version)); - - return TEE_SUCCESS; -} - -TEE_Result pas_meta_segment_hash_len(const uint8_t *meta_data, - size_t meta_data_size, uint32_t *hash_len) -{ - TEE_Result res = TEE_ERROR_GENERIC; - uint32_t version = 0; - - if (!hash_len) - return TEE_ERROR_BAD_PARAMETERS; - - res = pas_meta_get_version(meta_data, meta_data_size, &version); - if (res) - return res; - - switch (version) { - case PAS_MBN_VERSION_6: - *hash_len = TEE_SHA384_HASH_SIZE; - return TEE_SUCCESS; - default: - EMSG("PAS auth: unsupported MBN version %#"PRIx32, version); - return TEE_ERROR_NOT_SUPPORTED; - } -} - TEE_Result pas_meta_get_root_cert_sel(const uint8_t *meta_data, size_t meta_data_size, uint32_t *root_cert_sel) diff --git a/ta/qcom_pas/src/auth/pas_policy.c b/ta/qcom_pas/src/auth/pas_policy.c new file mode 100644 index 00000000000..7e3a041dd16 --- /dev/null +++ b/ta/qcom_pas/src/auth/pas_policy.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include + +TEE_Result pas_policy_expected_swid(uint32_t pas_id, uint32_t *swid) +{ + size_t i = 0; + + if (!swid) + return TEE_ERROR_BAD_PARAMETERS; + + for (i = 0; i < ARRAY_SIZE(pas_swid_map); i++) { + if (pas_swid_map[i].pas_id == pas_id) { + *swid = pas_swid_map[i].swid; + return TEE_SUCCESS; + } + } + + return TEE_ERROR_NOT_SUPPORTED; +} diff --git a/ta/qcom_pas/src/auth/pas_sig.c b/ta/qcom_pas/src/auth/pas_sig.c new file mode 100644 index 00000000000..5c4f5884a30 --- /dev/null +++ b/ta/qcom_pas/src/auth/pas_sig.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DER_SEQUENCE_TAG \ + (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) + +static TEE_Result md_from_tee(uint32_t hash_algo, mbedtls_md_type_t *md) +{ + switch (hash_algo) { + case TEE_ALG_SHA256: + *md = MBEDTLS_MD_SHA256; + return TEE_SUCCESS; + case TEE_ALG_SHA384: + *md = MBEDTLS_MD_SHA384; + return TEE_SUCCESS; + default: + return TEE_ERROR_NOT_SUPPORTED; + } +} + +static TEE_Result digest(uint32_t hash_algo, const uint8_t *msg, size_t msg_len, + uint8_t *out, size_t *out_len) +{ + TEE_OperationHandle op = TEE_HANDLE_NULL; + TEE_Result res = TEE_ERROR_GENERIC; + + res = TEE_AllocateOperation(&op, hash_algo, TEE_MODE_DIGEST, 0); + if (res != TEE_SUCCESS) + return res; + + res = TEE_DigestDoFinal(op, msg, msg_len, out, out_len); + + TEE_FreeOperation(op); + + return res; +} + +static const mbedtls_x509_crt_profile pas_crt_profile = { + .allowed_mds = MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384), + .allowed_pks = MBEDTLS_X509_ID_FLAG(MBEDTLS_PK_ECDSA) | + MBEDTLS_X509_ID_FLAG(MBEDTLS_PK_ECKEY), + .allowed_curves = MBEDTLS_X509_ID_FLAG(MBEDTLS_ECP_DP_SECP384R1), +}; + +/* mbedTLS treats an absent EKU extension as unrestricted; require it. */ +static TEE_Result check_eku(const mbedtls_x509_crt *leaf, bool enforced) +{ + size_t oid_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CODE_SIGNING); + int ext = MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE; + + if (!enforced) + return TEE_SUCCESS; + + if (!mbedtls_x509_crt_has_ext_type(leaf, ext)) { + EMSG("PAS auth: leaf cert has no EKU extension"); + return TEE_ERROR_SECURITY; + } + + if (mbedtls_x509_crt_check_extended_key_usage(leaf, + MBEDTLS_OID_CODE_SIGNING, + oid_len)) { + EMSG("PAS auth: leaf cert missing code-signing EKU"); + return TEE_ERROR_SECURITY; + } + + return TEE_SUCCESS; +} + +/* mbedTLS treats an absent KeyUsage extension as unrestricted; require it. */ +static TEE_Result check_chain_constraints(const mbedtls_x509_crt *leaf, + bool eku_enforced) +{ + uint32_t ku = MBEDTLS_X509_KU_DIGITAL_SIGNATURE; + const mbedtls_x509_crt *crt = NULL; + TEE_Result res = TEE_ERROR_GENERIC; + size_t depth = 0; + + if (!mbedtls_x509_crt_has_ext_type(leaf, MBEDTLS_X509_EXT_KEY_USAGE)) { + EMSG("PAS auth: leaf cert has no KeyUsage extension"); + return TEE_ERROR_SECURITY; + } + + if (mbedtls_x509_crt_check_key_usage(leaf, ku)) { + EMSG("PAS auth: leaf cert missing digitalSignature KeyUsage"); + return TEE_ERROR_SECURITY; + } + + res = check_eku(leaf, eku_enforced); + if (res) + return res; + + for (crt = leaf; crt; crt = crt->next, depth++) { + int ca = mbedtls_x509_crt_get_ca_istrue(crt); + + if (ca < 0) { + EMSG("PAS auth: cannot read CA flag at depth %zu", + depth); + return TEE_ERROR_SECURITY; + } + + if (crt == leaf) { + if (ca) { + EMSG("PAS auth: leaf cert asserts CA"); + return TEE_ERROR_SECURITY; + } + continue; + } + + if (!ca) { + EMSG("PAS auth: issuer at depth %zu is not a CA", + depth); + return TEE_ERROR_SECURITY; + } + } + + return TEE_SUCCESS; +} + +static bool asn1_buf_eq(const mbedtls_x509_buf *a, const mbedtls_x509_buf *b) +{ + return a->len && a->len == b->len && !memcmp(a->p, b->p, a->len); +} + +static TEE_Result check_issuer_linkage(const mbedtls_x509_crt *issuer, + const mbedtls_x509_crt *subject) +{ + const mbedtls_x509_authority *akid = &subject->authority_key_id; + + if (akid->keyIdentifier.len && + issuer->subject_key_id.len && + !asn1_buf_eq(&akid->keyIdentifier, &issuer->subject_key_id)) { + EMSG("PAS auth: AKID/SKID mismatch in cert chain"); + return TEE_ERROR_SECURITY; + } + + if (akid->authorityCertSerialNumber.len && + !asn1_buf_eq(&akid->authorityCertSerialNumber, &issuer->serial)) { + EMSG("PAS auth: AKID serial mismatch in cert chain"); + return TEE_ERROR_SECURITY; + } + + return TEE_SUCCESS; +} + +/* mbedTLS's cert profile can't express exact algo pairings; gate them here. */ +static TEE_Result check_sig_algo(const mbedtls_x509_crt *crt, + mbedtls_pk_type_t *pk_out, + mbedtls_md_type_t *md_out) +{ + mbedtls_pk_type_t pk = MBEDTLS_PK_NONE; + mbedtls_md_type_t md = MBEDTLS_MD_NONE; + + if (mbedtls_oid_get_sig_alg(&crt->sig_oid, &md, &pk)) + return TEE_ERROR_SECURITY; + + if (pk != MBEDTLS_PK_ECDSA || md != MBEDTLS_MD_SHA384) + return TEE_ERROR_SECURITY; + + if (pk_out) + *pk_out = pk; + if (md_out) + *md_out = md; + + return TEE_SUCCESS; +} + +static TEE_Result check_chain_sig_algos(const mbedtls_x509_crt *leaf, + size_t num_prefix) +{ + const mbedtls_x509_crt *crt = NULL; + size_t i = 0; + + for (crt = leaf->next, i = 1; crt && i < num_prefix; + crt = crt->next, i++) { + if (check_sig_algo(crt, NULL, NULL)) { + EMSG("PAS auth: unsupported sig algo at depth %zu", i); + return TEE_ERROR_SECURITY; + } + } + + return TEE_SUCCESS; +} + +#define PAS_MIN_NUM_CERTS 2U +#define PAS_MAX_CERT_CHAIN_LEVEL 3U +#define PAS_MAX_NUM_ROOT_CERTS 4U +#define PAS_TOTAL_MAX_CERTS (PAS_MAX_NUM_ROOT_CERTS + \ + PAS_MAX_CERT_CHAIN_LEVEL - 1) + +TEE_Result pas_sig_verify_cert_chain(const uint8_t *chain_der, + size_t chain_der_len, bool eku_enforced, + uint32_t num_roots, + uint32_t root_cert_sel, + const uint8_t **leaf_der, + size_t *leaf_der_len, + const uint8_t **roots_der, + size_t *roots_der_len) +{ + TEE_Result res = TEE_ERROR_SECURITY; + const mbedtls_x509_crt *crt = NULL; + mbedtls_x509_crt *sel_root = NULL; + mbedtls_x509_crt *leaf = NULL; + mbedtls_x509_crt chain = { }; + mbedtls_x509_crt trust = { }; + size_t num_prefix = 0; + size_t sel_index = 0; + size_t roots_off = 0; + size_t num_certs = 0; + uint32_t flags = 0; + size_t off = 0; + size_t i = 0; + int rc = 0; + + if (!chain_der || !chain_der_len || !leaf_der || !leaf_der_len || + !num_roots || num_roots > PAS_MAX_NUM_ROOT_CERTS || + root_cert_sel >= num_roots) + return TEE_ERROR_BAD_PARAMETERS; + + mbedtls_x509_crt_init(&chain); + mbedtls_x509_crt_init(&trust); + + while (off < chain_der_len && chain_der[off] == DER_SEQUENCE_TAG) { + mbedtls_x509_crt *added = NULL; + + if (num_certs >= PAS_TOTAL_MAX_CERTS) + break; + + if (mbedtls_x509_crt_parse_der(&chain, chain_der + off, + chain_der_len - off)) { + EMSG("PAS auth: cert %zu parse failed", num_certs); + goto out; + } + + added = &chain; + while (added->next) + added = added->next; + + off += added->raw.len; + num_certs++; + } + + for (i = off; i < chain_der_len; i++) { + if (chain_der[i] != 0xFF) { + EMSG("PAS auth: non-0xFF byte at chain offset %zu", i); + goto out; + } + } + + if (num_certs <= num_roots) { + EMSG("PAS auth: chain has %zu certs, need > %#"PRIx32" roots", + num_certs, num_roots); + goto out; + } + num_prefix = num_certs - num_roots; + if (num_prefix < (PAS_MIN_NUM_CERTS - 1) || + num_prefix > (PAS_MAX_CERT_CHAIN_LEVEL - 1)) { + EMSG("PAS auth: chain has %zu non-root certs, want [%u, %u]", + num_prefix, PAS_MIN_NUM_CERTS - 1, + PAS_MAX_CERT_CHAIN_LEVEL - 1); + goto out; + } + + leaf = &chain; + + sel_index = num_prefix + root_cert_sel; + for (crt = &chain, i = 0; crt; crt = crt->next, i++) { + if (i < num_prefix) + roots_off += crt->raw.len; + if (i == sel_index) + sel_root = (mbedtls_x509_crt *)crt; + } + if (!sel_root) { + EMSG("PAS auth: selected root %zu not present", sel_index); + goto out; + } + + if (mbedtls_x509_crt_parse_der(&trust, sel_root->raw.p, + sel_root->raw.len)) { + EMSG("PAS auth: root cert re-parse failed"); + goto out; + } + + rc = mbedtls_x509_crt_verify_with_profile(leaf, &trust, NULL, + &pas_crt_profile, NULL, + &flags, NULL, NULL); + /* + * No trusted time: tolerate a failure whose only cause is an + * expired/future cert. A zero flags with a nonzero rc means + * verification did not run to completion for an unrelated reason + * (e.g. a parse or allocation failure), which must still be fatal. + */ + if (rc && (!flags || + (flags & ~(uint32_t)(MBEDTLS_X509_BADCERT_EXPIRED | + MBEDTLS_X509_BADCERT_FUTURE)))) { + EMSG("PAS auth: cert chain verify failed (%#"PRIx32")", flags); + goto out; + } + + res = check_chain_constraints(leaf, eku_enforced); + if (res) + goto out; + + res = check_chain_sig_algos(leaf, num_prefix); + if (res) + goto out; + + crt = leaf; + for (i = 0; i + 1 < num_prefix; i++) { + res = check_issuer_linkage(crt->next, crt); + if (res) + goto out; + crt = crt->next; + } + res = check_issuer_linkage(sel_root, crt); + if (res) + goto out; + + if (roots_off > off || off > chain_der_len || + leaf->raw.len > chain_der_len) { + EMSG("PAS auth: cert DER length exceeds chain buffer"); + res = TEE_ERROR_SECURITY; + goto out; + } + + *leaf_der = chain_der; + *leaf_der_len = leaf->raw.len; + if (roots_der) + *roots_der = chain_der + roots_off; + if (roots_der_len) + *roots_der_len = off - roots_off; + + res = TEE_SUCCESS; +out: + mbedtls_x509_crt_free(&trust); + mbedtls_x509_crt_free(&chain); + + return res; +} + +static TEE_Result pas_sig_verify_hash(uint32_t hash_algo, + const uint8_t *data, size_t data_len, + const uint8_t *expected, + size_t hash_len) +{ + uint8_t digest_buf[PAS_SIG_MAX_HASH_SIZE] = { }; + TEE_Result res = TEE_ERROR_GENERIC; + size_t digest_buf_len = sizeof(digest_buf); + + if (!data || !expected || !hash_len || hash_len > digest_buf_len) + return TEE_ERROR_BAD_PARAMETERS; + + res = digest(hash_algo, data, data_len, digest_buf, &digest_buf_len); + if (res != TEE_SUCCESS) + goto out; + + if (digest_buf_len != hash_len) { + res = TEE_ERROR_SECURITY; + goto out; + } + + if (consttime_memcmp(digest_buf, expected, hash_len) != 0) + res = TEE_ERROR_SECURITY; + else + res = TEE_SUCCESS; +out: + memzero_explicit(digest_buf, sizeof(digest_buf)); + + return res; +} + +TEE_Result pas_sig_check_root_of_trust(uint32_t rot_hash_algo, + size_t rot_hash_len, + const uint8_t *root_der, + size_t root_der_len, + const uint8_t *expected) +{ + if (!root_der || !root_der_len || !expected) + return TEE_ERROR_BAD_PARAMETERS; + + return pas_sig_verify_hash(rot_hash_algo, root_der, root_der_len, + expected, rot_hash_len); +} + +TEE_Result pas_sig_algo_from_leaf(const uint8_t *leaf_der, + size_t leaf_der_len, uint32_t *sig_algo, + uint32_t *sig_hash_algo) +{ + mbedtls_pk_type_t pk = MBEDTLS_PK_NONE; + mbedtls_md_type_t md = MBEDTLS_MD_NONE; + TEE_Result res = TEE_ERROR_SECURITY; + mbedtls_x509_crt leaf = { }; + + if (!leaf_der || !leaf_der_len || !sig_algo || !sig_hash_algo) + return TEE_ERROR_BAD_PARAMETERS; + + mbedtls_x509_crt_init(&leaf); + if (mbedtls_x509_crt_parse_der(&leaf, leaf_der, leaf_der_len)) { + EMSG("PAS auth: leaf cert parse failed"); + goto out; + } + + res = check_sig_algo(&leaf, &pk, &md); + if (res) { + EMSG("PAS auth: leaf signatureAlgorithm not accepted"); + goto out; + } + + switch (pk) { + case MBEDTLS_PK_ECDSA: + *sig_hash_algo = TEE_ALG_SHA384; + *sig_algo = TEE_ALG_ECDSA_SHA384; + break; + default: + res = TEE_ERROR_SECURITY; + goto out; + } + + res = TEE_SUCCESS; +out: + mbedtls_x509_crt_free(&leaf); + + return res; +} + +/* mbedTLS rejects trailing padding; derive the true DER length here. */ +static size_t ecdsa_der_sig_len(const uint8_t *sig, size_t field_len) +{ + size_t len = 0; + + if (field_len < 2 || sig[0] != DER_SEQUENCE_TAG) + return field_len; + + if (sig[1] < 0x80) + len = (size_t)sig[1] + 2; + else if (sig[1] == 0x81 && field_len >= 3) + len = (size_t)sig[2] + 3; + else + return field_len; + + return len <= field_len ? len : field_len; +} + +TEE_Result pas_sig_verify_signature(uint32_t sig_algo, + uint32_t sig_hash_algo, + const uint8_t *leaf_der, + size_t leaf_der_len, + const uint8_t *msg, size_t msg_len, + const uint8_t *sig, size_t sig_len) +{ + uint8_t digest_buf[PAS_SIG_MAX_HASH_SIZE] = { }; + size_t digest_buf_len = sizeof(digest_buf); + mbedtls_md_type_t md = MBEDTLS_MD_NONE; + TEE_Result res = TEE_ERROR_SECURITY; + mbedtls_x509_crt leaf = { }; + size_t actual_sig_len = 0; + int rc = 0; + + if (!leaf_der || !leaf_der_len || !msg || !msg_len || !sig || !sig_len) + return TEE_ERROR_BAD_PARAMETERS; + + if (sig_len > PAS_SIG_MAX_SIG_SIZE) + return TEE_ERROR_SECURITY; + + res = md_from_tee(sig_hash_algo, &md); + if (res != TEE_SUCCESS) + return res; + + res = digest(sig_hash_algo, msg, msg_len, digest_buf, &digest_buf_len); + if (res != TEE_SUCCESS) + return res; + + mbedtls_x509_crt_init(&leaf); + if (mbedtls_x509_crt_parse_der(&leaf, leaf_der, leaf_der_len)) { + res = TEE_ERROR_SECURITY; + goto out; + } + + switch (sig_algo) { + case TEE_ALG_ECDSA_SHA384: + actual_sig_len = ecdsa_der_sig_len(sig, sig_len); + rc = mbedtls_pk_verify(&leaf.pk, md, digest_buf, digest_buf_len, + sig, actual_sig_len); + break; + default: + res = TEE_ERROR_NOT_SUPPORTED; + goto out; + } + + if (rc) { + EMSG("PAS auth: signature verify failed (%d)", rc); + res = TEE_ERROR_SECURITY; + goto out; + } + + res = TEE_SUCCESS; +out: + mbedtls_x509_crt_free(&leaf); + memzero_explicit(digest_buf, sizeof(digest_buf)); + + return res; +} diff --git a/ta/qcom_pas/src/auth/pas_sig_auth.c b/ta/qcom_pas/src/auth/pas_sig_auth.c new file mode 100644 index 00000000000..df086615d16 --- /dev/null +++ b/ta/qcom_pas/src/auth/pas_sig_auth.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SECBOOT_METADATA_MAJOR_V0 0U +#define SECBOOT_METADATA_MAJOR_V1 1U +#define SECBOOT_METADATA_MINOR 0U +#define SECBOOT_DEFAULT_ROOT_CERT_SEL 0U + +static TEE_Result check_metadata_version(const struct pas_oem_metadata *meta) +{ + if ((meta->major == SECBOOT_METADATA_MAJOR_V0 || + meta->major == SECBOOT_METADATA_MAJOR_V1) && + meta->minor == SECBOOT_METADATA_MINOR) + return TEE_SUCCESS; + + EMSG("PAS auth: unsupported metadata version %#"PRIx32".%#"PRIx32, + meta->major, meta->minor); + return TEE_ERROR_SECURITY; +} + +static TEE_Result check_sw_binding(const struct pas_oem_metadata *meta, + uint32_t pas_id) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t expected = 0; + + res = pas_policy_expected_swid(pas_id, &expected); + if (res) { + EMSG("PAS auth: no SW_ID binding for pas_id %#"PRIx32, pas_id); + return res; + } + + if (meta->sw_id != expected) { + EMSG("PAS auth: SW_ID got %#"PRIx32" want %#"PRIx32, + meta->sw_id, expected); + return TEE_ERROR_SECURITY; + } + + return TEE_SUCCESS; +} + +static uint32_t pas_meta_option(uint32_t flags, uint32_t shift) +{ + return (flags >> shift) & PAS_META_OPTION_MASK; +} + +static bool pas_meta_option_sn_gated(uint32_t flags, uint32_t shift) +{ + return pas_meta_option(flags, shift) == PAS_META_OPTION_ENABLE_SN; +} + +static TEE_Result check_metadata_options(const struct pas_oem_metadata *meta) +{ + if (pas_meta_option(meta->flags, + PAS_META_FLAG_ROOT_REVOKE_ACTIVATE_SHIFT) <= + PAS_META_OPTION_MAX && + pas_meta_option(meta->flags, PAS_META_FLAG_UIE_KEY_SWITCH_SHIFT) <= + PAS_META_OPTION_MAX && + pas_meta_option(meta->flags, PAS_META_FLAG_DEBUG_SHIFT) <= + PAS_META_OPTION_MAX) + return TEE_SUCCESS; + + EMSG("PAS auth: reserved metadata option value, flags=%#"PRIx32, + meta->flags); + return TEE_ERROR_SECURITY; +} + +static TEE_Result check_oem_model_binding(const struct pas_oem_metadata *meta, + const struct pas_device_ids *ids) +{ + bool oem_independent = meta->flags & + BIT32(PAS_META_FLAG_OEM_ID_INDEPENDENT); + bool model_independent = false; + + /* v0 metadata has no MODEL_ID_INDEPENDENT bit; falls back to OEM's. */ + if (meta->major == 0) + model_independent = oem_independent; + else + model_independent = meta->flags & + BIT32(PAS_META_FLAG_MODEL_ID_INDEPENDENT); + + if (!oem_independent && meta->oem_id != ids->oem_id) { + EMSG("PAS auth: OEM_ID got %#"PRIx32" want %#"PRIx32, + meta->oem_id, ids->oem_id); + return TEE_ERROR_SECURITY; + } + + if (!model_independent && meta->model_id != ids->model_id) { + EMSG("PAS auth: MODEL_ID got %#"PRIx32" want %#"PRIx32, + meta->model_id, ids->model_id); + return TEE_ERROR_SECURITY; + } + + return TEE_SUCCESS; +} + +static TEE_Result check_jtag_binding(const struct pas_oem_metadata *meta, + const struct pas_device_ids *ids) +{ + if (!(meta->flags & BIT32(PAS_META_FLAG_IN_USE_JTAG_ID))) + return TEE_SUCCESS; + + if (meta->hw_id == ids->jtag_id) + return TEE_SUCCESS; + + EMSG("PAS auth: HW_ID got %#"PRIx32" want %#"PRIx32, meta->hw_id, + ids->jtag_id); + return TEE_ERROR_SECURITY; +} + +static TEE_Result check_serial_binding(const struct pas_oem_metadata *meta, + const struct pas_device_ids *ids, + bool use_serial_num_override) +{ + static const uint32_t sn_gated_shifts[] = { + PAS_META_FLAG_DEBUG_SHIFT, + PAS_META_FLAG_ROOT_REVOKE_ACTIVATE_SHIFT, + PAS_META_FLAG_UIE_KEY_SWITCH_SHIFT, + }; + bool sn_gated = false; + size_t i = 0; + + for (i = 0; i < ARRAY_SIZE(sn_gated_shifts); i++) { + if (pas_meta_option_sn_gated(meta->flags, sn_gated_shifts[i])) { + sn_gated = true; + break; + } + } + + if (!(meta->flags & BIT32(PAS_META_FLAG_USE_SERIAL_NUMBER)) && + !use_serial_num_override && !sn_gated) + return TEE_SUCCESS; + + if (!ids->serial_num) { + EMSG("PAS auth: serial binding required, no fused serial"); + return TEE_ERROR_SECURITY; + } + + for (i = 0; i < ARRAY_SIZE(meta->serial_num); i++) { + if (meta->serial_num[i] && + meta->serial_num[i] == ids->serial_num) + return TEE_SUCCESS; + } + + EMSG("PAS auth: serial number %#"PRIx32" not in metadata allow-list", + ids->serial_num); + return TEE_ERROR_SECURITY; +} + +static TEE_Result check_soc_vers_binding(const struct pas_oem_metadata *meta) +{ + TEE_Result res = TEE_ERROR_GENERIC; + uint32_t fam_dev = 0; + size_t i = 0; + + if (!(meta->flags & BIT32(PAS_META_FLAG_IN_USE_SOC_HW_VERSION))) + return TEE_SUCCESS; + + res = pas_fuse_get_soc_hw_version(&fam_dev); + if (res) + return res; + + for (i = 0; i < ARRAY_SIZE(meta->soc_vers); i++) { + if (meta->soc_vers[i] == fam_dev) + return TEE_SUCCESS; + } + + EMSG("PAS auth: SOC_HW_VERSION %#"PRIx32" not in metadata allow-list", + fam_dev); + return TEE_ERROR_SECURITY; +} + +static TEE_Result check_hw_binding(const struct pas_oem_metadata *meta) +{ + struct pas_fuse_hw_binding_info info = { }; + TEE_Result res = TEE_ERROR_GENERIC; + + res = check_metadata_options(meta); + if (res) + return res; + + res = pas_fuse_get_hw_binding_info(&info); + if (res) + return res; + + res = check_oem_model_binding(meta, &info.ids); + if (res) + return res; + + res = check_jtag_binding(meta, &info.ids); + if (res) + return res; + + res = check_serial_binding(meta, &info.ids, + info.use_serial_num_override); + if (res) + return res; + + return check_soc_vers_binding(meta); +} + +static TEE_Result verify_oem_signature(const struct pas_hash_segment_info *hs, + uint32_t pas_id, + const uint8_t *anchor) +{ + uint32_t rot_hash_algo = TEE_ALG_SHA384; + TEE_Result res = TEE_ERROR_GENERIC; + struct pas_oem_metadata meta = { }; + uint8_t *signed_copy = NULL; + const uint8_t *roots = NULL; + uint32_t sig_hash_algo = 0; + const uint8_t *leaf = NULL; + bool eku_enforced = false; + uint32_t sig_algo = 0; + size_t signed_len = 0; + size_t roots_len = 0; + size_t leaf_len = 0; + + if (!hs->oem_certs || !hs->oem_sig || !hs->signed_region) { + EMSG("PAS auth: metadata is not OEM-signed"); + return TEE_ERROR_SECURITY; + } + + res = pas_fuse_get_eku_enforcement_en(&eku_enforced); + if (res) { + EMSG("PAS auth: cannot read EKU enforcement fuse: %#"PRIx32, + res); + return res; + } + + res = pas_sig_verify_cert_chain(hs->oem_certs, hs->oem_certs_size, + eku_enforced, 1, + SECBOOT_DEFAULT_ROOT_CERT_SEL, &leaf, + &leaf_len, &roots, &roots_len); + if (res) { + EMSG("PAS auth: OEM cert chain invalid: %#"PRIx32, res); + return res; + } + + res = pas_sig_check_root_of_trust(rot_hash_algo, + PTA_QCOM_FUSE_ROOT_OF_TRUST_SIZE, + roots, roots_len, anchor); + if (res) { + EMSG("PAS auth: root-of-trust mismatch"); + return res; + } + + res = pas_meta_get(hs, &meta); + if (res == TEE_ERROR_NO_DATA) + res = TEE_ERROR_SECURITY; + if (res) { + EMSG("PAS auth: bad or missing OEM metadata"); + return res; + } + + res = check_metadata_version(&meta); + if (res) + return res; + + res = check_sw_binding(&meta, pas_id); + if (res) + return res; + + res = check_hw_binding(&meta); + if (res) + return res; + + res = pas_sig_algo_from_leaf(leaf, leaf_len, &sig_algo, + &sig_hash_algo); + if (res) { + EMSG("PAS auth: cannot determine signature algorithm: %#"PRIx32, + res); + return res; + } + + res = pas_meta_get_signed_region_copy(hs, &signed_copy, &signed_len); + if (res) + return res; + + res = pas_sig_verify_signature(sig_algo, sig_hash_algo, leaf, + leaf_len, signed_copy, signed_len, + hs->oem_sig, hs->oem_sig_size); + TEE_Free(signed_copy); + if (res) { + EMSG("PAS auth: OEM signature verify failed: %#"PRIx32, res); + return res; + } + + return TEE_SUCCESS; +} + +TEE_Result pas_sig_auth_segment_hash_len(const struct pas_md_slot *slot, + uint32_t *segment_hash_len) +{ + uint32_t root_cert_sel = SECBOOT_DEFAULT_ROOT_CERT_SEL; + TEE_Result res = TEE_ERROR_GENERIC; + + res = pas_meta_get_root_cert_sel(slot->meta_data, + slot->meta_data_size, + &root_cert_sel); + if (res == TEE_ERROR_NO_DATA) + root_cert_sel = SECBOOT_DEFAULT_ROOT_CERT_SEL; + else if (res) + return res; + + res = pas_fuse_get_segment_hash_len(root_cert_sel, segment_hash_len); + if (res) { + EMSG("PAS auth: segment hash size read failed: %#"PRIx32, res); + return TEE_ERROR_NOT_SUPPORTED; + } + + return TEE_SUCCESS; +} + +TEE_Result pas_sig_auth_verify_image(const struct pas_hash_segment_info *hs, + const uint8_t *meta_data, + size_t meta_data_size, + uint32_t pas_id, + uint32_t segment_hash_len, + const uint8_t *anchor) +{ + TEE_Result res = TEE_ERROR_GENERIC; + + if (hs->uie_encrypted) { + EMSG("PAS auth: UIE image encryption not supported"); + return TEE_ERROR_NOT_SUPPORTED; + } + + if (hs->qc_certs || hs->qc_sig) { + EMSG("PAS auth: QC-countersigned images are not supported"); + return TEE_ERROR_NOT_SUPPORTED; + } + + res = verify_oem_signature(hs, pas_id, anchor); + if (res) + return res; + + res = pas_meta_verify_elf_headers_hash(meta_data, meta_data_size, + hs->hash_table, + segment_hash_len); + if (res) + return res; + + return TEE_SUCCESS; +} diff --git a/ta/qcom_pas/src/auth/sub.mk b/ta/qcom_pas/src/auth/sub.mk index d6905c1d341..42df2463c24 100644 --- a/ta/qcom_pas/src/auth/sub.mk +++ b/ta/qcom_pas/src/auth/sub.mk @@ -1 +1,9 @@ -srcs-y += pas_auth.c pas_fuse.c pas_mbn.c pas_meta.c +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# + +srcs-y += pas_auth.c pas_fuse.c pas_mbn.c pas_meta.c pas_policy.c pas_sig.c \ + pas_sig_auth.c + +global-incdirs-y += $(PLATFORM_FLAVOR) diff --git a/ta/qcom_pas/src/qcom_pas.c b/ta/qcom_pas/src/qcom_pas.c index 195b4db748d..36e32dbeaf8 100644 --- a/ta/qcom_pas/src/qcom_pas.c +++ b/ta/qcom_pas/src/qcom_pas.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,7 @@ static TEE_Result qcom_pas_init_image(struct qcom_pas_session *s, uint32_t pt, if (res) return res; - return pas_auth_prepare(s, params[0].value.a); + return pas_auth_prepare_and_authenticate(s, params[0].value.a); } static TEE_Result qcom_pas_auth_and_reset(struct qcom_pas_session *s, @@ -121,6 +122,13 @@ TEE_Result TA_OpenSessionEntryPoint(uint32_t pt, TEE_Free(s); goto out; } + + res = pas_fuse_open(); + if (res) { + TEE_CloseTASession(pta_session); + TEE_Free(s); + goto out; + } } session_refcount++; @@ -147,8 +155,10 @@ void TA_CloseSessionEntryPoint(void *sess_ctx) session_refcount--; - if (!session_refcount) + if (!session_refcount) { + pas_fuse_close(); TEE_CloseTASession(pta_session); + } } TEE_Result TA_InvokeCommandEntryPoint(void *sess_ctx, uint32_t cmd_id, diff --git a/ta/qcom_pas/src/user_ta_header_defines.h b/ta/qcom_pas/src/user_ta_header_defines.h index f3122c028b7..3a47ec5bc48 100644 --- a/ta/qcom_pas/src/user_ta_header_defines.h +++ b/ta/qcom_pas/src/user_ta_header_defines.h @@ -14,16 +14,20 @@ TA_FLAG_SINGLE_INSTANCE | \ TA_FLAG_INSTANCE_KEEP_ALIVE) -/* Provisioned stack size */ +/* + * Provisioned stack size. mbedtls X.509/ECDSA verification recurses + * deeply and needs more than the hash-only default. + */ +#ifdef CFG_QCOM_PAS_AUTH +#define TA_STACK_SIZE (32 * 1024) +#else #define TA_STACK_SIZE (4 * 1024) +#endif -/* Provisioned heap size for TEE_Malloc() and friends */ #define TA_DATA_SIZE CFG_PAS_TA_HEAP_SIZE -/* The gpd.ta.version property */ #define TA_VERSION "1.0" -/* The gpd.ta.description property */ #define TA_DESCRIPTION "remote processor firmware management" #endif /* USER_TA_HEADER_DEFINES_H */ diff --git a/ta/qcom_pas/user_ta.mk b/ta/qcom_pas/user_ta.mk index 9591d8c4b63..e881e192e41 100644 --- a/ta/qcom_pas/user_ta.mk +++ b/ta/qcom_pas/user_ta.mk @@ -7,7 +7,7 @@ endif CFG_QCOM_PAS_AUTH ?= n ifeq ($(CFG_QCOM_PAS_AUTH),y) -CFG_PAS_TA_HEAP_SIZE ?= (512 * 1024) +CFG_PAS_TA_HEAP_SIZE ?= (1024 * 1024) else CFG_PAS_TA_HEAP_SIZE ?= (4 * 1024) endif From 54be022f3035fc3309e2b705cbf14e302de840dc Mon Sep 17 00:00:00 2001 From: Selvam Sathappan Periakaruppan Date: Fri, 28 Aug 2026 13:37:00 +0530 Subject: [PATCH 8/8] plat-qcom: hoya: lemans: wire up fuse PTA and QFPROM for PAS authentication Signature authentication reads fuses at runtime, so CFG_QCOM_PAS_AUTH alone is not enough: the fuse PTA and the qfprom driver behind it must come up with it too. Fold the existing fuse-provisioning enable into the same block so it and the new fuse-PTA consumer share one QFPROM enable and cannot drift apart. Signed-off-by: Selvam Sathappan Periakaruppan Assisted-by: Claude:sonnet-5 Reviewed-by: Sachin Grover --- core/arch/arm/plat-qcom/hoya/lemans/target.mk | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/arch/arm/plat-qcom/hoya/lemans/target.mk b/core/arch/arm/plat-qcom/hoya/lemans/target.mk index 93e62a8c5f4..8747dbce320 100644 --- a/core/arch/arm/plat-qcom/hoya/lemans/target.mk +++ b/core/arch/arm/plat-qcom/hoya/lemans/target.mk @@ -7,10 +7,6 @@ ifneq ($(CFG_INSECURE),y) CFG_QCOM_QFPROM_FUSEPROV ?= y endif -ifeq ($(CFG_QCOM_QFPROM_FUSEPROV),y) -$(call force,CFG_QCOM_QFPROM,y) -endif - CFG_QCOM_PAS_PTA ?= y ifeq ($(CFG_QCOM_PAS_PTA),y) @@ -20,7 +16,18 @@ ifeq ($(CFG_QCOM_PAS_PTA),y) CFG_RESERVED_VASPACE_SIZE ?= (256 * 1024 * 1024) CFG_IN_TREE_EARLY_TAS += qcom_pas/cff7d191-7ca0-4784-af13-48223b9a4fbe CFG_QCOM_PAS_AUTH ?= y -CFG_PAS_MD_SLOTS = 8 endif CFG_QCOM_HWKM ?= y + +ifeq ($(CFG_QCOM_PAS_AUTH),y) +$(call force,CFG_QCOM_FUSE_PTA,y) +CFG_PAS_MD_SLOTS = 8 +# This chip's OEM_CONFIG2 fuse row has a per-root-cert hash function +# select bit; targets without it always use SHA-384. +$(call force,CFG_QCOM_SEGMENT_HASH_SELECT,y) +endif + +ifneq ($(filter y,$(CFG_QCOM_QFPROM_FUSEPROV) $(CFG_QCOM_FUSE_PTA)),) +$(call force,CFG_QCOM_QFPROM,y) +endif