Skip to main content

<alp/update_log.h> — Firmware-Update Audit Log

A portable, tamper-evident record of every firmware update applied to a device — one surface across SoMs.

The software tier (every target today) gives a hash-chained, monotonic-counter-anchored log that detects mutation, truncation, rollback, and reorder. On SoMs with a secure backend the same API is hardware-enforced (TF-M Protected Storage + a non-decrementable monotonic counter). Query alp_update_log_assurance() to learn which tier you got.

#include <alp/update_log.h>

Record an update

alp_update_log_t *log = alp_update_log_open(); // NULL if no backend present

alp_update_log_entry_t entry = {
.status = ALP_UPDATE_STATUS_PENDING_CONFIRM,
.fw_version = "1.4.2",
.timestamp = epoch_now(),
// .image_hash[] = SHA-256 of the image
};
alp_update_log_append(log, &entry); // engine assigns entry.seq

seq is engine-assigned from the monotonic counter on append() — callers fill everything except seq. On get() every field is populated.

Trusted boot-metadata append

Prefer alp_update_log_append_boot() over the hand-filled append() when the platform can supply authenticated boot metadata. That path asks the SDK's boot-metadata provider for the booted image's version, SHA-256 image hash, and verification status, then appends the entry without accepting app-supplied identity fields — so firmware can't forge the logged booted-image version, hash, or verification status.

// Preferred update-audit path once a board wires a boot-metadata provider.
alp_update_log_append_boot(log, epoch_now()); // ALP_ERR_NOSUPPORT if no provider

// Or build the entry first (to inspect it) without appending:
alp_update_log_entry_t e;
if (alp_update_log_entry_from_boot_metadata(&e, epoch_now()) == ALP_OK) {
// e.fw_version / e.image_hash / e.status come from the trusted provider;
// e.seq is zeroed — the append engine assigns it.
}

The default provider returns ALP_ERR_NOSUPPORT, so callers avoid writing forged-but-well-formed audit entries by accident. Board integrations override it with authenticated facts (MCUboot shared-data / Alif SE verification).

Persistence (software tier)

With CONFIG_ALP_SDK_UPDATE_LOG_PERSIST (default y when CONFIG_NVS is on) and an alp_ulog_partition fixed partition in the board devicetree, the software tier stores its keyed entry store and its monotonic counter in Zephyr NVS — so the tamper-evident chain survives reboot and firmware update. Boards without the partition fall back to RAM (entries vanish on reboot).

The log is append-only and never wraps — wrapping would erase audit history. When the backing partition is full, alp_update_log_append() (and alp_update_log_append_boot()) return ALP_ERR_NOMEM and the existing chain stays intact and verifiable.

Persistence does not change the assurance level: the software tier is tamper-evident, not tamper-proof. alp_update_log_verify() detects out-of-band mutation, truncation, rollback, and reorder, but code that can write the backing partition can rebuild the store and counter consistently and forge history. App-immutability is the ALP_UPDATE_LOG_HW_ENFORCED tier's job (TF-M isolation + a hardware counter), which is unchanged.

Verify integrity

alp_update_log_verdict_t verdict;
uint64_t bad_seq;
alp_update_log_verify(log, &verdict, &bad_seq);

switch (verdict) {
case ALP_UPDATE_LOG_VERIFY_OK: /* chain intact */ break;
case ALP_UPDATE_LOG_VERIFY_CHAIN_BROKEN: /* entry mutated/reordered */ break;
case ALP_UPDATE_LOG_VERIFY_TRUNCATED: /* tail entries missing */ break;
case ALP_UPDATE_LOG_VERIFY_ROLLED_BACK: /* store regressed vs anchor */ break;
}

alp_update_log_close(log);

verify() returns ALP_OK when the walk ran (inspect verdict); ALP_ERR_IO only if the store was unreadable. bad_seq is the offending entry on CHAIN_BROKEN / TRUNCATED (may be NULL).

Entry fields

FieldTypeNotes
sequint64_tAuthoritative order; engine-assigned.
fw_versionchar[32]NUL-terminated (max ALP_UPDATE_LOG_FWVER_MAX = 31).
image_hashuint8_t[32]SHA-256 of the image (ALP_UPDATE_LOG_HASH_LEN = 32).
statusalp_update_status_tCONFIRMED / VERIFY_FAILED / ROLLED_BACK / PENDING_CONFIRM.
timestampuint64_tBest-effort epoch; 0 = unset.

Functions

CallReturns
alp_update_log_open(void)Handle, or NULL if no backend is present.
alp_update_log_append(log, entry)ALP_OK / ALP_ERR_INVAL / ALP_ERR_IO / ALP_ERR_NOSUPPORT; ALP_ERR_NOMEM when the store is full.
alp_update_log_entry_from_boot_metadata(entry_out, ts)ALP_OK / ALP_ERR_NOSUPPORT (no provider) / ALP_ERR_INVAL.
alp_update_log_append_boot(log, ts)ALP_OK / ALP_ERR_INVAL / ALP_ERR_IO / ALP_ERR_NOSUPPORT; ALP_ERR_NOMEM when the store is full.
alp_update_log_verify(log, verdict, bad_seq)ALP_OK (walk ran) / ALP_ERR_IO.
alp_update_log_count(log, count_out)Number of entries.
alp_update_log_get(log, seq, entry_out)ALP_ERR_NOT_FOUND if absent.
alp_update_log_assurance(log)ALP_UPDATE_LOG_SW_TAMPER_EVIDENT or ALP_UPDATE_LOG_HW_ENFORCED.
alp_update_log_close(log)void.

Assurance tiers

TierProtection
ALP_UPDATE_LOG_SW_TAMPER_EVIDENTSHA-256 hash-chain + monotonic counter; app-cooperative. The portable default on every SoM today.
ALP_UPDATE_LOG_HW_ENFORCEDTF-M-isolated Protected Storage + a hardware non-decrementable monotonic counter.

ABI status

[ABI-EXPERIMENTAL] — new in v0.7. The surface may change until the hardware backend is silicon-proven.

See also

Questions about this page? Discuss in Community Forum