#52283: fix: read asar contents through the archive's retained file handle
Description of Change
Reads of packed asar contents re-opened the archive by path on every read, while using file offsets and per-file integrity hashes from the cached header. If the asar on disk is replaced while the app is running — enterprise MDM / updater software swapping the app bundle is the common case — the fresh open reads the new file's bytes at the old header's offsets. Depending on timing this presents as:
- a fatal browser-process crash in
asar::ValidateIntegrityOrDie(viaasar::ReadFileToStringreading a preload script atReadyToCommitNavigation), or - a spurious
ENOENT: no such file or directory, open '.../app.asar/...'preload error when the file is briefly missing mid-swap, or - the equivalent block-hash
LOG(FATAL)inAsarFileValidatorfor apps that serve their UI overfile://from the asar.
The first two became much more visible after #51602 moved preload reads from main-process JS into C++: the JS read path (asar-fs-wrapper.ts) reads through the archive's retained fd and exits cleanly on mismatch, so the same race previously produced no crash reports.
This PR makes the C++ read paths behave like the JS path — read through the base::File the Archive has held open since its header was validated:
Archive::ReadFileAt()— positional reads on the retained handle;asar::ReadFileToString()now uses it (covers the three preload startup-data readers andnativeImagefile reads).Archive::DuplicateFile()— hands streaming consumers a duplicate of the retained handle;AsarURLLoaderuses it for packed files instead of re-opening by path.- The unused
Archive::path()accessor is removed so by-path opens of archive contents can't be reintroduced accidentally.
Behavior after this change: reads stay consistent with the header that was integrity-validated at open time, so an app whose asar is swapped underneath it keeps serving the version it launched with until relaunch (the pre-#51602 behavior). Note this also means packed reads from a deleted archive now succeed from the retained handle instead of returning ENOENT — intentional, as the transient-deletion window during non-atomic updates was the source of the spurious preload errors.
Security: integrity enforcement is unchanged in strength — in-place tampering is still caught, because the retained handle sees the modified bytes and the hash check fails exactly as before. A swap can no longer produce a false-positive integrity failure, and an attacker who replaces the file gains nothing since the retained handle never observes the replacement.
Verified on macOS with a fused (embeddedAsarIntegrityValidation) packaged build: swapping the asar mid-run previously crashed with the reported ValidateIntegrityOrDie stack on the next navigation and now serves the original content cleanly; removing the asar mid-run previously produced the handled ENOENT preload error and now also serves the original content; a tampered-in-place asar still fails integrity validation fatally.
Checklist
- PR description included and stakeholders cc'd
-
npm testpasses locally - relevant documentation is changed or added (n/a — no API change)
- PR title follows semantic commit guidelines
Release Notes
Notes: Fixed a browser-process crash (ValidateIntegrityOrDie) and spurious preload ENOENT errors when an app's app.asar is replaced on disk (e.g. by an updater or MDM software) while the app is running.
Backports
Semver Impact
Semantic Versioning helps users understand the impact of updates:
- Major (X.y.z): Breaking changes that may require code modifications
- Minor (x.Y.z): New features that maintain backward compatibility
- Patch (x.y.Z): Bug fixes that don't change the API
- None: Changes that don't affect using facing parts of Electron