Skip to content

Enable PIL authentication for Qualcomm Wildcat/Nord platform - #37

Draft
Akhilesh Kumar Verma (slategrey-vision-pwrf168) wants to merge 18 commits into
qualcomm-linux:qcom-nextfrom
slategrey-vision-pwrf168:feat/qcom-wildcat-pas
Draft

Enable PIL authentication for Qualcomm Wildcat/Nord platform#37
Akhilesh Kumar Verma (slategrey-vision-pwrf168) wants to merge 18 commits into
qualcomm-linux:qcom-nextfrom
slategrey-vision-pwrf168:feat/qcom-wildcat-pas

Conversation

@slategrey-vision-pwrf168

Copy link
Copy Markdown
Member

Summary

This series enables PIL (Peripheral Image Loading) firmware authentication for the Qualcomm Wildcat/Nord (SA8797P) platform in OP-TEE. It extends the existing Hoya PIL authentication framework to support Nord's hardware and MBN v7 metadata format.

Depends on: PR#20

Key Changes

MBN v7 Metadata Support

  • Parse and validate MBN v7 common-metadata and per-signing-metadata blocks
  • Implement v7-specific binding checks: lifecycle state, OEM root-cert-hash, SOC feature/segment ID
  • Support v7's 2-bit flag encoding (distinct from v6's single-bit encoding)
  • Validate hash-table algorithm selection from signed common-metadata (no fuse lookup needed for v7)

Nord Platform Enablement

  • Add Nord QFPROM register definitions (core/drivers/qcom/qfprom/nord/qfprom_target.h)
  • Enable CFG_QCOM_PAS_AUTH for Nord using sense registers instead of QFPROM driver
  • Gate anti-rollback (ARB) code for Nord (requires QFPROM corrected/raw registers not available on Nord)
  • Refactor fuse reads to use phys_to_virt() directly, removing QFPROM driver context dependency
    - Nord sense-register fuse reads validated against hardware specification

Security Fixes

  • Reject zero SOC_HW_VERSION family number when SOC_HW_VERSION binding is active (v7-only)
  • Validate common-metadata major/minor version before accepting hash_table_algo
  • Fix v6/v7 flag encoding mismatch in OEM_ID/MODEL_ID binding checks

Commits

  1. ta: qcom_pas: parse MBN version 7 hash segments — MBN v7 parsing
  2. ta: qcom_pas: bind MBN version 7 metadata to device fuses — MBN v7 binding checks
  3. ta: qcom_pas: close two MBN v7 validation gaps — v7 security fixes
  4. chore: use phys_to_virt() directly in secboot read helpers — Refactor fuse reads for Nord
  5. drivers: qcom: qfprom: add nord fuse register definitions — Nord QFPROM registers
  6. plat-qcom: wildcat: nord: enable PAS firmware loading and authentication — Nord platform enablement

A stale carveout could be reused across peripheral loads: shutdown
did not clear the cached MEM_SETUP coordinates, so a subsystem that
was stopped and reloaded without a fresh MEM_SETUP call would pass
the resulting VERIFY_IMAGE cross-check against physical memory it no
longer owns. qcom_pas_capabilities() also passed the wrong parameter
to pas_platform_capabilities(), reading the output flags field instead
of the caller-supplied pas_id.

Fix both ahead of the authentication work that builds on this code,
along with unrelated include and logging cleanup, so the feature
commits that follow stay focused on the feature. pas_lookup() is
exported for the same reason: later commits need it directly.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
PIL firmware images carry a Qualcomm MBN hash segment holding the
per-segment digest table, signature and certificate material the PAS
TA needs to authenticate an image before releasing the peripheral
from reset. The segment is located by scanning the program-header
table for the Qualcomm hash-segment flag rather than assuming a fixed
offset, so a malformed image is rejected up front instead of being
parsed at the wrong bytes.

Parse it once into a single typed view so the hash- and
signature-verification work that follows shares one source of truth
for where each region starts, rather than each phase re-walking the
raw metadata buffer and risking disagreement.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Segment-hash verification is the half of PIL firmware authentication
that stands on its own and can be enabled and stress-tested before any
signature work exists, so it lives in a self-contained module that
does not pull in the wider PAS command dispatch. Add a VERIFY_IMAGE
command that re-hashes each loaded segment against the image's own
digest table, so a compromised REE cannot substitute firmware after
the metadata has already been accepted.

MEM_SETUP is a hard precondition, so the check runs against
platform-validated memory rather than an attacker-chosen physical
address. A segment's zero-init tail is bounds-checked and zeroed
before hashing so stale REE bytes there cannot slip through the digest
or run uninitialised after reset, and an image with no hashed loadable
segment is rejected rather than launched on an entry-0-only check.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Segment-hash verification exists in the PTA but nothing invokes it
yet. Wire the PAS TA to drive it around the existing PTA calls,
keeping qcom_pas.c a thin dispatch layer by moving the logic into a
new backend module: the TA saves a TEE-private copy of the
REE-supplied metadata at INIT_IMAGE and hands [metadata | hash table]
to the PTA's VERIFY_IMAGE at reset time, so the REE cannot alter the
metadata it already committed to between the two calls.

INIT_IMAGE invokes the PTA before stashing metadata so a failed PTA
call cannot leak a per-pas_id session slot; per-session state is
keyed by pas_id so concurrent DSP loads on one session do not clobber
each other. Signature authentication is left as a placeholder here so
segment-hash verification can be reviewed and enabled without waiting
on it.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Enable CFG_QCOM_PAS_AUTH on Lemans so PIL images are authenticated
before the peripheral leaves reset. Segment-hash verification takes
effect immediately; signature authentication is a runtime step that
only engages once secure-boot fuses are blown, and is filled in
later in this series.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Signature authentication has to bind an image to the device it runs
on, which means reading the OEM root-of-trust anchor and enable
state, device identity, and EKU/image-encryption enforcement fuses.
Add those readers to the qfprom driver as straight reads over the
SECURITY_CONTROL window the driver already owns.

They are placed in a separate qfprom_secboot.c compiled only under
CFG_QCOM_FUSE_PTA, so a target that does not build the fuse PTA
carries none of this code and the platform-independent core driver
keeps no dependency on it. Nothing reads these until the fuse PTA is
turned on in a later commit, so existing paths are unaffected.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
The secure-boot fuse readers live in the OP-TEE core, but the PAS TA
that needs them runs in user space. Add a pseudo-TA that exposes each
reader as a command, 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 <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Signature authentication binds an image to its device and enforces
anti-rollback using fields carried in the OEM metadata block of the
MBN hash segment. Add a decoder that lifts those fields into typed
values so the authentication backend does not re-parse the raw block
itself.

It also exposes the early peeks the backend needs before full
parsing (metadata version, to reject rollback; root_cert_sel, to pick
the segment hash size) and materialises the OEM-signed region with
the QTI-controlled fields masked out, so the signature covers exactly
what the OEM signed. Nothing calls it until the signature-
authentication commit that follows.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
The signature-authentication path makes several fuse reads per image.
Wrap the fuse PTA in a helper that opens one session per TA session,
matching how the TA already holds its PAS PTA session, rather than
opening one per call, and that maps each read one-to-one onto a fuse
PTA command.

The helper applies no policy and fails closed: every read returns a
TEE_Result and propagates a fuse access failure to the caller instead
of substituting a default, so a fuse value that cannot be trusted
aborts authentication rather than silently weakening it. Nothing
calls the helpers yet.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
Segment-hash verification alone proves an image matches its own
digest table; it cannot prove the image was signed by a trusted party
or is permitted on this device. Replace the signature-authentication
placeholder with the real implementation, so that on a fused
secure-boot device the PAS TA establishes an image's provenance
before the peripheral is released from reset.

The decision forks on a single fuse read of the secure-boot state and
root-of-trust anchor, and that read fails closed: a fuse-PTA error is
treated as secure-boot enabled, so a transient failure can never
downgrade a secure-booted board to hash-only verification. When
secure boot is on, the TA verifies the OEM certificate chain against
the fused anchor, verifies the signature over the OEM-signed region,
and enforces the SW and HW bindings against the fused device
identity. UIE-encrypted images and QTI-countersigned images are
refused outright, since neither decryption nor countersignature
verification is implemented here. Devices whose secure-boot fuse is
unblown skip this path entirely and rely on segment-hash verification
alone.

The chain is verified against a single provisioned root (ECDSA
P-384 leaves, SHA-384). The crypto, orchestration and per-peripheral
SW_ID policy are split into three file pairs.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
The secure-boot fuse accessors added in the previous commit need
per-target register offsets, masks and layout to operate: the
SECURE_BOOT and device-identity sense registers, the root-of-trust
digest size, and the OEM_CONFIG2/OEM_CONFIG0 fields the EKU, segment-
hash-select and image-encryption checks read. Add the Lemans values as
driver platform data, kept separate from the accessor logic so a
future target only needs its own register layout, not a copy of the
reader code.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
…cation

Signature authentication reads device fuses at runtime, so enabling
CFG_QCOM_PAS_AUTH is not enough on its own - the fuse PTA and the
qfprom driver behind it (and the CMD_DB/RPMH client its fuse-write
path needs) have to come up with it. Force that whole chain from
CFG_QCOM_PAS_AUTH so a stock Lemans build brings up the complete PAS
authentication stack end to end.

