Skip to content

fix(kaos): Guard what an install evicts: file check, copy and CRC - #125

Merged
jbenedictocenteno merged 4 commits into
developfrom
fix/install-evictions
Nov 13, 2026
Merged

jbenedictocenteno merged 4 commits into
developfrom
fix/install-evictions

Conversation

@jbenedictocenteno

Copy link
Copy Markdown
Contributor

Description of Change

Three changes to an install that has to give up installed apps to make room.

  • The file is read through first (Refs #103). An install from a file overwrote what was installed before the file was known to be whole: a cut file cost the app it displaced. When the plan gives something up, AO_AppMgr now reads the file once before anything else, with the new kdrv_image_check: the header chain against the file's length and the SHA-256 the image carries. A file that does not hold together ends the install with nothing given up. A URL, a terminal install and a .kfw container are not covered, which is why the issue stays open.
  • A victim's copy is written under a temporary name (Closes #104). The copy went to /sdcard/<app>.bin in place, so a reset or a pulled card left a cut image under the app's name. It is written as <app>.bin.tmp and put on its name by one new AO_Storage request, STORAGE_PAIR_REPLACE, which moves what is there aside, puts the copy on the name and puts the original back if a step fails. The name never holds a cut file.
  • The CRC of each victim is taken in pieces (Closes #118). It was taken in one step, about 70 ms per MB with every Active Object below held back. The keep round takes it 512 KB a step, the app an install replaces included, and keeps the values in PSRAM. AO_AppMgr's own static shrinks to 2008 bytes.

Test Scenarios

  • tools/host_tests.sh: 5531 cases across 8 suites, all passing. tools/check_layers.sh clean. tests/target in both variants, with check_ram.sh and check_starts.sh.
  • A kodeOS build on this branch: size, RAM budget, layer rules, the MISRA gate (nothing past the baseline) and the tool tests.
  • On a DVT board with a card that takes writes on one line only:
    • a file cut inside its first segment is refused at its header; one cut later, whose install would evict, is refused by the read-through in 0.2 s with the apps and the card as they were;
    • a whole file reads through (0.85 s for 3.1 MB) and installs;
    • a copy onto a name that is taken, onto a free name, and with a .bak already there: the whole copy ends on the name, nothing is left over, and the copy installs afterwards;
    • a reset in the middle of a copy leaves the file that was there whole;
    • a reset early in a write that evicted two apps: the one the write reached is reported gone, the other is kept, its CRC matching at start-up; a reset late in a write reports every app it reached;
    • an app updated over itself with no room for both: its CRC is taken in pieces (0.31 s for 3 MB) and the install completes.
  • Not covered: a reset in the middle of a copy leaves <app>.bin.tmp behind until that app is copied again; nothing sweeps it yet.

Related links

Closes #104
Closes #118
Refs #103

The keep round wrote /sdcard/<app>.bin in place. A reset or a pulled
card in the middle left a cut image under the app's name, listed as an
app to install and taken by the next round for the app's copy.

The copy is written as <app>.bin.tmp and put on its name with one new
AO_Storage request, STORAGE_PAIR_REPLACE. FAT renames onto no name that
is taken, so AO_Storage moves the file there to <app>.bin.bak, puts the
copy on the name and unlinks the backup, and puts the original back if
a step fails: one step, one answer. AO_AppMgr asks once and reads one
answer.

The name never holds a cut file. A replacement that is refused, fails
or is never answered fails the copy and leaves the file that was there
on the name; the temporary is unlinked unless the answer is missing. A
reset between the two renames leaves that file as <app>.bin.bak and the
copy as <app>.bin.tmp; the next replacement of the name removes the
backup, and says so, as it does for any <app>.bin.bak it finds.

Closes #104
Since the undo table, AO_AppMgr took the CRC of every victim of an
install in one step: about 70 ms per MB, with every Active Object below
it held back. The keep round now takes each victim's CRC 512 KB a step,
one step a pacer tick, as the recovery at start-up does, before it asks
the card about that victim. The CRCs wait in a PSRAM block of the round,
as the undo table does, not in the Active Object's own memory.

The app an install replaces is evicted like a victim when there is no
room for both versions, and its CRC goes into the same table. It is in
the round for that CRC alone: no question to the card, no copy.

The second pass finds the CRCs taken and writes the table to NVS before
it evicts or writes anything. A victim the round did not reach, a
terminal install's or one for which PSRAM ran dry, is read in one step
as before.

Closes #118
An image that comes from a file can only be judged once it has been
written, by esp_image_verify() over the region. Something that must not
give up an installed app for a file that may be cut needs the verdict
first.

kdrv_image_check takes the file's bytes in any pieces and follows the
header chain against the file's length, refusing a segment that leaves
it as soon as its header is read. When the image carries a SHA-256 it
folds one over the bytes before it and compares at the end. Nothing is
read from flash or written.
An install from a file overwrote what was installed before the file was
known to be whole. A file cut short of its image, of an app already
installed, cost the app and ended in "did not verify".

When the plan has to give something up (it evicts, or drops the old copy
of the app it replaces), AO_AppMgr stops at it and reads the file through
once in AppMgr_verifying: the header chain against the file's length and
the SHA-256 the image carries. A file that does not hold together ends
the install with nothing given up. One that does starts the install
again, as the keep round does. The install's own second read, served
before the read-through's open has answered, is ignored.

A URL and a terminal install cannot be read twice, and the mailbox is
verified in place. A .kfw container is not covered: its decoder places
the image, and so evicts, at its header and compares the digest only at
the end, so a cut container still costs what it gives up. An image with
no appended hash is judged by its segment chain alone.

Refs #103
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant