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.
Found while reviewing
feat/v06-batchat13891f1, reviewing the tan-cli#353 SETOOLS work that landed ina4ef232+624d2c2.python/tan/core/setools.py:261-274. After spawningapp-gen-toc,sign_slot0decides it succeeded by reading two FIXED output paths:read_atoc_addressparses whateverbuild/app-package-map.txtcurrently holds;os.path.isfile(build/AppTocPackage.bin).Neither establishes that this spawn wrote them.
os.path.isfilecannot tell produced from left over.Reproduction
Seed the two paths as a previous run would have left them, then run
sign_slot0against anapp-gen-tocthat exits 0 and writes nothing:plan_alif_mram_jlinkthen emitsalongside 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-tocfails soft: the customer sees a successful flash and a dead board.The docstring at
setools.py:210-212claims 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-tocinvocation 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_slot0recordsst_mtime_nsand 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.app-gen-tocthat exits 0 and writes nothing, and asserts the refusal — it must FAIL against currentmain.Related: #353, #311.