Summary
IntegrityHelper.GetFileIntegrity assumes every Stream.Read returns a full block. A short read produces an undersized entry in integrity.blocks and shifts every boundary after it, yielding metadata that fails Electron's validateAsarIntegrity.
Observed on master @ 643c8f8 (1.0.9.4).
Root cause
AsarSharp/Integrity/IntegrityHelper.cs:63-67
while ((bytesRead = fileStream.Read(reusableBuffer, 0, reusableBuffer.Length)) > 0)
{
blockHashes.Add(ToLowerHex(blockHash.ComputeHash(reusableBuffer, 0, bytesRead)));
fileHash.AppendData(reusableBuffer, 0, bytesRead);
}
Stream.Read is contractually allowed to return fewer bytes than requested at any point, not just at end of file. The loop hashes whatever each call returned as though it were a complete block, so:
- a short read becomes an undersized
blocks[] entry, and
- every subsequent block starts at the wrong offset.
Electron requires every block except the last to be exactly blockSize, so the emitted integrity fails validation. The whole-file hash is unaffected — AppendData is order-correct regardless of chunking — so only per-block validation breaks.
Reachability
GetFileIntegrity is live code, not a dead helper. AsarSharp/AsarFileSystem/FileSystem.cs calls it as the fallback whenever no precomputed integrity is supplied:
// :175 (unpacked branch) and :184 (packed branch)
node.Integrity = precomputedIntegrity ?? IntegrityHelper.GetFileIntegrity(path);
Note the StreamingHasher class in the same file (:79-141) already implements the correct accumulate-then-emit rule, and it is what the main WriteFileSystem / CopyAndHash path uses. That is why packing has not visibly broken — the defect is confined to callers that reach GetFileIntegrity directly.
Trigger
Any input whose 4 MiB read returns short mid-file. In practice this is uncommon for ordinary local files, which is why it has gone unnoticed, but it is permitted by the API and becomes likelier with large multi-GB files, network/virtualised paths, or a non-plain-file stream. When it happens the corruption is certain rather than probabilistic.
Verification
I simulated the read/boundary arithmetic against Electron's rule (block size scaled down; identical arithmetic):
scenario expected master fixed verdict
short read mid-file 5 6 5 master diverges
every read short (1 byte) 5 40 5 master diverges
ragged reads 5 6 5 master diverges
all reads full 32 32 32 both correct
exact multiple of block 4 4 4 both correct
single sub-block file 1 1 1 both correct
empty file 0 0 0 both correct
master and the fix agree whenever every read is full — which is the normal case, and why this is latent rather than constantly visible. They diverge on every short-read scenario. The whole-file hash is preserved in all cases.
Suggested fix
Accumulate reads into the block buffer and emit a hash only once a full block is present, flushing the remainder after the loop — i.e. the rule StreamingHasher.Append/Finalise already applies. A fix is attached.
Summary
IntegrityHelper.GetFileIntegrityassumes everyStream.Readreturns a full block. A short read produces an undersized entry inintegrity.blocksand shifts every boundary after it, yielding metadata that fails Electron'svalidateAsarIntegrity.Observed on
master@643c8f8(1.0.9.4).Root cause
AsarSharp/Integrity/IntegrityHelper.cs:63-67Stream.Readis contractually allowed to return fewer bytes than requested at any point, not just at end of file. The loop hashes whatever each call returned as though it were a complete block, so:blocks[]entry, andElectron requires every block except the last to be exactly
blockSize, so the emittedintegrityfails validation. The whole-filehashis unaffected —AppendDatais order-correct regardless of chunking — so only per-block validation breaks.Reachability
GetFileIntegrityis live code, not a dead helper.AsarSharp/AsarFileSystem/FileSystem.cscalls it as the fallback whenever no precomputed integrity is supplied:Note the
StreamingHasherclass in the same file (:79-141) already implements the correct accumulate-then-emit rule, and it is what the mainWriteFileSystem/CopyAndHashpath uses. That is why packing has not visibly broken — the defect is confined to callers that reachGetFileIntegritydirectly.Trigger
Any input whose 4 MiB read returns short mid-file. In practice this is uncommon for ordinary local files, which is why it has gone unnoticed, but it is permitted by the API and becomes likelier with large multi-GB files, network/virtualised paths, or a non-plain-file stream. When it happens the corruption is certain rather than probabilistic.
Verification
I simulated the read/boundary arithmetic against Electron's rule (block size scaled down; identical arithmetic):
masterand the fix agree whenever every read is full — which is the normal case, and why this is latent rather than constantly visible. They diverge on every short-read scenario. The whole-file hash is preserved in all cases.Suggested fix
Accumulate reads into the block buffer and emit a hash only once a full block is present, flushing the remainder after the loop — i.e. the rule
StreamingHasher.Append/Finalisealready applies. A fix is attached.