vRoT & secure boot

On this page

OpenSMA does not just boot an application processor (AP); it gates that boot on a cryptographic verdict. Two subsystems collaborate:

  • src/nv/vrot/ — the virtual Root of Trust (vRoT). A thin, compile-time specialised interface that abstracts “which AP is this, and what physical operations hold, release, and flash it” behind a fixed set of platform operations.
  • src/nv/secure_boot/ — the secure-boot state machine. A dedicated task that drives one AP through hold reset -> authenticate -> release -> confirm boot, retries into a backup slot on failure, and records the outcome in non-volatile storage.

The actual signature and hash checking is performed by the SPDM crypto helper (src/nv/spdm/); the secure-boot task orchestrates when it runs and what happens to the verdict. See the Security & Root-of-Trust overview for where this sits in the system, and Architecture for the task model.


The vRoT interface

The public vRoT surface is a set of free functions in namespace nv::vrot, declared in src/nv/vrot/interface/interface.h:50 (hold_reset, pre_authenticate, post_authenticate, release_reset, check_booted). Each takes an ApInfo describing the target AP and returns an ApOpErrCode (src/nv/vrot/interface/types.h:40).

ApInfo (src/nv/vrot/interface/types.h:74) carries the AP identity and its flash placement — id, type, component_id, fw_size, fw_offset, and optional metadata_offset/metadata_size. ApType (src/nv/vrot/interface/types.h:34) currently enumerates two backends: Cpld and Lpu.

Compile-time dispatch by project

The set of APs a firmware image supports is a project constant, nv::vrot::ApList. Each project’s config.h defines it; on the reference projects it is an empty std::array<ApInfo, 0> (src/projects/p3957_cxx/config.h:1327). The interface layer keys every dispatch off that list at compile time:

constexpr bool HasAp   = !ApList.empty();
constexpr bool HasCpld = has_ap_type(ApType::Cpld, ApList);
constexpr bool HasLpu  = has_ap_type(ApType::Lpu, ApList);

(src/nv/vrot/interface/interface.cpp:29). Every op then switches on ap.type guarded by if constexpr (HasXxx) and falls through to ApOpErrCode::NotSupported — e.g. hold_reset at src/nv/vrot/interface/interface.cpp:35. Interpretation: on an AP-less build the whole vRoT/secure-boot chain compiles down to NotSupported returns and the SecureBoot task never advances past initialisation, so the feature carries no runtime cost when unused.

Platform Ops

Each ApType is backed by a *Ops struct. LpuOps (src/nv/vrot/platform/lpu.h:118) and CpldOps (src/nv/vrot/platform/cpld.h:28) implement the same static methods the interface dispatches to. The bodies are deliberately small — they are the physical half of secure boot. For the LPU backend:

  • hold_reset holds the LPU in reset (src/nv/vrot/platform/lpu.cpp:144).
  • pre_authenticate claims the shared SPI flash so the SMA can read the AP image (src/nv/vrot/platform/lpu.cpp:153).
  • release_reset releases the flash and then the reset line, letting the AP run (src/nv/vrot/platform/lpu.cpp:171).

The LPU flash is split into two equal slots, image first and a 4 KiB metadata block at the end of each slot (src/nv/vrot/platform/lpu.h:98).


The SecureBoot state machine

The secure-boot logic lives in a FreeRTOS task. nv::secure_boot::Task (src/nv/secure_boot/task.cpp:45) owns a single static SecureBoot instance and its main() simply calls secure_boot.run() (src/nv/secure_boot/task.cpp:76). A static_assert restricts the build to a single AP with an id below MaxApCount (src/nv/secure_boot/task.cpp:40, MaxApCount = 2 at src/nv/secure_boot/secure_boot.h:31).

run() (src/nv/secure_boot/secure_boot.cpp:384) is an infinite loop over the State enum (src/nv/secure_boot/secure_boot.h:33). The happy path and the recovery path both flow through it:

stateDiagram-v2
    direction TB
    [*] --> Initialization
    Initialization --> OnAuthenticate: hold_reset + pre_authenticate OK
    Initialization --> AuthFailed: physical setup failed
    OnAuthenticate --> AuthRequestPending: mark Auth_In_Progress
    AuthRequestPending --> AuthPending: SPDM request sent
    AuthRequestPending --> AuthFailed: send failed
    AuthPending --> Release: CryptoStatus Success
    AuthPending --> AuthFailed: bad signature or timeout
    Release --> WaitBoot: post_authenticate + release_reset OK
    Release --> BootFailed: physical release failed
    WaitBoot --> BootComplete: check_booted OK
    WaitBoot --> BootFailed: boot not observed
    BootComplete --> Idle: record status, sync debug tokens
    AuthFailed --> Recovery
    BootFailed --> Recovery
    Recovery --> Initialization: retry other slot
    Recovery --> Fatal: retries exhausted
    Fatal --> Idle
    Idle --> Initialization: AP reset requested

Reading the states against the code:

  • Initialization — calls hold_reset then pre_authenticate; either failure jumps straight to AuthFailed and, being pre-auth, leaves the persisted auth record untouched (src/nv/secure_boot/secure_boot.cpp:394).
  • AuthRequestPending — issues the authentication request to the SPDM helper via send_authenticate_firmware_request(ap_info, current_slot, ...) (src/nv/secure_boot/secure_boot.cpp:419).
  • AuthPending — waits on the AuthResultReady event, matches the queued result to this attempt, and branches on CryptoStatus::Success (src/nv/secure_boot/secure_boot.cpp:431). A wall-clock guard, AuthTimeoutTicks = 120 s (src/nv/secure_boot/secure_boot.cpp:56), forces a failure if no verdict arrives (src/nv/secure_boot/secure_boot.cpp:454).
  • Release — only reached on success; runs post_authenticate then release_reset. A failure here is a boot failure, not an auth failure, and is tracked separately (src/nv/secure_boot/secure_boot.cpp:462).
  • BootComplete — records Boot_Complete status and calls nv::debugtoken::sync_debug_token_features_on_boot() exactly once (src/nv/secure_boot/secure_boot.cpp:488).
  • Recovery — flips current_slot between ActiveSlot and UpdateSlot and retries; with MaxRecoveryRetries = 1 (src/nv/secure_boot/secure_boot.h:101) each boot gets at most one retry into the other slot before going Fatal (src/nv/secure_boot/secure_boot.cpp:519).

The distinct Auth_Failed vs Boot_Failed statuses (src/nv/fw_parser/fw_parser_ap.h:8) let a BMC or diagnostic tell a signature rejection apart from a good image that simply failed to come up.

Result plumbing

Authentication runs on the SPDM task and reports back asynchronously. The SPDM helper invokes SecureBoot::secure_boot_auth_callback (src/nv/secure_boot/secure_boot.cpp:548), which — for a SecureBoot-originated request — persists a successful verdict and then pushes a SecureBootAuthResult onto a queue and sets the AuthResultReady event bit. The FSM’s receive_auth_result (src/nv/secure_boot/secure_boot.cpp:254) matches the queued ap_index, slot, and auth_request_id against the in-flight attempt before accepting it, so a stale result cannot satisfy a new request.


What is authenticated, and when

The heavy lifting is _authenticate_firmware_result in the SPDM crypto helper (src/nv/spdm/spdm_crypto_helper.cpp:106), driven synchronously on the SPDM task once SecureBoot requests it. For the selected slot it performs, in order:

  1. Read the 4 KiB metadata block for the slot via the vRoT read_metadata op (src/nv/spdm/spdm_crypto_helper.cpp:122).
  2. Pin the signing key. The metadata’s verif_pub_key must equal one of the two build-time public keys ApFwPublicKeys[0/1]; otherwise FailApPublicKeyMismatch (src/nv/spdm/spdm_crypto_helper.cpp:129).
  3. Verify the metadata signature. SHA-384 over the to-be-signed portion (ApFwMetadata::TbsData), then ECDSA P-384 verify of the detached r/s signature (src/nv/spdm/spdm_crypto_helper.cpp:149).
  4. Enforce anti-rollback. The on-device secure FW version (from CFPA) must not exceed the image’s sec_version, else FailApRollbackProtection (src/nv/spdm/spdm_crypto_helper.cpp:166).
  5. Enforce key revocation, with a debug-token bypass path: if prod-key level is required but the image is debug-signed, a valid flash-debug token for that AP is consulted (src/nv/spdm/spdm_crypto_helper.cpp:198).
  6. Verify every sub-image. For each of ap_fw_images_count entries it streams the sub-image through SHA-384 and compares against the metadata hash table; any mismatch is FailApImageHashMismatch (src/nv/spdm/spdm_crypto_helper.cpp:224).

So the trust chain is: two pinned public keys -> a signed metadata block -> per-sub-image SHA-384 hashes listed inside that signed metadata. Only after all of these pass does the callback return Success and SecureBoot advance to Release. The metadata layout that anchors this — ApFwMetadata (4096 bytes, src/nv/fw_parser/fw_parser_ap.h:86), the MetadataHashTableEntry SHA-384 table (src/nv/fw_parser/fw_parser_ap.h:74), the verif_pub_key field (src/nv/fw_parser/fw_parser_ap.h:118), and the ECDSA P-384 NvSignature (src/nv/fw_parser/fw_parser_ap.h:39) — is described in Crypto & firmware parsing.

Interpretation. The code establishes image integrity and authenticity against keys baked into the SMA firmware, plus rollback and key-revocation policy. It does not by itself establish confidentiality of the AP image, and the physical check_booted step for the LPU backend currently just returns Success (src/nv/vrot/platform/lpu.cpp:181) — “booted” is not independently attested there. Do not read more into it than the checks above.


Persisting the verdict

Results are written to non-volatile NPDS storage as an AuthenticateData record (src/nv/secure_boot/authenticate_data.h:13): the metadata TBS data plus the CryptoStatus, defaulted to FailUnknown so an un-provisioned slot never reads as authenticated (src/nv/secure_boot/authenticate_data.h:19).

persist_authenticate_result (src/nv/secure_boot/secure_boot.cpp:216) routes to the right flash key by slot. A subtle policy lives in persist_authenticate_data (src/nv/secure_boot/secure_boot.cpp:179): when the Active slot authenticates, its record is also seeded into the Update slot — but only if Update has no prior successful record and PLDM is not mid-stage, so a good staged update is never clobbered.

Downstream consumers read this record rather than re-hashing the image. The AP firmware parser’s get_ap_metadata_data_from_flash (src/nv/fw_parser/fw_parser_ap.cpp:17) returns metadata only when the stored ap_auth_result is Success or ApAuthInProgress (src/nv/fw_parser/fw_parser_ap.cpp:34) — the persisted verdict is the gate.


AP background copy

src/nv/vrot/ap_background_copy.cpp mirrors one flash slot into the other, chunk by chunk, so an authenticated image can be duplicated to the backup slot without blocking. It is a small state machine (Idle/Pending/InProgress/ Done/Failed, src/nv/vrot/ap_background_copy.cpp:38) serviced incrementally:

  • start validates the source/dest pair and arms the copy (src/nv/vrot/ap_background_copy.cpp:275).
  • service advances one ChunkSize = 256-byte chunk per call (src/nv/vrot/ap_background_copy.cpp:34, :312) via next_chunk (src/nv/vrot/ap_background_copy.cpp:206).
  • Each chunk reads through read_partition and writes through write_partition, which transparently split a chunk that straddles the image/metadata boundary at ap.metadata_offset (src/nv/vrot/ap_background_copy.cpp:155).
  • After every chunk it releases the underlying resource and yields for ReleasedWindowUs (src/nv/vrot/ap_background_copy.cpp:261) so higher priority work is not starved.

The interface exposes the copy behind supports_ap_background_copy / background_copy_begin / background_copy_end (src/nv/vrot/interface/interface.h:108), again dispatched only for backends that opt in — CPLD does not (src/nv/vrot/interface/interface.cpp:326).


How the boot chain establishes trust

Putting the pieces together (mostly fact, some interpretation where noted):

  1. The SMA firmware ships with two trusted AP public keys and boots first; it is the root that decides whether the AP may run.
  2. SecureBoot holds the AP in reset and claims its flash before any verification, so the AP cannot execute unverified code (src/nv/secure_boot/secure_boot.cpp:394).
  3. The SPDM helper verifies the signed metadata and every sub-image hash against the pinned keys, applying rollback and revocation policy (src/nv/spdm/spdm_crypto_helper.cpp:106).
  4. Only a Success verdict releases reset (src/nv/secure_boot/secure_boot.cpp:462); a failure retries the other slot once, then latches Fatal.
  5. The verdict is persisted and becomes the gate for every later metadata read (src/nv/fw_parser/fw_parser_ap.cpp:34).

Interpretation: trust is anchored in the SMA’s own signed firmware and its built-in key set; the AP inherits trust transitively only after a full metadata-signature + per-image-hash check. The scheme protects against tampered or rolled-back AP images, not against a compromised SMA root itself.


Where to go next

  • Crypto & firmware parsing — the AES-GCM/key-wrap primitives, debug-token TLVs, and the image parsers that feed this chain.
  • Security & Root-of-Trust overview — section map.
  • The SPDM messaging layer that carries the authentication request is covered under the protocols section (SPDM); the crypto helper entry point is send_authenticate_firmware_request (src/nv/spdm/spdm_crypto_helper.h:233).
  • Build system — how ApList and per-project config.h select which APs (and therefore which vRoT backends) compile in.

Built with Just the Docs. Source-traced against NVIDIA/OpenSMA at 9b183d3 (v02.0020.0000). Methodology under appendices/trace-methodology.

This site uses Just the Docs, a documentation theme for Jekyll.