Fold the existing fuse-provisioning enable into the same block so
provisioning and the new fuse-PTA consumer share one QFPROM enable
and cannot drift apart.

Signed-off-by: Selvam Sathappan Periakaruppan <speriaka@qti.qualcomm.com>
Assisted-by: Claude:sonnet-5
MBN v7 keeps the "fixed header followed by concatenated variable-length
regions" shape of v5/v6, but reorders the header fields and adds a
common-metadata block that both signers share, ahead of the per-signer
metadata:

  v6: [header][qti meta][oem meta][hash table][sigs][certs]
  v7: [header][common meta][qti meta][oem meta][hash table][sigs][certs]

Because the header field order differs, v7 needs its own offset set
rather than reusing the v5/v6 MBN_OFF_* constants: only the version
word at 0x04 lands at the same place in both layouts, which is what
lets the version be read before the layout is known.

Add the v7 offsets and header size, teach pas_mbn_parse() to decode
them, and record the common-metadata region in struct pas_mbn so the
metadata decoder can reach the fields it carries. The signed region
grows to cover the common-metadata block, matching what the signature
is computed over.

Add pas_mbn_read_u64() alongside the existing u32 reader; v7 widens
the metadata serial-number entries to 64 bits.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Assisted-by: Claude:opus-5
Decode the v7 per-signing and common metadata blocks and apply the
existing binding checks to them.

The v7 flags word is not compatible with v6's. v6 uses single-bit
"independent"/"in use" flags; v7 uses 2-bit pairs per field where "10"
means bound and "01" means not bound, and the two encodings overlap at
the same shift positions with opposite polarity. Feeding a v7 flags
word to the v6 accessors would therefore silently invert several
binding decisions, so struct pas_meta now records which encoding its
flags field carries and every gate branches on it. The v6 paths are
unchanged.

Two v7 additions are enforced:

  - the flag word must use only the "10"/"01" encodings; "00" and "11"
    are rejected, covering all eleven defined bit pairs including the
    ones this port does not yet act on, so a malformed word cannot pass
    as a permissive one.

  - an image must bind to JTAG_ID or SOC_HW_VERSION unless its SW_ID is
    one that is allowed to be hardware-independent.

The segment hash-table digest size comes from the signed common
metadata's algorithm field for v7, so no fuse read is needed to pick
it, unlike v6 where it is fuse-selected via root_cert_sel. SHA-512 and
the zero-init hashing variants are rejected: the segment
re-verification path handles 32- and 48-byte digests only.

v7 metadata also carries SOC feature ID, product segment ID, SOC/OEM
lifecycle state and an OEM root-cert-hash field. Enforcing those needs
fuse values this platform's fuse PTA does not expose, so they are left
undecoded rather than partially checked.

The v7 field offsets were validated against the layout a compiler
computes for the corresponding packed structures.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Assisted-by: Claude:opus-5
An equivalence review against the reference found two checks the v7
metadata validator performs that this port was missing:

  - a zero fused SOC_HW_VERSION family number is rejected outright when
    SOC_HW_VERSION binding is active, before the allow-list is even
    consulted. Without this, a device whose family-number fuse reads
    zero authenticates any image whose allow-list happens to contain
    zero, which the reference treats as unconditionally invalid. v6 has
    no equivalent guard, so this is v7-only.

  - the common-metadata block's own major/minor version was decoded but
    never checked. The accepted major is fixed at 0; minor 0 and 1 both
    accept the hash_table_algo values this port supports, so checking
    the version does not change which algorithms authenticate today,
    but an unrecognized version is now rejected instead of silently
    parsed under minor-0 assumptions.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Assisted-by: Claude:opus-5
…boot reads

qfprom_core.c had grown a duplicate set of secboot fuse accessors
(qcom_secboot_is_enabled(), qcom_secboot_get_use_serial_num(),
read_corr_word(), qcom_secboot_get_root_of_trust(), read_sense_reg(),
qcom_secboot_get_device_ids(), qcom_secboot_get_segment_hash_size(),
qcom_secboot_get_eku_enforcement_en(), qcom_secboot_get_image_encryption_en(),
qcom_secboot_get_soc_hw_version()) alongside the pre-existing, correctly
CFG_QCOM_FUSE_PTA-gated versions in qfprom_secboot.c, plus unused
ARB/MRC helpers (qcom_secboot_get_pil_rollback_version(),
qcom_secboot_blow_pil_rollback_version(), qcom_secboot_get_mrc_info(),
popcount32(), unary_mask()) with no callers anywhere in the tree.
Remove the duplicated block from qfprom_core.c entirely.

In qfprom_secboot.c, read qcom_secboot_is_enabled(),
qcom_secboot_get_use_serial_num(), read_sense_reg() and
qcom_secboot_get_soc_hw_version() directly via phys_to_virt() on the
target physical address instead of through a driver-context base VA
(drv->raw_base_va). The physical-to-virtual mapping is already
established by the register_phys_mem_pgdir() calls at the top of the
file, so the result is equivalent, without requiring
qfprom_get_context() on these paths. read_corr_word() keeps the
driver-context style (drv->corr_base_va), since corrected-space reads
still go through the QFPROM driver's mapped VA.

Fix three leftover references to a per-function secboot_on tolerant-
check parameter in pas_sig_auth.c that predate this branch's current
design, where pas_auth_authenticate() gates the entire
pas_sig_auth_authenticate() call on secure-boot state once, making a
per-function tolerate/hard-fail branch downstream both dead and
non-compiling: check_soc_vers_binding() no longer references the
undeclared secboot_on when rejecting a zero SOC_HW_VERSION family
number, check_jtag_or_soc_vers_binding() drops its unused secboot_on
parameter, and check_hw_binding() calls check_metadata_options() with
its actual (no secboot_on) signature.

Fix pas_meta_peek_hash_table_algo() to use the current pas_mbn_locate()
(4-argument) and pas_mbn_reserve_region() API instead of a stale
5-argument pas_mbn_locate() call and a nonexistent
pas_mbn_take_region(), and fix pas_sig_auth_hash_size() to read
slot->meta_data/slot->meta_data_size instead of the nonexistent
slot->md/slot->md_size fields.

These bugs were undetected because CFG_QCOM_QFPROM is not yet enabled
for any platform on this branch; building with
CFG_QCOM_QFPROM=y CFG_QCOM_FUSE_PTA=y (verified against lemans) is
required to compile this code at all.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Add the nord flavor of qfprom_target.h, following the kodiak/lemans
layout and macro naming, for the fields confirmed against nord's fuse
controller register definitions: QFPROM_RAW/CORR base addresses,
SECURE_BOOT (a single register on this platform, unlike hoya's
per-code-segment SECURE_BOOTn array), the 48-byte root-of-trust digest
size, device-identity sense registers, EKU enforcement, and
ROOT_CERT_TOTAL_NUM. Add SECURITY_CONTROL_BASE/SIZE to
wildcat/arch_config.h for the sense-register block these live in.

Two fields are deliberately left undefined rather than approximated:

  - PIL anti-rollback: nord uses a separate fuse-row pair per
    subsystem type instead of hoya's single shared counter, which the
    existing fuse-PTA API (one get/blow call, no subsystem selector)
    cannot express without a shape change of its own. Out of scope
    for this change.

  - MRC activation/revocation: nord's layout splits QC and OEM root
    lists and has additional fields with no located documentation.

TCSR_SOC_HW_VERSION_ADDR is carried over from hoya as a placeholder -
nord's own TCSR register definitions were not found in the accessible
source, so this value is UNCONFIRMED and must be verified before
CFG_QCOM_PAS_AUTH is enabled for this platform, or the SOC_HW_VERSION
binding check will silently read the wrong register. Marked in a
comment above the definition.

This header alone does not make CFG_QCOM_QFPROM buildable for nord;
the region/permission table (qfprom_fuse_region.c) is a separate,
still-missing piece.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Assisted-by: Claude:opus-5
Bring up the PAS PTA and the qcom_pas TA on Nord, reserving the VA
space the subsystem controller windows are mapped from and adding the
TA to the early-TA list, mirroring how Lemans is configured.

This enables the PTA-side flow: per-segment hash verification of the
loaded firmware against the hash table in the image's MBN hash segment.

CFG_QCOM_PAS_AUTH, which adds certificate-chain, signature and
fuse-bound binding checks on top, is also enabled. Fuse reads use
sense registers (hardware shadow of fuse rows) instead of the QFPROM
driver, so no CFG_QCOM_QFPROM dependency is needed — see the
"chore: use phys_to_virt()..." commit for the sense-register fuse
read implementation.

CFG_QCOM_PAS_PTA remains disabled pending creation of
core/pta/qcom/pas/platform/nord/ (the per-subsystem PIL driver).

PIL anti-rollback (R-13) is a separate concern and does not block
auth enablement — anti-rollback is optional hardening, not a
prerequisite for authentication.

R-06 is now RESOLVED: sense-register fuse reads bypass QFPROM entirely.

Signed-off-by: Akhilesh Kumar Verma <akhiverm@qti.qualcomm.com>
Assisted-by: Claude:opus-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants