Skip to content

flash: sign_slot0 cannot tell a produced ATOC from a stale one, so a soft-failing re-sign burns a mismatched pair into MRAM #365

Description

@alpCaner

Found while reviewing

feat/v06-batch at 13891f1, reviewing the tan-cli#353 SETOOLS work that landed in a4ef232 + 624d2c2.

python/tan/core/setools.py:261-274. After spawning app-gen-toc, sign_slot0 decides it succeeded by reading two FIXED output paths:

  • read_atoc_address parses whatever build/app-package-map.txt currently holds;
  • the blob check is os.path.isfile(build/AppTocPackage.bin).

Neither establishes that this spawn wrote them. os.path.isfile cannot tell produced from left over.

Reproduction

Seed the two paths as a previous run would have left them, then run sign_slot0 against an app-gen-toc that exits 0 and writes nothing:

build/app-package-map.txt  <- "APP Package Start Address: 0xdeadbeef"
build/AppTocPackage.bin    <- b'OLD-ATOC-FROM-A-PREVIOUS-APP'
RETURNED ADDRESS: 0xdeadbeef
BLOB CONTENT    : b'OLD-ATOC-FROM-A-PREVIOUS-APP'

plan_alif_mram_jlink then emits

loadbin <stale AppTocPackage.bin> 0xdeadbeef

alongside the NEW app blob at slot0_load_address.

Impact

A mismatched ATOC/app pair written to on-die MRAM. That is the re-provision-over-SE-UART case, not a re-run — the board does not boot and the recovery path is the SETOOLS ISP channel. The realistic trigger is a re-sign after a rebuild where the second app-gen-toc fails soft: the customer sees a successful flash and a dead board.

The docstring at setools.py:210-212 claims the function raises on "a successful exit that did not actually produce the ATOC blob". It does not, and cannot as written — this is the same defect class as #299/#301/#306/#310/#312/#344/#350/#355: a check reporting a verdict it did not verify.

Not yet observed in the wild: no measured app-gen-toc invocation that exits 0 without writing. The refusal is cheap and the failure is silent, unrecoverable and hardware-facing, so this should not wait for a field report.

Acceptance criteria

  • sign_slot0 records st_mtime_ns and size of both output paths before the spawn and refuses if either is unchanged afterwards, or unlinks both before spawning so their mere existence proves this run.
  • The refusal names which artefact was stale and the path it was read from.
  • A regression test seeds both paths, spawns a fake app-gen-toc that exits 0 and writes nothing, and asserts the refusal — it must FAIL against current main.
  • The docstring's claim matches what the code actually checks.

Related: #353, #311.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions