Skip to content

fix: stop the raw install-if-different check at the end of the object - #449

Merged
otavio merged 2 commits into
masterfrom
fix/install-if-different-object-region
Sep 10, 2026
Merged

fix: stop the raw install-if-different check at the end of the object#449
otavio merged 2 commits into
masterfrom
fix/install-if-different-object-region

Conversation

@otavio

@otavio otavio commented Sep 10, 2026

Copy link
Copy Markdown
Member

What

An object installed with the raw handler could be skipped as "unchanged" on the strength of a different object's version string, leaving the old binary on the device while everything around it updated.

The failure

An i.MX6ULL package writes SPL at seek 1 and u-boot.img at seek 64 of the same /dev/mtdblock1, both guarded by the same U-Boot version. Install::handle sorts objects largest first, so U-Boot proper is written first. The SPL check then ran with count: -1, which handed should_skip_install a reader over the whole device — it read straight past the SPL's own 56320 bytes into the U-Boot image written moments earlier, found the version it was looking for, and skipped the SPL.

Downgrading a device left it with the new SPL and everything else rolled back.

The fix

Bound the check to the bytes the install writes:

let region_len = if self.compressed {
    self.required_uncompressed_size
} else {
    let written = self.size.saturating_sub(skip);
    count.map_or(written, |n| n.min(written))
};

The two cases differ because skip and count measure the source, not the region it lands in: for a compressed object the source is compressed, so narrowing an uncompressed length by a compressed byte count would under-bound the check. Converting count to a byte length once, up front, also lets the write path share it, which drops the self.count.clone() that only existed to allow matching the enum twice. With both arms of the check now yielding the same type, the local AsyncReadSeek trait and its Box<dyn> go away.

flash.rs and imxkobs.rs were left alone deliberately. Flash erases and rewrites the whole MTD partition, so its check already covers exactly its own region, and a size bound would cut off the banner on NAND where nandwrite -p skips bad blocks. Imxkobs reads /dev/mtdXro because kobs-ng writes FCB/DBBT plus firmware copies at layout-defined offsets, so there is no region expressible as seek + size.

Tests

  • install_if_different_stops_at_the_end_of_the_object — object at SHARED_OBJECT_AT, neighbour at SHARED_NEIGHBOUR_AT carrying the version the object asks for; the object must still be written. Fails against the previous code with "the neighbour's version answered for this object and skipped it".
  • install_if_different_matches_inside_the_object — the counterpart, so a matching version inside the object's own region still skips.

The dependency bump

The second commit pins find-binary-version to 0.5.2 rather than accepting any 0.5. 0.5.0 read binaries in fixed 0x200 windows with no overlap, so a version banner crossing a boundary was never found and the binary reported no version at all — which check_if_different reads as "different", quietly turning install-if-different into "always install" for whole builds at a time. 0.5.1 fixes that; 0.5.2 additionally stops the custom-pattern search from reading the whole source into memory before matching.

Both halves are needed here: the bound alone made the downgrade work only because nothing was detectable, and the detection fix alone would still have let U-Boot proper answer for the SPL.

Verified against the real packages, driving objects::Raw::install over a simulated /dev/mtdblock1:

kirkstone (fresh flash)            SPL: written
kirkstone (re-install, must skip)  SPL: skipped
wrynose (upgrade)                  SPL: written
wrynose (re-install, must skip)    SPL: skipped
kirkstone (DOWNGRADE)              SPL: written   <- the reported bug

@otavio
otavio force-pushed the fix/install-if-different-object-region branch from f224d35 to a9fd875 Compare September 10, 2026 14:34
The check handed `should_skip_install` a reader over the whole target device
whenever `count` was `-1`, so it kept scanning well past the bytes the object
owns. Objects sharing a device at different offsets then answer for each other.

An i.MX6ULL package hit this on a downgrade. It writes SPL at `seek` 1 and
U-Boot proper at `seek` 64 of the same `/dev/mtdblock1`, both guarded by the
same U-Boot version, and objects install largest first: U-Boot proper landed on
the device first, and the SPL check then read straight through its own region
into the U-Boot image just written, found the version it was looking for, and
skipped the SPL. The device kept the previous SPL while every other object
downgraded.

Bound the reader to the bytes the install writes. For a compressed object that
is the uncompressed size, since `skip` and `count` measure the source rather
than the region they land in; otherwise it is `size` minus `skip`, narrowed by
`count` when it is limited. Turning `count` into a byte length up front also
lets the write path share it, and drops both the clone it needed and the
`AsyncReadSeek` boxing.
@otavio
otavio force-pushed the fix/install-if-different-object-region branch from a9fd875 to 6d16a2b Compare September 10, 2026 14:36
0.5.0 cannot read a version banner that lands across the boundary of one of its
0x200 reads, and reports no version at all when that happens. The check guarding
an object then reads as "the target differs" every time, so an object that never
changed is written on every update, and the comparison the object asked for
never actually runs.

The i.MX6ULL builds this showed up on sat on both sides of that: one pair of
binaries kept their banners clear of a boundary and compared fine, the other pair
straddled one and never compared at all.

0.5.1 carries that fix, and 0.5.2 stops the custom-pattern search from reading
the whole source into memory before matching, which for a rule pointed at a
partition meant holding the image alongside the device it came from. Naming the
version rather than any 0.5 keeps a fresh resolve from picking up either of the
older behaviours.
@otavio
otavio force-pushed the fix/install-if-different-object-region branch from 6d16a2b to dbb2990 Compare September 10, 2026 14:42
@otavio
otavio merged commit f50a88a into master Sep 10, 2026
4 checks passed
@otavio
otavio deleted the fix/install-if-different-object-region branch September 10, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant