From 53c880ddffc4f58987ce98db8dc25b8eefd45b48 Mon Sep 17 00:00:00 2001 From: divya0795 <12871391+divya0795@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:57:30 +0000 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfix(asar):=20emit=20integrity=20blocks?= =?UTF-8?q?=20on=20real=20block=20boundaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- AsarSharp/Integrity/IntegrityHelper.cs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/AsarSharp/Integrity/IntegrityHelper.cs b/AsarSharp/Integrity/IntegrityHelper.cs index f6c5302..051ca87 100644 --- a/AsarSharp/Integrity/IntegrityHelper.cs +++ b/AsarSharp/Integrity/IntegrityHelper.cs @@ -46,8 +46,10 @@ public static FileIntegrity CreatePlaceholder(long fileSize) public static FileIntegrity GetFileIntegrity(string path, byte[] reusableBuffer = null) { - bool ownBuffer = reusableBuffer == null; - if (ownBuffer) reusableBuffer = new byte[BLOCK_SIZE]; + // A block must be exactly BLOCK_SIZE for Electron to validate it, so a buffer + // that cannot hold a whole block is replaced rather than used as-is. + if (reusableBuffer == null || reusableBuffer.Length < BLOCK_SIZE) + reusableBuffer = new byte[BLOCK_SIZE]; using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, FileOptions.SequentialScan)) @@ -58,14 +60,28 @@ public static FileIntegrity GetFileIntegrity(string path, byte[] reusableBuffer ? (int)((fileStream.Length + BLOCK_SIZE - 1) / BLOCK_SIZE) : 0; var blockHashes = new List(estimatedBlockCount); + int blockFill = 0; int bytesRead; - while ((bytesRead = fileStream.Read(reusableBuffer, 0, reusableBuffer.Length)) > 0) + // Stream.Read may return fewer bytes than requested at any point, so reads + // accumulate into the block buffer and a hash is only emitted once a full + // block is present. Hashing each read directly would turn a short read into + // an undersized block and desynchronise every boundary after it. + while ((bytesRead = fileStream.Read(reusableBuffer, blockFill, BLOCK_SIZE - blockFill)) > 0) { - blockHashes.Add(ToLowerHex(blockHash.ComputeHash(reusableBuffer, 0, bytesRead))); - fileHash.AppendData(reusableBuffer, 0, bytesRead); + fileHash.AppendData(reusableBuffer, blockFill, bytesRead); + blockFill += bytesRead; + + if (blockFill == BLOCK_SIZE) + { + blockHashes.Add(ToLowerHex(blockHash.ComputeHash(reusableBuffer, 0, BLOCK_SIZE))); + blockFill = 0; + } } + if (blockFill > 0) + blockHashes.Add(ToLowerHex(blockHash.ComputeHash(reusableBuffer, 0, blockFill))); + return new FileIntegrity { Algorithm = ALGORITHM,