Skip to content

fix(asar): emit integrity blocks on real block boundaries - #171

Open
divya0795 wants to merge 1 commit into
k1tbyte:masterfrom
divya0795:fix/asar-integrity-partial-reads
Open

fix(asar): emit integrity blocks on real block boundaries#171
divya0795 wants to merge 1 commit into
k1tbyte:masterfrom
divya0795:fix/asar-integrity-partial-reads

Conversation

@divya0795

@divya0795 divya0795 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #170

Problem

GetFileIntegrity hashed each Stream.Read result as if it were a complete block. Stream.Read may return fewer bytes than requested at any point, not only at EOF, so a short read produced an undersized integrity.blocks[] entry and shifted every boundary after it — metadata that fails Electron's validateAsarIntegrity, which requires every block except the last to be exactly blockSize.

Reachable through FileSystem.InsertFile, which calls it as the precomputedIntegrity ?? … fallback at :175 and :184. The whole-file hash was never affected.

Change

Accumulate reads into the block buffer and emit a hash only when a full block is present, flushing the remainder after the loop. Reads target the tail of the buffer (Read(buf, blockFill, BLOCK_SIZE - blockFill)), so there is no copying and no extra allocation.

Also replaces a caller-supplied buffer shorter than BLOCK_SIZE, since such a buffer cannot produce a valid block either way. No current caller passes one.

A note on reuse

StreamingHasher in the same file already implements this rule correctly, and delegating to it was the obvious way to remove the duplication. I did not, for a concrete reason: StreamingHasher.Append copies the caller's data into its own block buffer, so passing one array as both the read target and the shared block buffer would alias — Buffer.BlockCopy(data, src, _blockBuf, _blockFill, copy) would overwrite unread source bytes whenever _blockFill > 0. The alternatives were an extra 4 MiB allocation per file or a per-byte copy. Fixing the loop in place keeps the copy-free read path; happy to switch if you'd rather have the single implementation.

Verification

No local Visual Studio/MSBuild environment, so this is not compile-verified locally. Compile evidence comes from dispatching this repo's own .github/workflows/build.yml (full build.ps1 → MSBuild, windows-latest) on my fork; run linked in a comment below.

The algorithm itself I verified by simulating the read/boundary arithmetic against Electron's rule (block size scaled down, arithmetic identical):

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 — the normal case, and why this is latent — and diverge on every short-read scenario. Whole-file hash preserved throughout.

The repo has no C# test project, so there is no unit test to add here. I also have no licensed WeMod install, so CONTRIBUTING.md's "manually tested against the current WeMod version" is not satisfied — worth a maintainer check against a real packing run.

@divya0795

Copy link
Copy Markdown
Contributor Author

Compile verification: dispatched this repo's own build.yml against this branch on my fork. It runs the full build.ps1 - pnpm install, web-panel build, NuGet restore, then MSBuild of Wand-Enhancer.sln on windows-latest.

Result: success in ~4.2 min

https://github.com/divya0795/Wand-Enhancer/actions/runs/30205235457

That satisfies "your code compiles without errors" from CONTRIBUTING.md. The manual-testing-against-WeMod step remains outstanding on my side, as flagged in the PR description.

GetFileIntegrity treated every Stream.Read result as a complete block:

    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 permitted to return fewer bytes than requested at any point, not
only at end of file. Each short read therefore produced an undersized entry in
integrity.blocks and shifted every boundary after it, so the resulting metadata
fails Electron's validateAsarIntegrity, which requires every block except the
last to be exactly blockSize. The whole-file hash stayed correct, so only
per-block validation was affected.

Accumulate reads into the block buffer and emit a hash only once a full block
is present, flushing the remainder after the loop. Reads target the tail of the
buffer, so no copying or extra allocation is involved.

The buffer is now also replaced when a caller supplies one shorter than
BLOCK_SIZE, since a partial buffer cannot produce a valid block either way.

Note StreamingHasher already implements this accumulate-then-emit rule
correctly and is what the main WriteFileSystem path uses, which is why packing
has not been visibly affected; the defect was confined to direct
GetFileIntegrity callers. Delegating to StreamingHasher was the obvious way to
remove the duplication, but its Append copies the caller's data into its own
block buffer, so sharing one array as both read target and block buffer would
alias. Fixing the loop in place keeps the copy-free read path.

Verified by simulating the read/boundary arithmetic against Electron's rule
across full reads, mid-file short reads, single-byte reads, ragged reads, exact
multiples, a sub-block file and an empty file: the new logic matches the
expected block layout in every case and preserves the whole-file hash, while
the previous logic diverges on every short-read scenario (for example 40 blocks
instead of 5 when reads return one byte at a time).
@divya0795
divya0795 force-pushed the fix/asar-integrity-partial-reads branch from 4a7fcb3 to 53c880d Compare July 26, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AsarSharp: GetFileIntegrity assumes full-block reads, producing invalid integrity.blocks on short reads

1 participant