Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions ta/qcom_pas/include/pas_auth.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#ifndef _PAS_AUTH_H
#define _PAS_AUTH_H

#include <stddef.h>
#include <stdint.h>
#include <tee_api_types.h>

/*
* PAS firmware authentication helpers.
*
* Verify that each loadable ELF program segment of the peripheral firmware
* matches the corresponding entry in the firmware hash table. This proves
* image integrity: the loaded image is exactly the one described by the
* accompanying hash table.
*
* The cryptographic primitives are provided by the TEE Internal Core API
* (digest operations); these helpers do not call any crypto library directly.
*/

/* Maximum supported segment hash size (SHA-384). */
#define PAS_AUTH_MAX_HASH_SIZE 48U

/*
* struct pas_auth_hash_ctx - per-image authentication context
* @hash_algo: TEE_ALG_SHA256 or TEE_ALG_SHA384
* @hash_size: digest size in bytes (32 for SHA-256, 48 for SHA-384)
* @hash_table: pointer to the authenticated hash table (one digest per
* firmware segment, laid out contiguously)
* @num_entries: number of digest entries in @hash_table
*
* The hash table is owned by the caller for the lifetime of the verification.
*/
struct pas_auth_hash_ctx {
uint32_t hash_algo;
uint32_t hash_size;
const uint8_t *hash_table;
uint32_t num_entries;
};

/*
* pas_auth_verify_hash() - hash a buffer and compare to an expected digest
* @hash_algo: TEE_ALG_SHA256 or TEE_ALG_SHA384
* @data: input buffer to hash
* @data_len: length of @data in bytes
* @expected: expected digest to match against
* @hash_size: digest size in bytes (must match @hash_algo)
*
* Computes the digest of @data using the OP-TEE digest API and compares it,
* in constant time, against @expected.
*
* Return TEE_SUCCESS if the digest matches, TEE_ERROR_SECURITY on mismatch,
* or an appropriate error on failure.
*/
TEE_Result pas_auth_verify_hash(uint32_t hash_algo, const uint8_t *data,
size_t data_len, const uint8_t *expected,
size_t hash_size);

/*
* pas_auth_verify_segments() - per-segment hash verification
* @ctx: authentication context holding the hash table and algorithm
* @fw: firmware ELF image buffer
* @fw_size: size of @fw in bytes
*
* Parses the program headers of @fw (ELFCLASS32 or ELFCLASS64) and, for each
* PT_LOAD segment, computes its digest and compares it (constant time) to the
* matching entry in @ctx->hash_table. Fails closed on the first mismatch.
*
* Return TEE_SUCCESS if every segment matches, TEE_ERROR_SECURITY on any
* mismatch, or an appropriate error on malformed input.
*/
TEE_Result pas_auth_verify_segments(const struct pas_auth_hash_ctx *ctx,
const uint8_t *fw, size_t fw_size);

#endif /* _PAS_AUTH_H */
232 changes: 232 additions & 0 deletions ta/qcom_pas/src/pas_auth.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2026, Qualcomm Technologies, Inc. and/or its subsidiaries.
*/

#include <elf32.h>
#include <elf64.h>
#include <pas_auth.h>
#include <string.h>
#include <string_ext.h>
#include <tee_internal_api.h>
#include <tee_internal_api_extensions.h>
#include <util.h>
#include <utee_defines.h>

/*
* Firmware hash verification.
*
* The firmware delivered for PAS bring-up is an ELF image accompanied by a
* hash table holding one digest per loadable program segment. Each segment is
* hashed and compared against its hash-table entry using the TEE Internal Core
* digest API, so the loaded image is guaranteed to match the hash table.
*/

TEE_Result pas_auth_verify_hash(uint32_t hash_algo, const uint8_t *data,
size_t data_len, const uint8_t *expected,
size_t hash_size)
{
TEE_OperationHandle op = TEE_HANDLE_NULL;
uint8_t digest[PAS_AUTH_MAX_HASH_SIZE] = { };
size_t digest_len = sizeof(digest);
TEE_Result res = TEE_ERROR_GENERIC;

if (!data || !expected || !hash_size || hash_size > sizeof(digest))
return TEE_ERROR_BAD_PARAMETERS;

res = TEE_AllocateOperation(&op, hash_algo, TEE_MODE_DIGEST, 0);
if (res != TEE_SUCCESS)
return res;

res = TEE_DigestDoFinal(op, data, data_len, digest, &digest_len);
if (res != TEE_SUCCESS)
goto out;

if (digest_len != hash_size) {
res = TEE_ERROR_SECURITY;
goto out;
}

/* Constant-time compare to avoid leaking the match position */
if (consttime_memcmp(digest, expected, hash_size) != 0) {
res = TEE_ERROR_SECURITY;
goto out;
}

res = TEE_SUCCESS;
out:
TEE_FreeOperation(op);
memzero_explicit(digest, sizeof(digest));

return res;
}

/*
* Bounds-check that [off, off + len) lies fully inside [0, total).
* Guards against integer overflow in the addition.
*/
static bool range_within(size_t off, size_t len, size_t total)
{
size_t end = 0;

if (ADD_OVERFLOW(off, len, &end))
return false;

return end <= total;
}

/*
* struct pas_elf_info - ELF class-agnostic view of the program-header table
* @phoff: program-header table file offset
* @phentsize: size of one program-header entry
* @phnum: number of program-header entries
* @is_64: true for ELFCLASS64, false for ELFCLASS32
*/
struct pas_elf_info {
size_t phoff;
size_t phentsize;
size_t phnum;
bool is_64;
};

/*
* Parse and validate the ELF identification and the program-header table
* location for both ELFCLASS32 and ELFCLASS64 images.
*/
static TEE_Result pas_parse_elf(const uint8_t *fw, size_t fw_size,
struct pas_elf_info *info)
{
const unsigned char *e_ident = fw;

if (fw_size < EI_NIDENT)
return TEE_ERROR_BAD_FORMAT;

if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3)
return TEE_ERROR_BAD_FORMAT;

if (e_ident[EI_CLASS] == ELFCLASS64) {
const Elf64_Ehdr *ehdr = (const void *)fw;

if (fw_size < sizeof(*ehdr))
return TEE_ERROR_BAD_FORMAT;

info->is_64 = true;
info->phoff = ehdr->e_phoff;
info->phentsize = ehdr->e_phentsize;
info->phnum = ehdr->e_phnum;

if (info->phentsize < sizeof(Elf64_Phdr))
return TEE_ERROR_BAD_FORMAT;
} else if (e_ident[EI_CLASS] == ELFCLASS32) {
const Elf32_Ehdr *ehdr = (const void *)fw;

if (fw_size < sizeof(*ehdr))
return TEE_ERROR_BAD_FORMAT;

info->is_64 = false;
info->phoff = ehdr->e_phoff;
info->phentsize = ehdr->e_phentsize;
info->phnum = ehdr->e_phnum;

if (info->phentsize < sizeof(Elf32_Phdr))
return TEE_ERROR_BAD_FORMAT;
} else {
return TEE_ERROR_BAD_FORMAT;
}

return TEE_SUCCESS;
}

/*
* Read the type, file offset and file size of program header @idx, handling
* both ELF classes.
*/
static void pas_get_phdr(const uint8_t *fw, const struct pas_elf_info *info,
size_t idx, uint32_t *p_type, size_t *p_offset,
size_t *p_filesz)
{
const uint8_t *p = fw + info->phoff + idx * info->phentsize;

if (info->is_64) {
const Elf64_Phdr *phdr = (const void *)p;

*p_type = phdr->p_type;
*p_offset = phdr->p_offset;
*p_filesz = phdr->p_filesz;
} else {
const Elf32_Phdr *phdr = (const void *)p;

*p_type = phdr->p_type;
*p_offset = phdr->p_offset;
*p_filesz = phdr->p_filesz;
}
}

TEE_Result pas_auth_verify_segments(const struct pas_auth_hash_ctx *ctx,
const uint8_t *fw, size_t fw_size)
{
struct pas_elf_info info = { };
size_t i = 0;
size_t seg_idx = 0;
TEE_Result res = TEE_ERROR_GENERIC;

if (!ctx || !ctx->hash_table || !fw)
return TEE_ERROR_BAD_PARAMETERS;

if (ctx->hash_size > PAS_AUTH_MAX_HASH_SIZE || !ctx->hash_size)
return TEE_ERROR_BAD_PARAMETERS;

res = pas_parse_elf(fw, fw_size, &info);
if (res != TEE_SUCCESS)
return res;

/* The whole program-header table must lie within the image */
if (!range_within(info.phoff, info.phentsize * info.phnum, fw_size))
return TEE_ERROR_BAD_FORMAT;

for (i = 0; i < info.phnum; i++) {
uint32_t p_type = 0;
size_t p_offset = 0;
size_t p_filesz = 0;
const uint8_t *expected = NULL;

pas_get_phdr(fw, &info, i, &p_type, &p_offset, &p_filesz);

if (p_type != PT_LOAD)
continue;

/* A loadable segment with no file content has no hash */
if (!p_filesz)
continue;

/* The segment bytes must lie within the image */
if (!range_within(p_offset, p_filesz, fw_size))
return TEE_ERROR_BAD_FORMAT;

/* There must be a hash-table entry for this segment */
if (seg_idx >= ctx->num_entries)
return TEE_ERROR_SECURITY;

expected = ctx->hash_table + seg_idx * ctx->hash_size;

res = pas_auth_verify_hash(ctx->hash_algo, fw + p_offset,
p_filesz, expected, ctx->hash_size);
if (res != TEE_SUCCESS) {
EMSG("PAS segment %zu hash check failed", seg_idx);
return res;
}

seg_idx++;
}

if (!seg_idx) {
/* No loadable segment was verified - treat as malformed */
EMSG("PAS auth: no loadable segments found");
return TEE_ERROR_BAD_FORMAT;
}

DMSG("PAS auth: %zu segment(s) hash-verified", seg_idx);

return TEE_SUCCESS;
}
Loading