<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.
Header
#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
| Field | Type | Notes |
|---|---|---|
seq | uint64_t | Authoritative order; engine-assigned. |
fw_version | char[32] | NUL-terminated (max ALP_UPDATE_LOG_FWVER_MAX = 31). |
image_hash | uint8_t[32] | SHA-256 of the image (ALP_UPDATE_LOG_HASH_LEN = 32). |
status | alp_update_status_t | CONFIRMED / VERIFY_FAILED / ROLLED_BACK / PENDING_CONFIRM. |
timestamp | uint64_t | Best-effort epoch; 0 = unset. |
Functions
| Call | Returns |
|---|---|
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
| Tier | Protection |
|---|---|
ALP_UPDATE_LOG_SW_TAMPER_EVIDENT | SHA-256 hash-chain + monotonic counter; app-cooperative. The portable default on every SoM today. |
ALP_UPDATE_LOG_HW_ENFORCED | TF-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
- Examples: firmware-update-log
<alp/security.h>— the crypto primitives (SHA-256) underneath<alp/storage.h>— durable persistence