ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_dfu_antirollback.h File Reference

DFU anti-rollback (downgrade protection) policy + storage seam. More...

#include <stdint.h>
#include "ra8_err.h"
Include dependency graph for ra8_dfu_antirollback.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  ra8_rot_antirollback_store_t
 Dependency-injection vtable for the non-volatile version counter. More...

Typedefs

typedef ra8_err_t(* ra8_rot_antirollback_read_fn_t) (uint32_t *out_min_version)
 Read the stored highest-accepted image version (DI seam).
typedef ra8_err_t(* ra8_rot_antirollback_commit_fn_t) (uint32_t new_version)
 Durably advance the stored highest-accepted image version (DI seam).

Functions

ra8_err_t ra8_rot_antirollback_check (uint32_t image_version, uint32_t stored_min_version)
 Pure downgrade policy: accept iff the image is not older than stored.
ra8_err_t ra8_rot_antirollback_verify (const ra8_rot_antirollback_store_t *store, uint32_t image_version)
 Read the stored minimum, apply the policy, and commit on accept.
const ra8_rot_antirollback_store_tra8_rot_antirollback_default_store (void)
 Return the non-faking default store (reports "not provisioned").
bool ra8_rot_antirollback_on_probe_fault (uint32_t *exc_frame)
 Recover a fault-tolerant counter probe from the app fault handler.

Detailed Description

DFU anti-rollback (downgrade protection) policy + storage seam.

Tag
[Ring 4 / Service] {World: S}

Anti-rollback prevents an attacker re-flashing an OLDER but still correctly-signed image – one that may carry known, since-patched vulnerabilities. It is the second half of the copy-to-run trust gate: ra8_rot_verify_image (ra8_rot.h) proves an image is authentic; this module proves the authentic image is not a downgrade.

Policy

Each signed image's ra8_rot_trailer_t carries a monotonic img_version. The device keeps the highest version it has ever accepted in a non-volatile monotonic counter. The launch path:

  1. reads the stored minimum version through the injected store;
  2. accepts iff img_version >= stored_min_version (equal is the re-flash-same-version case and is allowed); and
  3. on accept, advances the stored counter to the new version.

Any failure – a null store, a failed read, or (per the default-deny contract) a failed commit – denies the launch.

Storage dependency-injection seam

The non-volatile counter is reached only through a ra8_rot_antirollback_store_t vtable (NASA Rule 9 deviation: function pointers enable Dependency Inversion and host mock injection). Host unit tests inject a mock counter; production wires the real backing once it exists. ra8_rot_antirollback_default_store returns a non-faking stub whose read/commit report "not provisioned" so that, until a real counter is wired, the gate DEFAULT-DENIES rather than silently passes.

Enabling (opt-in, default OFF)

Gated behind RA8_ENABLE_ROOT_OF_TRUST (default OFF), the same flag as the root of trust. With the flag OFF the implementation in ra8_dfu_antirollback.c compiles to nothing (an empty translation unit, mirroring ra8_rot.c) and the launch path does not check versions, so existing apps are byte-for-byte unchanged. The declarations below are always visible and reference no external symbol, so a flag-off translation unit gains no link dependency.

Note
The trailer img_version IS covered by the image signature: the ECDSA signature authenticates SHA-256 of the little-endian img_version concatenated with the body digest, rather than the bare body digest (see ra8_rot_verify_image / internal_bind_version in ra8_rot.c, matched by the signer scripts/secrets/rot_sign.py). An attacker holding an older validly-signed image therefore cannot raise the trailer version to defeat this check – the forged version invalidates the signature (T5-05).
Warning
The downgrade check is only as strong as the durable counter store, which is provisioning-gated: on a fresh device the extra-MRAM counter word is blank and cannot be programmed at runtime on this silicon (no BlankCheck – #194), so the first authentic image sets no floor and anti-rollback begins enforcing only once the counter is provisioned (see internal_default_store_commit in ra8_dfu_antirollback.c). Provisioning that initial counter word is bench-gated.

Definition in file ra8_dfu_antirollback.h.

Typedef Documentation

◆ ra8_rot_antirollback_commit_fn_t

typedef ra8_err_t(* ra8_rot_antirollback_commit_fn_t) (uint32_t new_version)

Durably advance the stored highest-accepted image version (DI seam).

Writes new_version to the device's non-volatile monotonic anti-rollback counter after an image has been accepted, so future downgrades below this version are rejected. The production implementation programs an OTP / data-flash counter; the host test implementation records a mock variable.

Parameters
[in]new_versionThe just-accepted image version to persist.
Returns
ra8_err_t Error code.
Return values
k_ra8_okCounter advanced (or already at/above new_version).
otherBacking-store program fault (verifier default-denies).
Note
Thread safety is the implementation's responsibility; the boot-path caller is single-threaded.
Since
0.1.0

Definition at line 123 of file ra8_dfu_antirollback.h.

◆ ra8_rot_antirollback_read_fn_t

typedef ra8_err_t(* ra8_rot_antirollback_read_fn_t) (uint32_t *out_min_version)

Read the stored highest-accepted image version (DI seam).

Reads the device's non-volatile monotonic anti-rollback counter – the highest image version ever accepted. The production implementation reads an OTP / data-flash counter; the host test implementation reads a mock variable. Returning any non-k_ra8_ok code causes the verifier to DEFAULT-DENY.

Parameters
[out]out_min_versionReceives the stored minimum version; non-NULL.
Returns
ra8_err_t Error code.
Return values
k_ra8_ok*out_min_version holds the stored counter.
k_ra8_err_null_ptrout_min_version is NULL.
otherBacking-store read fault (verifier default-denies).
Note
Thread safety is the implementation's responsibility; the boot-path caller is single-threaded.
Since
0.1.0

Definition at line 101 of file ra8_dfu_antirollback.h.

Function Documentation

◆ ra8_rot_antirollback_check()

ra8_err_t ra8_rot_antirollback_check ( uint32_t image_version,
uint32_t stored_min_version )
nodiscard

Pure downgrade policy: accept iff the image is not older than stored.

The side-effect-free core of anti-rollback. An image is accepted when its version is greater than or equal to the stored minimum (equal allows a same-version re-flash); a strictly lower version is a downgrade and is rejected. No storage is touched – callers read the stored minimum first (see ra8_rot_antirollback_verify) and default-deny on any read failure before reaching this comparison.

Parameters
[in]image_versionVersion recorded in the candidate image trailer.
[in]stored_min_versionHighest version the device has ever accepted.
Returns
ra8_err_t Error code.
Return values
k_ra8_okimage_version >= stored_min_version (newer or equal – launch permitted).
k_ra8_err_validation_failedimage_version < stored_min_version (downgrade – launch refused).
Precondition
stored_min_version was produced by a successful store read.
image_version came from an already-authenticated image trailer.
Postcondition
No state is mutated; the result depends only on the inputs.
On any non-k_ra8_ok return the caller must NOT launch the image.
Note
Thread-safe (pure; no statics).
See also
ra8_rot_antirollback_verify
Since
0.1.0

◆ ra8_rot_antirollback_default_store()

const ra8_rot_antirollback_store_t * ra8_rot_antirollback_default_store ( void )

Return the non-faking default store (reports "not provisioned").

The real OTP / data-flash monotonic counter is not yet wired (see the TODO in ra8_dfu_antirollback.c). Rather than fake a passing counter, this default store's read and commit both report a "not provisioned" status, so ra8_rot_antirollback_verify DEFAULT-DENIES until a real backing is wired. The returned pointer is to storage with static lifetime; the caller must not free it.

Returns
Pointer to the process-lifetime default store; never NULL.
Return values
non-NULLThe default (not-provisioned) store.
Precondition
RA8_ENABLE_ROOT_OF_TRUST is defined (otherwise this symbol is absent).
The caller treats a verify failure against this store as DEFAULT-DENY.
Postcondition
No state is mutated; the same pointer is returned on every call.
The returned store's read and commit never report k_ra8_ok.
Note
Thread-safe (returns a pointer to immutable static data).
See also
ra8_rot_antirollback_verify
Since
0.1.0

◆ ra8_rot_antirollback_on_probe_fault()

bool ra8_rot_antirollback_on_probe_fault ( uint32_t * exc_frame)

Recover a fault-tolerant counter probe from the app fault handler.

The default store reads the durable counter (extra-MRAM at k_ra8_flash_extra_start) under a transient fault-catch, because a never-written (blank) ECC-protected extra-MRAM word bus-faults on read and the RA8D2 has no MRAM BlankCheck command (#194). The app's BusFault/HardFault handler must call this first with a pointer to the exception stack frame ([R0 R1 R2 R3 R12 LR PC xPSR]): when a probe is in progress it records the fault, advances the stacked PC past the faulting load, clears the sticky fault status, and returns true so the handler does a plain exception return (the read then maps the blank word to version 0). Outside a probe it returns false and the handler proceeds with its normal fault reporting. No-op under RA8_OFF_TARGET.

Parameters
[in,out]exc_frameException stack frame captured at handler entry (MSP or PSP per EXC_RETURN); its stacked PC (index 6) is advanced.
Returns
bool True if a probe fault was recovered; false otherwise.
Return values
trueProbe fault recovered (handler should exception-return).
falseNot a probe fault (or exc_frame is NULL) – handle as real.
Precondition
Called only from a fault exception handler with a valid frame pointer.
exc_frame points at an 8-word basic exception frame.
Postcondition
On true the frame's stacked PC skips the faulting load and CFSR is cleared.
On false no state is changed.
Note
Not thread-safe; runs in handler context on the boot path.
See also
ra8_rot_antirollback_verify
Since
0.1.0

Referenced by blc_fault_dispatch().

◆ ra8_rot_antirollback_verify()

ra8_err_t ra8_rot_antirollback_verify ( const ra8_rot_antirollback_store_t * store,
uint32_t image_version )
nodiscard

Read the stored minimum, apply the policy, and commit on accept.

The full anti-rollback gate used by the copy-to-run launch path. Reads the stored highest-accepted version through store, runs ra8_rot_antirollback_check, and – only on accept – advances the stored counter to image_version. Enforces DEFAULT-DENY: a NULL store, a NULL accessor, a failed read, a downgrade verdict, or a failed commit all return a non-k_ra8_ok code and the caller must NOT launch.

Parameters
[in]storeStorage DI vtable; non-NULL with non-NULL members.
[in]image_versionVersion from the authenticated image trailer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okAccepted and the counter was advanced.
k_ra8_err_null_ptrstore (or a member) is NULL.
k_ra8_err_validation_failedThe image is a downgrade.
otherStore read or commit reported a fault.
Precondition
image_version belongs to an image that already passed ra8_rot_verify_image (authenticity before freshness).
The boot path is single-threaded (the store is not re-entrant).
Postcondition
On k_ra8_ok the store's persisted version is >= image_version.
On any non-k_ra8_ok return the caller must NOT launch the image.
Note
Not thread-safe: mutates the device's non-volatile counter via store->commit. Call from the single-threaded boot path.
See also
ra8_rot_antirollback_check
ra8_rot_antirollback_default_store
Since
0.1.0